共计 17374 个字符,预计需要花费 44 分钟才能阅读完成。
对 React 和 Vue 的了解,它们的异同
相似之处:
- 都将注意力集中放弃在外围库,而将其余性能如路由和全局状态治理交给相干的库;
- 都有本人的构建工具,能让你失去一个依据最佳实际设置的我的项目模板;
- 都应用了 Virtual DOM(虚构 DOM)进步重绘性能;
- 都有 props 的概念,容许组件间的数据传递;
- 都激励组件化利用,将利用分拆成一个个性能明确的模块,进步复用性。
不同之处:
1)数据流
Vue 默认反对数据双向绑定,而 React 始终提倡单向数据流
2)虚构 DOM
Vue2.x 开始引入 ”Virtual DOM”,打消了和 React 在这方面的差别,然而在具体的细节还是有各自的特点。
- Vue 声称能够更快地计算出 Virtual DOM 的差别,这是因为它在渲染过程中,会跟踪每一个组件的依赖关系,不须要从新渲染整个组件树。
- 对于 React 而言,每当利用的状态被扭转时,全副子组件都会从新渲染。当然,这能够通过 PureComponent/shouldComponentUpdate 这个生命周期办法来进行管制,但 Vue 将此视为默认的优化。
3)组件化
React 与 Vue 最大的不同是模板的编写。
- Vue 激励写近似惯例 HTML 的模板。写起来很靠近规范 HTML 元素,只是多了一些属性。
- React 举荐你所有的模板通用 JavaScript 的语法扩大——JSX 书写。
具体来讲:React 中 render 函数是反对闭包个性的,所以 import 的组件在 render 中能够间接调用。然而在 Vue 中,因为模板中应用的数据都必须挂在 this 上进行一次直达,所以 import 一个组件完了之后,还须要在 components 中再申明下。4)监听数据变动的实现原理不同
- Vue 通过 getter/setter 以及一些函数的劫持,能准确晓得数据变动,不须要特地的优化就能达到很好的性能
- React 默认是通过比拟援用的形式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的从新渲染。这是因为 Vue 应用的是可变数据,而 React 更强调数据的不可变。
5)高阶组件
react 能够通过高阶组件(HOC)来扩大,而 Vue 须要通过 mixins 来扩大。
高阶组件就是高阶函数,而 React 的组件自身就是纯正的函数,所以高阶函数对 React 来说大海捞针。相同 Vue.js 应用 HTML 模板创立视图组件,这时模板无奈无效的编译,因而 Vue 不能采纳 HOC 来实现。
6)构建工具
两者都有本人的构建工具:
- React ==> Create React APP
- Vue ==> vue-cli
7)跨平台
- React ==> React Native
- Vue ==> Weex
理解 nextTick 吗?
异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用 nextTick
会将办法存入队列,通过异步办法清空以后队列。
v-if 和 v -for 哪个优先级更高
- 实际中不应该把
v-for
和v-if
放一起 - 在
vue2
中,v-for
的优先级是高于v-if
,把它们放在一起,输入的渲染函数中能够看出会先执行循环再判断条件,哪怕咱们只渲染列表中一小部分元素,也得在每次重渲染的时候遍历整个列表,这会比拟节约;另外须要留神的是在vue3
中则齐全相同,v-if
的优先级高于v-for
,所以v-if
执行时,它调用的变量还不存在,就会导致异样 -
通常有两种状况下导致咱们这样做:
- 为了过滤列表中的我的项目 (比方
v-for="user in users" v-if="user.isActive"
)。此时定义一个计算属性 (比方activeUsers
),让其返回过滤后的列表即可(比方users.filter(u=>u.isActive)
) - 为了防止渲染本应该被暗藏的列表 (比方
v-for="user in users" v-if="shouldShowUsers"
)。此时把v-if
挪动至容器元素上 (比方ul
、ol
)或者外面包一层template
即可
- 为了过滤列表中的我的项目 (比方
- 文档中明确指出永远不要把
v-if
和v-for
同时用在同一个元素上,显然这是一个重要的注意事项 - 源码外面对于代码生成的局部,可能清晰的看到是先解决
v-if
还是v-for
,程序上vue2
和vue3
正好相同,因而产生了一些症状的不同,然而不管怎样都是不能把它们写在一起的
vue2.x 源码剖析
在 vue 模板编译的时候,会将指令系统转化成可执行的
render
函数
编写一个 p
标签,同时应用 v-if
与 v-for
<div id="app">
<p v-if="isShow" v-for="item in items">
{{item.title}}
</p>
</div>
创立 vue
实例,寄存 isShow
与items
数据
const app = new Vue({
el: "#app",
data() {
return {
items: [{ title: "foo"},
{title: "baz"}]
}
},
computed: {isShow() {return this.items && this.items.length > 0}
}
})
模板指令的代码都会生成在 render
函数中,通过 app.$options.render
就能失去渲染函数
ƒ anonymous() {with (this) { return
_c('div', { attrs: { "id": "app"} },
_l((items), function (item)
{return (isShow) ? _c('p', [_v("\n" + _s(item.title) + "\n")]) : _e()}), 0) }
}
_l
是vue
的列表渲染函数,函数外部都会进行一次if
判断- 初步失去论断:
v-for
优先级是比v-i
f 高 - 再将
v-for
与v-if
置于不同标签
<div id="app">
<template v-if="isShow">
<p v-for="item in items">{{item.title}}</p>
</template>
</div>
再输入下 render
函数
ƒ anonymous() {with(this){return
_c('div',{attrs:{"id":"app"}},
[(isShow)?[_v("\n"),
_l((items),function(item){return _c('p',[_v(_s(item.title))])})]:_e()],2)}
}
这时候咱们能够看到,v-for
与 v-if
作用在不同标签时候,是先进行判断,再进行列表的渲染
咱们再在查看下 vue 源码
源码地位:\vue-dev\src\compiler\codegen\index.js
export function genElement (el: ASTElement, state: CodegenState): string {if (el.parent) {el.pre = el.pre || el.parent.pre}
if (el.staticRoot && !el.staticProcessed) {return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {return genOnce(el, state)
} else if (el.for && !el.forProcessed) {return genFor(el, state)
} else if (el.if && !el.ifProcessed) {return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {return genSlot(el, state)
} else {
// component or element
...
}
在进行 if
判断的时候,v-for
是比 v-if
先进行判断
最终论断:v-for
优先级比 v-if
高
Vue 中的 key 到底有什么用?
key
是为 Vue 中的 vnode 标记的惟一 id, 通过这个 key, 咱们的 diff 操作能够更精确、更疾速
diff 算法的过程中, 先会进行新旧节点的首尾穿插比照, 当无奈匹配的时候会用新节点的 key
与旧节点进行比对, 而后超出差别.
diff 程能够概括为:oldCh 和 newCh 各有两个头尾的变量 StartIdx 和 EndIdx,它们的 2 个变量互相比拟,一共有 4 种比拟形式。如果 4 种比拟都没匹配,如果设置了 key,就会用 key 进行比拟,在比拟的过程中,变量会往两头靠,一旦 StartIdx>EndIdx 表明 oldCh 和 newCh 至多有一个曾经遍历完了,就会完结比拟, 这四种比拟形式就是首、尾、旧尾新头、旧头新尾.
- 精确: 如果不加
key
, 那么 vue 会抉择复用节点(Vue 的就地更新策略), 导致之前节点的状态被保留下来, 会产生一系列的 bug. - 疾速: key 的唯一性能够被 Map 数据结构充分利用, 相比于遍历查找的工夫复杂度 O(n),Map 的工夫复杂度仅仅为 O(1).
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信 这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref 与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent / $children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信 这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何prop
时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合inheritAttrs
选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的)v-on
事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信 先人组件中通过 provider
来提供变量,而后在子孙组件中通过 inject
来注入变量。provide / inject API
次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。(6)Vuex
实用于 父子、隔代、兄弟组件通信 Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue 组件间通信有哪几种形式?
Vue 组件间通信是面试常考的知识点之一,这题有点相似于凋谢题,你答复出越多办法当然越加分,表明你对 Vue 把握的越纯熟。Vue 组件间通信只有指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,上面咱们别离介绍每种通信形式且会阐明此种办法可实用于哪类组件间通信。
(1)props / $emit
实用 父子组件通信
这种办法是 Vue 组件的根底,置信大部分同学耳闻能详,所以此处就不举例开展介绍。
(2)ref
与 $parent / $children
实用 父子组件通信
ref
:如果在一般的 DOM 元素上应用,援用指向的就是 DOM 元素;如果用在子组件上,援用就指向组件实例$parent
/$children
:拜访父 / 子实例
(3)EventBus($emit / $on)
实用于 父子、隔代、兄弟组件通信
这种办法通过一个空的 Vue 实例作为地方事件总线(事件核心),用它来触发事件和监听事件,从而实现任何组件间的通信,包含父子、隔代、兄弟组件。
(4)$attrs
/$listeners
实用于 隔代组件通信
$attrs
:蕴含了父作用域中不被 prop 所辨认 (且获取) 的个性绑定 (class 和 style 除外)。当一个组件没有申明任何 prop 时,这里会蕴含所有父作用域的绑定 (class 和 style 除外),并且能够通过v-bind="$attrs"
传入外部组件。通常配合 inheritAttrs 选项一起应用。$listeners
:蕴含了父作用域中的 (不含 .native 润饰器的) v-on 事件监听器。它能够通过v-on="$listeners"
传入外部组件
(5)provide / inject
实用于 隔代组件通信
先人组件中通过 provider 来提供变量,而后在子孙组件中通过 inject 来注入变量。provide / inject API 次要解决了跨级组件间的通信问题,不过它的应用场景,次要是子组件获取下级组件的状态,跨级组件间建设了一种被动提供与依赖注入的关系。
(6)Vuex 实用于 父子、隔代、兄弟组件通信
Vuex 是一个专为 Vue.js 利用程序开发的状态管理模式。每一个 Vuex 利用的外围就是 store(仓库)。“store”基本上就是一个容器,它蕴含着你的利用中大部分的状态 (state)。
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地失去高效更新。
- 扭转 store 中的状态的惟一路径就是显式地提交 (commit) mutation。这样使得咱们能够不便地跟踪每一个状态的变动。
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?
考点: Vue 的变动侦测原理
前置常识: 依赖收集、虚构 DOM、响应式零碎
根本原因是 Vue 与 React 的变动侦测形式有所不同
React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.
Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.
参考:前端 vue 面试题具体解答
什么是 mixin?
- Mixin 使咱们可能为 Vue 组件编写可插拔和可重用的性能。
- 如果心愿在多个组件之间重用一组组件选项,例如生命周期 hook、办法等,则能够将其编写为 mixin,并在组件中简略的援用它。
- 而后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。
Vue 中 computed 和 watch 有什么区别?
计算属性 computed:
(1)** 反对缓存 **,只有依赖数据发生变化时,才会从新进行计算函数;(2)计算属性内 ** 不反对异步操作 **;(3)计算属性的函数中 ** 都有一个 get**(默认具备,获取计算属性)** 和 set**(手动增加,设置计算属性)办法;(4)计算属性是主动监听依赖值的变动,从而动静返回内容。
侦听属性 watch:
(1)** 不反对缓存 **,只有数据发生变化,就会执行侦听函数;(2)侦听属性内 ** 反对异步操作 **;(3)侦听属性的值 ** 能够是一个对象,接管 handler 回调,deep,immediate 三个属性 **;(3)监听是一个过程,在监听的值变动时,能够触发一个回调,并 ** 做一些其余事件 **。
Vue 中封装的数组办法有哪些,其如何实现页面更新
在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:
// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
// 缓存原生数组办法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
// 执行并缓存原生数组性能
const result = original.apply(this, args);
// 响应式解决
const ob = this.__ob__;
let inserted;
switch (method) {
// push、unshift 会新增索引,所以要手动 observer
case "push":
case "unshift":
inserted = args;
break;
// splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
inserted = args.slice(2);
break;
}
//
if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
// notify change
ob.dep.notify();// 告诉依赖更新
// 返回原生数组办法的执行后果
return result;
});
});
简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods
来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。
对 SSR 的了解
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端实现,而后再把 html 间接返回给客户端
SSR 的劣势:
- 更好的 SEO
- 首屏加载速度更快
SSR 的毛病:
- 开发条件会受到限制,服务器端渲染只反对 beforeCreate 和 created 两个钩子;
- 当须要一些内部扩大库时须要非凡解决,服务端渲染应用程序也须要处于 Node.js 的运行环境;
- 更多的服务端负载。
diff 算法
工夫复杂度: 个树的齐全 diff
算法是一个工夫复杂度为 O(n*3)
,vue 进行优化转化成 O(n)
。
了解:
-
最小量更新,
key
很重要。这个能够是这个节点的惟一标识,通知diff
算法,在更改前后它们是同一个 DOM 节点- 扩大
v-for
为什么要有key
,没有key
会暴力复用,举例子的话轻易说一个比方挪动节点或者减少节点(批改 DOM),加key
只会挪动缩小操作 DOM。
- 扩大
- 只有是同一个虚构节点才会进行精细化比拟,否则就是暴力删除旧的,插入新的。
- 只进行同层比拟,不会进行跨层比拟。
diff 算法的优化策略:四种命中查找,四个指针
- 旧前与新前(先比结尾,后插入和删除节点的这种状况)
- 旧后与新后(比结尾,前插入或删除的状况)
- 旧前与新后(头与尾比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧后之后)
- 旧后与新前(尾与头比,此种产生了,波及挪动节点,那么新前指向的节点,挪动到旧前之前)
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
用 VNode 来形容一个 DOM 构造
虚构节点就是用一个对象来形容一个实在的 DOM 元素。首先将 template
(实在 DOM)先转成 ast
, ast
树通过 codegen
生成 render
函数, render
函数里的 _c
办法将它转为虚构 dom
Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题?
受古代 JavaScript 的限度,Vue 无奈检测到对象属性的增加或删除。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的。然而 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
来实现为对象增加响应式属性,那框架自身是如何实现的呢?
咱们查看对应的 Vue 源码:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
// target 为数组
if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
target.length = Math.max(target.length, key)
// 利用数组的 splice 变异办法触发响应式
target.splice(key, 1, val)
return val
}
// key 曾经存在,间接批改属性值
if (key in target && !(key in Object.prototype)) {target[key] = val
return val
}
const ob = (target: any).__ob__
// target 自身就不是响应式数据, 间接赋值
if (!ob) {target[key] = val
return val
}
// 对属性进行响应式解决
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
咱们浏览以上源码可知,vm.$set 的实现原理是:
- 如果指标是数组,间接应用数组的 splice 办法触发相应式;
- 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)
对 keep-alive 的了解,它是如何实现的,具体缓存的是什么?
如果须要在组件切换的时候,保留一些组件的状态避免屡次渲染,就能够应用 keep-alive 组件包裹须要保留的组件。
(1)keep-alive
keep-alive 有以下三个属性:
- include 字符串或正则表达式,只有名称匹配的组件会被匹配;
- exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
- max 数字,最多能够缓存多少组件实例。
留神:keep-alive 包裹动静组件时,会缓存不流动的组件实例。
次要流程
- 判断组件 name,不在 include 或者在 exclude 中,间接返回 vnode,阐明该组件不被缓存。
- 获取组件实例 key,如果有获取实例的 key,否则从新生成。
- key 生成规定,cid +”∶∶”+ tag,仅靠 cid 是不够的,因为雷同的构造函数能够注册为不同的本地组件。
- 如果缓存对象内存在,则间接从缓存对象中获取组件实例给 vnode,不存在则增加到缓存对象中。5. 最大缓存数量,当缓存组件数量超过 max 值时,革除 keys 数组内第一个组件。
(2)keep-alive 的实现
const patternTypes: Array<Function> = [String, RegExp, Array] // 接管:字符串,正则,数组
export default {
name: 'keep-alive',
abstract: true, // 形象组件,是一个形象组件:它本身不会渲染一个 DOM 元素,也不会呈现在父组件链中。props: {
include: patternTypes, // 匹配的组件,缓存
exclude: patternTypes, // 不去匹配的组件,不缓存
max: [String, Number], // 缓存组件的最大实例数量, 因为缓存的是组件实例(vnode),数量过多的时候,会占用过多的内存,能够用 max 指定下限
},
created() {
// 用于初始化缓存虚构 DOM 数组和 vnode 的 key
this.cache = Object.create(null)
this.keys = []},
destroyed() {
// 销毁缓存 cache 的组件实例
for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {// prune 削减精简[v.]
// 去监控 include 和 exclude 的扭转,依据最新的 include 和 exclude 的内容,来实时削减缓存的组件的内容
this.$watch('include', (val) => {pruneCache(this, (name) => matches(val, name))
})
this.$watch('exclude', (val) => {pruneCache(this, (name) => !matches(val, name))
})
},
}
render 函数:
- 会在 keep-alive 组件外部去写本人的内容,所以能够去获取默认 slot 的内容,而后依据这个去获取组件
- keep-alive 只对第一个组件无效,所以获取第一个子组件。
- 和 keep-alive 搭配应用的个别有:动静组件 和 router-view
render () {
//
function getFirstComponentChild (children: ?Array<VNode>): ?VNode {if (Array.isArray(children)) {for (let i = 0; i < children.length; i++) {const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {return c}
}
}
}
const slot = this.$slots.default // 获取默认插槽
const vnode: VNode = getFirstComponentChild(slot)// 获取第一个子组件
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 组件参数
if (componentOptions) { // 是否有组件参数
// check pattern
const name: ?string = getComponentName(componentOptions) // 获取组件名
const {include, exclude} = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
// 如果不匹配以后组件的名字和 include 以及 exclude
// 那么间接返回组件的实例
return vnode
}
const {cache, keys} = this
// 获取这个组件的 key
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// LRU 缓存策略执行
vnode.componentInstance = cache[key].componentInstance // 组件首次渲染的时候 componentInstance 为 undefined
// make current key freshest
remove(keys, key)
keys.push(key)
// 依据 LRU 缓存策略执行,将 key 从原来的地位移除,而后将这个 key 值放到最初面
} else {
// 在缓存列表外面没有的话,则退出,同时判断以后退出之后,是否超过了 max 所设定的范畴,如果是,则去除
// 应用工夫距离最长的一个
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 将组件的 keepAlive 属性设置为 true
vnode.data.keepAlive = true // 作用:判断是否要执行组件的 created、mounted 生命周期函数
}
return vnode || (slot && slot[0])
}
keep-alive 具体是通过 cache 数组缓存所有组件的 vnode 实例。当 cache 内原有组件被应用时会将该组件 key 从 keys 数组中删除,而后 push 到 keys 数组最初,以便革除最不罕用组件。
实现步骤:
- 获取 keep-alive 下第一个子组件的实例对象,通过他去获取这个组件的组件名
- 通过以后组件名去匹配原来 include 和 exclude,判断以后组件是否须要缓存,不须要缓存,间接返回以后组件的实例 vNode
- 须要缓存,判断他以后是否在缓存数组外面:
- 存在,则将他原来地位上的 key 给移除,同时将这个组件的 key 放到数组最初面(LRU)
- 不存在,将组件 key 放入数组,而后判断以后 key 数组是否超过 max 所设置的范畴,超过,那么削减未应用工夫最长的一个组件的 key
- 最初将这个组件的 keepAlive 设置为 true
(3)keep-alive 自身的创立过程和 patch 过程
缓存渲染的时候,会依据 vnode.componentInstance(首次渲染 vnode.componentInstance 为 undefined)和 keepAlive 属性判断不会执行组件的 created、mounted 等钩子函数,而是对缓存的组件执行 patch 过程∶ 间接把缓存的 DOM 对象直接插入到指标元素中,实现了数据更新的状况下的渲染过程。
首次渲染
- 组件的首次渲染∶判断组件的 abstract 属性,才往父组件外面挂载 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) { // 判断组件的 abstract 属性,才往父组件外面挂载 DOM
while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
- 判断以后 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创立 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) { // componentInstance 在首次是 undefined!!!
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch 函数执行的是组件更新的过程
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch 操作就不会在执行组件的 mounted 和 created 生命周期函数,而是间接将 DOM 插入
(4)LRU(least recently used)缓存策略
LRU 缓存策略∶ 从内存中找出最久未应用的数据并置换新的数据。
LRU(Least rencently used)算法依据数据的历史拜访记录来进行淘汰数据,其核心思想是 “ 如果数据最近被拜访过,那么未来被拜访的几率也更高 ”。最常见的实现是应用一个链表保留缓存数据,具体算法实现如下∶
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被拜访),则将数据移到链表头部
- 链表满的时候,将链表尾部的数据抛弃。
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
Vue.extend 作用和原理
官网解释:Vue.extend 应用根底 Vue 结构器,创立一个“子类”。参数是一个蕴含组件选项的对象。
其实就是一个子类结构器 是 Vue 组件的外围 api 实现思路就是应用原型继承的办法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
Vue2.x 响应式数据原理
整体思路是数据劫持 + 观察者模式
对象外部通过 defineReactive
办法,应用 Object.defineProperty
来劫持各个属性的 setter
、getter
(只会劫持曾经存在的属性),数组则是通过 重写数组 7 个办法
来实现。当页面应用对应属性时,每个属性都领有本人的 dep
属性,寄存他所依赖的 watcher
(依赖收集),当属性变动后会告诉本人对应的 watcher
去更新(派发更新)
Object.defineProperty 根本应用
function observer(value) { // proxy reflect
if (typeof value === 'object' && typeof value !== null)
for (let key in value) {defineReactive(value, key, value[key]);
}
}
function defineReactive(obj, key, value) {observer(value);
Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
return value;
},
set(newValue) {if (newValue !== value) {observer(newValue);
value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
}
}
})
}
let obj1 = {school: { name: 'poetry', age: 20} };
observer(obj1);
console.log(obj1)
源码剖析
class Observer {
// 观测值
constructor(value) {this.walk(value);
}
walk(data) {
// 对象上的所有属性顺次进行观测
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
// Object.defineProperty 数据劫持外围 兼容性在 ie9 以及以上
function defineReactive(data, key, value) {observe(value); // 递归要害
// -- 如果 value 还是一个对象会持续走一遍 odefineReactive 层层遍历始终到 value 不是对象才进行
// 思考?如果 Vue 数据嵌套层级过深 >> 性能会受影响
Object.defineProperty(data, key, {get() {console.log("获取值");
// 须要做依赖收集过程 这里代码没写进去
return value;
},
set(newValue) {if (newValue === value) return;
console.log("设置值");
// 须要做派发更新过程 这里代码没写进去
value = newValue;
},
});
}
export function observe(value) {
// 如果传过来的是对象或者数组 进行属性劫持
if (Object.prototype.toString.call(value) === "[object Object]" ||
Array.isArray(value)
) {return new Observer(value);
}
}
说一说你对 vue 响应式了解答复范例
- 所谓数据响应式就是 可能使数据变动能够被检测并对这种变动做出响应的机制
MVVM
框架中要解决的一个外围问题是连贯数据层和视图层,通过 数据驱动 利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决- 以
vue
为例阐明,通过数据响应式加上虚构DOM
和patch
算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的 DOM 操作,从而大大晋升开发效率,升高开发难度 vue2
中的数据响应式会依据数据类型来做不同解决,如果是 对象则采纳Object.defineProperty()
的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的 7 个变更办法 ,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用Vue.set/delete
这样非凡的api
能力失效;对于es6
中新产生的Map
、Set
这些数据结构不反对等问题- 为了解决这些问题,
vue3
从新编写了这一部分的实现:利用ES6
的Proxy
代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡api
,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的reactivity
包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了