web前端面试高频考点——Vue原理(diff算法、模板编译、组件渲染和更新、JS实现路由)

简介: web前端面试高频考点——Vue原理(diff算法、模板编译、组件渲染和更新、JS实现路由)

一、虚拟 DOM - diff 算法概述

diff 算法参考链接

  • diff 算法是 vdom 中最核心、最关键的部分
  • diff 算法能在日常使用 vue react 中体现出来(如 key)

diff 算法概述:

  • diff 即对比,是一个广泛的概念,如 linux diff 命令,git diff 命令
  • 两个 js 对象也可以做 diff
  • 两棵树做 diff,如这里的 vdom diff

出处:https://coding.imooc.com/lesson/419.html#mid=33875

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

  • 第一,遍历 tree1
  • 第二,遍历 tree2
  • 第三,排序
  • 100个节点,要计算100万次,算法不可用

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

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

出处:https://coding.imooc.com/lesson/419.html#mid=33875

二、模板编译

1、模板编译 - with 语法

  • 模板是 vue 开发中最常用的部分,即与使用相关联的原理
  • 它不是 html,有指令、插值、JS表达式,到底是什么?

模板编译:

  • 前置知识:JS 的 with 语法
  • vue template complier 将模板编译为 render 函数
  • 执行 render 函数生成 vnode

with 语法:

  • 改变 {} 内自由变量的查找规则,当作 obj 属性来查找
  • 如果找不到匹配的 obj 属性,就会报错
  • with 要慎用,它打破了作用域规则,易读性差

示例:不使用 with,对象里没有定义的属性为 undefined

const obj = {a: 100, b: 200}
console.log(obj.a) // 100
console.log(obj.b) // 200
console.log(obj.c) // undefined

使用 with,对象里没有定义的属性会报错

const obj = {a: 100, b: 200}
// 使用 with,能改变 {} 内自由变量的查找方式
// 将 {} 内自由变量,当作 obj 的属性来查找
with (obj) {
    console.log(a) // 100
    console.log(b) // 200
    console.log(c) // Uncaught ReferenceError: c is not defined
}

2、vue模板被编译成什么

模板编译:

  • 模板不是 html,有指令、插值、JS 表达式,能实现判断、循环
  • html 是标签语言,只有 JS 才能实现判断、循环
  • 因此,模板一定是转换为某种 JS 代码,即模板编译

示例:

(1)安装 vue-template-compiler

npm i vue-template-compiler --save

(2)创建 index.js 文件

const compiler = require('vue-template-compiler')
......
// 编译
const res = compiler.compile(template)
console.log(res.render)

(3)演示(以下演示均在 index.js 文件的 … 中)

插值:

  • 打印结果即:创建一个 p 标签,子元素是 TextVNode,为一个字符串
  • _c:createElement,创建元素
  • _v:createTextVNode,创建文本节点
  • _s:toString,转为字符串类型
  • this:const vm = new Vue({…}),即 vm 实例
// 插值
const template = `<p>{{message}}</p>`
// with(this){return _c('p',['p',[_v(_s(message))]])}
// h -> vnode
// createElement -> vnode

表达式:

  • template 里面的表达式就是被当成 JS 代码执行
// 表达式
const template = `<p>{{flag ? message : 'no message found'}}</p>`
//with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

属性和动态属性:

  • container,div1 是静态字符串,所以编译后需要用引号包裹
  • imgUrl 是动态属性,是个变量,所以编译后不用引号包裹
// 属性和动态属性
const template = `
     <div id="div1" class="container">
         <img :src="imgUrl"/>
     </div>
 `
// with(this){return _c('div',
//      {staticClass:"container",attrs:{"id":"div1"}},
//      [_c('img',{attrs:{"src":imgUrl}})]
// )}

条件:

  • 转换成一个三元表达式
// 条件
const template = `
    <div>
        <p v-if="flag === 'a'">A</p>
        <p v-else>B</p>
    </div>
`
// with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}

