深入了解|深入了解 Object.defineProperty
属性的操作
在 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
中绘制页面信息:
Document - 锐客网
简单实现
最简单的实现方式就是进入页面的时候从缓存中获取数据,能够获取到就给输入框和显示区域赋值;同时监听输入框的输入事件,向缓存和显示区域中写入对应的信息。
function init() {
const eleInfo = document.getElementById('idInfo');
const eleShowInfo = document.getElementById('idShowInfo');
const _storageInfo = JSON.parse(localStorage.getItem('storageInfo') || '{}');
if (_storageInfo.info) {
eleInfo.value = https://www.it610.com/article/_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();
【深入了解|深入了解 Object.defineProperty】其次书写
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 = https://www.it610.com/article/resInfo.info;
viewDom.innerHTML = resInfo.info;
}
参考资料
- Object.defineProperty()
- Object.defineProperties()
- 从 0 到 1 学习「Object.defineProperty」
- 『Object.defineProperty』考题训练与考点应用
推荐阅读
- 深入浅出(数据流水线管理(下))
- 智能家居|深入了解开源智能家居平台,解决品牌割裂的终极利器()
- 深入了解Spark内存管理模型
- 10分钟了解Vue3递归组件的用法
- Laravel文件上传
- Python基础知识自检,离深入掌握 Python 还有多远
- 带你了解Go语言基础之切片
- Kotlin的第一个项目概念
- JavaNIO的深入研究4内存映射文件I/O,大文件读写操作,Java nio之MappedByteBuffer,高效文件/内存映射
- 一文带你了解数据分析的6大基本步骤,小白必看!