闭包是什么?
闭包指的是那些引用了另一个函数作用域中变量的函数(通常是在嵌套函数中实现的)。
function f1() {
let a = 0;
return function() {//内部函数
console.log(a);
}
}
内部函数(匿名函数)中引用了外部函数的变量a,在这个内部函数被返回并在其他地方被使用后,仍然引用着这个变量。这是因为:内部函数的作用域链包含外部函数的作用域。
函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后就会销毁,内存中就只剩下全局作用域。闭包却不这样。
在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中。因此f1函数中,匿名函数的作用域链中实际上包含f1()的活动对象。
function createComparisonFunction(propertyName) {
return function(object1, object2) {
let value1 = object1[propertyName];
let value2 = object2[propertyName];
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
};
}
//创建比较函数
let compare = createComparisonFunction('name');
//调用函数
let result = compare({ name: 'Nicholas' }, { name: 'Matt' });
//解除对函数的引用,释放内存
compare = null;
解析:
1.在 createComparisonFunction()返回匿名函数后,它的作用域链被初始化为包含 createComparisonFunction()的活动对象和全局变量对象。这样,匿名函数就可以访问到 createComparisonFunction()可以访问的所有变量。
2.重点!!!:createComparisonFunction()的活动对象并不能在它执行完毕后销毁,因为匿名函数的作用域链中仍然有对它的引用。
3.在createComparisonFunction()执行完毕后,其执行上下文的作用域链会销毁,但它的活动对象仍然会保留在内存中,直到匿名函数被销毁后才会被销毁:
4.这里,创建的比较函数被保存在变量compareNames中。把compareNames设置为等于null会解除对函数的引用,从而让垃圾回收程序可以将内存释放掉。
this对象
如果内部函数没有使用箭头函数定义,则this对象会在运行时绑定到执行函数的上下文。
在全局函数中调用,this为window
作为某个对象的方法调用,this等于这个对象。
匿名函数不会绑定到某个对象,this会指向window。
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentityFunc() {
return function() {
return this.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'The Window'
函数在被调用时都会自动创建两个特殊变量:this和arguments。内部函数不能直接访问外部函数的这个两个变量。
但是,可以把this或arguments保存到闭包中可以访问的另一个变量中。
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentityFunc() {
let that = this;
return function() {
return that.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'My Object'
理解作用域链创建和使用的细节
调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用arguments和其他命名参数来初始化这个函数的活动对象。
作用域链其实是一个包含指针的列表,每个指针分别指向一个变量对象。
外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象、直到全局执行上下文才终止。
在函数执行时要从作用域链中查找变量,来读写值。
函数执行时,每个执行上下文都会有一个包含其中变量的对象。
(每一个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象上)。
- 全局上下文中的叫变量对象(会在代码执行期间始终存在)。
- 函数局部上下文中的叫活动对象(只在函数执行期间存在)
function compare(value1, value2) {
if (value1 < value2) {
return -1;
} else if (value1 > value2) {
return 1;
} else {
return 0;
}
}
let result = compare(5, 10);
在第一次调用compare()时,会为它创建一个包含arguments、value1和value2的活动对象。这个对象是作用域链上的第一个对象。而全局上下文的变量对象则是compare()作用域链上的第二个对象,其中包含this、result和compare。
函数执行上下文的作用域链中有两个变量对象:局部变量对象和全局变量对象。
详细过程
- 在定义compare()函数时,就会为它创建作用域链,预装载全局变量对象,并保存在内部[ [scope] ]中。
- 在调用这个函数时,会创建相应的执行上下文,然后通过复制函数的[ [scope] ]来创建其作用域链。
- 接着会创建函数的活动对象(用作变量对象)并将其推入作用域链的前端。