JavaScript原始数据类型Symbol的用法详解
亦世凡华、 人气:0Symbol介绍与创建
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值,它是JavaScript语言的第七种数据类型,是一种类似于字符串的数据类型。
Symbol特点:
Symbol的值是唯一的,用来解决命名冲突问题。
Symbol值不能与其他数据进行运算。
Symbol定义的对象属性不能使用 for...in 循环遍历,但是可以使用 Reflect.ownKeys 来获取对象的所有键名。
Symbol值通过Symbol()函数生成,也就是说,对象的属性名可以有两种类型,一种就是原来就有的字符串,另一种就是新增的Symbol类型。
创建Symbol有俩种方式,Symbol和Symbol.for(),这两种方式的区别前者不能登记在全局环境中供搜索,后者却可以,所有Symbol.for()不会每次调用就返回一个新的Symbol类型值,而是先检查给定的key值是否存在,不存在才会新建一个值。
<script> // 创建Symbol let s = Symbol() console.log(s,typeof s);//Symbol() 'symbol' //Symbol()函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述 //Symbol()函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。 let s1 = Symbol('张三') let s2 = Symbol('张三') console.log(s1 === s2);//false // 创建Symbol的另一种方法:Symbol.for() 该方法创建的字符串参数相等,Symbol也相等 let s3 = Symbol.for('张三') let s4 = Symbol.for('张三') // Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。 console.log(Symbol.keyFor(s3));//张三 console.log(s3 === s4);//true </script>
Symbol 值不能与其他类型的值进行运算,否则会报错,但是Symbol值可以显示转换为字符串,而且Symbol也可以转为布尔值。
<script> let s = Symbol('My Symbol') // let result = s + 100 // console.log(result);//Cannot convert a Symbol value to a number // 转字符串 console.log(String(s));//Symbol(My Symbol) // 转布尔值 console.log(Boolean(s));//true </script>
当然如果你想直接返回 Symbol 的字符串参数的值的话,可以借用 Symbol 的实例属性:description,直接返回 Symbol 值的描述。
<script> let s = Symbol('My Symbol') console.log(s.description);//My Symbol </script>
针对数据类型的类别可以总结以下方式:(面试官问你JS有哪几种数据类型好用到)
USONB (you are so niubility)
u:undefined
s:string、Symbol
o:object
n:null、number
b:boolean
设置Symbol属性的注意点
为了保证不会出现同名的属性,防止对象的某一个键不小心被改写或覆盖。使用作为属性名的Symbol 进行操作。设置Symbol的方式如下:
注意:我们在进行设置Symbol值作为对象属性名时,是不能用点运算符,因为点运算符后面总是字符串,所以不会读取作为Symbol标识名所指代的那个值,导致Symbol的属性名实际上是一个字符串,而不是一个Symbol值,所以在对象内部使用Symbol值定义属性时,Symbol值必须放在方括号里面。
<script> let s = Symbol('My Symbol') // 第一种 let a = {} a[s] = 'hello world' // 第二种 let b = { [s]:'hello people' } // 第三种 let c = {} Object.defineProperty(c,s,{value:'hello animal'}) // 以上结果都能达到相同的效果,举例 console.log(c); </script>
Symbol属性名的遍历
如果想遍历Symbol的属性名,正常的方法是办不到的,需要使用特定的方法,有一个方法可以获取指定对象的所有键名并返回一个数组,方法为:Object.getOwnPropertySymbols()方法。
<script> let obj = {} let a = Symbol('a') let b = Symbol('b') obj[a] = 'hello' obj[b] = 'world' const objectSymbols = Object.getOwnPropertySymbols(obj) console.log(objectSymbols);//[Symbol(a), Symbol(b)] </script>
上面的方法能够返回Symbol的键名,还有一个方法能够返回Symbol键名,但不只有Symbol键名而是所有的键名都被返回出来。该方法为:Reflect.ownKeys(obj)。
<script> let obj = { name:'张三', age:18, [Symbol('mySymbol')]:1, gender:'男' } console.log(Reflect.ownKeys(obj));//['name', 'age', 'gender', Symbol(mySymbol)] </script>
Symbol内置值
除了定义自己使用的 Symbol 值以外,ES6还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。方法如下:
Symbol.hasInstance
当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法。
<script> class Person { static [Symbol.hasInstance](param){ console.log(param); console.log('我被用来检测类型了'); //return true } } let o = {} console.log(o instanceof Person); </script>
当出现 instanceof 时,symbol 的 hasInstance 属性就会被触发,并且可以把要判断的实例对象传进来。也就是说将 instanceof 前面的值传递到 param 形参这个方法,由它来决定返回的true还是false。说白了就是可以自己控制类型检测。
Symbol.isConcatSpreadable
对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于数组上面时Array.prototype.concat()语句,判断数组是否可以展开。false为不展开。
<script> const arr = [1,2,3] const arr1 = [4,5,6] console.log(arr.concat(arr1)); // 将arr1设置为不展开,作为一个整体与arr进行合并 arr1[Symbol.isConcatSpreadable] = false console.log(arr.concat(arr1)); </script>
类似数组的对象正好相反,默认不展开,它的 Symbol.isConcatSpreadable 属性设为 true,才能展开。
<script> let obj = { 0:'a', 1:'b', 2:'c', length:3 } console.log([1,2].concat(obj,'d')); // 将该属性设置为true,进行展开 obj[Symbol.isConcatSpreadable] = true console.log([1,2].concat(obj,'d')); </script>
Symbol.unscopables
该对象指定了使用 with 关键字时,哪些属性会被 with 环境排除。
<script> // 将排除的键名以数组方式打印出来 console.log(Object.keys(Array.prototype[Symbol.unscopables])); </script>
// 没有 unscopables 时 class MyClass { foo() { return 1; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 1 } // 有 unscopables 时 // 通过指定Symbol.unscopables属性,使得with语法块不会在当前作用域寻找foo属性,即foo将指向外层作用域的变量 class MyClass { foo() { return 1; } get [Symbol.unscopables]() { return { foo: true }; } } var foo = function () { return 2; }; with (MyClass.prototype) { foo(); // 2 }
Symbol.match
JavaScript中的Symbol.match属性是well-known符号,用于标识正则表达式与字符串的匹配,当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
<script> const reg = /hello world/ reg[Symbol.match] = false console.log('/hello/'.startsWith(reg));//false console.log('/hello world/'.startsWith(reg));//true </script>
Symbol.replace
当该对象被 str.replace(myObject) 方法调用时,会返回该方法的返回值。
<script> const x = {}; // Symbol.replace方法会收到两个参数,第一个参数是replace方法正在作用的对象,第二个参数是替换后的值。 x[Symbol.replace] = (...s) => console.log(s); // ["Hello", "World"] 'Hello'.replace(x, 'World') </script>
Symbol.search
指定了一个搜索方法,这个方法接受用户输入的正则表达式,返回该正则表达式在字符串中匹配到的下标,这个方法由以下的方法来调用 String.prototype.search()。该属性指向一个方法,当该对象被 str.search(myObject)方法调用时,会返回该方法的返回值。
<script> class caseInsensitiveSearch { constructor(value) { this.value = value.toLowerCase(); } [Symbol.search](string) { return string.toLowerCase().indexOf(this.value); } } console.log('foobar'.search(new caseInsensitiveSearch('BaR'))); // expected output: 3 </script>
Symbol.split
Symbol.split 这个属性方法 指向的是一个正则表达式的索引处分割字符串的方法,这个方法通过 String.prototype.split() 调用;该属性指向一个方法,当该对象被 str.split(myObject)方法调用时,会返回该方法的返回值。
<script> var exp = { pat:'in', [Symbol.split](str) { return str.split(this.pat); } } // "dayinlove".split(exp)调用[Symbol.split](str)处理,并把实参"dayinlove"传给形参str console.log("dayinlove".split(exp));//["day", "love"] </script>
Symbol.iterator
对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器。
<script> const myIterable = {}; myIterable[Symbol.iterator] = function* () { //function*这种声明方式会定义一个生成器函数,它返回一个对象。 yield 1; yield 2; yield 3; }; console.log([...myIterable]);// [1, 2, 3] </script>
Symbol.toPrimitive
作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。该函数被调用时,会传递一个字符粗参数 hint ,表示要转换到的原始值的预期类型。hint参数的取值是:number、string 和 default 中的任意一个。
// 一个没有提供 Symbol.toPrimitive 属性的对象,参与运算时的输出结果 var obj1 = {}; console.log(+obj1); // NaN console.log(`${obj1}`); // "[object Object]" console.log(obj1 + ""); // "[object Object]" // 接下面声明一个对象,手动赋予了 Symbol.toPrimitive 属性,再来查看输出结果 var obj2 = { [Symbol.toPrimitive](hint) { if (hint == "number") { return 10; } if (hint == "string") { return "hello"; } return true; } }; console.log(+obj2); // 10 -- hint 参数值是 "number" console.log(`${obj2}`); // "hello" -- hint 参数值是 "string" console.log(obj2 + ""); // "true" -- hint 参数值是 "default"
Symbol.toStringTag
通常作为对象的属性键使用,对应的属性值应该为字符串类型,这个字符串用来表示该对象的自定义类型标签。
<script> class Person { get [Symbol.toStringTag](){ return 'xxx' } } let per = new Person() console.log(Object.prototype.toString.call(per));//[object xxx] </script>
Symbol.species
指定构造函数用于创建派生对象的函数值属性,即创建衍生对象时,会使用该属性。作用:实例对象在运行过程中,会调用该属性指定的构造函数,用来返回基类的实例而不是子类的实例。
<script> class Array1 extends Array { static get [Symbol.species]() { return Array; } } const a = new Array1(1, 2, 3); const mapped = a.map(x => x * x); // 定义了Symbol.species属性后,导致 a.map(x => x * x) 生成的衍生对象不在是Array1的实例而是Array console.log(mapped instanceof Array1);//false console.log(mapped instanceof Array);//true </script>
加载全部内容