面试题之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后速度会有提高,还支持检测属性的新增和删除,以及监听数组,后续学习后更新文章进行记录。

猜你喜欢

转载自blog.csdn.net/xia_zai_ya/article/details/130154547
今日推荐