1.给 JavaScript 对象添加属性
在 JavaScript
中给对象添加属性,很平常的一件事情.
let obj = {}
obj.name = '李四'
obj.sayHi = function () {
console.log(`${this.name} SayHi~`)
}
obj.hobbies = ['看书','打游戏']
这没什么大不了的,JS
本身就是一个动态语言,可以非常自由的给对象添加一些属性.
且属性可以是任意的 JS
数据对象.
2.介绍 Object.defineProperty
因为我们之前JavaScript
的动态特性,可以很简单的给 JS.obj
添加属性.
但是为毛又要多出来一个 Object.defineProperty
呢?
为什么总喜欢把简单的事情复杂化呢????
我们先来看看 Object.defineProperty
方法在 MDN
上的定义.
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty(obj,prop,descriptor)参数说明
-
obj
需要定义属性的对象 -
prop
定义的属性名称 -
descriptor
被定义的属性描述(此参数是一个对象)-
value
:设置到属性的值 -
writable
: 设置属性是否可写,boolean
类型,默认值false
-
configurable
: 设置属性是否可以配置,boolean
类型,默认值false
.主要有两个功能- 设置属性是否可以被
delete
删除 - 设置属性是否可以被再次配置.
- 设置属性是否可以被
-
enumerable
: 设置属性是否可以被for in
&Object.keys()
枚举出来.默认值是false
-
-
set
: 给属性赋值的setter
-
get
: 获取属性值的getter
3. 使用 Object.defineProperty 来给对象设置属性
让我们暂时忘记JavaScript
方便的令人发指的属性增加语法,来使用蹩脚
的 Object.defineProperty
给对象赋值
let obj = {}
// String
Object.defineProperty(obj, 'name', {
value: '李四'
})
// Number
Object.defineProperty(obj, 'age', {
value: 22
})
// Object
Object.defineProperty(obj, 'carInfo', {
value: {
carBrand: '宝马',
carNumber: '京A12345',
toString() {
return `汽车品牌:${this.carBrand}
车牌号:${this.carNumber}
`
}
}
})
// Array
Object.defineProperty(obj, 'hobbies', {
value: ['看书', '玩游戏']
})
// Function
Object.defineProperty(obj, 'sayHi', {
value: function () {
console.log(`姓名:${this.name}
年龄:${this.age}
carInfo:${this.carInfo.toString()}
爱好:${this.hobbies.join(',')}
`)
}
})
obj.sayHi()
结果
姓名:李四
年龄:22
carInfo:汽车品牌:宝马
车牌号:京A12345
爱好:看书,玩游戏
发现除了语法麻烦点,基本使用和简单的对象赋值没有特别大区别.
4.Object.defineProperty-descriptor参数详解
descriptor - value
设置到定义属性的值,在不配置
wriable
为true
的情况下,是只读的.
let obj2 = {}
Object.defineProperty(obj2, 'value', {
value: 'this is value',
})
obj2.value = 'this is an other value' // 由于 writable 默认值是false,所以这里的修改无效,输出仍然是 this is value
console.log(obj2.value)
结果:
this is value
descriptor - writable
let obj2 = {}
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true // 配置了writable 就可以写了.
})
obj2.value = 'this is an other value'
console.log(obj2.value)
结果
this is an other value
descriptor-enumerable
默认值是
false
,不能被for in
&Object.keys()
枚举出来.
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true,
})
console.log("keys:" + Object.keys(obj2))
结果
keys:
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true,
enumerable: true // 设置此属性可以被枚举
})
结果:
keys:value
descriptor-configurable
默认值为
false
. 标识此属性不能被配置.
主要体现在:
- 此属性不能被
delete
符号删除 - 不能再次修改特性
false|true
Object.defineProperty(obj2, 'canDelete', {
value: '被删除了吗?',
// configurable: false
})
delete obj2.canDelete
console.log(obj2.canDelete) // 没有被删除
结果
被删除了吗?
Object.defineProperty(obj2, 'canDelete', {
value: '被删除了吗?',
configurable: true
})
delete obj2.canDelete
console.log(obj2.canDelete) // undefined 被删除了.
结果
undefined
Object.defineProperty(obj2, 'canDelete', {
value: '被删除了吗?',
configurable: false,
enumerable: false // 定义时,配置不能被枚举
})
结果
[ 'value' ] // canDelete 没有被枚举出来.
再一次定义
Object.defineProperty(obj2, 'canDelete', {
value: '被删除了吗?',
configurable: false, // 第一次定义为 false
enumerable: false
})
// 第二次定义 enumerable : true
Object.defineProperty(obj2, 'canDelete', {
value: '被删除了吗?',
configurable: true, // 第二次定义为true
enumerable: true
})
都是 canDelete 属性
运行直接报错:
Cannot redefine property: canDelete
descriptor-set&get
有点类似于 Java/.Net
里的属性访问器.
注意:在使用 set / get 的时候,就不能搭配 value & writable 两个属性了.否则直接报错.
let obj3 = {}
let defaultValue = undefined
Object.defineProperty(obj3, 'name', {
value: '在有set/get的时候能设置吗?',
writable: true,
set: function (newVal) {
defaultValue = newVal
},
get: function () {
return defaultValue
}
})
报错信息:
Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.
正确代码
let obj3 = {}
let defaultValue = undefined
Object.defineProperty(obj3, 'name', {
set: function (newVal) {
console.log('get被触发')
defaultValue = newVal
},
get: function () {
console.log('set被触发')
return defaultValue
}
})
obj3.name = '李四-obj3'
console.log(obj3.name)
结果
get被触发
set被触发
李四-obj3
对于 let defaultValue = undefined
这句代码有点疑问.
可能从 Java/.Net
转过来的程序员,会觉得 set
& get
里面应该这么写.
Object.defineProperty(obj3, 'property', {
set: function (newVal) {
this.property = newVal
},
get: function () {
return this.property
}
})
但实际运行起来,发现get&set
出现了死递归,出现了函数栈溢出的问题.
obj3.property = '可以设置值吗?' // set 死递归
Maximum call stack size exceeded
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:91:17)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:92:19)
console.log(obj3.property) // get 死递归
Maximum call stack size exceeded
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:94:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代码/前端学习/ES6/Object/Obj.js:95:17)
道理也比较简单:
当我们调用
this.property
时,其实又在执行get/set
.于是就造成了死递归.这也是为什么要在外面定义一个let defaultValue = undefined
的原因.
只能说:现在版本的 js
对属性的 get/set
支持的还不是很友好.
补充一点
Object.defineProperty
的 descriptor
参数可以定义属性的 get
/set
.属于ES5的功能.
其实,在ES6中提供的 Proxy
对象,也能提供这样一个功能.
let obj = {
id: 1,
level: 10,
name: '李四'
}
let objProxy = new Proxy(obj, {
// obj 当前被代理的对象
// prop 当前正在执行get的属性.
get(obj, prop) {},
// obj,被代理的对象
// prop,当前正在执行set的属性
// value,set的值.
set(obj, prop, value) {}
})
// 使用的时候记得使用代理返回的objProxy对象,而不是obj对象.
objProxy.name = '李四'
let name = objProxy.name