引子
先来一个例子
//main.js
import Vue from 'vue'
Vue.config.productionTip = false;
const myData = {
n: 0
}
console.log(myData)
const vm = new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(()=>{
myData.n += 10
console.log(myData)
},5000)
按照常理来说,第一次打印应该得到{n:0},第二次应该是{n:10}才对,
经过验证,第一次打印如下:
把 myData.n += 10传给new Vue之后,再次点开{n:0}
这是为啥?
带着这个疑问,开启本文
getter和setter
再来个例子
let obj0 = {
姓: "周",
名: "杰伦",
age: 18
};
需求一:得到姓名
很简单
let obj1 = {
姓: "周",
名: "杰伦",
姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求一:" + obj1.姓名());
需求二:我就是不想要obj1.姓名()怎么办?
也不是不行
let obj2 = {
姓: "周",
名: "杰伦",
get 姓名() {
return this.姓 + this.名;
},
age: 18
};
console.log("需求二:" + obj2.姓名);
OK,至此我们就已经了解了getter是怎么用的,不加括号的函数,仅此而已。
需求三:姓名可以被改写
let obj3 = {
姓: "周",
名: "杰伦",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.slice(1)
},
age: 18
};
obj3.姓名 = '周星驰'
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
setter 就是这样用的。用 = xxx 触发 set 函数,即用计算属性就能修改原始属性
我们再console.log(obj3)
会发现这个姓名:(...)跟之前的n:(...)很相近
但我们并没有一个属性叫做姓名,所以浏览器在显示姓名的时候是以姓名:(...)的方式显示的
就是说,你确实可以对姓名进行读和写,但并不存在一个叫做姓名的属性,读和写是通过下面的get 姓名和set 姓名来完成的
这说明我们之前得到的n:(...)也是同理,有一个get n和set n来模拟对n的读写操作
但为啥要把n变成get n和set n呢?
Object.defineProperty
如果我们想在已经声明完的对象添加一个新get/set,就需要Object.defineProperty
如,在上述的obj3上添加
var _xxx = 0
Object.defineProperty(obj3,'xxx',{
get(){
return _xxx
},
set(value){
_xxx = value
}
})
我们接着举例子
let data0 = {
n: 0
}
需求一:用 Object.defineProperty定义 n
let data1 = {}
Object.defineProperty(data1, 'n', {
value: 0
})
console.log(`需求一:${data1.n}`)//打印出:需求一:0
需求二:n 不能小于 0
let data2 = {}
data2._n = 0 // _n 用来偷偷存储 n 的值
Object.defineProperty(data2, 'n', {
get(){
return this._n
},
set(value){
if(value < 0) return
this._n = value
}
})
console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 设置为 -1 失败`)
data2.n = 1
console.log(`需求二:${data2.n} 设置为 1 成功`)
通过这个例子,可以知道set里面是可以做判断的
这时,来了一个杠精:“那如果别人直接使用 data2._n 呢?”
那确实没办法,所以
需求三:使用代理
let data3 = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问
function proxy({data}){
const obj = {}
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // obj 就是代理
}
// data3 就是 obj
这么一来proxy({ data:{n:0} })就没办法被修改了 ,
且对obj的n做什么,同时,就会对data的n做什么
但杠精仍不服,他又给出了以下代码
let myData = {n:0}
let data4 = proxy({ data:myData }) // 括号里是匿名对象,无法访问
// data3 就是 obj
console.log(`杠精:${data4.n}`)
myData.n = -1
console.log(`杠精:${data4.n},设置为 -1 成功!`)
需求四:就算用户擅自修改 myData,也要拦截他
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) // 括号里是匿名对象,无法访问
function proxy2({data}){
let value = data.n
delete data.n//把原始数据的n删掉,可以不写,再下面声明新的n时,会把旧的n覆盖
Object.defineProperty(data, 'n', {
get(){
return value
},
set(newValue){
if(newValue<0)return
value = newValue
}
})
// 加了上面几句,这几句话会监听 data
const obj = {}
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // obj 就是代理
}
这样一来,就能100%地防止数据在不知情的时候被修改
接下来做一个类比:
let data5 = proxy2({ data:myData5 })const vm = new Vue({data:{n:0}})
会发现很接近,其实上面例子的原理,就是Vue内部用到的原理(只是原理不是代码)
小结
Object.defineProperty
- 可以给对象添加属性value
- 可以给对象添加getter/setter
- getter/setter用于对属性的读写进行监控
代理
- 对myData对象的属性读写,全权由另一个对象vm负责
- 那么vm就是myData的代理
vm=new Vue({data:myData})
- 会让vm成为myData的代理
- 会对myData的所有属性进行监控
- 为什么要监控?为了防止myData的属性变了,vm却不知道
- vm知道了能干嘛?知道了属性变了就可以调用
render(data),UI就能够自动刷新~
总而言之就是,你对data做的任何修改,我Vue就必须知道,就是这么霸道
所以,在引子中,只要把 myData传给new Vue,new Vue就马上会对myData进行一个篡改,之前的n就不见了,取而代之的是一个get n和set n
数据响应式
什么是响应式?
-
你的女朋友给你一耳光,你会喊疼,那你就是响应式的
-
总之,一个物体能对外界的刺激做出反应,它就是响应式的
Vue的data是响应式
const vm = new Vue({data:{n:0}})- 我修改vm.n,那么UI中的n就会响应我
- Vue通过
Object.defineProperty来实现数据响应式