Vue2 中为什么“数组名=[]”就能更新数组?

今天这篇文章给大家分享一些常见的前端vue面试题。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。对于前端来说,尽管css、html、js是主要的基础知识,但是随着技术的不断发展,出现了很多优秀的mv*框架以及小程序框架。因此,对于前端开发者而言,需要对一些前端框架进行熟练掌握。这篇文章我们一起来聊一聊VUE及全家桶的常见面试问题。

1、请讲述下VUE的MVVM的理解?

MVVM 是 Model-View-ViewModel的缩写,即将数据模型与数据表现层通过数据驱动进行分离,从而只需要关系数据模型的开发,而不需要考虑页面的表现,具体说来如下:Model代表数据模型:主要用于定义数据和操作的业务逻辑。View代表页面展示组件(即dom展现形式):负责将数据模型转化成UI 展现出来。ViewModel为model和view之间的桥梁:监听模型数据的改变和控制视图行为、处理用户交互。通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

2、VUE的生命周期及理解?

答:总共分为8个阶段,具体为:创建前/后,载入前/后,更新前/后,销毁前/后。创建前/后: 在beforeCreated阶段:ue实例的挂载元素$el和数据对象data都为undefined,还未初始化;在created阶段,vue实例的数据对象data有了,$el还没有。载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换;在mounted阶段,vue实例挂载完成,data.message成功渲染。更新前/后:当data变化时,会触发beforeUpdate和updated方法。销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。

beforeCreate:在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法create:data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的,不能直接操作页面的dom和获取dom对象mounted:执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行beforeUpdate: 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的,页面还没有和最新的数据保持同步updated:页面显示的数据和data中的数据已经保持同步了,都是最新的beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的data和 methods、指令、过滤器 ……都是处于可用状态。还没有真正被销毁destroyed: 这个时候上所有的data和methods、指令、过滤器 ……都是处于不可用状态。组件已经被销毁了。

共同点:都能控制元素的显示和隐藏;不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。

4、v-if和v-for同时使用在同一个标签上的表现?

当v-if与v-for一起使用时,v-for具有比v-if更高的优先级,这意味着v-if将分别重复运行于每个v-for循环中。

所以,不推荐v-if和v-for同时使用。如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去

需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。主要是为了高效的更新虚拟DOM。

  • v-enter:定义元素进入过渡的初始状态,在元素插入前生效,插入后一帧删除,
  • v-enter-active:在元素插入前生效,在动画完成后删除,
  • v-enter-to:在元素插入后一帧生效,在动画完成后删除,
  • v-leave:离开过渡的初始状态,在元素离开时生效,下一帧删除
  • v-leave-active:在离开过渡时生效,在动画完成后删除
  • v-leave-to:离开过渡结束状态,在离开过渡下一帧生效,在动画完成后删除

4)在同时使用过渡和css动画的时候 可以设置type属性来制定vue内部机制监听transitioned或者animationed事件来完成过渡还是动画的监听

5)如果需要设置对应的过渡时间,可以直接设置属性duration,可以直接接收一个数字(单位为毫秒),也可以接收一个对象{enter:1000,leave:300}

7、vue的自定义指令?

自定义指令分为全局指令和组件指令,其中全局指令需要使用directive来进行定义,组件指令需要使用directives来进行定义,具体定义方法同过滤器filter或者其他生命周期,具体使用方法如下:

全局自定义指令 directive(name,{}),其中name表示定义的指令名称(定义指令的时候不需要带v-,但是在调用的时候需要哦带v-),第二个参数是一个对象,对象中包括五个自定义组件的钩子函数,具体包括:

  1. bind函数:只调用一次,指令第一次绑定在元素上调用,即初始化调用一次,
  2. inserted函数:并绑定元素插入父级元素(即new vue中el绑定的元素)时调用(此时父级元素不一定转化为了dom)
  3. update函数:在元素发生更新时就会调用,可以通过比较新旧的值来进行逻辑处理
  4. unbind函数:在元素所在的模板删除的时候就触发一次

a、el指令所绑定的元素 可以直接操组dom元素

