JS变量作用域--闭包 | 青训营笔记 第 2 天

163 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

当青训营遇上码上掘金

变量作用域

要理解闭包,首先要理解 JavasSript 的特殊的变量作用域。 变量的作用域无非就两种:全局变量和局部变量。

JavasSript 语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

What?

闭包,保存了一份变量(缓存了内部变量)

闭包四要素

  1. 存在函数嵌套
  2. 内部函数调用外部变量
  3. 返回值是函数
  4. 使用一个全局变量接受

四个前提条件

  1. 有函数嵌套
  2. 内部函数调用外部作用域的变量参数
  3. 返回值是函数
  4. 创建一个对象函数,让其长期驻留
  5. 方法中返回一个方法

Why?

全局变量容易污染环境,局部变量又无法长期驻留变量,所以需要一种机制:

能长期保存变量(延长变量生命周期)又不污染全局环境(创建私有环境),

这就是闭包 , ****闭包就是能够读取其他函数内部变量的函数。

其实质就是把函数,在外用函数包起来,然后里面再return 返回fa,仅此而已。

How?

一个是前面提到的可以读取函数内部的变量,

另一个就是让这些变量的值始终保持在内存中,不会在 fn 调用后被自动清除。

// 输出每调用一次,递减一次
function fa(num) { 
  let a = num;
  function fb() {
    console.log(a--);
  }
  return fb;
}
let fn = fa(10);
// 使用完后可以释放,不用担心内存泄露
fn = null; // 释放

使用闭包的注意点

  • (1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包, 否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
  • (2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值(代码中的a, num)。

Test

let a = 123;
function fn1(){
  console.log(a);
}
function fn2(){
  let a = 456;
  fn1();
}
fn2(); // => 123
// function 和 全局变量一样都被提前声明,函数保存了父级环境,
// 在fn2中调用fn1会在自身作用域下寻找a,如果没有会向上寻找父级作用域,其父级为全局
for (let i = 0; i < 5; i++) {
  setTimeout(function())  {
    console.log(i);
  });
}
// => 01234
// 上面的for意思是利用循环开启了5个定时器  即使定时器不会立刻执行 
// 但let定义的变量存在自己的作用域最后还是输出01234  
for (var i = 0; i < 5; i++) {
  setTimeout(function())  {
    console.log(i);
  });
}
// => 55555
// 计时器是异步进行的定时器是异步执行的 
// 下面的for循环也是用循环开启5个定时器,但是定时器不会立刻触发,由于定时器是异步执行的
// 所以当for循环执行完才会触发定时器,for循环执行完i的值会变成5,并且最后的i值会覆盖前面的值所以是55555
if (! "a" in window) {
  console.log("a");
  var a = 1;
}
if (! "b" in window) {
  console.log("b");
	let b = 1;
}
console.log(a);  // undefined
console.log(b);  // Uncaught ReferenceError: b is not defined