Skip to content

闭包

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

闭包的特点包括:

  1. 可以访问和持有外部函数的变量和参数,即使外部函数已经执行完毕。
  2. 外部函数的变量在闭包中被保存,形成了一个闭包环境。
  3. 闭包可以在外部函数的作用域之外被调用,延长了变量的生命周期。
  4. 闭包可以访问不同实例中的不同变量,每个闭包都有独立的引用环境。

通过使用闭包,我们可以创建私有变量、实现函数柯里化、实现模块化等功能。闭包在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的作用域链

image-20230824181323917

this对象的问题

​ 匿名函数在这种情况下不会绑定到某个对象,这就意味着this会指向window,除非在严格模式下 thisundefined。并且,每个函数在被调用时都会自动创建两个特殊变量:thisarguments,所以内部函数永远不可能直接访问外部函数的这两个变量。但是可以改写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

改变后的作用域链

image-20230824183006102

Released under the MIT License.