JavaScript Symbol 类型

🌙
手机阅读
本文目录结构

Symbol存在的意义

Symbol 是JavaScript的原始数据类型,Symbol实例是唯一且不可改变的.可以作为对象属性的标识符使用;

在没有ES6的时候,属性名是很容易被覆盖的;

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

比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。

如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。

为了从根本上防止属性名的冲突,就是ES6引入Symbol的原因。

在其他编程语言中 symbol也被称为原子(atoms).

在JavaScript中, Symbol 是 基本数据类型 的一种,Symbol 对象是 Symbol原始值的封装 ,Symbol 类型是 ECMAScript 6 (es6) 中新添加的特性,在ECMAScript 5中没有对应的类型。

Symbol特性

创建Symbol

Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型;

一是原来就有的字符串, 二是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let aloneVal = Symbol();//注意不要用new来生成new;
console.log(typeof aloneVal);//symbol

注意不要用new来生成new;即时在ES5中,除了使用自己定义构造函数外,也不推荐加new;因为历史遗留原因,ES6中new创建string也支持,但不推荐这么用;

Symbol函数前不能使用new命令,是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Symbol的参数

Symbol的参数,是描述使用的;仅仅是为了控制输出时候方便识别,没有别的用了;

const s1 = Symbol('foo');
const s2 = Symbol('bar');
console.log(s1,s2);//Symbol(foo) Symbol(bar)

如果不加参数,它们在控制台的输出都是Symbol(),不利于区分。有了参数以后,就等于为它们加上了描述,输出的时候就能够分清,到底是哪一个值。

注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。

//和NaN一样,连自己都不等于自己:因为里面的参数只是一个描述;
console.log(Symbol("foo") == Symbol("foo"));//false
console.log(Symbol("") == Symbol(""));//false
console.log(Symbol() == Symbol());//false   连自己都不等于自己:
const foo = Symbol();
const bar = Symbol();
const symbolVal = Symbol("11");
console.log(typeof foo , typeof bar);//symbol symbol
console.log(typeof foo === "symbol");//true
console.log(typeof bar === "symbol");//true
console.log(bar === bar);//true 当然如果用变量储存,俩个变量是相等的,但这并不说明2个相同Symbol值是相同的
console.log(bar === foo);//false
console.log(symbolVal);//Symbol(11)

Symbol值不能与其他类型的值进行运算,会报错。

Symbol值不能与其他类型的值进行运算,会报错;但是可以显式转为字符串/布尔值;但是不能转为数值;

//Symbol不可以进行运算,但是可以转换为字符串和布尔值,但是不能转为数值;
const sym=Symbol("flag");
//console.log(sym+"hello baby");// Cannot convert a Symbol value to a string
console.log(String(sym)+"hello one");//Symbol(flag)hello one
console.log(sym.toString()+"hello two");//Symbol(flag)hello two
console.log(Boolean(sym),typeof Boolean(sym));//true "boolean"
console.log(Number(sym));//Cannot convert a Symbol value to a number

作为属性名的Symbol

如果我们不知道Symbol的实例名字是什么,我们怎么获取到Symbol值的呢, Symbol无法被for in , for of循环,以及Object.keys, Object.values 等都无法遍历到Symbol的属性; Object下给我们提供了一个getOwnPropertySymbols;

let sym0 = Symbol("o_o?");
let obj = {
    [sym0] : "heheda"
}
for( let prop of Object.getOwnPropertySymbols(obj) ) {
    //prop就是Symbol的名字
    console.log( obj[prop] ); //输出:heheda
};

或者用ES6提供的反射 : Reflect.ownKeys, 反射?

let sym0 = Symbol("o_o?");
let obj = {
    [sym0] : "heheda"
}
console.log( Reflect.ownKeys(obj) ); //输出:[ Symbol(o_o?) ]

Symbol.for和 Symbol.keyFor

Symbol.for和Symbol的唯一区别是 Symbol.for创建的两个实例可能相等, 根据Symbol的参数生成实例, 如果参数一样, 那么会返回同一个实例;

let foo = Symbol.for( "1111" );
let bar = Symbol.for("1111");
console.log( foo === bar );  //输出: true
//只有通过Symbol.for创建的对象,才能用keyFor找到原来的参数;
console.log(Symbol.keyFor(foo)) //会输出:1111

