Js闭包使用姿势指南

来源:http://www.chinese-glasses.com 作者:Web前端 人气:94 发布时间:2020-04-07
摘要:时间: 2019-10-16阅读: 90标签: 指南引言 时间: 2019-08-25阅读: 153标签: 闭包什么是闭包? 闭包就是指 能够访问另一个函数作用域的变量的函数 ,闭包就是一个函数,能够访问其他函数的作用域

时间: 2019-10-16阅读: 90标签: 指南引言

时间: 2019-08-25阅读: 153标签: 闭包什么是闭包?

闭包就是指能够访问另一个函数作用域的变量的函数,闭包就是一个函数,能够访问其他函数的作用域中的变量,js有一个全局对象,在浏览器下是window,node下是global,所有的函数都在这个对象下,也能访问这个对象下的变量,这也就是说,js中的所有函数都是闭包

闭包的概念:《JavaScript权威指南》:函数对象可以通过作用域链相互关联起来,函数体内部的变量可以保存在函数作用域内,这种特性称为“闭包”。

闭包的定义

不好理解?那就通俗点讲:所谓闭包,就是一个函数,这个函数能够访问其他函数的作用域中的变量。

函数与对其状态即词法环境(lexical environment)的引用共同构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在JavaScript,函数在每次创建时生成闭包。

理解闭包

MDN对闭包的定义中说道了词法环境和引用同时也说道了每次创建时生成闭包

理解闭包首先要了解嵌套函数的词法作用域规则,先来看一下这段代码

参考代码

