JS之闭包与IIFE

2018-10-14  本文已影响0人  一只记录成长的小鱼

本篇文章主要讨论了:

  1. JavaScript引擎
  2. 全局对象
  3. 闭包
  4. 循环 + 闭包
  5. IIFE + 闭包

1.JavaScript引擎

2.全局对象

3.闭包

function makeHelloFunction() {
  const message = 'Hello!';

  function sayHello() {
    console.log(message);
  } return sayHello;
}
const sayHello = makeHelloFunction();
console.log('typeof message,', typeof message);
//console.log(message); //会出现ReferenceError
console.log(sayHello.toString());
sayHello();

结果如图:

运行结果
sayHello()被执行了,但是是在它被声明的词法作用域外部被执行的。sayHello()拥有一个词法作用域覆盖着makeHelloFunction()的内部作用域,闭包为了能使sayHello()在以后任意的时刻还可以引用这个作用域而保持它的存在。sayHello()依然拥有对母函数makeHelloFunction()作用域的引用,而这个引用称为闭包。

这个函数在它被编写时的词法作用域之外被调用。闭包使这个函数可以继续访问它在编写时被定义的词法作用域。

加法器是最直观的能观察到闭包的例子:

function makeAddThree() {
  var starter = 3;

function add(num) {
  return starter + num;
}

return add;
}


 const addThree = makeAddThree();

 console.log(addThree(0));   //  3

addThree()就是利用makeAddThree()作用域对add()形成的闭包来生成的。

4.循环 + 闭包

最老实巴交的for循环:

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

我们一般会期待这段代码的输出结果是分别打印数字“1”,“2“,”3“,...”5”,一次一个,一秒一个,但实际上我们得到的是“6”被打印五次,1秒1个。因为for循环的终结条件式i<=5,第一次满足这个条件时的i是6,所以输出结果反映的是i在循环终结后的最终值。超时的回调函数都将在循环完成之后立即运行。虽然所有这5个函数在每次循环迭代中分离地定义,由于作用域的工作方式,他们都闭包在同一个共享的全局作用域上,而它事实上只有一个i

举一反三:

function makeFunctionArray(){
  const arr = [];

  for(var i = 0; i < 5; i++) {
    arr.push(function() {console.log(i);
    });
  }
  return arr;
}
const arr = makeFunctionArray();
arr[0]();

结果为5,因为当调用arr0时循环已经结束,i=5i有作用于函数makeFunctionArray()闭包,所以被内部函数arr.push()添加进arr[]的五个i全为5。

同理,如果加上console.log,打印出的结果也为5。

function makeFunctionArray(){
  const arr = [];

  for(var i = 0; i < 5; i++) {
    arr.push(function() {console.log(i);
    });
  }
  console.log(i);
  return arr;
}
const arr = makeFunctionArray();

arr[0]();
//结果是5 5

因为i是用var声明的,var的有词法作用域作用到for循环块的}截止,而此处i被调用的位置已经结束了循环,所以console打印5的结果

但是注意!如果把for循环里的var换成let,我们会得到ReferenceError,因为let的块级作用域只作用到for循环的 ),因此在外部作用域被调用时JS引擎会当其不存在,而显示ReferenceError
但是同时,arr[0]();的结果也变了:

function makeFunctionArray(){
  const arr = [];

  for(let i = 0; i < 5; i++) {
    arr.push(function() {console.log(i);
    });
  }
  //console.log(i);
  return arr;
}
const arr = makeFunctionArray();

arr[0](); //结果为0

5.立即执行函数表达式IIFE + 闭包

IIFE(Immediately Invoked Function Expression)

const sayHello = (function makeHelloFunction() {
  const message = 'Hello!';

  function sayHello() {
    console.log(message);
  } return sayHello;
})()

sayHello();   // Hello!

也可以用IIFE来做加法器:

var add = (function () {
    var counter = 0;
    return function () {counter += 1; return counter}
})();

add();
add();
add();

// the counter is now 3

IIFE的好处在于它能创建出一个新的作用域,并且不会写入或者修改全局对象。因为JavaScript中只有全局作用域和函数作用域。为了避免变量污染,应该尽可能地少设置全局变量。

立即执行函数能配合闭包保存状态。
像普通的函数传参一样,立即执行函数也能传参数。如果在函数内部再定义一个函数,而里面的那个函数能引用外部的变量和参数(闭包),利用这一点,我们能使用立即执行函数锁住变量保存状态。

function makeFunctionArray(){
  const arr = [];

  for(var i = 0; i < 5; i++) {
    arr.push((function(a) {
      return function(){console.log(a);}
    })(i));
  }
  console.log(i);       //5
  return arr;
}
const arr = makeFunctionArray();

arr[0]();        //0
arr[1]();        //1

上一篇 下一篇

猜你喜欢

热点阅读