Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
相干代码如下
export function compileToFunctions(template) {
// 咱们须要把 html 字符串变成 render 函数
// 1. 把 html 代码转成 ast 语法树 ast 用来形容代码自身造成树结构 不仅能够形容 html 也能形容 css 以及 js 语法
// 很多库都使用到了 ast 比方 webpack babel eslint 等等
let ast = parse(template);
// 2. 优化动态节点
// 这个有趣味的能够去看源码 不影响外围性能就不实现了
// if (options.optimize !== false) {// optimize(ast, options);
// }
// 3. 通过 ast 从新生成代码
// 咱们最初生成的代码须要和 render 函数一样
// 相似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
// _c 代表创立元素 _v 代表创立文本 _s 代表文 Json.stringify-- 把对象解析成文本
let code = generate(ast);
// 应用 with 语法扭转作用域为 this 之后调用 render 函数能够应用 call 扭转 this 不便 code 外面的变量取值
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
Vue3.x 响应式数据原理
Vue3.x
改用Proxy
代替Object.defineProperty
。因为Proxy
能够间接监听对象和数组的变动,并且有多达13
种拦挡办法。并且作为新规范将受到浏览器厂商重点继续的性能优化。
proxy 根本用法
// proxy 默认只会代理第一层对象,只有取值再次是对象的时候再次代理,不是一上来就代理,进步性能。不像 vue2.x 递归遍历每个对象属性
let handler = {set(target, key, value) {return Reflect.set(target, key, value);
},
get(target, key) {if (typeof target[key] == 'object' && target[key] !== null) {return new Proxy(target[key], handler); // 懒代理,只有取值再次是对象的时候再次代理,进步性能
}
return Reflect.get(target, key);
}
}
let obj = {school: { name: 'poetry', age: 20} };
let proxy = new Proxy(obj, handler);
// 返回对象的代理
proxy.school
$nextTick 是什么?
Vue 实现响应式并不是在数据产生后立刻更新 DOM,应用 vm.$nextTick
是在下次 DOM 更新循环完结之后立刻执行提早回调。在批改数据之后应用,则 能够在回调中获取更新后的 DOM。
vue 初始化页面闪动问题
应用 vue 开发时,在 vue 初始化之前,因为 div 是不归 vue 管的,所以咱们写的代码在还没有解析的状况下会容易呈现花屏景象,看到相似于 {{message}} 的字样,尽管个别状况下这个工夫很短暂,然而还是有必要让解决这个问题的。
首先:在 css 里加上以下代码:
[v-cloak] {display: none;}
如果没有彻底解决问题,则在根元素加上style="display: none;" :style="{display:'block'}"
vue 和 react 的区别
=> 相同点:
1. 数据驱动页面,提供响应式的试图组件
2. 都有 virtual DOM, 组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 标准
3. 数据流动单向,都反对服务器的渲染 SSR
4. 都有反对 native 的办法,react 有 React native,vue 有 wexx
=> 不同点:
1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
2. 数据渲染:大规模的数据渲染,react 更快
3. 应用场景:React 配合 Redux 架构适宜大规模多人合作简单我的项目,Vue 适宜小快的我的项目
4. 开发格调:react 举荐做法 jsx + inline style 把 html 和 css 都写在 js 了
vue 是采纳 webpack + vue-loader 单文件组件格局,html, js, css 同一个文件
什么是 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.js
外部运行机制会有一个大略的意识。
初始化及挂载
在
new Vue()
之后。Vue 会调用_init
函数进行初始化,也就是这里的init
过程,它会初始化生命周期、事件、props、methods、data、computed 与 watch 等。其中最重要的是通过Object.defineProperty
设置setter
与getter
函数,用来实现「响应式 」以及「 依赖收集」,前面会具体讲到,这里只有有一个印象即可。初始化之后调用
$mount
会挂载组件,如果是运行时编译,即不存在 render function 然而存在 template 的状况,须要进行「编译」步骤。
编译
compile 编译能够分成 parse
、optimize
与 generate
三个阶段,最终须要失去 render function。
1. parse
parse
会用正则等形式解析 template 模板中的指令、class、style 等数据,造成 AST。
2. optimize
optimize
的次要作用是标记 static 动态节点,这是 Vue 在编译过程中的一处优化,前面当update
更新界面时,会有一个patch
的过程,diff 算法会间接跳过动态节点,从而缩小了比拟的过程,优化了patch
的性能。
3. generate
generate
是将 AST 转化成render function
字符串的过程,失去后果是render
的字符串以及 staticRenderFns 字符串。
- 在经验过
parse
、optimize
与generate
这三个阶段当前,组件中就会存在渲染VNode
所需的render function
了。
响应式
接下来也就是 Vue.js 响应式外围局部。
这里的
getter
跟setter
曾经在之前介绍过了,在init
的时候通过Object.defineProperty
进行了绑定,它使得当被设置的对象被读取的时候会执行getter
函数,而在当被赋值的时候会执行setter
函数。
- 当
render function
被渲染的时候,因为会读取所需对象的值,所以会触发getter
函数进行「依赖收集 」,「 依赖收集」的目标是将观察者Watcher
对象寄存到以后闭包中的订阅者Dep
的subs
中。造成如下所示的这样一个关系。
在批改对象的值的时候,会触发对应的
setter
,setter
告诉之前「依赖收集」失去的 Dep 中的每一个 Watcher,通知它们本人的值扭转了,须要从新渲染视图。这时候这些 Watcher 就会开始调用update
来更新视图,当然这两头还有一个patch
的过程以及应用队列来异步更新的策略,这个咱们前面再讲。
Virtual DOM
咱们晓得,
render function
会被转化成VNode
节点。Virtual DOM
其实就是一棵以 JavaScript 对象(VNode 节点)作为根底的树,用对象属性来形容节点,实际上它只是一层对实在 DOM 的形象。最终能够通过一系列操作使这棵树映射到实在环境上。因为 Virtual DOM 是以 JavaScript 对象为根底而不依赖实在平台环境,所以使它具备了跨平台的能力,比如说浏览器平台、Weex、Node 等。
比如说上面这样一个例子:
{
tag: 'div', /* 阐明这是一个 div 标签 */
children: [ /* 寄存该标签的子节点 */
{
tag: 'a', /* 阐明这是一个 a 标签 */
text: 'click me' /* 标签的内容 */
}
]
}
渲染后能够失去
<div>
<a>click me</a>
</div>
这只是一个简略的例子,实际上的节点有更多的属性来标记节点,比方 isStatic(代表是否为动态节点)、isComment(代表是否为正文节点)等。
更新视图
- 后面咱们说到,在批改一个对象值的时候,会通过
setter -> Watcher -> update
的流程来批改对应的视图,那么最终是如何更新视图的呢? - 当数据变动后,执行 render function 就能够失去一个新的 VNode 节点,咱们如果想要失去新的视图,最简略粗犷的办法就是间接解析这个新的
VNode
节点,而后用innerHTML
间接全副渲染到实在DOM
中。然而其实咱们只对其中的一小块内容进行了批改,这样做仿佛有些「节约」。 - 那么咱们为什么不能只批改那些「扭转了的中央」呢?这个时候就要介绍咱们的「
patch
」了。咱们会将新的VNode
与旧的VNode
一起传入patch
进行比拟,通过 diff 算法得出它们的「差别 」。最初咱们只须要将这些「 差别」的对应 DOM 进行批改即可。
再看全局
回过头再来看看这张图,是不是大脑中曾经有一个大略的脉络了呢?
说说你对 proxy 的了解,Proxy 相比于 defineProperty 的劣势
Object.defineProperty()
的问题次要有三个:
- 不能监听数组的变动:无奈监控到数组下标的变动,导致通过数组下标增加元素,不能实时响应
- 必须遍历对象的每个属性:只能劫持对象的属性,从而须要对每个对象,每个属性进行遍历,如果属性值是对象,还须要深度遍历。
Proxy
能够劫持整个对象,并返回一个新的对象 - 必须深层遍历嵌套的对象
Proxy 的劣势如下:
- 针对对象:针对整个对象,而不是对象的某个属性,所以也就不须要对
keys
进行遍历 - 反对数组:
Proxy
不须要对数组的办法进行重载,省去了泛滥 hack,缩小代码量等于缩小了保护老本,而且规范的就是最好的 Proxy
的第二个参数能够有13
种拦挡方:不限于apply
、ownKeys
、deleteProperty
、has
等等是Object.defineProperty
不具备的Proxy
返回的是一个新对象, 咱们能够只操作新的对象达到目标, 而Object.defineProperty
只能遍历对象属性间接批改Proxy
作为新规范将受到浏览器厂商重点继续的性能优化,也就是传说中的新规范的性能红利
proxy 具体应用点击查看(opens new window)
Object.defineProperty 的劣势如下:
兼容性好,反对
IE9
,而Proxy
的存在浏览器兼容性问题, 而且无奈用polyfill
磨平
defineProperty 的属性值有哪些
Object.defineProperty(obj, prop, descriptor)
// obj 要定义属性的对象
// prop 要定义或批改的属性的名称
// descriptor 要定义或批改的属性描述符
Object.defineProperty(obj,"name",{
value:"poetry", // 初始值
writable:true, // 该属性是否可写入
enumerable:true, // 该属性是否可被遍历失去(for...in,Object.keys 等)configurable:true, // 定该属性是否可被删除,且除 writable 外的其余描述符是否可被批改
get: function() {},
set: function(newVal) {}})
相干代码如下
import {mutableHandlers} from "./baseHandlers"; // 代理相干逻辑
import {isObject} from "./util"; // 工具办法
export function reactive(target) {
// 依据不同参数创立不同响应式对象
return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {if (!isObject(target)) {return target;}
const observed = new Proxy(target, baseHandler);
return observed;
}
const get = createGetter();
const set = createSetter();
function createGetter() {return function get(target, key, receiver) {
// 对获取的值进行喷射
const res = Reflect.get(target, key, receiver);
console.log("属性获取", key);
if (isObject(res)) {
// 如果获取的值是对象类型,则返回以后对象的代理对象
return reactive(res);
}
return res;
};
}
function createSetter() {return function set(target, key, value, receiver) {const oldValue = target[key];
const hadKey = hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
if (!hadKey) {console.log("属性新增", key, value);
} else if (hasChanged(value, oldValue)) {console.log("属性值被批改", key, value);
}
return result;
};
}
export const mutableHandlers = {
get, // 当获取属性时调用此办法
set, // 当批改属性时调用此办法
};
Proxy
只会代理对象的第一层,那么 Vue3
又是怎么解决这个问题的呢?
判断以后
Reflect.get 的
返回值是否为Object
,如果是则再通过reactive
办法做代理,这样就实现了深度观测。
监测数组的时候可能触发屡次 get/set,那么如何避免触发屡次呢?
咱们能够判断
key
是否为以后被代理对象target
本身属性,也能够判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger
常见的事件修饰符及其作用
.stop
:等同于 JavaScript 中的event.stopPropagation()
,避免事件冒泡;.prevent
:等同于 JavaScript 中的event.preventDefault()
,避免执行预设的行为(如果事件可勾销,则勾销该事件,而不进行事件的进一步流传);.capture
:与事件冒泡的方向相同,事件捕捉由外到内;.self
:只会触发本人范畴内的事件,不蕴含子元素;.once
:只会触发一次。
父组件能够监听到子组件的生命周期吗
比方有父组件 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
等都能够监听
为什么 Vue 采纳异步渲染呢?
Vue
是组件级更新,如果不采纳异步更新,那么每次更新数据都会对以后组件进行从新渲染,所以为了性能, Vue
会在本轮数据更新后,在异步更新视图。核心思想 nextTick
。
dep.notify()
告诉 watcher 进行更新, subs[i].update
顺次调用 watcher 的 update
, queueWatcher
将 watcher 去重放入队列,nextTick( flushSchedulerQueue
)在下一 tick 中刷新 watcher 队列(异步)。
computed 和 watch 的区别和使用的场景?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值;
watch: 更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作;
使用场景:
- 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时,都要从新计算;
- 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许咱们执行异步操作 (拜访一个 API),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
Vue 中的 key 到底有什么用?
key 是给每一个 vnode 的惟一 id, 依附 key, 咱们的 diff 操作能够更精确、更疾速 (对于简略列表页渲染来说 diff 节点也更快, 但会产生一些暗藏的副作用, 比方可能不会产生过渡成果, 或者在某些节点有绑定数据(表单)状态,会呈现状态错位。)
diff 算法的过程中, 先会进行新旧节点的首尾穿插比照, 当无奈匹配的时候会用新节点的 key 与旧节点进行比对, 从而找到相应旧节点.
更精确 : 因为带 key 就不是就地复用了, 在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确, 如果不加 key, 会导致之前节点的状态被保留下来, 会产生一系列的 bug。
更疾速 : key 的唯一性能够被 Map 数据结构充分利用, 相比于遍历查找的工夫复杂度 O(n),Map 的工夫复杂度仅仅为 O(1)
vue3.0 个性你有什么理解的吗?
Vue 3.0 正走在公布的路上,Vue 3.0 的指标是让 Vue 外围变得更小、更快、更弱小,因而 Vue 3.0 减少以下这些新个性:
(1)监测机制的扭转
3.0 将带来基于代理 Proxy 的 observer 实现,提供全语言笼罩的反馈性跟踪。这打消了 Vue 2 当中基于 Object.defineProperty 的实现所存在的很多限度:
- 只能监测属性,不能监测对象
- 检测属性的增加和删除;
- 检测数组索引和长度的变更;
- 反对 Map、Set、WeakMap 和 WeakSet。
新的 observer 还提供了以下个性:
- 用于创立 observable 的公开 API。这为中小规模场景提供了简略轻量级的跨组件状态治理解决方案。
- 默认采纳惰性察看。在 2.x 中,不论反应式数据有多大,都会在启动时被察看到。如果你的数据集很大,这可能会在利用启动时带来显著的开销。在 3.x 中,只察看用于渲染应用程序最后可见局部的数据。
- 更准确的变更告诉。在 2.x 中,通过 Vue.set 强制增加新属性将导致依赖于该对象的 watcher 收到变更告诉。在 3.x 中,只有依赖于特定属性的 watcher 才会收到告诉。
- 不可变的 observable:咱们能够创立值的“不可变”版本(即便是嵌套属性),除非零碎在外部临时将其“解禁”。这个机制可用于解冻 prop 传递或 Vuex 状态树以外的变动。
- 更好的调试性能:咱们能够应用新的 renderTracked 和 renderTriggered 钩子准确地跟踪组件在什么时候以及为什么从新渲染。
(2)模板
模板方面没有大的变更,只改了作用域插槽,2.x 的机制导致作用域插槽变了,父组件会从新渲染,而 3.0 把作用域插槽改成了函数的形式,这样只会影响子组件的从新渲染,晋升了渲染的性能。
同时,对于 render 函数的方面,vue3.0 也会进行一系列更改来不便习惯间接应用 api 来生成 vdom。
(3)对象式的组件申明形式
vue2.x 中的组件是通过申明的形式传入一系列 option,和 TypeScript 的联合须要通过一些装璜器的形式来做,尽管能实现性能,然而比拟麻烦。3.0 批改了组件的申明形式,改成了类式的写法,这样使得和 TypeScript 的联合变得很容易。
此外,vue 的源码也改用了 TypeScript 来写。其实当代码的性能简单之后,必须有一个动态类型零碎来做一些辅助治理。当初 vue3.0 也全面改用 TypeScript 来重写了,更是使得对外裸露的 api 更容易联合 TypeScript。动态类型零碎对于简单代码的保护的确很有必要。
(4)其它方面的更改
vue3.0 的扭转是全面的,下面只波及到次要的 3 个方面,还有一些其余的更改:
- 反对自定义渲染器,从而使得 weex 能够通过自定义渲染器的形式来扩大,而不是间接 fork 源码来改的形式。
- 反对 Fragment(多个根节点)和 Protal(在 dom 其余局部渲染组建内容)组件,针对一些非凡的场景做了解决。
- 基于 treeshaking 优化,提供了更多的内置性能。
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 这种手动优化的生命周期.
v-model 是如何实现的,语法糖理论是什么?
(1)作用在表单元素上 动静绑定了 input 的 value 指向了 messgae 变量,并且在触发 input 事件的时候去动静把 message 设置为目标值:
<input v-model="sth" />
// 等同于
<input v-bind:value="message" v-on:input="message=$event.target.value"
>
//$event 指代以后触发的事件对象;//$event.target 指代以后触发的事件对象的 dom;//$event.target.value 就是以后 dom 的 value 值;// 在 @input 办法中,value => sth;// 在:value 中,sth => value;
(2)作用在组件上 在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件
实质是一个父子组件通信的语法糖,通过 prop 和 $.emit 实现。 因而父组件 v-model 语法糖实质上能够批改为:
<child :value="message" @input="function(e){message = e}"></child>
在组件的实现中,能够通过 v-model 属性来配置子组件接管的 prop 名称,以及派发的事件名称。
例子:
// 父组件
<aa-input v-model="aa"></aa-input>
// 等价于
<aa-input v-bind:value="aa" v-on:input="aa=$event.target.value"></aa-input>
// 子组件:<input v-bind:value="aa" v-on:input="onmessage"></aa-input>
props:{value:aa,}
methods:{onmessage(e){$emit('input',e.target.value)
}
}
默认状况下,一个组件上的 v -model 会把 value 用作 prop 且把 input 用作 event。然而一些输出类型比方单选框和复选框按钮可能想应用 value prop 来达到不同的目标。应用 model 选项能够回避这些状况产生的抵触。js 监听 input 输入框输出数据扭转,用 oninput,数据扭转当前就会立即登程这个事件。通过 input 事件把数据 $emit 进来,在父组件承受。父组件设置 v -model 的值为 input $emit
过去的值。
理解 nextTick 吗?
异步办法,异步渲染最初一步,与 JS 事件循环分割严密。次要应用了宏工作微工作(setTimeout
、promise
那些),定义了一个异步办法,屡次调用 nextTick
会将办法存入队列,通过异步办法清空以后队列。
v-show 与 v-if 有什么区别?
v-if 是 真正 的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是 惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简略得多——不论初始条件是什么,元素总是会被渲染,并且只是简略地基于 CSS 的“display”属性进行切换。
所以,v-if 实用于在运行时很少扭转条件,不须要频繁切换条件的场景;v-show 则实用于须要十分频繁切换条件的场景。
vue 中应用了哪些设计模式
1. 工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2. 单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3. 公布 - 订阅模式 (vue 事件机制)
4. 观察者模式 (响应式数据原理)
5. 装璜模式: (@装璜器的用法)
6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略