b、binding一个对象,具体包括以下属性:

  1. 1)name:定义的指令名称 不包括v-
  2. 2)value:指令的绑定值,如果绑定的是一个计算式,value为对应计算结果
  3. 4)expression:指令绑定的原始值 不对值进行任何加工
  4. 5)arg:传递给指令的参数

8、vue的实现原理?

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

第一步:需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter

这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:

  1. 1、在自身实例化时往属性订阅器(dep)里面添加自己
  2. 2、自身必须有一个update()方法
  3. 3、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。

1)diff算法的作用:用来修改dom的一小段,不会引起dom树的重绘

2)diff算法的实现原理:diff算法将virtual dom的某个节点数据改变后生成的新的vnode与旧节点进行比较,并替换为新的节点,具体过程就是调用patch方法,比较新旧节点,一边比较一边给真实的dom打补丁进行替换

a、在采用diff算法进行新旧节点进行比较的时候,比较是按照在同级进行比较的,不会进行跨级比较:

b、当数据发生改变的时候,set方法会调用dep.notify通知所有的订阅者watcher,订阅者会调用patch函数给响应的dom进行打补丁,从而更新真实的视图

c、patch函数接受两个参数,第一个是旧节点,第二个是新节点,首先判断两个节点是否值得比较,值得比较则执行patchVnode函数,不值得比较则直接将旧节点替换为新节点。如果两个节点一样就直接检查对应的子节点,如果子节点不一样就说明整个子节点全部改变不再往下对比直接进行新旧节点的整体替换

d、patchVnode函数:找到真实的dom元素;判断新旧节点是否指向同一个对象,如果是就直接返回;如果新旧节点都有文本节点,那么直接将新的文本节点赋值给dom元素并且更新旧的节点为新的节点;如果旧节点有子节点而新节点没有,则直接删除dom元素中的子节点;如果旧节点没有子节点,新节点有子节点,那么直接将新节点中的子节点更新到dom中;如果两者都有子节点,那么继续调用函数updateChildren

e、updateChildren函数:抽离出新旧节点的所有子节点,并且设置新旧节点的开始指针和结束指针,然后进行两辆比较,从而更新dom(调整顺序或者插入新的内容 结束后删掉多余的内容)

10、vue组件的通信(父子组件和非父子组件)?

传递参数可以使用props,传递函数可以直接在调用子组件的时候传递自定义事件,并使用$emit来调用,例如:

首先建立一个vue实例空白页(js文件)

组件a(数据发送方)通过使用 $emit 自定义事件把数据带过去

组件b(数据接收方)使用而通过 $on监听自定义事件的callback接收数据

11、vue的路由模式及区别?

特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。history 模式下,前端的 URL必须和实际向后端发起请求的URL一致,否则会报404错误

轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;
简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
双向数据绑定:保留了angular的特点,在数据操作方面更为简单;
组件化:保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;

视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
虚拟DOM:dom操作是非常耗费性能的, 不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;
运行速度更快:相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。

React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。

React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

vue路由钩子大致可以分为三类:

from:当前导航即将离开的路由
next:Function,进行管道中的一个钩子,如果执行完了,则导航的状态就是 confirmed (确认的);否则为false,终止导航。
afterEach函数不用传next()函数这类钩子主要作用于全局,一般用来判断权限,以及以及页面丢失时候需要执行的操作,例如:

//使用钩子函数对路由进行权限跳转
 // 如果是管理员权限则可进入,这里只是简单的模拟管理员权限而已
 // 简单的判断IE10及以下不进入富文本编辑器,该组件不兼容
 请使用更高版本的浏览器查看', '浏览器不兼容通知', {
 

2)单个路由里面的钩子

主要用于写某个指定路由跳转时需要执行的逻辑

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化,具体包括:

1)state:Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。

2)getter:state的计算属性,类似vue的计算属性,主要用来过滤一些数据。

3)action:actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。可以异步函数调用

5)modules:项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

1)全局过滤器必须写在vue实例创建之前

2)局部写法:在组件实例对象里挂载。

