文章目录
- 前言
- 一、响应式是什么?
- 二、Object.defineProperty
- 二、简单模拟vue
- 三、深度监听
- 四、监听数组
- 总结
前言
为了应对面试而进行的学习记录,可能不够有深度甚至有错误,还请各位谅解,并不吝赐教,共同进步。
一、响应式是什么?
我个人的简单理解就是数据在发生变化时候,能自动的执行某些我们需要的操作行为。在vue
中就是我们首次将数据渲染到页面中后,如果我们后面在修改数据,页面中也会自动进行更新,很明显,vue
帮我们完成了响应式。
我们先来讲述vue2的响应式实现。
后面在讲述vue3,因为他们实现的原理有区别。
二、Object.defineProperty
首先我们需要先了解Object.defineProperty()
,这个方法可以帮助我们来劫持对象上面的属性,是实现响应式的核心内容。
我们先来基本使用用下Object.defineProperty()
。
const data = {};
let name = 'xiacan';
/**
* 第一个参数: 要监听的对象
* 第二个参数: 要监听的属性
* 第三个参数: 一个有get函数和set函数的对象
*/
Object.defineProperty(data,"name",{
get(){
console.log("name属性被获取啦!");
//返回值 就是我们data[name] 获取到的值
return name;
},
set(value) {
// value 就是 我们设置name属性时候赋值的值
console.log("name属性被赋新值了:",value);
name = value;
}
})
console.log(data.name);
data.name = 'xiazai';
console.log(data.name);
执行后的结果:
是不是很简单,我们已经完成了对对象属性的监听,当对象被使用和修改时候,我们已经可以做我们需要的操作了。
二、简单模拟vue
上面我们了解了怎么监听对象,我们来简单的模拟一下vue。
//模拟视图更新
function updateView(){
console.log("视图更新啦!");
}
// 具体进行数据监听的函数
function definePy(target, key, value){
Object.defineProperty(target, key, {
get() {
//直接返回当前值
return value;
},
set(newValue) {
//判断是否是修改为新值,否则不更新视图
if(newValue === value) return;
value = newValue;
//更新视图
updateView();
}
})
}
//监听数据函数
function observer(target){
//判断如果是基本类型就不监听
if(typeof target !== 'object' || target === null ) return target;
//我们循环对象的所有属性
for(let key in target){
//监听每个属性 将对象 当前属性 当前属性值传入
definePy(target,key,target[key])
}
}
//初始数据
const data = {
name: 'xiacan',
age: 22
}
//开始监听数据
observer(data)
data.name = 'xiazai';
console.log(data.name);
运行截图:
我们这样就成功的简单模拟了vue的响应式。
但是我们还不够,不能对data进行深程度的监听。
三、深度监听
我们将初始数据层级加深:
//初始数据
const data = {
name: 'xiacan',
age: 22,
like:{
one:'xi'
}
}
//开始监听数据
observer(data)
data.like.one = 'ai';
运行一下:
很明显,并未检测到like对象属性
的变换。
我们进一步优化,能够进行深度监听数据
。
//模拟视图更新
function updateView(){
console.log("视图更新啦!");
}
// 具体进行数据监听的函数
function definePy(target, key, value){
// 进行深度循环
observer(target[key]);
Object.defineProperty(target, key, {
get() {
//直接返回当前值
return value;
},
set(newValue) {
//判断是否是修改为新值,否则不更新视图
if(newValue === value) return;
// 进行深度循环,再有新值时候
observer(newValue);
value = newValue;
//更新视图
updateView();
}
})
}
//监听数据函数
function observer(target){
//判断如果是基本类型就不监听 同时返回
if(typeof target !== 'object' || target === null ) return target;
//我们循环对象的所有属性
for(let key in target){
//监听每个属性 将对象 当前属性 当前属性值传入
definePy(target,key,target[key]);
}
}
//初始数据
const data = {
name: 'xiacan',
age: 22,
like:{
one:'xi',
two:'22'
}
}
//开始监听数据
observer(data)
data.like.one = 'ai';
data.like.two = {
a:1
};
data.like.two.a = 2;
运行一下:
我们需要在开始劫持data属性前,将当前属性在进行一次监听就可以实现深度监听了,因为前面我们已经写好了结束的条件就是如果是基本类型就不监听,同时返回,这样就不会死循环。
然后还需要在每次属性赋新值
时候也进行一次进行深度循环,因为可能赋值一个对象,这样我们才能监听变化。
我们还需要进行下一步的优化,就是实现对数组的监听
。
四、监听数组
我们先来看一看对数组进行监听:
//初始数据
const data = {
name: 'xiacan',
age: 22,
like:{
one:'xi',
two:'22'
},
num: [1, 2, 3]
}
//开始监听数据
observer(data)
data.num[0] = 11
data.num.push(4)
运行一下:
很显然,能监听到数组原来的数据变化,但是执行数组的方法时候未能监听。
我们要实现对数组的监听,我们需要修改一下数组的原型,在原型上定义方法,让我们来实现监听数组的变化。
//模拟视图更新
function updateView(){
console.log("视图更新啦!");
}
//创建一个新对象,原型执行数组的原型,但是不会干扰原来的原型
const newArr = Object.create(Array.prototype);
//数组方法太多了,我们选几个常见的修改
[ 'push', 'unshift', 'shift', 'pop' ].forEach(newFunName =>{
newArr[newFunName] = function () {
//更新视图
updateView();
//执行数组原型上原始的方法
Array.prototype[newFunName].call(this, ...arguments)
}
})
// 具体进行数据监听的函数
function definePy(target, key, value){
// 进行深度循环
observer(target[key]);
Object.defineProperty(target, key, {
get() {
//直接返回当前值
return value;
},
set(newValue) {
//判断是否是修改为新值,否则不更新视图
if(newValue === value) return;
// 进行深度循环,再有新值时候
observer(newValue);
value = newValue;
//更新视图
updateView();
}
})
}
//监听数据函数
function observer(target){
//判断如果是基本类型就不监听 同时返回避免死递归
if(typeof target !== 'object' || target === null ) return target;
//判断如果是数组,我们就修改原型为我们新建的
if (Array.isArray(target)){
target.__proto__ = newArr;
}
//我们循环对象的所有属性
for(let key in target){
//监听每个属性 将对象 当前属性 当前属性值传入
definePy(target,key,target[key]);
}
}
//初始数据
const data = {
name: 'xiacan',
age: 22,
like:{
one:'xi',
two:'22'
},
num: [1, 2, 3]
}
//开始监听数据
observer(data)
data.num.push(4);
data.num.pop();
运行一下:
我们干嘛要修改数组原型,因为我们不能污染全局的数组原型,所以我们就自己定义一个新的原型,然后在对数组的方法进行修改,在里面添加进入了更新视图的方法,这也是vue中监听数组的实现方法,然后我们在开始监听data数据时候,先进行判断,如果为数组,我们就将当前对象的原型改变为我们新建的原型。
总结
我们已经大致将vue实现响应式简单的模拟了一下了,其中我们可以发现对于对象新增属性和删除属性
我们是无法监听到的,所以vue中有其他的专门方法来完成。对于数组,我们就需要重新修改原型来实现监听。在vue2中对于深度监听是一次不断循环来完成的,如果数据层级多,速度会变得慢。vue3使用proxy后速度会有提高,还支持检测属性的新增和删除,以及监听数组,后续学习后更新文章进行记录。