defineProperty
这个方法大家多多少少都有所耳闻。vue更是让他“声名远播”,今天就来好好总结下这个vue2版本里,不可或缺的defineProperty
方法。
1.定义
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
备注:应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。
2.语法
Object.defineProperty(obj, prop, descriptor)
2.1 参数
obj
要定义属性的对象。prop
要定义或修改的属性的名称或Symbol
。
在ES6中,由于 Symbol类型的特殊性,用
Symbol
类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty
是定义key为Symbol
的属性的方法之一。
descriptor
要定义或修改的属性描述符。
而最需要注意的就是这属性描述符了。对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter
函数和 setter
函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。这也就导致一个描述符不能同时拥有 value
或 writable
和 get
或 set
键,否则会产生一个异常。
2.1.1 属性描述符
2.1.1.1数据描述符
Writable
能否修改属性的值,如果直接使用字面量定义对象,默认值为true
Value
该属性对应的值,默认为undefined
2.1.1.2存取描述符
Get
一个给属性提供 getter 的方法(访问对象属性时调用的函数,返回值就是当前属性的值),如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined
Set
一个给属性提供 setter 的方法(给对象属性设置值时调用的函数),如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined
2.1.1.3公共描述符
还有两个描述符是存取与数据描述符都共有的。
Configurable
当且仅当该属性的 configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。
Enumerable
表示该属性是否可枚举,即是否通过for-in
循环或Object.keys()
返回属性,如果直接使用字面量定义对象,默认值为true
3. 简单使用
writable控制是否可修改
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu',
writable: false
})
console.log(obj.name); // fufu
obj.name = 'dandan'
console.log(obj.name); // fufu
--------------------------------------------------------------------
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu',
writable: true
})
console.log(obj.name); // fufu
obj.name = 'dandan'
console.log(obj.name); // dandan
configurable控制是否允许被删除
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu',
configurable: false,
})
delete obj.name
console.log(obj); // {name: 'fufu'}
-------------------------------------------------------------------
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu',
configurable: true,
})
delete obj.name
console.log(obj); // {}
enumerable控制属性可否被枚举,例如被keys或for…in…遍历。
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu',
enumerable: false,
})
Object.defineProperty(obj, 'age', {
value: 20,
enumerable: true,
})
console.log(Object.keys(obj)); //["age"]
注意一下代码的区别:
let obj = {}
obj.name = 'fufu'
// 等价于
Object.defineProperty(obj, 'name', {
value: 'fufu',
enumerable: true,
configurable: true,
writable: true
})
-------------------------------------------------------------
let obj = {}
Object.defineProperty(obj, 'name', {
value: 'fufu'
})
// 等价于
Object.defineProperty(obj, 'name', {
value: 'fufu',
enumerable: false,
configurable: false,
writable: false
})
get和set可以算是最关键的描述符了,vue3.0之前的observe就是靠他实现
let obj = {name: 'fufu'}
let val = undefined
Object.defineProperty(obj, 'age', {
get() {
console.log('获取')
return val
},
set(newValue) {
console.log('设置')
val = newValue + '岁'
}
})
obj.age = 18 //设置
// 获取
console.log(obj.age); //18岁
4.实际作用
4.1创建一个常量属性。
function notEdit(obj, attr, value) {
return Object.defineProperty(obj, attr, {
value,
configurable: false,
writable: false
})
}
let obj = notEdit({}, 'name', 'fufu')
delete obj.name
console.log(obj); //{name: "fufu"}
obj.name = 'dandan'
console.log(obj); //{name: "fufu"}
obj.age = 20
console.log(obj); //{age: 20, name: "fufu"}
4.2禁止拓展属性
想要让对象禁止添加属性,可以使用preventExtensions()方法。
let obj = {}
Object.preventExtensions(obj)
obj.name = 'fufu'
console.log(obj) //{}
4.3 对象封密
Object.seal()会创建一个密封的对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要原来是可写的就可以改变。该对象这个方法实际上会在一个现有对象上调用object.preventExtensions(…)并把所有现有属性标记为configurable:false。
由此可以自己封装出seal方法:
function seal(obj) {
for(let key in obj) {
let val = obj[key]
Object.defineProperty(obj, key, {
value: val,
configurable: false,
})
}
return Object.preventExtensions(obj)
}
let obj = seal({name: 'fufu', age: 20})
obj.name = 'dandan'
obj.age = '100'
obj.love = 'sing'
console.log(obj); //{name: "dandan", age: "100"}
4.4 冻结对象
Object.freeze()方法可以冻结一个对象。该对象不能添加新的属性,不能修改原有属性的值,不能进行属性删除。(只会冻结第一层,若属性为对象或数组,则冻结不了内部属性值的修改)
根据上面的方面,可以自己手写一个冻结对象方法:
function freeze(obj) {
for(let key in obj) {
let val = obj[key]
Object.defineProperty(obj, key, {
value: val,
configurable: false,
writable: false
})
}
return Object.preventExtensions(obj)
}
let obj = freeze({name: 'fufu', height: 170, arr: [1]})
obj.name = 'dandan'
obj.age = 20
obj.arr.push(2)
delete obj.height
// 若属性值为对象或数组这种非基本数据类型,那么属性值是可以被修改的。
console.log(obj); //{name: 'fufu', height: 170, arr: [1, 2]}