3.4.8 Symbol 类型
Symbol 是 ES6 引入的一种新的原始数据类型,Symbol(符号)是原始值,且符号实例是唯一、不可变的。表示唯一的、不可变的值,主要用于创建对象的唯一属性名,不会发生属性冲突的危险。
1、符号的基本用法
符号需要使用Symbol()函数初始化,调用该函数时也可以传入一个字符串作为对于符号的描述,将来可以通过这个字符串来调试代码,但是,这个字符串参数与符号定义或标识完全无关。注意,Symbol()函数不能与new关键字一起使用。
// 创建 Symbol
const sym1 = Symbol();
const sym2 = Symbol();
console.log(sym1 === sym2); // false - 每个 Symbol 都是唯一的
console.log(typeof sym1); // "symbol"
// 可以添加描述信息(用于调试)
const sym1 = Symbol("description");
const sym2 = Symbol("description");
console.log(sym1 === sym2); // false - 即使描述相同,Symbol 也不同
console.log(sym1.toString()); // "Symbol(description)"
2、使用全局符号注册表
如果运行时的不同部分需要共享和重用符号实例,那么可以用一个字符串作为键,在全局符号注册表中创建并重用符号。为此可以使用Symbol.for()方法:
// 从全局注册表获取,如果不存在则创建
const globalSym1 = Symbol.for("app.key");
const globalSym2 = Symbol.for("app.key");
console.log(globalSym1 === globalSym2); // true - 相同的全局 Symbol
// 与普通 Symbol 比较
const localSym = Symbol("app.key");
console.log(globalSym1 === localSym); // false
全局注册表中的符号必须使用字符串来创建,因此传递给Symbol.for()的任何值都会被转换为字符串。
可以使用Symbol.keyFor()来查询全局注册表,这个方法也接收符合,返回该全局符合对应的字符串键,若查询的不是全局符号则返回undefined
//创建全局符号
const globalSym = Symbol.for("myKey");
console.log(Symbol.keyFor(globalSym)); // "myKey"
//创建普通符号
const localSym = Symbol("localKey");
console.log(Symbol.keyFor(localSym)); // undefined - 非全局 Symbol
//传递的不是符号
Symbol.keyFor(123); //TypeError: 123 is not a symbol
3、 使用符号作为属性
凡是可以使用字符串或数值作为属性的地方,都可以使用符号。
创建Symbol属性:
可以使用Object.defineProperty()/Object.defineProperties()定义属性
const id = Symbol("id");
const address = Symbol("address");
const user = {
name: "John",
[id]: 123, // 使用 Symbol 作为属性名
age: 30
};
// Object.defineProperty(user, id, { value: 123 });
/* Object.defineProperties(user, {
[id]: { value: 123 },
[address]: { value: '中国' }
}) */
console.log(user[id]); // 123
console.log(user.id); // undefined - 不能使用点语法
枚举Symbol属性:
const id = Symbol("id");
const type = Symbol("type");
const obj = {
name: "Object",
[id]: "unique-id",
[type]: "user",
visible: true
};
// for...in 循环不包含 Symbol 属性
for (let key in obj) {
console.log(key); // "name", "visible"
}
// Object.keys() 也不包含 Symbol 属性
console.log(Object.keys(obj)); // ["name", "visible"]
// 获取所有 Symbol 属性
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(id), Symbol(type)]
//获取所有常规属性
console.log(Object.getOwnPropertyNames(obj)); //["name", "visible"]
// 获取所有属性(包括 Symbol)
console.log(Reflect.ownKeys(obj)); // ["name", "visible", Symbol(id), Symbol(type)]
4、常用内置符号
ES6 提供了一些内置的 Symbol,用于暴露语言内部行为,开发者可以修改对象的默认行为。这些内置符号都已Symbol工厂函数字符串的形式存在。这些内置符号就是全局函数Symbol的普通字符串属性,指向一个符号的实例。所有内置符号属性都是不可写、不可枚举、不可配置的。
(1)Symbol.asyncIterator(ES2018规范定义的)
Symbol.asyncIterator
是一个内置的 Symbol 值,用于定义对象的异步迭代器。它是异步迭代协议的核心,允许对象在 for-await-of
循环中被迭代。异步迭代器与普通迭代器类似,但它的 next()
方法返回一个 Promise,而不是直接返回值。这使得它可以处理异步数据源。
class Emitter {
constructor(max) {
this.max = max;
this.asyncIdx = 0;
}
async *[Symbol.asyncIterator]() {
while(this.asyncIdx < this.max){
yield new Promise((resolve) => resolve(this.asyncIdx++));
}
}
}
async function asyncCount() {
let emitter = new Emitter(5);
for await( const x of emitter ){
console.log(x);
}
}
asyncCount();
//0
//1
//2
//3
//4
(2)Symbol.hasInstance - 自定义 instanceof 行为
该方法决定一个构造器对象是否认可一个对象是它的实例。由instanceof操作符使用;instanceof操作符可以用来确定一个对象实例的原型链上是否有原型。
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance);
}
}
console.log([] instanceof MyArray); // true
console.log({} instanceof MyArray); // false
(3)Symbol.isConcatSpreadable
这个符号作为一个属性表示“一个布尔值,如果是true,则意味着对象应该用Array.prototype.concat()打平其数组元素”。false或假值会导致整个对象被追加到数组末尾,类数组对象默认情况下会被追加到数组末尾。
//普通数组
let initial = ["x", "y"];
let arr = ["z"];
console.log(arr[Symbol.isConcatSpreadable]); //undefined
console.log(initial.concat(arr)); //["x", "y", "z"]
arr[Symbol.isConcatSpreadable] = false;
console.log(initial.concat(arr)); //["x", "y", Array(1)]
//类数组
const arrayLike = {
0: "a",
1: "b",
length: 2,
};
console.log(arrayLike[Symbol.isConcatSpreadable]); //undefined
console.log(initial.concat(arrayLike)); // ["x", "y",{...}]
arrayLike[Symbol.isConcatSpreadable] = true;
console.log(initial.concat(arrayLike)); // ["x", "y", "a", "b"]
(4) Symbol.iterator - 定义对象的迭代器
这个符号作为一个属性表示:“一个方法,返回对象默认的迭代器。由for-of语句使用”。
const myIterable = {
[Symbol.iterator]: function* () {
yield 1;
yield 2;
yield 3;
}
};
for (let value of myIterable) {
console.log(value); // 1, 2, 3
}
(5) Symbol.match - 自定义字符串匹配
表示“一个正则表达式方法,该方法用正则表达式去匹配字符串。由String.prototype.match()方法使用”
class MyMatcher {
[Symbol.match](string) {
return string.indexOf("hello") !== -1;
}
}
const matcher = new MyMatcher();
console.log("hello world".match(matcher)); // true
console.log("world".match(matcher)); // false
(6) Symbol.replace
表示“一个正则表达式方法,该方法用替换一个字符串中匹配的子串。由String.prototype.replace()方法使用”
class MyReplacer {
constructor(value) {
this.value = value;
}
[Symbol.replace](string) {
return string.replace(this.value, "***");
}
}
console.log("hello world".replace(new MyReplacer("hello"))); // "*** world"
(7) Symbol.search
表示“一个正则表达式方法,该方法返回字符串中匹配正则表达式的索引。由String.prototype.search()方法使用”
class StringSearcher {
constructor(str) {
this.str = str;
}
[Symbol.search](target) {
return target.indexOf(this.str);
}
}
console.log('abc'.search(new StringSearcher('ab'))); // 0
console.log('abc'.search(new StringSearcher('d'))); // -1
(8) Symbol.species
表示“一个函数值,该函数作为创建派生对象的构造函数”,用 Symbol.species定义静态的获取器(getter)方法。
class MyArray extends Array {
static get [Symbol.species]() {
return Array; // 派生对象使用 Array 而不是 MyArray
}
}
const myArray = new MyArray(1, 2, 3);
const mapped = myArray.map(x => x * 2);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true
3.4.9 Object类型
Object 是 JavaScript 中最基本的数据类型,几乎所有对象都继承自 Object。可以说 "JavaScript 中一切皆对象"。
1、创建对象
//最常用的对象字面量形式
const person = {
name: "John",
age: 30,
isStudent: false,
greet: function() {
return `Hello, I'm ${this.name}`;
}
};
//构造函数方式
// 使用 Object 构造函数
const obj1 = new Object();
obj1.key = "value";
// 等同于
const obj2 = {};
obj2.key = "value";
//Object.create方法
const prototypeObj = {
greet() {
return "Hello!";
}
};
const newObj = Object.create(prototypeObj);
newObj.name = "Alice";
console.log(newObj.greet()); // "Hello!"
2、Object基本属性和方法
(1)constructor
指向创建该实例对象的构造函数。
const obj = {};
console.log(obj.constructor === Object); // true
const arr = [];
console.log(arr.constructor === Array); // true
function Person() {}
const person = new Person();
console.log(person.constructor === Person); // true
(2) hasOwnProperty(propertyName)
检查对象自身(非继承)是否具有指定属性。
const obj = { name: "John" };
console.log(obj.hasOwnProperty("name")); // true
console.log(obj.hasOwnProperty("toString")); // false (继承的)
// 与 in 操作符的区别
console.log("name" in obj); // true
console.log("toString" in obj); // true (包括继承属性)
(3) isPrototypeOf(object)
检查对象是否在另一个对象的原型链上。
function Animal() {}
function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
const dog = new Dog();
console.log(Animal.prototype.isPrototypeOf(dog)); // true
console.log(Dog.prototype.isPrototypeOf(dog)); // true
console.log(Object.prototype.isPrototypeOf(dog)); // true
(4) propertyIsEnumerable(propertyName)
检查属性是否可枚举。
const obj = {
name: "John",
age: 30
};
// 添加不可枚举属性
Object.defineProperty(obj, "id", {
value: 123,
enumerable: false
});
console.log(obj.propertyIsEnumerable("name")); // true
console.log(obj.propertyIsEnumerable("id")); // false
console.log(obj.propertyIsEnumerable("toString")); // false (继承的)
(5) toString()
返回对象的字符串表示。
const obj = { name: "John" };
console.log(obj.toString()); // "[object Object]"
const date = new Date();
console.log(date.toString()); // "Thu Dec 05 2024 10:30:00 GMT+0800"
// 自定义 toString 方法
const person = {
name: "Alice",
age: 25,
toString() {
return `Person: ${this.name}, ${this.age} years old`;
}
};
console.log(person.toString()); // "Person: Alice, 25 years old"
(6) valueOf()
返回对象的原始值。
const obj = { x: 10, y: 20 };
console.log(obj.valueOf()); // { x: 10, y: 20 } - 对象本身
const num = new Number(42);
console.log(num.valueOf()); // 42 - 原始数值
// 在数学运算中自动调用
const objWithValueOf = {
value: 100,
valueOf() {
return this.value;
}
};
console.log(objWithValueOf + 50); // 150