您的位置:首页 > 新闻 > 会展 > 有限责任公司怎么注册_龙岩网站优化费用_竞价托管多少钱一个月_广州推广排名

有限责任公司怎么注册_龙岩网站优化费用_竞价托管多少钱一个月_广州推广排名

2024/10/9 3:26:43 来源:https://blog.csdn.net/linzhangmeidi/article/details/142467599  浏览:    关键词:有限责任公司怎么注册_龙岩网站优化费用_竞价托管多少钱一个月_广州推广排名
有限责任公司怎么注册_龙岩网站优化费用_竞价托管多少钱一个月_广州推广排名

1. 什么是闭包

先说结论:当函数可以记住并访问所在的词法作用域变量时,就产生了闭包,即使函数是在当前词法作用域之外执行
思考以下代码:

function foo() {var a = 2;function bar() {console.log(a);}bar();
}
foo();

函数bar可以访问外部作用域中的变量a,这就是闭包吗?
严格来说这并不是,在上面代码片段中,函数bar具有一个覆盖foo作用域的闭包。也可以认为bar封闭在了foo的作用域中,但是通过这种定义无法明白这个代码片段中的闭包是如何工作的。
下面一段代码清晰的展示了闭包

function foo() {var a = 2;function bar() {console.log(a);}return bar;
}
const baz = foo();
baz(); // 2 这里输出的结果2就是闭包的效果

分析一下:在foo正常执行后,其返回值赋值给变量baz并调用baz,实际上只是通过不同的标识符引用调用了内部的函数barbar肯定可以被正确执行,但是在这个例子中bar是在自己定义的词法作用域意外的地方执行的。
foo被执行后通常会期待 foo的整个内部作用域会被销毁,因为垃圾回收器会释放不在使用的内存空间,由于看上去foo的内部不会在被使用,所以很自然的认为垃圾回收器会将foo进行回收。但是闭包的神奇之处就在于它可以阻止这个行为的发生。事实上内部作用域依然存在,因为bar对内部的作用域依然进行着引用,保证bar在任何时候可以正常的执行。
bar依然保持着对该作用域的引用,这个引用就叫做闭包。因此在baz即将执行的时候它依然可以正常的访问到变量a
无论以何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。
再来看个例子:

