你有多了解 Object?

1,015 阅读4分钟

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

今天来梳理一下与 Object 有关的内容。

在MDN上的介绍:Object - JavaScript | MDN (mozilla.org)

制作不可变对象

Object.freeze

我们都知道函数式编程中有一个概念叫纯函数,这是一个没有副作用的函数,对于相同的输入一定能得到相同的结果,不依赖外部变量,不能修改输入的值。

但是函数传参对于对象传的是引用地址,我们通过obj.a = 'xxx'很容易就修改了对象obj的实际内容。有没有一种方法让函数体内能够访问入参obj的内容而又保证在函数体内没有人能修改它?

答案就是用 Object.freeze()

Object.freeze()可以冻结一个对象,冻结后该对象就再也不能被修改。

const a = { val: 233}
Object.freeze(a)
a.val = 250
console.log(a)//输出结果 {val: 233}

与之相似的还有 Object.seal(), Object.preventExtensions(),三者的区别如下:

freezesealpreventExtensions
全都不能修改(不能修改值、原型、不能增删属性,不可配置)不能添加新的属性且不可配置不能添加新的属性

应用场景:如React的函数是组件的入参 props 是不可变的,在框架层上先将props用Object.freeze()包裹再传给组件函数,可以确保开发者一定无法在组件内修改prop的值。

获取原始类型

Object.prototype.toString.call

我们可以通过 typeof 鉴别基本类型,但是无法区分null和非null对象和数组。想要做到这一点,可以使用Object.prototype.toString.call

console.log(Object.prototype.toString.call([])) // [object Array]
console.log(Object.prototype.toString.call(null))// [object Null]
console.log(Object.prototype.toString.call({})) // [object Object]

注意,不能少了prototype属性,比如以下代码是不合法的:

Object.toString.call([])

这个会报TypeError

Uncaught TypeError: Function.prototype.toString requires that 'this' be a Function
    at Array.toString (<anonymous>)

原因是 Object和Boolen、Number、String等构造函数都是自身重写了 toString 方法,这个方法和 Object.prototype.toString完全不一样。只有后者能够获取到原始类型。

以某对象为原型创建一个新对象

Object.create()

对象有这么一个特性,当你访问对象a的一个字段b,即访问a.b的时候,如果 b 不是 a 自身的属性,js就会尝试沿着a的原型链查找最近的含有b属性的原型。而通过Object.create(obj)方法可以用obj作为原型创建一个空对象。

const a = Object.create({b: 1})
console.log(a)// {}
console.log(a.b)// 1

Object.create的其中一个应用场景是模拟散列表。在es6的 Map、Set的标准数据结构出来以前,实现Map的方法一般如下所示:

const map = Object.create(null)
// 增或改
map.a = 1
// 删
delete map.a
// 查
'a' in map

为了模拟散列表,我们使用Object.create(null)而不是直接使用{}是因为后者原型上还有一些方法,名字可能会和用户设置的key重复,而前者将原型设置 null 则无此问题。

除此之外,另一个应用场景就是构造原型链。在软件工程的设计模式中有一个模式叫职责链模式,它的概念如下:

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,沿着这条链传递该请求,直到有一个对象处理它为止。

原型链就是一个天生的职责链模式,我们可以将Object.create加一层封装,设计成一个构建职责链节点的函数。

function createBuildNodeFn() {
    let last;
    return (options) => {
        last = Object.create(last ?? null);
        Object.assign(last, options);
        return last;
    };
}
const buildNode = createBuildNodeFn();
const node1 = buildNode({
   handleXXX() {
   // ...
   }
});
const node2 = buildNode({
   handleXXX() {
   //...
   }
});

node2.handleXXX()

对象属性描述符是个啥

Object.getOwnPropertyDescriptor

对象属性描述符用于描述对象一个属性的性质,它是一个含有以下字段的对象:

  • value
  • enumerable
  • configurable
  • writable
const c = { b: undefined};
console.log(Object.getOwnPropertyDescriptor(c, 'b'))
// {value: undefined, writable: true, enumerable: true, configurable: true}

默认情况下,除了 value 以外的3个字段取值都是true,但是Object.createObject.definePropertiesObject.defineProperty都有机会修改对象属性描述符,详情可自行查阅 MDN (mozilla.org)

  • configurable 设为 false 则无法再次修改属性描述符,也无法删除 delete 该属性,
  • enumerable 设为 false 则无法被for循环遍历
  • writable 设为 false 则无法被修改此值。