乐趣区

关于程序员:快速了解-ES6-的Symbol

Symbol 是 ECMAScript 6 新增的根本数据类型。Symbol 提供的实例是惟一、不可变的。它的用处能够确保对象属性应用惟一标识符,不会产生属性抵触的危险。

Symbol 提供 Symbol() 函数来返回 Symbol 类型的值。语法如下所示:

Symbol(desc)
  • desc 可选的字符串类型的参数。是用来形容 Symbol 的。

通过 Symbol() 函数初始化,能够通过 typeof 运算符辨认 Symbol 类型,当然也能够传入参数对 Symbol 进行形容,但与其 Symbol 定义或标识齐全无关。

let s1 = Symbol();
console.log(typeof s1);    // symbol
let s2 = Symbol('symbol instance');
let s3 = Symbol('symbol instance');
console.log(s1 == s2);    // false
console.log(s2 == s3);    // false

留神,Symbol 不能通过 new 关键字像构造函数一样创立实例,并抛出 TypeError 谬误。这样做是为了防止创立 Symbol 包装对象。

let s = new Symbol();    // TypeError: Symbol is not a constructor

然而,能够应用 Object() 函数创立一个 Symbol 包装器对象。

let s = Symbol("symbol instance");
console.log(typeof s);    // symbol
let o = Object(s);
console.log(typeof o);    // object

全局共享的 Symbol

Symbol() 函数创立的 Symbol 实例不是全局可用的 Symbol 类型。如果须要全局可用的 Symbol 实例,能够应用 Symbol.for(key) 办法和 Symbol.keyFor(key) 办法。

Symbol.for() 办法创立的 Symbol 会被放入一个全局的 Symbol 注册表中。Symbol.for() 并不是每次都会创立一个新的 Symbol 实例,它会首先查看给定的 key 是否曾经在注册表中了。如果是,则会间接返回上次存储的 Symbol 值。否则,它会在新键一个。

let gs1 = Symbol.for('gloal symbol');       //  创立新符号 
let gs2 = Symbol.for('global symbol');  //  重用已有符号
console.log(gs1 === gs2);  // true 

能够应用 Symbol.keyFor() 来查问全局注册表,如果查到与 key 对应的 Symbol 实例,则返回该 Symbol 实例的 key 值,否则返回 undefined

//  创立全局符号 
let s = Symbol.for('foo'); 
console.log(Symbol.keyFor(s));   // foo 
//  创立一般符号 
let s2 = Symbol('bar'); 
console.log(Symbol.keyFor(s2));  // undefined 

如果传给 Symbol.keyFor() 的不是符号,则该办法抛出 TypeError

Symbol.keyFor(123); // TypeError: 123 is not a symbol 

Symbol 作为属性

Symbol 能够作为属性来应用。如下所示:

let s1 = Symbol("property one");

let o = {[s1]: 'symbol value one'
};
console.log(o);    // {[Symbol(property one)]: 'symbol value one' }

Object.getOwnPropertySymbols() 办法会让你在查找给定对象的 Symbol 属性时返回一个 Symbol 类型的数组。

let s1 = Symbol("property one"),
    s2 = Symbol("property two");

let o = {[s1]: 'symbol value one',
    [s2]: 'symbol value two',
    name: 'symbol property sample',
    index: 12
};
console.log(Object.getOwnPropertySymbols(o));    // [Symbol(property one), Symbol(property two) ]

应用 Object.getOwnPropertyNames() 正好相同。如下所示:

console.log(Object.getOwnPropertyNames(o));    // ['name', 'index']

Symbol 原型

所有 Symbol 都继承自 Symbol.prototype,而它也提供了 constructordescription 两个属性。constructor 属性会返回实例原型的函数,默认为 Symbol 函数;而 description 属性是一个只读字符串,返回 Symbol 对象的形容。

Symbol.prototype 也提供了 toString()valueOf() 办法用于返回 Symbol 描述符的字符串办法和对象的原始值。

