深入了解 Object.defineProperty

更新日期: 2022-04-30阅读: 860标签: object

属性的操作

在 JavaScript 中,给对象增加一个属性是非常简单的,直接调用属性并赋值即可。

const obj = {};
obj.name = 'Tom';
console.log(obj);
/**
 * 输出:
 * {name: 'Tom'}
 */

通过这种方式添加的属性,可以随意操作:

  • 可修改
  • 可枚举
  • 可删除

可修改:

// 可修改
+ obj.name = 'Jim';
+ console.log(obj.name);
  /**
  * 输出:
  * 'Jim'
  */

可枚举:

// 可枚举
+ for (let key in obj) {
+   console.log(`${key} : ${obj[key]}`);
+ }
  /**
  * 输出:
  * name : Jim
  */

可删除:

// 可删除
+ delete obj.name;
+ console.log(obj);
  /**
  * 输出:
  * {}
  */

如果想通过 Object.defineProperty 实现上面的功能,可以使用下面的代码

- obj.name = 'Tom';
+ Object.defineProperty(obj, 'name', {
+   value: 'Tom',
+   writable: true,
+   enumerable: true,
+   configurable: true,
+ });

函数签名

在对 Object.defineProperty 深入学习之前,先对这个方法签名有一个认识:

Object.defineProperty(obj, prop, descriptor);

从函数签名中可以看出, defineProperty 是 Object 上的一个静态方法,可以传递三个参数:

obj
prop
descriptor

返回值是被传递给函数的对象,也就是第一个参数 obj 。

描述符可以有以下几个可选值:

configurable
enumerable
value
writable
get
set

描述符

通过 Object.defineProperty 来为对象定义一个属性。

const obj = {};
Object.defineProperty(obj, 'name', {});
console.log(obj);
/**
 * 输出:
 * {name: undefined}
 */

从输出的结果可以看出,在对象 obj 上增加一个属性 name ,但是它的值是 undefined 。

value

如果想给属性赋值,可以使用描述符中的 value 属性。

- Object.defineProperty(obj, 'name', {});
+ Object.defineProperty(obj, 'name', {
+   value: 'Tom',
+ });
  /**
  * 输出:
  * {name: 'Tom'}
  */

writable

一般情况下,修改一个对象中的属性值,可以使用 obj.name = 'Jim' 的形式。

+ obj.name = 'Jim';
+ console.log(obj);
  /**
  * 输出:
  * {name: 'Tom'}
  */

从输出结果可以看出,并没有修改成功。如果想修改属性值,可以把描述符中的 writable 设置为 true 。

Object.defineProperty(obj, 'name', {
    value: 'Tom',
+   writable: true,
  });

enumerable

枚举对象的属性,可以使用 for...in 。

+ for (let key in obj) {
+   console.log(`${key} : ${obj[key]}`);
+ }

比较奇怪的是,执行上面的代码没有输出任何信息。

如果想正常枚举对象的属性,可以将描述符中的 enumerable 值设置为 true 。

Object.defineProperty(obj, 'name', {
    value: 'Tom',
    writable: true,
+   enumerable: true,
  });

configurable

当这个属性不需要时,可以通过 delete 来删除。

+ delete obj.name;
+ console.log(obj);
  /**
  * 输出:
  * {name: 'Jim'}
  */

从输出结果可以看出,并没有达到预期的效果。如果想从对象上正常删除属性,可以将描述符中的 configurable 设置为 true 。

Object.defineProperty(obj, 'name', {
    value: 'Tom',
    writable: true,
    enumerable: true,
+   configurable: true,
  });

get

如果需要获取对象的值,可以使用描述符中的 get 。

const obj = {};
let _tmpName = 'Tom';
Object.defineProperty(obj, 'name', {
  get() {
    return _tmpName;
  },
});
console.log(obj.name);
/**
 * 输出:
 * {name: 'Tom'}
 */

set

如果需要设置对象的值,可以使用描述符中的 set ,它需要传递一个参数,就是修改后的值。

Object.defineProperty(obj, 'name', {
    get() {
      return _tmpName;
    },
+   set(newVal) {
+     _tmpName = newVal;
+   },
  });

+ obj.name = 'Jim';
+ console.log(obj.name);
  /**
  * 输出:
  * {name: 'Jim'}
  */

注意事项

在操作符对象中,如果存在了 value 或 writable 中的任意一个或多个,就不能存在 get 或 set 了。

const obj = {};
Object.defineProperty(obj, 'name', {
  value: 1,
  get() {
    return 2;
  },
});

报错信息如下:

Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

为了方便后期查阅,总结一下互斥的情况:

  • value 和 get 互斥
  • value 和 set 互斥
  • value 和 set + get 互斥
  • writable 和 get 互斥
  • writable 和 set 互斥
  • writable 和 set + get 互斥

