數(shù)據(jù)響應(yīng)式
主要原理:深入響應(yīng)式原理
內(nèi)容:深入理解options.data
Vue對(duì)data做了什么岖妄?
const myData = {
n: 0
};
console.log(myData);
new Vue({
data: myData,
template: `
<div>{{n}}</div>
`
}).$mount("#app");
setTimeout(() => {
myData.n += 10;
console.log(myData);
}, 3000);
3s后n變成10防泵,這次沒有在vm里面加氧腰,而是在外面买猖,說明在外面也可以變更myData泻肯。
一般我們是在vm內(nèi)變更myData的
new Vue({
template:`
<div>
{{n}}
<button @click="add"> +1 </button>
</div>
`,
methods:{
add(){
//this.n +=10
myData.n +=10
}
}
}).$mount("#app")
平常我們都用this.n
海洼,今天試試myData.n
。
分別打印出myData剛聲明和3s后的結(jié)果休里,如果第1次是n:0,那第2次就應(yīng)該是n:10蛆挫,看下結(jié)果:
[圖片上傳失敗...(image-9e0436-1648768426411)]
第2次并不是n:10,那這個(gè)n:(...)
是什么呢妙黍?
我們需要先學(xué)習(xí)ES6的getter悴侵、setter
示例:要想得到姓名就要調(diào)用函數(shù)obj1.姓名()
所以括號(hào)不能省,但是ES6的get語法可以實(shí)現(xiàn)obj1.姓名
刪掉括號(hào)拭嫁。
// 需求一可免,姓名不要括號(hào)也能得出值
let obj1 = {
姓: "高",
名: "圓圓",
get 姓名() { //以函數(shù)的形式定義的屬性
return this.姓 + this.名;
}
};
console.log("需求一:" + obj1.姓名);
// 總結(jié):getter就是不加括號(hào)的函數(shù)而已。
// 需求二:姓名可以被寫
let obj2 = {
姓: "高",
名: "圓圓",
get 姓名() {
return this.姓 + this.名;
},
set 姓名(xxx){
this.姓 = xxx[0]
this.名 = xxx.substring(1)
}
};
obj2.姓名 = '高媛媛'
console.log(`需求二:姓 ${obj2.姓}做粤,名 ${obj2.名}`)
// 總結(jié):setter用= xxx觸發(fā)set函數(shù)
把obj2打出來
[圖片上傳失敗...(image-945ae2-1648768426411)]
瀏覽器說你確實(shí)可以對(duì)姓名進(jìn)行讀和寫浇借,但是并不存在一個(gè)叫姓名的屬性。但是你可以通過get和set設(shè)置它怕品。
推斷1: 由此推斷之前得到的n也是一個(gè)getter妇垢、setter
get 姓名、set 姓名
說明姓名:(...)
不是一個(gè)真實(shí)的屬性
推斷2:n:(...)
并不存在屬性n,而是有個(gè)get n堵泽、set n
,它們來模擬對(duì)n的讀寫操作修己。
那為什么要把n變成get n、set n
呢迎罗?
需要再學(xué)下Object.defineProperty()
之前在使用getter睬愤、setter
時(shí),是在定義這個(gè)對(duì)象時(shí)直接使用的纹安。在定義完一個(gè)對(duì)象之后尤辱,要想再添加新的get set時(shí)只能用Object.defineProperty()
Object.defineProperty(obj2,'xxx',{
//給obj2添加虛擬屬性xxx,注意xxx是不存在的厢岂。
var _xxx=0 //_xxx是用來存放set值的
get(){
return _xxx
},
set(value){
_xxx=value
}
})
let data0 = { n: 0 }
// 需求一:用Object.defineProperty定義n
let data1 = {}
Object.defineProperty(data1, 'n', { //給data1添加虛擬屬性n,n=0
value: 0
})
console.log(`需求一:${data1.n}`) //0
// 這語法把事情搞復(fù)雜了光督?非也,繼續(xù)看塔粒。
// 需求二:n不能小于0
let data2 = {}
data2._n = 0 //用_n存儲(chǔ)n的值
Object.defineProperty(data2, 'n', {
get(){
return this._n
},
set(value){
//set可以添加判斷:小于0直接return,否則將n值置為最新value
if(value < 0) return
this._n = value
}
})
console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 設(shè)置為 -1 失敗`)
data2.n = 1
console.log(`需求二:${data2.n} 設(shè)置為 1 成功`)
// 那如果對(duì)方直接用data2._n呢结借?_n可以直接設(shè)置為-1呀
// 需求三:使用代理obj
//括號(hào)里是匿名對(duì)象,直接改為沒有名字的對(duì)象{n:0},data可有可無
let data3 = proxy({ data:{n:0} })
function proxy({data}){ //接收data屬性
const obj = {}
//理論應(yīng)該遍歷data的所有key卒茬,這里做了簡化
Object.defineProperty(obj, 'n', {
get(){
return data.n //當(dāng)你取obj.n,就返回data.n
},
set(value){
if(value<0)return
data.n = value //當(dāng)你設(shè)置obj.n,就設(shè)置data.n
}
})
return obj
}
// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n}船老,設(shè)置為 -1 失敗`)
data3.n = 1
console.log(`需求三:${data3.n}咖熟,設(shè)置為 1 成功`)
//杠精說,你看下面代碼
// 需求四:繞過代理,通過引用
let myData = {n:0}
let data4 = proxy({ data:myData })//括號(hào)里是匿名對(duì)象柳畔,無法訪問
console.log(`杠精:${data4.n}`)
myData.n = -1
console.log(`杠精:${data4.n}馍管,設(shè)置為 -1 失敗了嗎!薪韩?`)
// 需求五:就算用戶擅自修改myData确沸,也要攔截他
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) //data5就是myData5的代理對(duì)象
//監(jiān)聽data
function proxy2({data}){
let value = data.n //拿到n,記錄下來
Object.defineProperty(data, 'n', {
get(){ return value },
set(newValue){
if(newValue<0)return
value = newValue
}
})
//代理的邏輯
const obj = {}
Object.defineProperty(obj, 'n', {
get(){ return data.n },
set(value){
if(value<0)return
data.n = value //通過替身value就不能直接修改data.n啦
}
})
return obj
}
console.log(`需求五:${data5.n}`)
myData5.n = -1
console.log(`需求五:${data5.n},設(shè)置為 -1 失敗了`)
myData5.n = 1
console.log(`需求五:${data5.n}俘陷,設(shè)置為 1 成功了`)
// 這代碼看著眼熟嗎罗捎?
let data5 = proxy2({ data:myData5 })
let vm = new Vue({data: myData})
現(xiàn)在我們可以說說 new Vue 做了什么了
[圖片上傳失敗...(image-151890-1648768426411)]
小結(jié)
4.Object.defineProperty
可以給對(duì)象添加屬性value
可以給對(duì)象添加getter/setter
getter/setter用于對(duì)屬性的讀寫進(jìn)行監(jiān)控
啥是代理(設(shè)計(jì)模式)
對(duì)myData對(duì)象的屬性讀寫,全權(quán)由另一個(gè)對(duì)象vm負(fù)責(zé)
那么vm就是myData的代理
比如myData.n不用岭洲,偏要用vm.n來操作myData.n
vm = new Vue({data: myData})
一.會(huì)讓vm成為myData的代理(proxy)
二.會(huì)對(duì)myData的所有屬性進(jìn)行監(jiān)控
為什么要監(jiān)控宛逗,為了防止myData的屬性變了,vm不知道
vm知道了又如何盾剩?
知道屬性變了可以render(data)刷新呀
UI=render(data)
注意: 全程這個(gè)對(duì)象n:0
都沒有被我扔掉過雷激,一直在改這個(gè)對(duì)象里面的東西,不是搞出了一個(gè)新對(duì)象告私。我是把這個(gè)對(duì)象的n給覆蓋掉了屎暇,變成get n、set n,我沒有把這個(gè)對(duì)象刪掉驻粟,因?yàn)槿绻野堰@個(gè)對(duì)象刪掉生成新的對(duì)象根悼,那關(guān)聯(lián)就斷開了,不是同一個(gè)對(duì)象了蜀撑。
我全程都是在這個(gè)對(duì)象上面修改挤巡,修改后得到一個(gè)被修改的對(duì)象。然后在被修改的基礎(chǔ)上酷麦,生成了一個(gè)新的代理矿卑。
明白2件事
1.Vue會(huì)對(duì)data后面的{n:0}
進(jìn)行竄改,給它加監(jiān)聽沃饶。
2.會(huì)新生成一個(gè)對(duì)象母廷,這個(gè)對(duì)象會(huì)代理篡改后的對(duì)象。
Object.defineProperty
可以給對(duì)象添加屬性value
可以給對(duì)象添加getter/setter
getter/setter用于對(duì)屬性的讀寫進(jìn)行監(jiān)控
數(shù)據(jù)響應(yīng)式
若一個(gè)物體能對(duì)外界的刺激做出反應(yīng)糊肤,它就是響應(yīng)式的
Vue的data是響應(yīng)式
const vm=new Vue({data:{n:0}})
我如果修改vm.n,那么UI中的n就會(huì)響應(yīng)我
Vue 2通過Object.defineProperty
來實(shí)現(xiàn)數(shù)據(jù)響應(yīng)式
響應(yīng)式網(wǎng)頁是啥琴昆?
如果我改變窗口大小,網(wǎng)頁內(nèi)容會(huì)做出響應(yīng)馆揉,那就是響應(yīng)式網(wǎng)頁业舍。
Vue的data的bug
目前你已經(jīng)知道了數(shù)據(jù)響應(yīng)式,但面試不會(huì)考常態(tài)一般考變態(tài)。
Object.defineProperty
的問題
Object.defineProperty(obj,'n',{...})
必須要有一個(gè)'n'舷暮,才能監(jiān)聽 & 代理obj.n對(duì)吧
如果前端開發(fā)者比較水蟋座,沒有給n怎么辦
示例1. Vue會(huì)給出一個(gè)警告
new Vue({
data: {},
template: `
<div>{{n}}</div>
`
}).$mount("#app");
示例2. Vue只會(huì)檢查第一層屬性
new Vue({
data: {
obj: {
a: 0 //obj.a會(huì)被Vue監(jiān)聽 & 代理
//b:undefined
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1; //頁面中不會(huì)顯示1
//Vue.set(this.obj,'b',1)
//this.$set(this.obj,'b'脚牍,1)
}
}
}).$mount("#app");
解決辦法
1.把k都聲明好,后面不再加屬性 比如b:undefined
2.使用Vue.set或者this.$set
$
是為了防止重名
比如Vue.set(this.obj,'b')
或者this.$set(this.obj,'b'巢墅,1)
Vue.set和this.$set作用:
新增key
自動(dòng)創(chuàng)建代理和監(jiān)聽(如果沒有創(chuàng)建過)
觸發(fā)UI更新(但不會(huì)立刻更新)
數(shù)組的變異方法
data中有數(shù)組诸狭,沒法提前聲明所有key怎么辦?
示例
new Vue({
data: {
array: ["a", "b", "c"]
},
template: `
<div>
{{array}}
<button @click="setD">set d</button>
</div>
`,
methods: {
setD() {
//this.array[3] = "d"; 頁面中不會(huì)顯示'd'
//this.$set(this.array,3,'d') 增加下標(biāo)的方式實(shí)現(xiàn)添加
this.array.push('d')//尤雨溪的做法
console.log(this.array)
}
}
}).$mount("#app");
[圖片上傳失敗...(image-76f371-1648768426411)]
尤雨溪的做法:篡改數(shù)組的API
Vue在這個(gè)對(duì)象君纫,你以為這個(gè)是數(shù)組對(duì)象驯遇,你傳給Vue之后,Vue就會(huì)篡改這個(gè)數(shù)組蓄髓。它會(huì)在中間加一層原型叉庐。這個(gè)原型有7個(gè)方法,這7個(gè)方法跟以前是同名的但是代碼被尤雨溪改了,會(huì)幫你set(監(jiān)聽,每次push都會(huì)通知Vue)会喝。也就是說push會(huì)做2件事情:調(diào)以前的push,調(diào)完后通知Vue添加監(jiān)聽和代理陡叠。
這7個(gè)API都會(huì)被Vue篡改,調(diào)用后會(huì)更新UI
總結(jié)
1.對(duì)象中新增的key
Vue無法事先監(jiān)聽和代理
要使用set來新增key,創(chuàng)建監(jiān)聽和代理,更新UI
最好提前把屬性都寫出來肢执,不要新增key
但數(shù)組做不到「不新增key」
2.數(shù)組中新增的key
也可用set來新增key,更新UI
不過尤雨溪篡改了7個(gè)API方便你對(duì)數(shù)組進(jìn)行增刪
這7個(gè)API會(huì)自動(dòng)處理監(jiān)聽和代理枉阵,并更新UI
結(jié)論:數(shù)組新增key最后通過7個(gè)API
[圖片上傳失敗...(image-151872-1648768426411)]
面試題
說說你對(duì)Vue數(shù)據(jù)響應(yīng)式的理解
Vue數(shù)據(jù)響應(yīng)式,使得數(shù)據(jù)更新時(shí)得到及時(shí)的渲染。vue通過Object.defineProperty()
給數(shù)據(jù)對(duì)象添加value屬性预茄,設(shè)置getter和setter監(jiān)控屬性的讀寫兴溜,并使用vm對(duì)象負(fù)責(zé)數(shù)據(jù)對(duì)象的代理,當(dāng)屬性更新時(shí)耻陕,調(diào)用rander()更新拙徽。