本文详解JavaScript ES6引入的Symbol原始数据类型,阐述其唯一性、不可运算性及遍历方法。介绍Symbol()与Symbol.for()的创建区别,以及使用Object.getOwnPropertySymbols和Reflect.ownKeys遍历Symbol属性。最后解析11个内置Symbol值,包括Symbol.hasInstance、Symbol.iterator等语言内部机制。
Symbol介绍与创建
ES6引入了Symbol这一新的原始数据类型,用于表示独一无二的值。它是JavaScript语言的第七种数据类型,在表现形式上类似于字符串。
Symbol具有以下特点:
Symbol的值具有唯一性,主要用于解决属性命名冲突的问题。
Symbol值不能直接与其他类型的数据进行数学运算。
通过Symbol定义的对象属性无法使用for...in循环进行遍历,但可以使用Reflect.ownKeys()方法获取对象的所有键名。
Symbol值通过调用Symbol()函数生成。这意味着对象的属性名现在有两种类型:原有的字符串类型,以及新增的Symbol类型。
创建Symbol主要有两种方式:Symbol()和Symbol.for()。两者的区别在于,前者创建的Symbol不会登记在全局环境中,无法被搜索到;而后者会进行全局登记。因此,Symbol.for()在每次调用时会先检查给定的key值是否已存在,若不存在才新建一个Symbol值,若存在则返回已有的值。
<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值可以显式转换为字符串,也可以转换为布尔值。
<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>针对JavaScript数据类型的分类,可以总结为USONB(you are so niubility)这一助记方式:
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值放在方括号[]中。
<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()方法可以获取指定对象的所有Symbol键名并返回一个数组。
<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键名,而Reflect.ownKeys(obj)方法则返回所有类型的键名,包括Symbol键名和非Symbol键名。
<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
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>到此这篇关于JavaScript原始数据类型Symbol的用法详解的文章就介绍到这了,更多相关JS Symbol内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!