Vue到底對 data 做了什么凳怨?
import Vue from 'vue/dist/vue.js'
Vue.config.productionTip = false
const myData = {
n: 0
}
console.log(myData)
const vm = new Vue({
data: myData,
template: `
<div>{{n}}<button @click="add">+1</button></div>
`,
methods: {
add(){
this.n +=10
}
}
}).$mount("#app")
setTimeout(() => {
this.n += 10
console.log(myData)
}, 3000)
- 我們有如上的代碼叉讥,其中 data 中的的數(shù)據(jù)引用外面的變量 myData华临;
- 我們在兩處進行打印芯杀,一處是初始化的時候打印,一處是在變化之后進行打友盘丁揭厚;
-
我們可以看到初始化的時候打印這個myData就只是我們定義的對象,但是在變化之后再打印這個對象就會發(fā)生變化扶供,好像是被包裹了一層東西筛圆;
image.png - 一開始是 {n:0},傳給 new Vue 之后就立馬變成{n:(...)}椿浓;
- {n:(...)}是個什么東西太援,為什么變現(xiàn)和 {n:0}一致?
- 我們了解這個知識點需要知道什么是 ES6 的 getter 和 setter扳碍。
getter 和 setter
- 我們聲明一個變量提岔,這個變量的屬性如下,具有一個姓的屬性笋敞,一個名的屬性碱蒙,還有一個age屬性;
let obj0 = { 姓: "高", 名: "圓圓", age: 18 };
- 需要一:得到姓名夯巷,那么只要在里面添加一個方法就好了振亮,在對象中巧还,屬性名和屬性值一樣的時候,是可以直接寫函數(shù)的坊秸。
let obj1 = { 姓: "高", 名: "圓圓", 姓名() { return this.姓 + this.名; }, age: 18 }; console.log("需求一:" + obj1.姓名())
- 但是這個函數(shù)我們需要調用麸祷,也就是后面又括號,那么怎么去掉括號呢褒搔?
- 需求二:姓名之后不加括號也能得出值
- 這個需要使用 ES6 的新語法阶牍,get方法
let obj2 = { "姓": "高", "名": "圓圓", get 姓名() { return this.姓 + this.名; }, age: 18 } console.log("需求二:" + obj2.姓名)
- 我們使用 get 之后發(fā)現(xiàn),這個姓名是一個屬性星瘾,不用加括號走孽,只不過是以函數(shù)形式定義的,看起來跟普通的屬性并沒有區(qū)別
- 這種寫法是 getter琳状,用于獲取這個值
- 需求三:姓名可以被寫磕瓷,這個寫法是 setter
let obj3 = { "姓": "高", "名": "圓圓", get 姓名() { return this.姓 + this.名; }, set 姓名(xxx){ this.姓 = xxx[0] this.名 = xxx.slice(1) }, age: 18 } obj3.姓名 = '劉詩詩' console.log(`需求三:姓 ${obj3.姓}, 名 ${obj3.名}`) console.log(obj3)
- 我們將這個obj3打印出來看看
- image.png
- 這個姓名其實跟我們之前的那個data中的數(shù)據(jù)是一樣的形式,說明我們之前的那個數(shù)據(jù)也是getter和setter形成的
- 這兒的姓名不是一個真實的屬性念逞,但是我們可以對這個屬性進行讀和寫困食,讀寫通過getter和setter進行操作
Object.defineProperty
- 我們之前在使用getter和setter的時候是在定義的時候直接使用的,當我們在定義好一個對象之后翎承,無法再添加get和set硕盹。
- 但是如果我們想在后面寫怎么辦?
- 這個時候我們就需要使用Object.defineProperty
- 下面是一個示例代碼
let obj3 = { "姓": "高", "名": "圓圓", get 姓名() { return this.姓 + this.名; }, set 姓名(xxx){ this.姓 = xxx[0] this.名 = xxx.slice(1) }, age: 18 } var _新屬性 = 0 Object.defineProperty(obj3, '新屬性', { get(){ return _新屬性 }, set(value){ _新屬性 = value } })
- 上述代碼中叨咖,我們想要添加一個新屬性瘩例,其中_新屬性是為了盛放新屬性的值
- 我們定義的屬性是不存在的,所以在get和set方法中不可以使用本屬性甸各,因為新屬性的使用是通過get和set方法的垛贤,如果在get和set中使用就會造成死循環(huán)
- 這個_新屬性其實是一個代理
代理和監(jiān)聽
- 需求一:用 Object.defineProperty 定義 n
let data1 = {} Object.defineProperty(data1, 'n', { value: 0 }) console.log(`需求一:${data1.n}`)
- 我們定義了一個data1,想要添加一個屬性趣倾,并設置成0
- 需求二:n不能小于0
let data2 = {} data._n = 0 Object.defineProperty(data2, 'n', { get(){ return this._n // _n用來存儲n的值 }, set(value){ if(value < 0) return this._n = value } }) console.log(`需求二:${data2.n}`) data2.n = -1
- 我們使用了一個臨時的變量_n去存儲
- 如果對方直接使用這個data._n呢聘惦?
- 我們怎么將這個data._n將其變得訪問不到呢?
- 可以使用代理
- 需求三:使用代理
let data3 = proxy({ data: {n:0} }) // 括號里面的匿名對象無法訪問誊酌,因為沒有名字 function proxy({data}){ const obj = {} // 這里的 n 寫死了部凑,理論上應該是遍歷屬性 Object.defineProperty(obj, 'n' { get(){ return data.n }, set(value){ if(value<0)return data.n = value } }) return obj // obj就是代理 } console.log(`需求三:${data3.n}`) data3.n = -1
- 函數(shù)傳進去的匿名對象是無法訪問的
- 函數(shù)里面使用的obj是一個代理
- 我們將data3中的所有屬性值通過get和set的方式轉給代理obj
- data3理論上跟obj是等價的露乏,我們通過在obj里面去設定一些機制碧浊,防止外界隨意篡改,如不可以小于0
- 代理的作用就是做一些限制保護瘟仿,只要原始傳進去的對象不暴露在外面就行箱锐。
- 但是如果對于一些個變量已經(jīng)暴露在外面怎么辦?
- 需求四:就算用戶擅自修改myData劳较,也要攔截它
let myData = {n:0} let data4 = proxy({ data: myData }) myData.n = -1
- 上面這個代碼就直接繞過了代理驹止,修改了最原始的myData
- 我們通過下面的代碼實現(xiàn)了怎樣能在用戶修改的時候做一個監(jiān)聽呢浩聋?
let myData5 = {n:0} let data5 = proxy2({ data: myData5 }) // 這個改進的代碼可以100%的防止超出規(guī)則的數(shù)據(jù)更改 function proxy2({data}){ // 使用這個value存儲原始的data中的n,先將其記下來臊恋,因為待會要刪除這個n let value = data.n // 我們創(chuàng)建一個新的n就是覆蓋原先的n衣洁,覆蓋就相當于刪除 // 這個部分是監(jiān)聽邏輯,只要n發(fā)生更改抖仅,就出觸發(fā)這個坊夫,但是返回值還是需要通過判斷才能更改的 // 我們將原先的屬性刪掉,通過get和set獲取以及重置撤卢,這樣就可以在其中設置規(guī)則环凿,即監(jiā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 } }) }
- 上面這串代碼改寫的代理函數(shù)分為兩部分,一部分是負責監(jiān)聽放吩,另一個部分是用于代理智听;
- 負責監(jiān)聽的那部分通過使用閉包,拿到數(shù)據(jù)就在內(nèi)部存儲一下數(shù)據(jù)渡紫,并通過get和set的方式重新定義同名數(shù)據(jù)到推,這樣外界在訪問的時候就必須通過這個get和set,在這個里面做一些限制腻惠,就可以實現(xiàn)監(jiān)聽效果
- 代理邏輯還是跟原先的一樣
- let data5 = proxy2({ data: myData5 })這段代碼跟const vm = new Vue({data: {n: 0}})很是類似
那么 Vue 到底對數(shù)據(jù)做了什么环肘?
- 啥是代理?(設計模式)
- 對 myData 對象的屬性讀寫集灌,全權由另一個對象 vm 負責(通過將myData中的屬性用get和set的方式給vm)
- 那么 vm 就是 myData 的代理
- 比如 myData.n 不用悔雹,偏要用 vm.n 來操作 myData.n
- 有關 vm = new Vue({data: myData})
- 會讓 vm 成為 myData 的代理(proxy)
- 會對 myData 的所有屬性進行監(jiān)控
- 為什么要監(jiān)控?為了防止 myData 的屬性變了欣喧,vm不知道
- vm 知道了又如何腌零?知道屬性變了就可以調用 render(data)
- UI=render(data)
- image.png
- 全程最開始的對象是不變的,我們操作和展示的時候唆阿,只動vm中的對象
什么是數(shù)據(jù)響應式
- 什么是響應式益涧?
- 如果一個物體對于外界的刺激能做出反應,他就是響應式的
- Vue 的 data 是響應式
- const vm = new Vue({data: {n: 0}})
- 我如果修改 vm.n驯鳖,那么 UI 中的 n 就會響應我
- Vue 2 通過 Object.defineProperty 來實現(xiàn)數(shù)據(jù)響應式
Vue 的 data 的 bug
- Object.defineProperty 的問題
- Object.defineProperty(obj, 'n', {...})
- 必須要有一個 'n'闲询,才能監(jiān)聽&代理obj.n
- 如果前端工程師沒有給出n怎么辦?
- 情況一:第一層屬性沒有n浅辙,Vue會給出一個警告
- image.png
- 會警告并沒有這個屬性
- 情況二:屬性是一個對象扭弧,對象里面有屬性,這種情況下记舆,Vue只會檢查第一層屬性
- 屬性里面的屬性如果不存在鸽捻,就無法進行后續(xù)操作的
- image.png
- image.png
- 點擊之后,并不會顯示b的值
- 為什么:因為Vue無法監(jiān)聽一開始就不存在的obj.b
- 解決方法:
- 一開始就將需要用到的key監(jiān)聽好,如b: undefined
- 使用 Vue.set 或者 this.$set
- this.$set(this.obj, 'b', 1)
- Vue.set(this.obj, 'b', 1)
Vue.set 和 this.$set
- 作用
- 新增 key
- 自動創(chuàng)建代理和監(jiān)聽(如果沒有創(chuàng)建過)
- 觸發(fā)UI更新(但并不會立刻更新)
- 舉例
- this.$set(this.object, 'm', 100)
數(shù)組的變異方法
- 我們知道數(shù)組最終是一個對象御蒲;
- 如a = ['a', 'b', 'c']衣赶,實際上會是a = {0: 'a', 1: 'b', 2: 'c'}
- 這樣在 vue 中,當我們需要給數(shù)組添加元素的時候厚满,不可以直接使用原先的this.a[3] = "d"這樣的方法府瞄,因為vue對原先不存在的數(shù)據(jù)是無法進行監(jiān)聽的,需要使用this.$set()
- 這樣子碘箍,我們對于數(shù)組的所有操作摘能,都得使用$.set?
-
使用 this.a.push('d')敲街,這個方法經(jīng)過Vue改編了团搞,同名,可以支持在vm中使用多艇,就是在原先的方法那邊添加一個set
image.png - 篡改的方式是添加一層原型逻恐,在原型上面篡改