今天从《JavaScript语言精粹》 中看到了闭包, 感觉书上讲的比较简略而这又是JS的一个亮点. 因此顺藤摸瓜,再看看MDN-Closures .
下面是文章的大致翻译, 文章最后更新于 Jan 18, 2017.
我自己不太确定的地方和重要关键字就顺带挂上英文了. 顺带也可以学习下表达方式. 欢迎指正.
JS闭包到底是什么?
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
闭包是函数.它们的指向自变量( independent (free) variables ).(指在封闭的作用域内定义,但只能局部使用的变量) (variables that are used locally, but defined in an enclosing scope).. 换言之,闭包函数能记住它们被创建时的情境.
理解闭包之前,首先通过以下代码了解 lexical scoping 的概念
function init() {
var name = "Mozilla"; // name is a local variable created by init
function displayName() { // displayName() is the inner function, a closure
alert(name); // use variable declared in the parent function
}
displayName();
}
init();
init( ) 创建了一个局部变量name和内置的函数displayName( ),
这个内置函数就是闭包.它有以下特点:
1. 是 init( ) 中的一个内置函数(inner function), 并且只有在int( )内部可得(available).
2.在该例中没有自己的局部变量, 但可以使用外层函数(outer functions)的变量, 自然也包括了父级外层函数(parent function).
这个例子展现了lexical scoping的应用
the code and see that this works. This is an example of lexical scoping: in JavaScript, the scope of a variable is defined by its location. within the source code (it is apparent lexically) and nested functions have access to variables declared in their outer scope.
简而言之有2点
1.JS中,变量的作用域范围取决于变量在源码中所处位置.(因此说作用域是lexical).
2.内置的函数可以获取外层函数的定义的变量
function makeFunc() {
var name = "Mozilla"; // name 是 local
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
这个例子和上一个一样都会弹出name.而区别在于,在该例中, 内置函数displayName( )在被外部函数执行前会先被返回. 对于没有文法环境 的语言来说, 函数中的局部变量生命周期截止到函数执行后. makeFunc( )一旦结束执行,那么理论上它就该跟name变量永别了.而 这个函数还能执行, 就归功于在JS中函数可以形成闭包( The reason is that functions in JavaScript form closures )
好,正文正式开始.
一个闭包通常由什么组成?
A closure is the combination of a function and the lexical environment (or simply "environment") within which that function wasdeclared. The environment consists of any local variables that were in-scope at the time that the closure was created.
闭包的组成分2部分,即一个函数及其声明该函数的
闭包的组成:一个函数及其声明该函数的文法环境(lexcial environment). 闭包被创建时,所有处于同一作用域下的的局部变量共同组成了这个环境.The environment consists of any local variables that were in-scope at the time that the closure was created.
该例中,当makeFunc执行时, myFunc指向displayName创建的一个实例,而该实例保持指向它的文法环境.(maintains a reference to its lexical environment), 而这个文法环境包含了 name变量. 因此调用myFunc时, 变量name的值就被传到了alert中.另一个栗子
function makeAdder(x) {
return function(y) {
return x + y;
};
}
//创建两个函数并传入不同的参数
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
该栗子中, 首先定义了函数makeAdder(x)
,它接受一个变量x,同时返回一个新函数. 被返回的新函数接受一个变量y,并返回x和y之和.
从本质上看, makeAdder
是一个函数工厂(a function factory).——函数工厂可以创建多个函数(functions), 而且允许为参数添加一个特定的值(a specific value). 在这个例子中,函数工厂makeAdder就创建了两个新函数——一个函数把5作为参数(adds 5 to its argument),另一个则把10作为参数.
add5和add10都是闭包. 两个函数的函数体相同(share the same function body),但却存储着不同的环境,(这体现在参数不同.)
说了那么多,闭包有哪些实际应用呢?
有了闭包我们就可以将一些数据(即文法环境)和基于该环境的函数结合起来运用. A closure lets you associate some data (the environment) with a function that operates on that data.这和面向对象有异曲同工之妙,对象(objects)允许我们将一些数据(对象的属性)和多个方法结合运用。因此,一般使用了单一方法的对象的地方都可使用闭包.(you can use a closure anywhere that you might normally use an object with only a single method.)
闭包在页面开发中用处极广, 页面开发中大部分JS代码都是基于事件的(event-based)——即开发者定义一些行为,并将行为与事件绑定(attach it to),最后用户通过点击鼠标和键盘触发事件.我们的代码基本上是作为回调函数(attached as a callback), 即用于响应事件的函数(executed in response to the event.)
闭包实际运用的小案例:给页面添加可调整字体大小的按钮( interactive text size buttons ).其中做法之一是
1.先给body中统一设置font-size,以px为单位;
2. 给所需元素设置font-size,用相对单位em
通过闭包在JS中模拟私有方法
Java本身支持声明将方法声明为私有(declare methods private),这样就可以保证方法只能被同一类下的其他方法调用.(can only be called by other methods in the same class)
JS可以通过闭包来模拟该方法.
私有方法的作用
1.可根据需要有效限制对部分代码的使用(restricting access to code)
2.可方便管理全局命名空间(global namespace), 维护页面整洁(keeping non-essential methods from cluttering up the public interface)
可以通过定义闭包来获取(access)私有函数(private function)和私有变量. 这时的闭包又称模块模式module pattern
http://codepen.io/anon/pen/GrNabO?editors=1010
详见 JS使用闭包模私有方法
小结:运用闭包在JS中模拟私有方法能具有面向对象的编程的优点,尤其在数据隐藏(data hiding)和封装方面(encapsulation)有独到之处:
常见的错误闭包——在循环内部创建闭包
解决方法
方法一:使用工厂函数 use a function factory
方法二:使用更多闭包use more
方法三:使用ES6中的let
延伸
What is lexical scope? http://stackoverflow.com/questions/1047454/what-is-lexical-scope
闭包的组成:一个函数及其声明该函数的lexcial environment.闭包被创建的同时,所有处于同一作用域下的的局部变量共同组成了这个环境.The environment consists of any local variables that were in-scope at the time that the closure was created.
因此,当调用myFunc时, 变量name的值Mozilla就被传到了alert中,提示框弹出.