一、setter和getter
var o = {
property: function ([parameters]) {},
get property() {},
set property(value) {},
};
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 函數
二、Object.defineProperty()方法 var o = {}; // 建立一個新對象
// 在對象中添加一個屬性與資料描述符的示例
Object.defineProperty(o, "a", {
value : 37,
});
// 對象 o 擁有了屬性 a,值為 37
思考new Vue 對資料做了什麼
let data0 = {
n: 0
}
// 需求一:用 Object.defineProperty 定義 n
let data1 = {}
Object.defineProperty(data1, 'n', {
value: 0
})
console.log(`需求一:${data1.n}`)
// 總結:這煞筆文法把事情搞複雜了?非也,繼續看。
// 需求二:n 不能小于 0
// 即 data2.n = -1 應該無效,但 data2.n = 1 有效
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 成功`)
// 擡杠:那如果對方直接使用 data2._n 呢?
// 算你狠
// 需求三:使用代理
let data3 = proxy({ data:{n:0} }) // 括号裡是匿名對象,無法通路
function proxy({data}/* 解構指派,别TM老問 */){
const obj = {}
// 這裡的 'n' 寫死了,理論上應該周遊 data 的所有 key,這裡做了簡化
// 因為我怕你們看不懂
Object.defineProperty(obj, 'n', {
get(){
return data.n
},
set(value){
if(value<0)return
data.n = value
}
})
return obj // obj 就是代理
}
// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n},設定為 -1 失敗`)
data3.n = 1
console.log(`需求三:${data3.n},設定為 1 成功`)
// 杠精你還有話說嗎?
// 杠精說有!你看下面代碼
// 需求四
let myData = {n:0}
let data4 = proxy({ data:myData }) // 括号裡是匿名對象,無法通路
// data3 就是 obj
console.log(`杠精:${data4.n}`)
myData.n = -1
console.log(`杠精:${data4.n},設定為 -1 失敗了嗎!?`)
// 我現在改 myData,是不是還能改?!你奈我何
// 艹,算你狠
// 需求五:就算使用者擅自修改 myData,也要攔截他
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) // 括号裡是匿名對象,無法通路
function proxy2({data}/* 解構指派 */){
// 這裡的 'n' 寫死了,理論上應該周遊 data 的所有 key,這裡做了簡化
// 因為我怕你們看不懂
let value = data.n//儲存原有n的值
Object.defineProperty(data, 'n', {//相當于重新設定了一個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 就是代理
}
// data3 就是 obj
console.log(`需求五:${data5.n}`)
myData5.n = -1
console.log(`需求五:${data5.n},設定為 -1 失敗了`)
myData5.n = 1
console.log(`需求五:${data5.n},設定為 1 成功了`)
// 這代碼看着眼熟嗎?
// let data5 = proxy2({ data:myData5 })
// let vm = new Vue({data: myData})
// 現在我們可以說說 new Vue 做了什麼了
下面是對上面代碼的總結(重點) Object.defineProperty的作用:
- 可以給對象添加屬性value
- 可以給對象添加getter/setter, getter/setter用來對屬性的讀寫進行監控
代理的設計模式,意思為vm執行個體負責對myData對象屬性的讀寫。
即vm就是myData的代理,類似于房屋中介,住戶通過中介和房東交流。
例如通常vm.n來操作myData.n,而不是直接操作myData.n
原理 const vm = new Vue({data: myData}) 作用:- 核心目的:對于data的任何變動,都得知情,進而重新進行渲染
- 會讓vm成為myData的代理,即proxy
- 同時會對myData的所有屬性進行監控
- 監控的原因是,為了防止myData屬性改變的時候,vm不知情。如果通知vm了,就可以進行操作,然後調用render(data)方法,渲染視圖,即UI=render(data)
- 全程對data進行修改,并沒有複制和生成新的data,隻有覆寫修改
- 如果data有多個屬性,n,m,k,那麼就會有get n/get m /get k等
資料響應式是指當資料改變時,UI或者視圖能夠做出相應的反應。比如我用力打你一拳,你會喊痛這就是響應式。
1、vue的data屬于響應式
在const vm = new Vue({data:{n:0}})中,如果我修改vm.n,那麼UI中的n就會響應我
2、Vue的資料響應式
Vue響應式系統即對資料進行修改時,視圖會進行更新;當把JS對象傳入Vue執行個體作為 data 選項,Vue将周遊此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉為 getter/setter,getter/setter 對使用者不可見,但是在内部它們讓 Vue 能夠追蹤依賴,在屬性被通路和修改時通知變更。
3、響應式網頁
響應式網頁是指當使用者改變視窗大小,網頁内容會做出響應
四、Object.defineProperty有問題或者說Object.defineProperty()實作資料響應的缺點Object.defineProperty在添加屬性時必須要屬性名,才能監聽&代理
Object.defineProperty(obj,'n',{...})
必須要有個‘n’才能監聽&代理obj.n
如果隊友沒給你n怎麼辦
import Vue from "vue/dist/vue.js";
Vue.config.productionTip = false;
new Vue({
data: {
obj: {
a: 0 // obj.a 會被 Vue 監聽 & 代理
}
},
template: `
<div>
{{obj.b}}
<button @click="setB">set b</button>
</div>
`,
methods: {
setB() {
this.obj.b = 1; //請問,頁面中會顯示 1 嗎?
}
}
}).$mount("#app");
//請問,頁面中會顯示 1 嗎?
//答案是不會,因為vue沒法監聽一開始就不存在的obj.b
//它隻會檢查obj在不在,是以obj.b不存在,他也不會報錯
下條代碼出解決辦法
總結:
- 檢測不到對象屬性的添加和删除:當你在對象上新加了一個屬性newProperty,目前新加的這個屬性并沒有加入vue檢測資料更新的機制(因為是在初始化之後添加的)。
- vue.$set是能讓vue知道你添加了屬性, 它會給你做處理,$set内部也是通過調用Object.defineProperty()去處理的 無法監控到數組下标的變化,導緻直接通過數組的下标給數組設定值,不能實時響應。
- 當data中資料比較多且層級很深的時候,會有性能問題,因為要周遊data中所有的資料并給其設定成響應式的。
解決辦法
1、把key都聲明好,後面不在加屬性(弱智的寫法)
2、使用vue.set和this.$set
vue.set和this.$set的作用1、新增key
2、自動建立代理和監聽(在沒有的情況下)
3、觸發UI更新(不會立刻更新)
methods:{
add({
vue.set(this.obj, 'b', 1)
// 或者
this.$set(this.obj, 'b', 1)
}
}
五、數組新增加key(也用set方法) 由于數組的長度可以一直增加,沒有辦法提前把數組的key都聲明出來,Vue也不能直接檢測新增的下标。可以使用set來新增key, 但不會自動添加監聽和代理, 但是尤雨溪纂改了數組的7個API來對數組進行操作,這7個API會自動處理監聽和代理,并更新UI。
推薦使用這7個API來新增數組key:(自行官網檢視)
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
篡改原理
class VueArray extends Array {
push(...args) {
const oldLength = this.length; // this
super.push(...args);
console.log(" push ");
for (let i = oldLength; i < this.length; i++) {
Vue.set(this, i, this[i]);
// key Vue
}
}
}
六、vue3中的Proxy 什麼是代理呢,可以了解為在對象之前設定一個“攔截”,當該對象被通路的時候,都必須經過這層攔截。意味着你可以在這層攔截中進行各種操作。比如你可以在這層攔截中對原對象進行處理,傳回你想傳回的資料結構。
ES6 原生提供 Proxy 構造函數,MDN上的解釋為:Proxy 對象用于定義基本操作的自定義行為(如屬性查找,指派,枚舉,函數調用等)。
const p = new Proxy(target, handler);
-
: 所要攔截的目标對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)target
-
:一個對象{},定義要攔截的行為,不可以寫nullhandler
const p = new Proxy({}, {
get(target, propKey) {
return '哈哈,你被我攔截了';
}
});
console.log(p.name);
// 哈哈,你被我攔截了
注意Proxy是用來操作對象的。代理的目的是為了拓展對象的能力。
再看一個例子 我們可以實作一個功能:不允許外部修改對象的name屬性。
const p = new Proxy({}, {
set(target, propKey, value) {
if (propKey === 'name') {
throw new TypeError('name屬性不允許修改');
}
// 不是 name 屬性,直接儲存
target[propKey] = value;
}
});
p.name = 'proxy';
// TypeError: name屬性不允許修改
p.a = 111;
console.log(p.a); // 111
總結:
- Proxy是用來操作對象的,Object.defineProperty() 是用來操作對象的屬性的。
- vue2.x使用 Object.defineProperty()實作資料的響應式,但是由于 Object.defineProperty()是對對象屬性的操作,是以需要對對象進行深度周遊去對屬性進行操作。
- vue3.0 用 Proxy 是對對象進行攔截操作,無論是對對象做什麼樣的操作都會走到 Proxy 的處理邏輯中