一、响应式原理
我们在设计属性值的时候,页面数据更新
用到的技巧就是Object.defineProperty()
Object.defineProperty(对象, '设置什么属性名', {
writable //设置可不可以改
configurable //可不可以配置
enumerable //控制属性是否可枚举,是不是可以被for-in循环取出来
value //值
set () {} //是一个函数,在赋值的时候触发
get () {} //是一个函数,取值的时候触发
})
最重要的就是里面的get和set方法
//Vue中的响应式做法
//模拟简化的版本
let o = {
name: '张三',
age: 19,
gender: '男'
}
function defineReactive (target, key, value, enumerable) {
//函数内部就是一个局部作用域,这个value就只在函数累使用的变量(闭包)
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
return value
},
set (newVal) {
value = newVal
}
})
}
//将对象的属性转换为响应式
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: [{},{}])
下面通过递归来实现深层的响应式
<!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: '数学'},
{name: '英语'}
]
}
//将对象o响应式话
function reactify (o) {
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i] //属性名
let value = o[key]
//判断这个属性是不是引用类型,判断是不是数组
//如果是引用类型就需要递归,如果不是就不用递归
//如果是数组就需要循环数组,然后将数组里面的元素进行响应式化
if (Array.isArray(value)) {
//数组类型
for (let j = 0; j < value.length; j++) {
reactify(value[j]) //递归
}
} else {
//对象或值类型
defineReactive(o, key, value, true)
}
}
}
function defineReactive (target, key, value, enumerable) {
//函数内部就是一个局部作用域,这个value就只在函数累使用的变量(闭包)
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
//判断value是非数组的引用类型
reactify(value) //递归
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
return value
},
set (newVal) {
value = newVal
}
})
}
reactify(data)
</script>
</body>
</html>
上面的代码还有一些缺陷,比如数组的push方法,在数组中push一个对象,数组的其他对象都是响应式的,而push的那个对象不是响应式的。
数组需要处理几个特殊方法:
push、pop、shift、unshift、reverse、sort、splice
1、在改变数组的数据的时候,要发出通知
2、加进来的元素也要变成响应式的
<script>
/*
如果一个函数已经定义了,但是我们需要扩展其功能,我们一般的处理办法是:
1.使用一个临时的函数名存储函数
2.重新定义原来的函数
3.定义扩展的功能
4.调用临时的那个函数
*/
function func () {
console.log('原始的功能')
}
// 1
let _tmpFn = func
// 2
func = function () {
//4
_tmpFn()
// 3
console.log('新的扩展的功能')
}
func() //1.打印出:原始的功能
//2.打印出:新的扩展功能
</script>
扩展数组的push和pop
1、直接修改prototype不行,会让所有数组都改变,不想变成响应式的也成了响应式
2、修改要进行响应式化的数组的原型
//只需要之前响应式代码的基础上加上及修改以下代码
let ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice'
]
//思路:原型式继承,修改原型链的结构
//let arr = []
//继承关系: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 () {
//将数据进行响应式化
console.log('检测到数据')
//将数据进行响应式化
for (let i = 0; i < arguments.length; i++) {
reactify(arguments[i])
}
//调用原来的方法
let res = Array.prototype[method].apply(this, arguments)
return res
}
})
//修改reactify方法,value.__proto__ = array_method
//将对象o响应式化
function reactify (o) {
let keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i] //属性名
let value = o[key]
//判断这个属性是不是引用类型,判断是不是数组
//如果是引用类型就需要递归,如果不是就不用递归
//如果是数组就需要循环数组,然后将数组里面的元素进行响应式化
if (Array.isArray(value)) {
//数组类型
value.__proto__ = array_method //数组就响应式了
for (let j = 0; j < value.length; j++) {
reactify(value[j]) //递归
}
} else {
//对象或值类型
defineReactive(o, key, value, true)
}
}
}
二、数据代理
我们在使用Vue的时候,获得属性。赋值属性都是直接使用的Vue实例
//像Vue中new一个Vue实例
const app = new vue({
data: {
name: 'feifei'
}
})
//访问name属性
app.name 也可以是 app._data.name
代理方法就是要将app._data中的成员给映射到app上
由于需要在更新数据的时候,更新页面的内容,所以app._data访问的成员与app访问的成员应该是同一成员。
由于app._data 已经是响应式的对象了,所以只需要让app访问的成员去访问app._data的对应成员就可以了。
//将_data上的属性代理到实例上
let keys = Object.keys(this._data)
//数据代理
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)
//数据代理
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', 属性名)