循环:

  • _l:renderList,渲染列表
  • 第二个 return,返回的第一个参数是 tag,第二个参数是属性,第三个参数是子元素
// 循环
const template = `
    <ul>
        <li v-for="item in list" :key="item.id">{{item.title}}</li>
    </ul>
`
// with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}

事件:

  • on 里面是事件,函数的名字是变量,没有引号包裹
// 事件
const template = `
    <button @click="clickHandler">submit</button>
`
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}

v-model:

// v-model
const template = `<input type="text" v-model="name">`
// 主要看 input 事件
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

编译模板:

  • 模板编译为 render 函数,执行 render 函数返回 vnode
  • 基于 vnode 再执行 patch 和 diff
  • 使用 webpack vue-loader,会在开发环境下编译模板

3、vue 组件中使用 render 代替 template

  • 有些复杂情况中,不能用 template,可以考虑用 render
  • react 一直都用 render(没有模板)
Vue.component('heading', {
    // template: `xxx`,
    render: function (createElement) {
        return createElement(
            'h' + this.level,
            [
                createElement('a', {
                    attrs: {
                        name: 'headerId',
                        href: '#' + 'headerId'
                    }
                }, 'this is a tag')
            ]
        )
    }
})

总结:

  • with 语法
  • 模板到 render 函数,再到 vnode,再到渲染和更新
  • vue 组件可以用 render 代替 template

4、总结组件 渲染/更新 过程

  • 一个组件渲染到页面,修改 data 触发更新(数据驱动视图)
  • 其背后原理是什么,需要掌握哪些要点?
  • 考察对流程了解的全面程度

三大知识点:

  • 响应式:监听 data 属性 getter setter
  • 模板编译:模板到 render 函数,再到 vnode
  • vdom:patch(elem,vnode) 和 patch(vnode, newVnode)

三个过程:

  • 初次渲染过程
  • 更新过程
  • 异步渲染

三、vue组件是如何渲染和更新的

1、初次渲染过程

  • 解析模板为 render 函数(或在开发环境已完成,vue-loader)
  • 触发响应式,监听 data 属性 getter setter
  • 执行 render 函数,生成 vnode,patch(elem, vnode)

2、执行 render 函数会触发 getter

  • 模板中使用到的会触发 get
  • 模板中没有使用到的不会触发 get,因为和视图没有关系
<p>{{message}}</p>
export default {
  data() {
    return {
      message: '杂货铺', // 会触发 get
      city: '北京' // 不会触发 get,因为模板没用到,即和视图没关系
    }
  }
}

3、更新过程

  • 修改 data,触发 setter(此前在 getter 中已被监听)
  • 重新执行 render 函数,生成 newVnode
  • patch(vnode, newVnode)

2558b92fa9fa44c786f2592f92bd5b5b.png

4、vue组件如何进行异步渲染

  • $nextTick 是异步的
  • 汇总 data 的修改,一次性更新视图
  • 减少 DOM 操作次数,提高性能

四、如何用 JS 实现 hash 路由

  • vue-router 的路由模式:hash模式,H5 history

1、网页 url 组成部分

http://127.0.0.1:8881/01-hash.html?a=100&b=20#/aaa/bbb
location.protocol // 'http:' 【协议】
location.hostname // '127.0.0.1' 【IP地址/域名】
location.host // '127.0.0.1:8881' 【IP地址带端口】
location.port // '8881' 【端口】
location.pathname // '/01-hash.html' 【文件路径名】
location.search // '?a=100&b=20' 【参数】
location.hash // '#/aaa/bbb' 【#及后面的部分】

2、hash 的特点

  • hash 变化会触发网页跳转,即浏览器的前进、后退
  • hash 变化不会刷新页面,SPA 必需的特点
  • hash 永远不会提交到 server 端(前端自生自灭)

3、hash 变化示例

  • onhashchange:用来监听 hash 值的变化
  • href 属性是一个可读可写的字符串,可设置或返回当前显示的文档的完整 URL