var scope = 'global scope'; // 全局变量var checkScope = function () { var scope = 'local scope'; // 局部变量 function f() { return scope; } return f(); // = local scope};checkScope();
const eg = ()={ let a ='测试变量' // 被eg创建的局部变量 let inner = ()={ // eg的内部函数,一个闭包 console.log(a) // 使用了父函数中声明的变量 } return inner // inner就是一个闭包函数 可以访问到eg函数的作用域}

checkScope()函数声明了一个局部变量,并定义了一个函数f(),函数f()反回了这个变量的值,最后将函数f()的执行结果返回。你应当非常清楚为什么调用checkscope()函数会返回“local scope”。

来个有趣的例子吧

这个词法作用域的例子介绍了引擎是如何解析函数嵌套中的变量的。词法作用域中使用的域,是变量在代码中声明的位置所决定的。嵌套的函数可以访问在其外部声明的变量。

function init() { var name = "Mozilla"; // name 是一个被 init 创建的局部变量 function displayName() { // displayName() 是内部函数,一个闭包 alert(name); // 使用了父函数中声明的变量 } displayName(); } init();

现在来考虑以下例子 :

由于js作用域的原因,dispplayName可以访问到父级作用域init的变量name,这点母庸质疑

var scope = 'global scope'; // 全局变量var checkScope = function () { var scope = 'local scope'; // 局部变量 function f() { return scope; } return f; };checkScope()(); // 返回值是什么?

那么再看这个例子

这段代码中,我们将函数内的一对圆括号移动到了checkscope()之后。checkscope()现在仅仅返回函数内嵌套的一个函数对象,而不是直接返回结果。在函数作用域外面,调用这个嵌套的函数会发生什么呢?

function makeFunc() { var name = "Mozilla"; function displayName() { alert(name); } return displayName; }var myFunc = makeFunc();myFunc();

这个谜题的答案是,JavaScript中的函数会形成闭包,闭包是由函数以及创建该函数的词法环境组合而成。也就是说,这个环境包含了这个闭包创建时所能访问的所有局部变量。

这段代码和之前的代码执行结果完全一样,其中的不同 — 也是有意思的地方 — 在于内部函数displayName()在执行前,被外部函数返回。你很可能认为它无法执行,那么我们再改变一下代码

在这个例子中,嵌套的函数f()定义在这个作用域链里,其中的变量scope一定是局部变量,不管何时何地执行f(),这种绑定在执行f()时依然有效。因此最后一行代码返回“local scope”,而不是“global scope“。

var name2 = 123function makeFunc() { var name = "Mozilla"; function displayName() { alert(name2); } return displayName; }var myFunc = makeFunc();myFunc();

如果你理解了词法作用域的规则,你就能很容易地理解闭包:函数定义时的作用域链到函数执行时依然有效。

你几乎不用想就能知道结果肯定是123那么我们在返回之前的代码,为什么你就无法肯定代码的执行结果了呢

然而很多同学觉得闭包非常难理解,因为他们在深入学习闭包的实现细节时将自已搞得晕头转向。他们觉得在外部函数中定义的局部变量在函数返回后就不存在了,那么嵌套的函数如何能调用不存在的作用域链呢?如果你想搞清楚这个问题,你需要更深入地了解类似C语言这种更底层的编程语言,并了解基于栈的CPU架构:如果一个函数的局部变量定义在CPU的栈中,那么当函数返回时它们的确就不存在了。

答案是,JavaScript中的函数会形成闭包。 闭包是由函数以及创建该函数的词法环境组合而成。请仔细阅读这段话,js的闭包是由函数及创建该函数的词法环境组合而成,创建它的词法环境有这个变量,所有直接使用这个变量,没有则向上查找,直至在全局环境都找不到,返回undefind

但回想一下我们是如何定义作用域链的。我们将作用域链描述为一个对象列表,不是绑定的栈。每次调用JavaScript函数的时候,都会为之创建一个新的对象用来保存局部变量,把这个对象添加至作用域链中。当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。但如果这些嵌套的函数对象在外部函数中保存下来,那么它们也会和所指向的变量绑定对象一样当做垃圾回收。但是如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。

那么我们再把例子换一下

下面再来看一个更有意思的示例:— makeAdder函数:

var object = { name: ''object", getName: function() { return function() { console.info(this.name) } }}object.getName()() // underfined
function makeAdder(x) { return function(y) { return x + y; };}var add5 = makeAdder(5);var add10 = makeAdder(10);console.log(add5(2)); // 7console.log(add10(2)); // 12

这个时候this指向哪里呢?答案是全局因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows

在这个示例中,我们定义了makeAdder(x)函数,它接受一个参数x,并返回一个新的函数。返回的函数接受一个参数y,并返回x+y的值。

现在我们换个例子吧

从本质上讲,makeAdder是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。

function outer() { var a = '变量1' var inner = function () { console.info(a) } return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域}var inner = outer() // 获得inner闭包函数inner() //"变量1"

Add5和add10都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在add5的环境中,x为 5。而在add10中,x则为 10。

当程序执行完var inner = outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被被inner的函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;《JavaScript高级编程》书中建议:由于闭包会携带包含它的函数的作用域,因为会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多。

实用的闭包

我们再来个有趣的例子

闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

function makeAdder(x) { return function(y) { return x + y; }; }var add5 = makeAdder(5);var add10 = makeAdder(10);console.log(add5(2)); // 7console.log(add10(2)); // 12

因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。

add5和add10都是闭包,也共享函数的定义,但是保存了不同的词法环境,在add5中x=5而在add10中x为10

接着来看一个uniqueInteger()函数,这个函数使用自身的一个属性来保存每次返回的值,以便每次都能跟踪上次返回的值。

内存泄露问题

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

闭包函数引用外层的变量,当执行完外层函数是,变量会无法释放

你需要仔细阅读这段代码才能理解其含义。粗略来看,第一行代码看起来像将函数赋值给一个变量 uniqueInteger,实际上,这段代码定义了一个立即调用的函数,因此是这个函数的返回值赋值给变量uniqueInteger。现在,我们来看函数体,这个函数返回另外一个函数,这是一个嵌套的函数,我们将它赋值给变量uniqueInteger,嵌套的函数是可以访问作用域内的变量的,而且可以访问外部函数中定义的 counter变量。当外部函数返回之后,其他任何代码都无法访问 counter变量,只有内部的函数才能访问到它。

本文由10bet发布于Web前端,转载请注明出处:Js闭包使用姿势指南

关键词:

上一篇:随机数10bet

下一篇:没有了

频道精选

最火资讯