详解Symbol(自定义值,内置值)-------小小的Symbol,大大的学问


ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是
JavaScript 语言的第七种数据类型

Symbol 特点:

1. Symbol 的值是唯一的,用来解决命名冲突的问题,即使参数相同

// 没有参数的情况
let name1 = Symbol();
let name2 = Symbol();
name1 === name2  // false
name1 === name2 // false

// 有参数的情况
let name1 = Symbol('flag');
let name2 = Symbol('flag');
name1 === name2  // false
name1 === name2 // false

2.Symbol 值不能与其他数据进行运算

 - 数学计算:不能转换为数字
 - 字符串拼接:隐式转换不可以,但是可以显示转换
 - 模板字符串

在这里插入图片描述


3) Symbol 定义的对象属性不参与 for…in/of 遍历,但是可以使用
Reflect.ownKeys / Object.getOwnPropertySymbols() 来获取对象的所有键名

let sy = Symbol();
let obj = {
    
    
	name:"zhangsan",
	age:21
};
obj[sy] = "symbol";
console.log(obj);   //{name: "zhangsan", age: 21, Symbol(): "symbol"}

for(let key in obj) {
    
    
	console.log(key);
}  //只输出了name,age



Object.getOwnPropertySymbols(obj);	 //[Symbol()]
Reflect.ownKeys(obj); 	//["name", "age", Symbol()]

Object.keys(obj);					 //["name", "age"]
Object.getOwnPropertyNames(obj) 	 //["name", "age"]
Object.keys(obj) 		//["name", "age"]
Object.values(obj) 		//["zhangsan", 21]
JSON.stringify(obj)		//{"name":"zhangsan","age":21}

注: 遇到唯一性的场景时要想到 Symbol


Symbol的方法:

1.Symbol.for()
                作用:用于将描述相同的Symbol变量指向同一个Symbol值,这样的话,就方便我们通过描述(标识)区分开不同的Symbol了,阅读起来方便

Symbol.for("foo"); // 创建一个 symbol 并放入 symbol 注册表中,键为 "foo"
Symbol.for("foo"); // 从 symbol 注册表中读取键为"foo"的 symbol


Symbol.for("bar") === Symbol.for("bar"); // true,证明了上面说的
Symbol("bar") === Symbol("bar"); // false,Symbol() 函数每次都会返回新的一个 symbol


var sym = Symbol.for("mario");
sym.toString(); 
// "Symbol(mario)",mario 既是该 symbol 在 symbol 注册表中的键名,又是该 symbol 自身的描述字符串

Symbol()和Symbol.for()的相同点:

  • 它们定义的值类型都为"symbol";

Symbol()和Symbol.for()的不同点:

  • Symbol()定义的值不放入全局 symbol 注册表中,每次都是新建,即使描述相同值也不相等;

  • 用 Symbol.for() 方法创建的 symbol 会被放入一个全局 symbol 注册表中。Symbol.for() 并不是每次都会创建一个新的 symbol,它会首先检查给定的 key 是否已经在注册表中了。假如是,则会直接返回上次存储的那个。否则,它会再新建一个。


2.Symbol.keyFor()
                作用: 方法用来获取 symbol 注册表中与某个 symbol 关联的键。如果全局注册表中查找到该symbol,则返回该symbol的key值,形式为string。如果symbol未在注册表中,返回undefined

// 创建一个 symbol 并放入 Symbol 注册表,key 为 "foo"
var globalSym = Symbol.for("foo"); 
Symbol.keyFor(globalSym); // "foo"

// 创建一个 symbol,但不放入 symbol 注册表中
var localSym = Symbol(); 
Symbol.keyFor(localSym); // undefined,所以是找不到 key 的

Symbol的属性

Symbol.prototype.description
                description 是一个只读属性,它会返回 Symbol 对象的可选描述的字符串。

// Symbol()定义的数据
let a = Symbol("acc");
a.description  // "acc"
Symbol.keyFor(a);  // undefined

// Symbol.for()定义的数据
let a1 = Symbol.for("acc");
a1.description  // "acc"
Symbol.keyFor(a1);  // acc

// 未指定描述的数据
let a2 = Symbol();
a2.description  // undefined


