# 第 7 章: 闭包 vs 对象

Anton van Straaten 6/4/2003

http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html

## 达成共识

``````function outer() {
var one = 1;
var two = 2;

return function inner(){
return one + two;
};
}

var three = outer();

three();            // 3
``````

``````var obj = {
one: 1,
two: 2
};

function three(outer) {
return outer.one + outer.two;
}

three( obj );        // 3
``````

## 相像

1. 一个没有闭包的编程语言可以用对象来模拟闭包。
2. 一个没有对象的编程语言可以用闭包来模拟对象。

### 状态

``````function outer() {
var one = 1;
var two = 2;

return function inner(){
return one + two;
};
}

var obj = {
one: 1,
two: 2
};
``````

`inner()``obj` 对象持有的作用域都包含了两个元素状态：值为 `1``one` 和值为 `2``two`。从语法和机制来说，这两种声明状态是不同的。但概念上，他们的确相当相似。

``````var point = {
x: 10,
y: 12,
z: 14
};
``````

``````function outer() {
var x = 10;
var y = 12;
var z = 14;

return function inner(){
return [x,y,z];
}
};

var point = outer();
``````

``````var person = {
name: "Kyle Simpson",
street: "123 Easy St",
city: "JS'ville",
state: "ES"
}
};
``````

``````function outer() {
var name = "Kyle Simpson";
return middle();

// ********************

function middle() {
var street = "123 Easy St";
var city = "JS'ville";
var state = "ES";

return function inner(){
return [name,street,city,state];
};
}
}

var person = outer();
``````

``````function point(x1,y1) {
return function distFromPoint(x2,y2){
return Math.sqrt(
Math.pow( x2 - x1, 2 ) +
Math.pow( y2 - y1, 2 )
);
};
}

var pointDistance = point( 1, 1 );

pointDistance( 4, 5 );        // 5
``````

`distFromPoint(..)` 封装了 `x1``y1`，但是我们也可以通过传入一个具体的对象作为替代值：

``````function pointDistance(point,x2,y2) {
return Math.sqrt(
Math.pow( x2 - point.x1, 2 ) +
Math.pow( y2 - point.y1, 2 )
);
};

pointDistance(
{ x1: 1, y1: 1 },
4,    // x2
5    // y2
);
// 5
``````

#### 行为，也是一样！

``````function person(name,age) {
return happyBirthday(){
age++;
console.log(
"Happy " + age + "th Birthday, " + name + "!"
);
}
}

var birthdayBoy = person( "Kyle", 36 );

birthdayBoy();            // Happy 37th Birthday, Kyle!
``````

``````var birthdayBoy = {
name: "Kyle",
age: 36,
happyBirthday() {
this.age++;
console.log(
"Happy " + this.age + "th Birthday, " + this.name + "!"
);
}
};

birthdayBoy.happyBirthday();
// Happy 37th Birthday, Kyle!
``````

``````var person = {
firstName: "Kyle",
lastName: "Simpson",
first() {
return this.firstName;
},
last() {
return this.lastName;
}
}

person.first() + " " + person.last();
// Kyle Simpson
``````

``````function createPerson(firstName,lastName) {
return API;

// ********************

function API(methodName) {
switch (methodName) {
case "first":
return first();
break;
case "last":
return last();
break;
};
}

function first() {
return firstName;
}

function last() {
return lastName;
}
}

var person = createPerson( "Kyle", "Simpson" );

person( "first" ) + " " + person( "last" );
// Kyle Simpson
``````

### （不）可变

``````function outer() {
var x = 1;
var y = [2,3];

return function inner(){
return [ x, y[0], y[1] ];
};
}

var xyPublic = {
x: 1,
y: [2,3]
};
``````

`outer()` 中字面变量 `x` 存储的值是不可变的 —— 记住，定义的基本类型如 `2` 是不可变的。但是 `y` 的引用值，一个数组，绝对是可变的。这点对于 `xyPublic` 中的 `x``y` 属性也是完全相同的。