使用场景

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。该方法允许精确地添加或修改对象的属性。

这个方法是 JavaScript 的一个比较底层的方法,主要用于在对象上添加或修改对象的属性。

简单应用

基础修饰符的使用

假设现在有一个需求,实现下面的效果:

const obj = { a: 1, b: 2, c: 3 };
for (let key in obj) {
  obj[key] += 1;
}
console.log(obj);
/*
输出:
{ a: 3, b: 3, c: 5 }
*/

Object.defineProperty() 不仅可以定义属性,也可以修改属性。这个时候就可以使用修改属性的方式来实现上面的需求。

for (let key in obj) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    value: ++obj[key],
    writable: key !== 'b',
  });
}

get 的使用

使用 Object.defineProperty 中的 get 将 console.log 中的信息正常输出。

if (num === 1 && num === 2 && num === 3) {
  console.log('you win ...');
}

从题目中可以看出, num 不可能即等于 1 ,又等于 2 ,还等于 3 。如果想实现这样的效果,必然需要在 num 取值的同时自增。

要实现一个变量取值并自增,就需要使用 Object.defineProperty 中的 get 。 num 是直接使用的,在浏览器中,只有挂载到 window 对象上的属性可以直接使用。

let _tmpNum = 0;
Object.defineProperty(window, 'num', {
  get() {
    return ++_tmpNum;
  },
});

假设现在有一个需求,实现如下效果:

_ // a
_ + _ // ab
_ + _ + _ // abc

这个需求其实就是需要在 window 对象上挂载一个 _ 属性,每调用一次 _ 就会自增一次 ASCII 码。

Object.defineProperty(window, '_', {
  get() {
    // 获取字母 a 的 ASCII 码
    const aAsciiCode = 'a'.charCodeAt(0);
    // 获取字母 z 的 ASCII 码
    const zAsciiCode = 'z'.charCodeAt(0);
    // 如果 _code 不存在,将其赋值为 a 的 ASCII 码
    this._code = this._code || aAsciiCode;
    // 如果 _code 的范围超出了小写字母的范围,直接返回
    if (this._code > zAsciiCode) return;
    // 获取当前 ASCII 码对应的字母
    const _char = String.fromCharCode(this._code);
    // 每调用一次自增一次
    this._code++;
    // 返回
    return _char;
  },
});

如果想打印输出 26 个字母的组合,可以通过遍历的方式。

let resStr = '';
for (let i = 0; i < 26; i++) {
  resStr += _;
}
console.log(resStr);

set 的使用

如果将一个字符串赋值为 'Object' ,打印这个字符串输出 {type: 'Object', length: 6} ;如果将一个字符串赋值为 'Object' ,打印这个字符串输出 {type: 'Array', length: 5} ;如果将字符串赋值成其他值,程序报错 TypeError: This type is invalid. 。

分析这个题目,打印的时候其实就是取值的过程,需要用到 get 操作符, type 是当前字符串的值, length 是当前字符串的长度。

let _tmpStr = '';
Object.defineProperty(window, 'str', {
  get() {
    return { type: _tmpStr, length: _tmpStr.length };
  },
});

在给字符串赋值的时候,当字符串的值是 'Object' 或 'Array' 的时候正常赋值,其余情况直接抛出错误。这个操作就需要在操作符的 set 中实现。

Object.defineProperty(window, 'str', {
    get() {
      return { type: _tmpStr, length: _tmpStr.length };
    },
    set(newVal) {
+     if (newVal === 'Object' || newVal === 'Array') {
+       _tmpStr = newVal;
+     } else {
+       throw new TypeError('This type is invalid.');
+     }
    },
  });

验证代码的执行效果:

str = 'Object';
console.log(str);
/*
输出:
{type: 'Object', length: 6}
*/

str = 'Array';
console.log(str);
/*
输出:
{type: 'Array', length: 5}
*/

str = '123';
console.log(str);
/*
输出:
TypeError: This type is invalid.
*/

复杂应用

需求:在页面中有一个输入框,下面有一个显示区域,当输入框中的内容发生变化时,显示区中的内容同步变化。当刷新页面时,页面中的信息保持和刷新前一致。

首先,在 index.html 中绘制页面信息:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div><input type="text" placeholder="输入信息" id="idInfo" /></div>
    <div id="idShowInfo"></div>
    <script type="module" src="./js/index.js"></script>
  </body>
</html>

简单实现

最简单的实现方式就是进入页面的时候从缓存中获取数据,能够获取到就给输入框和显示区域赋值;同时监听输入框的输入事件,向缓存和显示区域中写入对应的信息。

