• 作者:老汪软件技巧
  • 发表时间:2024-09-06 15:01
  • 浏览量:

相信最近秋招大家都在疯狂投简历,面试吧!不知道各位大佬们面试怎么样,不过我觉得面试的内容都大差不差。在面试的时候面试官就会问到很多的底层的内容,什么东西都会问出来。37℃的嘴怎么问出这么寒心的题目,哈哈哈。今天就给大家来讲一下vue2和vue3的响应式区别,因为这道题目在面试这么后对这个题目还是很深刻的,因为我并没有系统的学习过vue2,所以在回答时也是纯纯靠着前辈们的面试宝典来回答的,心虚的很。所以还是得花点时间来学习一下,这样回答出来才更自信。

响应式

vue中所谓的响应式就是指 Vue 能够自动追踪数据的变化,并在数据发生变化时更新视图的过程。在vue2中,数据是存储在date对象中的,Vue 会将data对象中的所有属性转换成响应式的属性。而在vue3中则是引入一个新的响应式API,如reactive和ref函数。

vue2中响应式原理

在vue2中实现响应式核心就是通过 Object.defineProperty() 这个函数来实现数据劫持,它包含三个参数,分别是需要拦截的对象,需要拦截的属性以及对新属性的描述或者操作。如:

Object.defineProperty(target,key,{
        get(){
            return value
        },
        set(newVal){
            if(newVal !== value){
                value = newVal
                // 通知更新视图
                updateView()
            }
        }
    })

当使用这个函数把需要拦截下来的对象进行拦截的时候,如果在get方法内不进行任何操作,我们是拿不到这个数据的,同理,修改这个参数也是如此。这个就很像是java中的private私有属性,如果将其如果设置get,set方法,在其他的类中就获取不到对应的值。

为了便于理解,我们可以把这个想象成我们家中的一家之主,我们想出去干啥都需要经过她的同意,不然就搓衣板伺候了。

而这个函数只能够实现一个数据的拦截修改,要想实现vue2的响应式,就需要遍历拦截整个data对象的属性。在vue2响应式的原码中就通过设置一个观察者,更好的实现整个data对象中属性的拦截。使用的是for in来遍历整个对象。如下:

function observe(target){
    for(let key in target){
        defineReactive(target,key,target[key])
    }
}

但是Object.defineProperty()对数组还是有一定的局限性的,主要原因是原生Object.defineProperty() 无法拦截数组的一些特定方法,如 push, splice, unshift 等,这些方法会直接改变数组的内容,但不会触发数据劫持机制,另外数组通过索引赋值也不能触发数据劫持机制。这时候我们的尤大大也是二话不说,既然原始的数组不能拦截,那就不要这个,自己再造一个数组出来。于是尤大直接重写了数组上的所有方法,解决了这个问题。具体的操作就是这样。

_响应式原理vue_vue响应式源码解析

let oldArrayPrototype = Array.prototype
let proto = Object.create(oldArrayPrototype)
Array.from(['push','shift','pop']).forEach((method)=>{
    //函数劫持,重写函数
    proto[method] = function(){
        oldArrayPrototype[method].call(this,...arguments)
        updateView()
    }
})
//观察者
function observe(target){
    // 如果是数组,强行把数组的原型指向自己
    if(Array.isArray(target)){
        target.__proto__ = proto
        return
    }
    for(let key in target){
        defineReactive(target,key,target[key])
    }
}

另外,如果data的属性中还有对象是,我们就需要递归处理。因为数据响应式是当我们的数据发生变化时,就需要,动态修改数据,更新视图。但是到目前为止我们还不能深层的拦截数据。也就是说,当对象中还有对象时,里层对象的属性发生改变,就不会触发数据劫持。所以我们就需要递归我们劫持到的对象属性,判断其是不是对象。可以直接通过

if(typeof value === 'object' && value!== null){
        observe(value)
    }

这个来判断是不是对象,如果是的话就递归的再来一遍。

最后尤大大还发现了一个问题,就是如果我们需要再对象中新添加一个属性,这样也不会触发数据劫持,于是他就新打造了一个$.set方法,目的就是解决添加属性时不会触发视图更新的这一缺点。

经过这一系列的操作,这个响应式总算是可以了。也是很不容易的!

简易完整源码

let obj = {
    a:1,
    b:2,
    c:{
        n:4
    },
    d: [5,6,7]
}
let oldArrayPrototype = Array.prototype
let proto = Object.create(oldArrayPrototype)
Array.from(['push','shift','pop']).forEach((method)=>{
    //函数劫持,重写函数
    proto[method] = function(){
        oldArrayPrototype[method].call(this,...arguments)
        updateView()
    }
})
//观察者
function observe(target){
    // 强行把数组的原型指向自己
    if(Array.isArray(target)){
        target.__proto__ = proto
        return
    }
    for(let key in target){
        defineReactive(target,key,target[key])
    }
}
function defineReactive(target,key,value){
    if(typeof value === 'object' && value!== null){
        observe(value)
    }
    Object.defineProperty(target,key,{
        get(){
            return value
        },
        set(newVal){
            if(newVal !== value){
                value = newVal
                // 通知更新视图
                updateView()
            }
             
        }
    })
}
observe(obj)
//obj.b =100
// obj.c.n = 100
obj.d.push(100)
console.log(obj.d);
function updateView(){
    console.log('更新视图');
}

总结

从这个简易版的vue 2 的响应式源码中,我们其实可以发现这响应式中还是有一些缺陷的:

不能深度劫持,需要进行递归劫持对象的多层属性。且这个递归还是默认递归,不管用到没用到都会进行递归,性能开销较大。不过在后续的vue2新版本中好像解决了这个问题,只有用到了这个属性才会递归。重写了数组中的所有方法,否则不能对数组进行操作直接修改数组的length属性时是没有效果的对象上不存在的属性无法劫持,打造了个$.set方法

emmm,当我学习了vue2的响应式原码我,我的想法就是,得亏了尤大缝缝补补,不然我们就需要噼里啪啦写一大堆了。下次我们介绍一下vue3的响应式原理,它比vue2更简洁,更优美。