ES6系列目录
- 1 let 和 const命令
- 2 变量的解构赋值
- 3 字符串的拓展
- 4 正则的拓展
- 5 数值的拓展
- 6 函数的拓展
- 7 数组的拓展
- 8 对象的拓展
- 9 Symbol
- 10 Set和Map数据结构
- 11 Proxy
- 12 Promise对象
- 13 Iterator和 for...of循环
- 14 Generator函数和应用
- 15 Class语法和继承
- 16 Module语法和加载实现
所有整理的文章都收录到我《Cute-JavaScript》系列文章中,访问地址:http://js.pingan8787.com
8 对象的拓展
8.1 属性的简洁表示
-
let a = 'a1';
-
let b = { a }; // b => { a : 'a1' }
-
// 等同于
-
let b = { a : a };
-
function f(a, b){
-
return {a, b};
-
}
-
// 等同于
-
function f (a, b){
-
return {a:a ,b:b};
-
}
-
let a = {
-
fun () {
-
return 'leo';
-
}
-
}
-
// 等同于
-
let a = {
-
fun : function(){
-
return 'leo';
-
}
-
}
8.2 属性名表达式
JavaScript
提供2种方法定义对象的属性。
-
// 方法1 标识符作为属性名
-
a.f = true;
-
// 方法2 字符串作为属性名
-
a['f' + 'un'] = true;
延伸出来的还有:
-
let a = 'hi leo';
-
let b = {
-
[a]: true,
-
['a'+'bc']: 123,
-
['my' + 'fun'] (){
-
return 'hi';
-
}
-
};
-
// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'
-
// b[a] => true ; b['abc'] => 123 ; b['myfun'] => ƒ ['my' + 'fun'] (){ return 'hi'; }
注意:
属性名表达式不能与简洁表示法同时使用,否则报错。
-
// 报错
-
let a1 = 'aa';
-
let a2 = 'bb';
-
let b1 = {[a1]};
-
// 正确
-
let a1 = 'aa';
-
let b1 = { [a1] : 'bb'};
8.3 Object.is()
Object.is()
用于比较两个值是否严格相等,在ES5时候只要使用相等运算符( ==
)和严格相等运算符( ===
)就可以做比较,但是它们都有缺点,前者会自动转换数据类型,后者的 NaN
不等于自身,以及 +0
等于 -0
。
-
Object.is('a','a'); // true
-
Object.is({}, {}); // false
-
// ES5
-
+0 === -0 ; // true
-
NaN === NaN; // false
-
// ES6
-
Object.is(+0,-0); // false
-
Object.is(NaN,NaN); // true
8.4 Object.assign()
Object.assign()
方法用于对象的合并,将原对象的所有可枚举属性复制到目标对象。
基础用法:
第一个参数是目标对象,后面参数都是源对象。
-
let a = {a:1};
-
let b = {b:2};
-
Object.assign(a,b); // a=> {a:1,b:2}
注意:
- 若目标对象与源对象有同名属性,则后面属性会覆盖前面属性。
-
let a = {a:1, b:2};
-
let b = {b:3, c:4};
-
Object.assign(a, b); // a => {a:1, b:3, c:4}
- 若只有一个参数,则返回该参数。
-
let a = {a:1};
-
Object.assign(a) === a; // true
- 若参数不是对象,则先转成对象后返回。
-
typeof Object.assign(2); // 'object'
- 由于
undefined
或 NaN
无法转成对象,所以做为参数会报错。
-
Object.assign(undefined) // 报错
-
Object.assign(NaN); // 报错
-
Object.assign()
实现的是浅拷贝。
Object.assign()
拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
-
let a = {a: {b:1}};
-
let b = Object.assign({},a);
-
a.a.b = 2;
-
console.log(b.a.b); // 2
- 将数组当做对象处理,键名为数组下标,键值为数组下标对应的值。
-
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]
9 Symbol
9.1 介绍
ES6引入 Symbol
作为一种新的原始数据类型,表示独一无二的值,主要是为了防止属性名冲突。
ES6之后,JavaScript一共有其中数据类型: Symbol
、 undefined
、 null
、 Boolean
、 String
、 Number
、 Object
。
简单实用:
-
let a = Symbol();
-
typeof a; // "symbol"
注意:
-
Symbol
函数不能用 new
,会报错。由于 Symbol
是一个原始类型,不是对象,所以不能添加属性,它是类似于字符串的数据类型。 -
Symbol
都是不相等的,即使参数相同。
-
// 没有参数
-
let a1 = Symbol();
-
let a2 = Symbol();
-
a1 === a2; // false
-
// 有参数
-
let a1 = Symbol('abc');
-
let a2 = Symbol('abc');
-
a1 === a2; // false
-
Symbol
不能与其他类型的值计算,会报错。
-
let a = Symbol('hello');
-
a + " world!"; // 报错
-
`${a} world!`; // 报错
Symbol可以显式转换为字符串:
-
let a1 = Symbol('hello');
-
String(a1); // "Symbol(hello)"
-
a1.toString(); // "Symbol(hello)"
Symbol可以转换为布尔值,但不能转为数值:
-
let a1 = Symbol();
-
Boolean(a1);
-
!a1; // false
-
Number(a1); // TypeError
-
a1 + 1 ; // TypeError
9.2 Symbol作为属性名
好处:防止同名属性,还有防止键被改写或覆盖。
-
let a1 = Symbol();
-
// 写法1
-
let b = {};
-
b[a1] = 'hello';
-
// 写法2
-
let b = {
-
[a1] : 'hello'
-
}
-
// 写法3
-
let b = {};
-
Object.defineProperty(b, a1, {value : 'hello' });
-
// 3种写法 结果相同
-
b[a1]; // 'hello'
需要注意: Symbol作为对象属性名时,不能用点运算符,并且必须放在方括号内。
-
let a = Symbol();
-
let b = {};
-
// 不能用点运算
-
b.a = 'hello';
-
b[a] ; // undefined
-
b['a'] ; // 'hello'
-
// 必须放在方括号内
-
let c = {
-
[a] : function (text){
-
console.log(text);
-
}
-
}
-
c[a]('leo'); // 'leo'
-
// 上面等价于 更简洁
-
let c = {
-
[a](text){
-
console.log(text);
-
}
-
}
常常还用于创建一组常量,保证所有值不相等:
-
let a = {};
-
a.a1 = {
-
AAA: Symbol('aaa'),
-
BBB: Symbol('bbb'),
-
CCC: Symbol('ccc')
-
}
9.3 应用:消除魔术字符串
魔术字符串:指代码中多次出现,强耦合的字符串或数值,应该避免,而使用含义清晰的变量代替。
-
function f(a){
-
if(a == 'leo') {
-
console.log('hello');
-
}
-
}
-
f('leo'); // 'leo' 为魔术字符串
常使用变量,消除魔术字符串:
-
let obj = {
-
name: 'leo'
-
};
-
function f (a){
-
if(a == obj.name){
-
console.log('hello');
-
}
-
}
-
f(obj.name); // 'leo'
使用Symbol消除强耦合,使得不需关系具体的值:
-
let obj = {
-
name: Symbol()
-
};
-
function f (a){
-
if(a == obj.name){
-
console.log('hello');
-
}
-
}
-
f(obj.name);
9.4 属性名遍历
Symbol作为属性名遍历,不出现在 for...in
、 for...of
循环,也不被 Object.keys()
、 Object.getOwnPropertyNames()
、 JSON.stringify()
返回。
-
let a = Symbol('aa'),b= Symbol('bb');
-
let obj = {
-
[a]:'11', [b]:'22'
-
}
-
for(let k of Object.values(obj)){console.log(k)}
-
// 无输出
-
let obj = {};
-
let aa = Symbol('leo');
-
Object.defineProperty(obj, aa, {value: 'hi'});
-
for(let k in obj){
-
console.log(k); // 无输出
-
}
-
Object.getOwnPropertyNames(obj); // []
-
Object.getOwnPropertySymbols(obj); // [Symbol(leo)]
Object.getOwnPropertySymbols
方法返回一个数组,包含当前对象所有用做属性名的Symbol值。
-
let a = {};
-
let a1 = Symbol('a');
-
let a2 = Symbol('b');
-
a[a1] = 'hi';
-
a[a2] = 'oi';
-
let obj = Object.getOwnPropertySymbols(a);
-
obj; // [Symbol(a), Symbol(b)]
另外可以使用 Reflect.ownKeys
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
-
let a = {
-
[Symbol('leo')]: 1,
-
aa : 2,
-
bb : 3,
-
}
-
Reflect.ownKeys(a); // ['aa', 'bb',Symbol('leo')]
由于Symbol值作为名称的属性不被常规方法遍历获取,因此常用于定义对象的一些非私有,且内部使用的方法。
9.5 Symbol.for()、Symbol.keyFor()
- Symbol.for()
用于重复使用一个Symbol值,接收一个字符串作为参数,若存在用此参数作为名称的Symbol值,返回这个Symbol,否则新建并返回以这个参数为名称的Symbol值。
-
let a = Symbol.for('aaa');
-
let b = Symbol.for('aaa');
-
a === b; // true
Symbol()
和 Symbol.for()
区别:
-
Symbol.for('aa') === Symbol.for('aa'); // true
-
Symbol('aa') === Symbol('aa'); // false
- Symbol.keyFor()
用于返回一个已使用的Symbol类型的key:
-
let a = Symbol.for('aa');
-
Symbol.keyFor(a); // 'aa'
-
let b = Symbol('aa');
-
Symbol.keyFor(b); // undefined
9.6 内置的Symbol值
ES6提供11个内置的Symbol值,指向语言内部使用的方法:
- 1.Symbol.hasInstance
当其他对象使用 instanceof
运算符,判断是否为该对象的实例时,会调用这个方法。比如, fooinstanceofFoo
在语言内部,实际调用的是 Foo[Symbol.hasInstance](foo)
。
-
class P {
-
[Symbol.hasInstance](a){
-
return a instanceof Array;
-
}
-
}
-
[1, 2, 3] instanceof new P(); // true
P是一个类,new P()会返回一个实例,该实例的 Symbol.hasInstance
方法,会在进行 instanceof
运算时自动调用,判断左侧的运算子是否为 Array
的实例。
- 2.Symbol.isConcatSpreadable
值为布尔值,表示该对象用于 Array.prototype.concat()
时,是否可以展开。
-
let a = ['aa','bb'];
-
['cc','dd'].concat(a, 'ee');
-
// ['cc', 'dd', 'aa', 'bb', 'ee']
-
a[Symbol.isConcatSpreadable]; // undefined
-
let b = ['aa','bb'];
-
b[Symbol.isConcatSpreadable] = false;
-
['cc','dd'].concat(b, 'ee');
-
// ['cc', 'dd',[ 'aa', 'bb'], 'ee']
- 3.Symbol.species
指向一个构造函数,在创建衍生对象时会使用,使用时需要用 get
取值器。
-
class P extends Array {
-
static get [Symbol.species](){
-
return this;
-
}
-
}
解决下面问题:
-
// 问题: b应该是 Array 的实例,实际上是 P 的实例
-
class P extends Array{}
-
let a = new P(1,2,3);
-
let b = a.map(x => x);
-
b instanceof Array; // true
-
b instanceof P; // true
-
// 解决: 通过使用 Symbol.species
-
class P extends Array {
-
static get [Symbol.species]() { return Array; }
-
}
-
let a = new P();
-
let b = a.map(x => x);
-
b instanceof P; // false
-
b instanceof Array; // true
- 4.Symbol.match
当执行 str.match(myObject)
,传入的属性存在时会调用,并返回该方法的返回值。
-
class P {
-
[Symbol.match](string){
-
return 'hello world'.indexOf(string);
-
}
-
}
-
'h'.match(new P()); // 0
- 5.Symbol.replace 当该对象被
String.prototype.replace
方法调用时,会返回该方法的返回值。
-
let a = {};
-
a[Symbol.replace] = (...s) => console.log(s);
-
'Hello'.replace(a , 'World') // ["Hello", "World"]
- 6.Symbol.hasInstance
当该对象被 String.prototype.search
方法调用时,会返回该方法的返回值。
-
class P {
-
constructor(val) {
-
this.val = val;
-
}
-
[Symbol.search](s){
-
return s.indexOf(this.val);
-
}
-
}
-
'hileo'.search(new P('leo')); // 2
- 7.Symbol.split
当该对象被 String.prototype.split
方法调用时,会返回该方法的返回值。
-
// 重新定义了字符串对象的split方法的行为
-
class P {
-
constructor(val) {
-
this.val = val;
-
}
-
[Symbol.split](s) {
-
let i = s.indexOf(this.val);
-
if(i == -1) return s;
-
return [
-
s.substr(0, i),
-
s.substr(i + this.val.length)
-
]
-
}
-
}
-
'helloworld'.split(new P('hello')); // ["hello", ""]
-
'helloworld'.split(new P('world')); // ["", "world"]
-
'helloworld'.split(new P('leo')); // "helloworld"
- 8.Symbol.iterator
对象进行 for...of
循环时,会调用 Symbol.iterator
方法,返回该对象的默认遍历器。
-
class P {
-
*[Symbol.interator]() {
-
let i = 0;
-
while(this[i] !== undefined ) {
-
yield this[i];
-
++i;
-
}
-
}
-
}
-
let a = new P();
-
a[0] = 1;
-
a[1] = 2;
-
for (let k of a){
-
console.log(k);
-
}
- 9.Symbol.toPrimitive
该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。调用时,需要接收一个字符串参数,表示当前运算模式,运算模式有:
- Number : 此时需要转换成数值
- String : 此时需要转换成字符串
- Default : 此时可以转换成数值或字符串
-
let obj = {
-
[Symbol.toPrimitive](hint) {
-
switch (hint) {
-
case 'number':
-
return 123;
-
case 'string':
-
return 'str';
-
case 'default':
-
return 'default';
-
default:
-
throw new Error();
-
}
-
}
-
};
-
2 * obj // 246
-
3 + obj // '3default'
-
obj == 'default' // true
-
String(obj) // 'str'
- 10.Symbol.toStringTag
在该对象上面调用 Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在 toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制 [objectObject
]或 [objectArray]
中 object
后面的那个字符串。
-
// 例一
-
({[Symbol.toStringTag]: 'Foo'}.toString())
-
// "[object Foo]"
-
// 例二
-
class Collection {
-
get [Symbol.toStringTag]() {
-
return 'xxx';
-
}
-
}
-
let x = new Collection();
-
Object.prototype.toString.call(x) // "[object xxx]"
- 11.Symbol.unscopables
该对象指定了使用with关键字时,哪些属性会被with环境排除。
-
// 没有 unscopables 时
-
class MyClass {
-
foo() { return 1; }
-
}
-
var foo = function () { return 2; };
-
with (MyClass.prototype) {
-
foo(); // 1
-
}
-
// 有 unscopables 时
-
class MyClass {
-
foo() { return 1; }
-
get [Symbol.unscopables]() {
-
return { foo: true };
-
}
-
}
-
var foo = function () { return 2; };
-
with (MyClass.prototype) {
-
foo(); // 2
-
}
上面代码通过指定 Symbol.unscopables
属性,使得 with
语法块不会在当前作用域寻找 foo
属性,即 foo
将指向外层作用域的变量。