es6-Symbol

时间:2019-09-18
本文章向大家介绍es6-Symbol,主要包括es6-Symbol使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

es5定义属性名只能使用字符串,这就可能导致一个问题,就是你定义的属性名跟别人定义的属性名冲突。为了解决这个问题,es6引入了一个新的原始数据类型Symbol。Symbol的值是通过Symbol函数生成的,所以现在的属性名可以有两种类型,一种是字符串,另一种就是Symbol类型,凡是Symbol类型的属性名就是独一无二的,不会与其它任何属性名冲入。

Symbol概述

定义一个Symbol类型的变量看看

let s = Symbol();
typeof s;    // "symbol"

嗯,变量s果然是Symbol类型,不是字符串了。

Symbol是一种原始数据类型,不是对象,所以Symbol不能new(会报错),Symbol不能添加属性和方法。基本上,它是一种类似于字符串的数据类型。

Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。如果不加参数,控制台打印的都是Symbol,难以区分。
  let s1 = Symbol('foo');

let s2 = Symbol('bar'); 

  s1;    //Symbol(foo)

s2;    //Symbol(bar) 

let s3 = Symbol('foo');

s1 和 s3是不相等的两个值。

Symbol还有以下一些特征,Symbol可以显示转为字符串 

s1.toString();    // "Symbol(foo)"

如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。

var a = {};

var b = Symbol(a);

  b;   // "Symbol([object Object])"

a['toString'] = function () { return 'a symbol' } 

  var c = Symbol(a);

  c;  // "Symbol(a symbol)"

也可以转为布尔类型

 Boolean(s1);    // true

但是不能转为数字,也不能与其它数据类型参与计算,会报错。

获取Symbol的描述,可以通过 Symbol.prototype提供的description属性获取

s1.description    //"foo"

------------------------------------

作为属性名的Symbol 

通过方括号结构和Object.defineProperty将对象的属性名指定为一个Symbol值。

var mySymbol = Symbol(); 

//第一种写法

var a = {};

a[mySymbol] = 'hello'; 

//第二种写法

var a = {

[mySymbol]: 'hello'

};

//第三种写法

var a = {};

 Object.defineProperty(a, mySymbol, {value: 'hello'});

//注意,Symbol值作为对象属性名时不能用点运算符。

使用Symbol作为属性名不会出现同名的属性

 1.  let obj = { 

  type: 'PC',        //小A定义的

type: 'NEW'      //小B定义的,把小A的覆盖了

2.     const type = 'type';    //小A定义的

const type1 = 'type';    //小B定义的

  let obj = { 

  [type]: 'PC',       

[type1]: 'NEW'     //把小A的覆盖了

}

3.     使用Symbol不会被覆盖

const type = Symbol('type');    //小A定义的

const type1 =  Symbol('type');    //小B定义的

  let obj = { 

  [type]: 'PC',       

[type1]: 'NEW'     

}

------------------------------------

 消除魔术字符串

魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。

function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case 'Triangle': // 魔术字符串
      area = .5 * options.width * options.height;
      break;
    /* ... more code ... */
  }
  return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串


'Triangle'就是一个魔术字符串,它出现多次,不利于维护,所以我们要把他定义成一个变量或者属性,消除强耦合。


const shapeType = {

triangle:Symbol('triangle')            //这里如果triangle的值是什么并不重要,只是一个标识,不会与业务耦合

  //triangle: 'Triangle'                       //值容易与其它属性的值相同
};
function getArea(shape, options) {
  let area = 0;
  switch (shape) {
    case shapeType.triangle:
      area = .5 * options.width * options.height;
      break;
  }
  return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });

------------------------------------

属性名的遍历

在a.js文件中定义一个对象testSymbol,并将它对外暴露

let t2 = Symbol('t2');

export let testSymbol = {
t1: 't1value',

[t2]: 't2value' 

};

在b.js中引入a.js,只能访问到属性t1, 无法访问到t2属性

import { testSymbol } from '/a.js'

console.log(testSymbol['t1']);  //t1value

  console.log(testSymbol[t2]);  //Uncaught (in promise) ReferenceError: t2 is not defined

