属性的操作

在 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);

从函数签名中能够看出,definePropertyObject 上的一个静态方法,能够传递三个参数:

  • 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'}  */

注意事项

在操作符对象中,如果存在了 valuewritable 中的任意一个或多个,就不能存在 getset 了。

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

为了不便前期查阅,总结一下互斥的状况:

  • valueget 互斥
  • valueset 互斥
  • valueset + get 互斥
  • writableget 互斥
  • writableset 互斥
  • writableset + 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 中的 getconsole.log 中的信息失常输入。

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

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

要实现一个变量取值并自增,就须要应用 Object.defineProperty 中的 getnum 是间接应用的,在浏览器中,只有挂载到 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;}

参考资料

  • Object.defineProperty()
  • Object.defineProperties()
  • 从 0 到 1 学习「Object.defineProperty」
  • 『Object.defineProperty』考题训练与考点利用