Symbol('desc').toString();   // "Symbol(desc)"
Symbol('desc').description;  // "desc"
Symbol('').description;      // ""
Symbol().description;        // undefined

description属性和Symbol.keyFor()方法的区别是:

  • description能返回所有Symbol类型数据的描述,而Symbol.keyFor()只能返回Symbol.for()在全局注册过的描述



以上就是Symbol的基本用法,你以为这就完了吗?上面的只是开胃菜而已,Symbol真正难的地方在于,它玩的都是底层



内置的Symbol值:

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。可以称这些方法为魔术方法,因为它们会在特定的场景下自动执行。

内置Symbol的值 调用时机
Symbol.hasInstance 当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法
Symbol.isConcatSpreadable 对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。
Symbol.species 创建衍生对象时,会使用该属性
Symbol.match 当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
Symbol.replace 当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。
Symbol.search 当该对象被 str. search (myObject)方法调用时,会返回该方法的返回值。
Symbol.split 当该对象被 str. split (myObject)方法调用时,会返回该方法的返回值。
Symbol.iterator 对象进行 for…of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器
Symbol.toPrimitive 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
Symbol. toStringTag 在该对象上面调用 toString 方法时,返回该方法的返回值
Symbol. unscopables 该对象指定了使用 with 关键字时,哪些属性会被 with环境排除。

特别的: Symbol内置值的使用,都是作为某个对象类型的属性去使用



内置值的应用:

Symbol.hasInstance:

对象的Symbol.hasInstance属性,指向一个内部方法,当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法


		class Person {
    
    }
		let p1 = new Person;
		console.log(p1 instanceof Person); //true
		// instanceof  和  [Symbol.hasInstance]  是等价的
		console.log(Person[Symbol.hasInstance](p1)); //true
		console.log(Object[Symbol.hasInstance]({
    
    })); //true


		//Symbol内置值得使用,都是作为某个对象类型的属性去使用
		class Person {
    
    
			static[Symbol.hasInstance](params) {
    
    
				console.log(params)
				console.log("有人用我来检测类型了")
				//可以自己控制 instanceof 检测的结果
				return true
				//return false
			}
		}
		let o = {
    
    }
		console.log(o instanceof Person)    //重写为true

Symbol.isConcatSpreadable

值为布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开

		let arr = [1, 2, 24, 23]
		let arr2 = [42, 25, 24, 235]
		//控制arr2是否可以展开
		arr2[Symbol.isConcatSpreadable] = false
		console.log(arr.concat(arr2)) //(5)[1, 2, 24, 23, Array(4)]

Symbol.iterator

ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费,这个内置值是比较常见的,也是一个对象可以被for of被迭代的原因,我们可以查看对象是否存在这个Symbol.iterator值,判断是否可被for of迭代,拥有此属性的对象被誉为可被迭代的对象可以使用for…of循环
在这里插入图片描述
打印{}对象可以发现,不存在Symbol.iterator,所以{}对象是无法被for of迭代的,而[]数组是可以,因为数组上面有Symbol.iterator属性
在这里插入图片描述

小提示:原生具备 iterator 接口的数据(可用 for of 遍历)

  • Array
  • Arguments
  • Set
  • Map
  • String
  • TypedArray
  • NodeList

那么知道原理了,我们就可以手动的给{}对象加上Symbol.iterator属性,使其可以被for of 遍历出来

	// 让对象变为可迭代的值,手动加上数组的可迭代方法
		let obj = {
    
    
			0: 'zhangsan',
			1: 21,
			length: 2,
			[Symbol.iterator]: Array.prototype[Symbol.iterator]
		};
		for(let item of obj) {
    
    
			console.log(item);
		}

