本文整理了高频出现的 Vue 相关面试题并且附带详解答案 难度分为简单 中等 困难 三种类型 大家可以先不看答案自测一下自己的 Vue 水平哈 如果对原理感兴趣的同学 欢迎查看小编的手写 Vue 源码系列文章 如果对答案有不一样见解的同学欢迎评论区补充讨论 最后欢迎大家点击 链接 加入到鲨鱼哥的前端群 内推 讨论技术 摸鱼 求助 皆可(进群免费领取 Vue2 源码思维导图哈)
整理不易 如果觉得本文有帮助 记得点赞三连哦 十分感谢!
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范
MVC 的思想:一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。
MVVM 新增了 VM 类
MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)
整体看来,MVVM 比 MVC 精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作 DOM 元素。因为在 MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也观察不到 View,这种低耦合模式提高代码的可重用性
注意:Vue 并没有完全遵循 MVVM 的思想 这一点官网自己也有说明
那么问题来了 为什么官方要说 Vue 没有完全遵循 MVVM 思想呢?
- 严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果
beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,可以通过 vm.$nextTick 来访问 Dom
beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted 在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点
beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁(patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated 发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行善后收尾工作,比如清除计时器。
destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
activated keep-alive 专属,组件被激活时调用
deactivated keep-alive 专属,组件被销毁时调用
异步请求在哪一步发起?
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (display:none)
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
v-show 适用于需要非常频繁切换条件的场景
扩展补充:display:none、visibility:hidden 和 opacity:0 之间的区别?
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
注意:在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告
如果实在要改变父组件的 prop 值 可以再 data 里面定义一个变量 并用 prop 的值初始化它 之后用$emit 通知父组件去修改
computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,它可以设置 getter 和 setter。
watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
计算属性一般用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
计算属性原理详解 传送门
侦听属性原理详解 传送门
v-for 和 v-if 不要在同一个标签中使用,因为解析时先解析 v-for 再解析 v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式。
整体思路是数据劫持+观察者模式
对象内部通过 defineReactive 方法,使用 Object.defineProperty 将属性进行劫持(只会劫持已经存在的属性),数组则是通过重写数组方法来实现。当页面使用对应属性时,每个属性都拥有自己的 dep 属性,存放他所依赖的 watcher(依赖收集),当属性变化后会通知自己对应的 watcher 去更新(派发更新)。
相关代码如下
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 |
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Observer</span> </span>{ <span class="hljs-comment">// 观测值</span> <span class="hljs-function"><span class="hljs-title">constructor</span>(<span class="hljs-params">value</span>)</span> { <span class="hljs-built_in">this</span>.walk(value); } <span class="hljs-function"><span class="hljs-title">walk</span>(<span class="hljs-params">data</span>)</span> { <span class="hljs-comment">// 对象上的所有属性依次进行观测</span> <span class="hljs-keyword">let</span> keys = <span class="hljs-built_in">Object</span>.keys(data); <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < keys.length; i++) { <span class="hljs-keyword">let</span> key = keys[i]; <span class="hljs-keyword">let</span> value = data[key]; defineReactive(data, key, value); } } } <span class="hljs-comment">// Object.defineProperty数据劫持核心 兼容性在ie9以及以上</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">defineReactive</span>(<span class="hljs-params">data, key, value</span>) </span>{ observe(value); <span class="hljs-comment">// 递归关键</span> <span class="hljs-comment">// --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止</span> <span class="hljs-comment">// 思考?如果Vue数据嵌套层级过深 >>性能会受影响</span> <span class="hljs-built_in">Object</span>.defineProperty(data, key, { <span class="hljs-function"><span class="hljs-title">get</span>()</span> { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"获取值"</span>); <span class="hljs-comment">//需要做依赖收集过程 这里代码没写出来</span> <span class="hljs-keyword">return</span> value; }, <span class="hljs-function"><span class="hljs-title">set</span>(<span class="hljs-params">newValue</span>)</span> { <span class="hljs-keyword">if</span> (newValue === value) <span class="hljs-keyword">return</span>; <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"设置值"</span>); <span class="hljs-comment">//需要做派发更新过程 这里代码没写出来</span> value = newValue; }, }); } <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">observe</span>(<span class="hljs-params">value</span>) </span>{ <span class="hljs-comment">// 如果传过来的是对象或者数组 进行属性劫持</span> <span class="hljs-keyword">if</span> ( <span class="hljs-built_in">Object</span>.prototype.toString.call(value) === <span class="hljs-string">"[object Object]"</span> || <span class="hljs-built_in">Array</span>.isArray(value) ) { <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Observer(value); } } <span class="copy-code-btn">复制代码</span> |
响应式数据原理详解 传送门
数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择对 7 种数组(push,shift,pop,splice,unshift,sort,reverse)方法进行重写(AOP 切片思想)
所以在 Vue 中修改数组的索引和长度是无法监控到的。需要通过以上 7 种变异方法修改数组才会触发数组对应的 watcher 进行更新
相关代码如下
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 |
<span class="hljs-comment">// src/obserber/array.js</span> <span class="hljs-comment">// 先保留数组原型</span> <span class="hljs-keyword">const</span> arrayProto = <span class="hljs-built_in">Array</span>.prototype; <span class="hljs-comment">// 然后将arrayMethods继承自数组原型</span> <span class="hljs-comment">// 这里是面向切片编程思想(AOP)--不破坏封装的前提下,动态的扩展功能</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> arrayMethods = <span class="hljs-built_in">Object</span>.create(arrayProto); <span class="hljs-keyword">let</span> methodsToPatch = [ <span class="hljs-string">"push"</span>, <span class="hljs-string">"pop"</span>, <span class="hljs-string">"shift"</span>, <span class="hljs-string">"unshift"</span>, <span class="hljs-string">"splice"</span>, <span class="hljs-string">"reverse"</span>, <span class="hljs-string">"sort"</span>, ]; methodsToPatch.forEach(<span class="hljs-function">(<span class="hljs-params">method</span>) =></span> { arrayMethods[method] = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">...args</span>) </span>{ <span class="hljs-comment">// 这里保留原型方法的执行结果</span> <span class="hljs-keyword">const</span> result = arrayProto[method].apply(<span class="hljs-built_in">this</span>, args); <span class="hljs-comment">// 这句话是关键</span> <span class="hljs-comment">// this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4) this就是a ob就是a.__ob__ 这个属性就是上段代码增加的 代表的是该数据已经被响应式观察过了指向Observer实例</span> <span class="hljs-keyword">const</span> ob = <span class="hljs-built_in">this</span>.__ob__; <span class="hljs-comment">// 这里的标志就是代表数组有新增操作</span> <span class="hljs-keyword">let</span> inserted; <span class="hljs-keyword">switch</span> (method) { <span class="hljs-keyword">case</span> <span class="hljs-string">"push"</span>: <span class="hljs-keyword">case</span> <span class="hljs-string">"unshift"</span>: inserted = args; <span class="hljs-keyword">break</span>; <span class="hljs-keyword">case</span> <span class="hljs-string">"splice"</span>: inserted = args.slice(<span class="hljs-number">2</span>); <span class="hljs-keyword">default</span>: <span class="hljs-keyword">break</span>; } <span class="hljs-comment">// 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测</span> <span class="hljs-keyword">if</span> (inserted) ob.observeArray(inserted); <span class="hljs-comment">// 之后咱们还可以在这里检测到数组改变了之后从而触发视图更新的操作--后续源码会揭晓</span> <span class="hljs-keyword">return</span> result; }; }); <span class="copy-code-btn">复制代码</span> |
数组的观测原理详解 传送门
Vue3.0 新特性以及使用经验总结 传送门
Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。
相关代码如下
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 |
<span class="hljs-keyword">import</span> { mutableHandlers } <span class="hljs-keyword">from</span> <span class="hljs-string">"./baseHandlers"</span>; <span class="hljs-comment">// 代理相关逻辑</span> <span class="hljs-keyword">import</span> { isObject } <span class="hljs-keyword">from</span> <span class="hljs-string">"./util"</span>; <span class="hljs-comment">// 工具方法</span> <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reactive</span>(<span class="hljs-params">target</span>) </span>{ <span class="hljs-comment">// 根据不同参数创建不同响应式对象</span> <span class="hljs-keyword">return</span> createReactiveObject(target, mutableHandlers); } <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createReactiveObject</span>(<span class="hljs-params">target, baseHandler</span>) </span>{ <span class="hljs-keyword">if</span> (!isObject(target)) { <span class="hljs-keyword">return</span> target; } <span class="hljs-keyword">const</span> observed = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Proxy</span>(target, baseHandler); <span class="hljs-keyword">return</span> observed; } <span class="hljs-keyword">const</span> get = createGetter(); <span class="hljs-keyword">const</span> set = createSetter(); <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createGetter</span>() </span>{ <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params">target, key, receiver</span>) </span>{ <span class="hljs-comment">// 对获取的值进行放射</span> <span class="hljs-keyword">const</span> res = <span class="hljs-built_in">Reflect</span>.get(target, key, receiver); <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"属性获取"</span>, key); <span class="hljs-keyword">if</span> (isObject(res)) { <span class="hljs-comment">// 如果获取的值是对象类型,则返回当前对象的代理对象</span> <span class="hljs-keyword">return</span> reactive(res); } <span class="hljs-keyword">return</span> res; }; } <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createSetter</span>() </span>{ <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params">target, key, value, receiver</span>) </span>{ <span class="hljs-keyword">const</span> oldValue = target[key]; <span class="hljs-keyword">const</span> hadKey = hasOwn(target, key); <span class="hljs-keyword">const</span> result = <span class="hljs-built_in">Reflect</span>.set(target, key, value, receiver); <span class="hljs-keyword">if</span> (!hadKey) { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"属性新增"</span>, key, value); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (hasChanged(value, oldValue)) { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"属性值被修改"</span>, key, value); } <span class="hljs-keyword">return</span> result; }; } <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> mutableHandlers = { get, <span class="hljs-comment">// 当获取属性时调用此方法</span> set, <span class="hljs-comment">// 当修改属性时调用此方法</span> }; <span class="copy-code-btn">复制代码</span> |
父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted
父 beforeUpdate->子 beforeUpdate->子 updated->父 updated
父 beforeUpdate->父 updated
父 beforeDestroy->子 beforeDestroy->子 destroyed->父 destroyed
由于在浏览器中操作 DOM 是很昂贵的。频繁的操作 DOM,会产生一定的性能问题。这就是虚拟 Dom 的产生原因。Vue2 的 Virtual DOM 借鉴了开源库 snabbdom 的实现。Virtual DOM 本质就是用一个原生的 JS 对象去描述一个 DOM 节点,是对真实 DOM 的一层抽象。
优点:
缺点:
v-model 只是语法糖而已
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
注意:对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。
在普通标签上
1 2 3 |
<input v-model=<span class="hljs-string">"sth"</span> /> <span class="hljs-comment">//这一行等于下一行</span> <span class="xml"><span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">v-bind:value</span>=<span class="hljs-string">"sth"</span> <span class="hljs-attr">v-on:input</span>=<span class="hljs-string">"sth = $event.target.value"</span> /></span></span> <span class="copy-code-btn">复制代码</span> |
在组件上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<span class="hljs-tag"><<span class="hljs-name">currency-input</span> <span class="hljs-attr">v-model</span>=<span class="hljs-string">"price"</span>></span><span class="hljs-tag"></<span class="hljs-name">currentcy-input</span>></span> <span class="hljs-comment"><!--上行代码是下行的语法糖 <currency-input :value="price" @input="price = arguments[0]"></currency-input> --></span> <span class="hljs-comment"><!-- 子组件定义 --></span> Vue.component('currency-input', { template: ` <span class="hljs-tag"><<span class="hljs-name">span</span>></span> <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">"input"</span> <span class="hljs-attr">:value</span>=<span class="hljs-string">"value"</span> @<span class="hljs-attr">input</span>=<span class="hljs-string">"$emit('input', $event.target.value)"</span> ></span> <span class="hljs-tag"></<span class="hljs-name">span</span>></span> `, props: ['value'], }) <span class="copy-code-btn">复制代码</span> |
如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快
相关代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class="hljs-comment">// 判断两个vnode的标签和key是否相同 如果相同 就可以认为是同一节点就地复用</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isSameVnode</span>(<span class="hljs-params">oldVnode, newVnode</span>) </span>{ <span class="hljs-keyword">return</span> oldVnode.tag === newVnode.tag && oldVnode.key === newVnode.key; } <span class="hljs-comment">// 根据key来创建老的儿子的index映射表 类似 {'a':0,'b':1} 代表key为'a'的节点在第一个位置 key为'b'的节点在第二个位置</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeIndexByKey</span>(<span class="hljs-params">children</span>) </span>{ <span class="hljs-keyword">let</span> map = {}; children.forEach(<span class="hljs-function">(<span class="hljs-params">item, index</span>) =></span> { map[item.key] = index; }); <span class="hljs-keyword">return</span> map; } <span class="hljs-comment">// 生成的映射表</span> <span class="hljs-keyword">let</span> map = makeIndexByKey(oldCh); <span class="copy-code-btn">复制代码</span> |
diff 算法详解 传送门
原生事件绑定是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 Vue 自定义的$on 实现的。如果要在组件上使用原生事件,需要加.native 修饰符,这样就相当于在父组件中把子组件当做普通 html 标签,然后加上原生事件。
$on、$emit 是基于发布订阅模式的,维护一个事件中心,on 的时候将事件按名称存在事件中心里,称之为订阅者,然后 emit 将对应的事件进行发布,去执行事件中心里的对应的监听器
手写发布订阅原理 传送门
路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫
完整的导航解析流程:
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
1 2 3 4 5 6 7 8 9 10 11 |
<span class="hljs-keyword">const</span> User = { <span class="hljs-attr">template</span>: <span class="hljs-string">"<div>User</div>"</span>, }; <span class="hljs-keyword">const</span> router = <span class="hljs-keyword">new</span> VueRouter({ <span class="hljs-attr">routes</span>: [ <span class="hljs-comment">// 动态路径参数 以冒号开头</span> { <span class="hljs-attr">path</span>: <span class="hljs-string">"/user/:id"</span>, <span class="hljs-attr">component</span>: User }, ], }); <span class="copy-code-btn">复制代码</span> |
问题:vue-router 组件复用导致路由参数失效怎么办?
解决方法:
1.通过 watch 监听路由参数再发请求
1 2 3 4 5 6 7 |
watch: { <span class="hljs-comment">//通过watch来监听路由变化</span> <span class="hljs-string">"$route"</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>()</span>{ <span class="hljs-built_in">this</span>.getData(<span class="hljs-built_in">this</span>.$route.params.xxx); } } <span class="copy-code-btn">复制代码</span> |
2.用 :key 来阻止“复用”
1 2 |
<span class="hljs-tag"><<span class="hljs-name">router-view</span> <span class="hljs-attr">:key</span>=<span class="hljs-string">"$route.fullPath"</span> /></span> <span class="copy-code-btn">复制代码</span> |
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
主要包括以下几个模块:
需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件
推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
模块:由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间:默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端。
优点:
SSR 有着更好的 SEO、并且首屏加载速度更快
缺点: 开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。
服务器会有更大的负载需求
1.工厂模式 – 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装饰模式: (@装饰器的用法)
6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
…其他模式欢迎补充
这里只列举针对 Vue 的性能优化 整个项目的性能优化是一个大工程 可以另写一篇性能优化的文章 哈哈
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
相关代码如下
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 |
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initMixin</span>(<span class="hljs-params">Vue</span>)</span>{ Vue.mixin = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">mixin</span>) </span>{ <span class="hljs-comment">// 合并对象</span> <span class="hljs-built_in">this</span>.options=mergeOptions(<span class="hljs-built_in">this</span>.options,mixin) }; } }; <span class="hljs-comment">// src/util/index.js</span> <span class="hljs-comment">// 定义生命周期</span> <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> LIFECYCLE_HOOKS = [ <span class="hljs-string">"beforeCreate"</span>, <span class="hljs-string">"created"</span>, <span class="hljs-string">"beforeMount"</span>, <span class="hljs-string">"mounted"</span>, <span class="hljs-string">"beforeUpdate"</span>, <span class="hljs-string">"updated"</span>, <span class="hljs-string">"beforeDestroy"</span>, <span class="hljs-string">"destroyed"</span>, ]; <span class="hljs-comment">// 合并策略</span> <span class="hljs-keyword">const</span> strats = {}; <span class="hljs-comment">// mixin核心方法</span> <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mergeOptions</span>(<span class="hljs-params">parent, child</span>) </span>{ <span class="hljs-keyword">const</span> options = {}; <span class="hljs-comment">// 遍历父亲</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> k <span class="hljs-keyword">in</span> parent) { mergeFiled(k); } <span class="hljs-comment">// 父亲没有 儿子有</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> k <span class="hljs-keyword">in</span> child) { <span class="hljs-keyword">if</span> (!parent.hasOwnProperty(k)) { mergeFiled(k); } } <span class="hljs-comment">//真正合并字段方法</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mergeFiled</span>(<span class="hljs-params">k</span>) </span>{ <span class="hljs-keyword">if</span> (strats[k]) { options[k] = strats[k](parent[k], child[k]); } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 默认策略</span> options[k] = child[k] ? child[k] : parent[k]; } } <span class="hljs-keyword">return</span> options; } <span class="copy-code-btn">复制代码</span> |
Vue.mixin 原理详解 传送门
nextTick 中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。主要思路就是采用微任务优先的方式调用异步方法去执行 nextTick 包装的方法
相关代码如下
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 |
<span class="hljs-keyword">let</span> callbacks = []; <span class="hljs-keyword">let</span> pending = <span class="hljs-literal">false</span>; <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">flushCallbacks</span>() </span>{ pending = <span class="hljs-literal">false</span>; <span class="hljs-comment">//把标志还原为false</span> <span class="hljs-comment">// 依次执行回调</span> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < callbacks.length; i++) { callbacks[i](); } } <span class="hljs-keyword">let</span> timerFunc; <span class="hljs-comment">//定义异步方法 采用优雅降级</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> <span class="hljs-built_in">Promise</span> !== <span class="hljs-string">"undefined"</span>) { <span class="hljs-comment">// 如果支持promise</span> <span class="hljs-keyword">const</span> p = <span class="hljs-built_in">Promise</span>.resolve(); timerFunc = <span class="hljs-function">() =></span> { p.then(flushCallbacks); }; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> MutationObserver !== <span class="hljs-string">"undefined"</span>) { <span class="hljs-comment">// MutationObserver 主要是监听dom变化 也是一个异步方法</span> <span class="hljs-keyword">let</span> counter = <span class="hljs-number">1</span>; <span class="hljs-keyword">const</span> observer = <span class="hljs-keyword">new</span> MutationObserver(flushCallbacks); <span class="hljs-keyword">const</span> textNode = <span class="hljs-built_in">document</span>.createTextNode(<span class="hljs-built_in">String</span>(counter)); observer.observe(textNode, { <span class="hljs-attr">characterData</span>: <span class="hljs-literal">true</span>, }); timerFunc = <span class="hljs-function">() =></span> { counter = (counter + <span class="hljs-number">1</span>) % <span class="hljs-number">2</span>; textNode.data = <span class="hljs-built_in">String</span>(counter); }; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> setImmediate !== <span class="hljs-string">"undefined"</span>) { <span class="hljs-comment">// 如果前面都不支持 判断setImmediate</span> timerFunc = <span class="hljs-function">() =></span> { setImmediate(flushCallbacks); }; } <span class="hljs-keyword">else</span> { <span class="hljs-comment">// 最后降级采用setTimeout</span> timerFunc = <span class="hljs-function">() =></span> { <span class="hljs-built_in">setTimeout</span>(flushCallbacks, <span class="hljs-number">0</span>); }; } <span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nextTick</span>(<span class="hljs-params">cb</span>) </span>{ <span class="hljs-comment">// 除了渲染watcher 还有用户自己手动调用的nextTick 一起被收集到数组</span> callbacks.push(cb); <span class="hljs-keyword">if</span> (!pending) { <span class="hljs-comment">// 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false</span> pending = <span class="hljs-literal">true</span>; timerFunc(); } } <span class="copy-code-btn">复制代码</span> |
nextTick 原理详解 传送门
keep-alive 是 Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
相关代码如下
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 77 78 79 80 81 |
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> { <span class="hljs-attr">name</span>: <span class="hljs-string">"keep-alive"</span>, <span class="hljs-attr">abstract</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">//抽象组件</span> <span class="hljs-attr">props</span>: { <span class="hljs-attr">include</span>: patternTypes, <span class="hljs-comment">//要缓存的组件</span> <span class="hljs-attr">exclude</span>: patternTypes, <span class="hljs-comment">//要排除的组件</span> <span class="hljs-attr">max</span>: [<span class="hljs-built_in">String</span>, <span class="hljs-built_in">Number</span>], <span class="hljs-comment">//最大缓存数</span> }, <span class="hljs-function"><span class="hljs-title">created</span>()</span> { <span class="hljs-built_in">this</span>.cache = <span class="hljs-built_in">Object</span>.create(<span class="hljs-literal">null</span>); <span class="hljs-comment">//缓存对象 {a:vNode,b:vNode}</span> <span class="hljs-built_in">this</span>.keys = []; <span class="hljs-comment">//缓存组件的key集合 [a,b]</span> }, <span class="hljs-function"><span class="hljs-title">destroyed</span>()</span> { <span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> key <span class="hljs-keyword">in</span> <span class="hljs-built_in">this</span>.cache) { pruneCacheEntry(<span class="hljs-built_in">this</span>.cache, key, <span class="hljs-built_in">this</span>.keys); } }, <span class="hljs-function"><span class="hljs-title">mounted</span>()</span> { <span class="hljs-comment">//动态监听include exclude</span> <span class="hljs-built_in">this</span>.$watch(<span class="hljs-string">"include"</span>, <span class="hljs-function">(<span class="hljs-params">val</span>) =></span> { pruneCache(<span class="hljs-built_in">this</span>, <span class="hljs-function">(<span class="hljs-params">name</span>) =></span> matches(val, name)); }); <span class="hljs-built_in">this</span>.$watch(<span class="hljs-string">"exclude"</span>, <span class="hljs-function">(<span class="hljs-params">val</span>) =></span> { pruneCache(<span class="hljs-built_in">this</span>, <span class="hljs-function">(<span class="hljs-params">name</span>) =></span> !matches(val, name)); }); }, <span class="hljs-function"><span class="hljs-title">render</span>()</span> { <span class="hljs-keyword">const</span> slot = <span class="hljs-built_in">this</span>.$slots.default; <span class="hljs-comment">//获取包裹的插槽默认值</span> <span class="hljs-keyword">const</span> vnode: VNode = getFirstComponentChild(slot); <span class="hljs-comment">//获取第一个子组件</span> <span class="hljs-keyword">const</span> componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions; <span class="hljs-keyword">if</span> (componentOptions) { <span class="hljs-comment">// check pattern</span> <span class="hljs-keyword">const</span> name: ?string = getComponentName(componentOptions); <span class="hljs-keyword">const</span> { include, exclude } = <span class="hljs-built_in">this</span>; <span class="hljs-comment">// 不走缓存</span> <span class="hljs-keyword">if</span> ( <span class="hljs-comment">// not included 不包含</span> (include && (!name || !matches(include, name))) || <span class="hljs-comment">// excluded 排除里面</span> (exclude && name && matches(exclude, name)) ) { <span class="hljs-comment">//返回虚拟节点</span> <span class="hljs-keyword">return</span> vnode; } <span class="hljs-keyword">const</span> { cache, keys } = <span class="hljs-built_in">this</span>; <span class="hljs-keyword">const</span> key: ?string = vnode.key == <span class="hljs-literal">null</span> ? <span class="hljs-comment">// same constructor may get registered as different local components</span> <span class="hljs-comment">// so cid alone is not enough (#3269)</span> componentOptions.Ctor.cid + (componentOptions.tag ? <span class="hljs-string">`::<span class="hljs-subst">${componentOptions.tag}</span>`</span> : <span class="hljs-string">""</span>) : vnode.key; <span class="hljs-keyword">if</span> (cache[key]) { <span class="hljs-comment">//通过key 找到缓存 获取实例</span> vnode.componentInstance = cache[key].componentInstance; <span class="hljs-comment">// make current key freshest</span> remove(keys, key); <span class="hljs-comment">//通过LRU算法把数组里面的key删掉</span> keys.push(key); <span class="hljs-comment">//把它放在数组末尾</span> } <span class="hljs-keyword">else</span> { cache[key] = vnode; <span class="hljs-comment">//没找到就换存下来</span> keys.push(key); <span class="hljs-comment">//把它放在数组末尾</span> <span class="hljs-comment">// prune oldest entry //如果超过最大值就把数组第0项删掉</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.max && keys.length > <span class="hljs-built_in">parseInt</span>(<span class="hljs-built_in">this</span>.max)) { pruneCacheEntry(cache, keys[<span class="hljs-number">0</span>], keys, <span class="hljs-built_in">this</span>._vnode); } } vnode.data.keepAlive = <span class="hljs-literal">true</span>; <span class="hljs-comment">//标记虚拟节点已经被缓存</span> } <span class="hljs-comment">// 返回虚拟节点</span> <span class="hljs-keyword">return</span> vnode || (slot && slot[<span class="hljs-number">0</span>]); }, }; <span class="copy-code-btn">复制代码</span> |
扩展补充:LRU 算法是什么?
LRU 的核心思想是如果数据最近被访问过,那么将来被访问的几率也更高,所以我们将命中缓存的组件 key 重新插入到 this.keys 的尾部,这样一来,this.keys 中越往头部的数据即将来被访问几率越低,所以当缓存数量达到最大值时,我们就删除将来被访问几率最低的数据,即 this.keys 中第一个缓存的组件。
了解 Vue 响应式原理的同学都知道在两种情况下修改数据 Vue 是不会触发视图更新的
1.在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
2.直接更改数组下标来修改数组的值
Vue.set 或者说是$set 原理如下
因为响应式数据 我们给对象和数组本身都增加了__ob__属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象__ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组
相关代码如下
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 |
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">set</span>(<span class="hljs-params">target: <span class="hljs-built_in">Array</span> | <span class="hljs-built_in">Object</span>, key: <span class="hljs-built_in">any</span>, val: <span class="hljs-built_in">any</span></span>): <span class="hljs-title">any</span> </span>{ <span class="hljs-comment">// 如果是数组 调用我们重写的splice方法 (这样可以更新视图)</span> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(target) && isValidArrayIndex(key)) { target.length = <span class="hljs-built_in">Math</span>.max(target.length, key); target.splice(key, <span class="hljs-number">1</span>, val); <span class="hljs-keyword">return</span> val; } <span class="hljs-comment">// 如果是对象本身的属性,则直接添加即可</span> <span class="hljs-keyword">if</span> (key <span class="hljs-keyword">in</span> target && !(key <span class="hljs-keyword">in</span> <span class="hljs-built_in">Object</span>.prototype)) { target[key] = val; <span class="hljs-keyword">return</span> val; } <span class="hljs-keyword">const</span> ob = (target: <span class="hljs-built_in">any</span>).__ob__; <span class="hljs-comment">// 如果不是响应式的也不需要将其定义成响应式属性</span> <span class="hljs-keyword">if</span> (!ob) { target[key] = val; <span class="hljs-keyword">return</span> val; } <span class="hljs-comment">// 将属性定义成响应式的</span> defineReactive(ob.value, key, val); <span class="hljs-comment">// 通知视图更新</span> ob.dep.notify(); <span class="hljs-keyword">return</span> val; } <span class="copy-code-btn">复制代码</span> |
响应式数据原理详解 传送门
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
相关代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initExtend</span>(<span class="hljs-params">Vue</span>) </span>{ <span class="hljs-keyword">let</span> cid = <span class="hljs-number">0</span>; <span class="hljs-comment">//组件的唯一标识</span> <span class="hljs-comment">// 创建子类继承Vue父类 便于属性扩展</span> Vue.extend = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">extendOptions</span>) </span>{ <span class="hljs-comment">// 创建子类的构造函数 并且调用初始化方法</span> <span class="hljs-keyword">const</span> Sub = <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">VueComponent</span>(<span class="hljs-params">options</span>) </span>{ <span class="hljs-built_in">this</span>._init(options); <span class="hljs-comment">//调用Vue初始化方法</span> }; Sub.cid = cid++; Sub.prototype = <span class="hljs-built_in">Object</span>.create(<span class="hljs-built_in">this</span>.prototype); <span class="hljs-comment">// 子类原型指向父类</span> Sub.prototype.constructor = Sub; <span class="hljs-comment">//constructor指向自己</span> Sub.options = mergeOptions(<span class="hljs-built_in">this</span>.options, extendOptions); <span class="hljs-comment">//合并自己的options和父类的options</span> <span class="hljs-keyword">return</span> Sub; }; } <span class="copy-code-btn">复制代码</span> |
Vue 组件原理详解 传送门
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
1 2 3 4 5 6 7 8 9 10 |
1. bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 3. update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。 4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。 5. unbind:只调用一次,指令与元素解绑时调用。 <span class="copy-code-btn">复制代码</span> |
原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
事件修饰符
v-model 的修饰符
键盘事件的修饰符
系统修饰键
鼠标按钮修饰符
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
1 2 3 4 |
第一步是将 模板字符串 转换成 element ASTs(解析器) 第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器) 第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器) <span class="copy-code-btn">复制代码</span> |
相关代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">compileToFunctions</span>(<span class="hljs-params">template</span>) </span>{ <span class="hljs-comment">// 我们需要把html字符串变成render函数</span> <span class="hljs-comment">// 1.把html代码转成ast语法树 ast用来描述代码本身形成树结构 不仅可以描述html 也能描述css以及js语法</span> <span class="hljs-comment">// 很多库都运用到了ast 比如 webpack babel eslint等等</span> <span class="hljs-keyword">let</span> ast = parse(template); <span class="hljs-comment">// 2.优化静态节点</span> <span class="hljs-comment">// 这个有兴趣的可以去看源码 不影响核心功能就不实现了</span> <span class="hljs-comment">// if (options.optimize !== false) {</span> <span class="hljs-comment">// optimize(ast, options);</span> <span class="hljs-comment">// }</span> <span class="hljs-comment">// 3.通过ast 重新生成代码</span> <span class="hljs-comment">// 我们最后生成的代码需要和render函数一样</span> <span class="hljs-comment">// 类似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))</span> <span class="hljs-comment">// _c代表创建元素 _v代表创建文本 _s代表文Json.stringify--把对象解析成文本</span> <span class="hljs-keyword">let</span> code = generate(ast); <span class="hljs-comment">// 使用with语法改变作用域为this 之后调用render函数可以使用call改变this 方便code里面的变量取值</span> <span class="hljs-keyword">let</span> renderFn = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Function</span>(<span class="hljs-string">`with(this){return <span class="hljs-subst">${code}</span>}`</span>); <span class="hljs-keyword">return</span> renderFn; } <span class="copy-code-btn">复制代码</span> |
模板编译原理详解 传送门
Vue 的生命周期钩子核心实现是利用发布订阅模式先把用户传入的的生命周期钩子订阅好(内部采用数组的方式存储)然后在创建组件实例的过程中会一次执行对应的钩子方法(发布)
相关代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">callHook</span>(<span class="hljs-params">vm, hook</span>) </span>{ <span class="hljs-comment">// 依次执行生命周期对应的方法</span> <span class="hljs-keyword">const</span> handlers = vm.$options[hook]; <span class="hljs-keyword">if</span> (handlers) { <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < handlers.length; i++) { handlers[i].call(vm); <span class="hljs-comment">//生命周期里面的this指向当前实例</span> } } } <span class="hljs-comment">// 调用的时候</span> Vue.prototype._init = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">options</span>) </span>{ <span class="hljs-keyword">const</span> vm = <span class="hljs-built_in">this</span>; vm.$options = mergeOptions(vm.constructor.options, options); callHook(vm, <span class="hljs-string">"beforeCreate"</span>); <span class="hljs-comment">//初始化数据之前</span> <span class="hljs-comment">// 初始化状态</span> initState(vm); callHook(vm, <span class="hljs-string">"created"</span>); <span class="hljs-comment">//初始化数据之后</span> <span class="hljs-keyword">if</span> (vm.$options.el) { vm.$mount(vm.$options.el); } }; <span class="copy-code-btn">复制代码</span> |
生命周期实现详解 传送门
函数式组件与普通组件的区别
1 2 3 4 5 6 7 |
1.函数式组件需要在声明组件是指定 functional:true 2.不需要实例化,所以没有this,this通过render函数的第二个参数context来代替 3.没有生命周期钩子函数,不能使用计算属性,watch 4.不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件 5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement 6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止) <span class="copy-code-btn">复制代码</span> |
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰
使用场景:
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
“高阶组件”——用于接收一个组件作为参数,返回一个被包装过的组件
相关代码如下
1 2 3 4 5 6 7 8 |
<span class="hljs-keyword">if</span> (isTrue(Ctor.options.functional)) { <span class="hljs-comment">// 带有functional的属性的就是函数式组件</span> <span class="hljs-keyword">return</span> createFunctionalComponent(Ctor, propsData, data, context, children); } <span class="hljs-keyword">const</span> listeners = data.on; data.on = data.nativeOn; installComponentHooks(data); <span class="hljs-comment">// 安装组件相关钩子 (函数式组件没有调用此方法,从而性能高于普通组件)</span> <span class="copy-code-btn">复制代码</span> |
hash 模式
1 2 |
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">"hashchange"</span>, funcRef, <span class="hljs-literal">false</span>); <span class="copy-code-btn">复制代码</span> |
每一次改变 hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了
特点:兼容性好但是不美观
history 模式
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
特点:虽然美观,但是刷新会出现 404 需要后端进行配置
建议直接看 diff 算法详解 传送门
作者:Big shark@LX
链接:https://juejin.cn/post/6961222829979697165
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。