【JS 口袋书】第 6 章:JS 中的闭包与模块

做者:valentinogagliardihtml

译者:前端小智前端

来源:githubgit


阿里云最近在作活动,低至2折,有兴趣能够看看: promotion.aliyun.com/ntms/yunpar…github


为了保证的可读性,本文采用意译而非直译。数组

全局变量使用容易引起bug,我们常常教导尽可能不要使用全局变量,尽管全局变量在某些状况下是有用的。 例如,在浏览器中使用JS时,我们能够访问全局window对象,window中有不少有用的方法,好比:浏览器

window.alert('Hello world'); // Shows an alert
window.setTimeout(callback, 3000); // Delay execution
window.fetch(someUrl); // make XHR requests
window.open(); // Opens a new tab
复制代码

这些方法也像这样使用:微信

alert('Hello world'); // Shows an alert
setTimeout(callback, 3000); // Delay execution
fetch(someUrl); // make XHR requests
open(); // Opens a new tab
复制代码

这是方便的。Redux是另外一个“好”全局变量的例子:整个应用程序的状态存储在一个JS对象中,这个对象能够从整个应用程序(经过Redux)访问。可是当在一个团队若是同时有50个编写代码时,以如何处理这样的代码:闭包

var arr = [];

function addToArr(element) {
  arr.push(element);
  return element + " added!";
}
复制代码

我们同事在另外一个文件中建立一个名为arr的新全局数组的概率有多大?我以为很是高。JS中的全局变量很是糟糕的另外一个缘由是引擎足够友好,能够为我们建立全局变量。若是忘了在变量名前加上var,就像这样:模块化

name = "Valentino";
复制代码

JS引擎为会建立一个全局变量,更糟糕的是,能够在函数中建立了“非预期”变量:函数

function doStuff() {
  name = "Valentino";
}

doStuff();

console.log(name); // "Valentino"
复制代码

无辜的功能最终污染了全球环境。 幸运的是,能够用“严格模式”来消除这种行为, 在每一个JS文件使用“use strict”足以免愚蠢的错误:

"use strict";

function doStuff() {
  name = "Valentino";
}

doStuff();

console.log(name); // ReferenceError: name is not defined
复制代码

但一直使用严格模式也是一个问题,并不确实每一个开发人员都会使用严格模式,所以,我们必须找到一种解决“全局变量污染”问题的方法,幸运的是,JS 一直有一个内置的机制来解决这个问题。

揭秘闭包

那么,我们如何保护全局变量不被污染?让我们从一个简单的解开始,把arr移动到一个函数中:

function addToArr(element) {
  var arr = [];
  arr.push(element);
  return element + " added to " + arr;
}
复制代码

彷佛合理,但结果不是我们所指望的:

var firstPass = addToArr("a");
var secondPass = addToArr("b");
console.log(firstPass); // a added to a
console.log(secondPass); // b added to b
复制代码

arr在每次函数调用时都会被重置,如今它成了一个局部变量,而在第一个例子中我们声明的arr是全局变量。 全局变量是“实时的”,不会被重围。 局部变量在函数执行完后就会被销毁了彷佛没有办法防止局部变量被破坏? 闭包会有帮助吗? 可是什么是 闭包呢?

JS函数能够包含其余函数,这到如今是很常见的,以下所示:

function addToArr(element) {
  var arr = [];

  function push() {
    arr.push(element);
  }

  return element + " added to " + arr;
}
复制代码

但若是我们直接把 push 函数返回,又会怎么样呢?以下所示:

function addToArr(element) {
  var arr = [];

  return function push() {
    arr.push(element);
    console.log(arr);
  };

  //return element + " added to " + arr;
}
复制代码

外部函数变成一个容器,返回另外一个函数。第二个return语句被注释,由于该代码永远不会被执行。此时,我们知道函数调用的结果能够保存在变量中。

var result = addToArr();
复制代码

如今result变成了一个可执行的JS函数:

var result = addToArr();
result("a");
result("b");
复制代码

只需修复一下,将参数“element”从外部函数移动到内部函数:

function addToArr() {
  var arr = [];

  return function push(element) {
    arr.push(element);
    console.log(arr);
  };

  //return element + " added to " + arr;
}
复制代码

神奇的现象出现了,完整代码以下:

function addToArr() {
  var arr = [];

  return function push(element) {
    arr.push(element);
    console.log(arr);
  };

  //return element + " added to " + arr;
}

var result = addToArr();
result("a"); // [ 'a' ]
result("b"); // [ 'a', 'b' ]
复制代码

