什么网站做首页,西部数码创建php网站,口碑好网站建设费用,二手交易平台的网站怎么做目录 0. 前言
1. vm.$mount
1.1 用法回顾
1.2 内部原理
2. vm.$forceUpdate
2.1 用法回顾
2.2 内部原理
3. vm.$nextTick
3.1 用法回顾
3.2 JS的运行机制
3.3 内部原理
能力检测
执行回调队列
4. vm.$destory
4.1 用法回顾
4.2 内部原理 0. 前言
与生命周期相关…目录 0. 前言
1. vm.$mount
1.1 用法回顾
1.2 内部原理
2. vm.$forceUpdate
2.1 用法回顾
2.2 内部原理
3. vm.$nextTick
3.1 用法回顾
3.2 JS的运行机制
3.3 内部原理
能力检测
执行回调队列
4. vm.$destory
4.1 用法回顾
4.2 内部原理 0. 前言
与生命周期相关的实例方法有4个分别是vm.$mount、vm.$forceUpdate、vm.$nextTick和vm.$destory。其中$forceUpdate和$destroy方法是在lifecycleMixin函数中挂载到Vue原型上的$nextTick方法是在renderMixin函数中挂载到Vue原型上的而$mount方法是在跨平台的代码中挂载到Vue原型上的。代码如下
export function lifecycleMixin (Vue) {Vue.prototype.$forceUpdate function () {}Vue.prototype.$destroy function (fn) {}
}export function renderMixin (Vue) {Vue.prototype.$nextTick function (fn) {}
}当执行lifecycleMixin和renderMixin函数后会向Vue原型上挂载相应的实例方法。
接下来我们就来分析这4个与生命周期相关的实例方法其内部的原理都是怎样的。
1. vm.$mount
1.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$mount( [elementOrSelector] )参数 {Element | string} [elementOrSelector]{boolean} [hydrating] 返回值vm - 实例自身 作用 如果 Vue 实例在实例化时没有收到 el 选项则它处于“未挂载”状态没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。 如果没有提供 elementOrSelector 参数模板将被渲染为文档之外的的元素并且你必须使用原生 DOM API把它插入文档中。 这个方法返回实例自身因而可以链式调用其它实例方法。
1.2 内部原理
关于该方法的内部原理在介绍生命周期篇的模板编译阶段中已经详细分析过此处不再重复。
2. vm.$forceUpdate
2.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$forceUpdate()作用 迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件而不是所有子组件。
2.2 内部原理
通过用法回顾我们知道 该方法是用来迫使Vue 实例重新渲染的。也就是说当调用了该方法当前实例会立即重新渲染。
在分析原理之前我们先思考这样一个问题什么情况下实例会重新渲染那就是当实例依赖的数据发生变化时变化的数据会通知其收集的依赖列表中的依赖进行更新在之前的文章中我们说过收集依赖就是收集watcher依赖更新就是watcher调用update方法更新所以实例依赖的数据发生变化时就会通知实例watcher去执行update方法进行更新。
那么我们就知道了实例的重新渲染其实就是实例watcher执行了update方法。
OK有了这个概念之后接下来我们来分析$forceUpdate源码实现代码如下
Vue.prototype.$forceUpdate function () {const vm: Component thisif (vm._watcher) {vm._watcher.update()}
}可以看到源码实现的逻辑跟我们上面分析的是一致的。在之前的文章中我们说过当前实例的_watcher属性就是该实例的watcher所以要想让实例重新渲染我们只需手动的去执行一下实例watcher的update方法即可。
3. vm.$nextTick
vm.$nextTick 是全局 Vue.nextTick 的别名其用法相同。
3.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$nextTick( [callback] )参数 {Function} [callback] 用法 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样不同的是回调的 this 自动绑定到调用它的实例上。 2.1.0 起新增如果没有提供回调且在支持 Promise 的环境中则返回一个 Promise。请注意 Vue 不自带 Promise 的 polyfill所以如果你的目标浏览器不是原生支持 Promise (IE你们都看我干嘛)你得自行 polyfill。
从上面的官方文档对$nextTick方法的介绍中我们似乎还是不能理解该方法的作用那么我们举个例子看一下如下
templatediv idexample{{message}}/div
/template
scriptvar vm new Vue({el: ##example,data: {message: 123}})vm.message new message // 更改数据console.log(vm.$el.innerHTML) // 123Vue.nextTick(function () {console.log(vm.$el.innerHTML) // new message})
/script在上面例子中当我们更新了message的数据后立即获取vm.$el.innerHTML发现此时获取到的还是更新之前的数据123。但是当我们使用nextTick来获取vm.$el.innerHTML时此时就可以获取到更新后的数据了。这是为什么呢
这里就涉及到Vue中对DOM的更新策略了Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化Vue 将开启一个事件队列并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发只会被推入到事件队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后在下一个的事件循环“tick”中Vue 刷新事件队列并执行实际 (已去重的) 工作。
在上面这个例子中当我们通过 vm.message new message更新数据时此时该组件不会立即重新渲染。当刷新事件队列时组件会在下一个事件循环“tick”中重新渲染。所以当我们更新完数据后此时又想基于更新后的 DOM 状态来做点什么此时我们就需要使用Vue.nextTick(callback)把基于更新后的DOM 状态所需要的操作放入回调函数callback中这样回调函数将在 DOM 更新完成后被调用。
OK现在大家应该对nextTick是什么、为什么要有nextTick以及怎么使用nextTick有个大概的了解了。那么问题又来了Vue为什么要这么设计为什么要异步更新DOM这就涉及到另外一个知识JS的运行机制。
3.2 JS的运行机制
我们知道 JS 执行是单线程的它是基于事件循环的。事件循环大致分为以下几个步骤
所有同步任务都在主线程上执行形成一个执行栈execution context stack。主线程之外还存在一个任务队列task queue。只要异步任务有了运行结果就在任务队列之中放置一个事件。一旦执行栈中的所有同步任务执行完毕系统就会读取任务队列看看里面有哪些事件。那些对应的异步任务于是结束等待状态进入执行栈开始执行。主线程不断重复上面的第三步。
主线程的执行过程就是一个 tick而所有的异步结果都是通过 “任务队列” 来调度。 任务队列中存放的是一个个的任务task。 规范中规定 task 分为两大类分别是宏任务(macro task) 和微任务(micro task并且每执行完一个个宏任务(macro task)后都要去清空该宏任务所对应的微任务队列中所有的微任务(micro task他们的执行顺序如下所示
for (macroTask of macroTaskQueue) {// 1. 处理当前的宏任务handleMacroTask();// 2. 处理对应的所有微任务for (microTask of microTaskQueue) {handleMicroTask(microTask);}
}在浏览器环境中常见的
宏任务(macro task) 有 setTimeout、MessageChannel、postMessage、setImmediate微任务(micro task有MutationObsever 和 Promise.then。
OK有了这个概念之后接下来我们就进入正菜从Vue源码角度来分析nextTick的实现原理。
3.3 内部原理
nextTick 的定义位于源码的src/core/util/next-tick.js中其大概可分为两大部分
能力检测根据能力检测以不同方式执行回调队列
能力检测
Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate如果执行环境不支持则会采用 setTimeout(fn, 0) 代替。
宏任务耗费的时间是大于微任务的所以在浏览器支持的情况下优先使用微任务。如果浏览器不支持微任务使用宏任务但是各种宏任务之间也有效率的不同需要根据浏览器的支持情况使用不同的宏任务。
这一部分的源码如下
let microTimerFunc
let macroTimerFunc
let useMacroTask false/* 对于宏任务(macro task) */
// 检测是否支持原生 setImmediate(高版本 IE 和 Edge 支持)
if (typeof setImmediate ! undefined isNative(setImmediate)) {macroTimerFunc () {setImmediate(flushCallbacks)}
}
// 检测是否支持原生的 MessageChannel
else if (typeof MessageChannel ! undefined (isNative(MessageChannel) ||// PhantomJSMessageChannel.toString() [object MessageChannelConstructor]
)) {const channel new MessageChannel()const port channel.port2channel.port1.onmessage flushCallbacksmacroTimerFunc () {port.postMessage(1)}
}
// 都不支持的情况下使用setTimeout
else {macroTimerFunc () {setTimeout(flushCallbacks, 0)}
}/* 对于微任务(micro task) */
// 检测浏览器是否原生支持 Promise
if (typeof Promise ! undefined isNative(Promise)) {const p Promise.resolve()microTimerFunc () {p.then(flushCallbacks)}
}
// 不支持的话直接指向 macro task 的实现。
else {// fallback to macromicroTimerFunc macroTimerFunc
}首先声明了两个变量 microTimerFunc 和 macroTimerFunc 它们分别对应的是 micro task 的函数和 macro task 的函数。对于 macro task 的实现优先检测是否支持原生 setImmediate这是一个高版本 IE 和Edge 才支持的特性不支持的话再去检测是否支持原生的 MessageChannel如果也不支持的话就会降级为 setTimeout 0而对于 micro task 的实现则检测浏览器是否原生支持 Promise不支持的话直接指向 macro task 的实现。
执行回调队列
接下来就进入了核心函数nextTick中如下
const callbacks [] // 回调队列
let pending false // 异步锁// 执行队列中的每一个回调
function flushCallbacks () {pending false // 重置异步锁// 防止出现nextTick中包含nextTick时出现问题在执行回调函数队列前提前复制备份并清空回调函数队列const copies callbacks.slice(0)callbacks.length 0// 执行回调函数队列for (let i 0; i copies.length; i) {copies[i]()}
}export function nextTick (cb?: Function, ctx?: Object) {let _resolve// 将回调函数推入回调队列callbacks.push(() {if (cb) {try {cb.call(ctx)} catch (e) {handleError(e, ctx, nextTick)}} else if (_resolve) {_resolve(ctx)}})// 如果异步锁未锁上锁上异步锁调用异步函数准备等同步函数执行完后就开始执行回调函数队列if (!pending) {pending trueif (useMacroTask) {macroTimerFunc()} else {microTimerFunc()}}// 如果没有提供回调并且支持Promise返回一个Promiseif (!cb typeof Promise ! undefined) {return new Promise(resolve {_resolve resolve})}
}首先先来看 nextTick函数该函数的主要逻辑是先把传入的回调函数 cb 推入 回调队列callbacks 数组同时在接收第一个回调函数时执行能力检测中对应的异步方法异步方法中调用了回调函数队列。最后一次性地根据 useMacroTask 条件执行 macroTimerFunc 或者是 microTimerFunc而它们都会在下一个 tick 执行 flushCallbacksflushCallbacks 的逻辑非常简单对 callbacks 遍历然后执行相应的回调函数。
nextTick 函数最后还有一段逻辑
if (!cb typeof Promise ! undefined) {return new Promise(resolve {_resolve resolve})
}这是当 nextTick 不传 cb 参数的时候提供一个 Promise 化的调用比如
nextTick().then(() {})当 _resolve 函数执行就会跳到 then 的逻辑中。
这里有两个问题需要注意 如何保证只在接收第一个回调函数时执行异步方法 nextTick源码中使用了一个异步锁的概念即接收第一个回调函数时先关上锁执行异步方法。此时浏览器处于等待执行完同步代码就执行异步代码的情况。 执行 flushCallbacks 函数时为什么需要备份回调函数队列执行的也是备份的回调函数队列 因为会出现这么一种情况nextTick 的回调函数中还使用 nextTick。如果 flushCallbacks 不做特殊处理直接循环执行回调函数会导致里面nextTick 中的回调函数会进入回调队列。
以上就是对 nextTick 的源码分析我们了解到数据的变化到 DOM 的重新渲染是一个异步过程发生在下一个 tick。当我们在实际开发中比如从服务端接口去获取数据的时候数据做了修改如果我们的某些方法去依赖了数据修改后的 DOM 变化我们就必须在 nextTick 后执行。
4. vm.$destory
4.1 用法回顾
在介绍方法的内部原理之前我们先根据官方文档示例回顾一下它的用法。
vm.$destroy()用法 完全销毁一个实例。清理它与其它实例的连接解绑它的全部指令及事件监听器。 触发 beforeDestroy 和 destroyed 的钩子。
4.2 内部原理
关于该方法的内部原理在介绍生命周期篇的销毁阶段中已经详细分析过此处不再重复。