Symbol.prototype[@@toPrimitive] 会将 Symbol 对象转换为原始值。语法如下:

Symbol()[Symbol.toPrimitive](hint)

Symbol[@@toPrimitive]() 办法返回该 Symbol 对象原始值作为 Symbol 数据模式。hint 参数未被应用。

JavaScript 调用 [@@toPrimitive]() 办法将一个对象转换为原始值示意。你不须要本人调用 [@@toPrimitive]() 办法;当对象须要被转换为原始值时,JavaScript 会主动地调用该办法。

内置的 Symbol 属性

除了本人创立的 Symbol,JavaScript 还内建了一些在 ECMAScript 5 之前没有裸露给开发者的 Symbol,它们代表了外部语言行为。这些 Symbol 最重要的用处之一是从新定义它们,从而扭转原生构造的行为。如从新定义 Symbol.iterator 属性的值,来扭转 for-of 在迭代对象时的行为。

留神,在提到 ECMAScript 标准时,常常会援用符号在标准中的名称,前缀为 @@。比方,@@iterator 指的就是 Symbol.iterator

Symbol.iterator

该属性返回一个对象默认的迭代器,被 for-of 应用。上面自定义一个迭代器。

class List {constructor() {
        this.index = 0;
        this.data = arguments;
    }
    *[Symbol.iterator]() {while(this.index < this.data.length) {yield this.data[this.index++];
        }
    }
}
function iter() {let list = new List("小玲", "小霞", "小星", "小民");
    for (const v of list) {console.log(v);
    }
}
iter();
// 小玲
// 小霞
// 小星
// 小民 

如上所示,通过 for-of 循环对一个对象进行迭代时,@@iterator 办法在不传参的状况下被调用,返回的迭代器用于获取要迭代的值。

Symbol.asyncIterator

一个返回对象默认的异步迭代器的办法。被 for await of 应用。

该办法返回一个对象默认的异步迭代器,由 for-await-of 语句应用。

class List {constructor() {
        this.index = 0;
        this.data = arguments;
    }
    async *[Symbol.asyncIterator]() {while(this.index < this.data.length) {yield new Promise((resolve) => resolve(this.data[this.index++]));
        }
    }
}
async function asyIter() {let list = new List("小玲", "小霞", "小星", "小民");
    for await(const  v of list) {console.log(v);
    }
}
asyIter();
// 小玲
// 小霞
// 小星
// 小民 

如上所示,for-await-of 循环会利用 List 类中的函数执行异步迭代操作。

留神,Symbol.asyncIteratorES2018 标准定义的,只有新版本的浏览器反对它。

Symbol.match

该办法指定了用正则表达式去匹配字符串。String.prototype.match() 办法会调用此函数。

console.log('It\'s a real horror story'.match(/horror/));
/*
[
  'horror',
  index: 12,
  input: "It's a real horror story",
  groups: undefined
]
*/

给这个办法传入非正则表达式值会导致该值被转换为 RegExp 对象。如果想扭转这种行为,让办法间接应用参数,则从新定义 Symbol.match 函数以取代默认对正则表达式求值的行为,从而让 match() 办法应用非正则表达式实例。Symbol.match 函数接管一个参数,就是调用 match() 办法的字符串实例。返回的值没有限度:

class StringMatcher {static [Symbol.match](target) {return target.includes('horror');
    }
    constructor(str) {this.str = str;}
    [Symbol.match](target) {return target.includes(this.str);
    }
}
console.log('It\'s a real horror story'.match(StringMatcher)); // true
console.log('It\'s a real relaxing story'.match(StringMatcher)); // false
console.log('It\'s a real horror story'.match(new StringMatcher('horror'))); // true
console.log('It\'s a real horror story'.match(new StringMatcher('relaxing'))); // false

Symbol.replace

该属性指定了当一个字符串替换所匹配字符串时所调用的办法。供 String.prototype.replace() 办法调用此办法,用于替换子字符串。

