zoukankan      html  css  js  c++  java
  • js之Symbol类型

    ECMA2019资料

    官方文档地址

    ECMA2019离线版(html版+pdf版+ES6入门)

    引入Symbol类型的背景

    • ES5 的对象属性名都是字符串,这容易造成属性名冲突的问题

      举例: 使用别人的模块/对象, 又想为之添加新的属性,这就容易使得新属性名与原有属性名冲突

    Symbol类型简介

    • Symbol是一种原始数据类型

      • 其余原始类型: undefined 、 null 、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)
      • Symbol表示独一无二的值
      • Symbol类型的"真实值"无法获取,也就是说Symbol类型没有对应的字面量
      • Symbol类型的意义在于区分彼此和不重复,不在于真实值

    Symbol特性(所有数据类型都需要探讨的问题)

    创建

    • Symbol值只能通过Symbol()函数生成
    let s1 s1 = = Symbol Symbol( ('foo' 'foo') ); ;
    let let s2 s2 = = Symbol Symbol( ('bar' 'bar') ); ;
    s1 s1 // Symbol(foo) // Symbol(foo)
    s2 s2 // Symbol(bar) // Symbol(bar)
    s1 s1. .toString toString( () ) // "Symbol(foo)" // "Symbol(foo)"
    s2 s2. .toString toString( () ) // "Symbol(bar)"
    
    • Symbol()函数前不能使用new命令(Symbol类型是原始值,不是对象)
    • Symbol类型不能添加属性(不是对象)
    • Symbol类型是一种类似字符串的类型(可用作属性名

    ECMA2019标准相关:
    在这里插入图片描述

    ​ 解读: (NewTarget是使用new命令调用函数时会创建的原生对象)

    ​ (1) 如果NewTarget不为undefined(也就是使用了new命令), 抛出错误==>Symbol函数前不能使用new

    ​ (2) 描述字符串description保存在symbol值内部的[[Description]]中

    Symbol值的描述

    • Symbol 函数可以接受一个字符串作为参数

    • Symbol 函数的参数只是表示对当前 Symbol 值的描述

    • Symbol值的描述: 帮助开发者区分Symbol值

      • 在控制台打印两Symbol值时,能区分开来
      • 转为字符串时,能区分开来

    Symbol值的类型转换与运算

    类型转换

    根据ECMA2019,:

    • 类型转换是用一些抽象操作来描述的
    • 隐式转换是直接调用某一个抽象操作
    • 显示转换是抽象操作的包装(加入判断和控制等)
      • 类型的构造函数 当做 函数使用(例如: String())
      • 一些可能调用抽象操作的方法(例如: toString()valueOf())

    主要的类型转换抽象操作:

    1.对象转原始类型:
    在这里插入图片描述
    在这里插入图片描述

    2.转Boolean:
    在这里插入图片描述

    3.转Number:
    在这里插入图片描述

    4.转字符串:
    在这里插入图片描述

    5.原始类型转对象:
    在这里插入图片描述

    如上图, 直接对symbol值应用抽象操作(隐式转换):

    • ToBoolean(Symbol)==>true
    • ToString(Symbol)==>报错
    • ToNumber==>报错

    类型转换举例

    let sym = Symbol('My symbol');
    
    "your symbol is" + sym; //TypeError: can't convert symbol to string
    `your symbol is ${sym}`; //TypeError: can't convert
    
    String(sym); //'Symbol(My symbol)'
    sym.toString(); //'Symbol(My symbol)'
    

    为什么上述代码中String()和sym.toString()可以成功将symbol转换为字符串?

    ECMA2019:
    在这里插入图片描述
    (1)Symbol作为原始类型, 有对应的包装对象类型, 所以我们可以用sym.toString() 调用方法而不出错.

    更进一步,我们测试一下Symbol类型的实例是否可改变和添加属性:

    let s = Symbol('s');
    
    //实例对象的对象保护检测
    console.log(`s实例是否可扩展: ${Object.isExtensible(s)}`);
    console.log(`s实例是否被冻结: ${Object.isFrozen(s)}`);
    console.log(`s实例是否被封闭: ${Object.isSealed(s)}`);
    //输出:
    // s实例是否可扩展: false
    // s实例是否被冻结: true
    // s实例是否被seal: true
    
    
    //实例对象是否存在toString实例方法与[[Symbol.toStringTag]]
    console.log(`s实例是否有自定义toString方法: ${s.hasOwnProperty('toString')}`);
    console.log(`s实例是否有[[Symbol.toStringTag]]: ${s.hasOwnProperty(Symbol.toStringTag)}`);
    //输出:
    // s实例是否有自定义toString方法: false
    // s实例是否有[[Symbol.toStringTag]]: false
    
    //----------------------------------------------------------------------------------------------------------
    
    //Symbol.prototype的对象保护检测
    console.log(`Symbol.prototype是否可扩展: ${Object.isExtensible(Symbol.prototype)}`);
    console.log(`Symbol.prototype是否被冻结: ${Object.isFrozen(Symbol.prototype)}`);
    console.log(`Symbol.prototype是否被封闭: ${Object.isSealed(Symbol.prototype)}`);
    //输出:
    // Symbol.prototype是否可扩展: true
    // Symbol.prototype是否被冻结: false
    // Symbol.prototype是否被封闭: false
    

    总结:

    • Symbol类的实例是不可扩展的,不能添加,修改和删除属性
    • 因此, sym.toString()所调用的,必定是Symbol.prototype.toString()原型方法
    • Symbol.prototype.toString()调用Symbol.DescriptiveString(sym),返回一个组合字符串: "Symbol(" + 描述字符串 + ")"

    (2)String(sym)可行的原因:
    在这里插入图片描述

    可见, 在进行ToString抽象操作之前,对参数进行了判断, 并对为Symbol类型的情况另行处理(调用Symbol.DescriptiveString(value)),故而没有发生我们预期中的报错.

    涉及Symbol类型的运算: ±*/%

    结论: 一律报错

    原因:(ECMA2019)

    加法:
    在这里插入图片描述

    减法:
    在这里插入图片描述

    乘除余:
    在这里插入图片描述

    可见, 进行运算,必然逃不过要进行抽象操作 ToNumber,而对Symbol值进行ToNumber抽象操作就会报错.因此,Symbol值无法参与运算.


    全局Symbol表

    Symbol.for() 与 Symbol.keyFor()

    • 应用背景:

      • Symbol值解决了对象属性不被覆盖问题
      • 但Symbol值只能作用在局部
        • node环境: 不同时导出Symbol值, 导出对象的对应属性无法被其他模块主动访问
        • 浏览器属性: 不同时传递Symbol值, 对象的对应属性不能被其他iframe主动访问
      • 为此,出现了全局Symbol表及管理它的两个函数Symbol.for()与Symbol.keyFor()
    • Symbol.for()函数

      ECMA2019:
      在这里插入图片描述

    • Symbol.keyFor()函数

      ECMA2019:
      在这里插入图片描述

    全局Symbol表模型

    在这里插入图片描述

    注意事项

    ​ Symbol.for()与Symbol.keyFor()都是针对全局Symbol表进行查询和新建的, 其余位置的Symbol值不会被访问或影响.

    全局的测试

    • node: 全局环境–js程序的运行环境(在各个模块之上)
    //主文件.js
    const symMain = Symbol.for('Maintest');
    
    const {sym,IsSameSymbol} = require('./附文件.js');
    
    console.log(sym === Symbol.for('test')); //true
    console.log(IsSameSymbol(symMain)); //true
    
    //附文件.js
    const sym = Symbol.for('test');
    const symMain = Symbol.for('Maintest');
    
    function IsSameSymbol(s){
    	return s===symMain;
    }
    
    module.exports = {
    	sym: sym,
    	IsSameSymbol: IsSameSymbol
    };
    
    • 浏览器端: 全局环境–在各个frame之上
      <!DOCTYPE html>
      <html lang="zh-cn">
      <head>
      	<meta charset="UTF-8">
      	<title>全局Symbol测试</title>
      </head>
      <body>
      	hello world!
      	<script>
      		var iframe = document.createElement('iframe');
      		iframe.src = String(window.location);
      		document.body.appendChild(iframe);
      
      		window.onload = function(){
      			alert(iframe.contentWindow.Symbol.for('test')===Symbol.for('test'));
      		};
      	</script>
      </body>
      </html>
    

    Symbol类型的辨析和理解

    • symbol数据类型的真实值

      • 与symbol值关联的字符串其实是 它的描述,方便控制台打印时区分各个symbol值,而不是symbol值的真实值

      • symbol类型的真实值是无法获取和访问的,也并不重要,因为不会被开发者用到

      • symbol类型的意义在于它的唯一不可重复性,而不在于其真实值

    • 如何理解symbol类型的值

      • symbol类型值的特点

        • 新的原始数据类型(不同于number,string,boolean等)
        • 可以充当属性名和变量名(类似string)
        • 除非是同一句symbol()生成的值,否则不相等(类似object)

        根据以上特点,这个symbol类型的值特别像我们日常生活中的一种东西–二维码. 二维码的特点如下:

        • 属于图片(不是数值,也不是字符串)(新的类型)

        • 可以完成字符串的一些功能

      • 我们可以把symbol类型理解成 包含特定信息的二维码

        它包含的特定信息有三个:

        • 生成它的symbol()代码调用的行号
        • 生成它的symbol()代码调用的起始列号
        • 该symbol()代码调用所在文件的完整文件路径(包含文件名)

        通过这三个特定信息,我们不难看出每一个symbol()调用生成的symbol值(二维码)都一定是唯一且不可重复的: (举例说明)

        完整文件路径
        在这里插入图片描述

        行号与起始列号
        在这里插入图片描述

        行号 起始列号
        t1 1 10
        t2 1 29
        t3 2 10

        由此可见: 要想三个信息都符合, 那必须是同一个文件中的同一句Symbol()生成的同一个symbol值.因此是唯一不可重复的.

        接着,我们将这三个信息生成二维码:
        在这里插入图片描述

        接着,我们用t1代表的symbol值作为属性名给obj对象添加属性:

        var t1 = Symbol(); var t2 = Symbol();
        var t3 = Symbol();
        
        console.log(`t1==t2: ${t1==t2}
        t1==t3: ${t1==t3}
        t2==t3: ${t2==t3}`); //全是false
        
        var obj = {
        	[t1]: function(){
        		console.log('hello_world');
        	}
        };
        
        obj[t1]();
        

      我们把symbol类型值比作二维码的话, obj[t1](); 就相当于:
      在这里插入图片描述


    Symbol值作为属性名

    • 概述:

      • 每个Symbol值均不相等,独一无二
      • Symbol值作为标识符
        • 完全避免属性同名问题
        • 防止方法/属性被不小心覆盖
    • 定义Symbol属性名的三种方法:

      • 当做普通字符串使用(不必加引号)

        let mySymbol = Symbol();
        
        let a ={};
        a[mySymbol] = 'hello';
        
      • 字面量对象内的方括号定义

        let mySymbol = Symbol();
        
        let a = {
        	[mySymbol]:'hello'
        };
        
      • 用Object.defineProperty定义(实质上还是直接当做普通字符串)

        let mySymbol = Symbol();
        
        let a = {};
        Object.defineProperty(a, mySymbol, {value: 'hello'});
        
    • 注意事项

      • 不能用点运算符访问Symbol名的属性

      • 字面量方括号定义法中,方括号是必须的,否则仍是一个普通字符串名属性

      • 因为每一个symbol值都是独一无二的
        要使用以symbol为名的属性,只能在定义symbol值的模块内使用
        要想将该对象的该属性开放给其模块访问,必须同时导出: 对象 + symbol值

        (该特性可以用于分配和限制属性的访问权限?)

        • 有权限的,导出symbol值
        • 无权限的,不导出symbol值

    含Symbol名属性的对象的遍历

    • Symbol名属性的键值无法被常规遍历方法发现,包括:
      • for…in
      • for…of
      • Object.keys()
      • Object.getOwnPropertyNames()
      • JSON.stringify()
    • 获取方法:
      • Object.getOwnPropertySymbols()
      • Reflect.ownKeys()–常规键名+Symbol键名

    ES6内置Symbol值

    说明:

    • 这些值保存在Symbol的Constructor函数对象的属性之中

    • 这些值通常指向对象的内部方法

    • 通过这些Symbol值修改对象内部方法不一定有效:

      • 对象可能被冻结或封闭(例如: Symbol类型值)

      • Symbol名属性的配置项可能被设为不可配置与不可修改

        (即属性描述符为:{configurable:false,writable:false})

    简介

    instanceof相关–Symbol.hasInstance

    foo instanceof Foo; ==> Foo[Symbol.hasInstance](foo)

    下例展示了一个对instanceof的欺骗:

    class Myclass{
        [Symbol.hasInstance](foo){
            return foo instanceof Array;
        }
    }
    
    [1,2,3] instanceof new Myclass; //true
    

    类数组连接展开–Symbol.isConcatSpreadable

    ​ 该属性规定了该对象用于Array.prototype.concat() 时,是否可以展开

    let arr1 = ['c','d'];
    ['a','b'].concat(arr1,'e'); //['a','b','c','d','e']
    arr1[Symbol.isConcatSpreadable]; //undefined
    
    let arr2 = ['c','d'];
    arr1[Symbol.isConcatSpreadable] = false;
    ['a','b'].concat(arr2,'e'); //['a','b',['c','d'],'e']
    

    ECMA2019相关资料:

    (1)Array.prototype.concat
    在这里插入图片描述

    (2)IsConcatSpreadable(O)
    在这里插入图片描述

    解读:

    • 只在调用Array.prorotype.concat()函数中生效,

      • 其他concat函数中无效(没有针对该symbol名属性的操作)
    • 根据上述资料:

      • 不仅可用于数组,也可以用于类数组对象(有length属性,有数字键名)

      • 不设置该symbol名属性时(undefined值),数组与类数组表现相反:

        这是因为IsConcatSpreadble(o)资料中的最后一句:return ?IsArray(o)

        也就是说: 无定义时,数组默认展开,类数组默认不展开

      • 数组/类数组之中的空位(数字键不连续)保留到合并后的新数组

    指定生成衍生对象的构造函数–访问器属性Symbol.species

    • Symbol.species

      • 是一个构造函数(类)的访问器属性,需要用get设置

      • 存在于一些可进行衍生的类中

        ECMA2019资料:
        21.2.4.2get RegExp [ @@species ]
        22.1.2.5get Array [ @@species ]
        22.2.2.4get %TypedArray% [ @@species ]
        23.1.2.2get Map [ @@species ]
        23.2.2.2get Set [ @@species ]
        24.1.3.3get ArrayBuffer [ @@species ]
        24.2.3.2get SharedArrayBuffer [ @@species ]
        25.6.4.6get Promise [ @@species ]

      • 该Symbol值代表的属性(访问器的返回值)是一个构造函数,被用于构造衍生对象

      • 默认值: this.constructor,也就是用于创建衍生对象的原对象实例的构造函数

    • 举例说明: (以数组的map方法为例)

      代码:

      class MyArray extends Array {
      	static get [Symbol.species](){
      		return Array;
      	}
      
      	introduce(){
      		console.log('I am a MyArray instance');
      	}
      }
      
      const a = new MyArray();
      const b = a.map(x=>x); //利用Array.prototype.map()创建衍生对象b
      
      console.log(a.constructor); //[Function: MyArray]
      console.log(b instanceof MyArray); //false
      console.log(b.introduce); //undefined
      

      资料及知识补充:

      Array.prototype.map
      在这里插入图片描述

      ArraySpeciesCreate
      在这里插入图片描述

    由上图,可见, 确实是在创建衍生对象时,获取了原对象的构造函数的Symbol.species属性作为衍生对象的构造函数.结合示例代码:

    (1)原对象: 对象a–MyArray类的实例

    (2)原对象的构造函数: MyArray类的构造函数

    (3)原对象的构造函数的Symbol.species: 返回值为数组的构造函数–Array

    String.prototype.match匹配相关–Symbol.match

    在这里插入图片描述

    因此: str.match(obj)等同于obj[Symbol.match](str)

    class MyMatcher {
    	[Symbol.match](str){
            return 'hello world'.indexOf(str);
        }
    }
    
    'e'.match(new MyMatcher()); //1
    

    同系列的其他Symbol:

    • Symbol.search
    • Symbol.replace
    • Symbol.split

    ToPrimitive抽象操作–Symbol.toPrimitive

    ​ 其值是一个将对象类型转换为原始类型值的方法, 被抽象操作ToPrimitive调用.
    在这里插入图片描述

    如上图, 可见该函数接收一个字符串参数hint(三种取值对应三种模式):

    • Number:需要转成数值
    • String:需要转成字符串
    • Default: 数值,字符串皆可(number)

    示例代码:

    let obj = {
        [Symbol.toPrimitive](hint){
            switch(hint){
                case 'number': return 123;
                case 'default': return 'default';
                case 'string': return 'str';
                default: throw new Error();
            }
        }
    };
    
    console.log(2 * obj); //246
    console.log(3 + obj); //3default, 此时hint为default,详见ECMA2019 加号(addtion operator)
    console.log(String(obj)); //str
    

    类型字符串–Symbol.toStringTag

    对象的 Symbol.toStringTag 属性的值是一个字符串

    在该对象上调用 Object.prototype.toString 方法,若该属性存在,其值会出现在 toString 方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object] 或 [object Array] 中 object 后面的那个字符串.

    ECMA2019资料:
    在这里插入图片描述

  • 相关阅读:
    JAVA类和对象
    JAVA数组
    JAVA流程控制语句
    JAVA运算符
    JAVA数据类型-整数、浮点、字符串、boolean、引用数据类型
    JAVA变量
    JAVA文档注释的三种方式
    @Transactional注解失效的场景总结
    接口幂等性
    事务的四个特性、四种隔离级别和七种传播行为
  • 原文地址:https://www.cnblogs.com/peterzhangsnail/p/12521645.html
Copyright © 2011-2022 走看看