3)使用方式:只能使用在{{}}和:v-bind中,定义时第一个参数固定为预处理的数,后面的数为调用时传入的参数,调用时参数第一个对应定义时第二个参数,依次往后类推

//多个过滤器也可以串行使用

4)vue-cli项目中注册多个全局过滤器写法:

//1.创建一个单独的文件定义并暴露函数对象
//3.循环注册过滤器
 

keep-alive 是Vue内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染,页面第一次进入,钩子的触发顺序:created-> mounted-> activated,退出时触发 deactivated ,当再次进入(前进或者后退)时,只触发activated事件挂载的方法等,只执行一次的放在 mounted 中;组件每次进去执行的方法放在 activated 中;其有几个属性如下:

1)include - 字符串或正则表达式,只有名称匹配的组件会被缓存
2)exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存
3)include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。

<!-- 逗号分隔字符串,只有组件a与b被缓存。-->
 

17、如何封装一个vue组件?

根据业务需求,建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。

准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
封装完毕了,直接调用即可

18、vue首屏白屏如何解决?

4)开启vue服务渲染模式
5)用webpack的externals属性把不需要打包的库文件分离出去,减少打包后文件的大小
6)在生产环境中删除掉不必要的console.log

8)添加loading效果,给用户一种进度感受

使用 v-cloak 指令设置样式,这些样式会在 Vue 实例编译结束时,从绑定的 HTML 元素上被移除。

一般用于解决网页闪屏的问题,在对一个的标签中使用v-cloak,然后在样式中设置[v-cloak]样式,[v-cloak]需写在 link 引入的css中,或者写一个内联css样式,写在import引入的css中不起作用。

答:就是先转化成AST树,再得到的render函数返回VNode(Vue的虚拟DOM节点),具体为:

答:v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
2)v-on指令给当前元素绑定input事件

1)变量不在 data中定义,而是定义在computed中,写法跟写方法一样,有返回值。函数名直接在页面模板中渲染,不加小括号 。
2)根据传入的变量的变化 进行结果的更新。
3)计算属性基于响应式依赖进行缓存。如其中的任意一个值未发生变化,它调用的就是上一次计算缓存的数据,因此提高了程序的性能。而methods中每调用一次就会重新计算一次,为了进行不必要的资源消耗,选择用计算属性。

1)计算属性的时候 初始化的时候就可以被监听到并且计算 但是watch是发生改变的时候才会触发。
2)当有一些数据需要随着其它数据变动而变动时,或者当需要在数据变化时执行异步或开销较大的操作时,使用 watch。

1)计算属性变量在computed中定义,属性监听在data中定义。
2)计算属性是声明式的描述一个值依赖了其他值,依赖的值改变后重新计算结果更新DOM。属性监听的是定义的变量,当定义的值发生变化时,执行相对应的函数。

答:在vue中理解修改数据后,对应的dom需要一定的时间进行更新,因此为了能够准确的后去更新后的dom,可以采用延迟回调的方法进行更新dom的获取,所以出现了$nextTick来进行延迟回调。即:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

24、data为什么是一个函数?

答:这是有JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。

25、vue单页面和传统的多页面区别?

通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 html, js, css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。

指一个应用中有多个页面,页面跳转时是整页刷新

用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小;前后端分离;页面效果会比较炫酷(比如切换页面内容时的专场动画)。

不利于seo;导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理);初次加载时耗时多;页面复杂度提高很多。

26、vue常用的修饰符?

.prevent:等同于JavaScript中的event.preventDefault(),防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播);
.capture:与事件冒泡的方向相反,事件捕获由外到内;
.self:只会触发自己范围内的事件,不包含子元素;
.once:只会触发一次。

27、vue更新数组时触发视图更新的方法?

router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由包含了许多关键的对象和属性,常见的有:
1)push:向 history 栈添加一个新的记录,当我们点击浏览器的返回按钮时可以看到之前的页面

2)go:页面路由跳转 前进或者后退

// 页面路由跳转 前进或者后退
 

3)replace:push方法会向 history 栈添加一个新的记录,而replace方法是替换当前的页面,不会向 history 栈添加一个新的记录

