← 返回
前端开发 2025.03.08

深入理解ES6 006【学习笔记】

前端开发

Symbol和Symbol属性

6种原始数据类型:Symbol。私有名称原本是为了让开发者们创建非字符串属性名称而设计的,但是一般的技术无法检测这些属性的私有名称

创建Symbol

let firstName = Symbol();
let person = {}
person[firstName] = "Nicholas";
console.log(person[firstName]); // Nicholas 

new Symbol() 会报错,Symbol函数接受一个可选参数,其可以让你添加一段文本描述即将创建的Symbol,这段描述不可用于属性访问,但是建议你在每次创建Symbol时都添加一段描述,以便于阅读代码和调试Symbol程序

let firstName = Symbol('first name');
let person = {}
person[firstName] = "Nicholas"
console.log("first name" in person); // false
console.log(person[firstName]); //Nicholas
console.log(firstName) // "Symbol(first name)"

Symbol的描述被存储在内部的[[Description]]属性中,只有当调用Symbol的toString()方法才可以读取这个属性,在执行console.log时,隐式调用了firstName的toString()方法,所以它的描述会被打印到日志中,但是不能直接在代码里访问[[Description]]

使用typeof symbol 如果是会返回’symbol’,所有使用计算属性名的地方,都可以使用Symbol。

Symbol的使用方法

我们之前都是在括号中使用Symbol,事实上,Symbol也可以用于可计算对象字面量属性、Object.defineProperty()方法和Object.defineProperties()方法的调用过程

let fristName = Symbol("first name");
// 使用一个可计算对象这字面量属性
let person = {
  [firstName]:"Nicholas"
}
// 将属性设置为只读
Object.defineProperty(person,firstName,{writable:false})

let lastName = Symbol('last name');

Object.defineProperties(person,{
  [lastName]:{
    value:"Zakas",
    writable:false
  }
})
console.log(person[firstName]) //Nicholas
console.log(person[lastName]) //Zakas

虽然在所有使用可计算属性名的地方,都可以使用Symbol来替代,但是为了在不同的代码片段间有效地共享这些Symbol,需要建立一个体系

Symbol共享体系

例如我们想在不同的对象类型,想使用同一个Symbol属性来表示一个独特的标识符。在很大的代码库或跨文件追踪Symbol非常困难且容易出错,出于这些原因ES6提供了一个可以随时访问全局Symbol注册表。使用Symbol.for(xxx) 只接受一个参数。要创建Symbol的字符串描述类似Symbol('xxx')Symbol.for(xxx)会去全局注册表中找有没有注册这个Symbol,有就返回,没有就创建一个并注册到全局表中,下次不用再新建一个了。

Symbol与类型转换

我上面的示例使用console.log()方法来输出Symbol的内容,它会调用Symbol的String()方法输出有用信息,如果你试图将一个Symbol和一个字符串拼接,会导致程序抛出错误。

let uid = Symbol.for("uid"),
desc = String(uid);
console.log(desc); // Symbol(uid)

Symbol的属性检索

Object.keys()Object.getOwnPropertyNames()方法可以检索对象中所有的属性名,后一个方法不考虑属性的可枚举性一律返回。这两个方法都支持Symbol属性。所以又推出了一个Object.getOwnPropertySymbols()方法返回一个包含所有Symbole自有属性的值。

let uid = Symbol.for("uid");
let object = {
  [uid]:"12345"
}
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length) // 1
console.log(symbols[0]) // "Symbol(uid)"
console.log(object[symbols[0]]) //12345

通过well-know Symbol暴露内部操作

  • Symbol.hasInstance
  • Symbol.isConcatSpreadable
  • Symbol.iterator
  • Symbol.match
  • Symbol.replace
  • Symbol.species
  • Symbol.split
  • Symbol.species
  • Symbol.toPrimitive
  • Symbol.toStringTag
  • Symbol.unscopables
obj instanceof Array;
// 等价于
Array[Symbol.hasInstance](obj)
// 本质上,es6只是将instanceof操作符重新定义为此方法的简写语法,现在引入调用后,就可以随意改变instanceof的运行方式了。
// 假设你想定义一个无实例的函数,就可以将Symbol.hasInstance的返回值硬编码为false
function MyObject(){
  // 空函数
}
Object.defindProperty(MyObject,Symbol.hasInstance,{
  value:function(v){
    return false
  }
})
let obj = new MyObject()
console.log(obj instanceof MyObject) // false

Symbol.isConcatSpreadable

js数组的concat方法被设计用于拼接两个数组,使用如下方法:

let color1 =["red","green"],
color2 = color1.concat(["blue","black"])
console.log(color2.length) // 4
console.log(color2) // ["red","green","blue","black"]

// color1与一个临时数组拼接成两个数组
// concat方法也可以接受非数组参数,此时该方法只会将这些参数逐一添加到数组末尾如下
let color1 =["red","green"],
color2 = color1.concat(["blue","black"],"brown")
console.log(color2.length) // 5
console.log(color2) // ["red","green","blue","black","brown"]
// 这个属性可以设置或阻止调用concat方法时是否展开