我才知道每天都在使用Object.defineProperty

lxf2023-03-14 09:04:01

之前写过一篇文章,让a==1&&a==2&&a==3成立中,有提到过访问器属性来解决该面试题的解法, 下面深入谈一谈。

一、Object.definePropertyObject.definpropertise介绍

先来看看下面的代码

let obj = {
  name: '橘子哥',
  age: '22'
}

想要控制obj对象中的属性进行配置或者操作,可以通过Object的definePropertydefinpropertise两种方法

我才知道每天都在使用Object.defineProperty

  • 定义:它们是函数,可以修改一个已知对象的属性,修改包括修改已有的对象属性以及给该对象创建新的属性。
  • 差别:从名字就可以知道,前者可以修改对象的单个属性,后者可以修改对象的多个属性。
  • 语法
    • 前者的语法为Object.defineProperty(obj, prop, descriptor)
    • 后者的是Object.defineProperties(obj, props)
我才知道每天都在使用Object.defineProperty

对象里目前存在的属性描述符有两种主要形式:数据描述符存取描述符数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。 ———— MDN

一般讨论的就是这两种两种描述符。上面展示的是数据描述符,下面是存取描述符的式图:

我才知道每天都在使用Object.defineProperty

可以从上图看到,存取描述符是不具有值的,所以说,涉及到值的valuewriable是没有的。不然需要的取值的时候,是应该拿value的还是调用get()的呢?这就是数据描述符和存取描述符不能同存。你不能既是男的,又是女的吧?

其实我更加愿意把存取描述符称之为钩子属性。它本身并不存储值,但是我们使用obj.name的时候,会触发get这个函数,就像钩子一样。

数据描述符和存取描述符的属性的关系如下:

我才知道每天都在使用Object.defineProperty

具体这6个属性的用法,推荐直接去看MDN的文档,已经足够的简单直白。

简单的了解了一些它们的用法,那么它们出现在我工作中的什么地方呢?

我一贯不理解为什么翻译为访问器属性这个字的意思,脑中没有画面感。后来看了MDN对于将其翻译为存取描述符,这就恰如其名了。

二、两种描述符对象出现在我们工作中哪里?

讨论两种描述符对象出现在我们工作中哪里,其实就是在讨论它们存在的意义。而我觉得这一点比它们本身的定义更加的重要。

只有了解了他们存在的意义,才可以直接感受到它们的重要性!自然会提高对它们的重视程度。它们早已经融入了我们的业务开发当中,以至于,我们时常忘记了它们的存在。

就拿Object.defineProperty来说,它存在于我们将ES6转化为ES5当中,它存在于我们在使用vue2的时候需要响应式的时候。

2.1 实现es6的class static修饰符 - 数据描述符的使用

这是我之前在面试富途时候的一个面试题,面试官叫我将es6 class中static、construct等等用ES5实现一遍。我平时确实没有思考过怎么实现,只知道是protptye那些的语法糖。面试评分估计也不甚理想,所以记忆犹新。

其中,defineProperty的存取描述符是关键点之一。我们以static修饰符的实现为例,下面代码片段摘抄于babel转换器:

"use strict";

function _defineProperty(obj, key, value) {
  key = _toPropertyKey(key);
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}
function _toPropertyKey(arg) {
  var key = _toPrimitive(arg, "string");
  return typeof key === "symbol" ? key : String(key);
}
function _toPrimitive(input, hint) {
  if (typeof input !== "object" || input === null) return input;
  var prim = input[Symbol.toPrimitive];
  if (prim !== undefined) {
    var res = prim.call(input, hint || "default");
    if (typeof res !== "object") return res;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return (hint === "string" ? String : Number)(input);
}
class Test {}
_defineProperty(Test, "name", 1);

可以看到其中对于变量的配置也有Object.defineProperty的影子。

2.2 Vue的响应式 - 存取描述符的使用

vue的响应式简单来说,就是监听变量的变化,当该变量改变之后,就改变视图。也就是我们要监听变量的变化,那么存取描述符岂不是正好合适。

核心代码如下:

data: {
  name: "橘子哥哥",
}
for (let key in this.data) {
 Object.defineProperty(this, key, {
   get() {
     return this.data[key]
   },
   set(newValue) {
     this.data[key] = newValue
     渲染界面({ [key]: newValue })
   },
 })
}

更详细的解析在我写的《手把手代你写MVC》一文,感兴趣的可以自行查阅。

2.3 让(a===1&&a===2&&a===3)为真 - 数据描述符和存取描述符的合作

Object.defineProperties(window, {
  _a: {
    value: 0,
    writable: true
  },
  a: {
    get: function() {
      return  ++_a
    }
  }
})

2.4 在屎山代码上面修改

想想这么一个场景,当我们有修改一个在多处使用的对象的属性的时候,我们需要找到所以使用到该对象的地方,让逐个去改。

或者说,又重新添加一个对象属性。

如果这时候我们使用存取描述符来监听这个对象的属性的变化,然后根据变化的进行相应的逻辑操作。这样是不是就简单很多了。

“开启AdminJS成长之旅!这是我参与「AdminJS日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情”