深入理解ES6--6.符号与符号属性

原创文章&经验总结&从校招到A厂一路阳光一路沧桑

详情请戳www.codercc.com

image

主要知识点:创建符号值、使用符号值、共享符号值、符号值转换。检索符号值属性以及知名符号

符号与符号属性的知识点.png

1. Symbol基础

1.1 创建符号值

在 JS 已有的基本类型(字符串、数值、布尔类型、 nullundefined ) 之外, ES6 引入了一种新的基本类型:符号(Symbol ) 。 符号起初被设计用于创建对象私有成员,而这也是 JS 开发者期待已久的特性。在符号诞生之前,将字符串作为属性名称导致属性可以被轻易访问,无论命名规则如何。而“私有名称”意味着开发者可以创建非字符串类型的属性名称,由此可以防止使用常规手段来探查这些名称。

创建符号值

let firstName = Symbol('first Name');
let person = {};
person[firstName] = 'hello world';
console.log(person[firstName]); //hello world
console.log(firstName); //Symbol(first Name)

此代码创建了一个符号类型的 firstName 变量,并将它作为 person 对象的一个属性,而每次访问该属性都要使用这个符号值。Symbol 函数还可以接受一个额外的参数用于描述符号值,该描述并不能用来访问对应属性,但它能用于调试。符号的描述信息被存储在内部属性 [[Description]] 中,当符号的 toString()方法被显式
或隐式调用时,该属性都会被读取。在本例中, console.log() 隐式调用了 firstName 变量的 toString() 方法,于是描述信息就被输出到日志。此外没有任何办法可以从代码中直接访问 [[Description]] 属性。

1.2 使用符号值

可以在任意使用 需计算属性名 的场合中使用符号值,比如在对象字面量中可以使用符号值来作为对象属性,另外还可以在Object.defineProperty()方法或Object.defineProperties()方法中使用。

1.3 共享符号值

如果想在不同的代码中共享同一个符号值的话,应使用 Symbol.for() 方法而不是 Symbol() 方法。 Symbol.for()方法仅接受单个字符串类型的参数,作为目标符号值的标识符,同时此参数也会成为该符号的描述信息。

//共享符号值

let uid = Symbol.for('uid');
let person = {};
person[uid] = 'hello';

let id = Symbol.for('uid');
let car = {};
car[id] = '88888888';

console.log(uid===id); //true

Symbol.for() 方法首先会搜索全局符号注册表,看是否存在一个键值为 "uid" 的符号值。
若是,该方法会返回这个已存在的符号值;否则,会创建一个新的符号值,并使用该键值将
其记录到全局符号注册表中,然后返回这个新的符号值。这就意味着此后使用同一个键值去调用 Symbol.for()方法都将会返回同一个符号值。

还可以使用 Symbol.keyFor() 方法在全局符号注册表中根据符号值检索出对应的键值

console.log(Symbol.keyFor(uid)); //uid
console.log(Symbol.keyFor(id)); //uid

1.4 符号值的转换

类型转换是 JS 语言重要的一部分,能够非常灵活地将一种数据类型转换为另一种。然而符号类型在进行转换时非常不灵活,因为其他类型缺乏与符号值的合理等价,尤其是符号值无法被转换为字符串值或数值,在逻辑运算符中会被认为等价于 true

例如,可使用String()方法获取符号值的描述信息,但是如果使用符号值在字符串中拼接,则会报错:

console.log(String(uid));//Symbol(uid)
console.log(uid+""); //Uncaught TypeError: Cannot convert a Symbol value to a string

1.5 检索符号属性

Object.keys()Object.getOwnPropertyNames()方法可以检索对象的所有属性名称,前者
返回所有的可枚举属性名称,而后者则返回所有属性名称,无论是否可以可枚举,然而两者都不能返回符号类型的属性。因此,在ES6中新增了
Object.getOwnPropertySymbols() 方法,以便让你可以检索对象的符号类型属性。

let uid = Symbol.for('uid');
let person = {};
person[uid] = 'hello';

let symbols = Object.getOwnPropertySymbols(person);
console.log(symbols.length); //1
console.log(symbols[0]); //Symbol(uid)

2. 知名符号

ES6 定义了“知名符号”来代表 JS 中一些公共行为,而这些行为此前被认为只能是内部操作,但在ES6中使用了知名符号来暴露了内部方法,提供了更加方便的调用这种公用方法的一种方式。每一个知名符号都对应全局 Symbol 对象的一个属性,例如 Symbol.create

知名符号有:

  • Symbol.hasInstance :供 instanceof 运算符使用的一个方法,用于判断对象继承关系;
  • Symbol.isConcatSpreadable :一个布尔类型值,在集合对象作为参数传递给
    Array.prototype.concat() 方法时,指示是否要将该集合的元素扁平化;
  • Symbol.iterator :返回迭代器的一个方法;
  • Symbol.match :供 String.prototype.match() 函数使用的一个方法,用于比较字符串;
  • Symbol.replace :供 String.prototype.replace() 函数使用的一个方法,用于替换子字符串;
  • Symbol.search :供 String.prototype.search() 函数使用的一个方法,用于定位子字符;
  • Symbol.species :用于产生派生对象的构造器;
  • Symbol.split :供 String.prototype.split() 函数使用的一个方法,用于分割字符串;
  • Symbol.toPrimitive :返回对象所对应的基本类型值的一个方法;
  • Symbol.toStringTag :供 String.prototype.toString() 函数使用的一个方法,用于创建对象的描述信息;
  • Symbol.unscopables :一个对象,该对象的属性指示了哪些属性名不允许被包含在with 语句中;

2.1 Symbol.hasInstance属性