console.log(Object.keys(testSymbol));                                  //["t1"]
    console.log(Object.getOwnPropertyNames(testSymbol));        //["t1"]
    console.log(Reflect.ownKeys(testSymbol));                            //["t1", Symbol(t2)]
    console.log(Object.getOwnPropertySymbols(testSymbol));      //[Symbol(t2)]

for(let i in testSymbol) { console.log(i); }                             //"t1" 

所以可以利用Symbol定义私有属性

-----------------------------------

Symbol.for()    Symbol.keyFor()

  Symbol()和Symbol.for()这两个方法都会生产Symbol对象,Symbol.for()与Symbol()的不同是它生成的Symbol对象会被注册到全局环境中。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。

let s1 = Symbol.for('foo');

let s2 = Symbol.for('foo');

console.log(s1 === s2);       // true

         Symbol.for('foo')先检查全局的注册表中有没有key是'foo'的Symbol对象,如果存在就返回这个对象,如不不存在就重新生成。

 Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。
 Symbol.keyFor(s1) // "foo"

------------------------------------

内置的Symbol值

es6提供了11个内置的Symbol值,指向了语言内部使用的方法,实现元编程(对js默认的行为做操作)。

  1. Symbol.hasInstance  

 对象的Symbol.hasInstance指向内部方法。当对其它对象(a)使用instanceof操作符判断是不是该对象(b)的实例时,会调用该对象(b)的[Symbol.hasInstance
]方法。比如a instanceof b,在方法内部实际调用的是b[Symbol.hasInstance](a)。

let a = [1,2,3], b;
class MyArray {
[Symbol.hasInstance] (val) {
return val instanceof Array;
}
}
b = new MyArray();
console.log(a instanceof b); //true

  2.Symbol.isConcatSpreadable

对象的Symbol.isConcatSpreadable属性是一个布尔值,它表示对该对象使用Array.prototype.concat()方法时,该对象是否可以展开。

当对象是数组时,Symbol.isConcatSpreadable的默认值是undefined,默认展开。设置为false不可以展开,设置为true可以展开。

当对象是一个类似数组的对象时,Symbol.isConcatSpreadable的默认值是undefined,默认不展开。设置为false不可以展开,设置为true可以展开。

  默认情况:

let a1 = [3, 4];
let a2 = {0: 3, 1: 4, length: 2};
console.log([1].concat(a1, [5, 6])); // [1, 3, 4, 5, 6]
console.log([1].concat(a2, [5, 6])); // [1, {0: 3, 1: 4, length: 2}, 5, 6]

  设置一下:

a1[Symbol.isConcatSpreadable] = false;
a2[Symbol.isConcatSpreadable] = true;
console.log([1].concat(a1, [5, 6])); // [1, 3, [4, 5], 6]
console.log([1].concat(a2, [5, 6])); // [1, 3, 4, 5, 6]

  Symbol.isConcatSpreadable还可以定义在类中

class A1 extends Array {
constructor (args) {
super(args);
this[Symbol.isConcatSpreadable] = true;
}
}
class A2 extends Array {
constructor (args) {
super(args);
}
get [Symbol.isConcatSpreadable] () {
return false;
}
}
let a1Obj = new A1();
let a2Obj = new A2();
a1Obj[0] = 3;
a1Obj[1] = 4;
a2Obj[0] = 5;
a2Obj[1] = 6;
console.log([1, 2].concat(a1Obj, a2Obj)); //[1, 2, 3, 4, [5, 6]]

A1是定义在实例上,A2是定义在类本身。

  3. Symbol.species

  对象的Symbol.species属性,指向构造函数。当创建衍生对象时,会调用这个属性。

class MyArray extends Array {};
let ma1 = new MyArray(1, 2, 3);
let ma2 = ma1.map(item => item * 2);
let ma3 = ma1.filter(item => item > 1);
console.log(ma2 instanceof MyArray); //true
console.log(ma2 instanceof Array); //true
console.log(ma3 instanceof MyArray); //true
console.log(ma3 instanceof Array); //true

从上面例子看,类MyArray继承Array,ma1是MyArray的实例,ma2和ma3是ma1的衍生对象。ma2和ma3都是通过数组的方法生成出来的对象,也就是说他们应该是Array的实例,但是他们并不是MyAarry的实力,所以上面得到的结果并不是我们想要的。那么通过设置Symbol.species属性就可以解决这个问题,我们要设置MyArray的Symbol.species属性。