这种被称为JS闭包:一个可以记住其环境变量的函数。为此,内部函数必须是一个封闭(外部)函数的返回值。这种也称为工厂函数。代码能够稍做调整,变动能够取更好的命名,内部函数能够是匿名的:

function addToArr() {
  var arr = [];

  return function(element) {
    arr.push(element);
    return element + " added to " + arr;
  };
}

var closure = addToArr();
console.log(closure("a")); // a added to a
console.log(closure("b")); // b added to a,b
复制代码

如今应该清楚了,“闭包”是内部函数。但有一个问题须要解决:我们为何要这样作?JS闭包的真正目的是什么?

闭包的须要

除了纯粹的“学术”知识以外,JS闭包还有不少用处:

  • 提供私有的全局变量
  • 在函数调用之间保存变量(状态)

JS中闭包最有趣的应用程序之一是模块模式。在ES6以前,除了将变量和方法封装在函数中以外,没有其余方法能够模块化JS代码并提供私有变量与方法”。闭包与当即调用的函数表达式相结合 是至今通用解决方案。

var Person = (function(){
  // do something
})()
复制代码

在模块中能够有“私有”变量和方法:

var Person = (function() {
  var person = {
    name: "",
    age: 0
  };

  function setName(personName) {
    person.name = personName;
  }

  function setAge(personAge) {
    person.age = personAge;
  }
})();
复制代码

从外部我们没法访问person.nameperson.age。我们也不能调用setNamesetAge。模块内的全部内容都是“私有的”。若是想公开我们的方法,咱们能够返回一个包含对私有方法引用的对象。

var Person = (function() {
  var person = {
    name: "",
    age: 0
  };

  function setName(personName) {
    person.name = personName;
  }

  function setAge(personAge) {
    person.age = personAge;
  }

  return {
    setName: setName,
    setAge: setAge
  };
})();
复制代码

若是想获取person对象,添加一个获取 person 对象的方法并返回便可。

var Person = (function() {
  var person = {
    name: "",
    age: 0
  };

  function setName(personName) {
    person.name = personName;
  }

  function setAge(personAge) {
    person.age = personAge;
  }

  function getPerson() {
    return person.name + " " + person.age;
  }

  return {
    setName: setName,
    setAge: setAge,
    getPerson: getPerson
  };
})();

Person.setName("Tom");
Person.setAge(44);
var person = Person.getPerson();
console.log(person); // Tom 44
复制代码

这种方式,外部获取不到 person 对象:

console.log(Person.person); // undefined
复制代码

模块模式不是构造JS代码的惟一方式。 使用对象,我们能够实现相同的结果:

var Person = {
  name: "",
  age: 0,
  setName: function(personName) {
    this.name = personName;
  }
  // other methods here
};
复制代码

可是这样,内部属性就不在是私有的了:

var Person = {
  name: "",
  age: 0,
  setName: function(personName) {
    this.name = personName;
  }
  // other methods here
};

Person.setName("Tom");

console.log(Person.name); // Tom
复制代码

这是模块的主要卖点之一。 另外一个好处是,模块有助于组织代码,使其具备重用性和可读性。 如,开发人员看到如下的代码就大概知道是作什么的:

"use strict";

var Person = (function() {
  var person = {
    name: "",
    age: 0
  };

  function setName(personName) {
    person.name = personName;
  }

  function setAge(personAge) {
    person.age = personAge;
  }

  function getPerson() {
    return person.name + " " + person.age;
  }

  return {
    setName: setName,
    setAge: setAge,
    getPerson: getPerson
  };
})();
复制代码

总结

全局变量很容易引起bug,我们应该尽量地避免它们。 有时全局变量是有用的,须要格外当心使用,由于JS引擎能够自由地建立全局变量。

这些年来出现了许多模式来管理全局变量,模块模式就是其中之一。 模块模式创建在闭包上,这是JS的固有特性。 JS 中的闭包是一种可以“记住”其变量环境的函数,即便在后续函数调用之间也是如此。 当我们从另外一个函数返回一个函数时,会建立一个闭包,这个模式也称为**“工厂函数**”。

思考

  • 什么是闭包?
  • 使用全局变量有哪些很差的方面?
  • 什么是 JS 模块,为何要使用它?

代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:github.com/valentinoga…

交流(欢迎加入群,群工做日都会发红包,互动讨论技术)

阿里云最近在作活动,低至2折,有兴趣能够看看:promotion.aliyun.com/ntms/yunpar…

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

github.com/qq449245884…

由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励

相关文章
相关标签/搜索