javascript前端知识体系(2)vue篇个人文章

首先你得说说相同点,两个都是MVVM框架,数据驱动视图,无争议。如果说不同,那可能分为以下这么几点:

MVVM的核心是数据驱动即ViewModel,ViewModel是View和Model的关系映射。

MVVM本质就是基于操作数据来操作视图进而操作DOM,借助于MVVM无需直接操作DOM,开发者只需编写ViewModel中有业务,使得View完全实现自动化。

SPA(single-pageapplication)即一个web项目就只有一个页面(即一个HTML文件,HTML内容的变换是利用路由机制实现的。

仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现HTML内容的变换,UI与用户的交互,避免页面的重新加载。

优点:

缺点:

前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;

SEO难度较大:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势。

注意:浏览器有8个钩子,但是node中做服务端渲染的时候只有beforeCreate和created

加载渲染过程父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程父beforeUpdate->父updated

销毁过程父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

它的生命周期中有多个事件钩子,让我们控制Vue实例过程更加清晰。

第一次页面加载时会触发beforeCreate,created,beforeMount,mounted这几个钩子

v-if

v-show

不管初始条件是什么,元素总是会被渲染,并且只是简单地基于CSS的“display”属性进行切换。

所以:

背景:

所有的prop都使得其父子prop之间形成了一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向变的混乱。

每次父级组件发生更新时,子组件中所有的prop都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变prop。如果你这样做了,Vue会在浏览器的控制台中发出警告。子组件想修改时,只能通过$emit派发一个自定义事件,父组件接收到后,由父组件修改。

有两种常见的试图改变一个prop的情形:

在第2情况下,最好定义一个本地的data属性并将这个prop用作其初始值:

props:['initialCounter'],data:function(){return{counter:this.initialCounter//定义本地的data属性接收prop初始值}}这个prop以一种原始的值传入且需要进行转换。

在这种情况下,最好使用这个prop的值来定义一个计算属性

props:['size'],computed:{normalizedSize:function(){returnthis.size.trim().toLowerCase()}}6.异步请求适合在哪个生命周期调用?官方实例的异步请求是在mounted生命周期中调用的,而实际上也可以在created生命周期中调用。

本人推荐在created钩子函数中调用异步请求,有以下优点:

1.父子props,on

//子组件

//父组件

//A子组件

exportdefault{data(){return{title:'a组件'}},methods:{sayHello(){alert('Hello');}}}//父组件

3.attrs,listeners

listeners::包含了父作用域中的(不含.native修饰器的)v-on事件监听器。它可以通过v-on="$listeners"传入内部组件

//index.vue

//childCom1.vue

//childCom3.vue

4.Provide、inject的使用:

父组件

子组件

8.什么是SSRSSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端。服务端渲染SSR的优缺点如下:

(1)服务端渲染的优点:

(2)服务端渲染的缺点:

vue-router有3种路由模式:hash、history、abstract,对应的源码如下所示:

switch(mode){case'history':this.history=newHTML5History(this,options.base)breakcase'hash':this.history=newHashHistory(this,options.base,this.fallback)breakcase'abstract':this.history=newAbstractHistory(this,options.base)breakdefault:if(process.env.NODE_ENV!=='production'){assert(false,`invalidmode:${mode}`)}}路由模式的说明如下:

(1)hash模式的实现原理

早期的前端路由的实现就是基于location.hash来实现的。其实现原理很简单,location.hash的值就是URL中#后面的内容。

比如下面这个网站,它的location.hash的值为'#search':

(2)history模式的实现原理

HTML5提供了HistoryAPI来实现URL的变化,其中做最主要的API有以下两个:

这两个API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:

window.history.pushState(null,null,path);window.history.replaceState(null,null,path);history路由模式的实现主要基于存在下面几个特性:

key是为Vue中vnode的唯一标记,通过这个key,我们的diff操作可以更准确、更快速。

Vue的diff过程可以概括为:

oldCh和newCh各有两个头尾的变量oldStartIndex、oldEndIndex和newStartIndex、newEndIndex,它们会新节点和旧节点会进行两两对比,即一共有4种比较方式:newStartIndex和oldStartIndex、newEndIndex和oldEndIndex、newStartIndex和oldEndIndex、newEndIndex和oldStartIndex,如果以上4种比较都没匹配,如果设置了key,就会用key再进行比较,在比较的过程中,遍历会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较。

所以Vue中key的作用是:key是为Vue中vnode的唯一标记,通过这个key,我们的diff操作可以更准确、更快速

更快速:利用key的唯一性生成map对象来获取对应节点,比遍历方式更快,源码如下:

虚拟DOM的实现原理主要包括以下3部分:

缺点:

Proxy的优势如下:

Object.defineProperty的优势如下:

Proxy是ES6中新增的功能,它可以用来自定义对象中的操作。

letp=newProxy(target,handler)target代表需要添加代理的对象handler用来自定义对象中的操作,比如可以用来自定义set或者get函数。下面来通过Proxy来实现一个数据响应式:

letonWatch=(obj,setBind,getLogger)=>{lethandler={get(target,property,receiver){getLogger(target,property)returnReflect.get(target,property,receiver)},set(target,property,value,receiver){setBind(value,property)returnReflect.set(target,property,value)}}returnnewProxy(obj,handler)}letobj={a:1}letp=onWatch(obj,(v,property)=>{console.log(`监听到属性${property}改变为${v}`)},(target,property)=>{console.log(`'${property}'=${target[property]}`)})p.a=2//监听到属性a改变为2p.a//'a'=2在上述代码中,通过自定义set和get函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个Vue中的响应式,需要在get中收集依赖,在set派发更新,之所以Vue3.0要使用Proxy替换原本的API原因在于Proxy无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是Proxy可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

Vue框架是通过遍历数组和递归遍历对象,从而达到利用Object.defineProperty()也能对对象和数组(部分方法的操作)进行监听。

vue2:

数组就是使用object.defineProperty重新定义数组的每一项,能引起数组变化的方法为pop、push、shift、unshift、splice、sort、reverse这七种,只要这些方法执行改了数组内容,就更新内容

vue3:

改用proxy,可直接监听对象数组的变化。

Vue数据双向绑定主要是指:数据变化更新视图,视图变化更新数据

输入框内容变化时,Data中的数据同步变化。即View=>Data的变化。Data中的数据变化时,文本节点的内容同步变化。即Data=>View的变化。

其中,View变化更新Data,可以通过事件监听的方式来实现,所以Vue的数据双向绑定的工作主要是如何根据Data变化更新View。

Vue主要通过以下4个步骤来实现数据双向绑定的:

v-model指令在表单input、textarea、select等元素上创建双向数据绑定,v-model本质上是语法糖,会在内部为不同的输入元素使用不同的属性并抛出不同的事件:

以input表单元素为例:

相当于

18.组件中data为什么是一个函数?为什么组件中的data必须是一个函数,然后return一个对象,而newVue实例里,data可以直接是一个对象?

//data

data(){return{message:"子组件",childName:this.name}}//newVue

newVue({el:'#app',router,template:'',components:{App}})一个组件被复用多次的话,也就会创建多个实例,本质上,这些实例用的都是同一个构造函数。

如果data是对象的话,对象属于引用类型,会影响到所有的实例,所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

而newVue的实例,是不会被复用的,因此不存在引用对象的问题。

keep-alive是Vue内置的一个组件,可以使被包含的组件保留状态,避免重新渲染,其有以下特性:

keep-alive的生命周期

比如有父组件Parent和子组件Child,如果父组件监听到子组件挂载mounted就做一些逻辑处理,可以通过以下写法实现:

//Parent.vue

//Child.vue

mounted(){this.$emit("mounted");}以上需要手动通过$emit触发父组件的事件,更简单的方式可以在父组件引用子组件时通过@hook来监听即可,如下所示:

doSomething(){console.log('父组件监听到mounted钩子函数...');},//Child.vue

mounted(){console.log('子组件触发mounted钩子函数...');},//以上输出顺序为://子组件触发mounted钩子函数...//父组件监听到mounted钩子函数...当然@hook方法不仅仅是可以监听mounted,其它的生命周期事件,例如:created,updated等都可以监听。

由于JavaScript的限制,Vue不能检测到以下数组的变动:

为了解决第一个问题,Vue提供了以下操作方法:

//Vue.set

Vue.set(vm.items,indexOfItem,newValue)//vm.$set(Vue.set的一个别名)

vm.$set(vm.items,indexOfItem,newValue)//Array.prototype.splice

vm.items.splice(indexOfItem,1,newValue)为了解决第二个问题,Vue提供了以下操作方法:

//Array.prototype.splice

vm.items.splice(newLength)22.vue2.x中如何监测数组变化使用了函数劫持的方式,重写了数组的方法,Vue将data中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。

简单来说,diff算法有以下过程

同级比较,再比较子节点,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)

比较都有子节点的情况(核心diff)递归比较子节点

Vue3.x借鉴了ivi算法和inferno算法在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。该算法中还运用了动态规划的思想求解最长递归子序列。

简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:

首先解析模版,生成AST语法树(一种用JavaScript对象的形式来描述整个模板)。

编译的最后一步是将优化后的AST树转换为可执行的代码。

computed:

watch:

小结:

在下次DOM更新循环结束之后执行延迟回调。在这里面的代码会等到dom更新以后再执行。

具体的过程:

Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。

Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理,这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

不能同名因为不管是计算属性还是data还是props都会被挂载在vm实例上,因此这三个都不能同名

找到config/index.js配置文件,找build打包对象里的assetsPublicPath属性默认值为/,更改为./就好了

因为动态添加src被当做静态资源处理了,没有进行编译,所以要加上require。

30.使用vue渲染大量数据时应该怎么优化?说下你的思路!Object.freeze适合一些bigdata的业务场景。尤其是做管理后台的时候,经常会有一些超大数据量的table,或者一个含有n多数据的图表,这种数据量很大的东西使用起来最明显的感受就是卡。但其实很多时候其实这些数据其实并不需要响应式变化,这时候你就可以使用Object.freeze方法了,它可以冻结一个对象(注意它不并是vue特有的api)。

当你把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter,它们让Vue能进行追踪依赖,在属性被访问和修改时通知变化。

使用了Object.freeze之后,不仅可以减少observer的开销,还能减少不少内存开销。

使用方式:

this.item=Object.freeze(Object.assign({},this.item))30.vue自定义指令先了解一下,在vue中,有很多内置的指令.

比如:

所以,关于指令,我们可以总结下面几点:

Vue自定义指令案例1

例如:我们需要一个指令,写在某个HTML表单元素上,然后让它在被加载到DOM中时,自动获取焦点.

//和自定义过滤器一样,我们这里定义的是全局指令Vue.directive('focus',{inserted(el){el.focus()}})

先总结几个点:

指令的生命周期

用指令我们需要:

当一个指令绑定到一个元素上时,其实指令的内部会有五个生命周期事件函数.

Vue.directive('gqs',{bind(){//当指令绑定到HTML元素上时触发.**只调用一次**console.log('bindtriggerd')},inserted(){//当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是`div#app`)**.但不保证,父元素已经插入了DOM文档.**console.log('insertedtriggerd')},updated(){//所在组件的`VNode`更新时调用.console.log('updatedtriggerd')},componentUpdated(){//指令所在组件的VNode及其子VNode全部更新后调用。console.log('componentUpdatedtriggerd')},unbind(){//只调用一次,指令与元素解绑时调用.console.log('unbindtriggerd')}})HTML

那么剩下的三个什么时候触发呢

unbind触发.gif

v-show设置元素的display:block|none,会触发componentUpdated事件

toggle-v-show一个把元素从DOM删除触发unbind().--->仅仅是删除.一个显示设置元素的隐藏和显示的时候触发componentUpdated()--->block|none都触发.31.vue实例挂载的过程是什么32.组件和插件有什么区别组件(Component)是用来构成你的App的业务模块,它的目标是App.vue。插件(Plugin)是用来增强你的技术栈的功能模块,它的目标是Vue本身。33.使用vue过程中可能会遇到的问题(坑)有哪些34.动态给vue的data添加一个新的属性时会发生什么根据官方文档定义:

如果在实例创建之后添加新的属性到实例上,它不会触发视图更新。

Vue不允许在已经创建的实例上动态添加新的根级响应式属性(root-levelreactiveproperty)。

然而它可以使用Vue.set(object,key,value)方法将响应属性添加到嵌套的对象上。

一个混入对象可以包含任意组件选项。同一个生命周期,混入对象会比组件的先执行。

//暴露两个mixins对象

exportconstmixinsTest1={methods:{hello1(){console.log("hello1");}},created(){this.hello1();},}exportconstmixinsTest2={methods:{hello2(){console.log("hello2");}},created(){this.hello2();},}hello2hello1121237.vue的核心是什么数据驱动专注于View层。它让开发者省去了操作DOM的过程,只需要改变数据。组件响应原理数据(model)改变驱动视图(view)自动更新组件化扩展HTML元素,封装可重用的代码。38.vue常用的修饰符有哪些v-model:.trim.numberv-on:.stop.prevent

40.template编译的理解41.axios是什么,如何中断axios的请求42.如何引入scss?安装scss依赖包:

npminstallsass-loader--save-devnpminstallnode-sass--save-dev在build文件夹下修改webpack.base.conf.js文件,在module下的rules里添加配置,如下:

{test:/\.scss$/,loaders:['style','css','sass']}应用:

created():在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(dataobserver),property和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$elproperty目前尚不可用。

activated():是在路由设置时,才会有这个生命周期。在被keep-alive缓存的组件激活时调用。

因为在插入数据或者删除数据的时候,会导致后面的数据的key绑定的index变化,进而导致从新渲染,效率会降低

动态组件使用方法

使用标签保存状态,即切换组件再次回来依然是原来的样子,页面不会刷新,若不需要可以去掉。通过事件改变is绑定的isWhich值即可切换成不同的组件,isWhich的值为组件名称。47.vue中怎么重置data使用场景:

比如,有一个表单,表单提交成功后,希望组件恢复到初始状态,重置data数据。

使用Object.assign(),vm.$data可以获取当前状态下的data,vm.$options.data可以获取到组件初始化状态下的data

初始状态下设置data数据的默认值,重置时直接bject.assign(this.$data,this.$options.data())

说明:

...49.style加scoped属性的用途和原理用途:防止全局同名CSS污染原理:在标签加上v-data-something属性,再在选择器时加上对应[v-data-something],即CSS属性选择器,以此完成类似作用域的选择方式50.在vue项目中如何配置favicon将favicon图片放到static文件夹下然后在index.html中添加:

举例来说,ES6在Array对象上新增了Array.from方法。Babel就不会转码这个方法。如果想让这个方法运行,必须使用babel-polyfill,为当前环境提供一个垫片。

强制重新渲染

this.$forceUpdate()强制重新刷新某组件

//模版上绑定key//选项里绑定datadata(){return{theKey:0}}//刷新key达到刷新组件的目的

theKey++;54.vue给组件绑定自定义事件无效怎么解决加入.native修饰符

报错"Method'xxx'hasalreadybeendefinedasadataproperty"

键名优先级:props>data>methods

实例创建之后,可以通过vm.$data访问原始数据对象。Vue实例也代理了data对象上所有的属性,因此访问vm.a等价于访问vm.$data.a。

以_或$开头的属性不会被Vue实例代理,因为它们可能和Vue内置的属性、API方法冲突。可以使用vm.$data._property的方式访问这些属性。

v-model默认的触发条件是input事件,加了.lazy修饰符之后,v-model会在change事件触发的时候去监听

diff算法要求,源码中patch.js中的patchVnode也是根据树状结构进行遍历

生命周期的钩子函数不能使用箭头函数,否者this不能指向vue实例

包裹嵌套其它元素,使元素具有区域性,自身具有三个特点:

解析和转换.vue文件,提取出其中的逻辑代码script、样式代码style、以及HTML模版template,再分别把它们交给对应的Loader去处理。

设置path:'*',并且放在最后一个

为什么要响应参数变化?

解决方案:

使用watch监听

watch:{$route(to,from){if(to!=from){console.log("监听到路由变化,做出相应的处理");}}}向router-view组件中添加key

$route.fullPath是完成后解析的URL,包含其查询参数信息和hash完整路径

在路由实例中配置scrollBehavior(ro,form,savedPosition){//滚动到顶部return{x:0,y:0}//保持原先的滚动位置return{selector:falsy}}74.路由懒加载75.MVVM全称:Model-View-ViewModel,Model表示数据模型层,view表示视图层,ViewModel是View和Model层的桥梁,数据绑定到viewModel层并自动渲染到页面中,视图变化通知viewModel层更新数据。

事件绑定有几种?

解释下这2种的区别:

Vue在更新DOM时是异步执行的,只要侦听到数据变化,将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更,如果同一个watcher被多次触发,只会被推入到队列中一次,这种在缓冲时去除重复数据对于减少不必要的计算和DOM操作是非常重要的.

然后,在下一个的事件循环tick中,Vue刷新队列并执行实际(已去重的)工作,Vue在内部对异步队列尝试使用原生的Promise.then、MutationObserver和setImmediate,如果执行环境不支持,则会采用setTimeout(fn,0)代替。

描述

对于Vue为何采用异步渲染,简单来说就是为了提升性能,因为不采用异步更新,在每次更新数据都会对当前组件进行重新渲染,为了性能考虑,Vue会在本轮数据更新后,再去异步更新视图,举个例子,让我们在一个方法内重复更新一个值。

this.msg=1;this.msg=2;this.msg=3;事实上,我们真正想要的其实只是最后一次更新而已,也就是说前三次DOM更新都是可以省略的,我们只需要等所有状态都修改好了之后再进行渲染就可以减少一些性能损耗。

假设这里是同步更新队列,this.msg=1,大致会发生这些事:

msg值更新->触发setter->触发Watcher的update->重新调用render->生成新的vdom->dom-diff->dom更新这里的dom更新并不是渲染(即布局、绘制、合成等一系列步骤),而是更新内存中的DOM树结构,之后再运行this.msg=2,再重复上述步骤,之后的第3次更新同样会触发相同的流程,等开始渲染的时候,最新的DOM树中确实只会存在更新完成3,从这里来看,前2次对msg的操作以及Vue内部对它的处理都是无用的操作,可以进行优化处理。

如果是异步更新队列,会是下面的情况:

运行this.msg=1,并不是立即进行上面的流程,而是将对msg有依赖的Watcher都保存在队列中,该队列可能这样[Watcher1,Watcher2...],当运行this.msg=2后,同样是将对msg有依赖的Watcher保存到队列中,Vue内部会做去重判断,这次操作后,可以认为队列数据没有发生变化,第3次更新也是上面的过程。

当然,你不可能只对msg有操作,你可能对该组件中的另一个属性也有操作,比如this.otherMsg=othermessage,同样会把对otherMsg有依赖的Watcher添加到异步更新队列中,因为有重复判断操作,这个Watcher也只会在队列中存在一次,本次异步任务执行结束后,会进入下一个任务执行流程,其实就是遍历异步更新队列中的每一个Watcher,触发其update,然后进行重新调用render->newvdom->dom-diff->dom更新等流程,但是这种方式和同步更新队列相比,不管操作多少次msg,Vue在内部只会进行一次重新调用真实更新流程。

此外,组件内部实际使用VirtualDOM进行渲染,也就是说,组件内部其实是不关心哪个状态发生了变化,它只需要计算一次就可以得知哪些节点需要更新,也就是说,如果更改了N个状态,其实只需要发送一个信号就可以将DOM更新到最新,如果我们更新多个值。

this.msg=1;this.age=2;this.name=3;此处我们分三次修改了三种状态,但其实Vue只会渲染一次,因为VIrtualDOM只需要一次就可以将整个组件的DOM更新到最新,它根本不会关心这个更新的信号到底是从哪个具体的状态发出来的。

而为了达到这个目的,我们需要将渲染操作推迟到所有的状态都修改完成,为了做到这一点只需要将渲染操作推迟到本轮事件循环的最后或者下一轮事件循环,也就是说,只需要在本轮事件循环的最后,等前面更新状态的语句都执行完之后,执行一次渲染操作,它就可以无视前面各种更新状态的语法,无论前面写了多少条更新状态的语句,只在最后渲染一次就可以了。

将渲染推迟到本轮事件循环的最后执行渲染的时机会比推迟到下一轮快很多,所以Vue优先将渲染操作推迟到本轮事件循环的最后,如果执行环境不支持会降级到下一轮,Vue的变化侦测机制(setter)决定了它必然会在每次状态发生变化时都会发出渲染的信号,但Vue会在收到信号之后检查队列中是否已经存在这个任务,保证队列中不会有重复,如果队列中不存在则将渲染操作添加到队列中,之后通过异步的方式延迟执行队列中的所有渲染的操作并清空队列,当同一轮事件循环中反复修改状态时,并不会反复向队列中添加相同的渲染操作,所以我们在使用Vue时,修改状态后更新DOM都是异步的。

当数据变化后会调用notify方法,将watcher遍历,调用update方法通知watcher进行更新,这时候watcher并不会立即去执行,在update中会调用queueWatcher方法将watcher放到了一个队列里,在queueWatcher会根据watcher的进行去重,若多个属性依赖一个watcher,则如果队列中没有该watcher就会将该watcher添加到队列中,然后便会在$nextTick方法的执行队列中加入一个flushSchedulerQueue方法(这个方法将会触发在缓冲队列的所有回调的执行),然后将$nextTick方法的回调加入$nextTick方法中维护的执行队列,flushSchedulerQueue中开始会触发一个before的方法,其实就是beforeUpdate,然后watcher.run()才开始真正执行watcher,执行完页面就渲染完成,更新完成后会调用updated钩子。

$nextTick

在上文中谈到了对于Vue为何采用异步渲染,假如此时我们有一个需求,需要在页面渲染完成后取得页面的DOM元素,而由于渲染是异步的,我们不能直接在定义的方法中同步取得这个值的,于是就有了vm.$nextTick方法,Vue中$nextTick方法将回调延迟到下次DOM更新循环之后执行,也就是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,能够获取更新后的DOM。简单来说就是当数据更新时,在DOM中渲染完成后,执行回调函数。

通过一个简单的例子来演示$nextTick方法的作用,首先需要知道Vue在更新DOM时是异步执行的,也就是说在更新数据时其不会阻塞代码的执行,直到执行栈中代码执行结束之后,才开始执行异步任务队列的代码,所以在数据更新时,组件不会立即渲染,此时在获取到DOM结构后取得的值依然是旧的值,而在$nextTick方法中设定的回调函数会在组件渲染完成之后执行,取得DOM结构后取得的值便是新的值。

Js是单线程的,其引入了同步阻塞与异步非阻塞的执行模式,在Js异步模式中维护了一个EventLoop,EventLoop是一个执行模型,在不同的地方有不同的实现,浏览器和NodeJS基于不同的技术实现了各自的EventLoop。浏览器的EventLoop是在HTML5的规范中明确定义,NodeJS的EventLoop是基于libuv实现的。

在浏览器中的EventLoop由执行栈ExecutionStack、后台线程BackgroundThreads、宏队列MacrotaskQueue、微队列MicrotaskQueue组成。

当Js执行时,进行如下流程:

实例#

//Step1console.log(1);//Step2setTimeout(()=>{console.log(2);Promise.resolve().then(()=>{console.log(3);});},0);//Step3newPromise((resolve,reject)=>{console.log(4);resolve();}).then(()=>{console.log(5);})//Step4setTimeout(()=>{console.log(6);},0);//Step5console.log(7);//StepN//...//Result/*1475236*/分析#

在了解异步任务的执行队列后,回到中$nextTick方法,当用户数据更新时,Vue将会维护一个缓冲队列,对于所有的更新数据将要进行的组件渲染与DOM操作进行一定的策略处理后加入缓冲队列,然后便会在$nextTick方法的执行队列中加入一个flushSchedulerQueue方法(这个方法将会触发在缓冲队列的所有回调的执行),然后将$nextTick方法的回调加入$nextTick方法中维护的执行队列,在异步挂载的执行队列触发时就会首先会首先执行flushSchedulerQueue方法来处理DOM渲染的任务,然后再去执行$nextTick方法构建的任务,这样就可以实现在$nextTick方法中取得已渲染完成的DOM结构。

在测试的过程中发现了一个很有意思的现象,在上述例子中的加入两个按钮,在点击updateMsg按钮的结果是321,点击updateMsgTest按钮的运行结果是231。

首先对有数据更新的updateMsg按钮触发的方法进行debug,断点设置在Vue.js的715行,版本为2.4.2,在查看调用栈以及传入的参数时可以观察到第一次执行$nextTick方法的其实是由于数据更新而调用的nextTick(flushSchedulerQueue)语句,也就是说在执行this.msg="Update";的时候就已经触发了第一次的$nextTick方法,此时在$nextTick方法中的任务队列会首先将flushSchedulerQueue方法加入队列并挂载$nextTick方法的执行队列到Promise对象上,然后才是自行自定义的Promise.resolve().then(()=>console.log(2))语句的挂载,当执行微任务队列中的任务时,首先会执行第一个挂载到Promise的任务,此时这个任务是运行执行队列,这个队列中有两个方法,首先会运行flushSchedulerQueue方法去触发组件的DOM渲染操作,然后再执行console.log(3),然后执行第二个微队列的任务也就是()=>console.log(2),此时微任务队列清空,然后再去宏任务队列执行console.log(1)。

接下来对于没有数据更新的updateMsgTest按钮触发的方法进行debug,断点设置在同样的位置,此时没有数据更新,那么第一次触发$nextTick方法的是自行定义的回调函数,那么此时$nextTick方法的执行队列才会被挂载到Promise对象上,很显然在此之前自行定义的输出2的Promise回调已经被挂载,那么对于这个按钮绑定的方法的执行流程便是首先执行console.log(2),然后执行$nextTick方法闭包的执行队列,此时执行队列中只有一个回调函数console.log(3),此时微任务队列清空,然后再去宏任务队列执行console.log(1)。

简单来说就是谁先挂载Promise对象的问题,在调用$nextTick方法时就会将其闭包内部维护的执行队列挂载到Promise对象,在数据更新时Vue内部首先就会执行$nextTick方法,之后便将执行队列挂载到了Promise对象上,其实在明白Js的EventLoop模型后,将数据更新也看做一个$nextTick方法的调用,并且明白$nextTick方法会一次性执行所有推入的回调,就可以明白其执行顺序的问题了,下面是一个关于$nextTick方法的最小化的DEMO。

THE END
1.前端和后端的区别前端和后端有什么区别常见问题前端:代码在用户的浏览器中执行,主要在客户端运行。 后端:代码在服务器端执行,通常在服务器上部署和运行。 总的来说,前端主要关注用户界面和用户交互,而后端则负责处理数据和业务逻辑,二者相互配合构建完整的Web应用程序。 前端入门到VUE实战笔记:立即学习 ...https://m.php.cn/faq/713299.html
2.前端常见面试题vue优点:一次加载后,后面都是本地浏览器缓存,有点像客户端软件,交互快。 通过异步请求方式交互数据,有效的前后端分离,后端只关心接口处理, 减轻服务端的压力,展示和渲染都在前端浏览器完成。 后端的接口可以复用,通过json格式,可以在pc移动端都通用 缺点:首屏加载慢,很多资源都要第一次一次性加载完,容易白屏 ...https://www.jianshu.com/p/8522cfd5aee7
3.im由于历史原因,现在主流的 http 协议是无状态协议(HTTP2 暂时应用不广泛),一般情况是由客户端主动发起请求,然后服务端去响应。那么为了实现服务端向客户端推送信息,就需要前端主动向后端去轮询,这种方式低效且容易出错,在之前我们的管理端首页确实是这么做的(5s 一次)。 https://gitee.com/colala/im-server/
4.前端,后台,后端,前台他们区别是什么?小辣椒樱桃前台和后台都是在客户端或者浏览器上浏览者浏览的界面和管理者管理的界面 3.前端 这个是编程时候的概念,基本包括所有可见部分的代码编写,如果三层架构的话,可以看做是UI层. 4.后端 这个是对应前端而言的,编写的代码基本上都是提供给前端调用,而不需要处理UI的内容.例如逻辑层,或者存储过程. ...https://www.cnblogs.com/aaaazzzz/p/13023372.html
1.服务端和客户端以及前后端相关概念区分服务端和客户端到底是什么服务端和客户端以及前端和后端是两组相关但不完全相同的概念。 一、服务端(Server-side)和客户端(Client-side) 服务端和客户端是指在分布式系统或网络应用中相对的两个部分。是指在计算机网络中不同角色的两个主要实体。 服务端:服务端是指提供服务的计算机或服务器。它接受来自客户端的请求,并根据请求提供相应的...https://blog.csdn.net/m0_62006803/article/details/136151175
2.客户端服务端前台前端后端后台区别在哪?今天来聊一聊网站开发的基础知识:客户端、服务端、前端、前台、后端、后台之前有什么区别和联系。 首先是客户端、客户端也常被叫做用户端、顾名思义、就是用户使用的终端、例如你电脑上的QQ、微信、钉钉等软件、当然手机上也是一样、这些都是客户端(Client)。与之相对应的就是服务端了、服务端是为客户端提供数据...https://blog.51cto.com/u_11541173/5763222
3....也是奔波于各种面试。我自己总结了很多方向的前端面试题,服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。 状态码304不应该认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。 https://juejin.cn/post/7149438206419664927
4.关于前端和后端:说一说前端路由与后端路由的区别后端路由与服务端渲染 前面说了,「刀耕火种」的年代里,网页通常是通过后端路由直出给客户端浏览器的。也就是网页的html一般是在后端服务器里通过模板引擎渲染好再交给前端的。至于一些其他的效果,是通过预先写在页面里的jQuery、Bootstrap等常见的前端框架去负责的。 https://maimai.cn/article/detail?fid=1749517863&efid=RknOuyXMMmiLjUJ7SWEFTg
5.三明市第一医院生态新城院区智慧医院智能化项目院区楼宇智能化...10、支持安全组服务,可以对进出虚拟机端口的网络报文进行安全过滤规则设置。虚拟机端口与安全组关联后,安全组规则可对进出虚拟机端口的网络报文进行过滤,只有规则允许的报文可通过。 11、支持对访问负载均衡的客户端IP进行白名单安全控制,如果使用访问控制能力,则只有被允许的IP能通过ELB访问后端云服务器/物理机;如果不...http://zfcg.cz.sm.gov.cn/upload/document/20221024/df8e2d7d2af449fbbe8f17d25733ff6d.html
6.全面讨论后端前端客户端的区别全面讨论 后端、前端、客户端的区别 帖子背景 楼主看到今年不少友友暑期实习都或多或少,被客户端岗位打捞起来面试;也有很多友友本来是投的后端,结果拿了客户端的offer,不知道改不改转客户端。 楼主之前在字节的CapCut做过半年的客户端开发实习生,对客户端有一个基本的了解,再加上后端楼主也实习过,所以两个方向...https://m.nowcoder.com/discuss/616306212254015488
7.后端测试和前端测试的区别后端测试和前端测试的区别 后端测试是对服务器端应用程序进行测试,例如 API 接口、数据库操作等。后端测试需要掌握服务器端编程语言(如 Java、Python、PHP 等)、数据库等相关技术,主要关注服务器端的逻辑、性能和安全性。 前端测试则是测试应用程序的客户端部分,即用户界面、页面布局、交互设计、功能实现等,需要掌握...https://www.hxsd.com/content/31353/
8.服务端开发和前端有什么区别前端 职位描述 1. 负责产品服务端模块的设计和开发 2. 负责产品服务端框架设计和开发 3. 负责服务器相关工具开发和维护 4. 负责深入优化服务端性能,保证系统服务端的质量和性能 5. 同策划及客户端人员进行有效沟通,分析、解决各种服务器的问题 职位要求 ...https://www.jobui.com/gangwei/pk/fuwuduankaifa-qianduan/
9.一口气说出前后端10种鉴权方案~腾讯云开发者社区Session-Cookie认证是利用服务端的Session(会话)和浏览器(客户端)的 Cookie 来实现的前后端通信认证模式。 在理解这句话之前我们先简单了解下什么是 Cookie以及什么是 Session? 2.1 什么是 Cookie 众所周知,HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息)...https://cloud.tencent.com/developer/article/2144184
10.服务端渲染(SSR)Vue.js然而,Vue 也支持将组件在服务端直接渲染成 HTML 字符串,作为服务端响应返回给浏览器,最后在浏览器端将静态的 HTML“激活”(hydrate) 为能够交互的客户端应用。 一个由服务端渲染的 Vue.js 应用也可以被认为是“同构的”(Isomorphic) 或“通用的”(Universal),因为应用的大部分代码同时运行在服务端和客户端。https://cn.vuejs.org/guide/scaling-up/ssr