属性的操作
在 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;}
参考资料
- Object.defineProperty()
- Object.defineProperties()
- 从 0 到 1 学习「Object.defineProperty」
- 『Object.defineProperty』考题训练与考点利用