【ES6 笔记】Symbol和Symbol属性

回忆一下JS中的原始类型:字符串型、数字型、布尔型、null和undefined。

ES6中引入了第6种原始类型:Symbol

创建Symbol

let firstName = Symbol();
let person = {};
person[firstName] = '欧阳不乖'
console.log(person[firstName]); //'欧阳不乖'

Symbol函数接受一个可选参数,可以添加一段文本描述即将创建的Symbol,这段属描述不可用于属性访问,但是建议每次创建Symbol时都添加一段描述,便于阅读代码和调试Symbol程序。

let firstName = Symbol('first name');
let person = {};
person[firstName] = '欧阳不乖';
console.log('first name' in person); //false
console.log(person[firstName]); // ''欧阳不乖
console.log(firstName); //Symbol('first name')

Symbol的描述被存储在内部的[[Description]]属性中,只有调用Symbol的toString()方法时才可以读取这个属性。在执行console.log的时候隐式的调用了toString()方法。

  • Symbol的辨识方法
    Symbol是原始值,且ES6同时扩展了typeof操作符,支持返回“Symbol”,所以可以用typeof来检测变量是否为Symbol类型
let symbol = Symbol('test symbol');
 console.log(typeof symbol);  //'symnbol'      

Symbol的使用方法

所有使用可计算属性名的地方,都可以使用Symbol。

let firstName = Symbol('first name');
let person = {
    //使用一个可计算对象字面量属性
    [firstName] : '欧阳不乖'
}
//将属性设置为只读
Object.defineProperty( person, firstName, { writable : false});
console.log(person[firstName]); //'欧阳不乖'

Symbol共享体系

如果想创建一个可共享的Symbol,要使用Symbol.for()方法。它只接受一个参数,也就是即将创建的Symbol的字符串标识符,这个参数同样也被用作Symbol的描述:

let uid = Symbol.for('uid');
let object = {};
object[ uid ] = '12345';
console.log(object[uid]); //12345
console.log(uid); //Symbol(uid)

Symbol.for()方法首先在全局Symbol注册表中搜索键为‘uid’的Symbol是否存在,如果存在,直接返回已有的Symbol;否则,创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随机返回新创建的Symbol。
后续如果再传入同样的键调用Symbol.for()会返回相同的Symbol:

let uid = Symbol.for('uid');
let uid2 = Symbol.for('uid');
let object = {
     [uid] : '12345' 
} ;
console.log(uid === uid2); //true
console.log(object[uid]);  //12345
console.log(object[uid2]);  //12345

还有一个与Symbol共享有关的特性:可以使用Symbol.keyFor()方法在Symbol全局注册表中检索与Symbol有关的键:

let uid = Symbol.for('uid');
console.log(Symbol.keyFor(uid)); //uid

let uid2 = Symbol.for('uid');
console.log(Symbol.keyFor(uid2)); //uid

let uid3 = Symbol('uid');
console.log(Symbol.keyFor(uid3)); //undefined

Symbol全局注册表是一个类似全局作用域的共享环境,也就是说你不能假设目前环境中存在哪些键

Symbol与类型强制转换

由于其他类型没有与Symbol逻辑等价的值,所以不能将Symbol强制转换为字符串或是数字类型。
在使用console.log()方法来输出Symbol的内容时,它会调用Symbol的String()方法并输出有用的信息。也可以像下面这样直接调用String()方法来获取相同的内容:

let uid = Symbol.for('uid'),
    desc = String(uid);
console.log(desc); //Symbol(uid)

String()函数调用了uid.toString()方法,返回字符串类型的Symbol描述内容,但是,如果将Symbol与一个字符串拼接会导致程序抛出错误:

let uidDesc = Symbol.for('uid') + ''; //报错

Symbol不可以被转为字符串,同样也不能转为数字类型:

let uidSum = Symbol.for('uid') /1; //报错

只有在使用逻辑操作符的时候,Symbol可以正常运行,因为Symbol与JS中的非空值类似,其等价布尔值为true

Symbol属性检索

Object.keys()和Object.getOwnPropertyNames()方法可以检索对象中所有的属性名:前一个方法返回所有的可枚举属性名;后一个方法不考虑属性的可枚举性一律返回。在ES6中新增一个Object.getOwnPropertySymbols()方法来检索对象中的Symbol属性。
Object.getOwnPropertySymbols()方法的返回值是一个包含所有Symbol自有属性的数组:

