理解js中的闭包

写在前边: 咱们知道,当函数执行时,会造成本身的执行期上下文,并把它挂到本身的做用域链上,当函数执行完以后,它的执行期上下文就会被释放。因此,通常状况下,在函数外部访问函数内部的变量须要特殊的方法才能解决,这个特殊的方法就是闭包。es6

在理解闭包前,我建议你先了解下js的做用域。 理解js中的做用域面试

闭包的概念

闭包:闭包指的是在函数的外部能够访问函数内部的变量。函数没有被释放,整条做用域链上的局部变量都将获得保留。建立闭包的通常方法是在函数内部返回一个新函数。bash

通俗的理解: 闭包:顾名思义,是一个封闭的包,可是这个包露出内部的一条线,这条线就是闭包内部返回函数的做用域链,它上面挂载了这个函数以及他的全部父级函数的变量,咱们能够经过这条线访问到函数内的变量,这就是闭包。闭包

闭包的造成机制

咱们知道: 当一个函数被定义时,它的scope属性会指向他的父级的scope的引用 当一个函数执行时,会造成它本身的执行期上下文(AO),并把它挂载到他的做用域(scope chain)的最顶端,它的父级的scope依次下移。 当函数执行完毕后,他本身的执行期上下文(AO)会被销毁 关于scope、AO详情见理解js中的做用域异步

这样,当咱们在函数内部返回一个函数并在其外部被一个变量接收时,它的做用域链上存的是它的父级的做用域链,只要这个函数存在则它的做用域链就会一直存在,这样它的做用域链上的变量得不到释放,即能在函数外部访问做用域内部的变量。函数

为了便于理解,咱们举个简单的例子:post

function test(){
	var a = 100
	function b(){
		a++
		console.log(a)
	}
	return b
}
var global = 100
var c = test()
c() // 101
c() // 102 
复制代码

1.当定义并执行test函数时,它的做用域链指向它的AO以及全局的GO 性能

在这里插入图片描述
2.当函数b被返回时,b的做用域链上挂载这它的父级的做用域链,即test执行时的做用域链
在这里插入图片描述
3.此时,虽然test()函数执行完毕,它的执行期上下文被销毁( 注意这里的销毁指的是指向被销毁,即图中箭头消失)。可是此时返回的函数b的scope上拥有b的父级的全部做用域链。此时,又将return 的b 赋值给c。因此,当执行c函数时,它会在b的做用域链上寻找所须要的变量,这样就实现了闭包。因此当c执行两次时,结果分别是10一、102

闭包的做用

闭包的做用通常有两个: 1.能够在函数外部,使用用函数内部的变量 2.函数内部的变量不会被释放。ui

闭包的缺陷

因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,this

关于闭包的常见面试题

第一题:
var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();
复制代码

输出结果都是3。为何?

1.当执行完for循环后,此时的全局执行期上下文为

GO:{
	data:[...],
	i:3
}
复制代码

2.当执行data[0]时,它产生它本身的执行期上下文(AO),此时他的做用域链为

scope:[AO,GO]
复制代码

此时,它的AO上没有i变量,就向它的上一级的执行期上下文中找,即以上的GO,因此输出结果为3

其余两个执行结果同理。

当咱们将其修改成闭包时,即以下代码

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (j) {
        return function(){
            console.log(j);
        }
  })(i)
}

data[0](); // 0
data[1](); // 1
data[2](); // 2
复制代码

此时,他的执行结果分别为0,1,2

当执行完for循环时,此时的GO为

GO:{
	data:[...],
	i:3
}
复制代码

当data[0]执行时,此时他的做用域链为

scope:[AO,匿名函数的AO,GO]
复制代码

而此时匿名函数的AO为

AO:{
	i:0
}
复制代码

data[0]的AO中没有变量i,因此它沿着做用域链向上寻找,找到匿名函数的AO,即此时i为0.

执行data[1],data[2]同理

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

console.log(i);
复制代码

以上这道题是在面试中常常问到的问题,那么它输出的是什么呢?相信大多数朋友均可以知道,最后他的结果为 5,5,5,5,5,5。 只要了解了js的运行机制、以及同步异步的问题,咱们横容易知道第一个5是当即输出,以后的5在1s后同时输出。

那么咱们将它改造为闭包。

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

它的结果为5,0,1,2,3,4 咱们分析下它的做用域。

首先,先定义了5个当即执行函数,而后执行循环外部的console,此时的GO为{i:5},因此先输出5 1s后5个当即执行函数同时执行,此时定时器内部的i为其外部函数(即当即执行函数)的i,此时i分别为0,1,2,3,4,因此输出为5,0,1,2,3,4

想要那么有没有什么其它方法来改造呢?答案是有的,es6里提供了一个叫let的东西,他会造成块级做用域。

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

console.log(i);  

复制代码

以上代码会报错,由于最后的i是不存在的,由于let造成了块级做用域,只在for循环内部起做用。

第三题
var name = "The Window";
var object = {
    name : "My Object",
    getNameFunc : function(){
        return function(){
            return this.name;
        };
    }
};
alert(object.getNameFunc()());  // The Window

复制代码
第四题
var name = "The Window";

var object = {
    name : "My Object",
    getNameFunc : function(){
        var that = this;
        return function(){
            return that.name;
        };
    }
};
alert(object.getNameFunc()());  // My Object

复制代码

上面这两道题,考察了闭包的用法以及this的指向问题,这里就很少作解释了。

相信看过个人这篇文章 关于js中的this指向问题,以后就能够搞明白了。

相关文章
相关标签/搜索