一道关于Vue的数据绑定和依赖收集的面试题

7 篇文章 0 订阅
6 篇文章 0 订阅

概要

分享一道Vue的面试题,该题涉及Vue的响应式数据绑定和依赖收集,希望可以加深大家对Vue原理的理解。

题面

有如下html和JS,要求使用Vue的响应式数据原理和依赖收集原理,实现createApps和ref方法。只能使用原生JS,不得使用已有的Vue2.0或3.0框架。

  1. 要求实现响应式数据title和description与DOM节点的数据绑定;
  2. 要求可以通过changeTitle和changeContent,更新响应式数据,绑定的DOM可以随着响应式数据的更新而自动更新内容。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h1>{{title}}</h1>
        <span>{{description}}</span>
        <h1>{{title}}</h1>
        <span>{{description}}</span>
        <p>
            <button @click="changeTitle">Change Title</button>
            <button @click="changeContent">Change Description</button>
        </p>
    </div>
</body>
createApps({
    refs: {
        title: ref('default title'),
        description: ref('default description')
    },
    methods:{
        changeTitle(){
            this.title.value = "new title";
        },
        changeContent(){
            this.description.value = "new description";
        }
    }
});

解决方法

思路分析

首先我们先分析createApps的参数,该对象包含两个代码段:

  • refs段中包含所有需要转换成响应式数据的内容,需要通过调用ref方法,实现数据换换;
  • methods段中包含两个方法,我们需要将该方法通过click事件真正绑定到DOM元素上,该方法的this不再指向DOM节点,而是指向当前的响应数据。

基于上面的分析,createApps方法应该包含以下功能:

  1. 为title和description生成响应式数据;
  2. 将响应式数据绑定到指定的DOM元素上;
  3. 为DOM元素绑定Click事件处理函数,并更改函数中this的指向。

代码实现

ref方法,根据指定数据生成响应式数据:

function ref(content){
    const refObject = {
        _value: content,
        deps: new Set()
    };
    Object.defineProperty(refObject, "value", {
        get: () => refObject._value,
        set: (newValue) => {
            refObject._value = newValue;
        }
    });
    return refObject;
}
  1. 根据题目要求,通过value属性可以获取和修改响应式数据的值,所以通过Object.defineProperty方法,为value属性设置getter和setter;
  2. deps为响应式数据的依赖收集的DOM节点,初始为一个空Set。

createRefs方法用于实现响应式数据的依赖收集

function createRefs(options, nodes){   
    const reg = /\{\{\s*(?<refKey>.+?)\s*\}\}/;
    [...nodes].forEach(el => {
        if (reg.test(el.textContent)){
            const {refKey} = el.textContent.match(reg).groups;
            options.refs[refKey].deps.add(el);
        }
    });
    return options.refs;
}
  1. 该方法包含两个参数:options是题面传入的参数,nodes是题面中app节点先的所有DOM元素;
  2. 通过正则表达式找到包含{{}}的DOM节点,通过大括号内数据内容,从options.refs中找到对应的响应式数据;
  3. 将包含{{}}的DOM节点放入响应式数据的deps中,作为依赖收集项;
  4. 返回当前app的所有响应式数据。

render方法根据已经生成的响应式数据和依赖收集的DOM节点信息,更新DOM元数,实现数据绑定。

function render(refs){
    const keys = Object.keys(refs);
    keys.forEach(key => {
        _render(refs[key]);
    });
}
function _render({deps, value}){
    deps.forEach(node => {
        node.textContent = value;
    });
}
function update({deps, value}){
    _render({deps, value});
}

render方法的参数是当前app的全部响应式数据对象,数据结构和内容如下所示:
在这里插入图片描述

  1. 获取上述对象的所有key值,然后进行遍历;
  2. 调用_render,传入当前响应式数据,更新DOM元素;
  3. update方法作为_render方法的别名, 添加到ref方法中,实现数据到UI的联动更新。
function ref(content){
    const refObject = {
        _value: content,
        deps: new Set()
    };
    Object.defineProperty(refObject, "value", {
        get: () => refObject._value,
        set: (newValue) => {
            refObject._value = newValue;
            update(refObject);
        }
    });
    return refObject;
}

bindEvent方法将options参数中题目指定的DOM事件和方法进行绑定

function bindEvent(options, nodes){
    [...nodes].forEach(el => {
        let methodKey = el.getAttribute("@click");
        if (!methodKey){
            return;
        }
        methodKey = methodKey.trim();
        el.addEventListener("click", function(e){
            options.methods[methodKey].apply(options.refs,[e]);
        }, false);
    });
}
  1. 该方法包含两个参数:options是题面传入的参数,nodes是题面中app节点先的所有HTML元素;
  2. 遍历所有app下的节点,找到包含@click属性的节点;
  3. 将click事件绑定到题目指定的DOM上;
  4. 将事件的处理函数中的this强制指定为当前app的响应式数据。

createApps代码实现如下:

function createApps(options){
    const root = document.querySelector("#app");
    const nodes = root.querySelectorAll("*");
    const refs = createRefs(options, nodes);
    render(refs);
    bindEvent(options, nodes);
}
  1. 获取app的所有DOM节点;
  2. 调用createRefs生成响应式数据;
  3. 利用生成的响应式数据,调用render方法,渲染页面;
  4. 绑定题目指定的Click事件处理函数。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值