let uid = Symbol.for('uid');
let object = {
     [uid]:12345
}
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); //1
console.log(symbols[0]);  //Symbol('uid')
console.log(object[symbols[0]]);   //12345

通过well-known Symbol暴露内部操作

  • Symbol.hasInstance:一个在执行instanceof时调用的内部方法,用来检测对象的继承信息。
    Symbol.hasInstance方法只接受一个参数,即要检查的值。如果传入的值是函数的实例,则返回true:
let arr = []
console.log(arr instanceof Array);  //true
// 等价于
console.log(Array[Symbol.hasInstance](arr));  //true

本质上,ES6只是将instanceof操作符重新定义为此方法的简写语法,现在引入方法调用以后,就可以随便改变instanceof的运行方式了:

function MyObject(){
    // empty
}
Object.defineProperty(MyObject, Symbol.hasInstance,{
    value : function(v){
          //console.log(v);   //MyObject {}
          return false;
    }
})
let obj = new MyObject();
console.log(obj instanceof MyObject );  // false
/*
* obj实际上是MyObject的实例,但是我们将Symbol.hasInstance的返回值硬编码为false以后
* 即使使用instanceof运算符也只是返回false
*/

我们可以按照自己的喜好任意重构Symbol.hasInstance,但是改写源码会造成不可预期的后果,所以请在必要的情况下只改写自己声明的函数Symbol.hasInstance属性

  • Symbol.isConcatSpreadable:一个布尔值,用于表示当传递一个集合作为Array.prototype.concat()方法的参数时,是否应该将合集内的元素规整到同一层级。
    JS数组的concat方法被设计用于拼接两个数组,不但接受数组参数,也可以接收非数组参数:
let colors1 = ['red'];
let colors2 = ['green'];
let color3 = 'black';
console.log(colors1.concat(colors2, color3)); //["red", "green", "black"]

JS规范声明,凡是传入了数组的参数,就会自动将他们分解为独立元素。
Symbol.isConcatSpreadable属性是一个布尔值,如果该属性值为true,则表示对象有length属性和数字键,故它的数值型属性值应该被独立添加到concat调用的结果中。这个属性默认情况下不会出现在标准对象中,它只是可选属性,用于增强作用于特定对象类型的concat方法的功能,有效简化其默认特性:

let collection = {
      0:'Hello',
      1:'World',
      length:2,
      [Symbol.isConcatSpreadable]:true
}
let message = ['Hi'].concat(collection);
console.log(message.length); //3
console.log(message); //["Hi", "Hello", "World"]

// 假设将 [Symbol.isConcatSpreadable]:false改为这样,那么运行结果就变为了:
/ * 
* console.log(message);
* ['Hi', {
*       0:'Hello',
*       1:'World',
*       length:2,
*       [Symbol.isConcatSpreadable]:true
* }]
* /
  • Symbol.match:一个在调用String.prototype.match()方法时调用的方法,用于比较字符串。接受一个字符串类型的参数,如果匹配成功则返回匹配元素的数组,否则返回null。
  • Symbol.replace:一个在调用String.prototype.replace()方法时调用的方法,用于替换字符串的子串。接受一个字符串类型的参数和一个替换用的字符串,最终依然返回一个字符串。
  • Symbol.search:一个在调用String.prototype.search()方法时调用的方法,用于在字符串中定位子串。接受一个字符串类型的参数,如果匹配到则返回数字索引,否则返回 -1 。
  • Symbol.split:一个在调用String.prototype.split()方法时调用的方法,用于分割字符串。接受一个字符串参数,根据匹配内容将字符串分解,并返回一个包含分解后片段的数组。
    在JS中字符串与正则表达式经常一起使用,尤其是字符串类型的几个方法,可以接受正则表达式作为参数:
    1. match(regex) 确定给定字符串是否匹配正则表达式regex
    2. replace(regex,replacement) 将字符串中匹配正则表达式的regex部分替换为replacement
    3. search(regex) 在字符串中定位匹配正则表达式regex的位置索引
    4. split(regex) 按照匹配正则表达式regex的元素将字符串分切,并将结果存入数组
    在ES6之前,以上4个方法无法使用开发者自定义的对象来替代正则表达式进行字符串匹配。而在ES6中定义了与上边4个方法相对应的4个Symbol,将语言内建的RegExp对象的原生特性完全暴露出来。
