Symbol 及其 属性

栏目: JavaScript · 发布时间: 5年前

内容简介:其他原始数据类型都有各自的字面量形式, 例如数字类型的 998 , 所以可以通过字面量的形式来创建这个类型的实例, 比如通过字面量形式创建一个数值类型和一个字符串类型的变量:然而 Symbol 没有自己的字面量形式, 所以无法像这样创建一个 Symbol 实例. 要使用全局的函数 Symbol() 来做这件事, 例如创建一个名为 name 的Symbol:注意: 不能使用 new 关键字加上 Symbol() 函数来创建, 这样会报错. 虽然其他原始类型都可以使用 new 加上构造函数来创建一个对应类型的变

其他原始数据类型都有各自的字面量形式, 例如数字类型的 998 , 所以可以通过字面量的形式来创建这个类型的实例, 比如通过字面量形式创建一个数值类型和一个字符串类型的变量:

let age = 99;
let name = 'Ross';
复制代码

然而 Symbol 没有自己的字面量形式, 所以无法像这样创建一个 Symbol 实例. 要使用全局的函数 Symbol() 来做这件事, 例如创建一个名为 name 的Symbol:

let name  = Symbol();  // 创建名为 name 的Symbol

let person = {};
person[name] = 'Ross';  // 使用这个 Symbol

// 输出以 Symbol 为属性名的属性值
console.log(person[name]);  // Ross
复制代码

注意: 不能使用 new 关键字加上 Symbol() 函数来创建, 这样会报错. 虽然其他原始类型都可以使用 new 加上构造函数来创建一个对应类型的变量, 但是 Symbol() 不能当做构造函数来使用.

创建一个带描述性文字的 Symbol

在创建一个 Symbol 时可以加上一段描述性的字符串, 用来标记这个 Symbol 是干嘛的. 当使用 console.log 打印出这个 Symbol 时, 会将标记同时打印出来. 例如:

let firstName = Symbol('first name'); // 标记是 'first name'

let person = {};
person[firstName] = 'Ross';  // 使用这个 Symbol
console.log(person[firstName]);  // Ross

// 输出这个 Symbol
console.log(firstName);  // "Symbol(first name)"
复制代码

使用 typeof 来准确判断 Symbol 类型

对 Symbol 使用 typeof 会返回 "symbol", 所以可以通过 typeof 操作符准确判断 Symbol 的类型:

console.log(typeof firstName);  // symbol
复制代码

这是首选的判断 Symbol 类型的方式.

Symbol 的使用场合--所有使用可计算属性名的地方都可以使用 Symbol

不仅可以通过中括号调用属性名的方法来使用 Symbol, 它还可以用在所有可计算属性名的地方, 例如可计算对象字面量属性名、Object.defineProperty()方法和Object.defineProperties()方法的调用过程中.

let firstName = Symbol('first name');
let lastName = Symbol('last name');

// 可计算对象字面量属性
let person = {
    [firstName]: 'Ross',
    [lastName]: 'Geller'
};

// 在 Object.defineProperty() 方法设置以 Symbol:firstName 为属性名的属性值的属性, 将其属性值设置成只读
Object.defineProperty(person, firstName, { writable: false });

// 在 Object.defineProperties() 方法设置以 Symbol:lastName 为属性名的属性值的属性, 将其属性值设置成只读
Object.defineProperties(person, {
    [lastName]: {
        writable: false
    }
});
复制代码

共享的 Symbol

有时我们希望在不同的代码中共享同一个 Symbol, 例如在不同的文件中使用同一个 Symbol. 如果代码规模较大, 像这样就比较困难了, 所幸 Symbol 带有一个共享体系, 可以供我们在全局中创建并使用全局共享的 Symbol.

创建共享 Symbol 的语法

在全局中存在一个注册表, 用来保存全局的共享 Symbol 的名单, 通过向这个注册表中增加元素来设置一个 Symbol 为全局共享的.

