一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 7 天,点击查看活动详情。
如何理解前端渲染
把数据填充到HTML标签中:模板 + 数据 → 前端渲染 → 静态 HTML 内容
为何 Vue 采用异步渲染
因为如果不采用异步更新,那么每次更新数据都会对当前组件进行重新渲染.所以为了性能考虑。Vue会在本轮数据更新后,再去异步更新视图
如何理解响应式
Html5 中的响应式
屏幕尺寸的变化导致样式的变化
数据的响应式
数据的变化导致页面内容的变化
Vue 的响应式系统
- 任何⼀个
Vue Component都有⼀个与之对应的Watcher实例 Vue的data上的属性会被添加getter和setter属性- 当
Vue Component render函数被执⾏的时候,data上会被 触碰(touch),即被读,getter⽅法会被调用,此时Vue会去记录此Vue component所依赖的所有data。(这⼀过程被称为依赖收集) data被改动时(主要是用户操作) , 即被写 ,setter方法会被调⽤ , 此时Vue会去通知所有依赖于此data的组件去调用他们的render函数进⾏更新
谈谈双向数据绑定 当数据发生变化的时候,视图也就发生变化 ;当视图发生变化的时候,数据也会跟着同步变化
实现数据绑定的方法
发布者-订阅者模式(backdone.js)
脏值检查(angular.js)
数据劫持(vue.js)
`Vue 的数据双向绑定是如何实现的
数据劫持
理解:通过Object.defineProperty劫持对象的访问器
你对 MVVM 的理解
MVVM
构成:
Model(数据层):数据模型(获取数据的逻辑)View(视图层):视图模板(Vue中指的是各种template)ViewModel(控制层):视图适配器(Vue中对应的js,声明绑定的元素及绑定的数据)。暴露给View层需要的数据,并处理View层具体业务逻辑
优点:
- 分离
View(视图)和Model(模型),降低代码耦合,提⾼视图或者逻辑的重⽤性:比如View可以独⽴于Model变化和修改,⼀个ViewModel可以绑定在不同的View上,当View变化的时候Model可以不变,当Model变化 的时候View也可以不变。你也可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多View重⽤这段视图逻辑 - 提⾼可测试性:
ViewModel的存在可以帮助开发者更好地编写测试代码 - ⾃动更新
Dom::利⽤双向绑定,数据更新后视图⾃动更新,开发者从繁琐的⼿动Dom中解放
缺点:
Bug很难被调试:因为使⽤双向绑定的模式,当你看到界⾯异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得⼀个位置的Bug被快速传递到别的位置,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的- ⼀个⼤的模块中
Model也会很⼤,虽然使⽤⽅便且保证了数据的⼀致性,但是如果⻓期持有不释放内存,就会造成内存的浪费 - 对于⼤型的图形应⽤程序,视图状态较多,
ViewModel的构建和维护的成本都会⽐较⾼
MVC
构成:
Model(数据层):程序需要操作的数据或信息(用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法)---- 数据保存View(视图层):提供给用户的操作界面,是程序的外壳 ---- 用户界面Controller(控制层):M 和 V 之间的连接器,用于控制应用程序的流程,及页面的业务逻辑 ---- 业务逻辑
理解:
它的构成可以看做"外观"、"机制"和"功能/数据"这三层结构。实际运作便是视图层传送指令给控制层,控制层调用数据层对象方法完成业务逻辑,并把结果返回到视图层(V-C-M-V-V)
三层紧密联系(对外提供接口),又相互独立(内部变化不影响其它层)
- 实现模块化
- 变化不影响其它层,方便维护
MVVM 和 MVC 有什么不同
MVVM实现了数据的双向绑定MVVM可维护性更高MVC某些时候性能更好MVC是后端分层开发概念,MVVM是前端视图层的概念,主要关注于视图层分离
实现一个 MVVM(双向绑定)
vue2.0 基础
Vue 中 v-html 会导致哪些问题?
- 可能会导致
xss攻击 v-html会替换掉标签内部的子元素
Vue 父子组件生命周期调用顺序
-
加载渲染过程 :父
beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted -
子组件更新过程 :父
beforeUpdate->子beforeUpdate->子updated->父updated -
父组件更新过程 :父
beforeUpdate->父updated -
销毁过程 :父
beforeDestroy->子beforeDestroy->子destroyed->父destroyed
组件的调用顺序都是先父后子,渲染完成的顺序是先子后父 。组件的销毁操作是先父后子,销毁完成的顺序是先子后父
Vue 组件如何通信? 单向数据流
-
父子间通信 :父->子通过
props、子-> 父$on、$emit(发布订阅) -
获取父子组件实例的方式 :
$parent、$children -
在父组件中提供数据子组件进行消费 :
Provide、inject插件 -
Ref获取实例的方式:调用组件的属性或者方法 -
Event Bus实现跨组件通信 :Vue.prototype.$bus = new Vue -
Vuex状态管理实现通信 :$attrs $listeners
Vue 组件中的 data 为什么必须是函数
关于这个问题,需要意识到:这是因为js 的特性,跟vue本身设计无关。js本身的面向对象编程是基于原型链和构造函数,我们会注意到,原型链上添加一般都是一个函数方法而不会去添加一个对象。vue实例中的data是一个对象,作为绑定的数据。而这里组件中的data()必须是一个函数,其实应该叫做setData()。
现在我们开始一步步理解:
1、关于 js 的数据类型
js 中的数据可以分为基础数据类型和引用数据类型
-
基础数据类型 如
undefinded,null,Number,String,Boolean,symbol,数据存放在栈中; -
引用数据类型 即对象数据类型,如:
object、array、function,数据存放在堆中,栈中存放的是堆中的引用地址。
2、关于 js 的深浅拷贝
我们来看看深浅拷贝的区别:
浅拷贝:将原对象或原数组的引用直接赋给新对象/新数组,新对象/数组只是原对象的一个引用 深拷贝:创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”
3、数据类型与深浅拷贝
- 基本数据类型是放在栈里面的,它是按值访问,在栈内存中发生复制行为时系统会为新的变量提供新值,所以两个变量互不影响。
- 引用数据类型是放在堆内存中的,它是按引用访问的,在栈内存中有一个地址是指向堆内存中的引用数据类型的,所以我们拷贝引用数据类型其实就是拷贝了栈内存中的地址,因为地址一样,他们都是指向同一个引用数据类型,所以两个变量会相互影响,这时就必须使用深拷贝了。
4、为什么 data 要用函数?
经过以上理解,我们可以类比引用数据类型:
Object对象是引用数据类型,如果不用function函数返回,每个组件的data都是内存的同一个地址,如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。而两个实例应该有自己各自的域才对。
这里补充一点:javascipt只有函数构成作用域(注意理解作用域,只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域)。data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
Watch,Computed 区别与使用场景
computed:
-
computed内部的函数在调用时不加()。即vm.属性名,如vm.reversedMessage -
computed是依赖vm中data的属性变化而变化的,也就是说,当data中的属性发生改变的时候,当前函数才会执行,data中的属性没有改变的时候,当前函数不会执行。
data 和 computed 最核心的区别在于 data 中的属性并不会随赋值变量的改动而改动,而 computed 会。
-
computed 中的函数必须用 return 返回。
-
注意:在
computed中不要对data中的属性进行赋值操作。如果对data中的属性进行赋值操作了,就是 data 中的属性发生改变,从而触发computed中的函数,形成死循环了。 -
当
computed中的函数所依赖的属性没有发生改变,那么调用当前函数的时候会从缓存中读取。(不会再次计算)
watch:
-
watch中的函数名称必须要和data中的属性名一致,因为watch是依赖data中的属性,当data中的属性发生改变的时候,watch中的函数就会执行。 -
watch中的函数有两个参数,前者是newVal最新的值,后者是oldVal输入之前的值。 -
watch中的函数是不需要调用的。 -
watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
"obj.name"(){} //如果obj的属性太多,这种方法的效率很低,
obj:{handler(newVal){},deep:true} //用handler+deep的方式进行深度监听。
- 特殊情况下,
watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者$set。
this.arr.splice(0, 1, 100); //修改arr中第0项开始的1个数据为100,
this.$set(this.arr, 0, 100); //修改arr第0项值为100。
immediate:true页面首次加载的时候做一次监听,组件加载立即触发回调函数执行。
(1)区别
-
功能上:
computed是计算属性;watch是监听一个值的变化,然后执行对应的回调。 -
是否调用缓存:
computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取(支持缓存),也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值;而watch在每次监听的值发生变化的时候都会执行回调(不支持缓存)。 -
是否调用
return:computed中的函数必须要用return返回,watch中的函数不是必须要用return。 -
是否支持异步:
computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化;watch支持异步。
(2)使用场景区别
computed:比较适合对多个变量或者对象进行处理后返回一个结果值,也就是说多个变量中的某一个值发生了变化,则我们监控的这个值也就会发生变化。当一个属性受多个属性影响的时候(多对一),使用computed-------购物车商品结算。
watch:一般用于异步或者开销较大的操作。当一条数据影响多条数据的时候(一对多),使用watch-------搜索框。
v-show 与 v-if 区别与使用场景
共同点:都能实现元素的显示和隐藏
(1)区别
⚫ v-if控制元素是否渲染到页面:v-if是动态的向DOM树内添加或者删除DOM元素
v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件
⚫ v-show控制元素是否显示(已经渲染到了页面):v-show本质就是标签display设置为none,控制隐藏
v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点
⚫ 补充:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;v-if有更高的切换消耗;v-show有更高的初始渲染消耗;v-if可以搭配template使用,而v-show不行。在使用template时,v-show将失去作用。因为v-show是设置显示与隐藏,而template 是没有实际东西的 dom,所以v-show与template联合使用将失效。
(2)使用场景
如果需要非常频繁地切换,则使用v-show较好;如果在运行时条件很少改变,则使用v-if较好,tab中使用较好。
Vue 里的 key 有什么作用,可以用数组的 index 代替么?
(1)Vue和React都实现了一套虚拟DOM,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面。而隐藏在背后的原理便是其高效的Diff算法。核心假设如下:
- 两个相同的组件产生类似的
DOM结构,不同的组件产生不同的DOM结构。 - 同一层级的一组节点,他们可以通过唯一的
id进行区分。
基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从 O(n^3)降到了 O(n)。
(2)Vue 中 key 的作用(官网):
(3)总结:当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。只适用于不依赖子组件状态或临时DOM状态 (例如:表单输入值) 的列表渲染输出
概括来说 key 的作用主要是为了高效准确的更新虚拟 DOM。另外vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
补充:
-
在写
v-for的时候,都需要给元素加上一个key属性 -
key的主要作用就是用来提高渲染性能的! -
key属性可以避免数据混乱的情况出现 (如果元素中包含了有临时数据的元素,如果不用key就会产生数据混乱)
(4)不能使用 index 代替 key:给key加上index等于没有加,key默认得就是Index。如果数据项的顺序被改变,Vue将不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
Vue 组件的生命周期
每个生命周期什么时候被调用 ?
beforeCreate:在实例初始化之后,数据观测(data observer) 之前被调用。created:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。这里没有$elbeforeMount:在挂载开始之前被调用:相关的render函数首次被调用。mounted:el 被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件 监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
每个生命周期内部可以做什么事?
created:实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。mounted:实例已经挂载完成,可以进行一些DOM操作beforeUpdate:可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。updated:可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态, 因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。- destroyed `:可以执行一些优化操作,清空定时器,解除绑定事件
何时需要使用
beforeDestroy?
- 可能在当前页面中使用了
$on方法,那需要在组件销毁前解绑。 - 清除自己定义的定时器
- 解除事件的绑定
scroll mousemove....
为什么 v-for 和 v-if 不能连用?
v-for会比v-if的优先级高一些,如果连用的话会把v-if 给每个元素都添加一下,会造成性能问题