作用域 [[scope]]:
函数创建时 ,生成的一个JS内部的隐式属性
函数存储【作用域链】的容器
作用域链:
AO - 函数的执行期上下文
GO - 全局的执行期上下文
函数执行完成以后,AO会被销毁,再执行会重新创建一个新的AO
全局执行的前一刻 GO -> 函数声明已经定义
🌈 案例 1 2 3 4 5 6 7 8 9 function a ( ) { function b ( ) { var b = 2 ; } var a = 1 ; b(); } var c = 3 ;a();
当a函数被定义时(每一个函数被定义的时候【还没被执行】,[[scope]]就存在了,作用域链第一项此刻已经被保存了,是GO):
当a函数被执行时(前一刻,也就是预编译阶段)(生成了自己的AO,并且把AO存在了作用域链的最顶端,把GO挤下去了)
a函数执行,内部b函数立即被定义
当b函数被执行时(前一刻,预编译),把自身AO排顶端,先前的被挤到下边
b函数执行完毕以后,b的ao销毁;b的scope回归b被定义时的状态
b函数执行完毕的同时,也代表a函数执行完毕。此时a的AO销毁,由于a的AO中包含b,而且被销毁,所以 b的[[scope]]也不存在了
分析作用域链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function a ( ) { function b ( ) { function c ( ) { } c(); } b(); } a(); a定义: a.[[scope]] -> 0 : GO a执行: a.[[scope]] -> 0 :a的AO 1 :GO b定义: b.[[scope]] -> 0 :a的AO 1 :GO b执行: b.[[scope]] -> 0 :b的AO 1 :a的AO 2 :GO c定义: c.[[scope]] -> 0 :b的AO 1 :a的AO 2 :GO c执行: c.[[scope]] -> 0 : c的AO 1 :b的AO 2 :a的AO 3 :GO c结束:c.[[scope]] -> 0 :b的AO 1 :a的AO 2 :GO b结束:b.[[scope]] -> 0 :a的AO 1 :GO a结束:c.[[scope]] 销毁 b.[[scope]] 销毁 a.[[scope]] -> 0 : GO
🌈 函数默认值带来的影响 事实上,如果一些 (至少有一个)参数具有默认值,ES6 会定义一个中间作用域 用于存储参数,并且这个作用域与函数体 的作用域不共享 。这是与 ES5 存在主要区别的一个方面。我们用例子来证明:
1 2 3 4 5 6 7 8 9 10 11 12 13 var x = 1 ; function foo (x, y = function () { x = 2 ; } ) { var x = 3 ; y(); console .log(x); } foo(); console .log(x);
在这个例子中,我们有三个作用域:全局环境,参数环境,以及函数环境:
1 2 3 foo : -> { x : 3 } 参数 : -> { x : undefined , y : function ( ) { x = 2 ; } } 全局 : -> { x : 1 }
我们可以看到,当函数 y
执行时,它在最近的环境(即参数环境)中解析 x
,函数作用域对其并不可见。
来源:https://juejin.cn/post/6844903839280136205#heading-5
总结
只要函数被定义,就生成作用域(scope)和相应的作用域链(scope chain),并把GO放进去
只要函数被执行的那一刻(准确的说应该是执行前的预编译阶段),就生成AO,然后把自身的AO放进作用域链,并自身排首位,把之前的作用域们(其他AO和GO)依次往下挪
函数执行完毕,销毁自身AO;此时自身作用域回归被定义时的状态
闭包 案例 1 2 3 4 5 6 7 8 9 10 11 12 function test1 ( ) { function test2 ( ) { var b = 2 ; console .log(a); } var a = 1 ; return test2; } var c = 3 ;var test3 = test1();test3();
当test1被定义时:(此时全局的test3是test1内部的test2)
当test1被执行时,test2被定义,所以此刻他们俩的作用域相同
当test1执行完毕时,本来要销毁自身的AO,但由于test1返回了test2,导致test2被全局GO引用,所以test2的作用域链中的test1的AO此刻还不能销毁,因此此AO仍然存在,没有被销毁,只是test1的作用域链中的自身AO连接切断了。 test1的AO不能被销毁的理由是,test2现在在全局函数,而且test2的作用域链中还攥着test1的ao,所以test1的ao不能被销毁。由于垃圾回收的标记清除,导致test2是全局的,还被引用着,不能被销毁。
当test3执行时,
当test3执行完毕后,
总结 当内部函数被返回到外部并保存时 ,一定会产生闭包。闭包会导致原来的作用域链不释放 。过度的闭包可能会导致内存泄漏(因为常驻内存),或加载过慢 。
for循环的i值打印 代码执行后的结果是什么,为什么?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 function test ( ) { var arr = []; for (var i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { document .write(i + ' ' ); } } return arr; } var myArr = test();for (var j = 0 ; j < 10 ; j++) { myArr[j](); } function test ( ) { var arr = []; var i = 0 ; for (;i < 10 ;) { arr[i] = function ( ) { document .write(i + ' ' ); } i++; } return arr; }
🌈 分析 当此 for 循环执行完后,arr 中存储了 10 个匿名函数,这 10 个匿名函数还没执行,所以先不看匿名函数里边的内容。 由于 test 执行完毕后把 arr 返回了出去形成了闭包,所以每个匿名函数其实都保留了外层test函数的AO。 所以每个匿名函数就能拿到 test AO 中的变量 i ,而 i 在 for 循环结束后已经累加为了 10 所以最终在循环执行每个匿名函数时,都拿到的是最终的 i 的值,也就是 10
如何改造才能使其打印 0 - 9 在外层使用立即执行函数包裹 1 2 3 4 5 6 7 8 function test ( ) { for (var i = 0 ; i < 10 ; i++) { (function ( ) { document .write(i + ' ' ); })(); } } test();
直接函数尾部加括号,因为前边有 arr[i] = ,是个表达式 1 2 3 4 5 6 7 8 9 10 11 function test ( ) { var arr = []; for (var i = 0 ; i < 10 ; i++) { arr[i] = function ( ) { console .log(i + ' ' ); }(); } return arr; } console .log(test());
循环立即执行函数+把传参传进匿名函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 function test ( ) { var arr = []; for (var i = 0 ; i < 10 ; i++) { (function (j ) { arr[j] = function ( ) { document .write(j + ' ' ); } })(i); } return arr; } var myArr = test();for (var j = 0 ; j < 10 ; j++) { myArr[j](); } function test ( ) { var arr = []; for (var i = 0 ; i < 10 ; i++) { arr[i] = function (num ) { document .write(num + ' ' ); } } return arr; } var myArr = test();for (var j = 0 ; j < 10 ; j++) { myArr[j](j); }
window 和 return return 形成闭包 1 2 3 4 5 6 7 8 9 10 11 12 function test ( ) { var a = 1 ; function add1 ( ) { a++; console .log(a); } return add1; } var add = test();add(); add(); add();
window 形成闭包 1 2 3 4 5 6 7 8 9 10 11 12 function test ( ) { var a = 1 ; function add1 ( ) { a++; console .log(a); } window .add = add1; } test(); add(); add(); add();
1 2 3 4 5 6 7 8 9 10 11 var add = (function ( ) { var a = 1 ; function add ( ) { a++; console .log(a); } return add; })(); add(); add(); add();
1 2 3 4 5 6 7 8 9 10 11 (function ( ) { var a = 1 ; function add ( ) { a++; console .log(a); } window .add = add; })(); add(); add(); add();
JS 插件写法 1 2 3 4 5 6 7 8 (function ( ) { function Test ( ) { } Test.prototype = {} window .Test = Test; })(); var test = new Test();