``````function outer() {
var x = 1;
return middle();

// ********************

function middle() {
var y0 = 2;
var y1 = 3;

return function inner(){
return [ x, y0, y1 ];
};
}
}

var xyPublic = {
x: 1,
y: {
0: 2,
1: 3
}
};
``````

### 内部结构

``````function outer() {
var x = 1;

return function inner(){
return x;
};
}
``````

``````scopeOfOuter = {
x: 1
};
``````

``````scopeOfInner = {};
Object.setPrototypeOf( scopeOfInner, scopeOfOuter );
``````

``````return scopeOfInner.x;
``````

`scopeOfInner` 并没有一个 `x` 的属性，当他的 `[[Prototype]]` 连接到拥有 `x` 属性的 `scopeOfOuter`时。通过原型委托访问 `scopeOfOuter.x` 返回值是 `1`

## 同根异枝

### 结构可变性

``````function trackEvent(evt,keypresses = []) {
return keypresses.concat( evt );
}

var keypresses = trackEvent( newEvent1 );

keypresses = trackEvent( newEvent2, keypresses );
``````

``````function trackEvent(evt,keypresses = () => []) {
return function newKeypresses() {
return [ ...keypresses(), evt ];
};
}

var keypresses = trackEvent( newEvent1 );

keypresses = trackEvent( newEvent2, keypresses );
``````

### 私有

``````function outer() {
var x = 1;

return function inner(){
return x;
};
}

var xHidden = outer();

xHidden();            // 1
``````

``````var xPublic = {
x: 1
};

xPublic.x;            // 1
``````

#### 可见性

``````function recordKeypress(keypressEvt) {
// 数据库实用程序
DB.store( "keypress-events", keypressEvt );
}
``````

If you already have an array -- just an object with public numerically-named properties -- this is very straightforward using a built-in JS array utility `forEach(..)`:

``````keypresses.forEach( recordKeypress );
``````

``````function trackEvent(
evt,
keypresses = {
list() { return []; },
forEach() {}
}
) {
return {
list() {
return [ ...keypresses.list(), evt ];
},
forEach(fn) {
keypresses.forEach( fn );
fn( evt );
}
};
}

// ..

keypresses.list();        // [ evt, evt, .. ]

keypresses.forEach( recordKeypress );
``````

### 状态拷贝

``````var a = [ 1, 2, 3 ];

var b = a.slice();
b.push( 4 );

a;            // [1,2,3]
b;            // [1,2,3,4]
``````

``````var o = {
x: 1,
y: 2
};

// 在 ES2017 以后，使用对象的解构：
var p = { ...o };
p.y = 3;

// 在 ES2015 以后：
var p = Object.assign( {}, o );
p.y = 3;
``````

### 性能

``````function StudentRecord(name,major,gpa) {
return function printStudent(){
return `\${name}, Major: \${major}, GPA: \${gpa.toFixed(1)}`;
};
}

var student = StudentRecord( "Kyle Simpson", "[email protected]", "CS", 4 );

// 随后

student();
// Kyle Simpson, Major: CS, GPA: 4.0
``````

``````function StudentRecord(){
return `\${this.name}, Major: \${this.major}, GPA: \${this.gpa.toFixed(1)}`;
}

var student = StudentRecord.bind( {
name: "Kyle Simpson",
major: "CS",
gpa: 4
} );

// 随后

student();
// Kyle Simpson, Major: CS, GPA: 4.0
``````

`student()` 函数，学术上叫做“边界函数” —— 有一个硬性边界 `this` 来引用我们传入的对象字面量，因此之后任何调用 `student()` 将使用这个对象作为`this`，于是它的封装状态可以被访问。

``````function bind(orinFn,thisObj) {
return function boundFn(...args) {
return origFn.apply( thisObj, args );
};
}

var student = bind( StudentRecord, { name: "Kyle.." } );
``````