1
0
mirror of https://github.com/ruanyf/es6tutorial.git synced 2025-05-29 05:42:20 +00:00

修改object/Symbol

This commit is contained in:
Ruan Yifeng 2015-02-15 19:13:15 +08:00
parent f6b5bf640b
commit 0d3a7ea26a
2 changed files with 189 additions and 83 deletions

View File

@ -349,18 +349,24 @@ Object.getPrototypeOf(obj)
## Symbol ## Symbol
ES6引入了一种新的原始数据类型Symbol表示独一无二的ID。它通过Symbol函数生成。 ### 概述
在ES5中对象的属性名都是字符串这容易造成属性名的冲突。比如你使用了一个他人提供的对象但又想为这个对象添加新的方法新方法的名字有可能与现有方法产生冲突。如果有一种机制保证每个属性的名字都是独一无二的就好了这样就从根本上防止属性名的冲突。这就是ES6引入Symbol的原因。
ES6引入了一种新的原始数据类型Symbol表示独一无二的ID。它通过Symbol函数生成。这就是说对象的属性名现在可以有两种类型一种是原来就有的字符串另一种就是新增的Symbol类型。凡是属性名属于Symbol类型就都是独一无二的可以保证不会与其他属性名产生冲突。
```javascript ```javascript
let symbol1 = Symbol(); let s = Symbol();
typeof symbol typeof s
// "symbol" // "symbol"
``` ```
上面代码中变量symbol1就是一个独一无二的ID。typeof运算符的结果表明变量symbol1是Symbol数据类型而不是字符串之类的其他类型。 上面代码中变量s就是一个独一无二的ID。typeof运算符的结果表明变量s是Symbol数据类型而不是字符串之类的其他类型。
注意Symbol函数前不能使用new命令否则会报错。这是因为生成的Symbol是一个原始类型的值不是对象。
Symbol函数可以接受一个字符串作为参数表示Symbol实例的名称。 Symbol函数可以接受一个字符串作为参数表示Symbol实例的名称。
@ -373,9 +379,27 @@ mySymbol.name
``` ```
上面代码表示Symbol函数的字符串参数用来指定生成的Symbol的名称可以通过name属性读取。之所以要新增name属性是因为键名是Symbol类型,而有些场合需要一个字符串类型的值来指代这个键。 上面代码表示Symbol函数的字符串参数用来指定生成的Symbol的名称可以通过name属性读取。之所以要新增name属性是因为如果一个对象的属性名是Symbol类型可能不太方便引用,而有些场合需要一个字符串类型的值来指代这个键。
注意Symbol函数前不能使用new命令否则会报错。这是因为生成的Symbol是一个原始类型的值不是对象。 Symbol函数的参数只是表示对当前Symbol类型的值的描述因此相同参数的Symbol函数的返回值是不相等的。
```javascript
// 没有参数的情况
var s1 = Symbol();
var s2 = Symbol();
s1 === s2 // false
// 有参数的情况
var s1 = Symbol("foo");
var s2 = Symbol("foo");
s1 === s2 // false
```
上面代码中s1和s2都是Symbol函数的返回值而且参数相同但是它们是不相等的。
Symbol类型的值不能与其他类型的值进行运算会报错。 Symbol类型的值不能与其他类型的值进行运算会报错。
@ -399,34 +423,9 @@ sym.toString()
``` ```
symbol的最大特点就是每一个Symbol都是不相等的保证产生一个独一无二的值。 ### 作为属性名的Symbol
```javascript Symbol类型作为标识符用于对象的属性名时保证了属性名之间不会发生冲突。如果一个对象由多个模块构成这样就不会出现同名的属性也就防止了键值被不小心改写或覆盖。Symbol类型还可以用于定义一组常量防止它们的值发生冲突。
let w1 = Symbol();
let w2 = Symbol();
let w3 = Symbol();
w1 === w2 // false
w1 === w3 // false
w2 === w3 // false
function f(w) {
switch (w) {
case w1:
...
case w2:
...
case w3:
...
}
}
```
上面代码中w1、w2、w3三个变量都等于`Symbol()`,但是它们的值是不相等的。
由于这种特点Symbol类型适合作为标识符用于对象的属性名保证了属性名之间不会发生冲突。如果一个对象由多个模块构成这样就不会出现同名的属性也就防止了键值被不小心改写或覆盖。Symbol类型还可以用于定义一组常量防止它们的值发生冲突。
```javascript ```javascript
@ -445,11 +444,36 @@ var a = {
var a = {}; var a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' }); Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!" a[mySymbol] // "Hello!"
``` ```
上面代码通过方括号结构和Object.defineProperty两种方法将对象的属性名指定为一个Symbol值。 上面代码通过方括号结构和Object.defineProperty将对象的属性名指定为一个Symbol值。
在对象内部使用Symbol属性名必须采用属性名表达式就像上面的第二种写法。
```javascript
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);
```
采用增强的对象写法上面代码的obj对象可以写得更简洁一些。
```javascript
let obj = {
[s](arg) { ... }
};
```
注意不能使用点结构将Symbol值作为对象的属性名。 注意不能使用点结构将Symbol值作为对象的属性名。
@ -466,50 +490,63 @@ a[mySymbol] // undefined
上面代码中mySymbol属性的值为未定义原因在于`a.mySymbol`这样的写法并不是把一个Symbol值当作属性名而是把mySymbol这个字符串当作属性名进行赋值这是因为点结构中的属性名永远都是字符串。 上面代码中mySymbol属性的值为未定义原因在于`a.mySymbol`这样的写法并不是把一个Symbol值当作属性名而是把mySymbol这个字符串当作属性名进行赋值这是因为点结构中的属性名永远都是字符串。
下面的写法为Map结构添加了一个成员但是该成员永远无法被引用。 需要注意的是Symbol类型作为属性名时该属性还是公开属性不是私有属性。
### Symbol.for()Symbol.keyFor()
Symbol.for方法在全局环境中搜索指定key的Symbol值如果存在就返回这个Symbol值否则就新建一个指定key的Symbol值并返回。
`Symbol.for()``Symbol()`这两种写法的区别是,前者会被登记在全局环境中供搜索,后者不会。`Symbol.for()`不会每次调用就返回一个新的Symbol类型的值而是会先检查跟定的key是否已经存在如果不存在才会新建一个值。
```javascript ```javascript
let a = Map(); Symbol.for("bar") === Symbol.for("bar")
a.set(Symbol(), 'Noise'); // true
a.size // 1
Symbol("bar") === Symbol("bar")
// false
``` ```
为Symbol函数添加一个参数就可以引用了。 上面代码中,由于`Symbol()`写法没有登记机制,所以每次调用都会返回一个不同的值。
Symbol.keyFor方法返回一个已登记的Symbol类型值的key。
```javascript ```javascript
let a = Map(); var s1 = Symbol.for("foo");
a.set(Symbol('my_key'), 'Noise'); Symbol.keyFor(s1) // "foo"
var s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
``` ```
如果要在对象内部使用Symbol属性名必须采用属性名表达式。 上面代码中变量s2属于未登记的Symbol值所以返回undefined。
### 属性名的遍历
Symbol作为属性名该属性不会出现在for...in循环中也不会被Object.keys()、Object.getOwnPropertyNames()返回但是有一个对应的Object.getOwnPropertySymbols方法以及Object.getOwnPropertyKeys方法都可以获取指定对象的所有Symbol属性名。
Object.getOwnPropertySymbols方法返回一个数组成员是当前对象的所有用作属性名的Symbol值。
```javascript ```javascript
let specialMethod = Symbol(); var obj = {};
var a = Symbol('a');
var b = Symbol.for('b');
let obj = { obj[a] = 'Hello';
[specialMethod]: function (arg) { ... } obj[b] = 'World';
};
obj[specialMethod](123); var objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]
``` ```
采用增强的对象写法上面代码的obj对象可以写得更简洁一些。 下面是另一个例子Object.getOwnPropertySymbols方法与for...in循环、Object.getOwnPropertyNames方法进行对比的例子。
```javascript
let obj = {
[specialMethod](arg) { ... }
};
```
Symbol类型作为属性名不会出现在for...in循环中也不会被Object.keys()、Object.getOwnPropertyNames()返回但是有一个对应的Object.getOwnPropertySymbols方法以及Object.getOwnPropertyKeys方法都可以获取Symbol属性名。
```javascript ```javascript
@ -518,9 +555,13 @@ var obj = {};
var foo = Symbol("foo"); var foo = Symbol("foo");
Object.defineProperty(obj, foo, { Object.defineProperty(obj, foo, {
value: "foobar", value: "foobar",
}); });
for (var i in obj) {
console.log(i); // 无输出
}
Object.getOwnPropertyNames(obj) Object.getOwnPropertyNames(obj)
// [] // []
@ -531,7 +572,7 @@ Object.getOwnPropertySymbols(obj)
上面代码中使用Object.getOwnPropertyNames方法得不到Symbol属性名需要使用Object.getOwnPropertySymbols方法。 上面代码中使用Object.getOwnPropertyNames方法得不到Symbol属性名需要使用Object.getOwnPropertySymbols方法。
Reflect.ownKeys方法返回所有类型的键名。 Reflect.ownKeys方法可以返回所有类型的键名。
```javascript ```javascript
@ -546,8 +587,42 @@ Reflect.ownKeys(obj)
``` ```
### 内置的Symbol值
除了定义自己使用的Symbol值以外ES6还提供一些内置的Symbol值指向语言内部使用的方法。
1Symbol.hasInstance
该值指向对象的内部方法@@hasInstance(两个@表示这是内部方法外部无法直接调用下同该对象使用instanceof运算符时会调用这个方法判断该对象是否为某个构造函数的实例。
2Symbol.isConcatSpreadable
该值指向对象的内部方法@@isConcatSpreadable该对象使用Array.prototype.concat()时,会调用这个方法,返回一个布尔值,表示该对象是否可以扩展成数组。
3Symbol.isRegExp
该值指向对象的内部方法@@isRegExp,该对象被用作正则表达式时,会调用这个方法,返回一个布尔值,表示该对象是否为一个正则对象。
4Symbol.iterator
该值指向对象的内部方法@@iterator该对象进行for...of循环时会调用这个方法返回该对象的默认遍历器详细介绍参见《Iterator和for...of循环》一章。
5Symbol.toPrimitive
该值指向对象的内部方法@@toPrimitive,该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
6Symbol.toStringTag
该值指向对象的内部属性@@toStringTag在该对象上调用Object.prototype.toString()时,会返回这个属性,它是一个字符串,表示该对象的字符串形式。
7Symbol.unscopables
该值指向对象的内部属性@@unscopables返回一个数组成员为该对象使用with关键字时会被with环境排除在的那些属性值。
## Proxy ## Proxy
### 概述
Proxy用于修改某些操作的默认行为等同于在语言层面做出修改所以属于一种“元编程”meta programming即对编程语言进行编程。 Proxy用于修改某些操作的默认行为等同于在语言层面做出修改所以属于一种“元编程”meta programming即对编程语言进行编程。
Proxy可以理解成在目标对象之前架设一层“拦截”外界对该对象的访问都必须先通过这层拦截因此提供了一种机制可以对外界的访问进行过滤和改写。proxy这个词的原意是代理用在这里表示由它来“代理”某些操作。 Proxy可以理解成在目标对象之前架设一层“拦截”外界对该对象的访问都必须先通过这层拦截因此提供了一种机制可以对外界的访问进行过滤和改写。proxy这个词的原意是代理用在这里表示由它来“代理”某些操作。
@ -592,7 +667,29 @@ obj.time // 35
对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。 对于没有设置拦截的操作,则直接落在目标对象上,按照原先的方式产生结果。
下面是另一个拦截读取操作的例子。 Proxy支持的拦截操作一览。
- defineProperty(target, propKey, propDesc)返回一个布尔值拦截Object.defineProperty(proxy, propKey, propDesc
- deleteProperty(target, propKey) 返回一个布尔值拦截delete proxy[propKey]
- enumerate(target)返回一个遍历器拦截for (x in proxy)
- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取
- getOwnPropertyDescriptor(target, propKey) 返回属性的描述对象拦截Object.getOwnPropertyDescriptor(proxy, propKey)
- getPrototypeOf(target) 返回一个对象拦截Object.getPrototypeOf(proxy)
- has(target, propKey)返回一个布尔值拦截propKey in proxy
- isExtensible(target)返回一个布尔值拦截Object.isExtensible(proxy)
- ownKeys(target)返回一个数组拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy)
- preventExtensions(target)返回一个布尔值拦截Object.preventExtensions(proxy)
- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置
- setPrototypeOf(target, proto)返回一个布尔值拦截Object.setPrototypeOf(proxy, proto)
如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply方法拦截Proxy实例作为函数调用的操作比如proxy(···)、proxy.call(···)、proxy.apply(···)。
- construct方法拦截Proxy实例作为构造函数调用的操作比如new proxy(···)。
### get
get方法用于拦截某个属性的读取操作。上文已经有一个例子下面是另一个拦截读取操作的例子。
```javascript ```javascript
@ -650,7 +747,9 @@ pipe(3) . double . pow . reverseInt . get
上面代码设置Proxy以后达到了将函数名链式使用的效果。 上面代码设置Proxy以后达到了将函数名链式使用的效果。
除了取值函数getProxy还可以设置存值函数set用来拦截某个属性的赋值行为。假定Person对象有一个age属性该属性应该是一个不大于200的整数那么可以使用Proxy对象保证age的属性值符合要求。 ### set
set方法用来拦截某个属性的赋值操作。假定Person对象有一个age属性该属性应该是一个不大于200的整数那么可以使用Proxy对象保证age的属性值符合要求。
```javascript ```javascript
@ -682,6 +781,30 @@ person.age = 300 // 报错
上面代码中由于设置了存值函数set任何不符合要求的age属性赋值都会抛出一个错误。利用set方法还可以数据绑定即每当对象发生变化时会自动更新DOM。 上面代码中由于设置了存值函数set任何不符合要求的age属性赋值都会抛出一个错误。利用set方法还可以数据绑定即每当对象发生变化时会自动更新DOM。
### apply
apply方法拦截函数的调用、call和apply操作。
```javascript
var target = function () { return 'I am the target'; };
var handler = {
apply: function (receiver, ...args) {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p() === 'I am the proxy';
// true
```
上面代码中变量p是Proxy的实例当它作为函数调用时p()就会被apply方法拦截返回一个字符串。
### ownKeys
ownKeys方法用来拦截Object.keys()操作。 ownKeys方法用来拦截Object.keys()操作。
```javascript ```javascript
@ -703,25 +826,7 @@ Object.keys(proxy)
上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。 上面代码拦截了对于target对象的Object.keys()操作,返回预先设定的数组。
Proxy支持的拦截操作一览。 ### Proxy.revocable()
- defineProperty(target, propKey, propDesc)返回一个布尔值拦截Object.defineProperty(proxy, propKey, propDesc
- deleteProperty(target, propKey) 返回一个布尔值拦截delete proxy[propKey]
- enumerate(target)返回一个遍历器拦截for (x in proxy)
- get(target, propKey, receiver):返回类型不限,拦截对象属性的读取
- getOwnPropertyDescriptor(target, propKey) 返回属性的描述对象拦截Object.getOwnPropertyDescriptor(proxy, propKey)
- getPrototypeOf(target) 返回一个对象拦截Object.getPrototypeOf(proxy)
- has(target, propKey)返回一个布尔值拦截propKey in proxy
- isExtensible(target)返回一个布尔值拦截Object.isExtensible(proxy)
- ownKeys(target)返回一个数组拦截Object.getOwnPropertyPropertyNames(proxy)、Object.getOwnPropertyPropertySymbols(proxy)、Object.keys(proxy)
- preventExtensions(target)返回一个布尔值拦截Object.preventExtensions(proxy)
- set(target, propKey, value, receiver):返回一个布尔值,拦截对象属性的设置
- setPrototypeOf(target, proto)返回一个布尔值拦截Object.setPrototypeOf(proxy, proto)
如果目标对象是函数,那么还有两种额外操作可以拦截。
- apply方法拦截Proxy实例作为函数调用的操作比如proxy(···)、proxy.call(···)、proxy.apply(···)。
- construct方法拦截Proxy实例作为构造函数调用的操作比如new proxy(···)。
Proxy.revocable方法返回一个可取消的Proxy实例。 Proxy.revocable方法返回一个可取消的Proxy实例。
@ -731,7 +836,7 @@ let target = {};
let handler = {}; let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler); let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123; proxy.foo = 123;
proxy.foo // 123 proxy.foo // 123

View File

@ -52,7 +52,8 @@
- Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法实现数据对象与DOM对象的双向绑定 - Sella Rafaeli, [Native JavaScript Data-Binding](http://www.sellarafaeli.com/blog/native_javascript_data_binding): 如何使用Object.observe方法实现数据对象与DOM对象的双向绑定
- Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介 - Axel Rauschmayer, [Symbols in ECMAScript 6](http://www.2ality.com/2014/12/es6-symbols.html): Symbol简介
- Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解 - Axel Rauschmayer, [Meta programming with ECMAScript 6 proxies](http://www.2ality.com/2014/12/es6-proxies.html): Proxy详解
- Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/)使用Proxy实现元编程 - Daniel Zautner, [Meta-programming JavaScript Using Proxies](http://dzautner.com/meta-programming-javascript-using-proxies/): 使用Proxy实现元编程
- MDN, [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol): Symbol类型的详细介绍
## Iterator和Generator ## Iterator和Generator