1. 简略说下vue
- vue是突变式框架,依据本人的需要增加性能
- vue数据驱动采纳mvvm模式,m是数据层,v是视图层,vm是调度者
- SPA单页面利用,只有一个页面,加载速率快
- 组件化,复用性强
那么,它有什么毛病?
- vue2底层基于Object.defineProperty实现响应式,这个api自身不反对IE8及以下浏览器
- csr的先天不足,首屏性能问题(白屏)
- 因为百度等搜索引擎爬虫无奈爬取js中的内容,故spa先天就对seo优化心有余力有余(谷歌的puppeteer就挺牛逼的,实现预渲染底层也是用到了这个工具)
2. vuehereact的区别
首先你得说说相同点,两个都是MVVM框架,数据驱动视图,无争议。如果说不同,那可能分为以下这么几点:
- vue是残缺一套由官网保护的框架,外围库次要有由尤雨溪大神单独保护,而react是不要脸的书保护(很多库由社区保护),已经一段时间很多人质疑vue的后续维护性,仿佛这并不是问题。
- vue上手简略,进阶式框架,文言说你能够学一点,就能够在你我的项目中去用一点,你不肯定须要一次性学习整个vue能力去应用它,而react,恐怕如果你这样会面对我的项目大刀阔斧。
- 语法上vue并不限度你必须es6+齐全js模式编写页面,能够视图和js逻辑尽可能拆散,缩小很多人看不惯react-jsx的恶心嵌套,毕竟都是作为前端开发者,还是更习惯于html洁净。
- 很多人说react适宜大型项目,适宜什么什么,vue轻量级,适宜挪动端中小型我的项目,其实我想说,说这话的人是心里基本没点逼数,vue齐全能够应答简单的大型利用,甚至于说如果你react学的不是很好,写进去的货色或基本不如vue写的,毕竟vue跟着官网文档撸就行,自有人帮你标准,而react比拟懒惰自在,能够自由发挥
- vue在国内人气显著胜过react,这很大水平上得益于它的很多语法包含编程思维更合乎国人思维
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上的数据和办法都不能被拜访。 能够做页面拦挡。当进一个路由的时候咱们能够判断是否有权限进去,是否平安进去,携带参数是否残缺,参数是否平安。应用这个钩子好函数的时候就防止了让页面去判断,省掉了创立一个组建Vue实例。 - created 产生在
实例创立实现后
,以后阶段曾经实现了数据观测
,也就是能够应用数据,更改数据,在这里更改
数据不会
触发updated
函数。能够做一些初始数据的获取,在以后阶段无奈
与Dom
进行交互
(因为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 数据来应用。
在第2状况下,最好定义一个本地的 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组件通信有哪些形式?
7-1 父子组件通信
- 父->子props;子(emit)->父(on)
- 获取父子组件实例 $parent / $children 如:间接在子组件的methods办法外面写:this.$parent.show()//show为父组件中定义的办法
- Ref (如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例),如在引入的子组件的标签上挂载一个: ref="comA",而后在办法中或者子组件的数据,this.$refs.comA.titles
- Provide、inject 官网不举荐应用,然而写组件库时很罕用,先人组件中通过provider来提供变量,而后在子孙组件中通过inject来注入变量
7-2 兄弟组件通信
- Event Bus 实现跨组件通信: Vue.prototype.$bus = new Vue
- Vuex
7-3 跨级组件通信
- Vuex
- attrs,listeners
- Provide、inject
7-4 应用
1. 父子props,on
// 子组件
<template> <header> <h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件 </header></template><script>export default { data() { return { title:"Vue.js Demo" } }, methods:{ changeTitle() { this.$emit("titleChanged","子向父组件传值");//自定义事件 传递值“子向父组件传值” } }}</script>
// 父组件
<template> <div id="app"> <Header @titleChanged="updateTitle" ></Header>//与子组件titleChanged自定义事件保持一致 <h2>{{title}}</h2> </div></template><script>import Header from "./Header"export default { data(){ return{ title:"传递的是一个值" } }, methods:{ updateTitle(e){ //申明这个函数 this.title = e; } }, components:{ Header }}</script>
2. parent / $children与 ref
// A 子组件
export default { data () { return { title: 'a组件' } }, methods: { sayHello () { alert('Hello'); } }}
// 父组件
<template> <A ref="comA"></A></template><script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // a组件 comA.sayHello(); // 弹窗 } }</script>
3.attrs,listeners
attrs: 蕴含了父作用域
中不被 prop
所辨认
(且获取) 的个性绑定 ( class 和 style 除外 )。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 ( class 和 style 除外 ),并且能够通过 v-bind="$attrs" 传入外部组件。通常配合 inheritAttrs 选项一起应用。
listeners: :蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过 v-on="$listeners" 传入外部组件
// index.vue
<template> <div> <h2>浪里行舟</h2> <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠"></child-com1> </div></template><script>const childCom1 = () => import("./childCom1.vue");export default { components: { childCom1 }, data() { return { foo: "Javascript", boo: "Html", coo: "CSS", doo: "Vue" }; }};</script>
// childCom1.vue
<template class="border"> <div> <p>foo: {{ foo }}</p> <p>childCom1的$attrs: {{ $attrs }}</p> <child-com2 v-bind="$attrs"></child-com2> </div></template><script>const childCom2 = () => import("./childCom2.vue");export default { components: { childCom2 }, inheritAttrs: false, // 能够敞开主动挂载到组件根元素上的没有在props申明的属性 props: { foo: String // foo作为props属性绑定 }, created() { console.log(this.$attrs); // 父组件中的属性,且不在以后组件props中的属性。{ "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" } }};</script>
// childCom2.vue
<template> <div class="border"> <p>boo: {{ boo }}</p> <p>childCom2: {{ $attrs }}</p> <child-com3 v-bind="$attrs"></child-com3> </div></template><script>const childCom3 = () => import("./childCom3.vue");export default { components: { childCom3 }, inheritAttrs: false, props: { boo: String }, created() { console.log(this.$attrs); // / 父组件中的属性,且不在以后组件props中的属性。{"coo": "CSS", "doo": "Vue", "title": "前端工匠" } }};</script>
// childCom3.vue
<template> <div class="border"> <p>childCom3: {{ $attrs }}</p> </div></template><script>export default { props: { coo: String, title: String }};</script>
4. 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}`) }}
路由模式的阐明如下:
- 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 中的 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, keyconst map = {}for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key if (isDef(key)) map[key] = i}return map}
参考1:Vue2.0 v-for 中 :key 到底有什么用?
11.虚构 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 磨平
14-1 那么,Proxy 能够实现什么性能?
Proxy 是 ES6 中新增的性能,它能够用来自定义对象中的操作。
let p = new Proxy(target, handler)
- target 代表须要
增加代理
的对象 - handler 用来自定义
对象中
的操作
,比方能够用来自定义 set 或者 get 函数。
上面来通过 Proxy 来实现一个数据响应式:
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver) }, set(target, property, value, receiver) { setBind(value, property) return Reflect.set(target, property, value) } } return new Proxy(obj, handler)}let obj = { a: 1 }let p = onWatch( obj, (v, property) => { console.log(`监听到属性${property}扭转为${v}`) }, (target, property) => { console.log(`'${property}' = ${target[property]}`) })p.a = 2 // 监听到属性a扭转为2p.a // 'a' = 2
在上述代码中,通过自定义 set 和 get 函数的形式,在本来的逻辑中插入了咱们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简略版的响应式实现,如果须要实现一个 Vue 中的响应式,须要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要应用 Proxy 替换本来的 API 起因在于 Proxy 无需一层层递归为每个属性增加代理,一次即可实现以上操作,性能上更好,并且本来的实现有一些数据更新不能监听到,然而 Proxy 能够完满监听到任何形式的数据扭转,惟一缺点就是浏览器的兼容性不好。
15.Vue 框架怎么实现对象和数组的监听?
Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(局部办法的操作)进行监听。
vue2:
数组
就是应用 object.defineProperty
从新定义数组的每一项
,能引起数组变动的办法为 pop 、 push 、 shift 、 unshift 、 splice 、 sort 、 reverse
这七种,只有这些办法执行改了数组内容,就更新内容
- 是用来
函数劫持
的形式,重写
了数组办法,具体就是更改了数组的原型,更改成本人的,用户调数组的一些办法的时候,走的就是本人的办法,而后告诉视图去更新(实质就是在原有的办法上又调用了更新数据的办法)。 - 数组项可能是
对象
,那么就对数组的每一项进行观测
vue3:
改用 proxy ,可间接监听对象数组的变动。
参考
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 订阅者是 Observer 和 Compile 之间通信的桥梁 ,次要的工作是订阅 Observer 中的属性值变动的音讯,当收到属性值变动的音讯时,触发解析器 Compile 中对应的更新函数。
- 实现一个订阅器 Dep 订阅器采纳 公布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行对立治理。
17.v-model 的原理
v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,v-model 实质上是语法糖
,会在外部为不同的输出元素应用不同的属性并抛出不同的事件:
- text 和 textarea 元素应用
value
属性和input
事件; - checkbox 和 radio 应用
checked
属性和change
事件; - select 字段将 value 作为
prop
并将change
作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input :value="something" @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>
具体点击这路
26.vue响应式原理
26-1 Vue2.x响应式数据原理
Vue在初始化数据时,会应用Object.defineProperty
从新定义data中的所有属性
,当页面应用
对应属性时,首先会进行依赖收集
(收集以后组件的watcher),如果属性产生变动
会告诉
相干依赖进行更新操作
(公布订阅)
具体的过程:
- 首先Vue应用
initData
初始化用户传入的参数
- 而后应用
new Observer
对数据进行观测
- 如果数据是一个
对象类型
就会调用this.walk(value)
对对象进行解决,外部应用defineeReactive
循环对象属性定义响应式变动,外围就是应用Object.defineProperty
从新定义数据。
26-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))
30. vue 自定义指令
先理解一下,在 vue 中,有很多内置的指令.
比方:
- v-bind: 属性绑定,把数据绑定在HTML元素的属性上.
- v-html & v-text 把数据绑定在HTML元素的属性上,作用同 innerHTML & innerText
- v-on: 绑定HTML元素事件
所以,对于指令,咱们能够总结上面几点:
- 指令是写在
HTML 属性
中央的,<input v-model='name' type='text' />
- 指令都是以
v-
结尾的. - 指令表达式的左边个别也能够跟值 v-if = false
Vue自定义指令案例1
例如:咱们须要一个指令,写在某个HTML表单元素上,而后让它在被加载到DOM中时,主动获取焦点.
// 和自定义过滤器一样,咱们这里定义的是全局指令Vue.directive('focus',{ inserted(el) { el.focus() }})<div id='app'> <input type="text"> <input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点"> </div>
先总结几个点:
- 应用
Vue.directive()
来新建一个全局指令
,(指令应用在HTML元素属性上的) - Vue.directive('focus')
第一个参数
focus是指令名
,指令名在申明的时候,不须要加 v- - 在应用指令的HTML元素上,
<input type="text" v-focus placeholder="我有v-focus,所以,我获取了焦点"/>
咱们须要加上 v-. - Vue.directive('focus',{})
第二个参数
是一个对象
,对象外部
有个inserted()
的函数,函数有el
这个参数. - el 这个参数示意了绑定这个指令的
DOM元素
,在这里就是前面那个有placeholder
的input
,el 就等价于document.getElementById('el.id')
- 能够利用
$(el)
无缝连贯 jQuery
指令的生命周期
用指令咱们须要:
- 新增一个指令
- 定义指令的第二个参数里的 inserted 函数
- 在须要获取焦点的元素上,应用这个指令.
当一个指令绑定到一个元素上时,其实指令的外部会有五个生命周期事件函数.
bind(){}
当指令绑定
到 HTML元素
上时触发.只调用一次.inserted()
当绑定了指令的这个HTML元素
插入到父元素
上时触发(在这里父元素是div#app
).但不保障,父元素曾经插入了 DOM 文档.updated()
所在组件的VNode更新
时调用.componentUpdate
指令所在的组件的VNode以及其子VNode全副更新后
调用.unbind
: 指令和元素解绑
的时候调用,只调用一次
Vue 指令的申明周期函数
Vue.directive('gqs',{ bind() { // 当指令绑定到 HTML 元素上时触发.**只调用一次** console.log('bind triggerd') }, inserted() { // 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保障,父元素曾经插入了 DOM 文档.** console.log('inserted triggerd') }, updated() { // 所在组件的`VNode`更新时调用. console.log('updated triggerd') }, componentUpdated() { // 指令所在组件的 VNode 及其子 VNode 全副更新后调用。 console.log('componentUpdated triggerd') }, unbind() { // 只调用一次,指令与元素解绑时调用. console.log('unbind triggerd') } })
HTML
<div id='app' v-gqs></div>后果:bind triggerdinserted triggerd
发现默认状况下只有 bind 和 inserted 申明周期函数触发了.
那么剩下的三个什么时候触发呢?
<div id='app' > <p v-gqs v-if="show">v-if是删除或者新建dom元素,它会触发unbind指令申明周期吗?</p> <button @click="show=!show">toggle</button> </div>
当指令绑定的元素被销毁时,会触发指令的 unbind 事件.
(新建并显示,依然是触发 bind & inserted)
unbind触发.gif
<p v-gqs v-show="show2">v-show设置元素的display:block|none,会触发componentUpdated事件</p> <button @click="show2=!show2">toggle-v-show</button>
- 一个把元素从DOM删除触发unbind().---> 仅仅是删除.
- 一个显示设置元素的暗藏和显示的时候触发 componentUpdated() ---> block | none 都触发.
31. vue实例挂载的过程是什么
32. 组件和插件有什么区别
- 组件 (Component) 是用来形成你的 App 的业务模块,它的指标是 App.vue。
- 插件 (Plugin) 是用来加强你的技术栈的功能模块,它的指标是 Vue 自身。
33.应用vue过程中可能会遇到的问题(坑)有哪些
34. 动静给vue的data增加一个新的属性时会产生什么
依据官网文档定义:
如果在实例创立之后
增加新的属性
到实例上,它不会触发视图更新。
Vue 不容许在曾经创立
的实例
上动静增加
新的根级响应式属性
(root-level reactive property)。
然而它能够应用 Vue.set(object, key, value)
办法将响应属性增加到嵌套的对象上。
35. SPA首屏加载速度慢的怎么解决
- 通过Gzip压缩
- 应用路由懒加载
- 利用webpack中的externals这个属性把不须要打包的库文件都拆散进来,减小我的项目打包后的大小
- 应用SSR渲染
36. mixin
多个实例
援用了雷同或类似的办法或属性
等,可将这些反复的内容抽取进去作为mixins的js,export进来,在须要援用的vue文件通过mixins属性注入,与以后实例
的其余内容
进行merge
。
一个混入对象能够蕴含任意组件选项
。同一个生命周期,混入对象
会比组件
的先执行
。
//裸露两个mixins对象
export const mixinsTest1 = { methods: { hello1() { console.log("hello1"); } }, created() { this.hello1(); },}export const mixinsTest2 = { methods:{ hello2(){ console.log("hello2"); } }, created() { this.hello2(); },}
<template><div> home</div></template><script>import {mixinsTest1,mixinsTest2} from '../util/test.js'export default { name: "Home", data () { return { }; }, created(){ console.log("1212"); }, mixins:[mixinsTest2,mixinsTest1] // 先调用哪个mixins对象,就先执行哪个}</script>hello2hello11212
37. vue的外围是什么
- 数据驱动 专一于View 层。它让开发者省去了操作DOM的过程,只须要扭转数据。
- 组件响应原理 数据(model)扭转驱动视图(view)自动更新
- 组件化 扩大HTML元素,封装可重用的代码。
38. vue罕用的修饰符有哪些
- v-model: .trim .number
v-on: .stop .prevent
39. v-on能够绑定多个办法吗?
<input v-model="msg" type="text" v-on="{input:a, focus:b}"/>
40. template编译的了解
41.axios是什么,如何中断axios的申请
42. 如何引入scss?
装置scss依赖包:
npm install sass-loader --save-dev npm install node-sass --save-dev
在build文件夹下批改 webpack.base.conf.js 文件,在 module 下的 rules 里增加配置,如下:
{ test: /\.scss$/, loaders: ['style', 'css', 'sass'] }
利用:
在vue文件中利用scss时,须要在style款式标签上增加lang="scss",即<style lang="scss">。
43. 在vue中watch和created哪个先执行
watch 中的 immediate 会让监听在初始值申明的时候去执行监听计算,否则就是 created 先执行
44. 在vue中created与activated有什么区别
created():在实例创立实现后被立刻调用。在这一步,实例已实现以下的配置:数据观测 (data observer),property 和办法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。
activated():是在路由设置<keep-alive></keep-alive>
时,才会有这个生命周期。在被 keep-alive 缓存的组件激活时调用。
45. 为什么在v-for中的key不举荐应用随机数或者index呢
因为在插入数据或者删除数据的时候,会导致前面的数据的key绑定的index变动,进而导致从新渲染,效率会升高
46. 如何批量引入组件
动静组件应用办法
<keep-alive> <component :is="isWhich"></component></keep-alive>应用标签保留状态,即切换组件再次回来仍然是原来的样子,页面不会刷新,若不须要能够去掉。通过事件扭转is绑定的isWhich值即可切换成不同的组件,isWhich的值为组件名称。
47. vue中怎么重置data
应用场景:
比方,有一个表单,表单提交胜利后,心愿组件复原到初始状态,重置data数据。
应用Object.assign()
,vm.$data
能够获取以后状态下的data,vm.$options.data
能够获取到组件初始化状态下的data
初始状态下设置data数据的默认值,重置时间接bject.assign(this.$data, this.$options.data())
阐明:
- this.$data获取以后状态下的data
- this.$options.data()获取该组件初始状态下的data(即初始默认值)
- 如果只想批改data的某个属性值,能够
this[属性名] = this.$options.data()[属性名]
,如this.message = this.$options.data().message
48. vue渲染模板时怎么保留模板中的HTML正文呢
<template comments> ...</template>
49. style加scoped属性的用处和原理
- 用处:避免全局同名CSS净化
- 原理:在标签加上
v-data-something
属性,再在选择器时加上对应[v-data-something]
,即CSS属性选择器,以此实现相似作用域的抉择形式
50.在vue我的项目中如何配置favicon
- 将 favicon 图片放到 static 文件夹下
而后在 index.html 中增加:
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico">
51. babel-polyfill模块
Babel
默认只转换新的JavaScript句法
(syntax),而不转换新的API
,比方Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise
等全局对象,以及一些定义在全局对象
上的办法(比方Object.assign
)都不会转码。
举例来说,ES6在Array对象上新增了Array.from办法。Babel就不会转码这个办法。如果想让这个办法运行,必须应用babel-polyfill
,为以后环境提供一个垫片。
52. 在vue事件中传入$event,应用e.target和e.currentTarget有什么区别
- currentTarget: 事件绑定的元素
- target: 鼠标触发的元素
53. vue怎么实现强制刷新组件
强制从新渲染
this.$forceUpdate()
强制从新刷新某组件
//模版上绑定key<SomeComponent :key="theKey"/>//选项里绑定datadata(){ return{ theKey:0 }}
//刷新key达到刷新组件的目标
theKey++;
54. vue给组件绑定自定义事件有效怎么解决
退出.native修饰符
55. vue的属性名与method的办法名一样时会产生什么问题
报错 "Method 'xxx' has already been defined as a data property"
键名优先级:props > data > methods
56. vue变量名如果以_、$结尾的属性会产生什么问题
实例创立之后,能够通过 vm.$data
拜访原始数据对象
。Vue 实例也代理了 data 对象上所有的属性,因而拜访 vm.a
等价于拜访 vm.$data.a
。
以 _
或 $
结尾的属性 不会
被 Vue 实例代理
,因为它们可能和 Vue 内置
的属性、API 办法抵触
。能够应用 vm.$data._property
的形式拜访
这些属性。
57. vue我的项目本地开发实现后部署到服务器后报404
应用了history模式,而后端又没有进行相干资源配置。
58. vue的表单修饰符.lazy
v-model默认的触发条件是input事件,加了.lazy
修饰符之后,v-model会在change
事件触发的时候去监听
59. vue为什么要求组件模板只能有一个根元素
diff算法要求,源码中patch.js中的patchVnode也是依据树状构造进行遍历
60. 在vue中应用this应该留神哪些问题
生命周期的钩子函数不能应用箭头函数,否者this不能指向vue实例
61. <template></template>
有什么用
包裹嵌套其它元素,使元素具备区域性,本身具备三个特点:
- 暗藏性:不会显示在页面中
- 任意性:能够写在页面的任意中央
- 有效性: 没有一个根元素包裹,任何HTML内容都是有效的
62. 组件中写name选项有什么作用
- 我的项目应用keep-alive时,可搭配组件name进行
缓存过滤
- DOM做递归组件时须要调用本身name
- vue-devtools调试工具里显示的
组见名称
是由vue中组件name决定的
63. prop是怎么做验证的
- 单个类型就用Number等根底类型
- 多个类型用数组
- 必填的话设置require为true
- 默认值的话设置default
- 对象和数组设置默认用工厂函数
- 自定义验证函数validator。
64. vue权限治理,按钮和路由
65. 大型项目你该怎么划分构造和划分组件
- views目录寄存一级路由的组件,即视图组件
- Components目录寄存组件
- Store寄存vuex相干文件
- Router目录寄存路由相干文件
- Untils目录寄存工具js文件
- API目录寄存封装好的与后端交互的逻辑
- Assets寄存动态文件
66. vue3.0的新个性
67. 高阶组件
68. vue-loader是什么
解析和转换
.vue 文件,提取
出其中的逻辑代码 script、款式代码 style、以及 HTML 模版 template,再别离把它们交给对应的 Loader 去解决。
69. 怎么捕捉组件vue的错误信息
70.怎么配置跨域
71. vue-router怎么配置404页面
设置 path: '*'
, 并且放在最初一个
72. vue-router如何响应路由参数的变动
为什么要响应参数变动?
- 切换路由,
路由参数
产生了变动
,然而页面数据
并未
及时更新,须要强制刷新后才会变动。 - 不同路由渲染雷同的组件时(组件复用比销毁从新创立效率要高),在切换路由后,以后组件下的生命周期函数不会再被调用。
解决方案:
应用 watch 监听
watch: { $route(to, from){ if(to != from) { console.log("监听到路由变动,做出相应的解决"); } }}
向 router-view 组件中增加 key
<router-view :key="$route.fullPath"></router-view>
$route.fullPath
是实现后解析的URL,蕴含其查问参数信息和hash残缺门路
73. 切换到新路由时,面要滚动到顶部或放弃原先的滚动地位
在路由实例中配置scrollBehavior(ro,form,savedPosition){//滚动到顶部return {x:0,y:0}//放弃原先的滚动地位return {selector:falsy}}
74. 路由懒加载
75. MVVM
全称: Model-View-ViewModel
, Model 示意数据模型层
, view 示意视图层
, ViewModel 是 View 和 Model 层的桥梁
,数据绑定
到 viewModel
层并主动渲染到页面中,视图变动
告诉 viewModel
层更新数据。
76. vue中的事件绑定原理
事件绑定有几种?
- 原生的事件绑定,原生 dom 事件的绑定,采纳的是
addEventListener
实现。 - 组件的事件绑定,组件绑定事件采纳的是
$on
办法 。
一般元素
的原生
事件绑定在上是通过@click
进行绑定的组件
的原生
事件绑定是通过@click.native
进行绑定的,组件中的nativeOn
是等价于on的。组件
的自定义
事件是通过@click
绑定的,是通过$on
办法来实现的,必须有$emit
才能够触发。
解释下这2种的区别:
- 原生:比方咱们在原生便签上写一个事件
<div @click="getData"></div>
,间接触发的就是原生的点击事件 - 组件: 当初咱们自定义了个组件,想要组件下面写事件,
<BtnGroup @click="getName" @click.native="getData"></BtnGroup>
,这时候,要触发原生的点击事件getData,就须要应用修饰符.native
,因为间接应用@click
是接管来自子组件emit
过去的事件getName,这样才不会抵触。
let compiler = require('vue-template-compiler'); // vue loader中的包let r1 = compiler.compile('<div @click="fn()"></div>'); // 给一般标签绑定click事件// 给组件绑定一个事件,有两种绑定办法// 一种@click.native,这个绑定的就是原生事件// 另一种@click,这个绑定的就是组件自定义事件let r2 = compiler.compile('<my-component @click.native="fn" @click="fn1"></mycomponent>');console.log(r1.render); // {on:{click}} console.log(r2.render); // {nativeOn:{click},on:{click}}// 为什么组件要加native?因为组件最终会把nativeOn属性放到on的属性中去,这个on会独自解决// 组件中的nativeOn 等价于 一般元素on,组件on会独自解决
具体连贯
77. Vue为何采纳异步渲染
Vue在更新DOM
时是异步执行
的,只有侦听
到数据变动
,将开启
一个队列
,并缓冲
在同一事件循环中产生的所有数据变更
,如果同一个watcher
被屡次触发
,只会被推入
到队列
中一次
,这种在缓冲
时去除反复数据
对于缩小不必要
的计算和DOM操作
是十分重要的.
而后,在下一个的事件循环tick中,Vue刷新队列并执行理论(已去重的)工作,Vue在外部对异步队列尝试应用原生的Promise.then、MutationObserver和setImmediate
,如果执行环境不反对,则会采纳setTimeout(fn, 0)
代替。
形容
对于Vue为何采纳异步渲染,简略来说就是为了晋升性能
,因为不采纳异步更新,在每次更新
数据都会对以后组件进行从新渲染
,为了性能思考,Vue会在本轮数据
更新后,再去异步更新视图
,举个例子,让咱们在一个办法内反复更新一个值。
this.msg = 1;this.msg = 2;this.msg = 3;
事实上,咱们真正想要的其实只是最初一次更新而已,也就是说前三次DOM更新都是能够省略的,咱们只须要等所有状态都批改好了之后再进行渲染就能够缩小一些性能损耗。
对于渲染方面的问题是很明确的,最终只渲染一次必定比批改之后即渲染所消耗的性能少,在这里咱们还须要考虑一下异步更新队列的相干问题,假如咱们当初是进行了相干解决使得每次更新数据只进行一次实在DOM渲染,来让咱们思考异步更新队列的性能优化。
假如这里是同步更新队列,this.msg=1,大抵会产生这些事:
msg值更新 -> 触发setter -> 触发Watcher的update -> 从新调用 render -> 生成新的vdom -> dom-diff -> dom更新
这里的dom更新并不是渲染
(即布局、绘制、合成等一系列步骤),而是更新内存
中的DOM树结构
,之后再运行this.msg=2,再反复上述步骤,之后的第3次更新同样会触发雷同的流程,等开始渲染的时候,最新的DOM树中的确只会存在更新实现3,从这里来看,前2次对msg的操作以及Vue外部对它的解决都是无用的操作,能够进行优化解决。
如果是异步更新队列,会是上面的状况:
运行this.msg=1,并不是立刻进行下面的流程,而是将对msg有依赖的Watcher都保留在队列中,该队列可能这样[Watcher1, Watcher2...]
,当运行this.msg=2后,同样是将对msg有依赖的Watcher保留到队列中,Vue外部
会做去重判断
,这次操作后,能够认为队列数据没有发生变化,第3次更新也是下面的过程。
当然,你不可能只对msg有操作,你可能对该组件中的另一个属性也有操作,比方this.otherMsg=othermessage,同样会把对otherMsg有依赖的Watcher增加到异步更新队列中,因为有反复判断操作,这个Watcher也只会在队列中存在一次,本次异步工作执行完结后,会进入下一个工作执行流程,其实就是遍历异步更新队列中的每一个Watcher,触发其update,而后进行从新调用render -> new vdom -> dom-diff -> dom更新
等流程,然而这种形式和同步更新队列相比,不论操作多少次msg,Vue在外部只会进行一次从新调用实在更新流程。
所以,对于异步更新队列不是节俭了渲染老本
,而是节俭了Vue外部计算及DOM树操作的老本
,不论采纳哪种形式,渲染的确只有一次。
此外,组件外部理论应用VirtualDOM进行渲染,也就是说,组件外部其实是不关怀哪个状态产生了变动,它只须要计算一次就能够得悉哪些节点须要更新,也就是说,如果更改了N个状态,其实只须要发送一个信号就能够将DOM更新到最新,如果咱们更新多个值。
this.msg = 1;this.age = 2;this.name = 3;
此处咱们分三次批改了三种状态,但其实Vue只会渲染一次,因为VIrtualDOM只须要一次就能够将整个组件的DOM更新到最新,它基本不会关怀这个更新的信号到底是从哪个具体的状态收回来的。
而为了达到这个目标,咱们须要将渲染操作
推延到所有
的状态都批改实现
,为了做到这一点只须要将渲染操作推延到本轮事件循环的最初或者下一轮事件循环,也就是说,只须要在本轮事件循环
的最初
,等后面更新状态
的语句都执行完之后
,执行一次
渲染操作,它就能够忽视后面各种更新状态的语法,无论后面写了多少条更新状态的语句,只在最初渲染一次就能够了。
将渲染推延到本轮事件循环的最初执行渲染的时机会比推延到下一轮快很多,所以Vue优先将渲染操作推延到本轮事件循环的最初,如果执行环境不反对会降级到下一轮,Vue的变动侦测机制(setter)决定了它必然会在每次状态发生变化时都会收回渲染的信号,但Vue会在收到信号之后查看队列中是否曾经存在这个工作,保障队列中不会有反复,如果队列中不存在则将渲染操作增加到队列中,之后通过异步的形式提早执行队列中的所有渲染的操作并清空队列,当同一轮事件循环中重复批改状态时,并不会重复向队列中增加雷同的渲染操作,所以咱们在应用Vue时,批改状态后更新DOM都是异步的。
当数据变动
后会调用notify
办法,将watcher
遍历,调用update
办法告诉watcher进行更新,这时候watcher并不会立刻去执行,在update中会调用queueWatcher
办法将watcher放到了一个队列里,在queueWatcher会依据watcher的进行去重,若多个属性依赖一个watcher,则如果队列中没有该watcher就会将该watcher增加到队列中,而后便会在$nextTick
办法的执行队列中退出一个flushSchedulerQueue办法(这个办法将会触发在缓冲队列的所有回调的执行),而后将$nextTick
办法的回调退出$nextTick
办法中保护的执行队列,flushSchedulerQueue中开始会触发一个before的办法,其实就是beforeUpdate,而后watcher.run()才开始真正执行watcher,执行完页面就渲染实现,更新实现后会调用updated钩子。
$nextTick
在上文中谈到了对于Vue为何采纳异步渲染,如果此时咱们有一个需要,须要在页面渲染实现后获得页面的DOM元素,而因为渲染是异步的,咱们不能间接在定义的办法中同步获得这个值的,于是就有了vm.$nextTick
办法,Vue中$nextTick
办法将回调提早到下次DOM更新循环之后执行,也就是在下次DOM更新循环完结之后执行提早回调,在批改数据之后立刻应用这个办法,可能获取更新后的DOM。简略来说就是当数据更新时,在DOM中渲染实现后,执行回调函数。
通过一个简略的例子来演示$nextTick
办法的作用,首先须要晓得Vue在更新DOM时是异步执行的,也就是说在更新数据时其不会阻塞代码的执行,直到执行栈中代码执行完结之后,才开始执行异步工作队列的代码,所以在数据更新时,组件不会立刻渲染,此时在获取到DOM构造后获得的值仍然是旧的值,而在$nextTick
办法中设定的回调函数会在组件渲染实现之后执行,获得DOM构造后获得的值便是新的值。
<!DOCTYPE html><html><head> <title>Vue</title></head><body> <div id="app"></div></body><script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script><script type="text/javascript"> var vm = new Vue({ el: '#app', data: { msg: 'Vue' }, template:` <div> <div ref="msgElement">{{msg}}</div> <button @click="updateMsg">updateMsg</button> </div> `, methods:{ updateMsg: function(){ this.msg = "Update"; console.log("DOM未更新:", this.$refs.msgElement.innerHTML) this.$nextTick(() => { console.log("DOM已更新:", this.$refs.msgElement.innerHTML) }) } }, })</script></html>
异步机制#
Js是单线程的,其引入了同步阻塞与异步非阻塞的执行模式,在Js异步模式中保护了一个Event Loop
,Event Loop是一个执行模型
,在不同的中央有不同的实现,浏览器和NodeJS基于不同的技术实现了各自的Event Loop。浏览器的Event Loop是在HTML5
的标准中明确定义,NodeJS的Event Loop是基于libuv
实现的。
在浏览器中的Event Loop
由执行栈
Execution Stack、后盾线程
Background Threads、宏队列
Macrotask Queue、微队列
Microtask Queue组成。
- 执行栈就是在
主线程
执行同步工作
的数据结构
,函数调用造成了一个由若干帧组成的栈。 - 后盾线程就是浏览器实现对于
setTimeout、setInterval、XMLHttpRequest
等等的执行线程。 - 宏队列,一些
异步工作
的回调会顺次进入宏队列,期待后续被调用,包含setTimeout、setInterval、setImmediate(Node)、requestAnimationFrame、UI rendering、I/O等操作。 - 微队列,另一些
异步工作
的回调会顺次进入微队列,期待后续调用,包含Promise、process.nextTick(Node)、Object.observe、MutationObserver等操作。
当Js执行时,进行如下流程:
- 首先将执行栈中代码同步执行,将这些代码中异步工作退出后盾线程中。
- 执行栈中的同步代码执行结束后,执行栈清空,并开始扫描微队列。
- 取出微队列队首工作,放入执行栈中执行,此时微队列是进行了出队操作。
- 当执行栈执行实现后,持续出队微队列工作并执行,直到微队列工作全副执行结束。
- 最初一个微队列工作出队并进入执行栈后微队列中工作为空,当执行栈工作实现后,开始扫面微队列为空,持续扫描宏队列工作,宏队列出队,放入执行栈中执行,执行结束后持续扫描微队列为空则扫描宏队列,出队执行。一直往返...。
实例#
// Step 1console.log(1);// Step 2setTimeout(() => { console.log(2); Promise.resolve().then(() => { console.log(3); });}, 0);// Step 3new Promise((resolve, reject) => { console.log(4); resolve();}).then(() => { console.log(5);})// Step 4setTimeout(() => { console.log(6);}, 0);// Step 5console.log(7);// Step N// ...// Result/* 1 4 7 5 2 3 6*/
剖析#
在理解异步工作的执行队列后,回到中$nextTick
办法,当用户数据更新时,Vue将会保护一个缓冲队列
,对于所有的更新数据将要进行的组件渲染与DOM操作进行肯定的策略解决后退出缓冲队列,而后便会在$nextTick
办法的执行队列中退出一个flushSchedulerQueue
办法(这个办法将会触发在缓冲队列的所有回调的执行),而后将$nextTick
办法的回调退出$nextTick
办法中保护的执行队列,在异步挂载的执行队列触发时就会首先会首先执行flushSchedulerQueue
办法来解决DOM渲染的工作,而后再去执行$nextTick
办法构建的工作,这样就能够实现在$nextTick
办法中获得已渲染实现的DOM构造。
在测试的过程中发现了一个很有意思的景象,在上述例子中的退出两个按钮,在点击updateMsg按钮的后果是3 2 1,点击updateMsgTest按钮的运行后果是2 3 1。
<!DOCTYPE html><html><head> <title>Vue</title></head><body> <div id="app"></div></body><script src="https://cdn.bootcss.com/vue/2.4.2/vue.js"></script><script type="text/javascript"> var vm = new Vue({ el: '#app', data: { msg: 'Vue' }, template:` <div> <div ref="msgElement">{{msg}}</div> <button @click="updateMsg">updateMsg</button> <button @click="updateMsgTest">updateMsgTest</button> </div> `, methods:{ updateMsg: function(){ this.msg = "Update"; setTimeout(() => console.log(1)) Promise.resolve().then(() => console.log(2)) this.$nextTick(() => { console.log(3) }) }, updateMsgTest: function(){ setTimeout(() => console.log(1)) Promise.resolve().then(() => console.log(2)) this.$nextTick(() => { console.log(3) }) } }, })</script></html>
这里假如运行环境中Promise对象是齐全反对的,那么应用setTimeout是宏队列在最初执行这个是没有异议的,然而应用$nextTick
办法以及自行定义的Promise实例是有执行程序的问题的,尽管都是微队列工作,然而在Vue中具体实现的起因导致了执行程序可能会有所不同,首先间接看一下$nextTick
办法的源码,要害中央增加了正文,请留神这是Vue2.4.2版本的源码,在前期$nextTick
办法可能有所变更。
/** * Defer a task to execute it asynchronously. */var nextTick = (function () { // 闭包 外部变量 var callbacks = []; // 执行队列 var pending = false; // 标识,用以判断在某个事件循环中是否为第一次退出,第一次退出的时候才触发异步执行的队列挂载 var timerFunc; // 以何种办法执行挂载异步执行队列,这里假如Promise是齐全反对的 function nextTickHandler () { // 异步挂载的执行工作,触发时就曾经正式筹备开始执行异步工作了 pending = false; // 标识置false var copies = callbacks.slice(0); // 创立正本 callbacks.length = 0; // 执行队列置空 for (var i = 0; i < copies.length; i++) { copies[i](); // 执行 } } // 如果反对promise if (typeof Promise !== 'undefined' && isNative(Promise)) { var p = Promise.resolve(); var logError = function (err) { console.error(err); }; timerFunc = function () { p.then(nextTickHandler).catch(logError); // 挂载异步工作队列 if (isIOS) { setTimeout(noop); } }; } else if (typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]' )) { var counter = 1; var observer = new MutationObserver(nextTickHandler); var textNode = document.createTextNode(String(counter)); observer.observe(textNode, { characterData: true }); timerFunc = function () { counter = (counter + 1) % 2; textNode.data = String(counter); }; } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = function () { setTimeout(nextTickHandler, 0); }; } return function queueNextTick (cb, ctx) { // nextTick办法真正导出的办法 var _resolve; callbacks.push(function () { // 增加到执行队列中 并退出异样解决 if (cb) { try { cb.call(ctx); } catch (e) { handleError(e, ctx, 'nextTick'); } } else if (_resolve) { _resolve(ctx); } }); //判断在以后事件循环中是否为第一次退出,若是第一次退出则置标识为true并执行timerFunc函数用以挂载执行队列到Promise // 这个标识在执行队列中的工作将要执行时便置为false并创立执行队列的正本去运行执行队列中的工作,参见nextTickHandler函数的实现 // 在以后事件循环中置标识true并挂载,而后再次调用nextTick办法时只是将工作退出到执行队列中,直到挂载的异步工作触发,便置标识为false而后执行工作,再次调用nextTick办法时就是同样的执行形式而后一直如此往返 if (!pending) { pending = true; timerFunc(); } if (!cb && typeof Promise !== 'undefined') { return new Promise(function (resolve, reject) { _resolve = resolve; }) } }})();
回到方才提出的问题上,在更新DOM操作时会先触发$nextTick
办法的回调,解决这个问题的关键在于谁先将异步工作挂载到Promise对象上。
首先对有数据更新的updateMsg按钮触发的办法进行debug,断点设置在Vue.js的715行,版本为2.4.2,在查看调用栈以及传入的参数时能够察看到第一次执行$nextTick
办法的其实是因为数据更新而调用的nextTick(flushSchedulerQueue)
语句,也就是说在执行this.msg = "Update";的时候就曾经触发了第一次的$nextTick
办法,此时在$nextTick
办法中的工作队列会首先将flushSchedulerQueue办法退出队列并挂载$nextTick
办法的执行队列到Promise对象上,而后才是自行自定义的Promise.resolve().then(() => console.log(2))
语句的挂载,当执行微工作队列中的工作时,首先会执行第一个挂载到Promise的工作,此时这个工作是运行执行队列,这个队列中有两个办法,首先会运行flushSchedulerQueue办法去触发组件的DOM渲染操作,而后再执行console.log(3),而后执行第二个微队列的工作也就是() => console.log(2),此时微工作队列清空,而后再去宏工作队列执行console.log(1)。
接下来对于没有数据更新的updateMsgTest按钮触发的办法进行debug,断点设置在同样的地位,此时没有数据更新,那么第一次触发$nextTic
k办法的是自行定义的回调函数,那么此时$nextTick
办法的执行队列才会被挂载到Promise对象上,很显然在此之前自行定义的输入2的Promise回调曾经被挂载,那么对于这个按钮绑定的办法的执行流程便是首先执行console.log(2),而后执行$nextTick
办法闭包的执行队列,此时执行队列中只有一个回调函数console.log(3),此时微工作队列清空,而后再去宏工作队列执行console.log(1)。
简略来说就是谁先挂载Promise对象的问题,在调用$nextTick
办法时就会将其闭包外部保护的执行队列挂载到Promise对象,在数据更新时Vue外部首先就会执行$nextTick
办法,之后便将执行队列挂载到了Promise对象上,其实在明确Js的Event Loop模型后,将数据更新也看做一个$nextTick
办法的调用,并且明确$nextTick
办法会一次性执行所有推入的回调,就能够明确其执行程序的问题了,上面是一个对于$nextTick办法的最小化的DEMO。
var nextTick = (function(){ var pending = false; const callback = []; var p = Promise.resolve(); var handler = function(){ pending = true; callback.forEach(fn => fn()); } var timerFunc = function(){ p.then(handler); } return function queueNextTick(fn){ callback.push(() => fn()); if(!pending){ pending = true; timerFunc(); } }})();(function(){ nextTick(() => console.log("触发DOM渲染队列的办法")); // 正文 / 勾销正文 来查看成果 setTimeout(() => console.log(1)) Promise.resolve().then(() => console.log(2)) nextTick(() => { console.log(3) })})();