使用 Symbol.for(description) 来向注册表中添加一个 Symbol, 其中 description 是个字符串, 起到描述的作用, 只要两个全局 Symbol 的描述相同, 那么他们就是同一个 Symbol.

Symbol.for(description) 方法先在注册表中寻找这个 Symbol 有没有被注册, 也就是找注册表中有没有这个 description, 如果有就直接返回, 否则就用这个description新建一个, 保存之后将这个 Symbol 返回. 这样这个Symbol 就是全局共享的了. 例如:

// 新建两个描述符都是 dog 的共享 Symbol
let wangcai = Symbol.for('dog');
let dahuang = Symbol.for('dog');

console.log(wangcai === dahuang);  // true
复制代码

这里可能会有个疑问: 如果两个 普通 Symbol 的描述字符串一样, 那么他们是同一个 Symbol 吗? 答案为不是, 通过实验可以得出:

// 创建两个描述一样的非共享的 Symbol 
let wangcai = Symbol('dog');
let dahuang = Symbol('dog');

console.log(wangcai === dahuang);  // false
复制代码

通过 Symbol.keyFor() 得到共享的 Symbol 的描述文字

如果有了一个共享的 Symbol, 但是不知道他的描述, 可以通过 Symbol.keyFor() 将他的描述性文字取出来, 例如把上面例子中的 wangcai和dahuang的描述文字取出来:

console.log(Symbol.keyFor(wangcai));  // dog
console.log(Symbol.keyFor(dahuang));  // dog
复制代码

Symbol 的强制类型转换

JavaScript 的一个比较让人头疼的语言特性是强制类型转换, 但是对于 Symbol 来说就显得比较简单了.

虽然在使用 console.log 输出一个 Symbol 时会调用这个 Symbol 的 toString() 方法, 也可以使用 String(symbol)来获得它的有关信息, 但是在其他情况下却并不会这样, 例如想把一个Symbol用加号操作符 "+" 转换为字符串就会报错, 如果想将它通过除法操作符转换成一个数值型变量则也会报错.

所以不要把 Symbol 强制转换成数值和字符串类型, 原因不仅如此, 也是因为 Symbol 的出现就是在某些场合下来替代字符串作为属性名的, 在不恰当的时候把他转化为字符串就违背了添加 Symbol 的本意了.

遍历所有的 Symbol 属性

对象的一般属性可以使用 Object.getOwnPropertyNames() 方法和 Object.keys() 方法来遍历, 二者的区别是前者返回所有属性, 而后者只返回对象中可枚举的属性名. 但是这两个方法都不支持 Symbol 属性, 所以ES6 中新增了一个专门用来检索 Symbol 属性的方法 Object.getOwnPropertySymbols() , 这个方法返回一个对象中所有的 Symbol 属性名. 使用方法如下:

let firstName = Symbol.for('first name');
let lastName = Symbol('last name');

let person = {
    [firstName]: 'Ross',
    [lastName]: 'Geller'
};

let symbolPropertyNames = Object.getOwnPropertySymbols(person);

console.log(symbolPropertyNames.length, symbolPropertyNames[0], person[symbolPropertyNames[0]]);
// 2  Symbol(first name)  Ross
复制代码

几个通过 well-known Symbol 暴露出来的内部操作

背景: 从 ES5 开始, JavaScript 语言就尝试将其提供的一些自建函数的内部逻辑展示出来并允许开发者自己修改. 在ES6 中由于 Symbol 的出现, 增加了在 原型链 上定义的与 Symbol 相关的属性来暴露更多的内部逻辑.

ES6通过Symbol对象的一些属性暴露了语言中一些方法的内部实现, 例如使用 Symbol.hasInstance 来暴露使用 instanceof 操作符时具体的工作流程; 使用 Symbol.toPrimitive 来暴露将一个对象转换为原始类型时会调用的方法.

