函数和循环闭包的理解
7.19
函数声明周期:
函数创建阶段:函数的内部属性形成,即函数的 [[scope]] 属性,包含了声明该函数的作用域链。
函数执行阶段:首先,进入执行上下文,预解析和提升变量,生成 VO,之后把 VO 放入函数作用域链的顶端,之后,开始执行代码。
1:函数定义:
组成: function 关键字
函数名称
一对圆括号,其中包含由 0 个或多个用逗号分隔的形参标识符
函数体 有 return 语句,返回 return 后面的内容,没有,返回 undefined
1:函数声明语句
function 关键字开头
2:函数定义表达式
不是 function 关键字开头
函数名称 (可选)
两者区别:例如:
- function one() {...
- };
- var two = function thr() {....
- }; //具名函数
- var two = function() {....
- }; //匿名函数
- (function four() {.....
- })(); //立即执行函数表达式
1: 函数名称标识符绑定在何处:函数声明的函数名可在函数体外、内使用,函数定义表达式的函数名(如果有)只能在函数体内使用
2: 函数是否有提示行为:函数声明存在提升行为,函数定义表达式没有。
2:函数调用
在函数名后面加上一对圆括号,其中包含由 0 个或多个用逗号分隔的实参
4 种方式调用函数
由于 JavaScript 中的 this 依赖于函数的调用方式所以,将 this 称为调用上下文(invocation context)
1:作为函数
作为普通函数进行调用时,this->window/undefined
2:作为方法
作为方法进行调用时,this-> 拥有该方法的(一级)对象
【
- 例如:
- function one() {....
- }
- var obj1 = {fun: one,
-
- };
- obj1.fun(); //通过obj上下文去调用;this->one
- var obj2 = {
- obj1: obj1,
- fun1: one,
- fun2: obj1.fun;
- };
- obj2.obj1.fun(); == obj2.fun1(); == obj2.fun2(); //这几种是等价的, 函数是引用的,本体只有一个,都是one的引用而已。
在 JavaScript 中,函数复制的不是副本,函数只有引用。
引用,不管有几个不同的函数名指向同一个函数,函数只有一个,不会有两个一样的函数。
】
3:作为构造函数
作为构造器进行调用时,this-> 新分配的对象
4:通过 call 和 apply 方法调用
this-> 第一个参数 /arguments[0]
3: 进入函数体
控制器进入函数执行环境,即创建函数执行环境(execution context/EC),把函数执行环境推入执行上下文栈(execution context stack / ECS)栈顶;
ECS 的栈底一直都是全局执行环境,直到这个网页退出
其中,执行环境的状态组件 / 执行环境的属性如下
(该图源于网络)
【 VO:变量对象,执行环境中 / 该作用域中其中定义的所有变量和函数都保存在这个对象中】
当创建 EC,进入函数执行环境,
1:this 值的绑定,根据函数的调用方式,确定 this 的值【这说明 this 的值是在函数执行的时候确定的】
2: 将函数的 AO 作为 VO,变量的提升 / 初始化执行环境的 AO/VO
【 函数只有 AO 活动对象(activation object /AO) ,所以,把函数的 AO 作为 VO,AO 在一开始只有 arguments 对象。并且在进入函数体时,arguments 的值都已经被实参(若有)赋值】
3: [[scope]]属性的值,就是在函数的内部属性 [[scope]] 的前端加上 AO/VO,即该值是一个栈,栈顶是 VO/AO
之后,就开始执行函数体
4:函数调用结束
如果该函数没有被引用,则被回收;若还有引用,则继续保留。
总体过程:
引擎对全局代码进行预解析,提升变量
普通变量,赋值 undefined
函数声明,整个函数代码块提升
函数有一个内部属性 [[scope]] 属性,该属性的值为一个作用域栈,从栈顶到栈底依次为:
外部函数的活动对象,外部函数的外部函数的活动对象,...., 全局作用域 。
引擎进入全局执行环境,将全局上下文推入执行上下文栈栈顶
根据 ecma 的描述:
1:将变量环境设置为全局环境
2:将词法环境设置为全局环境
3:将 this 绑定设置为全局对象
逐行 / 逐块执行代码,遇到函数调用,就进入函数体,创建执行环境,将函数执行环境放到执行上下文栈
栈顶,然后对执行环境的属性进行初始化赋值,之后,执行函数体代码。
执行完毕,将该函数执行环境推出执行上下文栈,函数调用结束;当前的 ECS 栈顶的执行环境即为当前运行
的执行环境(即全局执行环境或者当前正在执行的函数执行环境)。
闭包:
在计算机科学中,闭包是词法闭包,是引用了自由变量的函数。【自由变量:在作用域 A 中使用却不是在 A 中声明的变量】
闭包是有函数和其他相关引用环境组合而成的实体。
JavaScript 权威指南:
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中成为 "闭包";
在同一个作用域链中定义两个闭包,这两个闭包共享同样的私有变量或变量。
JavaScript 忍者秘籍:
闭包是一个函数在创建时允许该自身函数访问并操作该自身函数之外的变量时所创建的作用域。即闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明是的作用域内就行。
JavaScript 高级程序设计 3:
闭包只能取得包含函数中任何变量的最后一个值。 闭包所保存的是整个变量对象,而不是某个特殊的变量。
当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:
在后台执行环境中,闭包的作用域链包含着自己的作用域,包含函数的作用域和全局作用域;
通常,函数的作用域机器所有变量都会在函数执行结束后被撤销;
但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止 ;
JavaScript 核心概念及实践 :
如果一个函数需要在其父级函数返回之后留住对父级作用域链的话,就必须要为此建立一个闭包;
函数所绑定的是作用域本身,而不是该作用域中变量或变量当前所返回的值。
JavaScript 语言精粹:
一个内部函数除了可以访问自己的参数和变量,同时他也能自由访问把它嵌套在其中的复函数的参数与变量。
通过函数字面量创建的函数对象包含一个连到外部上下文的连接,这被称为闭包。
函数可以访问它被创建时所处的上下文环境,这杯称为闭包。
(我只是摘抄了一些我觉得比较有意思的关于闭包的说明)
我个人觉得闭包就是一段本该消失但没有消失的作用域链 / 作用域(只是个人理解)
图解循环中的闭包
1:
- function f() {
- //debugger; //调试
- var a = [],
- i;
- for (i = 0; i < 3; i++) {
- a[i] = function() {
- return i;
- }
- }
- return a;
- }
- var a = f();
- a[0](); //3
- a[1](); //3
- a[2](); //3
初始状态:
f() 调用结束后:
这是在 chrome 浏览器中截图的,可以验证上面的关系
f() 调用结束后,数组 a 内的 3 个函数指向的是同一个作用域,即共享的作用域,所以 3 个函数取到的自由变量 i 的值是一样的,都来自 f 函数,并且都是 3,这同时也说明,作用域保存的是整个作用域而不是某个变量
(__parent__ :用于连接作用域,连成作用域链,这个属性是自定义的,之前在网上看到,觉得挺好的)
:2:
- function f() {
- //debugger;
- var a = [],
- i;
- for (i = 0; i < 3; i++) {
- a[i] = (function(x) {
- return function() {
- return x;
- };
- })(i);
- }
- return a;
- }
- var a = f();
- a[0](); //0
- a[1](); //1
- a[2](); //2
初始状态:
因为和 1 没有什么区别,所以,图和 1 一样。
f() 调用结束后:
闭包的作用之一就是保存私有变量
x 是通过传参给匿名函数,这个匿名函数将 x 作为局部变量,附着在这个作用域上,保存;
3:
- function f() {
- //debugger;
- var a = [],
- i,
- m = 90;
- for (i = 0; i < 3; i++) {
- a[i] = (function(x) {
- return function() {
- console.log(m);
- return x;
- };
- })(i);
- }
- return a;
- }
- var a = f();
- a[0](); //0
- a[1](); //1
- a[2](); //2
初始状态:
同上;
f() 调用结束后:
有一点:这个函数和上面那个没有什么区别,但是,有很重要的一点就是,这里的数组函数的作用域有 3 个,而上面的只有 2 个;
我一直认为,上面那个也应该是 3 个,但是看到 chrome 调试出来只有 2 个;
然后,才有了第三个,我觉得应该是 3 个,只不过对第二个没用,所有就没显示出来。(额,是这样吗?)
以上就是书里的解释和自己的理解,如果有写错的地方,还请各位前辈指正,谢谢。
来源: http://www.cnblogs.com/senhaishusheng/p/7208582.html