使用 Object.defineProperty() 来进行数据劫持有什么缺点?
在对对象添加属性或者通过下标方式修改数组时,Object.defineproperty无法监测到,Vue底层对数组的监测是通过建立一个对象,在这个对象里重写push,pop,shift,unshift,sort,reverse,splice七种方法,在这七种方法里对数组元素添加响应,并让这个对象指向array.prototype。
在Vue3中已经采用proxy方法,可以监听到所有属性,但由于是ES6的语法,有兼容性问题。
MVVM和MVC
MVC模式分离了Model,View和Controller层,其中View负责页面的显示逻辑,Model层负责存储页面的数据以及进行对数据的操作,View与Model层应用了观察者模式,当Model层发生变化会通知View层更新页面。Controller层是View层与Model层的纽带,当用户与页面进行交互,Controller层中的事件触发器开始工作,通过调用Model层,完成对Model的修改,然后Model层再通知view层更新。
MVVM模式分为Model,View和ViewModel层:
Model代表数据模型,数据和业务逻辑都在Model层定义
View层代表视图,负责展示数据
ViewModel层监听Model中数据的改变并控制视图的更新,处理用户交互
Model与ViewModel层之间存在着双向数据绑定,因此虽然Model与View层无直接关联,但通过ViewModel层,Model层的数据变化会引起View层视图刷新,View层用户交互改变的数据也会在Model层同步。
整理语言版
Vue中的MVVM模式包括model层数据层,view层视图层和viewModel层,viewModel层与view层、model层实现了双向数据绑定。ViewModel层做了两件事,一是通过DataBindings完成了Model到View层的映射,无需手动update view,二是通过DOM listeners完成view到Model的监听,这样model就会随view触发事件而改变。
computed 和 watch 的区别
功能上:computed是计算属性,watch是监听一个值的变化,然后执行相应的回调。
是否调用缓存:computed中的函数所依赖的属性没有发生变化,那么调用当前的函数的时候会从缓存中读取,而watch在每次监听的值发生变化时都会执行回调。
是否调用return:computed中的函数必须要用return返回,watch不用
computed默认第一次加载时就开始监听,而watch默认第一次加载不进行监听,如果需要,要加immediate:true
使用场景:computed—当一个属性收到多个属性影响时,如计算购物车商品总价。watch----当一条数据影响到多条数据时,如搜索框。
computed 和 methods 的区别
可以将同一函数定义为methods或者一个计算属性,于结果而言两者相同,不过对于逻辑较为繁杂的一般使用Methods
不同点:computed会基于他的依赖进行缓存,只有当它的依赖发生变化时,computed才会重新求值,否则会从缓存中调用函数。而methods调用时会始终执行该函数。
slot插槽
slot分为三类,默认插槽、具名插槽和作用域插槽。插槽slot是子组件的一个模板标签元素,这一标签是否显示、如何显示由父组件指定。
默认插槽:又名匿名插槽,当slot没有指定name属性时就为默认插槽,一个组件内只能有一个默认插槽(多个默认插槽无法分辨是哪个)
具名插槽,具有name属性的插槽,一个组件内可以有多个具名插槽。
作用域插槽,可以是默认插槽,也可以是具名插槽。作用域插槽的特点是子组件在渲染作用域插槽时,可以将子组件内部的数据传递给父组件,父组件就可以在模板内接收数组并决定如何渲染这个插槽
在Vue2.6.0中,具名插槽和作用域插槽的写法统一为v-slot,缩写为#
//父组件
{{ user.firstName }}
父组件是无法获取到user的,于是使用作用域插槽
{{ user.lastName }}
//父组件用v-slot绑定,我们选择将包含所有插槽 prop 的对象命名为 slotProps
{{ slotProps.user.firstName }}
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签内的内容,存放在vm.slot中,默认插槽为vm.slot中,默认插槽为vm.slot中,默认插槽为vm.slot.default,具名插槽为vm.slot.xxx,当组件执行渲染函数时,遇到slot标签,使用slot.xxx,当组件执行渲染函数时,遇到slot标签,使用slot.xxx,当组件执行渲染函数时,遇到slot标签,使用slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
过滤器的作用,如何实现过滤器
Vue中filters不会修改数据,而是会过滤数据,改变用户看到的输出。
使用场景:
需要格式化数据的情况,比如需要处理时间、价格等数据格式的输出 / 显示。
比如后端返回一个 年月日的日期字符串,前端需要展示为 多少天前 的数据格式,此时就可以用fliters过滤器来处理数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在插值表达式 {{ }} 和 v-bind 表达式 中,然后放在操作符“ | ”后面进行指示。
例如,在显示金额,给商品价格添加单位:
- 商品价格:{{item.price | filterPrice}}
filters: {
filterPrice (price) {
return price ? (‘¥’ + price) : ‘–’
}
}如何保存页面当前的状态
用keep-alive标签,当组件在keep-alive内被切换时组件的activated,deactivated这两个生命周期钩子函数会被执行,被包裹在keep-alive标签中的组件的状态会被保留
// 等同于
<input
v-bind:value=“message”
v-on:input=“message=$event.target.value”// e v e n t 指代当前触发的事件对象 ; / / event 指代当前触发的事件对象; // event指代当前触发的事件对象;//event.target 指代当前触发的事件对象的dom;
//$event.target.value 就是当前dom的value值;
//在@input方法中,value => sth;
//在:value中,sth => value;
v-if v-show v-html原理
v-if:v-if会调用addIfCondition方法,生成vnode的时候会忽略对应节点,所以不会被渲染。
v-show:v-show会生成vnode,也会生成真实dom插入dom树中,但会被修改display属性。
v-html:v-html就是将innerHTML设置为v-html的值。
v-pre:不进行解析为什么不建议v-if v-for连用
v-for优先级高于v-if,每次都会进行v-if的判断
v-if是局部编译卸载,与v-for配合使用性能消耗大。常见的事件修饰符及其应用
.stop: 等同于event.stopProppagation(),防止事件冒泡
.prevent 等同于event.preventDefault(),阻止浏览器默认行为
.capture 与事件冒泡方向相反,事件捕获由外到内
.self 只触发自己范围内元素,不包含子元素
.once 只触发一次
事件冒泡的应用在于给父元素添加事件,从而在子元素触发事件时通过事件冒泡调用到父元素身上的事件。v-if v-show 区别
手段:v-if是动态的往DOM树内插入删除dom节点,v-show是控制dom元素的display样式来控制显隐。
编译过程:v-if切换有一个局部编译、卸载的过程。
编译条件:v-if是惰性的,如果初始条件为假,那么什么都不做,只有在条件第一次变为真时才开始局部编译;而v-show无论首次条件是否为真都会被编译,然后被缓存,而且dom元素保留v-model是如何实现的,语法糖实际是什么
作用在表单元素上,动态绑定input的value指向message变量,并且在触发input事件时动态的将message设置为目标值。
// 等同于 //$event 指代当前触发的事件对象; //$event.target 指代当前触发的事件对象的dom; //$event.target.value 就是当前dom的value值; //在@input方法中,value => sth; //在:value中,sth => value;作用在组件上 在自定义组件中,v-model会默认利用名为value的prop和名为input的事件
本质是用prop和$emit实现Vue.component(‘base-checkbox’, {
model: {
prop: ‘checked’,
event: ‘change’
},
props: {
checked: Boolean
},
template: `
<input
type=“checkbox”
v-bind:checked=“checked”
v-on:change=“$emit(‘change’, $event.target.checked)”`
})
这两个组件具体交互是怎么样的呢:
首先将父组件转化为如下格式
父组件将lovingVue变量传入base-checkbox组件,如果不使用model选项使用的prop名为value(默认),一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component(‘base-checkbox’, {
model: {
prop: ‘checked’,
event: ‘change’
},
props: {
checked: Boolean
},
template:<input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" >
})base-checkbox子组件接收props,将其动态绑定到input的checked上,并向父组件传出名为change的事件,父组件将接收到的值赋值给lovingVue
总结来讲,v-model作用在组件上时,默认prop时value,event是input,model选项可以修改这个默认。data为什么是一个函数而不是对象
Javascript中的对象是引用数据类型,当多个实例引用一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化。
而在Vue中,我们编写组件需要让每个组件的数据独立,即组件的复用性,我们需要让同一个组件在不同地方引用时有私有的数据空间,所以要写成函数返回值的模式。
对keepalive的理解keepalive组件能够缓存子组件,使其不经历销毁流程(即beforeDestroy, Destroyed),而是经历(activated, deactivate)。
keepalive有三个配置项,分别是include,exclude和max,分别是缓存名单,不缓存名单和最大缓存个数。配合路由的meta进行使用。
keepalive在各个生命周期内做的事:created—初始化一个cache和keys,前者用来存放缓存的虚拟节点集合,后者用来缓存组件的key集合; mounted — 检测include和exclude变化进行相应操作; destroyed:清除所有缓存相关的东西
keepalive自身组件不会渲染到页面上。$nextTick原理及作用
Vue中DOM更新是异步的,只要观察到数据变化,Vue将开启一个队列,并缓冲同一个事件循环中发生的所有数据改变并去重,然后在下一次事件循环中,Vue刷新队列并执行工作。
在Vue中,DOM的更新一般是在一轮事件循环中的最后执行,但当我们执行一个同步操作(如获取DOM)时,同步任务栈并未完成,异步任务队列也就没有被放进执行栈,这时我们就可以使用nextTick进行更改。nextTick进行更改。
nextTick进行更改。nextTick当中的回调函数会被放在下一次事件循环中执行,这样就保证了这个回调一定是在Dom更新完成之后执行的
使用场景:当数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构时,这个操作的方法要在nextTick中。
{{ message }}
getMyWidth() {
this.showMe = true;
//this.message = this. r e f s . m y W i d t h . o f f s e t W i d t h ; / / 报错 T y p e E r r o r : t h i s . refs.myWidth.offsetWidth; //报错 TypeError: this. refs.myWidth.offsetWidth;//报错TypeError:this.refs.myWidth is undefined
this.KaTeX parse error: Expected '}', got 'EOF' at end of input: …message = this.refs.myWidth.offsetWidth;
})
}在vue生命周期中,如何要在created钩子里获取dom,需要放在nextTick里,watch同理。
立即更改某个数据后,获取DOM元素发现其数据并没有更改,此时是因为更新DOM的操作被放在了一个异步队列中,直到同一事件循环中所有数据变化完成,才会进行异步队列的处理。所以这本质上是一种优化策略。$nexttick返回的是一个Promise对象。Vue中给data中的对象属性添加一个新属性会发生什么,如何解决
- {{value}}
addObjB方法给obj添加了一个b属性,值为obj.b,这个添加显然是会生效的,但视图却不会刷新,因为在vue实例创建时,obj.b并未声明,因此就没有被Vue转换为响应式属性,也就不会触发视图的更新,这时就需要用KaTeX parse error: Expected '}', got 'EOF' at end of input: …jB(){ this.set(this.obj,“b”,“obj.b”)
Vue2响应式原理对于数组的处理
Vue2响应式原理的核心就是Object.defineProperty,而对于数组而言,如果把它当作对象,key为下标,value为元素来监听的话,在splice、reverse等方法就会多次触发getter和setter,这在数据量大时的性能消耗是巨大的。
于是Vue2重写了数组的七种方法,建立了一个对象,这个对象指向Array.prototype,首先这个对象可以通过原型链调用原始方法更改数组,其次这个对象会在调用这些方法的同时提示watcher更新,并对push unshift splice这些新增元素的方法进行判断,即对新增的元素进行响应式的处理。单页面与多页面对比
概念:
SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。
MPA多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。
Vue data 中某一个属性值发生变化,视图会立即同步执行渲染吗
不会,因为Vue对DOM的更新是异步的,Vue会维护一个队列,然后监听到数据变化就将同一事件循环的这些数据进行缓冲,同时,如果同一个watcher被重复触发即数据被多次更改,只有最后一次更改会被推入缓冲队列中,然后在下一个事件循环tick中,Vue刷新队列并执行工作。
mixin extends 留坑
留坑
Vue自定义指令
除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
一般使用自定义指令只用来操作DOM展示,不修改内部的值。下面举一个输入框的例子,当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。现在让我们用指令来实现这个功能:
// 注册一个全局自定义指令v-focus
Vue.directive(‘focus’, {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})当然也可以局部注册
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus property,如下:
子组件可以直接改变父组件的数据吗
不可以,因为Vue提倡单向数据流。每次父组件更新时,子组件的所有prop都会刷新为最新的值。只能通过$emit派发一个自定义事件,父组件收到后由父组件更改
assets和static的区别
相同点:assets和static两个都是存放静态资源文件。项目中所需要的资源图片文件,图标等都可以放在这两个文件夹下。
不同点:assets中存放的静态资源文件会经过打包上传,而static不会进行打包。
Vue项目的性能优化
(1) 编码减少data中的数据,data中的数据都是响应式的,也就意味着data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不连用。v-if的条件变化会导致整个v-for内的dom插入删除。
采用keep-alive缓存组件
路由懒加载,异步加载组件
防抖、节流
按需引入第三方模块
长列表滚动到可使界面动态加载
图片懒加载
(2) 打包
生产环境下删除console.log,用uglifyjs-webpack-plugin
使用CDN加载第三方模块留坑why
thread-load多线程打包
splitChunks抽离公共文件 ?留坑对SPA的理解
SPA就是单页面应用,他会web页面初始化的时候加载对应的html,css,js,当页面加载完成,SPA不会因为用户操作而重新加载资源,而是用路由机制实现HTML的变化。
hash路由
hash路由的本质是锚点(#)定位,可以总结出以下几个特点hash路由会触发网页跳转,即浏览器的前进后退
hash可以改变url,但不会触发页面的重新加载(hash的改变记录在window.history)中,即不会刷新页面,不会重新加载,自然就不算是http请求。
锚点后面(#)的内容是不会提交到server端的。history路由
history路由使用了h5提供的pushState(),replaceState方法实现浏览器的前进后退,通过history.state将任意类型的数据添加到记录中。用户在history模式下操作切换页面,虽然url会被改变,但浏览器不会刷新页面也不会往服务器发送请求,但会触发代码里的监听事件从而改变页面内容,所以无需向服务端发送请求。但如果此时用户刷新页面,浏览器发送给服务端的就是新的url,所以服务端要进行配置使得这些url返回同一个index.html
生命周期
生命周期
Vue实例有一个完整的生命周期,从开始创建,初始化数据,编译模板,挂载DOM到渲染,然后更新DOM,渲染,卸载等一系列流程。beforeCreate: 数据观测和初始化事件还未开始,此时还未对data进行响应式化,watcher也就没有依赖数据,所以在这个data、computed等都不可访问。
created: 实例创建完成,但DOM树未挂载,无法获取DOM元素,但data、methods、computed等都已经配置完成。
beforeMount:在挂载前执行,实例已经完成编译模板,把data中的数据和模板生成html。此时还没有挂载html到页面上,render函数首次被调用生成虚拟dom。
mounted:挂载完成,此时虚拟dom已经被转化为真实dom并插入到DOM树上。
beforeUpdate:数据有更新时被调用
updated:已经用diff算法用最小dom开支完成重新渲染dom
beforeDestroy:实例销毁之前调用,此时实例仍然可用
destroyed:实例销毁后调用,所有事件监听器会被移除,子实例也被销毁
另外keep-alive也有独特的生命周期,为activated和deactivated,用keep-alive包裹的组件在切换时不会销毁,而是缓存到内存中并执行deactivated钩子函数,命中缓存渲染后会执行activated钩子函数生命周期详细版
首先我们把创建Vue实例到created这部分称为初始化阶段
判断是根组件还是子组件,如果是根组件就合并全局配置到vm.options上,如果是子组件就把深层次的配置对象挂载到vm.options上,如果是子组件就把深层次的配置对象挂载到vm.options上,如果是子组件就把深层次的配置对象挂载到vm.options上
初始化实例属性,如$children, $refs,初始化事件监听、渲染函数等。
调用beforeCreate
初始化data, methods, computed, watch等
调用created函数
进入模板编译阶段模板编译阶段在created和beforeMount之间
模板解析:将模板字符串通过正则转化为抽象语法书AST
优化:遍历AST,将其中的静态节点打上标记
将AST转换为渲染函数
进入挂载阶段挂载阶段在beforeMount 和 Mounted间
调用beforeMount钩子
生成虚拟DOM,进行数据劫持,依赖收集。
将虚拟DOM渲染为真实DOM挂载到页面
调用Mounted函数销毁阶段
beforeDestroy
移除依赖,销毁子组件
销毁组件Vue子组件和父组件执行顺序
父组件 beforeCreate
父组件 created
父组件 beforeMount
子组件 beforeCreate created beforeMount mounted
父组件 mounted
父组件 beforeUpdate
子组件 beforeUpdate updated
父组件 updated在哪个生命周期发送ajax请求
在created发送异步请求,因为在created发送异步请求能更快获取到服务端数据,减少页面加载时间。个人理解时beforeMount要生成虚拟dom,而且beforeMount之后要将虚拟dom转为真实dom插入到dom树上,所以排除beforeMount;而mounted要在子组件挂载完之后才能调用,所以排除mounted,而且mounted发送异步请求,由于此时DOM已经挂载,可能会造成抖动。
组件通信
props/ e m i t 父组件向子组件传值用 p r o p s , 子组件向父组件传值用 emit 父组件向子组件传值用props,子组件向父组件传值用 emit父组件向子组件传值用props,子组件向父组件传值用emit绑定事件// 父组件
//子组件
全局事件总线(emit/emit/emit/on)
eventbus事件总线适合父子兄弟组件间通信,相当于将传输的数据存储在事件总线中,其他组件通过引入事件总线进行传输。
// event-bus.jsimport Vue from ‘vue’
export const EventBus = new Vue()求和: {{count}}依赖注入(provide/ inject)
适用于祖孙组件通信,即多层次的父子组件通信,无需多层props
provide/ inject是vue提供的两个钩子,与data,methods同级provide用来发送数据或方法
inject 用来接收数据或方法provide() {
return {
num: this.num
};
}provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}inject: [‘app’]
console.log(this.app.num)
复制代码
也可以这样写,来访问父组件的所有属性
provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}inject: [‘app’]
console.log(this.app.num)ref/$refs
这个用在子组件上就可以访问子组件的实例
$ p a r e n t parent parentchildren
使用 p a r e n t 可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)使用 parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法) 使用 parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)使用children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。
.sync v-model
两者作用相同,都是父子组件v-bind $emit的语法糖,.sync默认子组件emit的事件名为update:传递数据名,v-model默认子组件是接受value的prop,emit的是input,不过可以通过model属性配置。
vuex
$root
获取app.vue的数据
slot
作用域插槽
$attrs, l i s t e n e r s 绑定在子组件的非 p r o p 属性会作为子组件根元素的 c s s 属性,可以通过 i n h e r i t A t t r s = f a l s e 阻止这种继承,同时通过 listeners 绑定在子组件的非prop属性会作为子组件根元素的css属性,可以通过inheritAttrs=false阻止这种继承,同时通过 listeners绑定在子组件的非prop属性会作为子组件根元素的css属性,可以通过inheritAttrs=false阻止这种继承,同时通过attrs接收这些属性。它也可以绑定v-bind:" a t t r s " 在孙子组件上,孙子组件上就可以通过 p r o p s 接收。组件通信总结父子组件父组件 v − b i n d 动态绑定数据给子组件传输,子组件用 p r o p s 接收;子组件用 t h i s . attrs"在孙子组件上,孙子组件上就可以通过props接收。 组件通信总结 父子组件 父组件v-bind动态绑定数据给子组件传输,子组件用props接收;子组件用this. attrs"在孙子组件上,孙子组件上就可以通过props接收。组件通信总结父子组件父组件v−bind动态绑定数据给子组件传输,子组件用props接收;子组件用this.emit传递事件名和参数,父组件用v-on绑定事件接收
深层次的可以用provide,inject, 祖先组件用provide,写法类似data,子孙组件用inject,写法类似props
p a r e n t 和 parent和 parent和 c h i l d r e n 父组件用 children 父组件用 children父组件用refs访问子组件实例兄弟组件
eventbus,本质相当于创建了一个空的vue实例,其他组件引入这个实例,在这个实例上传递和监听事件。
为什么动态绑定的图片地址需要用require
因为不使用require,地址不会进行编译。首先我们知道,如果使用静态指定图片地址,进行编译后,图片指定的地址会被改变。
// vue文件中静态的引入一张图片
//最终编译的结果
//这张图片是可以被正确打开的
为什么data要是函数
当组件实例化时,如果data是对象,那么所有组件的实例就会指向同一个对象,那么一个实例数据的更改就会污染其他实例。data如果是函数,那么每次组件实例化都会调用这个函数返回新的对象。