Symbol.hasInstance

instanceof 操作符用来判断一个对象是不是某个类的实例, 例如:

function Person() {
    // ...
}

// 创建一个类的实例
let p1 = new Person();

console.log(p1 instanceof Person); // true , p1 是 Person 类的实例
复制代码

在 ES6 中, 可以通过修改一个类的 Symbol.hasInstance 属性来改变 instanceof 操作符的行为, 参照下面的实验:

// 和上面一样, 定义 Person 类
function Person() {
    // ...
}

// 通过修改 Person 对象的 Symbol.hasInstance 属性来修改对 Person 类使用 instanceof 的结果
Object.defineProperty(Person, Symbol.hasInstance, {
    value(v){
        return false;  // 修改为无论是不是 Person 的实例, 都返回 false
    }
});

// 创建 Person 类的实例
let p1 = new Person();

// 输出对 Person 类使用 instanceof 的结果
console.log(p1 instanceof Person); // false , 看到了效果
复制代码

从上面的例子可以看出来, 其实在对一个类使用 instanceof 时, 后台会调用这个类的名为 Symbol.hasInstance 的函数来进行判断并返回结果, 所以才可以通过修改它来修改 instanceof 操作符的行为.

Symbol.toPrimitive

名为 Symbol.toPrimitive 的函数定义了一个非原始类型的对象在转换为原始类型值时的行为, 之前写过一篇讨论强制类型转换的文章, 正片文章就可以归结为这个函数的行为. 简单来说这个函数可以根据传入的偏好来决定将一个怎么转换成一个原始类型的值, 对于大多数对象, 这个函数定义的转换机制是这样的:

valueOf()
toString()

但是对于 Date 这一个特殊的对象, 上面两个步骤是反过来的. 至于不同种类对象的 valueOf()toString() 方法之前的文章中有讨论.

我们可以通过自己定义这个函数来修改一个对象转换成原始值的方式, 例如:

// 创建一个类
function Temprature(degrees) {
    this.degrees = degrees;
}

// 通过修改原型链上的 Symbol.toPrimitive 来修改这个类被转换为基本数据类型的行为
Temprature.prototype[Symbol.toPrimitive] = function(hint){
    switch(hint){
        case 'string':
            return this.degrees + '\u00b0';
            break;

        case 'number':
            return this.degrees;
            break;

        default:
            return this.degrees + ' degrees';
            break;
    }
};

// 新建这个类的对象
let temprature = new Temprature(99); 

// 触发这个类转换为基本数据类型的行为
console.log(temprature + ''); // 触发默认行为, 结果是 "99 degrees"
console.log(temprature / 1);  // 触发转换为数值类型的行为, 结果是 99 
console.log(String(temprature)); // 触发转换为字符串类型的行为, 结果是 "99°"
复制代码

由上面的例子可以看到通过修改一个类的原型上的 Symbol.toPrimitive 方法可以修改这个类的对象转换为原始值的行为.

其他 well-known Symbol

除了以上提到的两个 well-known Symbol 方法之外, 还有许多类似的方法, 例如 Symbol.isConcatSpreadable , Symbol.iterator , Symbol.match , Symbol.split 等等, 详细可以参见MDN

总结

Symbol 主要用来作为对象的属性名来使用, 它具备一定的隐私性, 可以用在所有可计算属性的地方. 通过一些 well-known Symbol 来暴露出一些语言内部机制的具体实现, 我们可以通过这些实现来加深对于这门语言的了解, 这是有必要的.


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Design DeMYSTiFieD

Web Design DeMYSTiFieD

Willard, Wendy / 2010-11 / $ 24.86

Website Design just got a whole lot easier! This title helps you to learn the latest website development tools, techniques, and best practices. "Web Design Demystified" provides the hands-on help you ......一起来看看 《Web Design DeMYSTiFieD》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

html转js在线工具
html转js在线工具

html转js在线工具