一:vue2的響應式原理 (數據雙向綁定原理)
Object.defineProperty()
Object.defineProperty() 其實核心的並不是爲對象做數據雙向綁定,而是去給對象做屬性標籤,只不過屬性裏的 get 和 set 實現了響應式。
1. Object.defineproperty()
屬性名 | 默認值 |
value | undefined |
set | undefined |
get | undefined |
writable(可寫性) | true |
enumerable (可枚舉性)for/in 是否可循環 | true |
configurable (可配置性) | true |
例子:
var o = {}
Object.defineProperty(o, 'name', {
value: 'zhoufangbing',
configurable: false,
enumerable: false,
writable: false
})
console.log(o) // { name: "zhoufangbing" }
console.log(o.name) // zhoufangbing
o.name = 'xx'
console.log(o.name) // zhoufangbing
// 因爲 writable (可寫性)爲false,所以值不可更改,仍爲zhoufangbing
// 若值爲 true , 則會輸出 xx
// Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用 for...in 循環遍歷該對象時返回的順序一致 。
console.log(Object.keys(o)) // []
// 因爲 enumerable (可枚舉性)false 所以返回 [], 如果enumerable 爲true
console.log(Object.keys(o)) // ['name']
// 訪問器屬性 get set
var person = {
_age: 22, // 屬性前面加_,代表屬性只能通過對象方法訪問 不然瀏覽器報錯
isGrowUp: false
}
Object.defineProperty(person, 'age', {
get: function() {
return this._age // this 指向 person
},
set: function(value) {
this._age = value // set 重新賦值
if (value >= 18) {
this.isGrowUp = true
} else {
this.isGrowUp = false
}
}
})
console.log(person.age) // 22
person.age = 19
console.log(person.age) // 19
console.log(person.isGrowUp) // true
person.age = 12
console.log(person.age) // 12
console.log(person.isGrowUp) // false
2. Object.defineProperties()
Object.defineProperty(object, propertyName, descriptor) 定義新屬性時,descriptor 中不能同時有 訪問器(getter/setter) 與 value/writable 屬性
var person = {}
Object.defineProperties(person, {
age: {
value: 18,
writable: true,
enumerable: true
},
isGroup: {
get: function() {
if (this.age >= 18) {
return true
} else {
return false
}
}
}
})
筆記:
1. vue2 中的數據雙向綁定原理:使用Object.defineProperty() 中的 get 和 set
2. 當從對象中取值時,會觸發 get 函數,當重新賦值時,會觸發 set 函數~
如果定義了get 函數,get函數必須要有 return ,否則 【對象.屬性】 取值 會是undefined~ return 的值 就是 【對象.屬性】
二: vue 從改變一個數據到發生改變的全過程
1. 數據更改觸發 Object.defineProperty() 中的set函數
2. Set 函數觸發更新
3. 更改對應的虛擬dom
4. 更新render
三:數據雙向綁定簡易版代碼
<body>
<div id="app"></div>
<script>
function Vue(dataObj) {
this.$data = dataObj
this.virtualDom = ''
this.el = document.getElementById('app')
this.observer(dataObj)
this.render()
}
// 註冊 set 和 get
Vue.prototype.observer = function(obj) {
var _this = this
// 遞歸一下 防止對象裏面套對象
for(var key in obj) {
var value = obj[key]
if (typeof value === 'object') {
this.observer(value)
} else {
Object.defineProperty(obj, key, { // TODO
get: function() {
// 進行依賴收集
// 1. data 中的數據並不是所有地方都能用到的
// 2. 如果直接更新整個視圖,有點虧
// 3. 先收集來依賴 改變變量 的視圖(組件
// 4. 把依賴的組件進行視圖更新,其他不變視圖
return value
},
set: function(newValue) {
value = newValue
_this.render()
}
})
}
}
}
// 渲染頁面
Vue.prototype.render = function() {
this.virtualDom = 'i am ' + this.$data.a
this.el.innerHTML = this.virtualDom
}
// 創建實例
var vm = new Vue({
a: 1
})
setTimeout(() => {
vm.$data.a = 2 // 值改變,執行set函數
}, 1000)
</script>
</body>
四: Object.defineProperty() 定義的get 和 set 是對象的屬性,那麼數組怎麼辦?
在 Vue 中,通過數組下標更改數組內容,是不會觸發視圖更新的
var arr = [1,2,3,4]
arr[3] = 5
只有使用數組的方法,視圖才能更新
arr.push()
arr.unshift()
arr.shift()
arr.pop()
arr.reserve()
先學透知識、會用,再去看原理、源碼~
五:Vue3 的響應式原理
Proxy
// Proxy 對象用於定義基本操作的自定義行爲
// 和 Object.defineProperty() 功能基本一樣,但是用法不同
Proxy: 代理
作用:擴展(增強)對象的一些功能,比如預警、上報、擴展功能、統計等
語法: new Proxy(被代理的對象,對代理的對象做什麼操作)
new Proxy : 優點不會污染原對象,重新代理 objProxy
比如:
// 原始對象
let obj = { name: 'zhouzhou' }
// 代理對象
let objProxy = new Proxy(obj, {
// get 方法兩個參數,第一個是對象,第二個是你訪問的是誰就是誰
// objProxy.name 則 property 就是 name
get: function(target, property) {
console.log(`您訪問了${property}屬性`)
return target[property]
}
})
console.log(objProxy.name) // get 函數的return值
例子1:實現一個訪問一個對象的屬性,正常情況下是返回 undefined,希望如果不存在返回 warning 警告信息
<script>
let obj = {
name: 'zhouzhou',
age: 24,
sex: '女'
}
let objProxy = new Proxy(obj, {
get: function(target, property) {
if (obj[property]) {
return target[property]
} else {
throw new Error(`您訪問的屬性${property}不存在`)
}
}
})
console.log(objProxy.sex)
console.log(objProxy.money)
</script>
例子2:定義一個Proxy,DOM.div() 則生成一個div,DOM.a() 生成一個 a 標籤,DOM.span 生成一個 span
<div id="app"></div>
<script>
let DOM = new Proxy({}, {
get: function(target, peoperty) {
return function(attr = {}, ...argument) {
// 創建對象
const el = document.createElement(peoperty)
// 設定屬性
for(keys of Object.keys(attr)) {
el.setAttribute(keys, attr[keys])
}
// 設定內容
for(let elem of argument) {
if (typeof elem === 'string') {
child = document.createTextNode(elem)
} else {
child = elem
}
el.appendChild(child)
}
return el
}
}
})
const node = DOM.div(
{ class: 'wrapper'},
'hhhhh',
DOM.p({}, 'aaa'),
DOM.a({href: 'https:www.baidu.com'}, '點擊跳轉')
)
console.log(node)
const app = document.getElementById('app')
app.appendChild(node)
</script>
Proxy 的其他屬性~~
1. set : 設置值 攔截
var obj = new Proxy({}, {
// set 三個參數:目標對象、屬性、屬性值
set: function(target, property, value) {
// 可以進行值攔截校驗
if (property === 'age') {
if (!Number.isInteger(value)) {
throw TypeError('年齡必須爲整數')
return
}
if (value < 18 || value > 80) {
throw RangeError('年齡不在合法範圍內')
return
}
}
// 設置屬性值
target[property] = value
}
})
obj.name = 'zhouzhou'
obj.age = 19
console.log(obj) // {name: "zhouzhou", age: 19}
2. deleteProperty() : 刪除屬性
let json = {
a: 1,
b: 2
}
var obj = new Proxy(json, {
deleteProperty: function(target, property) {
console.log(`you will delete ${property}`) // 刪除之前提示
delete target[property]
}
})
delete obj.a
console.log(obj)
3. has():判斷屬性是否在 搭配關鍵字 in
let json = {
a: 1,
b: 2
}
var obj = new Proxy(json, {
has: function(target, property) {
console.log(`判斷是否存在${property}屬性`)
return property in target
}
})
console.log('a' in obj) // true
console.log('c' in obj) // false
4. apply() 攔截方法
function sum (a, b) {
return a + b
}
let newSum = new Proxy(sum, {
// 目標對象、this指向、函數參數(數組形式)
apply: function(target, context, args) {
return Reflect.apply(...arguments) // 執行目標函數 sum 返回 5
}
})
const result = newSum(2,3)
console.log(result)
所以 Vue3 的響應式原理:Proxy 的 set 和 get
重寫:
function Vue(dataObj) {
this.$data = dataObj
this.virtualDom = ''
this.el = document.getElementById('app')
this.observer(dataObj)
this.render()
}
// 註冊 set 和 get
Vue.prototype.observer = function(obj) {
var _this = this
// 遞歸一下 防止對象裏面套對象
this.$data = new Proxy(this.$data, {
set: function(target, property, value) {
target[property] = value
_this.render()
},
get: function(target, property) {
return target[property]
}
})
}
// 渲染頁面
Vue.prototype.render = function() {
this.virtualDom = 'i am ' + this.$data.a
this.el.innerHTML = this.virtualDom
}
// 創建實例
var vm = new Vue({
a: 1
})
setTimeout(() => {
vm.$data.a = 2 // 值改變,執行set函數
}, 1000)