js闭包的强大功能
我觉得闭包的强大之处主要在于他的封装性,也就是信息隐藏。下面我们从以下几个方面来详细说明。
函数与私有状态
通过闭包,我们可以创建拥有私有状态的函数,闭包使得状态被封装起来。
工厂模式与私有原型对象
我们先来看一个通过原型创建对象的常规方式,如下:
let todoPrototype = {
toString : function() {
return this.id + " " + this.userName + ": " + this.title;
}
}
function Todo(todo){
let newTodo = Object.create(todoPrototype);
Object.assign(newTodo, todo);
return newTodo;
}
在这个例子中,todoPrototype 原型对象是一个全局对象。
我们可以通过闭包,只用创建原型对象一次,也能够被所有 Todo 函数调用所公用,并且保证其私有性。示例如下:
let Todo = (function createTodoFactory(){
let todoPrototype = {
toString : function() {
return this.id + " " + this.userName + ": " + this.title;
}
}
return function(todo){
let newTodo = Object.create(todoPrototype);
Object.assign(newTodo, todo);
return newTodo;
}
})();
let todo = Todo({id : 1, title: "This is a title", userName: "Cristi", completed: false });
这里,Todo() 就是一个拥有私有状态的函数。
工厂模式与私有构造函数
查看如下代码:
let Todo = (function createTodoFactory(){
function Todo(spec){
Object.assign(this, spec);
}
return function(spec){
let todo = new Todo(spec);
return Object.freeze(todo);
}
})();
这里,Todo() 工厂函数就是一个闭包。通过它,不管是否使用 new ,我们都可以创建不可变对象,原型对象也只用创建一次,并且它是私有的。
let todo = Todo({title : "A description"});
todo.title = "Another description";
// Cannot assign to read only property 'title' of object
todo.toString = function() {};
//Cannot assign to read only property 'toString' of object
而且,在内存快照中,我们可以通过构造函数名来识别这些示例对象。
翻译功能与私有map
通过闭包,我们可以创建一个 map,在所有翻译调用中被使用,且是私有的。
示例如下:
let translate = (function(){
let translations = {};
translations["yes"] = "oui";
translations["no"] = "non";
return function(key){
return translations[key];
}
})();
translate("yes"); //oui
自增生成器函数
通过闭包,我们可以创建自增生成器函数。同样,内部状态是私有的。示例如下:
function createAGenerate(count, increment) {
return function(){
count += increment;
return count;
}
}
let generateNextNumber = createAGenerate(0, 1);
console.log(generateNextNumber()); //1
console.log(generateNextNumber()); //2
console.log(generateNextNumber()); //3
let generateMultipleOfTen = createAGenerate(0, 10);
console.log(generateMultipleOfTen()); //10
console.log(generateMultipleOfTen()); //20
console.log(generateMultipleOfTen()); //30
对象与私有状态
以上示例中,我们可以创建一个拥有私有状态的函数。同时,我们也可以创建多个拥有同一私有状态的函数。基于此,我们还可以创建一个拥有私有状态的对象。
示例如下:
function TodoStore(){
let todos = [];
function add(todo){
todos.push(todo);
}
function get(){
return todos.filter(isPriorityTodo).map(toTodoViewModel);
}
function isPriorityTodo(todo){
return task.type === "RE" && !task.completed;
}
function toTodoViewModel(todo) {
return { id : todo.id, title : todo.title };
}
return Object.freeze({
add,
get
});
}
TodoStore() 函数返回了一个拥有私有状态的对象。在外部,我们无法访问私有的 todos 变量,并且 add 和 get 这两个闭包拥有相同的私有状态。在这里,TodoStore() 是一个工厂函数。
闭包 vs 纯函数
闭包就是那些引用了外部作用域中变量的函数。
为了更好的理解,我们将内部函数拆成闭包和纯函数两个方面:
- 闭包是那些引用了外部作用域中变量的函数。
- 纯函数是那些没有引用外部作用域中变量的函数,它们通常返回一个值并且没有副作用。
在上述例子中,add() 和 get() 函数是闭包,而 isPriorityTodo() 和 toTodoViewModel() 则是纯函数。
闭包在函数式编程中的应用
闭包在函数式编程中也应用广泛。譬如,underscore 源码中 函数相关小节 中的所有函数都利用了闭包这一特性。
A function decorator is a higher-order function that takes one function as an argument and returns another function, and the returned function is a variation of the argument function — Javascript Allongé
装饰器函数也使用了闭包的特性。
我们来看如下 not 这个简单的装饰器函数:
function not(fn){
return function decorator(...args){
return !fn.apply(this, args);
}
}
decorator() 函数引用了外部作用域的fn变量,因此它是一个闭包。