console.log('It\'s a real horror story'.replace(/horror/,'relaxing'));
// It's a real relaxing story

而正则表达式的原型上默认由 Symbol.replace 函数定义。给 String.prototype.replace 办法传入非正则表达式值会导致该值被转换为 RegExp 对象。

能够从新定义 Symbol.replace 函数,用来取代默认行为。两种定义 Symbol.replace 函数的形式如下所示:

class StringReplacer {static [Symbol.replace](target, replacement) {return target.split('horror').join(replacement);
    }
    constructor(str) {this.str = str;}
    [Symbol.replace](target, replacement) {return target.split(this.str).join(replacement);
    }
}
console.log('It\'s a real horror story'.replace(StringReplacer,'qux'));    // It's a real qux story

console.log('It\'s a real horror story'.replace(new StringReplacer('horror'),'relaxing'));    // It's a real relaxing story

Symbol.replace 函数接管两个参数,即调用 replace() 办法的字符串实例和替换字符串。

Symbol.search

该办法会返回正则表达式在字符串中匹配的索引。String.prototype.search() 办法会调用此办法,用于查找索引。

console.log('It\'s a real horror story'.search(/horror/));    // 12

能够从新定义 Symbol.search 函数,用来取代默认行为,从而让 search() 办法应用非正则表达式的实例。如下所示:

class StringSearcher {static [Symbol.search](target) {return target.indexOf('qux');
    }
    constructor(str) {this.str = str;}
    [Symbol.search](target) {return target.indexOf(this.str);
    }
}

console.log('It\'s a real horror story'.search(StringSearcher)); // -1
console.log('It\'s a real qux story'.search(StringSearcher)); // 12
console.log('qux, It\'s a real horror story'.search(StringSearcher)); // 0

console.log('It\'s a real horror story'.search(new StringSearcher('qux'))); // -1
console.log('It\'s a real horror story'.search(new StringSearcher('horror'))); // 12
console.log('It\'s a real horror story'.search(new StringSearcher('s'))); // 3

Symbol.search 函数接管一个参数,就是调用 search() 办法的字符串实例。

Symbol.split

该办法会通过一个正则表达式的索引,来分隔字符串。String.prototype.split() 办法会调用此办法。

console.log('It\'s a real horror story'.split(/ /)); // 正则匹配的是空格
// ["It's",'a','real','horror','story']

能够从新定义 Symbol.split 函数,用来取代默认行为,从而让 split() 办法应用非正则表达式实例。

class StringSplitter {static [Symbol.split](target) {return target.split('qux');
    }
    constructor(str) {this.str = str;}
    [Symbol.split](target) {return target.split(this.str);
    }
}
console.log('It\'s a real qux story'.split(StringSplitter));    // ["It's a real", 'story']
console.log('It\'s a real horror story'.split(new StringSplitter(' ')));    // ["It's", 'a', 'real', 'horror', 'story']

Symbol.split 函数接管一个参数,就是调用 split() 办法的字符串实例。

Symbol.hasInstance

该属性用于判断某对象是否为某结构器对象的实例。能够用 Symbol.hasInstance 函数自定义 instanceof 在某个类上的行为。

instanceof 的场景如下:

class Instance {}

let ist = new Instance();
console.log(ist instanceof Instance);    // true

ES6 中,instanceof 会应用 Symbol.hasInstance 属性来确定关系。

console.log(Instance[Symbol.hasInstance](ist));    // true

Symbol.hasInstance 属性定义在 Function 的原型上,默认所有函数和类都能够调用。因为 instanceof 会在原型链上寻找这个属性定义,就跟在原型链上寻找其余属性一样,因而能够在继承的类上通过静态方法从新定义这个属性:

class Instance {}
class SubInstance extends Instance {static [Symbol.hasInstance]() {return false;}
}
let sist = new SubInstance();
console.log(Instance[Symbol.hasInstance](sist)); // true
console.log(sist instanceof Instance);           // true
console.log(SubInstance[Symbol.hasInstance](sist)); // false
console.log(sist instanceof SubInstance);           // false

