• ES6之Symbol和Symbol属性



    ES6学习系列


    在ES5以及早期版本中,JavaScript语言包含5中原始类型:字符串型、数字型、布尔型、null和undefined。ES6引入了第6种原始类型:Symbol。 起初,人们用它来创建对象的私有成员,在Symbol出现以前,一直都是通过属性名来访问所有属性,无论属性名由什么元素构成,全部都是通过一个字符串类型的名称来访问的。私有名称原本是为了让开发者们创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称。私有名称最终演变成了ES6中的Symbol。虽然通过Symbol可以为属性添加非字符串名称,但是其隐私性被打破了,新标准中将Symbol属性与对象中的其他属性分别分类。

    symbol 是一种基本数据类型(primitive data type)。Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:“new Symbol()”。

    每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。

    创建Symbol

    所有原始值,除了Symbol之外都有各自的字面形式,例如布尔类型的true或数字类型的42。可以通过全局的Symbol函数创建一个Symbol,作为对象属性,并用于访问。

    // 创建一个名为firstName的Symbol
    let firstName = Symbol();
    let person = {};
    // 用Symbol将一个新的属性赋值给person对象
    person[firstName] = "Nocholas";
    // 进行访问的时候一定要用到最初定义的Symbol
    // Nocholas
    console.log(person[firstName]);
    // Symbol()
    console.log(firstName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Symbol是原始值,ES6同样扩展了typeof操作符,所以可以用typeof来检测变量是否为Symbol类型

    // symbol
    console.log(typeof firstName);
    
    • 1
    • 2

    由于Symbol是原始值,因此调用new Symbol()会导致程序抛出错误Uncaught TypeError: Symbol is not a constructor。
    在这里插入图片描述
    Symbol函数接受一个可选参数,让你添加一段文本描述即将创建的Symbol,但这段描述不能作为属性名用于属性访问。建议在每次创建Symbol时都传递可选参数,以便于阅读代码和调试Symbol程序。

    // 创建一个名为firstName的Symbol
    let firstName =Symbol("first name");
    let person = {};
    // 用Symbol将一个新的属性赋值给person对象
    person[firstName] = "Nocholas";
    // 进行访问的时候一定要用到最初定义的Symbol
    // Symbol的描述参数不能用于对象的属性访问
    // false
    console.log("first name" in person);
    // true
    console.log(firstName in person);
    // undefined
    console.log(person['first name'])
    // Nocholas
    console.log(person[firstName]);
    // Symbol(first name)
    console.log(firstName);
    // symbol
    console.log(typeof firstName);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    Symbol的使用方法

    所有使用可计算属性名的地方,都可以使用Symbol。Symbol可以用于计算对象字面量属性名、Object.defineProperty方法和Object.defineProperties方法的调用过程中。

    // 创建一个名为firstName的Symbol
    let firstName = Symbol("first name");
    // 使用一个可计算对象字面量属性
    let person = {
        [firstName]: "Nocholas"
    };
    
    // 将属性设置为只读
    Object.defineProperty(person, firstName, { writable: false });
    
    let lastName = Symbol("last name");
    Object.defineProperties(person, {
        [lastName]: {
            value: "Zakas",
            writable: false
        }
    });
    // Nocholas
    console.log(person[firstName]);
    // Zakas
    console.log(person[lastName]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    尽管在所有可计算属性名的地方,都可以使用Symbol来代替,但是为了在不同代码片段间有效地共享这些Symbol,需要建立一个体系。

    Symbol共享体系

    有时候我们希望在不同的代码中共享同一个Symbol,例如,在你的应用中有两种不同的对象类型,但是你希望它们使用同一个Symbol属性来表示一个独特的标识符。一般而言,在很大的代码库中或跨文件追踪Symbol非常困难而且容易出错,出于这些原因,ES6提供了一个可以随时访问的全局Symbol注册表。

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

    let uid = Symbol.for("uid");
    let object = {};
    object[uid] = "12345";
    // 12345
    console.log(object[uid]);
    // Symbol(uid)
    console.log(uid);
    
    let uid2 =  Symbol.for("uid");
    // true
    console.log(uid === uid2);
    // 12345
    console.log(object[uid2]);
    // Symbol(uid)
    console.log(uid2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Symbol.for()方法首先在全局Symbol注册表中搜索键为"uid"的Symbol是否存在,如果存在,直接返回已有的Symbol;否则,创建一个新的Symbol,并使用这个键在Symbol全局注册表中注册,随即返回新创建的Symbol。后续如果在传入同样的键调用Symbol.for()会返回相同的Symbol。比如上面的案例中,uid和uid2包含相同的Symbol并且可以互换使用。第一次调用Symbol.for()方法创建这个Symbol,第二次调用可以直接从Symbol的群居注册表中检索这个Symbol。

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

    let uid = Symbol.for("uid");
    // uid
    console.log(Symbol.keyFor(uid));
    let uid2 =  Symbol.for("uid");
    // uid
    console.log(Symbol.keyFor(uid2));
    // 没有使用Symbol.for不会注册到全局注册表中
    let uid3 = Symbol("uid");
    // undefined
    console.log(Symbol.keyFor(uid3));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注意,uid和uid2都返回了"uid"这个键,而在Symbol全局注册表中不存在uid3这个Symbol,也就是不存在与之有关的键,所以最终返回undefined。

    Symbol全局注册表是一个类似全局作用域的共享环境,也就是说你不能假设目前环境中存在哪些键。当使用第三方组件时,尽量使用Symbol键的命名空间以减少命名冲突。举个例子,jQuery的代码可以为所有键添加"jquery"前缀,就像"jquery.element"或其他类似的键。

    Symbol与类型强制转换

    自动转型是JavaScript中的一个重要语言特性,利用这个特性能够在特定场景下将某个数据强制转换为其他类型。但是Symbol不能被转换为字符串和数字类型。
    比如

    let uid = Symbol.for("uid");
    let desc = uid + "";
    
    • 1
    • 2

    会直接报错:Uncaught TypeError: Cannot convert a Symbol value to a string

    在这里插入图片描述

    let uid = Symbol.for("uid");
    let desc = uid/1;
    
    • 1
    • 2

    会直接报错:Uncaught TypeError: Cannot convert a Symbol value to a number
    在这里插入图片描述
    当然,逻辑操作符除外,因为Symbol与JavaScript中的非空值类似,其等价布尔值为true。
    在这里插入图片描述

    Symbol属性检索

    Object.keys和Object.getOwnPropertyNames方法可以检索对象中的所有的属性名:前一个方法返回所有可枚举的属性名;后一个方法不考虑属性的可枚举性一律返回。然后为了保持ES5函数的所有功能,这两个方法都不支持Symbol属性,而是在ES6中添加了一个Object.getOwnPropertySymbols方法来检索对象中的Symbol属性。Object.getOwnPropertySymbols方法的返回值是一个包含所有Symbol自有属性的数组。
    比如

    // 创建一个名为firstName的Symbol
    let firstName = Symbol("first name");
    // 使用一个可计算对象字面量属性
    let person = {
        [firstName]: "Nocholas"
    };
    
    Object.defineProperty(person, "age", { enumerable: true });
    Object.defineProperty(person, "name", { writable: false });
    // 将属性设置为只读
    Object.defineProperty(person, firstName, { writable: false });
    
    let lastName = Symbol("last name");
    Object.defineProperties(person, {
        [lastName]: {
            value: "Zakas",
            writable: false
        }
    });
    // Nocholas
    console.log(person[firstName]);
    // Zakas
    console.log(person[lastName]);
    // ['age']
    console.log(Object.keys(person));
    // ['age', 'name']
    console.log(Object.getOwnPropertyNames(person));
    // [Symbol(first name), Symbol(last name)]
    console.log(Object.getOwnPropertySymbols(person));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    执行结果如下
    在这里插入图片描述
    三个方法的返回值都不一样。
    Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
    Object.getOwnPropertyNames()方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括 Symbol 值作为名称的属性)组成的数组。
    Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。

  • 相关阅读:
    模拟一个js底层数据类型隐式转换
    什么是GPT
    微信小程序云开发实战
    java经典面试题并发篇(持续更新)
    Linux 中常用的基础命令
    HTML5 游戏开发实战 | 黑白棋
    软件测试工程师——你不仅仅应该会点点点
    (Mysql高级语句(进阶查询语句+数据库函数+连接查询))
    K8S - Volume - NFS 卷的简介和使用
    大数据应用概览(林子雨慕课课程)
  • 原文地址:https://blog.csdn.net/m0_37607945/article/details/127701418