前言
大家好!我是江南功,下面讲讲开发中可能会遇到的问题,话不多说,接招?
问题描述
const smallblack={
name: '张三',
age: 18,
sex: '男'
}
这里有个对象smallblack
,是一些个人信息的对象.但是发现要用这个对象的时候缺少了一个属性brithday
,那么如何给其添加属性呢,小林同学二话不说写出如下代码:
// 构造函数
function PersonInfo(val){
this.data = val
this.brithday = '2022-10-11'
}
// 或者是ES6的写法
class PersonInfo{
constructor(val){
this.data = val;
this.brithday = '2022-10-11'
}
}
我们来看看ES6的写法,其中的一行代码this.data = val
,会使得我们有个危机感,什么危机感呢?就是这行代码不够严谨不够稳,容易出现问题,会出什么问题呢?
const p = new PersonInfo(smallblack)
console.log(p)
// PersonInfo {
// data: { name: '张三', age: 18, sex: '男' },
// brithday: '2022-10-11'
// }
// 但是进行如下操作
p.data = 'null'
console.log(p)
// PersonInfo { data: 'null', brithday: '2022-10-11' }
p.data = 'null'
这行代码将一个对象改为了字符串’null’,你一不小心改动了,那么整个数据就用不了了,因为被你重新赋值了!这就会产生一些不可预料的问题.那么我们就希望这个p.data不能修改,如何做到呢?因此我们就引出了属性描述符
,本文将围绕它进行展开!
什么是属性描述符
const obj = {
a: 1,
b: 2
}
上述obj对象的a属性我们可以整么描述呢?
- 值为1
- 可以重新赋值
obj.a =10
console.log(obj.a) // 10
- 可以被遍历
for (const key in obj) {
console.log(key)
}
// a
// b
以上三个特点描述就是属性描述符,当然我们可以通过js中提供的函数Object.getOwnPropertyDescriptor
来获取属性描述:
const aDesc = Object.getOwnPropertyDescriptor(obj, 'a')
console.log(aDesc)
// { value: 1, writable: true, enumerable: true, configurable: true }
上述代码打印出来的就是属性描述符的具体信息.实际上每个属性都有一套对象来描述自身,而且这个对象是可以修改的.那么如何更改呢?
js中提供了一个函数Object.defineProperty
,是不是大家对这个函数很熟悉呢?没错在vue2中的响应式原理就用到了这个函数,大家如果对vue2的响应式原理感兴趣可自行百度,这里不再展开.
// 该函数是用来设置属性描述符
Object.defineProperty(obj, 'a', {
value: 10, // 设置值
writable: false, // 不可重写
enumerable: false // 不可遍历
})
在上述代码中value
是设置obj属性a的值,writable
则表示属性a是否可以重新被赋值,而enumerable
则表示属性a是否可以被遍历.当writable为false不可重写时,enumerable为false,不可遍历时,如下代码所示:
// 当属性a 不可重写时
obj.a = 'abc'
console.log(obj.a) // 10
// 当属性a 不可遍历时
for (const key in obj) {
console.log(key)
} // b
const keys = Object.keys(obj)
console.log(keys) // ['b']
console.log(obj) // { b: 2 }
configurable
表示属性描述符本身是否可以被修改,如下代码:
Object.defineProperty(obj, 'a', {
value: 10, // 设置值
writable: false, // 不可重写
enumerable: false, // 不可遍历
configurable: false // 不可修改属性描述符本身
})
// 我在重新定义a的属性描述符
Object.defineProperty(obj, 'a', {
writable: true, // 可重写
enumerable: false, // 不可遍历
configurable: false // 不可修改属性描述符本身
})
// 报错----------------------
运行上述代码会报错,因为configurable
已经设置为false了,此时不可再重新定义a的属性描述符.
好的,讲到这里我们再回到前面的问题.稍微改下代码:
const smallblack={
name: '张三',
age: 18,
sex: '男'
}
class PersonInfo {
constructor(val) {
Object.defineProperty(this, 'data', {
value: val,
writable: false,
configurable: false
})
}
}
const p = new PersonInfo(smallblack)
p.data = 'null' // 无效
console.log(p.data)
// { name: '张三', age: 18, sex: '男' }
上述代码中属性data无法被重新赋值,这样就解决了问题.但是完全解决了吗?没有举个例子:当我代码写了一大串后,其中有修改这个data属性,由于时间过的太久了,早就忘了data是无法修改的,这就使得你编写的代码会出现一些问题;再者说你的同事拿到这一大段代码,同事想当然的认为data属性是可以修改的,就写了类似于p.data = 'null'
的代码,发现没有生效,瞬间懵逼了!找了半天都没有解决,想了半天都没弄明白,气得口吐芬芳……最后还是原先编写这段代码的同事说明这个data属性不能被修改,找了半天问题的同事无奈地说,大哥你能写个报错程序来提示其他人注意这个data属性不能修改吗?
为了使这段代码程序更加可读可维护,我们应该给其添加报错程序.那么我们就要介绍Object.defineProperty的getter函数和setter函数了.
getter函数:当属性被读取时触发;setter函数:当属性被设置时触发.
const obj = {}
Object.defineProperty(obj, 'a', {
// 读取器
get: function () {
console.log('handle getter')
return 123
},
// 设置器
set: function () {
console.log('handle setter')
}
})
console.log(obj.a)
// handle getter
// 123
obj.a = 1 + 1 // 触发set(1+1)
console.log(obj.a) // 相当于console.log(get()) 123
obj.a = obj.a + 1
console.log(obj.a) // 123
最终代码
好的,基本了解getter和setter后,我们着手解决下如何写个报错程序来提示其他人注意这个data属性不能修改:
//
const smallblack = {
name: '张三',
age: 18,
sex: '男'
}
class PersonInfo {
constructor(val) {
Object.defineProperty(this, 'data', {
configurable: false,
get: function () {
return val
},
set: function () {
// 错误处理
throw new Error('the data is not redined!')
}
})
}
}
const p = new PersonInfo(smallblack)
p.data = 'null' // 报错
console.log(p.data)
执行上述data属性重新赋值代码报错,这样代码虽然写多了,但整体上代码的可读性增强了,也便于后人维护,减少不必要的交流成本.
总结
好了,问题解决了.总结一下吧.首先上述解决问题的思维能力在我们写业务代码是比较难形成的,只有写一些组件,第三方库等等才能培养出这种能力!少年易学老难成,一寸光阴不可轻.诸位共勉!