2019-02-03

Symbol的学习

概述

Symbol是一种新的原始数据类型,是es6的第七种数据类型,其余六种分别是:undefined,null,布尔值(Boolean),字符串(String),数值(Number),对象(Object)。
通过Symbol函数创建一个Symbol,对象的属性名有两种类型:字符串和Symbol.Symbol 的属性名是独一无二的,不会与任何属性名冲突。
声明Symbol变量

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

特点

  • 不能使用new创建,因为它是原始数据类型,而不是对象或者数组
  • 为了区分,可以接受一个字符串的参数
let s1 =Symbol('foo')
let s2= Symbol('boo')

s1//Symbol(foo)
s2//Symbol(boo)

s1.toString()//"Symbol(foo)"
s2.toString()//"Symbol(boo)"
  • 如果参数是对象的话,需要将参数转换成字符串
let obj={
  toString(){
     return "abc"
 }
}
let s =Symbol(obj)
s//Symbol(abc)
  • Symbol是独一无二的,因此无参和同参的函数返回都是不相等
//无参
let s1 = Symbol();
let s2 = Symbol();
s1 === s2;//false

//有参
let s3 =Symbol("foo");
let s4 =Symbol("foo");
s3 === s4;//false
  • 不能与其他类型进行运算
let sym =Symbol("My Symbol");
" my symbol is"+ sym;
//TypeError: Cannot convert a Symbol value to a string
'my symbol is $(sym)'
//TypeError: Cannot convert a Symbol value to a string
  • 可以直接转换成字符串和布尔值,但是不能转换成数值
let sym = Symbol("My Symbol")
String(sym)//"Symbol(My Symbol)"
sym.toString()//"Symbol(My Symbol)"

Boolean(sym)//true
!sym//false

Number(sym)//TypeError: Cannot convert a Symbol value to a number
sym+2 //TypeError: Cannot convert a Symbol value to a number

作为属性名的Symbol

  • 作为对象的属性名,保证不会出现相同的标识符
let mySymbol = Symbol();
//第一种写法
let a ={};
a = {
  [mySymbol]:"hello"
}

//第二种
let a={
  [mySymbol]:"hello"
}

//第三种
let a ={};
Object.defineProperty(a,mySymbol,{value:'hello'})
//{ [Symbol()]: 'Hello!' }
a[mySymbol]//"hello"
  • 不能使用点运算符,因为点运算符后面跟着的是字符串,不能识别为Symbol值
const mySymbol = Symbol();
const a ={};

a.mySymbol="hello";
a[mySymbol]//undefined
a['mySymbol']//"hello"
  • 如果在对象内部,必须使用方括号,否则键名将会变成字符串,而不是Symbol值
let mySymbol = Symbol();
let obj ={
  [mySymbol](arg)
}

obj[mySymbol](123)
  • 定义一组互不相等的常量,常用于对象属性值和switch语句
const log ={}; 
log.levels={
   DEBUG:Symbol('debug'),
   INFO:Symbol('info').
   WARN:Symbol('warn')
}
//{ DEBUG: Symbol(debug), INFO: Symbol(info), WARN: Symbol(warn) }
console.log(log.levels.DEBUG,'debug message')
//Symbol(debug) 'debug message'

const RED =Symbol()
const GREEN = Symbol()

function getComplement(color){
   switch(color){
      case RED:
         return GREEN;
         break;
      case GREEN:
         return RED;
         break;
      default:throw new Error("undefined color");
  }
}

实例:消除魔术字符串

魔术字符串就是代码中重复多次的,与代码形成强耦合关系的某一具体字符串,良好的代码风格,就是要尽量避免魔术字符串的出现

function getArea(shape,options){
    switch(shape){
       case 'triangle':
           area = .5 *options.width*options.height;
           break;
    }
    return area;
}

getArea('trangle',{width:100,height:500})//魔术字符串为triangle
//250000

消除方法

  • 可以将triangle变成一个变量
  • 写成Symbol值
const shapeType ={
   triangle:'triangle'
   //triangle:Symbol
};

function getArea(shape,options){
   switch(shape){
        case shapeType.triangle:
           area = .5* options.width * options.height;
           break;
    }  
    return area;
}
getArea(shapeType.triangle,{width:100,height:100});
//5000

有利于代码的修改和维护

属性名的遍历

在Symbol作为属性名遍历的时候,不会for ... in 或者for...of循环以及Object.keys(),Object.getOwnPropertyName(),JSON.stringify()等方法获取。
一般使用getOwnPropertySymbols()和Reflect.ownKeys()

  • Object.getOwnpropertySymbols()的参数是一个对象,返回是一个数组,成员是当前对象所有属性名的Symbol值
const obj = {};

const a= Symbol('a');
const b=Symbol('b');

