es6中的getter setter属性
getter setter是一种语法;
一个属性被设置了getter setter方法之后,就变成了【getter setter属性】;
【getter setter属性】声明时以函数的形式去,但在使用时仍把他当作属性去使用;
【getter setter属性】的特点是能动态获取最新结果,适合用作【计算属性】
以下是一个计算属性的例子,也就是把原有的普通属性拼凑在一起
let obj3 = {
姓: "高",
名: "圆圆",
old姓名() {
return this.姓 + this.名;
},
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.slice(1)
}
};
console.log(obj3.old姓名())
console.log(obj3.姓名) // 姓名是一个计算属性,把他当作一个普通的属性一样去访问就好
obj3.姓名 = '高媛媛' // 姓名是一个计算属性,把他当作一个普通的属性一样去设置就好
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
- es6之前要得到姓名 需要定义函数
old姓名并且调用obj3.old姓名()注意,因为这是传统的函数,所以肯定是要圆括号的 - 但现在你只需要
console.log(obj3.姓名) - 而很显然es6前如果我们还要修改姓名,那肯定还要写一个函数,但有了getter setter就方便很多
defineProperty用法解析 (vue全解 class3 - 3rd)
- 重要结论1:
defineProperty设置【getter setter属性】必须有一个数据源 - 重要结论2:
所使用的数据源也就是中间变量!
defineProperty的作用是
- 可以给对象添加普通属性和值
- 可以给对象添加【getter setter属性】
以下示例告诉你defineProperty设置普通属性
let obj={};
Object.defineProperty(obj,'name',{ // 第三个参数要对象的形式
value:'ryan' // 赋值的话要记得写value
})
用defineProperty的getter setter方法设置一个【getter setter属性】就需要用到
第三者| 数据源写在全局上 var xxx | 数据源写在自身对象上,_xxx | 数据源+中间变量value | |------|------------|------------| |xxx容易被篡改| obj身上需要多一个下划线属性 _age |不需要身上多一个下划线属性| || obj._age可被篡改 |但中间变量value会被篡改|
下面先引出前两种设置【getter setter属性】的方式,至于第三种方式就隐藏在后面例子中的proxy函数中
// 例1 数据源写在全局上
let obj={}
var _age;
Object.defineProperty(obj,'age',{
get(){
return _age;
},
set(value){
_age=value;
}
})
// 例2 数据源设置在对象上
let obj={_age:0}
Object.defineProperty(obj,'age',{
get(){
return this._age;
},
set(value){
value>20&& (this._age=value) // 只接受大于20的值
}
})
// 例3 数据源+中间变量
let obj={age:0}
let value=obj.age // 中间变量
Object.defineProperty(obj,'age',{
get(){
return value
},
set(newValue){
newValue>20 && (value=newValue)
}
})
问题:数据被篡改怎么办
- 例一例二中的数据源 一旦被修改,【getter setter属性】也会受牵连 比如:
obj._age=100会影响obj.age;window._age=100会影响obj.age - 例三中的中间变量 一旦被修改
value=999【getter setter属性】obj.age也会受牵连
使用代理 取消外部引用 防止数据篡改
- 把数据源作为函数参数传入 不会在外部留下任何引用,确保了不可被访问和修改,以防止
obj._age=100的操作
什么是代理?
下面的 vm 叫做代理,因为vm全权代理一切关于对象参数的操作,对象参数无法被操作(为了数据安全性的考虑),因此暴露出代理来操作下面这个例子基本上已经是 Vue数据响应式的绝大部分基本原理
let vm = proxy({data:{_age:0}}) // 1处 data获取到一个对象,代理数据源
// proxy函数的作用是新建一个对象 把数据源中的属性改为新对象中的【getter setter属性】
function proxy({data}){
const obj = {}
Object.defineProperty(obj,'age',{
get(){
return data._age
},
set(value){
value>20&& (data._age=value)
}
})
return obj
}
// 此时 vm 已全权拿到数据源的代理权,谁也无法篡改传递的参数!
console.log(vm.age) // 0
vm.age=30
console.log(vm.age) // 30
现在,基本需求已满足,给函数直接传 数据源 已经能确保数据完全安全了;
那么,我们再提一个过分的要求吧!
作为一个框架,你总得让人把外部数据也能传进去吧!
// **********************************
let myData = {_age:0} // 外面定义好 数据源myData
let vm = proxy({data:myData}) // 把外部数据传进来!
// **********************************
function proxy({data}){
const obj = {}
Object.defineProperty(obj,'age',{
get(){
return data._age
},
set(value){
value>20&& (data._age=value)
}
})
return obj
}
console.log(vm.age) // 0
myData._age=999
console.log(vm.age) // 999
vm.age=1000
console.log(myData._age) // 1000
现在,一切都很好!
我们可以发现,外部数据和代理之间完全是互通的!
但是依旧有一点小缺点需要弥补!
你不觉得外部数据源每次都要设置下划线属性这样很麻烦吗?
Vue数据响应式的源码的完整思路!
终极需求如下:
- 要允许使用外部数据源
- 数据源与代理之间的数据要保持互通
- 数据源和代理两者设置的属性名要完全一样的
终极需求的解决方案如下:
使用函数的内部属性value作为中间变量 外部数据源<===> vm代理
let myData={age:0} // 数据源myData
// 由于用到中间变量value,因此不用下划线属性 _age
let vm = proxy({data:myData}) // {data:{age:0}}
function proxy({data}){
// ************start********
let value=data.age
// 这一份内部属性 value很关键,这是 数据源myData.age <===> data.age 之间的桥梁
// 利用中间变量value 把myData.age这个普通属性重写为为【getter setter属性】 详见上述例三
Object.defineProperty(data,'age',{ // data是一个对象{age:0}
get(){
return value
},
set(newValue){
newValue>20 && (value=newValue)
}
})
// ************end********
const obj={}
Object.defineProperty(obj,'age',{
get(){
return value
},
set(newValue){
newValue>20 && (value=newValue)
}
})
return obj
}
console.log(vm.age,myData.age)
myData.age=999;
console.log(vm.age,myData.age)
vm.age=1000;
console.log(vm.age,myData.age)
总结如下
上述的讨论结果,
1.【setter getter属性】的作用相当于允许属性获取动态最新的结果
2. 我们知道了Vue作为一个框架之所以在new Vue中用户书写data的name属性
能够在调用this.name时让两边的名字保持一致是因为用到了中间变量nameValue;
同时中间变量value的存在还让数据源与代理之间保持了数据的互通