当前位置:首页 » 《随便一记》 » 正文

Vue原理面试题_米儿的博客

26 人参与  2022年02月25日 08:02  分类 : 《随便一记》  评论

点击全文阅读


一、大厂必考原理

1.组件化和MVVM

2.响应式原理

3.vdom和diff算法

4.模板编译

5.组件渲染过程

6.前端路由

1.组件化基础=>(MVVM模型)

 传统组件,知识静态渲染,更新依赖于操作DOM

数据驱动视图 - Vue MVVM

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化的时候通知viewModel层更新数据


2.Vue响应式原理的实现

组件data的数据一旦改变,立马触发视图的更新。

核心API -- Object.defineProperty

Object.defineProperty有缺点(Vue3启用Proxy)

Proxy的兼容性不太好,且无法使用polyfill

Object.defineProperty基本用法

 Object.defineProperty实现响应式

  • 监听对象,监听数组
  • 复杂对象,深度监听

 Object.defineProperty的缺点

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

总结;

1.核心API -- Object.defineProperty

2.如何监听对象(深度监听),如何监听数组

3.缺点

// 触发更新视图
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(newValue)

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue

                // 触发更新视图
                updateView()
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    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.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) // 监听数组

3.虚拟DOM(vdom)和diff算法

  • DOM操作非常耗费性能
  • 以前用jQuery,可以自行控制DOM操作时机,手动调整
  • vue和react都是数据驱动试图,如何有效控制DOM操作?

解决方案——vdom

  • 有一定的复杂度,想减少计算次数比较难
  • 难不能把计算,更多的转移为JS计算?因为JS执行比较快
  • vdom——用JS模拟DOM结构,计算出最小的变更,操作DOM

面试题:用JS模拟DOM元素

包含三部分:标签tag,附着在标签上的属性、样式、事件props,子元素children

通过snabbdom 学习vdom

  • vue3重写了vdom的代码,优化了性能
  • 但vdom的理念不变,面试考点不变

h函数、vnode数据结构、patch函数

vdom总结

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

diff算法

两个数做diff,如这里的vdom diff

 vnode   ->patch ->new vnode

树diff的时间复杂度O(n^3)

  • 第一,遍历tree1;第二,遍历tree2
  • 第三,排序
  • 1000个节点,要计算1亿次,算法不可用

优化时间复杂度到O(n)

  • 只比较同一层级,不跨级比较
  • tag不相同,直接删掉重建,不再深度比较
  • tag和key,两者都相同,则认为是相同的节点,不再深度比较

diff算法总结

  • patchVnode
  • addVnodes removeVnodes
  • updateChildren(key的重要性)

vdom和diff总结

  • 细节不重要,updateChildren更新过程也不重要,不要深究
  • vnode核心概念很重要:h vnode patch diff key 等
  • vnode的存在价值更重要:数据驱动试图,控制DOM操作

4.模板编译

 

with语法 

 

模板编译

总结

 

vue中使用render代替template 

总结

 

 5.组件渲染更新过程

vue原理的三大知识点

组件渲染/更新过程 

初次渲染过程

第二步是因为,执行render函数会触发getter操作 

更新过程

触发setter,看是修改的data是否在getter中已经被监听,如果是,就执行render函数

patch的diff算法,会计算出最小差异,更新在DOM上 

完整流程图

模板编译完,生成render函数,执行render函数生成vnode (虚拟DOM的树)

执行render函数的时候会touch getter,即执行函数的时候回触发Data里的getter

触发的时候就会收集依赖,即在模板中出发了哪个变量的getter就会把哪个给观察起来(watcher)

在修改Data的时候,看这个Data是否是之前作为依赖被观察起来的

如果是,就重新出发re-render,重新渲染,重新生成vdom tree,重新touch

异步渲染

1.$nextTick:

vue是异步渲染,$nextTick会待Dom渲染完之后调用

页面渲染时会将data的修改做整合,多次data修改只会渲染一次

2.汇总data的修改,一次性更新试图

3.减少DOM操作次数,提高性能

6.前端路由原理

网页url组成部分

 hash的特点

<!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>hash test</title>
</head>
<body>
    <p>hash test</p>
    <button id="btn1">修改 hash</button>

    <script>
        // hash 变化,包括:
        // a. JS 修改 url
        // b. 手动修改 url 的 hash
        // c. 浏览器前进、后退
        window.onhashchange = (event) => {
            console.log('old url', event.oldURL)
            console.log('new url', event.newURL)

            console.log('hash:', location.hash)
        }

        // 页面初次加载,获取 hash
        document.addEventListener('DOMContentLoaded', () => {
            console.log('hash:', location.hash)
        })

        // JS 修改 url
        document.getElementById('btn1').addEventListener('click', () => {
            location.href = '#/user'
        })
    </script>
</body>
</html>

H5 history

  • 用url规范的路由,但跳转时不刷新页面
  • history.pushState
  • window.onpopstate

<!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>history API test</title>
</head>
<body>
    <p>history API test</p>
    <button id="btn1">修改 url</button>

    <script>
        // 页面初次加载,获取 path
        document.addEventListener('DOMContentLoaded', () => {
            console.log('load', location.pathname)
        })

        // 打开一个新的路由
        // 【注意】用 pushState 方式,浏览器不会刷新页面
        document.getElementById('btn1').addEventListener('click', () => {
            const state = { name: 'page1' }
            console.log('切换路由到', 'page1')
            history.pushState(state, '', 'page1') // 重要!!
        })

        // 监听浏览器前进、后退
        window.onpopstate = (event) => { // 重要!!
            console.log('onpopstate', event.state, location.pathname)
        }

        // 需要 server 端配合,可参考
        // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
    </script>
</body>
</html>

 总结

 两者选择

 

to c的要是不需要管seo、搜索引擎也不需要用H5 history 简单一点就好


点击全文阅读


本文链接:http://zhangshiyu.com/post/35308.html

监听  渲染  更新  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1