什么是词法作用域?
在之前讲过,我们平常写代码的时候,创建一个变量和方法的时候在其书写的位置(所在环境)会形
成一个作用域,即为词法作用域,该作用域中的属性和方法只能在当前环境内使用。
闭包
最简单的一个闭包实例:
function fun(){ var a = 2; function bar(){ return a; } return bar; } var baz = fun(); console.log(baz()); //2
上面例子中定义了一个函数fun,函数内有声明了一个变量a和函数bar,代用fun的时候return出函数bar。
由于作用域的规则,函数bar内可以使用变量a,当调用函数fun时,return出函数bar并赋值给baz,这个
时候实际上baz引用了内部函数bar,也在外边访问到了函数fun内的变量a,形成闭包。闭包的一大特性
就是将函数内部变量通过闭包让内部函数在其词法作用域外被执行,这个时候有用bar被baz引用,而变量
a又被bar引用,所以浏览器回收机制不会回收fun中的作用域。
其他闭包场景:
//将函数内部嵌套函数作为参数传给外部函数 function foo(){ var a = 2; function bar(){ return a++; } //形成闭包 baz(bar); } function baz(fn){ console.log(fn(),fn(),fn()); //2,3,4 } foo(); //间接传递函数 var fn; function foo(){ var a = 2; function bar(){ return a; } //将内部函数保存到全局作用域,间接传递内部函数,形成闭包 fn = bar; } function baz(){ console.log(fn()); } foo(); baz(); //2 baz(); //3 baz(); //4
setTimeout中的闭包:
function fn(msg){ window.setTimeout(function timer(){ console.log(msg); },1000); } fn(‘hello world‘);
解析上面中的例子:将一个内部函数(这里是timer)传递给setTimeout作为第一个参数,根据词法作用域,timer函
数具有涵盖函数fn作用域的闭包,so,timer保有对变量msg的一引用。
1s后函数timer执行时,fn内的作用域依然不会消失(回收),timer依然保有fn作用域的闭包。
深入引擎内部原理,内置的工具函数setTimeout,持有对一个参数的引用,这个参数可以叫任何名字,引擎会调用这个函数,在上面的例子中为timer,而setTimeout所在词法作用域在这个过程中保持完整(回调执行过程中劫持了工具函数所在词法作用域,形成闭包,保证工具函数回调执行时可以访问到词法作用域中变量)。
循环和闭包:
要说明闭包,for循环时最常见的例子:
for(var i=0;i<3;i++){ //回调会在for循环完毕后才会执行 window.setTimeout(function(){ console.log(i); //3,3,3 },0); }
我们试图假设每次循环中,每个迭代在运行时都会给自己‘捕获’一个i的副本,但是根据作用域工作原理,尽管每次迭代都会分别定义一个函数,但是他们都封闭在一个共享的全局作用域里面,因此只有一个i。
使用闭包在每次迭代的时候创建一个封闭的作用域:
for(var i=0;i<3;i++){ //每次迭代创建一个作用域 (function(){ //劫持对当前循环中i的引用 var j = i; window.setTimeout(function(){ console.log(j); //1,2,3 },0); })(); } 或者 for(var i=0;i<3;i++){ (function(i){ window.setTimeout(function(){ console.log(i); //1,2,3 },0); })(i); }
模块:
考虑一下示例:
function model(){ var msg = ‘hello world‘; function alt(){ console.log(msg); } function reve(){ console.log(msg.split(‘‘).reverse()); } return { alt:alt, rev:reve }; } var mo = model(); mo.alt(); //‘hello world‘ mo.rev(); //["d", "l", "r", "o", "w", " ", "o", "l", "l", "e", "h"]
这个模式在JavaScript中称为模块,最常见的实现模块模式通常被称为模块暴露。
模块模式具备两个必要条件:
1,必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)。
2,封闭函数必须至少返回一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或修改私有的状态。
上面的例子每次调用外部封闭函数都会创建一个模块实例,如果只需要一个模块实例时:
var mo = (function(){ var msg = ‘hello world‘; function alt(){ console.log(msg); } function reve(){ console.log(msg.split(‘‘).reverse()); } return { alt:alt, rev:reve }; })(); mo.alt(); //‘hello world‘ mo.rev(); //["d", "l", "r", "o", "w", " ", "o", "l", "l", "e", "h"]
总结:当函数可以记住并访问所在的词法作用域,即使函数在当前词法作用域外调用,这是就产生了闭包。模块的两个主要特征:1,为创建内部作用域而调用一个包装函数。2,包装函数必须返回至少一个内部函数的引用,这样就会创建涵盖整个包装函数的内部作用域的闭包。
学习笔记=>《你不知道的JavaScript(上卷)》第五章:作用域闭包
原文:https://www.cnblogs.com/huangzhenghaoBKY/p/9818342.html