什么是闭包?

红宝书(p178)上对于闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数,

MDN 对闭包的定义为:闭包是指那些能够访问自由变量的函数。

由此,我们可以看出闭包共有两部分组成:

  • 是一个函数
  • 能访问另外一个函数作用域中的变量

对于闭包有下面三个特性:

1、闭包可以访问当前函数以外的变量

function getOuter(){
    var date = '815';
    function getDate(str){
        console.log(str + date);  //访问外部的date
    }
    return getDate('今天是:'); //"今天是:815"
}
getOuter();

2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量

function getOuter(){
    var date = '815';
    function getDate(str){
        console.log(str + date);  //访问外部的date
    }
    return getDate;     //外部函数返回
}
var today = getOuter();
today('今天是:');   //"今天是:815"
today('明天不是:');   //"明天不是:815"

3、闭包可以更新外部变量的值

function updateCount(){
    var count = 0;
    function getCount(val){
        count = val;
        console.log(count);
    }
    return getCount;     //外部函数返回
}
var count = updateCount();
count(815); //815

为什么闭包的应用都有关键词 return,引用 JavaScript 秘密花园中的一段话:

闭包是 JavaScript 一个非常重要的特性,这意味着当前作用域总是能够访问外部作用域中的变量。 因为 函数 是 JavaScript 中唯一拥有自身作用域的结构,因此闭包的创建依赖于函数。

应用场景

具体应用场景你知道哪些??

  • 保护函数内的变量安全:如迭代器、生成器。
  • 在内存中维持变量:如缓存数据、柯里化。

私有属性

var foo = (function(){
    var secret = 'secret'
    // “闭包”内的函数可以访问 secret 变量,而 secret 变量对于外部却是隐藏的
    return {
        get_secret() {
            return secret
        },
        new_secret(new_secret) {
            secret = new_secret
        }
    }
})()

foo.secret              // undefined
foo.get_secret()        // 'secret'
foo.new_secret('哈哈哈') // 修改secret值
foo.get_secret()        // '哈哈哈'

之所以可能通过这种方式在 JavaScript 种实现公有,私有,特权变量正是因为闭包,闭包是指在 JavaScript 中,内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数被返回(寿命终结)了之后。

let sque = (function () {
    let _width = Symbol();

    class Squery {
        constructor(s) {
            this[_width] = s
        }
        foo() {
            console.log(this[_width])
        }
    }
    return Squery
})();

let ss = new sque(20);

ss.foo()    // 20
console.log(ss[_width]) // ReferenceError: _width is not defined

单例模式

class Modal {
    constructor(name) {
        this.name = name
        this.getName()
    }
    getName() {
        return this.name
    }
}

let ProxySing = (function(){
    let instance;
    return function(name) {
        if (!instance) {
            instance = new Modal(name)
        }
        return instance
    }
})()

let a = new ProxySing('问题框');
let b = new ProxySing('回答框');

console.log(a === b); // true
console.log(a.getName());  // '问题框'
console.log(b.getName());  // '问题框'

函数防抖

const fn = () => console.log('fn')
window.onresize = debounce(fn, 1000)
function debounce(fn, interval) {
    let timer = null;
    return function (...args) {
        if(timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, interval);
    }
}

面试题

接下来,看这道刷题必刷,面试必考的加强版闭包题:

for (var i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}
console.log(i)
// 5 5 5 5 5 5

答案是都是5,6个5,让我们分析一下原因:

由于作用域链机制的影响,闭包只能取得内部函数的最后一个值,这引起的一个副作用就是如果内部函数在一个循环中,那么变量的值始终为最后一个值。

如果要强制返回预期的结果(5,0,1,2,3,4),怎么办???

加个闭包

方法1:立即执行函数

把值传参给一个自执行的函数,函数具有块级作用域

for (var i = 0; i < 5; i++) {
    ((num) => {
        setTimeout(() => {
            console.log(num);
        }, 1000);
    })(i);
}
console.log(i)

方法2:setTimeout传参

setTimeout被遗忘的第三个参数,定时器启动时候,第三个以后的参数是作为第一个func()的参数传进去。

for (var i = 0; i < 5; i++) {
    setTimeout((j) => {
        console.log(j);
    }, 1000, i);
}
console.log(i)

方法3:使用ES6中的let

let具有块级作用域,所以外面的会报错,未定义该变量,在这儿行不通

for (let i = 0; i < 5; i++) {
    setTimeout(() => {
        console.log(i);
    }, 1000);
}
console.log(i)  // i is not defined

方法4:函数调用

var output = function (i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
};
for (var i = 0; i < 5; i++) {
    output(i);  // 这里传过去的 i 值被复制了
}
console.log(i)

本文装转自https://wsydxiangwang.github.io/web/Base/3.html

用于记录学习

最后修改:2021 年 09 月 24 日 09 : 37 PM
如果觉得我的文章对你有用,请随意赞赏