Skip to content
On this page

闭包介绍

简要定义

  • 由函数以及声明该函数的词法环境组合而成的。

核心原理

  • 将所需的数据,构成 Closure 对象储存在堆(Heap)上,然后函数引用这个对象,不会随着函数调用结束而被回收。

示例

js
function once(fn) {
    let done = false; // 标记传入的函数是否被执行过
    return function () {
        // 函数作为返回值
        if (!done) {
            done = true;
            return fn.apply(this, arguments); // arguments指return的匿名函数的传参
        }
    };
}

let pay = once(function (money) {
    console.log(`支付:${money} RMB`);
});

pay(5);
pay(5);
function once(fn) {
    let done = false; // 标记传入的函数是否被执行过
    return function () {
        // 函数作为返回值
        if (!done) {
            done = true;
            return fn.apply(this, arguments); // arguments指return的匿名函数的传参
        }
    };
}

let pay = once(function (money) {
    console.log(`支付:${money} RMB`);
});

pay(5);
pay(5);

解析:当 once()被执行后,它会从执行栈上移除,但 once 函数中定义的 done 变量正被 pay 函数中引用,因此不能释放堆中 done 的内存。

优点

  • Closure 对象是私有的,不影响全局,可以用作私有变量。

开发者通常使用下划线作为私有变量的前缀, js 没有私有变量的概念。我们可以用闭包来模拟私有变量。

js
// 需求:实现一个计数器,不依赖于外部变量。每次执行打印一个加1的数字
function count() {
    let num = 0;
    function add() {
        num++;
        return num;
    }
    function reset() {
        num = 0;
        return num;
    }
    return {
        add,
        reset,
    };
}

let { add, reset } = count();

console.log(add());
console.log(add());
console.log(reset());
console.log(add());
console.log(add());
// 需求:实现一个计数器,不依赖于外部变量。每次执行打印一个加1的数字
function count() {
    let num = 0;
    function add() {
        num++;
        return num;
    }
    function reset() {
        num = 0;
        return num;
    }
    return {
        add,
        reset,
    };
}

let { add, reset } = count();

console.log(add());
console.log(add());
console.log(reset());
console.log(add());
console.log(add());
  • 可以外部读取局部变量
  • 让变量的值始终保持在内存中

缺点

  • 处理速度和内存消耗方面对脚本的性能有负面的影响
  • 由于数据被 Closure 对象引用,无法被释放回收,也容易出现内存泄漏的问题

用途

  • 解决命名空间污染问题(立即执行函数 + 闭包)

    闭包就是能够读取其他函数内部变量的函数,局部变量无法共享和长久的保存,而全局变量可能造成变量污染,造成数据的不安全。 因此我们希望有一种机制,既可以长久的保存变量,又不会造成全局污染保证数据的安全。

js
let count = 0; // 全局会造成污染
function demo() {
    count++;
    console.log(count);
}
demo();
demo();

let demo1 = (() => {
    // 闭包确实可以解决污染的问题
    let count = 3;
    return () => {
        count++;
        console.log(count);
    };
})();
demo1();
let count = 0; // 全局会造成污染
function demo() {
    count++;
    console.log(count);
}
demo();
demo();

let demo1 = (() => {
    // 闭包确实可以解决污染的问题
    let count = 3;
    return () => {
        count++;
        console.log(count);
    };
})();
demo1();
  • 模拟类的私有属性或方法、
js
// 基于functin 形式模拟
var Person = function (name) {
    var _name = name;
    this.getName = function () {
        return _name;
    };
    this.setName = function (str) {
        _name = str;
    };
};
var person = new Person('hugh');

person.name; // undefined, name是Person函数内的变量,外部无法直接访问
person.getName(); // 'hugh'
person.setName('test');
// 存在问题:需要为每个变量定义getter和setter,当你定义了getter,外部也可以通过这个getter来获取私有变量。
// 基于functin 形式模拟
var Person = function (name) {
    var _name = name;
    this.getName = function () {
        return _name;
    };
    this.setName = function (str) {
        _name = str;
    };
};
var person = new Person('hugh');

person.name; // undefined, name是Person函数内的变量,外部无法直接访问
person.getName(); // 'hugh'
person.setName('test');
// 存在问题:需要为每个变量定义getter和setter,当你定义了getter,外部也可以通过这个getter来获取私有变量。

我们知道,要实现私有属性,只要外部无法知道这个属性名,只有内部知道属性名, 就可以做到外部无法访问的特性,基于 ES6 的新语法 symbol 和 weakMap,我们可以去实现这个能力。

js
// 基于symbol 形式模拟
var Person = (function () {
    const _name = Symbol('name');
    class Person {
        constructor(name) {
            this[_name] = name;
        }
        get name() {
            return this[_name];
        }
    }
    return Person;
})();

let person = new Person('hugh');
person.name; // hugh
// 基于symbol 形式模拟
var Person = (function () {
    const _name = Symbol('name');
    class Person {
        constructor(name) {
            this[_name] = name;
        }
        get name() {
            return this[_name];
        }
    }
    return Person;
})();

let person = new Person('hugh');
person.name; // hugh
js
// 基于WeakMap
var Person = (function () {
    const _name = new WeakMap();
    class Person {
        constructor(name) {
            _name.set(this, name);
        }
        get name() {
            return _name.get(this);
        }
    }
    return Person;
})();

let person = new Person('hugh');
person.name; // hugh
// 基于WeakMap
var Person = (function () {
    const _name = new WeakMap();
    class Person {
        constructor(name) {
            _name.set(this, name);
        }
        get name() {
            return _name.get(this);
        }
    }
    return Person;
})();

let person = new Person('hugh');
person.name; // hugh
  • 模拟的块级作用域
js
for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(() => {
            console.log(j);
        }, j * 1000);
    })(i);
}
for (var i = 0; i < 5; i++) {
    (function (j) {
        setTimeout(() => {
            console.log(j);
        }, j * 1000);
    })(i);
}
  • 高阶函数
  • 函数柯里化
  • 节流防抖
  • 有状态的函数

内存泄漏

  • 在退出函数前,将不使用的局部变量赋值为 null
js
//  这段代码会导致内存泄露
window.onload = function () {
    var el = document.getElementById('id');
    el.onclick = function () {
        alert(el.id);
    };
};
// 解决方法为
window.onload = function () {
    var el = document.getElementById('id');
    var id = el.id; //解除循环引用
    el.onclick = function () {
        alert(id);
    };
    el = null; // 将闭包引用的外部函数中活动对象清除
};
//  这段代码会导致内存泄露
window.onload = function () {
    var el = document.getElementById('id');
    el.onclick = function () {
        alert(el.id);
    };
};
// 解决方法为
window.onload = function () {
    var el = document.getElementById('id');
    var id = el.id; //解除循环引用
    el.onclick = function () {
        alert(id);
    };
    el = null; // 将闭包引用的外部函数中活动对象清除
};
  • 将闭包函数赋值为 null,解除引用关系
js
function foo() {
    var name = 'foo';
    function bar() {
        console.log(name);
    }
    return bar;
}

var fn = foo();
fn();
fn = null;
function foo() {
    var name = 'foo';
    function bar() {
        console.log(name);
    }
    return bar;
}

var fn = foo();
fn();
fn = null;

Released under the MIT License.