每个函数都具有一个 Symbol.hasInstance 属性,用于判断指定对象是否为本函数的一个实例。这个方法定义在 Function.prototype 上,因此所有函数都继承了面对 instanceof 运算符时的默认行为。 Symbol.hasInstance 属性自身是不可写入、不可配置、不可枚举的,从而保证它不会被错误地重写。

Symbol.hasInstance 方法只接受单个参数,即需要检测的值。如果该值是本函数的一个实例,则方法会返回 true

obj instanceof Array
//等价于
Array[Symbol.hasInstance](obj)

2.2 Symbol.isConcatSpreadable属性

Symbol.isConcatSpreadable属性是一个布尔类型的属性,它表示拥有长度length属性与数值类型的键的类数组的对象,在调用 concat() 方法时,会将数值键对应的值分离为单独项,添加在数组的后部,完成数组拼接。

它只出现在特定类型的对象上,用来标示该对象在作为concat() 参数时应如何工作,从而有效改变该对象的默认行为。你可以用它来定义任意类型的对象,让该对象在参与 concat() 调用时能够表现得像数组一样。

//Symbol.isConcatSpreadable属性

let collection = {
    0:'hello',
    1:'world',
    length:2,
    [Symbol.isConcatSpreadable]:true
}

let arr = ['es6'].concat(collection);
console.log(arr); //["es6", "hello", "world"]

2.3 Symbol.match 、 Symbol.replace 、 Symbol.search与Symbol.split

在 JS 中,字符串与正则表达式有着密切的联系,尤其是字符串具有几个可以接受正则表达式作为参数的方法:

  • match(regex) :判断指定字符串是否与一个正则表达式相匹配;
  • replace(regex, replacement) :对正则表达式的匹配结果进行替换;
  • search(regex) :在字符串内对正则表达式的匹配结果进行定位;
  • split(regex) :使用正则表达式将字符串分割为数组

ES6 定义了 4 个符号以及对应的方法,可以将正则表达式作为字符串对应方法的第一个参数传入, Symbol.match 对应 match() 方法, Symbol.replace 对应 replace()Symbol.search对应 search()Symbol.split 则对应 split() 。这些符号属性被定义在 RegExp.prototype上作为默认实现,以供对应的字符串方法使用。

2.4 Symbol.toPrimitive

JS 经常在使用特定运算符的时候试图进行隐式转换,以便将对象转换为基本类型值。例如,当你使用相等(== ) 运算符来对字符串与对象进行比较的时候,该对象会在比较之前被转换为一个基本类型值。到底转换为什么基本类型值,在此前属于内部操作,而 ES6 则通过Symbol.toPrimitive 属性将其暴露出来,以便让对应方法可以被修改。

function Temperature(degrees) {
    this.degrees = degrees;
} 
Temperature.prototype[Symbol.toPrimitive] = function(hint) {
    switch (hint) {
        case "string":
            return this.degrees + "\u00b0"; // 温度符号
        case "number":
            return this.degrees;
        case "default":
            return this.degrees + " degrees";
    }
};
let freezing = new Temperature(32);
console.log(freezing + "!"); // "32 degrees!"
console.log(freezing / 2); // 16
console.log(String(freezing)); // "32°"

这段脚本定义了一个 Temperature 构造器,并重写了其原型上的Symbol.toPrimitive 方法。返回值会依据方法的提示性参数而有所不同,可以使用字符串模式、数值模式或是默认模式,而该提示性参数会在调用时由 JS 引擎自动填写。字符串模式中, Temperature 函数返回的温度会附带着 Unicode 温度符号;数值模式只会返回温度数值;而默认模式中,返回的温度会附带着字符串 "degrees" 。

2.5 Symbol.toStringTag

ES6 通过 Symbol.toStringTag 重定义了相关行为,该符号代表了所有对象的一个属性,定义了 Object.prototype.toString.call() 被调用时应当返回什么值。对于数组来说,在Symbol.toStringTag 属性中存储了 "Array" 值,于是该函数的返回值也就是 "Array" 。

function Person(name){
    this.name = name;
}


Person.prototype[Symbol.toStringTag] = 'person';
let person = new Person('hello world');
console.log(person.toString()); //[object person]

2.6 Symbol.unscopables

Symbol.unscopables 符号在 Array.prototype 上使用,以指定哪些属性不允许在 with 语句内被绑定。 Symbol.unscopables 属性是一个对象,当提供该属性时,它的键就是用于忽略with 语句绑定的标识符,键值为 true 代表屏蔽绑定。以下是数组的 Symbol.unscopables属性的默认值:

// 默认内置在 ES6 中
Array.prototype[Symbol.unscopables] = Object.assign(Object.create(null), {
    copyWithin: true,
    entries: true,
    fill: true,
    find: true,
    findIndex: true,
    keys: true,
    values: true
});

3. 总结

  1. 虽然符号类型的属性不是真正的私有属性,但它们难以被无意修改,因此在需要提供保护以防止开发者改动的场合中,它们非常合适;
  2. 为符号提供描述信息以便更容易地辨识它们的值。当需要在不同代码片段中共享符号,可以是用Symbol.for()在全局符号注册表中共享符号;
  3. Object.keys()Object.getOwnPropertyNames() 不会返回符号值,因此 ES6 新增了一个Object.getOwnPropertySymbols()方法,允许检索符号类型的对象属性。同时依然可以使用Object.defineProperty()Object.defineProperties() 方法对符号类型的属性进行修改;
  4. “知名符号”使用了全局符号常量(例如 Symbol.hasInstance ) ,为常规对象定义了一些功能,而这些功能原先仅限内部使用。这些符号按规范使用 Symbol. 的前缀,允许开发者通过多种方式去修改常规对象的行为。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容