[JS] Topic - this is ”closure“
Ref: 为什么要用闭包?
背景
闭包是自带运行环境的函数
发哥是自带背景音乐的男人~
就是有权访问另一个函数作用域的变量的函数。
函数式编程的闭包,就是函数的调味包。
方便用户调用函数。不必为了维护繁杂的外部状态而烦恼。
我们常见的闭包形式就是:【基于静态作用域的一个编程技巧】
(1) a 函数套 b 函数,
(2) 然后 a 函数返回 b 函数,
这样 b 函数在 a 函数以外的地方执行时,依然能访问 a 函数的作用域。
其中 “b 函数在 a 函数以外的地方执行时” 这一点,才体现了闭包的真正的强大之处。
静态作用域
Ref: 浅谈静态作用域和动态作用域
Ref: JavaScript 词法、静态、动态作用域初级理解
若干需要关注点(概念)
1)执行环境(作用域)函数执行环境 变量作用域 2)函数作用域和声明提前 3)自由变量 4)词法作用域和静态作用域 5)动态作用域
1)执行环境(作用域)函数执行环境 变量作用域
每个执行环境都有一个与之关联的“变量对象(variable object)”,环境中定义的所有变量和函数都保存在这个对象中。我们编写的代码是无法访问这个对象的,但解析器在处理数据时会在后台使用它。
全局执行环境是最外围的一个执行环境,在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法来创建的。某个执行环境中的所有代码执行完毕后,该环境就会被销毁,保存在其中的所有变量和函数定义也随之销毁。
函数执行环境:每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。
作用域链:它的用途是保证对执行环境有权访问的所有变量和函数的有序访问,作用域链的前端,始终都是当前执行的代码所在的环境的变量对象。当代码在一个执行环境中执行时,会创建变量对象的一个作用域链(scope chain)。
2)声明提前
【不怎么理解原例子】
3)自由变量:在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
var x = 10; function fn(){ var b = 20; console.log(x + b); // <---- x 就是一个自由变量。 }
4)词法作用域和静态作用域:词法作用域等同于静态作用域,静态作用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系;【JavaScript 是使用词法作用域】
5)动态作用域规则依赖的是程序执行时的函数调用顺序。
静态作用域和动态作用域的一个重要区别在于:
* 静态作用域规则查找一个变量声明时依赖的是源程序中块之间的静态关系;
* 而动态作用域规则依赖的是程序执行时的函数调用顺序。
说的具体点,就是静态作用域查找的是距离当前作用域最近的外层作用域中同名标识符的声明,
而动态作用域则是查找最近的活动记录。
大多数现在程序设计语言都是采用静态作用域规则,
而只有为数不多的几种语言采用动态作用域规则,包括APL、Snobol和Lisp的早期方言。
而采用静态作用域的语言中,基本都是最内嵌套作用域规则:由一个声明引进的标识符在这个声明所在的作用域里可见,而且在其内部嵌套的每个作用域里也可见,除非它被嵌套于内部的对同名标识符的另一个声明所掩盖。为了找到某个给定的标识符所引用的对象,应该在当前最内层作用域里查找。如果找到了一个声明,也就可以找到该标识符所引用的对象。否则我们就到直接的外层作用域里去查找,并继续向外顺序地检查外层作用域,直到到达程序的最外嵌套层次,也就是全局对象声明所在的作用域。如果在所有层次上都没有找到有关声明,那么这个程序就有错误。
Ref: Javascript之旅——第十站:为什么都说闭包难理解呢?
例子一:
闭包变量:name
<script type="text/javascript"> //比较函数 function createComparison(propertyName) { return function ( obj1, obj2 ) {
var item1 = obj1[propertyName]; var item2 = obj2[propertyName]; if (item1 < item2) return -1; if (item1 > item2) return 1; if (item1 == item2) return 0; } } //比较name var compare = createComparison("name"); var result = compare( { name: "d", age: 20 }, { name: "c", age: 27 } );
</script>
注意对下面这个列表所表示的scope的检索范围优先级的理解。
例子二:
<script type="text/javascript"> var arr = new Array(); function Person() { for (var i = 0; i < 10; i++) { //要记住,这个属性函数申明,只有立即执行才会取scope属性,但这里不会立即执行,哈哈 var item = function () { return i; }; arr.push(item); } } Person(); for (var i = 0; i < arr.length; i++) { console.log(arr[i]()); }
</script>
给匿名函数再增加一个副本。
<script type="text/javascript"> var arr = new Array(); function Person() { for (var i = 0; i < 10; i++) { var item = function (num) { return function () { // 给每个匿名function一个副本就好了
return num;
} } (i); arr.push(item); } } Person(); for (var i = 0; i < arr.length; i++) { console.log(arr[i]()); }
</script>
延长作用域链:无论函数在哪里被调用或者如何被调用,它的词法作用域都只由函数被声明时所处的位置决定,所以,函数所能访问变量的"权限"只由声明位置决定,利用这个特性就可以利用闭包(将这个函数return出去,该函数声明位置不变,所以可访问变量的"权限"依然不变)使函数外部可以访问函数内部的变量。