vue异步组件技术 ==== 异步加载
vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 。但是,这种情况下一个组件生成一个js文件

// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。把组件按组分块
 

vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

答:delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。Vue.delete 直接删除了数组 改变了数组的键值。

使用路由方式跳转,无刷新页面,静态跳转;

在子组件内使用特殊的<slot>元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot> 标签及它的内容。

简单说来就是:在子组件内部用 <slot></slot>标签占位,当在父组件中使用子组件的时候,我们可以在子组件中插入内容,而这些插入的内容则会替换 <slot></slot>标签的位置。

当然:单个solt的时候可以不对solt进行命名,如果存在多个 则一个可以不命名,其他必须命名,在调用的时候指定名称的对应替换slot,没有指定的则直接默认无名称的solt

触发当前实例上的自定义事件(并将附加参数都传给监听器回调)

监听实例上自定义事件并调用回调函数,监听emit触发的事件

监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。

用来移除自定义事件监听器。如果没有提供参数,则移除所有的事件监听器;如果只提供了事件,则移除该事件所有的监听器;如果同时提供了事件与回调,则只移除这个回调的监听器。

这四个方法的实现原理是:通过对vue实例挂载,然后分别使用对象存储数组对应的函数事件,其中emit通过循环查找存储的数组中对应的函数进行调用,once只匹配一次就就结束,on是将对应的函数存储到数组中,off是删除数组中指定的元素或者所有的元素事件。具体可以参考文章:VUEemit实现

可以用来获取vue的根实例,比如在简单的项目中将公共数据放再vue根实例上(可以理解为一个全局 store ),因此可以代替vuex实现状态管理;

在子组件上使用ref特性后,this.属性可以直接访问该子组件。可以代替事件emit 和$on 的作用。

使用方式是通过ref特性为这个子组件赋予一个ID引用,再通过this.$refs.testId获取指定元素。

注意:$refs只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问$refs。

$parent属性可以用来从一个子组件访问父组件的实例,可以替代将数据以 prop 的方式传入子组件的方式;当变更父级组件的数据的时候,容易造成调试和理解难度增加;

35、vue开发遇到的问题?

答:在编写样式中,如果需要防止样式的污染,可以使用两种方式,一种是在组件的根元素上增加一个唯一的class或者id,然后在编写组件的样式时候在根元素对应的class或者id下进行编写;另一种方式是在对应的style上添加scoped关键字,不过该关键字对引用的框架UI无效

答:不起作用的原因是因为转码编译的问题,可以使用babel来进行处理,安装babel polypill插件解决

3)初始化页面出现闪屏乱码的问题

答:这是因为vue还没有解析的情况下会容易出现花屏现象,看到类似于{{data}}的字样,可以使用两种方式来进行处理,一种为:在设置index.html的根元素的元素的样式为display:none,然后在mounted中的$nextTick函数中display:block展示;另一种方式是使用vue的内置指令:v-cloak,并且在css中设置样式

web前端资料已经给大家打包好了!

  1. 首先判断数据的类型,如果是基础数据类型,直接返回,如果已经有 ob 属性,表示已经是响应式的数据了,直接返回该数据。如果是对象就走第2步,如果是数组就走第3步
  2. 数组是首先拷贝数组的原型,然后基于拷贝的原型改写(push,pop,unshift,shift,sort,reverse,splice)七个可以改变数组长度的方法,然后将改写后的原型赋给数组的隐式原型
  3. 对数组的隐式原型赋值后,还要观测数组的每一项,重复第一步
  4. 如果对数组的操作是有数据新增(push,unshift,splice),还需要观测数组新增的每一项,同第4步(这里Vue源码的实现是给每个响应式数据[对象和数组]新增了一个不可枚举的属性 ob,它的作用有三,其一是用来判断数据是否已经是响应式的数据,如果是就不需再次观测,其二是属性 ob 是 Observer 类的一个实例,实例上有对数组每一项进行响应式处理的方法),其三是 $set 方法中,ob 用来判断要设置属性的对象是不是响应式的对象,如果它本身就不是响应式对象,则该属性无需定义为响应式的属性

