【js重学系列】闭包

闭包概念

MDN 上这样定义闭包:闭包是函数和声明该函数的词法环境的组合。

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

闭包产生的本质,当前环境中存在指向父级作用域的引用。

闭包的表现形式

返回一个函数

// 案例1
function c() {
  var d = 1;
  console.log("d:", ++d);
  d = d + 5;
  return function () {
    console.log("此时d的值:", ++d);
  };
}
c(); //d: 2
c(); //d: 2
var e = c(); //d: 2
e(); //d: 8

// 案例2
var x = 1;

function demo() {
  var x = 2;

  function demo2() {
    console.log(x);
  }
  return demo2;
}
var h = demo();
h(); // 2
// 当调用demo后,会返回一个函数,此时全局变量 h接受这个函数,因为demo2存在指向父级作用域的引用,h变量会拿到父级作用域的变量,demo2恰恰引用了window demo 和 本身的作用域。 因此demo2可以访问到demo作用域中的变量。

作为函数参数传递

var a = 1;
function foo() {
  var a = 2;
  function baz() {
    console.log(a);
  }
  bar(baz);
}
function bar(fn) {
  // 这就是闭包
  fn();
}
// 输出2,而不是1
foo();

闭包实质

问题来了? 是不是只有返回函数才会产生闭包呢?

回到闭包实质:只需要让父级作用域的引用存在即可

当函数能够记住并访问所在的词法作用域时,就产生了闭包。

var demo2;

function demo() {
  var x = 2;

  demo2 = function () {
    console.log(x);
  };
}
demo();
demo2(); // 2

//首先让外部函数执行,给 demo2 赋值,等于 demo2 现在拥有了 window demo 和 自身的作用域的访问权限,那么查找变量 x 的时候,会逐级的向上去找,在最近的 demo 作用域找到标示符就返回结果,输出 2。
// 🤭 在这里外部的变量 demo2 存在父级作用域的引用,因此产生了闭包,形式变了,本质还是没有改变。

闭包的应用

防抖节流函数

单例模式

js最初版模块化

在定时器、事件监听、Ajax 请求、跨窗口通信、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包
以下的闭包保存的仅仅是 window 和当前作用域。

// 定时器
setTimeout(function timeHandler(){
    console.log('2222');
},100)

// 事件监听
$('#div').click(function(){
    console.log('DIV Click');
})

IIFE(立即执行函数表达式)创建闭包, 保存了全局作用域 window 和当前函数的作用域,因此可以全局的变量。

var x = 22;
(function IIFE() {
  // 输出22
  console.log(x);
})();
var arr = [1, 2, 4, 6, 2, 8, 9];

function bet(a, b) {
  return function (v) {
    return v >= a && v <= b;
  };
}
console.log(arr.filter(bet(2, 5))); //[2,4,2]

// 为什么说这是利用了闭包特性呢,1函数嵌套函数,1子函数里有对父函数变量的引用,bet(2,5)调用后就返回的是子函数,但是子函数有对父函数实参的引用,

闭包的优点

让外部作用域访问内部变量

保存变量不被销毁

闭包的缺点

会造成内存泄漏的问题

解决方法 把元素设置为 null

this 指向问题

let obj2 = {
  user: 123,
  get() {
    return this.user;
  },
};
console.log(obj2.get()); //123 this指向obj2
let obj2 = {
  user: 123,
  get() {
    return function () {
      return this.user; //未定义 this指向window
    };
  },
};
console.log(obj2.get()());
// 解决方法:
let obj2 = {
  user: 123,
  get() {
    // console.log(this); //obj2
    let that = this;
    return function () {
      return that.user; //未定义 this指向window
    };
  },
};
console.log(obj2.get()());
// 或者用箭头函数

链接:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
https://blog.csdn.net/weixin_43586120/article/details/89456183
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://www.cnblogs.com/sandaizi/p/11582488.html
https://zhuanlan.zhihu.com/p/22486908
https://juejin.cn/post/6844904014232944648#heading-9

posted @ 2020-08-04 13:36  有风吹过的地方丨  阅读(167)  评论(0编辑  收藏  举报