定义
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式是在一个函数中创建另一个函数
函数是怎样创建的
js在执行代码前会对代码进行预处理。例如用var声明变量和用function声明函数,这一阶段同样包括声明提升的情况。当预处理发现有函数声明时,就会创建该函数。但预处理会跳过所有函数内部代码。只有调用函数时才,执行流进入函数内部才会进行处理。
函数创建时发生了什么
创建一个预先包含外部环境变量对象的作用域链,这个作用域链被保存在函数内部的[[scope]]属性中。
函数被调用时发生了什么
函数被调用,即执行流进入函数中
- 为函数创建一个执行环境,复制函数内部的[[scope]]属性中的对象构建起函数执行环境的作用域链。
- 使用arguments和其它命名参数的值来初始化函数的活动对象。
- 将函数的活动对象(在此作为变量对象使用)推入执行环境作用域链的前端。
如上例所示,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。在函数中访问变量时,会从作用域链中从本地活动变量一直搜索到全局变量对象。函数执行完毕后,局部活动对象被销毁,内存中仅保存全局执行环境和它的变量对象。
在函数内部创建函数形成闭包后,作用域链是怎样的
function outer(propertyAge) {
return function (obj1, obj2) {
var val1 = obj1[propertyAge];
var val2 = obj2[propertyAge];
if (val1 > val2) {
console.log(`${obj1.name}比较大`);
} else if (val1 < val2) {
console.log(`${obj2.name}比较大`);
} else {
console.log(`${obj1.name}与${obj2.name}一样大`);
}
}
}
var person1 = {
name: \'Nick\',
age: 27
};
var person2 = {
name: \'Jane\',
age: 25
};
var compare = outer(\'age\');
compare(person1, person2); //\'Nick比较大\'
从头开始捋:
1 创建outer函数时,将全局作用域的作用域链(即全局执行环境下的变量对象的指针)的副本加入到函数的内部的[[scope]]属性中。
2 调用outer函数,为outer函数创建一个执行环境,创建作用域链,复制函数内部的[[scope]]属性的变量对象的指针,把它塞进作用域链。
3 将outer函数的arguments对象和其它命名参数的值初始化为活动变量,作为当前局部作用域的变量对象,并把它塞进作用域链的前端。(此时的outer函数的作用域链拥有本地活动对象和全局变量对象的指针)
4 执行outer函数完毕,将内部匿名函数返回给全局变量compare。执行流进入到outer函数内时便会对局部作用域执行预处理(js预处理不会进入到函数内部,全局作用域预处理阶段outer函数连局部作用域都没有创建,当代码执行流进入到outer函数内部后才会创建匿名函数),内部函数因为函数声明提升会被直接创建(执行流进入到outer函数中时,匿名函数就被创建了)。执行创建函数的流程————将外部作用域的作用域链的副本存入到自己的[[scope]]属性中去。执行return语句后执行流会直接跳出当前作用域,outer函数已经完成使命,其作用域被直接销毁,作用域中保存的作用域链也会被直接被销毁。
5 outer函数的作用域和作用域链虽然被销毁了,但是对outer函数作用域链中保存的全局变量对象和outer函数本地活动对象的引用被复制保存到匿名函数中了(这些变量对象和活动对象被完好地保存在堆内存中,销毁的只是outer函数作用域链对它们的引用的指针),对于对象而言,只要引用存在就不会被销毁。
6 调用匿名函数,创建匿名函数的作用域,创建该作用域的作用域链,将[[scope]]属性中的作用域链副本塞进作用域链,使用arguments对象和其它命名参数的值初始化本地活动对象,并将本地活动对象视为变量对象,塞进匿名函数作用域链的最前端。此时的匿名函数作用域链按索引优先级包括了本地活动对象,outer函数的活动对象,全局变量对象。也因此匿名函数可以访问到在outer函数和全局作用域中定义的变量,函数以及参数。完成闭包操作。
需要注意的地方
- js预处理不会进入函数。当执行流进入函数创建局部作用域后才会对局部作用域进行预处理,才会创建匿名函数。
- 创建函数时只会做一件事,保存外部作用域的副本到函数的[[scope]]内部属性中。
- 调用函数执行的操作:
1.创建局部作用域。
2.复制[[scope]]中保存的作用域链。
3.初始化本地活动对象并塞到作用域链最前端(索引最优先)