不管是根组件还是非根组件(函数),它们的 data 最终的值都是对象,所以只会在 data 最外层对象的某些属性值是数组,所以在 Object.defineProperty 的 getter 里对数组进行依赖收集,我们知道依赖的收集是调用 dep 类上收集依赖的方法,Vue 的做法是在创建 Observer 类的实例的时候,定义了一个属性 dep,dep 是 Dep 类的实例。对于多维数组和数组新增的数据,Vue 的做法是,在创建 Observer 类的实例的时候,设置了一个不可枚举的属性 ob ,它的值是 Observer 类的实例,所以我们在对多维数组进行依赖收集的时候,可以调用 ob 的 dep 的方法,对于数组新增的数据,调用 ob 上的方法对数组的每一项做数据响应式,并且调用 ob.dep 上的 notify 方法触发更新。

  • 如果 data 的层级过深会影响性能
  • 对象有新增和删除属性没办法做数据的响应式处理(通过$set解决)
  • 如果给对象的属性赋值为对象,也会对赋值后的对象进行响应式处理
  • 在重写数组原型之前,Vue 给每个响应式数据新增了一个不可枚举的 ob 属性,这个属性指向了 Observer 实例,可以用来防止已经被响应式处理的数据反复被响应式处理,其次,响应式的数据可以通过 ob 获取到 Observer 实例的相关方法
  • 对于数组的新增操作(push/unshift/splice),会对新增的数据也做响应式处理
  • 通过索引修改数组内容和直接修改数组长度是观测不到的
  1. 每个属性都有 dep 实例,dep 实例用来收集它所依赖的 watcher
  2. 在模板编译的时候,会取值触发依赖的收集
  3. 当属性发生变化时会触发 watcher 更新
    自动的去调用更新DOM渲染的方法)
  • 注意一:我们平时开发中使用的是不带编译的 Vue 版本(runtime-only),所以在传入选项的时候是不能使用 template 的
  1. 模板编译的整体逻辑主要分为三个部分:
  2. 第二步:对 AST 进行静态节点标记,主要用来做虚拟 DOM 的渲染优化 (优化器)(进行新旧vnode对比的时候可以跳过静态节点)
  • 其实就是 while 循环里不断的通过正则匹配字符串,如果是匹配到是开始标签,就触发 start 钩子处理开始标签和属性,如果匹配到文本,就触发 chars 钩子处理文本,如果匹配到结束标签,就调用 end 钩子处理结束标签。处理完后就把模板中已经匹配到子串截取出来,一直这样循环操作,直到模板的字符串被截取成空串跳出 while 循环。
  • 在匹配到开始标签后,就把开始标签压入栈中,匹配到结束标签就把栈顶元素出栈。第一个进栈的元素就是根节点,除了第一根元素外,其他元素在进栈之前,栈顶的元素就是该元素的父亲节点,所以可以维护元素之间的父子关系(入栈元素的parent是栈顶元素,该入栈元素是栈顶元素的儿子),当栈被清空之后,根节点就是生成的 AST 匹配到文本内容是没有子节点的,所以它直接作为栈顶元素的儿子即可。
  • AST 是用 JS 中的对象来描述节点,一个对象代表一个节点,对象的属性用来保存节点所需的各种数据。
  • 解析器内部分了好几个子解析器,比如 HTML解析器,文本解析器,过滤器解析器。其中最主要的是 HTML解析器,HTML解析器的作用就是解析HTML,它在解析的过程中会不断的触发各种钩子函数。这些钩子函数包括,开始标签钩子函数(start)、结束标签钩子函数(end),文本钩子函数(chars)和注释钩子函数(comment)。
  • 实际上,模板解析的过程就是不断的调用钩子函数的过程,读取 template,使用不同的正则表达式匹配到不同的内容,然后触发对应的钩子函数处理匹配到的字符串截取片段。比如比配到开始标签,触发 start 钩子函数,start 钩子函数处理匹配到开始标签片段,生成一个标签节点添加到抽象语法树上。
  • HTML解析器解析HTML的过程就是循环(while循环)的过程,简单来说就是利用 HTML 模板字符串来循环,每轮循环都从 HTML字符串中截取一小段字符串,重复以上过程,一直到 HTML字符串被截取成一个空串结束循环,解析完毕。
  • 在解析开始标签和结束标签是用栈来维护的,解析到开始标签就压入栈中,解析到结束标签,就从栈顶取出对应的开始标签的AST,栈顶的前一个开始标签就是该标签的父元素,然后就可以建立父子元素之间的关系。
  • 文本解析器是对 HTML 解析器解析出来的文本进行二次加工。文本分为两种类型,一种是纯文本,一种是带变量的文本。HTML解析器在解析文本的时候,并不会区分是纯文本还是带变量的文本,如果是纯文本,不需要进行任何处理,带变量的文本需要文本解析器的进一步解析,因为带变量的文本在使用虚拟DOM进行渲染时,需要将变量替换成变量中的值。
  • 文本解析器通过正则匹配出变量,把变量改写成 _s(x)的形式添加到数组中
  1. vm._update 方法将生成的虚拟DOM进行实例挂载 update 方法的核心是利用 patch 方法来渲染和更新视图,这里是初次渲染,patch 方法的第一个参数是真实DOM,更新阶段第一个参数是 oldVnode
  • Vue.mixin的作用就是抽离公共的业务逻辑,原理类似 “对象的继承”,当组件初始化的时候会调用 mergeOptions 方法进行合并,对于不同的 key(data,hooks,components...)有不同的合并策略。如果混入的数据和组件本身的数据有冲突,会采用“就近原则”,以组件本身的为准。
  • mixin有很多的缺陷:命名冲突,来源不清晰,依赖问题
  1. watch 的使用方式,可以是对象,可以是函数,也可以是数组
  2. 不论是哪种使用方式,watch 的每一个属性对应的函数(数组的使用方式,数组中的每一项(函数))都是一个 用户watcher,其实现都是调用的 $watch(vm, handler)
  3. watch 的属性对应的函数里有新值和旧值,我们是如何返回新值和旧值的呢?
  4. new Watcher() 的时候传递的是属性的 key,我们要把它包装成一个函数(函数内部就是根据 key 取值),赋值给 Watcher类的 getter 属性,在 Watcher 类实例化的时候,会调用一次 get 方法,我们就可以拿到它的值(取值同时会进行依赖收集)
  5. 在值更新后,会再次调用 Watcher 类的 get 方法获得新值
    对应的旧值了,取值也就进行了依赖收集,当 key 对应的数据改变了,watcher 的 getter 方法会再次执行,这时就拿到了新值,然后调用 key 对应的回调函数,将新值和旧值传给它
  1. 每个计算属性本质上也是一个用户 watcher,在它取值的时候进行依赖收集,computed 依赖的值改变后触发更新
  2. computed 是有缓存的,即依赖的值没有发生改变,多次获取,是不会多次调用 watcher 的 get 方法获取值的,所以用 dirty 属性来标记是否需要重新计算值,如果不需要计算,直接返回 watcher 的 value,如果需要计算,再来调用 get 方法获取新的值,再返回 watcher 的 value

Vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归和双指针的方式

  1. 首先比对是否是相同的节点,如果不是删除旧的DOM,生成新的DOM插入
  2. 如果是相同的节点,比对更新属性
  3. 判断是否是文本节点,如果是,判断文本内容是否相同,不同更新文本内容
  4. 比对新旧子节点,如果只有新的有子节点,新增子节点插入;如果只有旧的有子节点,将元素的 innerHTML 置为空
  5. 如果新旧都有子节点,比对新旧子节点(采用双指针)
  6. 依次是头头、尾尾、头尾、尾头比较,没有匹配到,就乱序比对
  7. 新的起始节点是否能在旧的映射表中找到,不能找到直接在旧的前面插入,如果找到,将映射表找到的旧的节点,移动到前面,并将该位置置为null
  8. 因为在乱序比对中,有将旧节点置为 null 的情况,所以在进行子节点比对前,先判断该节点是否为null,为null顺移
  9. 比对完之后如果新的节点还有,插入新的节点(插入的位置要判断是否在哪里插入),如果旧的节点还有,删除旧的节点(null的位置跳过)

学习框架技术是为了更好的开发,学习底层原理是为了让产品更好用,更好的让finclip小程序在各家平台上更好的兼容和流畅使用。

声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。

Vue的生命周期钩子就是回调函数而已,当创建组件实例的过程中会调用对应的钩子方法

