JS - 闭包

一、闭包是什么
含义

闭包指的是:能够访问另一个函数作用域的变量的函数。清晰的讲:闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量。

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

实际上,闭包是站在作用域的角度上来定义的,因为inner访问到outer作用域的变量,所以inner就是一个闭包函数。

创建方式

在一个函数内创建另一个函数(只要使用了回调函数,实际上就是在使用闭包)。

目的

主要是为了设计私有的方法和变量。

优点

可以避免全局变量的污染。

缺点
  1. 闭包会常驻内存(闭包会携带包含它的函数的作用域),会增大内存使用量,使用不当很容易造成内存泄露。
  2. 闭包只能取得包含函数中任何变量的最后一个值。
特性
  1. 函数嵌套函数;
  2. 函数内部可以引用外部的参数和变量;
  3. 参数和变量不会被垃圾回收机制回收;
  4. 在执行环境中,闭包的作用域包含着它自己的作用域、包含函数的作用域和全局作用域;
  5. 当函数返回了一个闭包时(闭包保存的是整个变量对象),该函数的执行环境的作用域链会被销毁,但是这个函数中的活动对象将会一直保存到闭包不存在为止。
  6. 闭包只能取得包含函数中任何变量的最后一个值。
二、闭包例子
第一个例子
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

console.log(i);

5
5,5,5,5,5

这个也是网上非常经典的一道题目了,可以这么去理解这道题,setTimeout 他存储的是一个函数地址,等到1000毫秒后,再真正去访问这个存储的函数,这个函数的作用域里没有找到变量i,所以会往上去查找,一层层去寻找这个变量。在这个例子中,i最后是5,所以console.log()出来的都是5。
把这个例子做个变型,便于更好的理解

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

等到setTimeout里面的函数执行的时候,i已经变成了10,所以输出的5个数都是10

第二个例子

在上个例子中,我们知道setTimeout里输出的 i 之所以是错的,因为他在函数作用域里没找到变量i,所以要往上一层寻找,然而往上一层的变量i,在执行for循环之后已经变成了5。如果想防止这种情况出现,那么就要在存储函数的时候,给他在当前作用域中传入变量i,下面是传入变量的是三种方式。

输出 5 -> 0,1,2,3,4

第一种方式

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

console.log(i);

第二种方式

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

console.log(i);

第三种方式

var output = function (i) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
};

for (var i = 0; i < 5; i++) {
    output(i); 
}

console.log(i);

第三个例子

输出变成 0 -> 1 -> 2 -> 3 -> 4 -> 5

和前两个例子不同的是,这次要再输出5

暴力解决

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

setTimeout(function() { // 这里增加定时器,超时设置为 5 秒
    console.log(i);
}, 1000 * i);

ES6 Promise

const tasks = [];
for (var i = 0; i < 5; i++) { 
    ((j) => {
        tasks.push(new Promise((resolve) => {
            setTimeout(() => {
                console.log(j);
                resolve();  // 这里一定要 resolve,否则代码不会按预期 work
            }, 1000 * j);   // 定时器的超时时间逐步增加
        }));
    })(i);
}

Promise.all(tasks).then(() => {
    setTimeout(() => {
        console.log(i);
    }, 1000);   // 注意这里只需要把超时设置为 1 秒
});

ES6 Promise 的模块化

const tasks = []; // 这里存放异步操作的 Promise
const output = (i) => new Promise((resolve) => {
    setTimeout(() => {
        console.log(i);
        resolve();
    }, 1000 * i);
});

// 生成全部的异步操作
for (var i = 0; i < 5; i++) {
    tasks.push(output(i));
}

// 异步操作完成之后,输出最后的 i
Promise.all(tasks).then(() => {
    setTimeout(() => {
        console.log(i);
    }, 1000);
});

ES7 的 async/await

// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
    setTimeout(resolve, timeountMS);
});

(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 5; i++) {
        if (i > 0) {
            await sleep(1000);
        }
        console.log(i);
    }

    await sleep(1000);
    console.log(i);
})();
三、在实际场景中使用
setTimeout

原生的setTimeout传递的第一个函数不能带参数,通过闭包可以实现传参效果。

function f1(a) {
  function f2() {
    console.log(a);
  }
  return f2;
}
var fun = f1(10);
setTimeout(fun,1000); // 10
回调

定义行为,然后把它关联到某个用户事件上(点击或者按键)。代码通常会作为一个回调(事件触发时调用的函数)绑定到事件。

function changeSize(size){
      return function(){
           document.body.style.fontSize = size + 'px';
       };
   }

   var size12 = changeSize(12);
   var size14 = changeSize(20);

   document.getElementById('size-12').onclick = size12;
   document.getElementById('size-12').onclick = size14;

可以对应改变字体大小。

函数防抖

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

实现的关键就在于setTimeOut这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现。

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null    //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeOut(fn,delay) 
        }else{
            timer = setTimeOut(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}
封装私有变量
function f1() {
  var value = 1;
  var obj = {
     getValue: function () {
         return value;
    },
    addVlaue: function () {
      value++;
    }
};
  return obj;
}
let result = f1();
console.log(result.getValue());//1
result.addVlaue();
console.log(result.getValue());//2

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。

四、闭包常见的坑
内存泄漏
function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
}

// 改成下面
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id) 
    }
    el = null    // 主动释放el
}
引用的变量可能发生变化
function outer() {
      var result = []
      for (var i = 0;i<10;i++){
        result[i] = function () {
            console.info(i)
        }
     }
     return result
}
this的指向
var object = {
     name: "object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows
递归调用

这种写法会报错

function  factorial(num) {
  if(num<= 1) {
      return 1
  } else {
     return num * factorial(num-1)
  }
}
var anotherFactorial = factorial
factorial = null;
anotherFactorial(4);

这种写法就没问题了,实际上起作用的是闭包函数f,而不是外面的函数newFactorial

var newFactorial = (function f(num){
  if(num<1) {return 1}
  else {
     return num* f(num-1)
  }
})

var anotherFactorial = newFactorial
newFactorial = null;
console.log(anotherFactorial(4)); // 24

参考链接

什么是闭包

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: JavaScript中的闭包是指一个函数可以访问外层作用域的自由变量。从广义的角度上,JavaScript中的函数都是闭包。从狭义的角度上,JavaScript中一个函数,如果确实是访问了外层作用域的变量,那么它是一个闭包。\[1\]闭包可以实现让内部函数访问外部函数的变量,并且可以保持对这些变量的引用,即使外部函数已经执行完毕。\[2\]闭包在JavaScript中被广泛应用,比如实现私有变量、模块化开发、异步回调等。\[3\]闭包的底层原理是通过创建一个函数内部的作用域,并将外部变量作为自由变量保存在这个作用域中,使得内部函数可以访问和修改这些变量。 #### 引用[.reference_title] - *1* [JavaScript中的闭包](https://blog.csdn.net/qq_44482048/article/details/128714553)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [JavaScript-闭包](https://blog.csdn.net/weixin_45203607/article/details/124227668)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值