이 글에서는 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

실제 세계에서 JavaScript의 응용 프로그램에는 서버 측 프로그래밍, 모바일 애플리케이션 개발 및 사물 인터넷 제어가 포함됩니다. 1. 서버 측 프로그래밍은 Node.js를 통해 실현되며 동시 요청 처리에 적합합니다. 2. 모바일 애플리케이션 개발은 재교육을 통해 수행되며 크로스 플랫폼 배포를 지원합니다. 3. Johnny-Five 라이브러리를 통한 IoT 장치 제어에 사용되며 하드웨어 상호 작용에 적합합니다.

일상적인 기술 도구를 사용하여 기능적 다중 테넌트 SaaS 응용 프로그램 (Edtech 앱)을 구축했으며 동일한 작업을 수행 할 수 있습니다. 먼저, 다중 테넌트 SaaS 응용 프로그램은 무엇입니까? 멀티 테넌트 SAAS 응용 프로그램은 노래에서 여러 고객에게 서비스를 제공 할 수 있습니다.

이 기사에서는 Contrim에 의해 확보 된 백엔드와의 프론트 엔드 통합을 보여 주며 Next.js를 사용하여 기능적인 Edtech SaaS 응용 프로그램을 구축합니다. Frontend는 UI 가시성을 제어하기 위해 사용자 권한을 가져오고 API가 역할 기반을 준수하도록합니다.

JavaScript는 현대 웹 개발의 핵심 언어이며 다양성과 유연성에 널리 사용됩니다. 1) 프론트 엔드 개발 : DOM 운영 및 최신 프레임 워크 (예 : React, Vue.js, Angular)를 통해 동적 웹 페이지 및 단일 페이지 응용 프로그램을 구축합니다. 2) 서버 측 개발 : Node.js는 비 차단 I/O 모델을 사용하여 높은 동시성 및 실시간 응용 프로그램을 처리합니다. 3) 모바일 및 데스크탑 애플리케이션 개발 : 크로스 플랫폼 개발은 개발 효율을 향상시키기 위해 반응 및 전자를 통해 실현됩니다.

JavaScript의 최신 트렌드에는 Typescript의 Rise, 현대 프레임 워크 및 라이브러리의 인기 및 WebAssembly의 적용이 포함됩니다. 향후 전망은보다 강력한 유형 시스템, 서버 측 JavaScript 개발, 인공 지능 및 기계 학습의 확장, IoT 및 Edge 컴퓨팅의 잠재력을 포함합니다.

JavaScript는 현대 웹 개발의 초석이며 주요 기능에는 이벤트 중심 프로그래밍, 동적 컨텐츠 생성 및 비동기 프로그래밍이 포함됩니다. 1) 이벤트 중심 프로그래밍을 사용하면 사용자 작업에 따라 웹 페이지가 동적으로 변경 될 수 있습니다. 2) 동적 컨텐츠 생성을 사용하면 조건에 따라 페이지 컨텐츠를 조정할 수 있습니다. 3) 비동기 프로그래밍은 사용자 인터페이스가 차단되지 않도록합니다. JavaScript는 웹 상호 작용, 단일 페이지 응용 프로그램 및 서버 측 개발에 널리 사용되며 사용자 경험 및 크로스 플랫폼 개발의 유연성을 크게 향상시킵니다.

Python은 데이터 과학 및 기계 학습에 더 적합한 반면 JavaScript는 프론트 엔드 및 풀 스택 개발에 더 적합합니다. 1. Python은 간결한 구문 및 풍부한 라이브러리 생태계로 유명하며 데이터 분석 및 웹 개발에 적합합니다. 2. JavaScript는 프론트 엔드 개발의 핵심입니다. Node.js는 서버 측 프로그래밍을 지원하며 풀 스택 개발에 적합합니다.

JavaScript는 이미 최신 브라우저에 내장되어 있기 때문에 설치가 필요하지 않습니다. 시작하려면 텍스트 편집기와 브라우저 만 있으면됩니다. 1) 브라우저 환경에서 태그를 통해 HTML 파일을 포함하여 실행하십시오. 2) Node.js 환경에서 Node.js를 다운로드하고 설치 한 후 명령 줄을 통해 JavaScript 파일을 실행하십시오.


핫 AI 도구

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

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

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

Clothoff.io
AI 옷 제거제

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

인기 기사

뜨거운 도구

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기

ZendStudio 13.5.1 맥
강력한 PHP 통합 개발 환경

DVWA
DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

WebStorm Mac 버전
유용한 JavaScript 개발 도구

안전한 시험 브라우저
안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.
