Vue 소스 코드의 일괄 비동기 업데이트 및 nextTick 원칙 분석
이 글에서는 Vue 소스 코드의 일괄 비동기 업데이트 분석과 nextTick 원칙을 소개합니다. 이는 도움이 필요한 친구들이 참고할 수 있습니다.
vue는 이미 국내 프론트엔드 웹엔드의 3분의 1을 차지하고 있고, 일상적으로 사용하는 주요 기술 스택 중 하나이기도 합니다. 게다가 수많은 vue 소스 코드가 있는 이유도 궁금합니다. 최근 커뮤니티에 이런 글이 올라왔습니다. 이런 글의 경우, 이번 기회에 여러분의 글과 토론에서 몇 가지 자양분을 끌어내는 동시에 소스 코드를 읽을 때 제가 생각한 몇 가지 내용을 요약하여 다음과 같은 글을 작성하겠습니다. 내 생각 요약
Target Vue 버전: 2.5 .17-beta.0
2.5.17-beta.0
vue源码注释:https://github.com/SHERlocked93/vue-analysis
声明:文章中源码的语法都使用 Flow,并且源码根据需要都有删节(为了不被迷糊 @_@),如果要看完整版的请进入上面的github地址,本文是系列文章,文章地址见底部~
1. 异步更新
我们在依赖收集原理的响应式化方法 defineReactive
中的 setter
访问器中有派发更新 dep.notify()
方法,这个方法会挨个通知在 dep
的 subs
中收集的订阅自己变动的watchers执行update。一起来看看 update
方法的实现:
// src/core/observer/watcher.js /* Subscriber接口,当依赖发生改变的时候进行回调 */ update() { if (this.computed) { // 一个computed watcher有两种模式:activated lazy(默认) // 只有当它被至少一个订阅者依赖时才置activated,这通常是另一个计算属性或组件的render function if (this.dep.subs.length === 0) { // 如果没人订阅这个计算属性的变化 // lazy时,我们希望它只在必要时执行计算,所以我们只是简单地将观察者标记为dirty // 当计算属性被访问时,实际的计算在this.evaluate()中执行 this.dirty = true } else { // activated模式下,我们希望主动执行计算,但只有当值确实发生变化时才通知我们的订阅者 this.getAndInvoke(() => { this.dep.notify() // 通知渲染watcher重新渲染,通知依赖自己的所有watcher执行update }) } } else if (this.sync) { // 同步 this.run() } else { queueWatcher(this) // 异步推送到调度者观察者队列中,下一个tick时调用 } }
如果不是 computed watcher
也非 sync
会把调用update的当前watcher推送到调度者队列中,下一个tick时调用,看看 queueWatcher
:
// src/core/observer/scheduler.js /* 将一个观察者对象push进观察者队列,在队列中已经存在相同的id则 * 该watcher将被跳过,除非它是在队列正被flush时推送 */ export function queueWatcher (watcher: Watcher) { const id = watcher.id if (has[id] == null) { // 检验id是否存在,已经存在则直接跳过,不存在则标记哈希表has,用于下次检验 has[id] = true queue.push(watcher) // 如果没有正在flush,直接push到队列中 if (!waiting) { // 标记是否已传给nextTick waiting = true nextTick(flushSchedulerQueue) } } } /* 重置调度者状态 */ function resetSchedulerState () { queue.length = 0 has = {} waiting = false }
这里使用了一个 has
的哈希map用来检查是否当前watcher的id是否存在,若已存在则跳过,不存在则就push到 queue
队列中并标记哈希表has,用于下次检验,防止重复添加。这就是一个去重的过程,比每次查重都要去queue中找要文明,在渲染的时候就不会重复 patch
相同watcher的变化,这样就算同步修改了一百次视图中用到的data,异步 patch
的时候也只会更新最后一次修改。
这里的 waiting
方法是用来标记 flushSchedulerQueue
是否已经传递给 nextTick
的标记位,如果已经传递则只push到队列中不传递 flushSchedulerQueue
给 nextTick
,等到 resetSchedulerState
重置调度者状态的时候 waiting
会被置回 false
允许 flushSchedulerQueue
被传递给下一个tick的回调,总之保证了 flushSchedulerQueue
回调在一个tick内只允许被传入一次。来看看被传递给 nextTick
的回调 flushSchedulerQueue
做了什么:
// src/core/observer/scheduler.js /* nextTick的回调函数,在下一个tick时flush掉两个队列同时运行watchers */ function flushSchedulerQueue () { flushing = true let watcher, id queue.sort((a, b) => a.id - b.id) // 排序 for (index = 0; index MAX_UPDATE_COUNT) { // 持续执行了一百次watch代表可能存在死循环 warn() // 进入死循环的警告 break } } } resetSchedulerState() // 重置调度者状态 callActivatedHooks() // 使子组件状态都置成active同时调用activated钩子 callUpdatedHooks() // 调用updated钩子 }
在 nextTick
方法中执行 flushSchedulerQueue
方法,这个方法挨个执行 queue
中的watcher的 run
方法。我们看到在首先有个 queue.sort()
方法把队列中的watcher按id从小到大排了个序,这样做可以保证:
组件更新的顺序是从父组件到子组件的顺序,因为父组件总是比子组件先创建。
一个组件的user watchers(侦听器watcher)比render watcher先运行,因为user watchers往往比render watcher更早创建
如果一个组件在父组件watcher运行期间被销毁,它的watcher执行将被跳过
在挨个执行队列中的for循环中,index 这里没有将length进行缓存,因为在执行处理现有watcher对象期间,更多的watcher对象可能会被push进queue。
那么数据的修改从model层反映到view的过程:数据更改 -> setter -> Dep -> Watcher -> nextTick -> patch -> 更新视图
2. nextTick原理
2.1 宏任务/微任务
这里就来看看包含着每个watcher执行的方法被作为回调传入 nextTick
之后,nextTick
对这个方法做了什么。不过首先要了解一下浏览器中的 EventLoop
、macro task
、micro task
진술: 글의 소스코드 구문은 Flow를 사용하였으며, 소스코드는 필요에 따라 축약하였습니다. (혼란을 피하기 위해@_@), 풀버전을 보시려면 위의 github 주소를 입력해 주세요. 일련의 기사는 하단의 기사 주소를 참조하세요~
1. 비동기 업데이트
우리는 응답형 메서드defineReactive의 원칙에 따라 <code>setter
접근자를 사용합니다. 에는 디스패치 업데이트 dep.notify()
메서드가 있습니다. 이 메서드는 dep
의 subs
에 수집된 감시자에게 하나씩 알립니다. 자신의 변경 사항을 구독하는 code>는 업데이트를 실행합니다. update
메소드의 구현을 살펴보겠습니다.
// src/core/util/next-tick.js const callbacks = [] // 存放异步执行的回调 let pending = false // 一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送 /* 挨个同步执行callbacks中回调 */ function flushCallbacks() { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || MessageChannel.toString() === '[object MessageChannelConstructor]' // PhantomJS )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } // 微任务 if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { microTimerFunc = macroTimerFunc // fallback to macro }
계산된 감시자
또는 sync
가 아닌 경우 호출하는 현재 감시자 업데이트는 스케줄러에 푸시됩니다. 대기열에서는 다음 틱에서 호출됩니다. queueWatcher
를 살펴보세요. - 여기에서는
has
의 해시 맵이 사용됩니다. 현재 감시자의 ID가 존재하는지 확인하려면, 존재하지 않으면 건너뛰고, 존재하지 않으면queue
대기열에 푸시하고 해시 테이블에 사용됩니다. 반복적인 추가를 방지하기 위해 다음 검사를 실시합니다. 이는 중복을 확인하기 위해 매번 대기열로 이동하는 것보다 더 문명화된 프로세스입니다. 이러한 방식으로 동일한 감시자에 대한패치
변경이 반복되지 않습니다. , 동시에 100개의 수정이 이루어지더라도 보조 보기에 사용된 데이터는 비동기패치
중에 마지막 수정으로만 업데이트됩니다. 여기서waiting
메소드는flushSchedulerQueue
가nextTick
의 태그 비트에 전달되었는지 여부를 표시하는 데 사용됩니다.flushSchedulerQueue
를nextTick
에 전달하지 않으면resetSchedulerStatewaiting
이 다시로 설정됩니다. /code>는 스케줄러 상태를 재설정합니다. >false
는flushSchedulerQueue
가 다음 틱의 콜백으로 전달되도록 허용합니다. 즉,flushSchedulerQueue
콜백을 보장합니다. 틱당 한 번만 전달될 수 있습니다.nextTick
에 전달된 콜백flushSchedulerQueue
가 수행하는 작업을 살펴보겠습니다. -
nextTick
메서드 > 메서드에서flushSchedulerQueue
를 실행합니다. 이 메소드는queue
에 있는 watcher의run
메소드를 하나씩 실행합니다. 먼저 대기열에 있는 감시자를 ID별로 작은 것부터 큰 것 순으로 정렬하는queue.sort()
메서드가 있다는 것을 알 수 있습니다. 이를 통해 다음이 보장됩니다. - 구성 요소 업데이트 순서는 상위 구성 요소가 항상 하위 구성 요소보다 먼저 생성되므로 상위 구성 요소에서 하위 구성 요소로의 순서입니다. 🎜🎜사용자 감시자는 렌더링 감시자보다 먼저 생성되는 경우가 많기 때문에 구성 요소의 사용자 감시자(리스너 감시자)는 렌더링 감시자보다 먼저 실행됩니다.🎜🎜🎜🎜상위 구성 요소 감시자가 실행되는 동안 구성 요소가 삭제되면 감시자 실행이 be skiped🎜🎜
// src/core/util/next-tick.js 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 = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } /* 强制使用macrotask的方法 */ export function withMacroTask(fn: Function): Function { return fn._withTask || (fn._withTask = function() { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) }
<p> <span>{{ name }}</span> <button>change name</button> </p><p></p> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93' } }, methods: { change() { const $name = this.$refs.name this.$nextTick(() => console.log('setter前:' + $name.innerHTML)) this.name = ' name改喽 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + $name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML)) } } }) </script>
index 실행 중에 기존 watcher 객체가 처리되기 때문에 여기에 길이가 캐시되지 않습니다. 이 기간 동안 더 많은 감시자 개체가 대기열에 푸시될 수 있습니다. 🎜🎜그런 다음 모델 레이어에서 뷰에 반영되는 데이터 수정 과정: <code>Data Change-> Dep -> nextTick -> code>🎜 <h2 id="nextTick의-원리">2. nextTick의 원리</h2>
<h3 id="매크로-작업-마이크로-작업">2.1 매크로 작업/마이크로 작업</h3>🎜여기에서는 각 감시자 실행을 포함하고 <code>nextTick에 전달되는 메서드를 살펴봅니다.
콜백 > 그 후 nextTick
은 이 메서드를 사용하여 작업을 수행합니다. 하지만 먼저 브라우저의 EventLoop
, 매크로 태스크
, 마이크로 태스크
의 개념을 이해해야 합니다. JS와 Node를 참조할 수 있습니다. js의 이벤트 루프에 대한 이 기사에서는 메인 스레드에서 후자 둘 사이의 실행 관계를 보여주는 그림이 있습니다. 동기화 작업: 🎜🎜🎜🎜 엔진은 먼저 매크로태스크 대기열에서 첫 번째 작업을 꺼냅니다. 실행이 완료된 후 마이크로태스크 대기열의 모든 작업을 꺼내어 모두 순서대로 실행합니다. 실행이 완료된 후 매크로태스크 대기열에서 첫 번째 작업을 다시 꺼냅니다. 🎜🎜🎜🎜 두 대기열의 모든 작업이 반복됩니다. 꺼냈다. 🎜浏览器环境中常见的异步任务种类,按照优先级:
macro task
:同步代码、setImmediate
、MessageChannel
、setTimeout/setInterval
micro task
:Promise.then
、MutationObserver
有的文章把 micro task
叫微任务,macro task
叫宏任务,因为这两个单词拼写太像了 -。- ,所以后面的注释多用中文表示~
先来看看源码中对 micro task
与 macro task
的实现: macroTimerFunc
、microTimerFunc
// src/core/util/next-tick.js const callbacks = [] // 存放异步执行的回调 let pending = false // 一个标记位,如果已经有timerFunc被推送到任务队列中去则不需要重复推送 /* 挨个同步执行callbacks中回调 */ function flushCallbacks() { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i { setImmediate(flushCallbacks) } } else if (typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || MessageChannel.toString() === '[object MessageChannelConstructor]' // PhantomJS )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } } else { macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } } // 微任务 if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) } } else { microTimerFunc = macroTimerFunc // fallback to macro }
flushCallbacks
这个方法就是挨个同步的去执行callbacks中的回调函数们,callbacks中的回调函数是在调用 nextTick
的时候添加进去的;那么怎么去使用 micro task
与 macro task
去执行 flushCallbacks
呢,这里他们的实现 macroTimerFunc
、microTimerFunc
使用浏览器中宏任务/微任务的API对flushCallbacks
方法进行了一层包装。比如宏任务方法 macroTimerFunc=()=>{ setImmediate(flushCallbacks) }
,这样在触发宏任务执行的时候 macroTimerFunc()
就可以在浏览器中的下一个宏任务loop的时候消费这些保存在callbacks数组中的回调了,微任务同理。同时也可以看出传给 nextTick
的异步回调函数是被压成了一个同步任务在一个tick执行完的,而不是开启多个异步任务。
注意这里有个比较难理解的地方,第一次调用 nextTick
的时候 pending
为false,此时已经push到浏览器event loop中一个宏任务或微任务的task,如果在没有flush掉的情况下继续往callbacks里面添加,那么在执行这个占位queue的时候会执行之后添加的回调,所以 macroTimerFunc
、microTimerFunc
相当于task queue的占位,以后 pending
为true则继续往占位queue里面添加,event loop轮到这个task queue的时候将一并执行。执行 flushCallbacks
时 pending
置false,允许下一轮执行 nextTick
时往event loop占位。
可以看到上面 macroTimerFunc
与 microTimerFunc
进行了在不同浏览器兼容性下的平稳退化,或者说降级策略:
macroTimerFunc
:setImmediate -> MessageChannel -> setTimeout
。首先检测是否原生支持setImmediate
,这个方法只在 IE、Edge 浏览器中原生实现,然后检测是否支持 MessageChannel,如果对MessageChannel
不了解可以参考一下这篇文章,还不支持的话最后使用setTimeout
;
为什么优先使用setImmediate
与MessageChannel
而不直接使用setTimeout
呢,是因为HTML5规定setTimeout执行的最小延时为4ms,而嵌套的timeout表现为10ms,为了尽可能快的让回调执行,没有最小延时限制的前两者显然要优于setTimeout
。microTimerFunc
:Promise.then -> macroTimerFunc
。首先检查是否支持Promise
,如果支持的话通过Promise.then
来调用flushCallbacks
方法,否则退化为macroTimerFunc
;
vue2.5之后nextTick
中因为兼容性原因删除了微任务平稳退化的MutationObserver
的方式。
2.2 nextTick实现
最后来看看我们平常用到的 nextTick
方法到底是如何实现的:
// src/core/util/next-tick.js 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 = true if (useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } /* 强制使用macrotask的方法 */ export function withMacroTask(fn: Function): Function { return fn._withTask || (fn._withTask = function() { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) }
nextTick
在这里分为三个部分,我们一起来看一下;
首先
nextTick
把传入的cb
回调函数用try-catch
包裹后放在一个匿名函数中推入callbacks数组中,这么做是因为防止单个cb
如果执行错误不至于让整个JS线程挂掉,每个cb
都包裹是防止这些回调函数如果执行错误不会相互影响,比如前一个抛错了后一个仍然可以执行。然后检查
pending
状态,这个跟之前介绍的queueWatcher
中的waiting
是一个意思,它是一个标记位,一开始是false
在进入macroTimerFunc
、microTimerFunc
方法前被置为true
,因此下次调用nextTick
就不会进入macroTimerFunc
、microTimerFunc
方法,这两个方法中会在下一个macro/micro tick
时候flushCallbacks
异步的去执行callbacks队列中收集的任务,而flushCallbacks
方法在执行一开始会把pending
置false
,因此下一次调用nextTick
时候又能开启新一轮的macroTimerFunc
、microTimerFunc
,这样就形成了vue中的event loop
。最后检查是否传入了
cb
,因为nextTick
还支持Promise化的调用:nextTick().then(() => {})
,所以如果没有传入cb
就直接return了一个Promise实例,并且把resolve传递给_resolve,这样后者执行的时候就跳到我们调用的时候传递进then
的方法中。
Vue源码中 next-tick.js
文件还有一段重要的注释,这里就翻译一下:
在vue2.5之前的版本中,nextTick基本上基于micro task
来实现的,但是在某些情况下micro task
具有太高的优先级,并且可能在连续顺序事件之间(例如#4521,#6690)或者甚至在同一事件的事件冒泡过程中之间触发(#6566)。但是如果全部都改成macro task
,对一些有重绘和动画的场景也会有性能影响,如 issue #6813。vue2.5之后版本提供的解决办法是默认使用micro task
,但在需要时(例如在v-on附加的事件处理程序中)强制使用macro task
。
为什么默认优先使用 micro task
呢,是利用其高优先级的特性,保证队列中的微任务在一次循环全部执行完毕。
强制 macro task
的方法是在绑定 DOM 事件的时候,默认会给回调的 handler 函数调用 withMacroTask
方法做一层包装 handler = withMacroTask(handler)
,它保证整个回调函数执行过程中,遇到数据状态的改变,这些改变都会被推到 macro task
中。以上实现在 src/platforms/web/runtime/modules/events.js 的 add
方法中,可以自己看一看具体代码。
刚好在写这篇文章的时候思否上有人问了个问题 vue 2.4 和2.5 版本的@input事件不一样 ,这个问题的原因也是因为2.5之前版本的DOM事件采用 micro task
,而之后采用 macro task
,解决的途径参考 中介绍的几个办法,这里就提供一个在mounted钩子中用 addEventListener
添加原生事件的方法来实现,参见 CodePen。
3. 一个例子
说这么多,不如来个例子,执行参见 CodePen
<p> <span>{{ name }}</span> <button>change name</button> </p><p></p> <script> new Vue({ el: '#app', data() { return { name: 'SHERlocked93' } }, methods: { change() { const $name = this.$refs.name this.$nextTick(() => console.log('setter前:' + $name.innerHTML)) this.name = ' name改喽 ' console.log('同步方式:' + this.$refs.name.innerHTML) setTimeout(() => this.console("setTimeout方式:" + this.$refs.name.innerHTML)) this.$nextTick(() => console.log('setter后:' + $name.innerHTML)) this.$nextTick().then(() => console.log('Promise方式:' + $name.innerHTML)) } } }) </script>
执行以下看看结果:
同步方式:SHERlocked93 setter前:SHERlocked93 setter后:name改喽 Promise方式:name改喽 setTimeout方式:name改喽
为什么是这样的结果呢,解释一下:
同步方式: 当把data中的name修改之后,此时会触发name的
setter
中的dep.notify
通知依赖本data的render watcher去update
,update
会把flushSchedulerQueue
函数传递给nextTick
,render watcher在flushSchedulerQueue
函数运行时watcher.run
再走diff -> patch
那一套重渲染re-render
视图,这个过程中会重新依赖收集,这个过程是异步的;所以当我们直接修改了name之后打印,这时异步的改动还没有被patch
到视图上,所以获取视图上的DOM元素还是原来的内容。Setter 이전: 원래 콘텐츠가 Setter 이전에 인쇄되는 이유는 콜백을 Push할 때
nextTick
이 호출되기 때문입니다. 콜백은 하나씩 배열한 다음for
루프를 하나씩 실행하므로 대기열과 비슷한 개념으로 이름을 수정한 후 렌더링 감시자가 실행됩니다.schedulerQueue
대기열을 채우고 실행 함수flushSchedulerQueue
를nextTick
에 전달합니다. 이때 이미pre-setter 함수를 콜백 대기열에 넣습니다. code>. 이 <code>cb
는setter 사전 함수
다음에 콜백 대기열로 푸시되기 때문에 첫 번째 콜백에서 콜백을 실행할 때 in, First-out 기준으로가 먼저 실행됩니다. 이때, Render Watcher의 <code>watcher.run
은 실행되지 않으므로 인쇄됩니다. DOM 요소는 여전히 원본 콘텐츠입니다.nextTick
在被调用的时候把回调挨个push进callbacks数组,之后执行的时候也是for
循环出来挨个执行,所以是类似于队列这样一个概念,先入先出;在修改name之后,触发把render watcher填入schedulerQueue
队列并把他的执行函数flushSchedulerQueue
传递给nextTick
,此时callbacks队列中已经有了setter前函数
了,因为这个cb
是在setter前函数
之后被push进callbacks队列的,那么先入先出的执行callbacks中回调的时候先执行setter前函数
,这时并未执行render watcher的watcher.run
,所以打印DOM元素仍然是原来的内容。setter后: setter后这时已经执行完
flushSchedulerQueue
,这时render watcher已经把改动patch
到视图上,所以此时获取DOM是改过之后的内容。Promise方式: 相当于
Promise.then
的方式执行这个函数,此时DOM已经更改。setTimeout方式: 最后执行macro task的任务,此时DOM已经更改。
注意,在执行 setter前函数
这个异步任务之前,同步的代码已经执行完毕,异步的任务都还未执行,所有的 $nextTick
函数也执行完毕,所有回调都被push进了callbacks队列中等待执行,所以在setter前函数
执行的时候,此时callbacks队列是这样的:[setter前函数
,flushSchedulerQueue
,setter后函数
,Promise方式函数
],它是一个micro task队列,执行完毕之后执行macro task setTimeout
,所以打印出上面的结果。
另外,如果浏览器的宏任务队列里面有setImmediate
、MessageChannel
、setTimeout/setInterval
各种类型的任务,那么会按照上面的顺序挨个按照添加进event loop中的顺序执行,所以如果浏览器支持MessageChannel
, nextTick
执行的是 macroTimerFunc
,那么如果 macrotask queue 中同时有 nextTick
添加的任务和用户自己添加的 setTimeout
类型的任务,会优先执行 nextTick
中的任务,因为MessageChannel
的优先级比 setTimeout
的高,setImmediate
Setter 이후 flushSchedulerQueue
가 실행되었고, 렌더링 감시자가 패치
하여 이때 얻은 DOM이 수정된 콘텐츠입니다.
Promise 메소드:
은 이 함수를 실행하는Promise.then
과 동일합니다. DOM이 변경되었습니다. setTimeout 메소드: #🎜🎜# 마지막으로 DOM이 변경되면 매크로 작업을 실행합니다. #🎜🎜##🎜🎜##🎜🎜#주의할 점은 pre-setter 함수
의 비동기 작업을 실행하기 전에 동기 코드가 실행되었고 아직 비동기 작업이 실행되지 않았다는 점입니다. 모든 $nextTick
함수도 실행되었고 모든 콜백은 실행을 기다리기 위해 콜백 큐에 푸시되었으므로 pre-setter 함수
가 실행될 때입니다. , 콜백 대기열은 다음과 같습니다: [setter 전 함수
, flushSchedulerQueue
, setter 후 함수
, Promise 모드 함수
], 이는 마이크로 작업 대기열이 완료 후 매크로 작업 setTimeout
을 실행하므로 위의 결과가 인쇄됩니다. #🎜🎜##🎜🎜#또한 브라우저의 매크로 작업 대기열에 다양한 유형의 setImmediate
, MessageChannel
, setTimeout/setInterval
이 포함되어 있는 경우 위의 순서대로 이벤트 루프에 추가된 순서대로 작업이 하나씩 실행되므로 브라우저가 MessageChannel
를 지원하는 경우 nextTick
는 macroTimerFunc
를 실행합니다. >, nextTick
에 의해 추가된 작업과 사용자가 매크로태스크 대기열에 추가한 setTimeout
유형의 작업이 있는 경우 nextTick
이 실행됩니다. MessageChannel
의 첫 번째 작업은 setTimeout
보다 우선순위가 높으며 setImmediate
에도 동일하게 적용됩니다. #🎜🎜##🎜🎜#관련 추천: #🎜🎜##🎜🎜##🎜🎜##🎜🎜#Vue에서 mixin 사용 방법 분석#🎜🎜##🎜🎜##🎜🎜##🎜🎜 # #🎜🎜##🎜🎜##🎜🎜#Vue2.0 사용자 정의 지시어와 인스턴스 속성 및 메서드 #🎜🎜##🎜🎜##🎜🎜#위 내용은 Vue 소스 코드의 일괄 비동기 업데이트 및 nextTick 원칙 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Vue 프레임워크를 사용하여 프런트엔드 프로젝트를 개발할 때 배포 시 여러 환경을 배포하게 되는데, 개발 환경, 테스트 환경, 온라인 환경에서 호출되는 인터페이스 도메인 이름이 다른 경우가 많습니다. 어떻게 구별할 수 있나요? 그것은 환경 변수와 패턴을 사용하는 것입니다.

구성요소화와 모듈화의 차이점: 모듈화는 코드 논리의 관점에서 구분되며, 코드 계층 개발을 용이하게 하고 각 기능 모듈의 기능이 일관되게 유지되도록 합니다. 컴포넌트화는 UI 인터페이스 관점에서 계획하는 것으로 프런트엔드의 컴포넌트화는 UI 컴포넌트의 재사용을 용이하게 합니다.

Ace는 JavaScript로 작성된 내장형 코드 편집기입니다. Sublime, Vim 및 TextMate와 같은 기본 편집기의 기능 및 성능과 일치합니다. 모든 웹페이지와 JavaScript 애플리케이션에 쉽게 삽입할 수 있습니다. Ace는 Cloud9 IDE의 메인 편집자로 유지되며 Mozilla Skywriter(Bespin) 프로젝트의 후속 버전입니다.

Vue.js는 오늘날 프런트엔드 개발에서 매우 인기 있는 프레임워크가 되었습니다. Vue.js가 계속 발전함에 따라 단위 테스트는 점점 더 중요해지고 있습니다. 오늘은 Vue.js 3에서 단위 테스트를 작성하는 방법을 살펴보고 몇 가지 모범 사례와 일반적인 문제 및 솔루션을 제공하겠습니다.

서문: vue3 개발에서 반응형은 반응형 데이터를 구현하는 방법을 제공합니다. 일상적인 개발에서 자주 사용되는 API입니다. 이 기사에서 저자는 내부 작동 메커니즘을 탐구합니다.

Vue.js에서 개발자는 JSX 구문과 템플릿 구문이라는 두 가지 다른 구문을 사용하여 사용자 인터페이스를 만들 수 있습니다. 두 구문 모두 장점과 단점이 있습니다. 차이점, 장점 및 단점을 논의해 보겠습니다.

Vue3 동적 구성 요소에서 예외를 처리하는 방법은 무엇입니까? 다음 기사에서는 Vue3 동적 구성 요소 예외 처리 방법에 대해 설명합니다. 이것이 모든 사람에게 도움이 되기를 바랍니다.

실제 개발 프로젝트 프로세스에서는 상대적으로 큰 파일을 업로드해야 하는 경우가 있는데, 그러면 업로드가 상대적으로 느려지므로 백그라운드에서 파일 조각을 업로드하려면 매우 간단합니다. 기가바이트 파일 스트림이 여러 개의 작은 파일 스트림으로 절단된 다음 인터페이스는 작은 파일 스트림을 각각 전달하도록 요청됩니다.