Symbol的属性以及这些属性的用处:

  • Symbol.prototype: Symbol有指向的原型:

    console.log(Symbol.prototype); //输出Symbol的原型
    
  • Symbol.length: Symbol的length为1, 也就是说调用Symbol需要一个参数, 当然不给参数也没啥事。

  • Symbol.Iterator:对象的Symbol.Iterator属性, 指向这个对象的默认遍历器:

    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
        yield 1;
        yield 2;
        yield 3;
    };
    console.log([...myIterable]); // [1, 2, 3]
    
  • Symbol.match:ES6中字符串去匹配对象的Symbol.match方法, 匹配的结果完全可控, 以下Demo,相当于把字符串"strstring"作为obj[Symbol.match]方法的参数, 然后返回用户设定的值, chrome和FF目前还不支持:

    let obj = {
        [Symbol.match](string) {
            console.log(string);
            return "heheda";
        }
    };;
    console.log("strstring".match(obj));
    
  • Symbol.replace:和上面的道理一样

    let obj = {
        [Symbol.replace](string) {
            console.log(string);
            return "replllll";
        }
    };
    console.log( "sssss".replace(obj) ); //输出:  sssss    replllll
    
  • Symbol.split:和上面的道理一样。

  • Symbol.toPrimitive:对象的Symbol.toPrimitive指向一个方法, 当对象转化为原始值得话, 会调用这个方法, 一个对象转为原始值由三种情况:string, number, default:

    var obj1 = {};
    console.log(+obj1);     // NaN
    console.log(`${obj1}`); // "[object Object]"
    console.log(obj1 + ""); // "[object Object]"
    
    // obj2有定义Symbol.toPrimitive属性;
    var obj2 = {
        [Symbol.toPrimitive](hint) {
        if (hint == "number") {
            return 10;
        }
        if (hint == "string") {
            return "hello";
        }
        return true;
    }
    };
    console.log(+obj2);     // 10      -- 转化 为 "number"
    console.log(`${obj2}`); // "hello" -- 转化 为 "string"
    console.log(obj2 + ""); // "true"  -- 转化 为 "default"
    
  • Symbol.toStringTag:Symbol.toStringTag, 这个玩意儿厉害了, 连对象的toString方法都给改了,而且用 Object.prototype.toString.call 这个对象 输出也是一样的, 这个情何以堪, 以后判断元素类型必须用别的方法了, 改动大了….

    console.log({ [Symbol.toStringTag] : "str "}.toString()); //输出:[object str ]
    console.log(Object.prototype.toString.call({ [Symbol.toStringTag] : "str "}))
    //输出:[object str ]
    class Collection { 
        get [Symbol.toStringTag]() { 
            return 'xxx'; 
        } 
    } 
    var x = new Collection(); 
    console.log(x.toString())// 输出:"[object xxx]"
    

代码回顾

var validateId={};//用来验证身份证的;
validateId.age=1900;
console.log(validateId.age);//1900

validateId.age=1940;
console.log(validateId.age);//1900

//模块内部的
validateId.demo1=Symbol();// AAXX111  demo1
console.log(validateId.demo1);//Symbol()

// 外部的
validateId.demo1=1900;  //demo1
console.log(validateId.demo1);//1900

var aaa=Symbol("a");// //AAXX222
var bbb=Symbol("a");// //AAXX333  生成的实例是唯一的,和其他任何实例都不相等,包括其他相同生成的实例
console.log(aaa);
console.log(bbb);
console.log(aaa==bbb);//相当于NaN
console.log(Symbol()==Symbol());//


var age1=2;
var age2=2;
console.log(age1,age2);
console.log(age1==age2);//true

//模块/插件里面的
var age=Symbol("age");//AAXX666
validateId[age]=222;
/*
*
*
*
*
*
* */

//other
//    var age=Symbol("age");//AAXX777
validateId[Symbol("age")]=333;


validateId.aaa=222;
validateId.bbb=222;

AXIHE / 精选资源

浏览全部教程

面试题

学习网站

前端培训
自己甄别

前端书籍

关于朱安邦

我叫 朱安邦,阿西河的站长,在杭州。

以前是一名平面设计师,后来开始接接触前端开发,主要研究前端技术中的JS方向。

业余时间我喜欢分享和交流自己的技术,欢迎大家关注我的 Bilibili

关注我: Github / 知乎

于2021年离开前端领域,目前重心放在研究区块链上面了

我叫朱安邦,阿西河的站长

目前在杭州从事区块链周边的开发工作,机械专业,以前从事平面设计工作。

2014年底脱产在老家自学6个月的前端技术,自学期间几乎从未出过家门,最终找到了满意的前端工作。更多>

于2021年离开前端领域,目前从事区块链方面工作了