JavaScript 和 ES6
在这个过程你会发现,有很多 JS 知识点你并不能更好的理解为什么这么设计,以及这样设计的好处是什么,这就逼着让你去学习这单个知识点的来龙去脉,去哪学?第一,书籍,我知道你不喜欢看,我最近通过刷大厂面试题整理了一份前端核心知识笔记,比较书籍更精简,一句废话都没有,这份笔记也让我通过跳槽从8k涨成20k。
如果你觉得对你有帮助,可以戳这里获取:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
其实它的意思就是
在Vue.js中,当状态发生改变时,watcher会得到通知,然后触发虚拟DOM的渲染过程。而watcher触发渲染这个操作并不是同步的,而是异步的。Vue.js中有一个队列(即callbacks),每当需要渲染时,会将watcher推送到这个队列中,在下一次事件循环中再让watcher触发渲染的过程。举个例子
mounted: function () {
this.message = “abc”
this.$nextTick(function () {
console.log(this.message)
})
}
上面代码中this.message = "abc"
使得watcher会得到通知,然后触发 渲染Dom操作,然后把它 放入了队列callback中,
此时callbacks = [渲染Dom操作],
然后
this.$nextTick(function () {
console.log(this.message)
})
又把 打印message这个操作 放入callbacks中。
此时callbacks= [渲染Dom操作,打印message]
最后再把 依次执行callbacks里的操作这个操作 包装成 异步任务。
包装成 异步任务 很简单,作为setTimeout,或promise.then的回调就行
不懂watcher是什么的同学,推荐先看学习vue源码(12)大白话谈响应式原理。
在这里 我必须先事先强调 nextTick在vue中有两种用途:
-
一种就是 vue内部 使用nextTick,把 渲染Dom操作 这个操作 放入到callbacks 中,
-
一种是把nextTick 给开发者使用,也就是 我们经常使用的
$nextTick
, 然后把我们传入$nextTick
的回调函数放到callbacks中0。
刚好这两种用途 对应上面我们一开始举的例子中的两个操作。
2. 为什么Vue.js使用异步更新队列
Vue.js2.0开始使用虚拟DOM进行渲染,变化侦测的通知只发送到组件,组件内用到的所有状态的变化都会通知到同一个watcher,然后虚拟DOM会对整个组件进行“比对(diff)”并更改DOM。
也就是说,如果在同一轮事件循环中有两个数据发生了变化,那么组件的watcher会受到两份通知,从而进行两次渲染。事实上,并不需要渲染两次,虚拟DOM会对整个组件进行渲染,所有只需要等所有状态都修改完毕后,一次性将整个组件的DOM渲染到最新即可。
要解决整个问题,Vue.js的实现方式是将收到通知的watcher实例添加到队列中缓存起来,并且在添加到队列之前检查其中是否已经存在相同的watcher,只有不存在时,才将watcher实例添加到队列中。然后在下一次事件循环(event loop)中,Vue.js会让队列中的watcher触发渲染流程并清空队列。这样就可以保证即使在同一事件循环中有两个状态发生改变,watcher最后也只执行一次渲染流程。
举个例子,假如
export default {
data () {
return {
msg1: 1,
msg2:2,
msg3:3
}
},
mounted () {
this.msg1 = 0
this.msg2 = 0
this.msg3 = 0
},
}
如果我们 不使用异步队列,那上面的例子中,
-
this.msg1 = 0
会触发 该组件 更新视图, -
this.msg2 = 0
会触发 该组件 更新视图 -
this.msg3 = 0
会触发 该组件 更新视图
显然,就 使得 同一个组件 更新了三次 视图。
注意:同一个组件里的任何一个数据发生改变都会触发整个组件Dom的更新。
当我们使用异步队列后。
-
this.msg1 = 0
把 更新该组件Dom的操作放入callbacks中, -
this.msg2 = 0
发现 callbacks 已经有 该组件的更新操作,没事了。 -
this.msg3 = 0
发现 callbacks 已经有 该组件的更新操作,没事了。
因此,当callbacks 被包装成 异步任务 执行时, 该组件就只会更新一次DOM,这对性能的提升是多么的高啊啊!!!
了解nextTick原理前,我们 需要 有 是事件循环 和 执行栈 的预备知识 , 不了解的可以看这一次,彻底弄懂 JavaScript 执行机制(别还不知道什么是宏任务,什么是微任务).
三 么是执行栈
当执行一个方法时,Javascript会生成一个与这个方法对应的执行环境,又叫执行上下文。这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数、私有作用域中定义的变量以及this对象。这个执行环境会被添加到一个栈中,这个栈就是执行栈。
如果在这个方法的代码中执行到了一行函数调用语句,那么Javascript会生成这个函数的执行环境并将其添加到执行栈中,然后进入这个执行环境继续执行其中的代码。执行完毕并返回结果后,Javascript会退出执行环境并把这个执行环境从栈中销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。这个执行环境的栈就是执行栈。
下次DOM更新周期的意思其实是下次微任务执行时更新DOM。而vm.$nextTick
其实是将回调添加到微任务中。只有在特殊情况下才会降级成宏任务,默认会添加到微任务中。
因此,如果使用vm.$nextTick
来获取更新后的DOM,则需要注意顺序的问题。因为不论是更新DOM的回调还是使用vm.$nextTick
注册的回调,都说向微任务队列中添加任务,所以哪个任务先添加到队列中,就先执行哪个任务。
事实上,更新DOM的回调也是使用vm.$nextTick
来注册到微任务中的。这个我们在上面也强调过
如果想要在vm.$nextTick
中获取更新后的DOM,则一定要在更改数据的后面使用vm.$nextTick
注册回调。
new Vue({
methods:{
example:function(){
this.message = ‘changed’;
this.$nextTick(function(){
})
}
}
})
如果先使用vm.$nextTick
注册回调,然后修改数据,则在微任务对垒中先执行使用vm.$nextTick
注册的回调,然后执行更新DOM的回调。所以在回调中得不到最新的DOM,因为此时DOM还没有更新。
在事件循环中,必须当微任务队列中的事件都执行完之后,才会从宏任务队列中取出一个事件执行下一轮,所以添加到微任务队列中的任务的执行时机优先于向宏任务队列中添加的任务。
new Vue({
methods:{
example:function(){
setTimeout(_=>{
},0)
this.message = ‘changed’;
}
})
setTimeout属于宏任务,使用它注册的回调会加入到宏任务中。宏任务的执行要比微任务晚,所以即便是先注册,也是先更新DOM后执行setTimeout中设置的回调
4. vm.$nextTick原理
vm.$nextTick
和全局Vue.nextTick是相同的,所以nextTick的具体实现并不是在Vue原型上的$nextTick方法中,而是抽象成了nextTick方法供两个方法共用。
import { nextTick } from ‘…/util/index’
Vue.prototype.$nextTick = function(fn){
return nextTick(fn,this);
}
Vue原型上的$nextTick
方法只是调用了nextTick方法,具体实现其实在nextTick中。
由于vm.$nextTick
会将回调添加到任务队列中延迟执行,所以在回调执行前,如果反复调用vm.$nextTick
,Vue.js并不会反复将回调添加到任务队列中,只会向任务队列中添加一个任务。(这个我们在上面说过了)
Vue.js内部有一个列表用来存储vm.$nextTick
参数中提供的回调。在一轮事件循环中,vm.$nextTick
只会向任务队列添加一个任务,多次使用vm.$nextTick
只会将回调添加到回调列表中缓存起来。当任务触发时,依次执行列表中的所有回调并清空列表。
(5)nextTick方法的实现方式
const callbacks = [];
let pending = false;
const callbacks = [];
let pending = false;
function flushCallbacks(){
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for(let i = 0;i<copies.length;i++){
copiesi;
}
}
let microTimerFunc;
const p = Promise.resolve();
microTimerFunc = () =>{
p.then(flushCallbacks)
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx);
}
})
if(!pending){
pending = true;
microTimerFunc();
}
}
nextTick(function(){
console.log(this.name);//Berwin
},{name:‘Berwin’})
解释上面代码
1、通过数组callbacks来存储用户注册的回调。
2、声明了变量pending来标记是否已经向队列中添加一个任务了。每当向任务队列中插入任务时,将pending设置为true,每当任务被执行时将pending设置为false,这样就可以通过pending的值来判断是否需要向任务队列中添加任务。
3、函数flushCallbacks,即被注册的那个任务。当这个函数被触发时,会将callbacks中的所有函数依次执行,然后清空callbacks,并将pending设置为false。即一轮事件循环中,flushCallbacks只会执行一次。
4、microTimerFunc函数,它的作用是使用Promise.then将flushCallbacks添加到微任务队列中。这个其实就是我们所说的包装成异步。
5、执行nextTick函数注册回调时,首先将回调函数添加到callbacks中,然后使用pending判断是否需要向任务队列中新增任务。
在Vue.js2.4版本之前,nextTick方法在任何地方都使用微任务,但是微任务的优先级太高,在某些场景下可能会出现问题。所以Vue.js提供了在特殊场合下可以强制使用宏任务的方法。
const callbacks = [];
let pending = false;
function flushCallbacks(){
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
for(let i = 0;i<copies.length;i++){
copiesi;
}
}
let microTimerFunc;
let macroTimerFunc = function(){…}
let userMacroTask = false;
const p = Promise.resolve();
microTimerFunc = () =>{
p.then(flushCallbacks)
}
export function withMacroTask(fn){
return fn._withTask || (fn_withTask = function({
userMacroTask = true;
const res = fn.apply(null,arguments);
userMacroTask = false;
retrun res;
}))
}
export function nextTick(cb,ctx){
callbacks.push(()=>{
if(cb){
cb.call(ctx);
}
})
if(!pending){
pending = true;
if(userMacroTask){
macroTimerFunc();
}else{
microTimerFunc();
}
}
前端资料汇总
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
我一直觉得技术面试不是考试,考前背背题,发给你一张考卷,答完交卷等通知。
首先,技术面试是一个 认识自己 的过程,知道自己和外面世界的差距。
更重要的是,技术面试是一个双向了解的过程,要让对方发现你的闪光点,同时也要 试图去找到对方的闪光点,因为他以后可能就是你的同事或者领导,所以,面试官问你有什么问题的时候,不要说没有了,要去试图了解他的工作内容、了解这个团队的氛围。
找工作无非就是看三点:和什么人、做什么事、给多少钱,要给这三者在自己的心里划分一个比例。
最后,祝愿大家在这并不友好的环境下都能找到自己心仪的归宿。