定义Symbol.species使用get取值器。Symbol.species是类的静态属性。Symbol.species内部会指向指定的构造函数(现在我们需要指向Array)。

class MyArray extends Array {
static get [Symbol.species] () {
return Array;
}
};

通过上面的设置后,ma2和ma3只是Array的实例,并不是MyArray的实例。

       Symbol.species主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。

  4.Symbol.match (Symbol.replace, Symbol.search,Symbol.split都是类似的)

对象的Symbol.match属性,指向一个方法,当执行str.match(myObj)时,如果该属性存在,会调用它,返回该方法的返回值。

class MyMatcher {
constructor (bstr) {
this.bstr = bstr;
}
[Symbol.match] (str) {
return this.bstr.indexOf(str);
}
}
'e'.match(new MyMatcher('hello world'));

  5. Symbol.replace

class MyReplace {
constructor (bstr) {
this.bstr = bstr;
}
[Symbol.replace](oldStr, newStr){
console.log('"'+ oldStr +'"被替换成了"'+ newStr +'"');
return this.bstr.replace(oldStr, newStr);
}
}
let res = '一'.replace(new MyReplace('我有一盆花'), '很多');
console.log(res);

   Symbol.replace方法会收到两个参数,第一个参数是replace  方法正在作用的对象,上面例子第一个参数是“一”,第二个参数是替换后的值“很多”

6. Symbol.search 

class MySearch {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
'foobar'.search(new MySearch('foo')) // 0

  7. Symbol.split

class MySplitter {
constructor(value) {
this.value = value;
}
[Symbol.split](string) {
let index = string.indexOf(this.value);
if (index === -1) {
return string;
}
return [
string.substr(0, index),
string.substr(index + this.value.length)
];
}
}
'foobar'.split(new MySplitter('foo')) // ['', 'bar']
'foobar'.split(new MySplitter('bar')) // ['foo', '']
'foobar'.split(new MySplitter('baz')) // 'foobar'

  8. Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法。对象执行for...of时,会调用该对象的Symbol.iterator方法,可参见Iterator 和 for...of 循环

9. Symbol.toPrimitive

  对象的Symbol.toPrimitive属性,指向一个方法。把一个对象转为原始类型值时。会调用Symbol.toPrimitive方法,返回值是该对象的原始类型值。Symbol.toPrimitive接受一个参数,该参数表示当前运算的模式,一共有三种模式:

Number:该场合需要转成数值

String:该场合需要转成字符串

Default: 该场合可以转成数值,也可以转成字符串

let obj = {
[Symbol.toPrimitive] (hint) {
switch (hint) {
case 'number':
return 1;
case 'string':
return 's';
case 'default':
return 'default';
default:
return new Error();
}
}
}
console.log(obj * 2); //2
console.log(String(obj)); //s
console.log(obj + 'abc'); //defaultabc

10.  Symbol.toStringTag

对象的Symbol.toStringTag属性,指向一个方法。我们在获取一个对象的类型是会调用Object.prototype.toString.call('abc')  返回的是[object String]。这里,Symbol.toStringTag的返回值就会出现在toString方法的返回字符串中(就是结果中String的部分),看个例子。

class StringTag {
get [Symbol.toStringTag] () {
return 'arr';
}
}
Object.prototype.toString.call(new StringTag()); //"[object arr]"

11. Symbol.unscopables

  对象的Symbol.unscopables属性,指向一个对象。指定了使用with关键字时,哪些属性是被with环境排除的。

举个例子,Array.prototype[Symbol.unscopables] 默认返回的是

  1. copyWithintrue
  2. entriestrue
  3. filltrue
  4. findtrue
  5. findIndextrue
  6. flattrue
  7. flatMaptrue
  8. includestrue
  9. keystrue
  10. valuestrue

  我们来验证一下

let a = [1, 2, 3]
with(a){console.log(indexOf)} //ƒ indexOf() { [native code] }
with(a){console.log(findIndex)} //VM15227:1 Uncaught ReferenceError: findIndex is not defined

原文地址:https://www.cnblogs.com/littlechen/p/11425022.html