一努酸、響應(yīng)式原理
我們在設(shè)計屬性值的時候罢浇,頁面數(shù)據(jù)更新
用到的技巧就是Object.defineProperty()
Object.defineProperty(對象, '設(shè)置什么屬性名', {
writable //設(shè)置可不可以改
configurable //可不可以配置
enumerable //控制屬性是否可枚舉乒验,是不是可以被for-in循環(huán)取出來
value //值
set () {} //是一個函數(shù)搪缨,在賦值的時候觸發(fā)
get () {} //是一個函數(shù)逆趋,取值的時候觸發(fā)
})
最重要的就是里面的get和set方法
//Vue中的響應(yīng)式做法
//模擬簡化的版本
let o = {
name: '張三',
age: 19,
gender: '男'
}
function defineReactive (target, key, value, enumerable) {
//函數(shù)內(nèi)部就是一個局部作用域哮伟,這個value就只在函數(shù)累使用的變量(閉包)
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
return value
},
set (newVal) {
value = newVal
}
})
}
//將對象的屬性轉(zhuǎn)換為響應(yīng)式
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
defineReactive(o, keys[i], o[keys[i]], true)
}
上面的簡化版是只有一層遍歷枉昏,只能處理let o = {name:'張三'}陈肛。沒有處理到深層次的 比如let o = {user:{name:'張三'}, data: [{},{}])
下面通過遞歸來實現(xiàn)深層的響應(yīng)式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewpoort" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>text</title>
</head>
<body>
<script>
let data = {
name: '張三',
age: 19,
course: [
{name: '語文'},
{name: '數(shù)學(xué)'},
{name: '英語'}
]
}
//將對象o響應(yīng)式話
function reactify (o) {
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i] //屬性名
let value = o[key]
//判斷這個屬性是不是引用類型,判斷是不是數(shù)組
//如果是引用類型就需要遞歸凶掰,如果不是就不用遞歸
//如果是數(shù)組就需要循環(huán)數(shù)組燥爷,然后將數(shù)組里面的元素進行響應(yīng)式化
if (Array.isArray(value)) {
//數(shù)組類型
for (let j = 0; j < value.length; j++) {
reactify(value[j]) //遞歸
}
} else {
//對象或值類型
defineReactive(o, key, value, true)
}
}
}
function defineReactive (target, key, value, enumerable) {
//函數(shù)內(nèi)部就是一個局部作用域,這個value就只在函數(shù)累使用的變量(閉包)
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
//判斷value是非數(shù)組的引用類型
reactify(value) //遞歸
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
return value
},
set (newVal) {
value = newVal
}
})
}
reactify(data)
</script>
</body>
</html>
上面的代碼還有一些缺陷懦窘,比如數(shù)組的push方法前翎,在數(shù)組中push一個對象,數(shù)組的其他對象都是響應(yīng)式的畅涂,而push的那個對象不是響應(yīng)式的港华。
數(shù)組需要處理幾個特殊方法:
push、pop午衰、shift立宜、unshift、reverse臊岸、sort橙数、splice
1、在改變數(shù)組的數(shù)據(jù)的時候帅戒,要發(fā)出通知
2灯帮、加進來的元素也要變成響應(yīng)式的
<script>
/*
如果一個函數(shù)已經(jīng)定義了,但是我們需要擴展其功能逻住,我們一般的處理辦法是:
1.使用一個臨時的函數(shù)名存儲函數(shù)
2.重新定義原來的函數(shù)
3.定義擴展的功能
4.調(diào)用臨時的那個函數(shù)
*/
function func () {
console.log('原始的功能')
}
// 1
let _tmpFn = func
// 2
func = function () {
//4
_tmpFn()
// 3
console.log('新的擴展的功能')
}
func() //1.打印出:原始的功能
//2.打印出:新的擴展功能
</script>
擴展數(shù)組的push和pop
1钟哥、直接修改prototype不行,會讓所有數(shù)組都改變瞎访,不想變成響應(yīng)式的也成了響應(yīng)式
2腻贰、修改要進行響應(yīng)式化的數(shù)組的原型
//只需要之前響應(yīng)式代碼的基礎(chǔ)上加上及修改以下代碼
let ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
//思路:原型式繼承,修改原型鏈的結(jié)構(gòu)
//let arr = []
//繼承關(guān)系:arr=>Array.prototype=>Object.prototype...
//改成:arr=>改寫的方法=>Array.prototype=>Object.prototype...
let array_method = Object.create(Array.prototype)
ARRAY_METHOD.forEach(method => {
array_method[method] = function () {
//將數(shù)據(jù)進行響應(yīng)式化
console.log('檢測到數(shù)據(jù)')
//將數(shù)據(jù)進行響應(yīng)式化
for (let i = 0; i < arguments.length; i++) {
reactify(arguments[i])
}
//調(diào)用原來的方法
let res = Array.prototype[method].apply(this, arguments)
return res
}
})
//修改reactify方法扒秸,value.__proto__ = array_method
//將對象o響應(yīng)式化
function reactify (o) {
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i] //屬性名
let value = o[key]
//判斷這個屬性是不是引用類型播演,判斷是不是數(shù)組
//如果是引用類型就需要遞歸,如果不是就不用遞歸
//如果是數(shù)組就需要循環(huán)數(shù)組伴奥,然后將數(shù)組里面的元素進行響應(yīng)式化
if (Array.isArray(value)) {
//數(shù)組類型
value.__proto__ = array_method //數(shù)組就響應(yīng)式了
for (let j = 0; j < value.length; j++) {
reactify(value[j]) //遞歸
}
} else {
//對象或值類型
defineReactive(o, key, value, true)
}
}
}
二宾巍、數(shù)據(jù)代理
我們在使用Vue的時候,獲得屬性渔伯。賦值屬性都是直接使用的Vue實例
//像Vue中new一個Vue實例
const app = new vue({
data: {
name: 'feifei'
}
})
//訪問name屬性
app.name 也可以是 app._data.name
代理方法就是要將app._data中的成員給映射到app上
由于需要在更新數(shù)據(jù)的時候,更新頁面的內(nèi)容肄程,所以app._data訪問的成員與app訪問的成員應(yīng)該是同一成員锣吼。
由于app._data 已經(jīng)是響應(yīng)式的對象了选浑,所以只需要讓app訪問的成員去訪問app._data的對應(yīng)成員就可以了。
//將_data上的屬性代理到實例上
let keys = Object.keys(this._data)
//數(shù)據(jù)代理
for (let i = 0; i < keys.length; i++) {
//this._data[keys[i]]映射到this[keys[i]]上
//就是要讓this提供keys[i]這個屬性
//在訪問這個屬性的時候相當于在訪問thid._data上的屬性
Object.defineProperty(this, keys[i], {
enumerable: true,
configurable: true,
get () {
return this._data[keys[i]]
},
set (newVal) {
this._data[keys[i]] = newVal
}
})
}
在Vue中不僅僅只有_data 還會有methods玄叠、props等等古徒,Vue封裝了一個proxy方法
//將_data上的屬性代理到實例上
let keys = Object.keys(this._data)
//數(shù)據(jù)代理
for (let i = 0; i < keys.length; i++) {
//this._data[keys[i]]映射到this[keys[i]]上
//就是要讓this提供keys[i]這個屬性
//在訪問這個屬性的時候相當于在訪問thid._data上的屬性
proxy(this, '_data', keys[i])
}
function proxy (app, prop, key) {
Object.defineProperty(app, key, {
enumerable: true,
configurable: true,
get () {
return app[prop][key]
},
set (newVal) {
app[prop][key] = newVal
}
})
}
//proxy(實例, '_data', 屬性名)
//proxy(實例, '_props', 屬性名)