<p>hash test</p>
<button id="btn1">修改 hash</button>
// 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'
})

9ff3aa26237e41588b85e4e06192f341.png



bdd0614b44aa4be4a555fcbaef3c2cdb.png

五、如何用 JS 实现 H5 history 路由

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

1、示例

  • history.pushState(state,title,url)
  • state:一个与指定网址相关的状态对象,popstate 事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null
  • title:新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
  • url:新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址
<p>history API test</p>
<button id="btn1">修改 url</button>
// 页面初次加载,获取 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)
}

3c991522bfab40078c9d2da8c68e033f.png07fab7ca1296411f9dc9b8657858bbe6.png


2、如何选择

  • to B(面向企业或特定用户群体) 的系统推荐用 hash,简单易用,对 url 规范不敏感
  • to C(面向个体消费者)的系统,可以考虑选择 H5 history,但需要服务端支持
  • 能选择简单的,就不要用复杂的,要考虑成本和利益

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

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


相关文章
|
1月前
|
JavaScript 前端开发 程序员
前端原生Js批量修改页面元素属性的2个方法
原生 Js 的 getElementsByClassName 和 querySelectorAll 都能获取批量的页面元素,但是它们之间有些细微的差别,稍不注意,就很容易弄错!
|
1月前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
28天前
|
JavaScript
使用Node.js创建一个简单的Web服务器
使用Node.js创建一个简单的Web服务器
|
1月前
|
机器学习/深度学习 人工智能 JavaScript
JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景
本文探讨了JavaScript和TypeScript的未来发展趋势及其在Web开发中的应用前景。JavaScript将注重性能优化、跨平台开发、AI融合及WebAssembly整合;TypeScript则强调与框架整合、强类型检查、前端工程化及WebAssembly的深度结合。两者结合发展,特别是在Vue 3.0中完全采用TypeScript编写,预示着未来的Web开发将更加高效、可靠。
42 4
|
1月前
|
缓存 JavaScript 前端开发
JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用
本文深入讲解了 JavaScript 与 DOM 交互的基础及进阶技巧,涵盖 DOM 获取、修改、创建、删除元素的方法,事件处理,性能优化及与其他前端技术的结合,助你构建动态交互的网页应用。
47 5
|
1月前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
38 2
|
1月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
74 1
|
1月前
|
JSON 前端开发 JavaScript
聊聊 Go 语言中的 JSON 序列化与 js 前端交互类型失真问题
在Web开发中,后端与前端的数据交换常使用JSON格式,但JavaScript的数字类型仅能安全处理-2^53到2^53间的整数,超出此范围会导致精度丢失。本文通过Go语言的`encoding/json`包,介绍如何通过将大整数以字符串形式序列化和反序列化,有效解决这一问题,确保前后端数据交换的准确性。
44 4
|
1月前
|
机器学习/深度学习 自然语言处理 前端开发
前端神经网络入门:Brain.js - 详细介绍和对比不同的实现 - CNN、RNN、DNN、FFNN -无需准备环境打开浏览器即可测试运行-支持WebGPU加速
本文介绍了如何使用 JavaScript 神经网络库 **Brain.js** 实现不同类型的神经网络,包括前馈神经网络(FFNN)、深度神经网络(DNN)和循环神经网络(RNN)。通过简单的示例和代码,帮助前端开发者快速入门并理解神经网络的基本概念。文章还对比了各类神经网络的特点和适用场景,并简要介绍了卷积神经网络(CNN)的替代方案。
130 1
|
1月前
|
移动开发 前端开发 JavaScript
前端实训,刚入门,我用原生技术(H5、C3、JS、JQ)手写【网易游戏】页面特效
于辰在大学期间带领团队参考网易游戏官网的部分游戏页面,开发了一系列前端实训作品。项目包括首页、2021校园招聘页面和明日之后游戏页面,涉及多种特效实现,如动态图片切换和人物聚合效果。作品源码已上传至CSDN,视频效果可在CSDN预览。
45 0

热门文章

最新文章