Operation of properties

In JavaScript, adding a property to an object is very simple, just call the property and assign it a value.

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

Properties added in this way can be manipulated at will:

  • Can be modified
  • enumerable
  • can be deleted

Can be modified:

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

Enumerable:

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

Can be deleted:

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

If you want to achieve the above function through Object.defineProperty , you can use the following code:

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

function signature

Before going deep into Object.defineProperty , let's have an understanding of this method signature:

 Object.defineProperty(obj, prop, descriptor);

As you can see from the function signature, defineProperty is a static method on Object that can pass three parameters:

  • obj The object whose properties are to be defined
  • prop Name of the attribute to be defined or modified
  • descriptor Descriptor to define or modify the attribute

The return value is the object passed to the function, which is the first argument obj .

The descriptor can have the following optional values:

  • configurable
  • enumerable
  • value
  • writable
  • get
  • set

Descriptor

Define a property for the object by Object.defineProperty .

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

As can be seen from the output, an attribute ---ae28d79bdcef2d8669227efc91155674 obj name , but its value is undefined .

value

If you want to assign a value to an attribute, you can use the value attribute in the descriptor.

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

writable

In general, to modify the property value in an object, you can use the form of obj.name = 'Jim' .

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

It can be seen from the output that the modification was not successful. If you want to modify the attribute value, you can set writable in the descriptor to true .

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

enumerable

To enumerate the properties of an object, you can use for...in .

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

Strangely enough, executing the above code does not output any information.

If you want to enumerate the properties of the object normally, you can set the enumerable value in the descriptor to true .

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

configurable

When this property is not needed, it can be removed by delete .

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

It can be seen from the output results that the expected effect has not been achieved. If you want to delete the attribute from the object normally, you can set configurable in the descriptor to true .

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

get

If you need to get the value of the object, you can use get in the descriptor.

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

set

If you need to set the value of the object, you can use set in the descriptor, which needs to pass a parameter, which is the modified value.

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

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

Precautions

In the operator object, if there is any one or more of ---20b6fc783d85af198b8150e7d2d165ae value or writable 51639160c99a6a50e02b4d8663bde2df---, it cannot exist get or set .

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

The error message is as follows:

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

In order to facilitate later reference, summarize the mutual exclusion situation:

  • value and get mutually exclusive
  • value and set mutually exclusive
  • value and set + get mutually exclusive
  • writable and get mutually exclusive
  • writable and set mutually exclusive
  • writable and set + get mutually exclusive

scenes to be used

Object.defineProperty() method will define a new property directly on an object, or modify an existing property of an object, and return the object. This method allows precise addition or modification of object properties.

This method is a relatively low-level method in JavaScript, and is mainly used to add or modify object properties on objects.

Simple application

Use of base modifiers

Suppose there is now a requirement to achieve the following effects:

 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() Not only can define attributes, but also modify attributes. At this time, you can use the method of modifying the properties to achieve the above requirements.

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

use of get

Use Object.defineProperty in get to output the information in console.log normally.

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

As can be seen from the title, num cannot be equal to 1 and 2 , and also equal to 3 . If you want to achieve this effect, you must automatically increase the value of num at the same time.

To achieve a variable value and auto-increment, you need to use Object.defineProperty in get . num is used directly. In the browser, only the attributes mounted on the window object can be used directly.

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

Suppose there is a requirement now, to achieve the following effects:

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

This requirement is actually to mount a _ attribute on the window object. Each time _ is called, the ASCII code will be incremented automatically.

 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;
  },
});

If you want to print out a combination of 26 letters, you can traverse it.

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

use of set

If you assign a string to 'Object' , print the string output {type: 'Object', length: 6} ; if you assign a string to 'Object' , print this string output {type: 'Array', length: 5} ; If you assign the string to another value, the program will report an error TypeError: This type is invalid. .

Analyzing this topic, when printing, it is actually the process of taking values. You need to use the get operator, type is the value of the current string, length is the current value The length of the string.

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

When assigning a value to a string, when the value of the string is 'Object' or 'Array' , the value is assigned normally, and the other cases throw an error directly. This operation needs to be implemented in the operator 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.');
+     }
    },
  });

Verify the execution of the code:

 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.
*/

complex application

Requirement: There is an input box on the page and a display area below it. When the content of the input box changes, the content in the display area changes synchronously. When the page is refreshed, the information in the page remains the same as before the refresh.

First, draw the page information in 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>

Simple implementation

The easiest way to implement it is to get data from the cache when entering the page, and assign values to the input box and display area when you can get it; at the same time, monitor the input event of the input box, and write the corresponding information into the cache and display area.

 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 implementation

The above implementation method is relatively straightforward and simple, but the encapsulation of the code is relatively poor, and the data coupling is relatively high. If you use Object.defineProperty you can organize the code better.

First write the code of the entry file 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();

Next, write the code in 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;
}

References


沫俱宏
763 声望33 粉丝

自己的肯定最重要,做任何决定,一定要从内心出发