资讯专栏INFORMATION COLUMN

javascript闭包介绍

weij / 1265人阅读

摘要:下面这个例子就是闭包,函数能够访问到不在其代码块里的变量。然而事实恰恰相反,唯一的解释就是是一个闭包。性能问题执行一次,就会重新构造两个函数。正确的做法应该是参考资料深入理解闭包学习闭包阮一峰

概念

闭包(closure)是一个拥有任意变量以及绑定这些变量的环境(environment)的表达式(一般来说是就是function)

A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).

在作用域内且在function定义时被访问的变量,那么这个变量就一直能够被那个function访问。

variables that are in scope and accessed from a function declaration will stay accessible by that function.

下面这个例子就是闭包,displayName函数能够访问到不在其代码块里的name变量。

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();
函数的作用域 functional scoping

一个变量的作用域是以其所在的源代码的位置来定义的,嵌套在里面的function可以访问到声明在外层作用域的变量

The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope.

还是拿刚才那个例子来说,displayName函数是嵌套在init函数里的,所以它能够访问到init函数里的变量

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();
闭包的组成

先看一下这个例子:

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

var myFunc = makeFunc();
myFunc();

按照java或C++的经验,局部变量name的生命周期在函数的执行后就结束了,所以会推断namemakeFunc()访问后应该就访问不到了。
然而事实恰恰相反,唯一的解释就是myFunc是一个闭包(closure)。

闭包由两部分组成:

function

创建该function的环境(创建闭包时,作用域内的所有局部变量)

对应到上面的这个例子里:

function: displayName

环境:name="Mozilla"

再看一个例子:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

alert(add5(2));  // 7
alert(add10(2)); // 12

这个例子说明闭包的function可以是相同的,但是环境可以是不同的,因此就会有不同的结果。

归纳

因此可以将闭包归纳为:

定义时,确定可访问变量

执行时,确定变量的值

常见错误

下面这段代码实际上执行的时候并不是alert 0,1,2,3,4,而是alert 5次5。
这是为什么?因为i变量在for循环后变成了5,而在执行的时候我们才会确定闭包里i的值,在定义的时候不会记住i的值是什么的。

var funcs = [];
for(var i=0; i < 5; i++) {
  funcs[i] = function() { alert(i); }
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}

正确的写法是:

var funcs = [];
function makeFunc(x) {
  return function() { alert(x); }
}
for(var i=0; i < 5; i++) {
  funcs[i] = makeFunc(i)
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}
闭包实践 函数工厂
function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + "px";
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
私有变量
var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */

在这个例子里:

外界不能访问: privateCounter,changeBy

外界间接访问: increment,decrement,value

私有变量+函数工厂
var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */

Counter1和Counter2绑定的环境相互独立。

性能问题
function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

执行一次,就会重新构造两个函数。

正确的做法应该是:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};
参考资料

Closures - MDN

Explaning Javascript Scope and closures

深入理解JavaScript闭包(closure)

学习Javascript闭包(Closure) - 阮一峰

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90878.html

相关文章

  • 十分钟快速了解《你不知道的 JavaScript》(上卷)

    摘要:最近刚刚看完了你不知道的上卷,对有了更进一步的了解。你不知道的上卷由两部分组成,第一部分是作用域和闭包,第二部分是和对象原型。附录词法这一章并没有说明机制,只是介绍了中的箭头函数引入的行为词法。第章混合对象类类理论类的机制类的继承混入。 最近刚刚看完了《你不知道的 JavaScript》上卷,对 JavaScript 有了更进一步的了解。 《你不知道的 JavaScript》上卷由两部...

    赵春朋 评论0 收藏0
  • 【进阶2-2期】JavaScript深入之从作用域链理解闭包

    摘要:使用上一篇文章的例子来说明下自由变量进阶期深入浅出图解作用域链和闭包访问外部的今天是今天是其中既不是参数,也不是局部变量,所以是自由变量。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第二期,本周的主题是作用域闭包,今天是第7天。 本计划一共28期,每期重点攻克一个面试重难点,如果你还不了解本进阶计...

    simpleapples 评论0 收藏0
  • javascript的函数式编程介绍

    摘要:在编程的世界里有两种基本类型的编程函数式编程强调将一系列的动作组合成一个体系对象式编程强调将一系列的成分聚合到一个类中对于这种弱类语言来说,它既有的特点通过或者封装一个类又有的特点。 在编程的世界里有两种基本类型的编程:函数式编程(OFP):强调将一系列的动作组合成一个体系;对象式编程(OOP):强调将一系列的成分聚合到一个类中;对于javascript这种弱类语言来说,它既有OOP的...

    tinna 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • JavaScript内部原理系列-闭包(Closures)

    摘要:对于采用面向堆栈模型来存储局部变量的系统而言,就意味着当函数调用结束后,其局部变量都会从堆栈中移除。因为它也是函数的局部变量,也会随着的返回而移除。 概要 本文将介绍一个在JavaScript经常会拿来讨论的话题 —— 闭包(closure)。闭包其实已经是个老生常谈的话题了; 有大量文章都介绍过闭包的内容(其中不失一些很好的文章,比如,扩展阅读中Richard Cornford的文...

    gghyoo 评论0 收藏0

发表评论

0条评论

weij

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<