Symbol.isConcatSpreadable

Symbol.isConcatSpreadable 用于配置某对象作为 Array.prototype.concat() 办法的参数时是否开展其数组元素。

const arr1 = ['a', 'b', 'c'];
const arr2 = [1, 2, 3];
let arr3 = arr1.concat(arr2);
console.log(arr3);    // ['a', 'b', 'c', 1, 2, 3]

这是在 Symbol.isConcatSpreadable = true 时的情景,如果设置为 false,就不会开展数组。

arr2[Symbol.isConcatSpreadable] = false;
let arr4 = arr1.concat(arr2);
console.log(arr4);
/*
[
  'a',
  'b',
  'c',
  [1, 2, 3, [Symbol(Symbol.isConcatSpreadable)]: false ]
]

*/

由上可知,对于数组对象,应用 concat 在默认状况下会将数组中元素开展进行连贯。重置 Symbol.isConcatSpreadable 能够扭转默认行为。

Symbol.unscopables

Symbol.unscopables 指定对象值,其对象所有的以及继承属性,都会从关联对象的 with 环境绑定中排除。设置 Symbol.unscopables 并让其映射对应属性的键值为 true,就能够阻止该属性呈现在 with 环境绑定中。如下所示:

let o = {name: '小玲'};
with (o) {console.log(name); // 小玲
}
o[Symbol.unscopables] = {name: true};
with (o) {console.log(name); // ReferenceError: name is not defined
}

留神,不举荐应用 with,因而也不举荐应用 Symbol.unscopables

Symbol.species

Symbol.species 作为创立派生对象的构造函数。用 Symbol.species 定义动态的 getter 办法,能够笼罩新创建实例的原型定义:

class Array1 extends Array {}
class Array2 extends Array {static get [Symbol.species]() {return Array;}
}
let a1 = new Array1();
console.log(a1 instanceof Array); // true
console.log(a1 instanceof Array1);   // true
a1 = a1.concat('species');
console.log(a1 instanceof Array); // true
console.log(a1 instanceof Array1);   // true
let a2 = new Array2();
console.log(a2 instanceof Array); // true
console.log(a2 instanceof Array2);   // true
a2 = a2.concat('species');
console.log(a2 instanceof Array); // true
console.log(a2 instanceof Array2);   // false

Symbol.toPrimitive

Symbol.toPrimitive 是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。

Symbol.toPrimitive 属性能够将对象转换为相应的原始值。很多内置操作都会尝试将值将对象转换为原始值,包含字符串、数值和未指定的原始类型。如下所示:

class Sample1 {}
let s1 = new Sample1();
console.log(+s1);    // NaN
console.log(`${s1}`);    // [object Object]
console.log(s1 + "");    // [object Object]
class Sample2 {constructor() {this[Symbol.toPrimitive] = function(hint) {switch (hint) {
                case 'number': return 77;
                case 'string': return 'hello world!';
                default: return true;
            }
        }
    }
}
let s2 = new Sample2();
console.log(+s2);    // 77
console.log(`${s2}`);    // hello world!
console.log(s2 + "");    // true

Symbol.toStringTag

Symbol.toStringTag 用于创建对象的默认字符串形容。由 Object.prototype.toString() 调用。许多内置类型曾经指定了这个值,但自定义类实例能够明确定义:

// 没有定义 `Symbol.toStringTag` 时
class StringTag {constructor() {}}
let a = new StringTag();
console.log(a);   // StringTag {}
console.log(a.toString());    // [object Object]

// 定义 `Symbol.toStringTag` 时
class StringTag {constructor() {this[Symbol.toStringTag] = 'StringTag';
    }
}
let a = new StringTag();
console.log(a);    // StringTag {[Symbol(Symbol.toStringTag)]: 'StringTag' }
console.log(a.toString());    // [object StringTag]

更多内容请关注公众号「 海人为记

退出移动版