【重温基本】19.闭包

本文是 重温基本 系列文章的第十九篇。
本日感觉:将杂沓的事变找出之间的联络,也是种才能。

系列目次:

本章节温习的是JS中的关于闭包,这个小哥哥呀,看看。

前置学问:
声明函数两种要领:

  • 函数声明,存在函数声明提拔,因而能够在函数声明之前挪用(不会报错)。
fun();  // ok
function fun(){};
  • 函数表达式,不存在函数声明提拔,若定义前挪用,会报错(函数还不存在)。
fun();  // error
var fun = function (){};

1.观点

2.1 词法作用域

这里先要相识一个观点,词法作用域:它是静态的作用域,是誊写变量和块作用域的作用域**。

function f (){
    var a = "leo";
    function g(){console.log(a)};
    g();
}
f(); // "leo"

由于函数g的作用域中没有a这个变量,然则它能够接见父作用域,并运用父作用域下的变量a,末了输出"leo"

词法作用域中运用的域,是变量在代码中声明的位置所决议的。嵌套的函数能够接见在其外部声明的变量。

2.2 闭包

接下来引见下闭包观点,闭包是指有权接见另一个函数作用域中的变量的函数

闭包是由函数以及建立该函数的词法环境组合而成。这个环境包含了这个闭包建立时所能接见的一切局部变量。

建立闭包的罕见体式格局:在一个函数内建立另一个函数。如:

function f (){
    var a = "leo";
    var g = function (){
        console.log(a);
    };
    return g;// 这里g就是一个闭包函数,能够接见到g作用域的变量a
}
var fun = f();
fun(); // "leo"

经由过程观点能够看出,闭包有以下三个特性:

  • 函数嵌套函数
  • 函数内部能够援用函数外部的参数和变量
  • 参数和变量不会被渣滓接纳机制接纳

注:关于内存接纳机制,能够检察阮一峰先生的《JavaScript 内存走漏教程》

别的,运用闭包有以下优点:

  • 将一个变量历久保留在内存中
  • 防止全局变量的污染
function f (){
    var a = 1; 
    return function(){
        a++;
        console.log(a);
    }
}
var fun = f();
fun(); // 2
fun(); // 3

由于渣滓接纳机制没有接纳,所以每次挪用fun()都邑返回新的值。

  • 私有化成员,使得外部不能接见
function f (){
    var a = 1;
    function f1 (){
        a++;
        console.log(a);
    };
    function f2 (){
        a++;
        console.log(a);
    };
    return {g1:f1, g2:f2};
};
var fun = f();
fun.g1(); // 2
fun.g2(); // 3

2.易错点

2.1 援用的变量发生变化

function f (){
    var a = [];
    for(var i = 0; i<10; i++){
        a[i] = function(){
            console.log(i);
        }
    }
    return a;
}
var fun = f();
fun[0]();  // 10
fun[1]();  // 10
// ...
fun[10]();  // 10

底本照我们的主意,fun要领中每一个元素上的要领实行的效果应该是1,2,3,...,10,而实际上,每一个返回都是10,由于每一个闭包函数援用的变量if实行环境下的变量i,轮回完毕后,i已变成10,所以都邑返回10
处理办法能够如许:

function f (){
    var a = [];
    for(var i = 0; i<10; i++){
        a[i] = function(index){
            return function(){
                console.log(index);
                // 此时的index,是父函数作用域的index,
                // 数组的10个函数对象,每一个对象的实行环境下的index都差别
            }
        }(i);
    };
    return a;
};
var fun = f();
fun[0]();  // 0
fun[1]();  // 1
// ...
fun[10]();  // 10

2.2 this指向题目

var obj = {
    name : "leo", 
    f : function(){
        return function(){
            console.log(this.name);
        }
    }
}
obj.f()();  // undefined

由于内里的闭包函数是在window作用域下实行,因而this指向window

2.3 内存走漏

当我们在闭包内援用父作用域的变量,会使得变量没法被接纳。

function f (){
    var a = document.getElementById("leo");
    a.onclick = function(){console.log(a.id)};
}

如许做的话,变量a会一向存在没法开释,相似的变量越来越多的话,很轻易引发内存走漏。我们能够这么处理:

function f (){
    var a = document.getElementById("leo");
    var id = a.id;
    a.onclick = function(){};
    a = null;  //主动开释变量a
}

经由过程把变量赋值成null来主动开释掉。

3.案例

3.1 典范案例——定时器和闭包

代码以下:

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

不出所料,返回的不是我们想要的0,1,2,3,...,9,而是10个10
这是由于js是单历程,所以在实行for轮回的时刻定时器setTimeout被部署到使命行列中列队期待实行,而在守候过程当中,for轮回已在实行,比及setTimeout要实行的时刻,for轮回已实行完成,i的值就是10,所以就打印了10个10
处理要领 :

  • 1.运用ES6新增的let

for轮回中的var替换成let

  • 2.运用闭包
for(var i = 0; i<10 ; i++){
    (function(i){
        setTimeout(function(){
            console.log(i);
        }, i*100);
    })(i);
}

3.2 运用闭包处理递归挪用题目

function f(num){
    return num >1 ? num*f(num-1) : 1;
}

var fun = f;
f = null;
fun(4)   // 报错 ,由于最好是return num* arguments.callee(num-1),arguments.callee指向当前实行函数,然则在严厉形式下不能运用该属性也会报错,所以借助闭包来完成

这里能够运用return num >1 ? num* arguments.callee(num-1) : 1;,由于arguments.callee指向当前实行函数,然则在严厉形式下不能运用,也会报错,所以这里须要运用闭包来完成。

function fun = (function f(num){
    return num >1 ? num*f(num-1) : 1;
})

如许做,实际上起作用的是闭包函数f,而不是表面的fun

3.3 运用闭包模拟块级作用域

ES6之前,运用var声明变量会有变量提拔题目:

for(var i = 0 ; i<10; i++){console.log(i)};
console.log(i);  // 变量提拔 返回10

为了防止这个题目,我们如许运用闭包(匿名自实行函数):

(function(){
    for(var i = 0 ; i<10; i++){console.log(i)};
})()
console.log(i);  // undefined

我们建立了一个匿名的函数,并马上实行它,由于外部没法援用它内部的变量,因而在函数实行完后会马上开释资本,关键是不污染全局对象。这里i跟着闭包函数的完毕,实行环境烧毁,变量接纳。
然则如今,我们用的更多的是ES6范例的letconst来声明。

参考文章

  1. MDN 闭包
  2. 《JavaScript高等程序设计》

本部分内容到这完毕

Author王安然
E-mailpingan8787@qq.com
博 客www.pingan8787.com
微 信pingan8787
逐日文章引荐https://github.com/pingan8787…
JS小册js.pingan8787.com

迎接关注微信民众号【前端自习课】天天清晨,与您一同进修一篇优异的前端手艺博文 .

    原文作者:pingan8787
    原文地址: https://segmentfault.com/a/1190000018001780
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