这里有个缺陷: 因为我们使用的是数组原型上的Symbol.iterator,所以对象必须是个伪数组才能遍历,自定义一个对象上的Symbol.iterator属性,使其更加通用

		let arr = [1, 52, 5, 14, 23, 2]
		let iterator = arr[Symbol.iterator]()
		console.log(iterator.next())  //{value: 1, done: false}        
		console.log(iterator.next())  //{value: 52, done: false}       
		console.log(iterator.next())  //{value: 5, done: false}    
		console.log(iterator.next())  //{value: 14, done: false}       
		console.log(iterator.next())  //{value: 23, done: false}       
		console.log(iterator.next())  //{value: 2, done: false}        
		console.log(iterator.next())  //{value: undefined, done: true}
		
		

		//自定义[Symbol.iterator],使得对象可以通过for of 遍历
		let obj = {
    
    
			name: "Ges",
			age: 21,
			hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
			[Symbol.iterator]() {
    
     
				console.log(this)
				let index = 0
				let Keyarr = Object.keys(this)
				let len = Keyarr.length
				return {
    
    
					next: () => {
    
    
						if(index >= len) return {
    
    
							value: undefined,
							done: true
						}
						let result = {
    
    
							value: this[Keyarr[index]],
							done: false
						}
						index++
						return result
					}
				}
			}
		}

		for(let item of obj) {
    
    
			console.log(item)
		}

使用generator和yield简化

		//简洁版
		let obj = {
    
    
			name: "Ges",
			age: 21,
			hobbies: ["ESgsg", "Sfgse", "Egs", "SEGSg"],
			*[Symbol.iterator]() {
    
    
				for(let arg of Object.values(this)) {
    
    
					yield arg;
				}
			}

		}

		for(let item of obj) {
    
    
			console.log(item)
		}

这样实现后,{}对象 就变得可以使用for of遍历了,当然如果挂载到Obejct.prototype上所以对象都可以使用for of 遍历了
注: 需要自定义遍历数据的时候,要想到迭代器。


Symbol.toPrimitive

该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值

/*
 * 对象数据类型进行转换:
 * 1. 调用obj[Symbol.toPrimitive](hint),前提是存在
 * 2. 否则,如果 hint 是 "string" —— 尝试 obj.toString() 和 obj.valueOf()
 * 3. 否则,如果 hint 是 "number" 或 "default" —— 尝试 obj.valueOf() 和 obj.toString()
 */
let a = {
    
    
    value: 0,
    [Symbol.toPrimitive](hint) {
    
    
        switch (hint) {
    
    
            case 'number': //此时需要转换成数值 例如:数学运算`
                return ++this.value;
            case 'string': // 此时需要转换成字符串 例如:字符串拼接
                return String(this.value);
            case 'default': //此时可以转换成数值或字符串 例如:==比较
                return ++this.value;
        }
    }
};
if (a == 1 && a == 2 && a == 3) {
    
    
    console.log('OK');
}

当然自定义一个valueOf/toString 都是可以的,数据类型进行转换时,调用优先级最高的还是Symbol.toPrimitive

		//存在[Symbol.toPrimitive] 属性,优先调用
		let a = {
    
    
			value: 0,
			[Symbol.toPrimitive](hint) {
    
    
				console.log(hint)
				switch(hint) {
    
    
					case 'number': //此时需要转换成数值 例如:数学运算时触发
						return ++this.value;
					case 'string': // 此时需要转换成字符串 例如:字符串拼接时触发
						return String(this.value);
					case 'default': //此时可以转换成数值或字符串 例如:==比较时触发
						return ++this.value;
				}
			},
			valueOf: function() {
    
    
				console.log("valueOf")
				return a.i++;
			},
			toString: function() {
    
    
				console.log("toString")
				return a.i++;
			}
		};

Symbol.toStringTag

在该对象上面调用Object.prototype.toString方法时,如果这个属性存在,它的返回值会出现在toString方法返回的字符串之中,表示对象的类型

class Person {
    
    
    get [Symbol.toStringTag]() {
    
    
        return 'Person';
    }
}
let p1 = new Person;
console.log(Object.prototype.toString.call(p1)); //"[object Person]"

上述只说了五个常见的Symobl内置值的使用,剩下的就不一一叙述了,调用时机的清单表已经列出来了,感兴趣的可以自己去尝试研究下

总结:

总的来说Symbol用的最多的地方,还是它作为一个唯一值去使用,但我们需要知道,它不仅仅只是代表一个唯一值,Symbol难的地方在于它的内置值,它玩的都是底层

猜你喜欢

转载自blog.csdn.net/fesfsefgs/article/details/108354248
今日推荐