理解JavaScript基本数据类型symbol
symbol
概念
symbol是⼀个ES6标准种新增的⼀种基本数据类型,在JavaScript中,共有七种基本数据类型:string、number、bigint、boolean、null、undefined、symbol。并且除了null和undefined之外,每个基本类型都有其包装对象。
symbol 的值是通过 Symbol() 函数⽣成,每⼀个 symbol 的值都是唯⼀的,并且 symbol 类型的值可以作为对象的属性标识符使⽤,这也是 symbol 类型设计的⽬的。
所以现在对象属性名可以为两种类型:⼀种就是原本的字符串类型,⼀种即为新增的 symbol 类型。凡是使⽤ symbol 命名的属性都是独⼀⽆⼆的,保证不与其他属性名产⽣冲突。
JavaScript 中⼤多数的数值都⽀持隐式转换为字符串,但 symbol 不会转换:
let s1 = Symbol('sym');
alert(s1); // TypeError: Cannot convert a Symbol value to a string
symbol 也不能与其他类型的值进⾏运算:
console.log('symbol is' + s1); // TypeError: Cannot convert a Symbol value to a string
但是如果有必要,可以⼿动将 symbol 转换成字符串
String());
或者获得定义 symbol 时的描述:
alert(s1.description);
symbol 转换为其他类型:
Boolean(s1); // true
Number(s1); // TypeError: Cannot convert a Symbol value to a number
parseInt(s1); // NaN
⽤法
创建⼀个 symbol 的值需要使⽤ Symbol() 函数,⽽不能使⽤ new 命令。
let s1 = Symbol('sym');
由于⽣成的 symbol 是⼀个值⽽不是对象,所以不能为其添加属性。
Symbol() 函数可以接受⼀个字符串作为参数,表⽰对该值的描述,因此即使定义 symbol 使⽤相同的参数互相之间也不是相同的:
let s1 = Symbol('sym');
let s2 = Symbol('sym');
s1 === s2 ; // false
Symbol.for() 、 Symbol.keyFor()
如果我们要重复使⽤⼀个 symbol 时,可以⽤到 Symbol.for() ⽅法。Symbol.for() ⽅法接受⼀个字符串参数,会在全局中搜索有没有以该参数命名的 symbol 的值,如果查到就返回这个值。如果没有查到则重新⽣成⼀个值,并将该值以参数名称注册到全局。
let s1 = Symbol.for('sym'); // 创建
let s2 = Symbol.for('sym'); // 查
s1 === s2; // true
Symbol.for() 和 Symbol() ⽅法都会⽣成新的 symbol 类型的值,不同的是 Symbol.for() ⽅法会查命名参数是否在全局中注册过,如果注册过的就不会创建新的值,⽽是会直接返回,所以我们可以使⽤到相同的 symbol 值。但使⽤ Symbol() ⽅法每次都会创建⼀个新的值,且不会注册到全局。
Symbol.keyFor() ⽅法表⽰获取⼀个 symbol 的值在全局中注册的命名参数 key,只有使⽤ Symbol.for() 创建的值才会有注册的命名参数,使⽤ Symbol() ⽣成的值则没有:
let s4 = Symbol('sym');
let s5 = Symbol.for('sym');
Symbol.keyFor(s4); // undefined
Symbol.keyFor(s5); // sym
注意使⽤ Symbol.for() 注册的全局命名参数是真正意义上的全局,⽽不管是否运⾏在全局环境。
let iframe = ateElement('iframe');
iframe.src = 'www.baidu';
document.body.append(iframe);
内置的 symbol 值
除了⾃定义 symbol 的值,ES6 还提供了 11 个内置的 symbol 值,指向语⾔内部使⽤的⽅法。
Symbol.hasInstance
Symbol.hasInstance 指向⼀个内部⽅法,当使⽤ instanceof 运算符时,判断⼀个实例是否为某种类型时,会调⽤这个内部⽅法。
例如在调⽤:foo instanceof Foo 时,实际是调⽤ Foo;
class Foo {
[Symbol.hasInstance](foo){
return foo instanceof Array;
}
}
[1,2,3] instanceof new Foo(); // true
Symbol.isConcatSpreadable
该属性的值是⼀个布尔型,表⽰当调⽤ at() ⽅法时,是否可以展开。
Symbol.isConcatSpreadable的默认值为undefined
例:
let arr = [1,2,3];
数组默认可以展开,当设置了 Symbol.isConcatSpreadable = false
let arr = [1,2,3];
arr[Symbol.isConcatSpreadable] = false;
Symbol.species
对象的 Symbol.species 属性指向⼀个构造函数,在创建衍⽣对象可以更改构造函数指向。
例:
class MyArr extends Array{}
let a = new MyArr(1,2,3);
let b = a.filter(e => e >= 2);
a instanceof MyArr; // true
b instanceof MyArr; // true
其中变量 b 是变量 a 的衍⽣对象,变量 b 也是 MyArr 的实例,⽽通过设置 Symbol.species 可以更改这个衍⽣对象的构造函数:
class MyArr extends Array{
static get [Symbol.species](){
return Array;
}
}
let a = new MyArr(1,2,3);
let b = a.filter(e => e >= 2);
a instanceof MyArr; // true
b instanceof MyArr; // false
实际上,默认的 Symbol.species 属性等同:
javascript全局数组static get [Symbol.species]() {
return this;
}
总之该属性实在实例运⾏过程中,需要再次调⽤⾃⾝构造函数时,可以使⽤指定的构造函数。
Symbol.match
该属性指向⼀个函数,当执⾏ str.match(object) 时,如果该属性存在,会调⽤该属性指向的⽅法:
let str = 'e';
class StrObj {
[Symbol.match](string){
return 'hello'.indexOf(string);
}
}
str.match(new StrObj()); // 1
// 等同于
new StrObj()[Symbol.match](str); // 1
该属性指向⼀个⽅法,当对象被 place ⽅法调⽤时,会执⾏该⽅法:
place(searchValue, replaceValue)
// 等同于
place](this, replaceValue)
Symbol.search
对象的 Symbol.search 属性,指向⼀个⽅法,当该对象被String.prototype.search ⽅法调⽤时,会返回该⽅法的返回值。
String.prototype.search(regexp)
// 等同于
regexp[Symbol.search](this)
Symbol.split
对象的 Symbol.split 属性,指向⼀个⽅法,当该对象被 String.prototype.split ⽅法调⽤时,会返回该⽅法的返回值。
String.prototype.split(separator, limit)
// 等同于
separator[Symbol.split](this, limit)
Symbol.iterator
对象的 Symbol.iterator 属性,指向该对象的默认遍历器⽅法。
const myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable] // [1, 2, 3]
对象的 Primitive 属性,指向⼀个⽅法。该对象被转为原始类型的值时,会调⽤这个⽅法,返回该对象对应的原始类型值。Primitive 被调⽤时,会接受⼀个字符串参数,表⽰当前运算的模式,⼀共有三种模式:
Number:该场合需要转成数值
String:该场合需要转成字符串
Default:该场合可以转成数值,也可以转成字符串
let obj = {
[Primitive](hint) {
switch (hint) {
case 'number':
return 123;
case 'string':
return 'str';
case 'default':
return 'default';
default:
throw new Error();
}
}
};
2 * obj // 246
3 + obj // '3default'
obj == 'default' // true
String(obj) // 'str'
对象的 StringTag 属性,指向⼀个⽅法。在该对象上⾯调⽤String ⽅法时,如果这个属性存在,它的返回值会出现在toString⽅法返回的字符串之中,表⽰对象的类型。也就是说,这个属性可以⽤来定制 [object Object] 或 [object Array] 中object 后⾯的那个字符串。
// 例⼀
({[StringTag]: 'Foo'}.toString())
// "[object Foo]"
// 例⼆
class Collection {
get [StringTag]() {
return 'xxx';
}
}
let x = new Collection();
String.call(x) // "[object xxx]"
Symbol.unscopables
对象的 Symbol.unscopables 属性,指向⼀个对象。该对象指定了使⽤with关键字时,哪些属性会被 with 环境排除。
即使⽤ Symbol.unscopables 指定的对象,在使⽤ with 语法块时,不在当前作⽤域中寻特定属性,⽽是从外层作⽤域开始查。
// 没有 unscopables 时
class MyClass {
foo() { return 1; }
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 1
}
// 有 unscopables 时
class MyClass {
foo() { return 1; }
get [Symbol.unscopables]() {
return { foo: true };
}
}
var foo = function () { return 2; };
with (MyClass.prototype) {
foo(); // 2
}
应⽤场景
使⽤ symbol 作为对象的属性名
由于每⼀个 symbol 的值都是不相同的,所以使⽤ symbol 作为属性名可以保证不会出现同名属性。防⽌某⼀个属性被改写覆盖:
let s1 = Symbol(‘sym’);
let obj = {
name: ‘test obj’,
[s1]: ‘this is symbol’
}
obj[s1]; // this is symbol
注意使⽤ symbol 作为对象的属性名时,是⽆法通过 Object.keys() 和 for…in 来遍历对象的属性的:
Object.keys(obj); // ["name"]
for(let name in obj){
console.log(name); // name
}
因此,在将对象进⾏ JSON 转换时: JSON.stringify() ,symbol 的属性也会被排除:
JSON.stringify(obj); // "{"name":"test obj"}"
需要注意的是,使⽤ symbol 定义的属性,还是公有的属性,不是私有属性。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论