// 实际上等价于 /^.{10}$/
let hasLengthOf10 = {
    [Symbol.match]:function(value){
        return value.length ===10 ? [ value ] : null ;
    },
    [Symbol.replace]:function(value, replacement){
        return value.length ===10 ? replacement : value ;
    },
    [Symbol.search]:function(value){
        return value.length ===10 ? 0 : -1 ;
    },
    [Symbol.split]:function(value){
        return value.length ===10 ? [ ,  ] : [ value ] ;
    },  
};
let message1 = 'Hello world'; //11个字符
let message2 = 'Hello 1234'; //10个字符

console.log( message1.match(hasLengthOf10) ); //null
console.log( message2.match(hasLengthOf10) ); //["Hello 1234"]

console.log( message1.replace(hasLengthOf10,'欧阳不乖') ); //Hello world
console.log( message2.replace(hasLengthOf10,'欧阳不乖') ); //欧阳不乖

console.log( message1.search(hasLengthOf10) ); // -1
console.log( message2.search(hasLengthOf10) ); // 0

console.log( message1.split(hasLengthOf10) ); //["Hello world"]
console.log( message2.split(hasLengthOf10) ); // [ ,  ] 注意这里原书写的['','']个人认为二者有区别
  • Symbol.toPrimitive:该方法被定义在每一个标准类型的原型上,并且规定了当对象被转换为原始值时应该执行的操作。
    该方法接受一个值作为参数,该值在规范中被称为“类型提示(hint)”,分别是:number、string或default,对应的返回分别是数字、字符串活无类型偏好的值。
    对于大多数标准对象,数字模式有以下的特性,根据优先级的顺序排列如下:
    1.调用valueOf()方法,结果为原始值,则返回;
    2.否则调用toString()方法,结果为原始值,则返回;
    3.如果再无可选值,则抛出错误。
    对于大多数标准对象,字符串模式有以下优先级排序:
    1.调用toString()方法,结果为原始值,则返回;
    2.否则调用valueOf()方法,结果为原始值,则返回;
    3.如果再无可选值,则抛出错误。
    在大多数情况下,标准对象会将默认模式按数字模式处理(除了Date对象,在这种情况下,会将默认模式按字符串模式处理)
function Temperature(degrees){
    this.degrees = degrees;
}
Temperature.prototype[Symbol.toPrimitive] = function(hint){
    switch (hint){
        case 'string' : 
                return this.degrees + '\u00b0' ; //degrees symbol
        case 'number':
                return this.degrees;
        case 'default':
                return this.degrees + '度'
    }
}
var freezing = new Temperature(32);

console.log( freezing/2 ) //16
console.log(String(freezing) ) //32°
console.log( freezing + '!' );  //32度!
  • Symbol.toStringTag:一个在调用Object.prototype.toString()方法时使用的字符串,用于创建对象描述。
  • Symbol.unscopables:一个定义了一些不可被with语句引用的对象属性名称的对象集合。

Symbol 的一些小扩展

let firstName = Symbol('欧阳不乖');
let lastName  ='Hello';
let person = {
    [firstName]:'爱谁谁',
    [lastName]:'World'
}

console.log( person.firstName ); // undefined
console.log( person[Symbol('欧阳不乖')] ) //undefined
console.log( person[firstName] ); // 爱谁谁

console.log( person.last ); // undefined
console.log( person[lastName] ); // World

console.log( firstName ); // Symbol('欧阳不乖')
console.log( Symbol('欧阳不乖') ); // Symbol('欧阳不乖')
console.log( firstName==Symbol('欧阳不乖') ); // false

console.log( String(Symbol('欧阳不乖'))+'yes' ); //Symbol(欧阳不乖)yes

console.log( Symbol('欧阳不乖')=== Symbol('欧阳不乖')  ); //false
console.log( Symbol('欧阳不乖')== Symbol('欧阳不乖')  ); //false

console.log( Object.is( Symbol('欧阳不乖'), Symbol('欧阳不乖')) );  //false

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

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,118评论 0 13
  • 概述 ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加...
    oWSQo阅读 509评论 1 3
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,212评论 0 4
  • 一、高手的暗箱:利用规律,放大努力 没有一个人是仅凭借努力、天赋、机遇而获得巨大 成功的,跃迁式的成功都是利用了更...
    造塔人阅读 371评论 0 0
  • 我为什么而活 为了山涧潺潺流动的清泉 为了漫山姹紫嫣红的花朵 为了林间婉转悦耳的天籁 我为什么而活 为了体验和感知...
    林清萓阅读 351评论 6 9