一、响应式原理
什么是响应式原理:
意思就是在改变数据的时候,视图会跟着更新。
实现响应式原理的核心:Object.defineProperty
Object.defineProperty( 对象, '设置什么属性名', {
writeable
configurable
enumerable: 控制属性是否可枚举, 是不是可以被 for-in 取出来
set() {} 赋值触发
get() {} 取值触发
} )
二、对象响应式化
<script>
let o = {
name: 'jim',
age: 19,
gender: '男'
}
// 简化后的版本
function defineReactive(target, key, value, enumerable) {
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get() {
console.log(`读取 o 的 ${key} 属性`); // 额外
return value
},
set(newVal) {
console.log(`设置 o 的 ${key} 属性为: ${newVal}`); // 额外
value = newVal
}
})
}
let keys = Object.keys(o)
keys.forEach(key => {
defineReactive(o, key, o[key], true)
})
</script>
三、对象数组响应式化
<script>
let data = {
name: '张三',
age: 19,
course: [
{ name: '语文' },
{ name: '数学' },
{ name: '英语' },
]
}
// 简化后的数据响应函数
function defineReactive(target, key, value, enumerable) {
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
if (typeof value === 'object' && value != null && !Array.isArray(value)) {
// 是非数组的引用类型
reactify(value); // 递归
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get() {
console.log(`读取 o 的 ${key} 属性`); // 额外
return value
},
set(newVal) {
console.log(`设置 o 的 ${key} 属性为: ${newVal}`); // 额外
value = newVal
}
})
}
// 将对象 o 响应式化
function reactify(o) {
keys = Object.keys(o)
for (let i = 0; i < keys.length; i++) {
let key = keys[i]
let value = o[key]
// 判断这个属性是不是引用类型, 判断是不是数组
// 如果引用类型就需要递归, 如果不是就不用递归
// 如果不是引用类型, 需要使用 defineReactive 将其变成响应式的
// 如果是引用类型, 还是需要调用 defineReactive 将其变成响应式的
// 如果是数组呢? 就需要循数组, 然后将数组里面的元素进行响应式化
if (Array.isArray(value)) {
for (let j = 0; j < value.length; j++) {
reactify(value[j])
}
} else {
// 对象或值类型
defineReactive(o, key, value, true);
}
}
}
reactify(data)
</script>
四、拦截数组方法
在改变数组时,加入的元素也要响应式化,所以要对数组方法进行重写,采用的方式是拦截数组的方法。
拦截数组方法:
<script>
let ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice',
];
// 思路, 原型式继承: 修改原型链的结构
let arr = [];
// 继承关系: arr -> Array.prototype -> Object.prototype -> ...
// 继承关系: arr -> 改写的方法 -> Array.prototype -> Object.prototype -> ...
let array_methods = Object.create(Array.prototype)
ARRAY_METHOD.forEach(method => {
array_methods[method] = function () {
// 调用原来的方法
console.log('调用的是拦截的 ' + method + ' 方法');
// 将数据进行响应式化
let res = Array.prototype[method].apply(this, arguments)
return res
}
})
arr.__proto__ = array_methods
</script>
五、完整的对象和数组响应式化代码
<script>
let data = {
name: '张三',
age: 19,
course: [
{ name: '语文' },
{ name: '数学' },
{ name: '英语' },
]
}; // 除了递归还可以使用队列 ( 深度优先转换为广度优先 )
let ARRAY_METHOD = [
'push',
'pop',
'shift',
'unshift',
'reverse',
'sort',
'splice',
];
let array_methods = Object.create(Array.prototype);
ARRAY_METHOD.forEach(method => {
array_methods[method] = function () {
// 调用原来的方法
console.log('调用的是拦截的 ' + method + ' 方法');
// 将数据进行响应式化
for (let i = 0; i < arguments.length; i++) {
reactify(arguments[i]);
}
let res = Array.prototype[method].apply(this, arguments);
// Array.prototype[ method ].call( this, ...arguments ); // 类比
return res;
}
});
// 简化后的版本
function defineReactive(target, key, value, enumerable) {
// 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
if (typeof value === 'object' && value != null && !Array.isArray(value)) {
// 是非数组的引用类型
reactify(value); // 递归
}
Object.defineProperty(target, key, {
configurable: true,
enumerable: !!enumerable,
get() {
console.log(`读取 ${key} 属性`); // 额外
return value;
},
set(newVal) {
console.log(`设置 ${key} 属性为: ${newVal}`); // 额外
value = newVal;
}
});
}
// 将对象 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_methods; // 数组就响应式了
for (let j = 0; j < value.length; j++) {
reactify(value[j]); // 递归
}
} else {
// 对象或值类型
defineReactive(o, key, value, true);
}
}
}
reactify(data);
</script>