1. 首页
  2. >
  3. 前端开发
  4. >
  5. Javascript

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变量,因此它是一个闭包。