内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布!

Vue.mixin的作用就是抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用mergeOptions方法进行合并,采用策略模式针对不同的属性进行合并。如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。

mixin中有很多缺陷 "命名冲突问题"、"依赖问题"、"数据来源问题",这里强调一下mixin的数据是不会被共享的!

nextTick中的回调是在下次 DOM 更新循环结束之后执行的延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。原理就是异步方法(promise,mutationObserver,setImmediate,setTimeout)经常与事件环一起来问(宏任务和微任务)

vue多次更新数据,最终会进行批处理更新。内部调用的就是nextTick实现了延迟更新,用户自定义的nextTick中的回调会被延迟到更新完成后调用,从而可以获取更新后的DOM。

Virtual DOM就是用js对象来描述真实DOM,是对真实DOM的抽象,由于直接操作DOM性能低但是js层的操作效率高,可以将DOM操作转化成对象操作,最终通过diff算法比对差异进行更新DOM(减少了对真实DOM的操作)。虚拟DOM不依赖真实平台环境从而也可以实现跨平台。

虚拟DOM的实现就是普通对象包含tag、data、children等属性对真实节点的描述。(本质上就是在JS和DOM之间的一个缓存)

Vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。

  • 1.先比较是否是相同节点
  • 2.相同节点比较属性,并复用老节点
  • 3.比较儿子节点,考虑老节点和新节点儿子的情况
  • 4.优化比较:头头、尾尾、头尾、尾头

Vue3中采用最长递增子序列实现diff算法

为什么$set可以触发更新,我们给对象和数组本身都增加了dep属性。当给对象新增不存在的属性则触发对象依赖的watcher去更新,当修改数组索引时我们调用数组本身的splice方法去更新数组

// 1.是开发环境 target 没定义或者是基础类型则报错

// 3.如果是对象本身的属性,则直接添加即可

// 5.如果不是响应式的也不需要将其定义成响应式属性

// 6.将属性定义成响应式的

// 7.通知视图更新

  • created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el
  • beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。
  • mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
  • beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
  • updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
  • beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
  • destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
  • created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。(服务端渲染支持created方法)
  • mounted 实例已经挂载完成,可以进行一些DOM操作
  • beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
  • updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  • destroyed 可以执行一些优化操作,清空定时器,解除绑定事件
  • props和父组件向子组件传递数据是通过传递的,子组件传递数据给父组件是通过emit触发事件来做到的
  • children 获取当前组件的父组件和当前组件的子组件
  • 父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量。
  • envetBus 平级组件数据传递 这种情况下可以使用中央事件总线的方式

$attrs主要的作用就是实现批量传递数据。provide/inject更适合应用在插件中,主要是实现跨级数据传递

1. 父子组件渲染的先后顺序

2. 组件是如何渲染到页面上的

①在渲染父组件时会创建父组件的虚拟节点,其中可能包含子组件的标签

②在创建虚拟节点时,获取组件的定义使用Vue.extend生成组件的构造函数。

③将虚拟节点转化成真实节点时,会创建组件的实例并且调用组件的$mount方法。

④所以组件的创建过程是先父后子

每次使用组件时都会对组件进行实例化操作,并且调用data函数返回一个对象作为组件的数据源。这样可以保证多个组件间数据互不影响

v-if在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。v-show会被编译成指令,条件不满足时控制样式将对应节点隐藏 (内部其他指令依旧会继续执行)

扩展回答: 频繁控制显示隐藏尽量不使用v-if,v-if和v-for尽量不要连用

前端开发Vue中的v-指令的使用

前端开发之Vue模板学习

前端开发之Vue框架的优势

我要回帖

更多关于 vue更新数组的方法 的文章

 

随机推荐