function init() {
  const eleInfo = document.getElementById('idInfo');
  const eleShowInfo = document.getElementById('idShowInfo');

  const _storageInfo = JSON.parse(localStorage.getItem('storageInfo') || '{}');
  if (_storageInfo.info) {
    eleInfo.value = _storageInfo.info;
  }
  eleShowInfo.innerHTML = eleInfo.value;

  eleInfo.addEventListener(
    'input',
    function () {
      localStorage.setItem(
        'storageInfo',
        JSON.stringify({ info: eleInfo.value || '' }),
      );
      eleShowInfo.innerHTML = eleInfo.value;
    },
    false,
  );
}

init();

Object.defineProperty 实现

上面的实现方式相对而言比较直接且比较简单,但是代码的封装性比较差,并且数据耦合性比较高。如果使用 Object.defineProperty 就可以更好的组织代码。

首先书写入口文件的代码 js/index.js :

import { observer } from './observer.js';

const eleInfo = document.getElementById('idInfo');
const eleShowInfo = document.getElementById('idShowInfo');
const infoObj = observer({ info: '' }, eleInfo, eleShowInfo);

function init() {
  bindEvent(eleInfo);
}

function bindEvent(ele) {
  ele.addEventListener('input', handleInput, false);
}

function handleInput(event) {
  const _info = event.target.value || '';
  infoObj.info = _info;
}

init();

其次书写 js/observer.js 中的代码:

export function observer(infoObj, inputdom, viewDom) {
  const _storageInfo = JSON.parse(localStorage.getItem('storageInfo') || '{}');
  const _resInfo = {};
  init(_storageInfo, infoObj, _resInfo, inputDom, viewDom);
  return _resInfo;
}

function init(storageInfo, infoObj, resInfo, inputDom, viewDom) {
  initData(storageInfo, infoObj, resInfo, inputDom, viewDom);
  initDom(resInfo, inputDom, viewDom);
}

function initData(storageInfo, infoObj, resInfo, inputDom, viewDom) {
  for (let key in storageInfo) {
    infoObj[key] = storageInfo[key];
  }

  for (let key in infoObj) {
    (function (key) {
      Object.defineProperty(resInfo, key, {
        get() {
          return infoObj[key];
        },
        set(newVal) {
          infoObj[key] = newVal;
          localStorage.setItem('storageInfo', JSON.stringify(infoObj));
          initDom(resInfo, inputDom, viewDom);
        },
      });
    })(key);
  }
}

function initDom(resInfo, inputDom, viewDom) {
  inputDom.value = resInfo.info;
  viewDom.innerHTML = resInfo.info;
}


链接: https://fly63.com/article/detial/11442

Js中Object对象的理解,Object常用的属性和方法有哪些?

在JavaScript中的所有对象都是继承Object对象而来的, 我们可以理解为Object是所有js对象通用的功能,讲解Object的prototype属性、Object.prototype属性和方法?Object.prototype.constructor属性等

Object.create()的使用总汇:创建对象,参数说明,兼容性的实现

JavaScript中Object.create()的含义和语法,使用它创建null原型的对象 ,创建一个普通的空对象,Object.create()第二个参数说明。

Javascript中的object相等

相等是JavaScript中起初最让人困惑的部分。==和===的比较、强制类型的顺序等等,都使得这个问题变得复杂。今天,我们会聚焦另一个方面:object相等是如何实现的。

js 中 Object.defineProperty 的用法

还能通过 Object.defineProperty() 方法,添加或修改对象的属性。更重要的是,除了目标对象 obj,属性名称 prop 外,方法能传入属性描述符 descriptor,以实现更复杂的性质。属性描述符是一个对象,有两种形式:一种是数据描述符,另一种是存取描述符。

理解js中Object的defineProperty()和defineProperties()

Object的defineProperty和defineProperties这两个方法在js中的重要性十分重要,主要功能就是用来定义或修改这些内部属性,与之相对应的getOwnPropertyDescriptor和getOwnPropertyDescriptors就是获取这行内部属性的描述。

js中的Object.defineProperty()和defineProperties()

Object.defineProperty()该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象,Object.defineProperties()该方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象

Object.keys方法

Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。

Js中Object.freeze()方法

Object.freeze() 方法可以冻结一个对象。一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。

Js Object.freeze和Object.seal

本文实例讲述了JS Object.preventExtensions(),Object.seal()与Object.freeze()用法。分享给大家供大家参考,Object.preventExtensions 只能阻止一个对象不能再添加新的自身属性,仍然可以为该对象的原型添加属性。

Object.assign实现浅拷贝的原理

什么是浅拷贝?浅拷贝就是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。浅拷贝Object.assign()是什么?主要将所有可枚举属性的值从一个或者多个数据源对象复制到目标对象,同时返回目标对象。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!