浅谈 Object.freeze()

对象常量

结合 writable: false 和 configurable: false 可以创建一个整整的常量属性(不可修改、重定义和删除)

1
2
3
4
5
6
7
const obj = {}

Object.defineProperty(obj, 'test', {
value: 123,
writable: false,
configurable: false
})

禁止扩展 Object.preventExtensions(obj)

禁止一个对象添加新属性,并保留已有属性,可以使用 Object.preventExtensions(obj)

1
2
3
const obj = {}

Object.preventExtensions(obj)

密封 Object.seal(obj)

Object.seal(obj) 会创建一个 “密封” 对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(obj),并把所有属性表记为 configurable: false

所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以修改属性的值)

冻结 Object.freeze(obj)

Object.freeze(obj) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(obj),并把所有 “数据访问” 属性标记为 writable: false,这样就无法修改它们的值

这个方法是你可以应用在对象上级别最高的不可变性,它会禁止对于对象本身及其任意直接属性的修改(属性引用的其他对象不受影响)

如果想 “深度冻结” 一个对象,对该对象调用 Object.freeze(obj),在遍历所有引用的对象,并在这些对象上调用 Object.freeze(..),需要注意,这样会无意中冻结其他对象

通过WeakMap实现私有类变量

主要利用 WeakMap(弱映射)的 “不可迭代键” 特性实现

  • 因为 WeakMap 中 键值对 任何时候都可能被销毁,所以没必要提供迭代其键值对的能力
  • 因为不可迭代,所以也不能在不知道对象引用的情况下从 WeakMap 中取值
  • 之所以限制只能使用 键值对 作为键,是为了保证只有通过键值对的引用才能取得值
  • 如果允许原始值,那就没法区分初始化时使用的字符串字面量和初始化之后的一个相等字符串了

基于上述特性,可以实现真正的私有变量的一种新方式,前提很明确,私有变量会存储在 WeakMap 中,以对象实例为键,以私有成员的字典为值。

通过闭包,维护一个 WeakMap 集合,维护私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const User = (() => {
const wm = new WeakMap()

class User {
constructor (id) {
this.idProperty = Symbol('id')
this.setId(id)
}

setPrivate (property, value) {
// 把当前实例当作键值,存储对应对象,存储对应 value
const privateMembers = wm.get(this) || {}
privateMembers[property] = value
wm.set(this, privateMembers)
}

getPrivate (property) {
return wm.get(this)[property]
}

setId (id) {
this.setPrivate(this.idProperty, id)
}

getId () {
return this.getPrivate(this.idProperty)
}
}

return User
})()

JavaScript reduce 方法

关于最后一个参数 – 初始值:

  1. 计算特定初始值的累计结果
    例如,计算数组中所有元素的乘积,并且希望从特定的初始值(比如 1)开始计算,而不是从数组的第一个元素开始。如果不设置初始值,第一个元素将作为初始累计值,可能不符合预期的计算逻辑。

    1
    2
    3
    const arr = [2, 3, 4];
    const productWithInitialValue = arr.reduce((acc, curr) => acc * curr, 1);
    console.log(productWithInitialValue); // 24
  2. 处理空数组
    如果不确定数组是否为空,设置初始值可以确保在空数组的情况下也能返回一个合理的值,而不是 undefined。
    例如,计算数组中所有数字的和,如果数组为空,希望返回 0。

    1
    2
    3
    const arr = [];
    const sumWithInitialValue = arr.reduce((acc, curr) => acc + curr, 0);
    console.log(sumWithInitialValue); // 0
  3. 处理非数值类型的初始状态
    如果要进行的累计操作不是简单的数值计算,而是涉及到更复杂的数据结构或状态,通常需要设置一个合适的初始值。
    例如,将数组中的对象合并为一个新的对象,可以设置一个空对象作为初始值。**

    1
    2
    3
    const arr = [{ name: 'Alice' }, { name: 'Bob' }];
    const mergedObject = arr.reduce((acc, curr) => ({...acc,...curr }), {});
    console.log(mergedObject); // { name: 'Bob' }
  4. 处理可能存在单个元素的数组
    当数组可能只有一个元素时,设置初始值可以确保处理逻辑的一致性。
    例如,对数组中的元素进行特定的转换操作,如果只有一个元素,也希望按照统一的逻辑进行处理。

    1
    2
    3
    const arr = [5];
    const transformedValue = arr.reduce((acc, curr) => acc + curr, 0);
    console.log(transformedValue); // 5