闭包
INFO
闭包是指在函数内部创建的函数,它可以访问并持有其所在作用域内的变量,即使在其所在作用域外部被调用时仍然有效。换句话说,闭包是函数以及其相关的引用环境的组合体。
闭包通常在以下情况下出现:
- 在一个函数内部定义了另一个函数,并且内部函数引用了外部函数的变量。
- 内部函数被返回,或者被传递给其他函数作为参数,从而在外部函数的作用域之外被调用。
js
// 举个🌰
function outerFunction() {
let outerVariable = 'Kevin';
return function innerFunction() {
console.log(outerVariable);
}
}
let closure = outerFunction();
closure(); // Kevin
// 举个🌰
function outerFunction() {
let outerVariable = 'Kevin';
return function innerFunction() {
console.log(outerVariable);
}
}
let closure = outerFunction();
closure(); // Kevin
TIP
闭包的特点包括:
- 可以访问和持有外部函数的变量和参数,即使外部函数已经执行完毕。
- 外部函数的变量在闭包中被保存,形成了一个闭包环境。
- 闭包可以在外部函数的作用域之外被调用,延长了变量的生命周期。
- 闭包可以访问不同实例中的不同变量,每个闭包都有独立的引用环境。
通过使用闭包,我们可以创建私有变量、实现函数柯里化、实现模块化等功能。闭包在JavaScript中是一个非常强大和常用的特性,它使得函数可以拥有状态并且能够保持对其相关变量的访问。
js
const myModule = (function() {
let privateVar = "private Variable";
function printPrivate() {
console.log(privateVar);
}
return {
printPublic: function() {
printPrivate();
},
publicVar: 'public Var'
}
})();
/**
* 立即执行函数(Immediately Invoked Function Expression,IIFE)在闭包模块化中的使用是为了创建一个独立的作用域。
* 为什么要使用立即执行函数呢?主要有以下几个原因:
* 1. 创建独立的作用域:立即执行函数可以创建一个私有的作用域,避免全局命名空间污染和变量冲突。
* 2. 封装实现细节:通过将模块的实现细节封装在立即执行函数内部,可以隐藏模块的内部实现,只暴露需要对外公开的接口。
* 3. 避免变量提升问题:立即执行函数可以防止变量和函数在外部被意外访问或修改,保持模块的封装性。
*/
myModule.printPublic(); // "private Variable"
console.log(myModule.privateVar, myModule.publicVar); // undefined "public Variable"
const myModule = (function() {
let privateVar = "private Variable";
function printPrivate() {
console.log(privateVar);
}
return {
printPublic: function() {
printPrivate();
},
publicVar: 'public Var'
}
})();
/**
* 立即执行函数(Immediately Invoked Function Expression,IIFE)在闭包模块化中的使用是为了创建一个独立的作用域。
* 为什么要使用立即执行函数呢?主要有以下几个原因:
* 1. 创建独立的作用域:立即执行函数可以创建一个私有的作用域,避免全局命名空间污染和变量冲突。
* 2. 封装实现细节:通过将模块的实现细节封装在立即执行函数内部,可以隐藏模块的内部实现,只暴露需要对外公开的接口。
* 3. 避免变量提升问题:立即执行函数可以防止变量和函数在外部被意外访问或修改,保持模块的封装性。
*/
myModule.printPublic(); // "private Variable"
console.log(myModule.privateVar, myModule.publicVar); // undefined "public Variable"
js
const curry = function(fn) {
return function curried(...args) {
// 如果当前柯里化函数参数个数多于或等于fn的参数个数(fn.length),说明已经成功柯里化
if(args.length >= fn.length) {
// 为fn入参
return fn.apply(this, args);
} else {
return function(...args2) {
// 否则,链接上一次的参数与新的参数,并且继续调用curried函数
return curried.apply(this, args.concat(args2));
}
}
}
}
const add = function(a, b, c) {
return a+b+c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2,3)); // 6
const curry = function(fn) {
return function curried(...args) {
// 如果当前柯里化函数参数个数多于或等于fn的参数个数(fn.length),说明已经成功柯里化
if(args.length >= fn.length) {
// 为fn入参
return fn.apply(this, args);
} else {
return function(...args2) {
// 否则,链接上一次的参数与新的参数,并且继续调用curried函数
return curried.apply(this, args.concat(args2));
}
}
}
}
const add = function(a, b, c) {
return a+b+c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2,3)); // 6
作用域链与this指向
INFO
一个实例
js
window.name = 'venki';
function f1() {
return function {
console.log(this.name, this);
}
}
const obj = {
name: 'kevin';
}
obj.f1 = f1;
obj.f1()(); // 'venki', window
window.name = 'venki';
function f1() {
return function {
console.log(this.name, this);
}
}
const obj = {
name: 'kevin';
}
obj.f1 = f1;
obj.f1()(); // 'venki', window
obj.f1的作用域链
this对象的问题
匿名函数在这种情况下不会绑定到某个对象,这就意味着this
会指向window
,除非在严格模式下 this
是 undefined
。并且,每个函数在被调用时都会自动创建两个特殊变量:this
和 arguments
,所以内部函数永远不可能直接访问外部函数的这两个变量。但是可以改写f1
,存储f1
中的this
对象为变量that
,返回的匿名函数改为that.name
,就可以从闭包中输出obj.name
。
js
// 改写后
window.name = 'venki';
function f1() {
const that = this;
return function {
console.log(that.name);
}
}
const obj = {
name: 'kevin',
}
obj.fun = f1;
obj.fun()(); // 'kevin'
// 改写后
window.name = 'venki';
function f1() {
const that = this;
return function {
console.log(that.name);
}
}
const obj = {
name: 'kevin',
}
obj.fun = f1;
obj.fun()(); // 'kevin'
TIP
查找对象的时候,会顺着作用域链从下往上查找,当当前的作用域找不到对应的对象,会继续找下一个作用域,直到找到为止。因此,闭包内的对象找不到that,就会去到f1里面找that,此时的that存储的即是f1的this,即obj