前端面试-vue(系列一)响应系统

56 阅读3分钟

本文只针对vue3的设计与实现进行展开。后续称vue

vue响应系统

响应式系统的实现

首先需要先介绍一下副作用函数。

// 什么是副作用函数?
function effect() {
    document.body.innerHTML = 'hello world';
}

// effect的执行,会改变body的内容,除了effect函数,别的函数也可以改变body的内容。也就是说,effect的执行,会直接或者间接影响别的函数的执行结果,所以effect函数就是副作用函数。


// 影响全局的副作用函数
var a = 'hello world';

function effect1() {
    a = 'hi world';
}

响应式实现的目标是:当数据发生改变的时候,页面会展示最新的结果。下面的例子并不会有这样的效果。effect仅执行了一次。

const obj = {
    text: 'hello world'
}

function effect() {
    document.body.innerHTML = obj.text
}

effect()

// 此时改变text的值的时候,页面展示的内容并没有发生改变
// 响应式需要的就是当对象属性值发生改变的时候,重新触发副作用函数
obj.text = 'hi world'

此时我们vue利用proxy实现对obj的代理。

const obj = {
    text: 'hello world'
}
const bucket = new Set() // 用于存储副作用函数
const proxy = new Proxy(obj, {
    get(target,key) {
        bucket.add(effect) // 当对象有读操作的时候,把副作用函数添加到bucket中。
        return target[key]
    },
    set(target,key,value) {
        target[key] = value
        bucket && bucket.forEach(fn => fn()) // 当对象有取操作的时候,将bucket里的函数逐一执行。
        return true
    }
})


// 副作用函数
function effect() {
    document.body.innerHTML = proxy.text
}

effect()

proxy.text = 'hi world'

// 此时页面就会展示 hi world

此时effect仅是一个函数,可以进行一下优化,当effect接受的是一个匿名函数也能执行,看下面代码:

const obj = {
    text: 'hello world',
}
const bucket = new Set()
const proxy = new Proxy(obj, {
    get(target,key) {
        bucket.add(activeEffect) // 此时activeEffect函数就是副作用函数要执行的函数。
        return target[key]
    },
    set(target,key,value) {
        target[key] = value
        bucket && bucket.forEach(fn => fn())
        return true
    }
})


// 注册副作用函数
let activeEffect
function effect(fn) {
    activeEffect = fn
    fn()
}

effect(() => {
    document.body.innerHTML = proxy.text
})

proxy.text = 'hi world'

以上的内容,就可以称为一个简易的响应式系统,但是距离一个完善的响应式系统,还有一些距离。

首先是,副作用函数和目标数据的字段没有明确的联系。当设置目标数据的任意字段值,都会触发同样的副作用函数。

一个完善的响应式系统,是需要建立副作用函数与被操纵字段的联系的。如下所示:

target->key1->effect1

      ->key2->effect2
      
      ......->......

具体实现思路如下:


const obj = {
    text: 'hello world',
    ok: true,
}

// 副作用函数与对象属性的关联关系: target -> key -> effect
const bucket = new WeakMap()
const proxy = new Proxy(obj, {
    get(target,key) {
        let despMap = bucket.get(target)
        if(!despMap) {
            bucket.set(target,(despMap = new Map()))
        }
        let deps = despMap.get(key)
        if(!deps) {
            despMap.set(key,(deps = new Set()))
        }
        deps.add(activeEffect)
        return target[key]
    },
    set(target,key,value) {
        target[key] = value
        let despMap = bucket.get(target)
        if(!despMap) return
        let effects = despMap.get(key)
        effects && effects.forEach(fn => fn())
        return true
    }
})


// 注册副作用函数
let activeEffect
function effect(fn) {
    activeEffect = fn
    fn()
}

effect(() => {
    console.log('执行了副作用函数');
    document.body.innerHTML = obj.ok ? obj.text : '123'
})

obj.text = 1

proxy.text = 'hi world'

上面代码我们分别使用了weakMapMapSet三种数据结构

其中: weakMap是由target->>>Map构成

Map是由key->>>Set构成。

以上便是一个较为完善的响应式系统的实现,也是vue响应式设计的思路。