obj[a] = 'hello';//"hello"
obj[b] ='world'; //"world"

let propertySymbol =Object.getPropertySymbol(obj);
[Symbol(a),Symbol(b)]
  • 与for ...in和Object.getOwnAPropertyNames()比较
let obj ={};
let mySymbol =Symbol('foo');
Object.getProperty(obj,mySymbol,{value:'hello!'});
//{[Symbol(foo)]:'hello!'}
for( let i in obj){
  console.log(i)//无输出
}

Oject.getOwnPropertyNanmes(obj);//[]
Oject.getOwnPropertySymbols(obj);//[Symbol(foo)]
  • Reflect.ownKeys()获取所有包括常规和Symbol的属性名.返回一个数组.成员当前对象为所有属性名
let mySymbol=Symbol();
   let obj1 ={
   [mySymbol]:'num1',
   num1:1,
   flag:true
};

Reflect.ownKeys(obj1);//[ 'num1', 'flag', Symbol() ]

Symbol.for()和Symbol.keyFor()

  • Symbol.for接受一个字符串参数作为键名,在全局环境中进行查找,如果已经存在,直接返回值,如果不存在,则新建,但是Symbol()不管存在与否都会新建,
let sym1 = Symbol.for('foo');
let sym2 =Symbol.for('foo');
 sym1===sym2;//true;
let sym3=Symbol('foo');
sym3=sym1;//false
  • Symbol.keyFor返回一个已经登记的Symbol的key
let s1=Symbol.for('foo');
Symbol.keyFor(s1);//"foo"

let s2=Symbol();
Symbol.keyFor(s2);//undefined

内置的Symbol值

Symbol.hasInstance()

指向一个内部方法,当其他对象使用instanceOf时,判断是否为该对象的实例时调用

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

Symbol.isConcatSpreadable

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

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 a1 = new A1();
a1[0] = 3;
a1[1] = 4;
let a2 = new A2();
a2[0] = 5;
a2[1] = 6;
[1, 2].concat(a1).concat(a2)
// [1, 2, 3, 4, [5, 6]]

Symbol.species

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

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

const a = new MyArray();
const b = a.map(x => x);

b instanceof MyArray // false
b instanceof Array // true

Symbol.match

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

String.prototype.match(regexp)
// 等同于
regexp[Symbol.match](this)

class MyMatcher {
  [Symbol.match](string) {
    return 'hello world'.indexOf(string);
  }
}

'e'.match(new MyMatcher()) // 1

Symbol.repalce

对象的Symbol.replace属性,指向一个方法,当该对象被String.prototype.replace方法调用时,会返回该方法的返回值。

const x = {};
x[Symbol.replace] = (...s) => console.log(s);

'Hello'.replace(x, 'World') // ["Hello", "World"]

Symbol.search

对象的Symbol.search属性,指向一个方法,当该对象被String.prototype.search方法调用时,会返回该方法的返回值。

String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)

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

Symbol.split

对象的Symbol.split属性,指向一个方法,当该对象被String.prototype.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'

Symbol.iterator

对象的Symbol.iterator属性,指向该对象的默认遍历器方法

const myIterable = {};
myIterable[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
};

[...myIterable] // [1, 2, 3]

Symbol.toPrimitive

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'

Symbol.toStringTag

对象的Symbol.toStringTag属性,指向一个方法。

({[Symbol.toStringTag]: 'Foo'}.toString())
// "[object Foo]"

// 例二
class Collection {
  get [Symbol.toStringTag]() {
    return 'xxx';
  }
}
let x = new Collection();
Object.prototype.toString.call(x) // "[object xxx]"

Symbol.unscopables

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

Array.prototype[Symbol.unscopables]
// {
//   copyWithin: true,
//   entries: true,
//   fill: true,
//   find: true,
//   findIndex: true,
//   includes: true,
//   keys: true
// }

Object.keys(Array.prototype[Symbol.unscopables])
// ['copyWithin', 'entries', 'fill', 'find', 'findIndex', 'includes', 'keys']

参考文献

Symbol 《ECMAscript入门》作者:阮一峰

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • [TOC] 参考阮一峰的ECMAScript 6 入门参考深入浅出ES6 let和const let和const都...
    郭子web阅读 1,768评论 0 1
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,205评论 0 4
  • 1 Object 对象 教程:https://wangdoc.com/javascript/stdlib/obje...
    智勇双全的小六阅读 1,560评论 0 0
  • 上证想回调夯实基础,深指被压制要回头找援军,创指要再次探底确认前低。
    向延炳阅读 114评论 0 0
  • [NodeJS] 优缺点及适用场景讨论 概述: NodeJS宣称其目标是“旨在提供一种简单的构建可伸缩网络程序的方...
    笑极阅读 9,124评论 1 23