精读《JavaScript高级程序设计 第4版》第三章 语言基础(五)

AI提效·半月创作挑战赛 10w+人浏览 707人参与

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修炼前端秘籍的小帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值