1.什么是mvvm
MVVM的核心是数据驱动
即ViewModel,ViewModel是View和Model的关系映射
。MVVM本质就是基于操作数据
来操作视图
进而操作DOM
,借助于MVVM无需直接操作DOM,开发者只需编写ViewModel中有业务,使得View完全实现自动化
。
2.什么是 SPA 单页面,它的优缺点分别是什么
SPA( single-page application )即一个web项目就只有一个页面(即一个HTML文件,HTML 内容的变换是利用路由机制实现的。
仅在 Web 页面初始化
时加载
相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转
;取而代之的是利用路由
机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载
。
优点:
-
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
-
基于上面一点,SPA 相对对服务器压力小;
-
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
-
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
-
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
-
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
3.生命周期
3-1 基本概念
★什么是vue生命周期? Vue 实例从创建到销毁的过程,就是生命周期。
”
注意:浏览器有8个钩子,但是node中做服务端渲染的时候只有beforeCreate和created
beforeCreate是new Vue()之后触发的第一个钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能
被访问。
created在实例创建完成后发生,当前阶段已经完成了数据观测
,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数
。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互
,如果非要想,可以通过vm.$nextTick
来访问Dom。
beforeMount发生在挂载之前
,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom
已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触
发updated。
mounted在挂载完成后
发生,在当前阶段,真实的Dom挂载完毕
,数据完成双向绑定,可以访问到Dom节点
,使用$refs属性对Dom进行操作。
beforeUpdate发生在更新之前
,也就是响应式数据发生更新,虚拟dom重新渲染之前
被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
updated发生在更新完成之后
,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环
的更新。
beforeDestroy发生在实例销毁之前
,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器,销毁父组件对子组件的重复监听。beforeDestroy(){Bus.$off("saveTheme")}
destroyed发生在实例销毁之后
,这个时候只剩下了dom空壳
。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
3-2 生命周期调用顺序
-
组件的
调用
顺序都是先父后子 -
渲染完成的
顺序是先子后父 -
组件的
销毁
操作是先父后子 -
销毁完成
的顺序是先子后父
加载渲染过程 父beforeCreate->父created->父beforeMount
->子beforeCreate
->子created->子beforeMount- >子mounted->父mounted
子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程 父 beforeUpdate -> 父 updated
销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
3-3 vue生命周期的作用是什么
它的生命周期中有多个事件钩子,让我们控制Vue实例过程更加清晰。
3-4 第一次页面加载会触发哪几个钩子
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
3-5 每个周期具体适合哪些场景
-
beforecreate
: 可以在这加个loading事件,在加载实例时触发 -
created
: 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用 -
mounted
: 挂载元素,获取到DOM节点 -
updated
: 如果对数据统一处理,在这里写上相应函数 -
beforeDestroy
: 可以清除定时器 -
nextTick
: 更新数据后立即操作dom
4.v-show 与 v-if 的区别
v-if
-
是真正的条件渲染,因为它会确保在切换过程中条件块内的
事件监听器
和子组件
适当地被销毁和重建; -
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show
-
不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以:
v-if 适用于在运行时很少改变条件
,不需要频繁切换
条件的场景;
v-show 则适用于需要非常频繁切换条件的场景。
5. Vue 的单向数据流
背景:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定
:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变
父级组件的状态,从而导致你的应用的数据流向
变的混乱。
每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
有两种常见的试图改变一个 prop 的情形 :
-
这个 prop 用来传递一个初始值;
这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
在这种情况下,最好定义一个本地的 data
属性并将这个 prop 用作其初始值
:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter//定义本地的data属性接收prop初始值
}
}
复制代码
-
这个 prop 以一种
原始的值
传入且需要进行转换。
在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
复制代码
6.异步请求适合在哪个生命周期调用?
官方实例的异步请求是在mounted
生命周期中调用的,而实际上也可以在created生命周期中调用。
本人推荐在 created 钩子函数中调用异步请求,有以下优点:
-
能更快获取到服务端数据,减少页面 loading 时间;
-
ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;
7.Vue2.x组件通信有哪些方式?
父子组件通信
-
父->子props;子(emit)->父(on)
-
获取父子组件实例
$parent / $children 如:直接在子组件的methods方法里面写:this.$parent.show()//show为父组件中定义的方法
-
Ref
(如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例),如在引入的子组件的标签上挂载一个: ref="comA",然后在方法中或者子组件的数据,this.$refs.comA.titles -
Provide、inject 官方不推荐使用,但是写组件库时很常用
兄弟组件通信
-
Event Bus 实现跨组件通信: Vue.prototype.$bus = new Vue
-
Vuex
跨级组件通信
-
Vuex
-
attrs,listeners
-
Provide、inject
attrs,listeners:
$attrs:
包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
Provide、inject的使用:
父组件
<template>
<div id="app">
</div>
</template>
<script>
export default {
data () {
return {
datas: [
{
id: 1,
label: '产品一'
}
]
}
},
provide {
return {
datas: this.datas
}
}
}
</script>
复制代码
子组件
<template>
<div>
<ul>
<li v-for="(item, index) in datas" :key="index">
{{ item.label }}
</li>
</ul>
</div>
</template>
<script>
export default {
inject: ['datas']
}
</script>
复制代码
8.什么是SSR
SSR也就是服务端渲染
,也就是将Vue在客户端把标签渲染成HTML
的工作放在服务端完成,然后再把html直接返回
给客户端。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
-
更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
-
更快的内容到达时间(首屏加载更快): SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
-
更多的开发条件限制: 例如服务端渲染只支持
beforCreate
和created
两个钩子函数,这会导致一些外部扩展库需要特殊处理
,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境
; -
更多的服务器负载: 在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
9.Vue路由
9-1 vue-router 路由模式有几种?
vue-router 有 3 种路由模式:hash、history、abstract
,对应的源码如下所示:
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
复制代码
3 种路由模式的说明如下:
hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器;
history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
9-2 hash路由和history路由实现原理
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash
来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search':
https://www.word.com#search
复制代码
hash 路由模式的实现主要是基于下面几个特性:
-
URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,
hash 部分不会被发送
; -
hash 值的改变,都会在浏览器的访问历史中
增加一个记录
。因此我们能通过浏览器的回退、前进按钮控制hash 的切换; -
可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 的 hash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值;
-
我们可以使用
hashchange
事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。
(2)history 模式的实现原理
HTML5 提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState()
和 history.repalceState()
。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录
。唯一不同的是,前者是新增
一个历史记录,后者是直接替换
当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
复制代码
history 路由模式的实现主要基于存在下面几个特性:
-
pushState 和 repalceState 两个 API 来操作实现 URL 的变化 ;
-
我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
-
history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。
10.你有对 Vue 项目进行哪些优化?
(1)代码层面的优化
-
v-if 和 v-show 区分使用场景
-
computed 和 watch 区分使用场景
-
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
-
长列表性能优化
-
事件的销毁
-
图片资源懒加载
-
路由懒加载
-
第三方插件的按需引入
-
优化无限列表性能
-
服务端渲染 SSR or 预渲染
(2)Webpack 层面的优化
-
Webpack 对图片进行压缩-------先引入npm install image-webpack-loader --save-dev,然后在 webpack.config.js 中配置
-
减少 ES6 转为 ES5 的冗余代码
-
提取公共代码
-
模板预编译
-
提取组件的 CSS
-
优化 SourceMap
-
构建结果输出分析
-
Vue 项目的编译优化
(3)基础的 Web 技术的优化
-
开启 gzip 压缩
-
浏览器缓存
-
CDN 的使用
-
使用 Chrome Performance 查找性能瓶颈
11.Vue 中的 key 有什么作用?
key 是为 Vue 中 vnode
的唯一标记,通过这个 key,我们的 diff
操作可以更准确、更快速
。
Vue 的 diff 过程可以概括为
:oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex
和 newStartIndex、newEndIndex
,它们会新节点和旧节点会进行两两对比
,即一共有4种
比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配
,如果设置了key,就会用 key 再进行比较,在比较的过程中,遍历会往中间靠,一旦 StartIdx > EndIdx
表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。
所以 Vue 中 key 的作用是:
key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
-
更准确因为带 key 就不是就地复用了,在 sameNode 函数
a.key === b.key
对比中可以避免就地复用的情况。所以会更加准确。 -
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
复制代码
12.虚拟 DOM 实现原理
虚拟 DOM 的实现原理主要包括以下 3 部分:
-
用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
-
diff 算法 — 比较两棵虚拟 DOM 树的差异;
-
pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
13.虚拟 DOM 的优缺点
优点:
-
保证性能下限: 框架的虚拟 DOM 需要
适配任何上层 API
可能产生的操作,它的一些 DOM 操作的实现必须是普适的
,所以它的性能并不是最优的
;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限; -
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据
虚拟 DOM 和 数据双向绑定
,帮我们以可预期的方式更新视图,极大提高我们的开发效率; -
跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点:
无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。
14.Proxy 与 Object.defineProperty 优劣对比
Proxy 的优势如下:
-
Proxy 可以直接
监听对象
而非属性; -
Proxy 可以直接监听
数组
的变化; -
Proxy 有多达
13 种拦截方法
,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的; -
Proxy 返回的是
一个新对象
,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改; -
Proxy 作为新标准将受到浏览器厂商重点
持续的性能优化
,也就是传说中的新标准的性能红利;
Object.defineProperty 的优势如下:
兼容性好
,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平
15.Vue 框架怎么实现对象和数组的监听?
Vue 框架是通过遍历数组
和递归遍历对象
,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
16.Vue 是如何实现数据双向绑定的
Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据
输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。 Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View
。
Vue 主要通过以下 4 个步骤
来实现数据双向绑定的:
-
实现一个监听器 Observer对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty() 对属性都加上 setter 和 getter。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
-
实现一个解析器 Compile解析 Vue 模板指令,将模板中的
变量
都替换成数据
,然后初始化渲染页面视图,并将每个指令
对应的节点
绑定更新函数
,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新
。 -
实现一个订阅者 Watcher:Watcher 订阅者是
Observer 和 Compile 之间通信的桥梁
,主要的任务是订阅 Observer 中的属性值变化
的消息,当收到属性值变化的消息时,触发解析器
Compile 中对应的更新函数。 -
实现一个订阅器 Dep订阅器采用
发布-订阅 设计模式
,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
17.v-model 的原理
我们在 vue 项目中主要使用 v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,我们知道 v-model 本质上不过是语法糖,v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
-
text 和 textarea 元素使用 value 属性和 input 事件;
-
checkbox 和 radio 使用 checked 属性和 change 事件;
-
select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
复制代码
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
复制代码
18.组件中 data 为什么是一个函数?
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
// data
data() {
return {
message: "子组件",
childName:this.name
}
}
复制代码
// new Vue new Vue({ el: '#app', router, template: '<App/>', components: {App} })
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。 而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
19.谈谈你对 keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态
,避免重新渲染
,其有以下特性:
-
一般结合路由和动态组件一起使用,用于缓存组件;
-
提供
include
和exclude
属性,两者都支持字符串或正则表达式, include 表示只有名称匹配
的组件会被缓存
,exclude 表示任何名称匹配的组件都不会被缓存
,其中 exclude 的优先级
比 include 高; -
对应两个钩子函数
activated
和deactivated
,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。
keep-alive的生命周期
-
activated: 页面第一次进入的时候,钩子触发的顺序是created->mounted->activated
-
deactivated: 页面退出的时候会触发deactivated,当再次前进或者后退的时候只触发activated
20.父组件可以监听到子组件的生命周期吗?
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
<Child @mounted="doSomething"/>
// Child.vue
mounted() {
this.$emit("mounted");
}
复制代码
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
<Child @hook:mounted="doSomething" ></Child>
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 ...
// 父组件监听到 mounted 钩子函数 ...
复制代码
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
21.直接给一个数组项赋值,Vue 能检测到变化吗?
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set,Vue.set的一个别名
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
复制代码
为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
复制代码
22.vue2.x中如何监测数组变化
使用了函数劫持
的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写
,指向了自己定义的数组原型方法
。这样当调用数组api时,可以通知依赖更新
。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历
进行监控。这样就实现了监测数组变化。
22.Vue2.x和Vue3.x渲染器的diff算法分别说一下
简单来说,diff算法有以下过程
同级比较, 再比较子节点,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除),
比较都有子节点的情况(核心diff)递归比较子节点
正常·Diff两个树·的时间复杂度是O(n^3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n^3) -> O(n),只有当新旧children都为多个子节点
时才需要用核心的Diff算法进行同层级比较
。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端
开始进行比较,借助key值找到可复用
的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了 ivi算法和 inferno算法 在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。 该算法中还运用了动态规划的思想求解最长递归子序列。
23.Vue模版编译原理
简单说,Vue的编译过程
就是将template
转化为render函数
的过程。会经历以下阶段:
-
生成AST
-
树优化
-
codegen
首先解析模版
,生成AST
语法树(一种用JavaScript对象的形式来描述整个模板)。
使用大量的正则表达式对模板进行解析
,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树
,按照相关条件对树节点进行标记
。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST树转换为可执行的代码
。
24.Computed和Watch
computed:
-
computed是计算属性,也就是计算值,它更多用于计算值的场景
-
computed具有
缓存性
,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后
,下一次获取computed的值时才会重新调用对应的getter
来计算 -
computed适用于计算比较消耗性能的计算场景
watch:
-
更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作
-
无缓存性,页面重新渲染时值不变化也会执行
小结:
当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化
25.nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在这里里面的代码会等到dom更新以后再执行。
<template>
<section>
<div ref="hello">
<h1>Hello World ~</h1>
</div>
<el-button type="danger" @click="get">点击</el-button>
</section>
</template>
<script>
export default {
methods: {
get() {
}
},
mounted() {
console.log(333);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(444);
console.log(this.$refs['hello']);
});
},
created() {
console.log(111);
console.log(this.$refs['hello']);
this.$nextTick(() => {
console.log(222);
console.log(this.$refs['hello']);
});
}
}
</script>
复制代码
打印
对于即将到来的 vue3.0 特性你有什么了解的吗?
26.vue响应式原理
(1)Vue2.x响应式数据原理
Vue在初始化数据时,会使用Object.defineProperty
重新定义data中的所有属性,当页面使用对应属性时,首先会进行依赖收集
(收集当前组件的watcher)如果属性发生变化
会通知相关依赖
进行更新操作(发布订阅)
(2)Vue3.x响应式数据原理
Vue3.x改用Proxy
替代Object.defineProperty。因为Proxy可以直接监听对象和数组
的变化,并且有多达13种拦截方法
。并且作为新标准将受到浏览器厂商重点持续的性能优化
。
Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?
判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。
27.在使用计算属性的时,函数名和data数据源中的数据可以同名吗?
不能同名 因为不管是计算属性还是data还是props 都会被挂载在vm实例上,因此 这三个都不能同名
28.怎么解决vue打包后静态资源图片失效的问题
找到config/index.js
配置文件,找build
打包对象里的assetsPublicPath
属性 默认值为/
,更改为./
就好了
29. 怎么解决vue动态设置img的src不生效的问题?
因为动态添加src被当做静态资源处理了,没有进行编译,所以要加上require。
<img :src="require('../../../assets/images/xxx.png')" />
复制代码
30. 使用vue渲染大量数据时应该怎么优化?说下你的思路!
Object.freeze
适合一些 big data的业务场景。尤其是做管理后台的时候,经常会有一些超大数据量的 table,或者一个含有 n 多数据的图表,这种数据量很大的东西使用起来最明显的感受就是卡。但其实很多时候其实这些数据其实并不需要响应式变化,这时候你就可以使用 Object.freeze 方法了,它可以冻结一个对象(注意它不并是 vue 特有的 api)。
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,它们让 Vue 能进行追踪依赖,在属性被访问和修改时通知变化。
使用了 Object.freeze 之后,不仅可以减少 observer 的开销,还能减少不少内存开销。 使用方式:this.item = Object.freeze(Object.assign({}, this.item))