function foo() {var a = 2;function baz () {console.log(a); // 2}bar(baz);
}
function bar(fn) {fn(); // 这里是闭包
}
foo();

来吧好好分析一下这段代码:
先来一段看似正确的分析:
首先在foo被调用的时候,foo内部创建了baz函数,随即baz函数被当做bar函数的参数传递了进去,最终在bar函数内部完成了对baz函数的调用。对于参数fn其实就是函数bazfn一直保持着对baz的引用,并可以在非baz的词法作用域的任意位置进行调用。
上述说法并不正确,回顾一下闭包的定义:当函数可以记住并访问所在的词法作用域变量时,就产生了闭包。记住是访问词法作用域的变量。
正确的解析为:内部函数baz传递给bar,当调用这个内部函数时(fn),baz可以正常的访问到foo作用域的a,这时闭包就产生了。baza的引用就是闭包。
再来一个贴合实际的例子:

function wait(message) {setTimeout(function timer() {console.log(message);}, 1000);
}
wait('Hello World!');

在实际开发中类似这种代码肯定写过很多了,将一个内部函数(timer)传递给setTimeout。timer具有覆盖wait作用域的闭包,对变量message保留着引用,因此wait执行1000毫秒后,它的内部作用域并不会消失。
再来归总一下:当函数当做一级的值类型到处传递,就非常容易产生闭包(如果函数对其所在的词法作用域有变量的引用)。

2. 循环闭包

对于循环闭包,for循环是一个很好的例子。

for(var i = 1; i <= 5; i++) {setTimeout(function timer () {console.log(i);}, i * 1000);
}

来分析一下这段代码,一眼看上去应该输出什么呢?可能会觉得每隔一秒输出当时i的值:1-5。但是这段代码最终的输出结果是每隔一秒输出一次6,一共输出56。这是为什么呢?6又是从哪里来呢?下面来分析一下。
这个循环的终止条件式i > 5也就是i = 6的时候,这里容易忽略的是:只有当循环执行完之后,setTimeout回调函数才开始执行,即使setTimeout执行的延迟时间为0即setTimeout(function timer(){...}, 0);。所有的回调函数是在循环结束时才开始执行,而这时i = 6。又因为每次回调都是拿i的值6,所以最终的输出结果就是56
思考一下:这到底是因为什么导致代码的行为跟实际想要的结果有偏差呢。
原因是:我们会自然的认为每个迭代在运行的时候会把自己要用的i保存下来。但是根据作用域的工作原理,实际情况是尽管循环中的五个函数都是在各自的迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,实际上它们共享一个i
那么如何解决这个问题呢?在前面说过立即执行函数会创建新的作用域,用立即执行函数可以解决这个问题吗?

for(var i = 1; i <= 5; i++){(function() {setTimeout(function timer(){console.log(i);}, i * 1000);})();
}

分析一下这段代码:首先这段代码并不能解决问题,虽然立即执行函数创建了新的词法作用域,但是这个词法作用域是空的,最终还是会根据作用域链找到最终外部作用域中的i = 6。这时候需要在立即执行函数的词法作用域中来保存i值。

for(var i = 1; i <= 5; i++){(function () {var j = i;setTimeout(function timer() {console.log(j);}, j * 1000);})();
}

上面这段代码就可以解决问题了,每次在迭代内使用立即执行函数都会生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代的内部,在每个迭代中都会有一个具体的正确的值来提供访问。
再来思考一下,使用立即执行函数在每次迭代时会创建新的作用域,换句话说,每次迭代我们都需要一个新的块级作用域来保存本次迭代i的值,在前面介绍过let声明,它可以劫持块作用域,并且在这个块作用域中声明变量。

for(var i = 1; i <= 5; i++){let j = i;setTimeout(function timer(){console.log(j);}, j * 1000);
}

本质上这是将一个块转成一个可以被关闭的作用域。它会劫持当前for循环大括号声明的块。
这里多说一下:for循环头部使用let声明还会有一个特殊行为:变量在循环过程中不止被声明一次,每次迭代都会声明。随后每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
最终修改后的代码

for(let i = 1; i <= 5; i++){setTimeout(function timer(){console.log(i);}, i * 1000);
}

这样代码的执行结果跟我们想让它执行的结果就一致了,这就是块作用域跟闭包结合使用的一个例子。

3. 模块

模块是利用闭包的一个典型事例。

function foo() {var s1 = 'module';var s2 = 'JavaScript';function bar() {console.log(s1);}function baz() {console.log(s2);}
}

上面那段代码并没有明显的闭包,只有两个私有变量和内部函数,它们的词法作用域(就是闭包)也就是foo的内部作用域。
思考一下代码

function FooModule() {var s1 = 'fmodule';function foo(){console.log(s1);}return {foo}
}
var fm = FooModule();
fm.foo(); // module

这种模式在JavaScript中被称为模块。最常见的实现模块的方式通常被称为模块暴露。
下面来分析一下,FooModule只是一个函数,必须要通过调用来创建一个模块实例。如果不执行,内部的作用域和闭包都无法被创建,FooModule返回一个对象字面量来表示对象。这个返回的对象中含有对内部函数而不是内部数据变量的引用。保持内部数据变量是隐藏且私有的状态。
模块模式的两个必要条件:
1、必须有外部的封闭函数,该函数必须至少被调用一次。
2、封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。
一个具有函数属性的对象本身并不是真正的模块。一个从函数调用所返回的,只有数据属性而没有闭包函数的对象并不是真正的模块。
用另一种方式创建模块

var foo = (function FooModule(){var s1 = 'module';function bar () {console.log(s1);}return {bar}
})();
foo.bar(); // module

将模块函数转为立即执行函数,模块也是普通的函数,因此也可以传递参数。

function FooModule(s1){function bar(){console.log(s1);}return {bar}
}
var foo1 = FooModule('foo1');
var foo2 = FooModule('foo2'); 
foo1.bar(); // foo1
foo2.bar(); // foo2

模块模式另一个强大的用法是命名将要作为公共API返回的对象。
看下面代码:

var foo = (function FooModule(id){function change() {publicAPI.identify = identify2;}function identify1(){console.log(id);}function identify2(){console.log(id.toUpperCase());}var publicAPI = {change,identify: identify1}return publicAPI;
})('foo module');
foo.identify(); // foo module
foo.change();
foo.identify(); // FOO MODULE

通过模块实例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改他们的值。

3.1现在模块机制

大多数模块依赖加载器/管理器本质上都是将这种模块定义封装到一个友好的API。这里简单的介绍一下。

var MyModules = (function Manager() {var modules = {};function define(name, deps, impl){for(var i = 0; i < deps.length; i++) {deps[i] = modules[deps[i]];}modules[name] = impl.apply(impl, deps);}function get(name) {return modules[name];}return {define: define,get: get}
})();

这段的核心是modules[name] = impl.apply(impl, deps)。为了模块的定义引入了包装函数,并且返回值,储存在一个根据名字来管理的模块列表中。
下面来使用一下

MyModules.define('bar', [], function() {function hello(s1){return '这是:' + s1;}return {hello: hello}
});
MyModules.define('foo', ['bar'], function (bar){var s2 = 's2';function baz() {console.log(bar.hello(s2).toUpperCase());}return {baz: baz}
});
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
console.log(bar.hello('s2')); // 这是:s2
foo.baz(); // 这是:S2

这段代码可能一下子不好理解,好好捋一下吧。

3.2 未来模块机制

ES6中为模块增加了一级语法支持。在通过模块系统进行加载时,ES6会将文件当做独立的模块来处理。每个模块都可以导入其他模块或特定的API成员,也可以导出自己的API成员。


基于函数的模块并不是一个能被静态识别的模式,它们的API语义只有在运行时才会被考虑进来,因此可以在运行时修改一个模块的API。
但是ES6模块API是静态的。因此可以在编译期检查对导入模块的API成员的引用是否真是存在。


ES6模块没有“行内”格式,必须被定义在一个文件中(一个文件一个模块)。浏览器或引擎有一个默认的“模块加载器”可以在导入模块时加载模块文件(下面是伪代码)。

// bar.js
function hello(who){return '你是:' + who;
}
export hello;
// foo.js
import hello from 'bar.js'
function baz () {console.log(hello('apple').toUpperCase());
}

模块文件中的内容会被当做好像包含在作用域闭包中一样来处理,就和前面介绍的函数闭包模块一样。

4. 小结

当函数可以记住并访问所在的词法作用域变量时,就产生了闭包,即使函数是在当前词法作用域之外执行。如果没有认清闭包,也不了解它的工作原理,在使用的过程中就很容易犯错,比如在循环中,学习完后可以发现我们的代码中闭包随处可见。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com