• web前端面试高频考点——Vue原理(理解MVVM模型、深度/监听data变化、监听数组变化、深入了解虚拟DOM)


    系列文章目录



    一、Vue原理

    1、理解MVVM模型

    组件化

    • 组件化就是将一个页面拆分成一个个小的功能模块
    • 每个功能模块完全属于自己这部分独立的功能
    • 使得整个页面的管理和维护变得非常容易。
    • asp jsp php 已经有组件化了
    • nodejs 中也有类似的组件化

    数据驱动视图

    • 传统组件,只是静态渲染,更新还要依赖于操作 DOM
    • 数据驱动视图 - Vue MVVM
    • 数据驱动视图 - React setState

    MVVM 模型
    在这里插入图片描述

    2、监听 data 变化的核心 API

    • 组件 data 的数据一旦变化,立刻触发视图的更新
    • 实现数据驱动视图的第一步
    • 核心 API - Object.defineProperty
    • 由于 Object.defineProperty 有一些缺点,Vue3.0 使用 Proxy
    • 但 Proxy 兼容性不好,且无法 polyfill

    Object.defineProperty() 基本用法

    示例:基本用法

    const data = {}
    const name = 'zhangsan'
    
    Object.defineProperty(data, "name", {
        get: function () {
            console.log('get')
        },
        set: function (newVal) {
            console.log('set')
            name = newVal
        }
    })
    
    console.log(data.name) // get zhangsan
    data.name = 'lisi' // set
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    3、vue如何深度监听 data 变化

    Object.defineProperty 缺点

    • 深度监听,需要递归到底,一次性计算量大
    • 无法监听新增属性/删除属性(Vue.set、Vue.delete)

    示例:视图更新三次,新增和删除属性监听不到

    // 触发更新视图
    function updateView() {
        console.log('视图更新');
    }
    
    // 重新定义属性,监听起来
    function defineReactive(target, key, value) {
        // 深度监听
        observer(value)
        // 核心 API
        Object.defineProperty(target, key, {
            get() {
                return value
            },
            set(newValue) {
                if(newValue !== value) {
                    // 深度监听
                    observer(value)
    
                    // 设置新值
                    // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                    value = newValue
    
                    // 触发更新视图
                    updateView()
                }
            }
        })
    }
    
    // 监听对象属性
    function observer(target) {
        if (typeof target !== 'object' || target === null) {
            // 不是对象或数组
            return target
        }
    
        // 重新定义各个属性(for in 也可以遍历数组)
        for (let key in target) {
            defineReactive(target, key, target[key])
        }
    }
    
    // 准备数据
    const data = {
        name: 'zhangsan',
        age: 20,
        info: {
            address: '北京' // 需要深度监听
        }
    }
    
    // 监听数据
    observer(data)
    
    // 测试
    data.name = 'lisi'
    data.age = 21
    // console.log('age', data.age)
    data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
    delete data.name // 删除属性,监听不到 —— 所以有 Vue.delete
    data.info.address = '上海' // 深度监听
    // data.nums.push(4) // 监听数组
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    在这里插入图片描述

    4、vue如何监听数组变化

    Object.defineProperty 缺点

    • 深度监听,需要递归到底,一次性计算量大
    • 无法监听新增属性/删除属性(Vue.set、Vue.delete)
    • 无法原生监听数组,需要特殊处理

    示例:

    // 触发更新视图
    function updateView() {
        console.log('视图更新');
    }
    
    // 重新定义数组原型
    const oldArrayProperty = Array.prototype
    
    // 创建新对象,原型指向 oldArrayProperty,再扩展新的方法不会影响原型
    const arrProto = Object.create(oldArrayProperty);
    ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
        arrProto[methodName] = function () {
            updateView() // 触发视图更新
            oldArrayProperty[methodName].call(this, ...arguments)
            // 相当于 Array.prototype.push.call(this, ...arguments)
        }
    })
    
    // 重新定义属性,监听起来
    function defineReactive(target, key, value) {
        // 深度监听
        observer(value)
        // 核心 API
        Object.defineProperty(target, key, {
            get() {
                return value
            },
            set(newValue) {
                if(newValue !== value) {
                    // 深度监听
                    observer(value)
    
                    // 设置新值
                    // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                    value = newValue
    
                    // 触发更新视图
                    updateView()
                }
            }
        })
    }
    
    // 监听对象属性
    function observer(target) {
        if (typeof target !== 'object' || target === null) {
            // 不是对象或数组
            return target
        }
    
        // 如果是数组,就把 arrProto 赋值给数组的原型
        if (Array.isArray(target)) {
            target.__proto__ = arrProto
        }
    
        // 重新定义各个属性(for in 也可以遍历数组)
        for (let key in target) {
            defineReactive(target, key, target[key])
        }
    }
    
    // 准备数据
    const data = {
        name: 'zhangsan',
        age: 20,
        info: {
            address: '北京' // 需要深度监听
        },
        nums: [10, 20, 30]
    }
    
    // 监听数据
    observer(data)
    
    // 测试 - 监听数组
    data.nums.push(4) // 监听数组
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    在这里插入图片描述

    5、虚拟 DOM

    • vdom 是实现 vue 和 react 的重要基石
    • diff 算法是 vdom 中最核心、最关键的部分
    • DOM 操作非常耗费性能
    • 以前用 jQuery,可以自行控制 DOM 操作的时机,手动调整
    • Vue 和 React 是数据驱动视图,使用 VDOM
    (1)解决方案:VDOM
    • 有了一定复杂度,想减少计算次数比较难
    • 能不能把计算,更多的转移为 JS 计算?因为 JS 执行速度很快
    • vdom - 用 JS 模拟 DOM 结构,计算出最小的变更,操作 DOM
    (2)用 JS 模拟 DOM 结构

    示例:JS 模拟 DOM 结构

    HTML 代码

    <div id="div1" class="container">
        <p>vdom</p>
        <ul style="font-size: 20px">
            <li>a</li>
        </ul>
    </div>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    JS 代码

    {
        tag: 'div',
        props: {
            className: 'container',
            id: 'div1'
        }
        children: [
            {
                tag: 'p',
                children: 'vdom'
            },
            {
                tag: 'ul',
                props: {style: 'font-size: 20px'}
                children: [
                    {
                        tag: 'li',
                        children: 'a'
                    }
                ]
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    (3)通过 snabbdom 学习 vdom

    示例:

    <div id="container"></div>
    <button id="btn-change">change</button>
    
    <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
    
    
    const snabbdom = window.snabbdom
    
    // 定义 patch
    const patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners
    ])
    
    // 定义 h
    const h = snabbdom.h
    
    const container = document.getElementById('container')
    
    // 生成 vnode
    const vnode = h('ul#list', {}, [
        h('li.item', {}, 'Item 1'),
        h('li.item', {}, 'Item 2')
    ])
    patch(container, vnode)
    
    document.getElementById('btn-change').addEventListener('click', () => {
        // 生成 newVnode
        const newVnode = h('ul#list', {}, [
            h('li.item', {}, 'Item 1'),
            h('li.item', {}, 'Item B'),
            h('li.item', {}, 'Item 3')
        ])
        patch(vnode, newVnode)
    
        // vnode = newVnode // patch 之后,应该用新的覆盖现有的 vnode ,否则每次 change 都是新旧对比
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    改变前:

    在这里插入图片描述
    改变后:
    在这里插入图片描述

    (4)vdom 总结
    • 用 JS 模拟 DOM 结构(vnode)
    • 新旧 vnode 对比,得出最小的更新范围,最后更新 DOM
    • 数据驱动视图的模式下,有效控制 DOM 操作

    不积跬步无以至千里 不积小流无以成江海

    点个关注不迷路,持续更新中…

  • 相关阅读:
    RISC-V 特权级架构
    新能源汽车展厅用哪些种类的显示屏比较好?
    SCM供应链管理系统解决方案:助力企业采购流程高效运行,全面降低供应链风险
    [⑥5G NR]: 无线接口协议,信道映射学习
    Kubernetes 部署 kubeflow1.6.1
    人大金仓分析型数据库外部表(二)
    计算机网络 —— 网络字节序
    Golang源码分析:本地缓存库cache2go
    节点属性、offset*获取元素尺寸、offsetLeft获取元素定位
    灵性图书馆:好书推荐-《奥修传》
  • 原文地址:https://blog.csdn.net/qq_45902692/article/details/126516912