完整版推荐在线阅读 https://poetries1.gitee.io/fe-interview
MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。
答:总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后
生命周期是什么
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期
各个生命周期的作用
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初,组件的属性生效之前 |
created | 组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用 |
beforeMount | 在挂载开始之前被调用:相关的 render 函数首次被调用 |
mounted | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子 |
beforeUpdate | 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前 |
update | 组件数据更新之后 |
activited | keep-alive专属,组件被激活时调用 |
deadctivated | keep-alive专属,组件被销毁时调用 |
beforeDestory | 组件销毁前调用 |
destoryed | 组件销毁后调用 |
由于Vue会在初始化实例时对属性执行
getter/setter
转化,所以属性必须在data
对象上存在才能让Vue
将它转换为响应式的。Vue提供了$set
方法用来触发视图更新
export default {
data(){
return {
obj: {
name: 'fei'
}
}
},
mounted(){
this.$set(this.obj, 'sex', 'man')
}
}
什么是vue生命周期?
vue生命周期的作用是什么?
vue生命周期总共有几个阶段?
8
个阶段:创建前/后、载入前/后、更新前/后、销毁前/销毁后。第一次页面加载会触发哪几个钩子?
beforeCreate
、created
、beforeMount
、mounted
。DOM 渲染在哪个周期中就已经完成?
DOM
渲染在 mounted
中就已经完成了vue
实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript
对象传给 Vue 实例来作为它的 data
选项时,Vue 将遍历它的属性,用 Object.defineProperty()
将它们转为 getter/setter
。用户看不到 getter/setter
,但是在内部它们让 Vue
追踪依赖,在属性被访问和修改时通知变化。MVVM
作为数据绑定的入口,整合Observer
,Compile
和Watcher
三者,通过Observer
来监听自己的model
的数据变化,通过Compile
来解析编译模板指令(vue
中是用来解析 {{}}
),最终利用watcher
搭起observer
和Compile
之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input
)—>数据model
变更双向绑定效果。父组件与子组件传值
父组件传给子组件:子组件通过
props
方法接受数据;
$emit
方法传递参数非父子组件间的数据传递,兄弟组件传值
eventBus
,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适(虽然也有不少人推荐直接用VUEX
,具体来说看需求)
hash
模式:在浏览器中符号“#”
,#以及#后面的字符称之为hash
,用 window.location.hash
读取。特点:hash
虽然在URL
中,但不被包括在HTTP
请求中;用来指导浏览器动作,对服务端安全无用,hash
不会重加载页面。history
模式:history
采用HTML5
的新特性;且提供了两个新方法: pushState()
, replaceState()
可以对浏览器历史记录栈进行修改,以及popState
事件的监听到状态变更首页可以控制导航跳转,
beforeEach
,afterEach
等,一般用于页面title
的修改。一些需要登录才能调整页面的重定向功能。
beforeEach
主要有3个参数to
,from
,next
。to
:route
即将进入的目标路由对象。from
:route
当前导航正要离开的路由。next
:function
一定要调用该方法resolve
这个钩子。执行效果依赖next
方法的调用参数。可以控制网页的跳转store
中; 改变状态的方式是提交mutations
,这是个同步的事物; 异步逻辑应该封装在action
中。main.js
引入store
,注入。新建了一个目录store
,… export
state
:Vuex
使用单一状态树,即每个应用将仅仅包含一个store
实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。mutations
:mutations
定义的方法动态修改Vuex
的 store
中的状态或数据getters
:类似vue
的计算属性,主要用来过滤一些数据。action
:actions
可以理解为通过将mutations
里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view
层通过 store.dispath
来分发 action
modules
:项目特别复杂的时候,可以让每一个模块拥有自己的state
、mutation
、action
、getters
,使得结构非常清晰,方便管理
v-if
按照条件是否渲染,v-show
是display
的block
或none
;$route
和$router
的区别$route
是“路由信息对象”,包括path
,params
,hash
,query
,fullPath
,matched
,name
等路由信息参数。$router
是“路由实例”对象包括了路由的跳转方法,钩子函数等将当前组件的
修改为
的作用是什么?keep-alive可以实现组件缓存,当组件切换时不会对当前组件进行卸载
包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用
进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染
include/exclude
,允许组件有条件的进行缓存activated/deactivated
,用来得知当前组件是否处于活跃状态提供一个在页面上已存在的
DOM
元素作为Vue
实例的挂载目标.可以是 CSS 选择器,也可以是一个HTMLElement
实例,
ES6
的import ... from ...
语法或CommonJS
的require()
方法引入插件Vue.use( plugin )
使用插件,可以传入一个选项对象Vue.use(MyPlugin, { someOption: true })
created
: 实例已经创建完成之后调用,在这一步,实例已经完成数据观测, 属性和方法的运算, watch/event
事件回调. 然而, 挂载阶段还没有开始, $el
属性目前还不可见mounted
: el
被新创建的 vm.$el
替换,并挂载到实例上去之后调用该钩子。如果 root
实例挂载了一个文档内元素,当 mounted
被调用时 vm.$el
也在文档内。activated
: keep-alive
组件激活时调用问题一:构建的 vue-cli 工程都到了哪些技术,它们的作用分别是什么?
vue.js
:vue-cli
工程的核心,主要特点是 双向数据绑定 和 组件系统。vue-router
:vue
官方推荐使用的路由框架。vuex
:专为 Vue.js
应用项目开发的状态管理器,主要用于维护vue
组件间共用的一些 变量 和 方法。axios
( 或者 fetch
、ajax
):用于发起 GET
、或 POST
等 http
请求,基于 Promise
设计。vuex
等:一个专为vue
设计的移动端UI组件库。emit.js
文件,用于vue
事件机制的管理。webpack
:模块加载和vue-cli
工程打包器。问题二:vue-cli 工程常用的 npm 命令有哪些?
node_modules
资源包的命令:npm install
vue-cli
开发环境的 npm命令:npm run dev
vue-cli
生成 生产环境部署资源 的 npm
命令:npm run build
vue-cli
生产环境部署资源文件大小的 npm
命令:npm run build --report
在浏览器上自动弹出一个 展示
vue-cli
工程打包后app.js
、manifest.js
、vendor.js
文件里面所包含代码的页面。可以具此优化vue-cli
生产环境部署的静态资源,提升 页面 的加载速度
nextTick
可以让我们在下次 DOM 更新循环结束之后执行延迟回调,用于获得更新后的DOM
View
)可以独立于Model
变化和修改,一个ViewModel
可以绑定到不同的"View"
上,当View变化的时候Model可以不变,当Model
变化的时候View
也可以不变ViewModel
里面,让很多view
重用这段视图逻辑ViewModel
来写声明式(标签跳转)
编程式( js跳转)
router.push('index')
其基本实现原理
app.js
作为客户端与服务端的公用入口,导出 Vue
根实例,供客户端 entry
与服务端 entry
使用。客户端 entry
主要作用挂载到 DOM
上,服务端 entry
除了创建和返回实例,还进行路由匹配与数据预获取。webpack
为客服端打包一个 Client Bundle
,为服务端打包一个 Server Bundle
。url
,加载相应组件,获取和解析异步数据,创建一个读取 Server Bundle
的 BundleRenderer
,然后生成 html
发送给客户端。DOM
与自己的生成的 DOM 进行对比,把不相同的 DOM
激活,使其可以能够响应后续变化,这个过程称为客户端激活 。为确保混合成功,客户端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 stroe
里,这样,在客户端挂载到 DOM
之前,可以直接从 store
里取数据。首屏的动态数据通过 window.__INITIAL_STATE__
发送到客户端
Vue SSR
的实现,主要就是把Vue
的组件输出成一个完整HTML
,vue-server-renderer
就是干这事的
Vue SSR
需要做的事多点(输出完整 HTML),除了complier -> vnode
,还需如数据获取填充至 HTML
、客户端混合(hydration
)、缓存等等。ejs
, jade
等),最终要实现的目的是一样的,性能上可能要差点Vue
的实例。data
属性,当 data
的值是同一个引用类型的值时,改变其中一个会影响其他data
、 Store
)的联系;实现时,主要如下
data
, 使用 Object.defineProperty
把这些属性全部转为 getter/setter
。computed
, 遍历 computed
里的每个属性,每个 computed
属性都是一个 watch
实例。每个属性提供的函数作为属性的 getter
,使用 Object.defineProperty
转化。Object.defineProperty getter
依赖收集。用于依赖发生变化时,触发属性重新计算。computed
计算属性嵌套其他 computed
计算属性时,先进行其他的依赖收集html
,最开始出现在后端,经过各种处理吐给前端。随着各种 mv*
的兴起,模板解析交由前端处理。Vue complier
是将 template
转化成一个 render
字符串。可以简单理解成以下步骤:
parse
过程,将 template
利用正则转化成AST
抽象语法树。optimize
过程,标记静态节点,后 diff
过程跳过静态节点,提升性能。generate
过程,生成 render
字符串用
timeline
工具。 大意是通过timeline
来查看每个函数的调用时常,定位出哪个函数的问题,从而能判断哪个组件出了问题
v-model
:一般用在表达输入,很轻松的实现表单控件和数据的双向绑定v-html
: 更新元素的 innerHTML
v-show
与 v-if
: 条件渲染, 注意二者区别使用了v-if的时候,如果值为false,那么页面将不会有这个html标签生成。 v-show则是不管值为true还是false,html元素都会存在,只是CSS中的display显示或隐藏
v-on
: click
: 可以简写为@click
,@
绑定一个事件。如果事件触发了,就可以指定事件的处理函数v-for
:基于源数据多次渲染元素或模板块v-bind
: 当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM
语法:
v-bind:title="msg"
简写::title="msg"
Object.defineProperty() 的问题主要有三个:
Proxy 在 ES2015 规范中被正式加入,它有以下几个特点
除了上述两点之外,Proxy 还拥有以下优势:
全局守卫
vue-router全局有三个守卫
router.beforeEach
全局前置守卫 进入路由之前router.beforeResolve
全局解析守卫(2.5.0+) 在beforeRouteEnter
调用之后调用router.afterEach
全局后置钩子 进入路由之后// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由独享守卫
如果你不想全局配置守卫的话,你可以为某些路由单独配置守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 参数用法什么的都一样,调用顺序在全局前置守卫后面,所以不会被全局守卫覆盖
// ...
}
}
]
})
路由组件内的守卫
组件之间通讯分为三种: 父传子、子传父、兄弟组件之间的通讯
1. 父组件给子组件传值
props
,父组件可以使用props
向子组件传递数据。vue
模板father.vue
:<template>
<child :msg="message">child>
template>
<script>
import child from './child.vue';
export default {
components: {
child
},
data () {
return {
message: 'father message';
}
}
}
script>
子组件vue模板child.vue:
<template>
<div>{{msg}}div>
template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
}
}
script>
2. 子组件向父组件通信
父组件向子组件传递事件方法,子组件通过
$emit
触发事件,回调给父组件
父组件vue模板father.vue:
<template>
<child @msgFunc="func">child>
template>
<script>
import child from './child.vue';
export default {
components: {
child
},
methods: {
func (msg) {
console.log(msg);
}
}
}
script>
子组件vue模板child.vue:
<template>
<button @click="handleClick">点我button>
template>
<script>
export default {
props: {
msg: {
type: String,
required: true
}
},
methods () {
handleClick () {
//........
this.$emit('msgFunc');
}
}
}
script>
3. 非父子, 兄弟组件之间通信
vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样:
import Vue from 'vue'
export default new Vue()
在需要通信的组件都引入Bus.js:
<template>
<button @click="toBus">子组件传给兄弟组件button>
template>
<script>
import Bus from '../common/js/bus.js'
export default{
methods: {
toBus () {
Bus.$emit('on', '来自兄弟组件')
}
}
}
script>
另一个组件也import Bus.js 在钩子函数中监听on事件
import Bus from '../common/js/bus.js'
export default {
data() {
return {
message: ''
}
},
mounted() {
Bus.$on('on', (msg) => {
this.message = msg
})
}
}
Vue与AngularJS的区别
Vue与React的区别
vuex的使用借助官方提供的一张图来说明:
Vuex有5种属性: 分别是 state、getter、mutation、action、module;
state
Vuex
使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据
mutations
mutations
定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据
action
一般面试官问到这里vue基本知识就差不多了, 如果更深入的研究就是和你探讨关于vue的底层源码;或者是具体在项目中遇到的问题,下面列举几个项目中可能遇到的问题:
computed:
watch:
小结:
利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,然后根据变化进行后续响应,在vue3.0中通过Proxy代理对象进行类似的操作。
// 这是将要被劫持的对象
const data = {
name: '',
};
function say(name) {
if (name === '古天乐') {
console.log('给大家推荐一款超好玩的游戏');
} else if (name === '渣渣辉') {
console.log('戏我演过很多,可游戏我只玩贪玩懒月');
} else {
console.log('来做我的兄弟');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
},
});
});
data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月
Vue 采用数据劫持结合发布—订阅模式的方法,通过 Object.defineProperty() 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Observer
遍历数据对象,给所有属性加上 setter
和 getter
,监听数据的变化compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情
dep
) 里面添加自己dep.notice()
通知时,调用自身的 update()
方法,并触发 Compile
中绑定的回调Vue3.x响应式数据原理
Vue3.x
改用Proxy
替代Object.defineProperty
。因为Proxy
可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy
只会代理对象的第一层,那么Vue3
又是怎样处理这个问题的呢?
判断当前
Reflect.get的
返回值是否为Object
,如果是则再通过reactive
方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断
key
是否为当前被代理对象target
自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger
v-model
本质上是语法糖,v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件
text
和 textarea
元素使用 value 属性和 input 事件checkbox
和 radio
使用 checked 属性和 change 事件select
字段将 value 作为 prop 并将 change 作为事件所以我们可以v-model进行如下改写:
<input v-model="sth" />
// 等同于
<input :value="sth" @input="sth = $event.target.value" />
value
,方法名必须为:input
。v-model
的原理,我们可以在自定义组件上实现v-model
//Parent
{{num}}
export default {
data(){
return {
num: 0
}
}
}
//Child
Add
export default {
props: ['value'],
methods:{
add(){
this.$emit('input', this.value + 1)
}
}
}
scoped
虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped
属性
/deep/
//Parent
//Child
//Parent
//Child
dom
元素this.$refs.box
datathis.$refs.box.msg
this.$refs.box.open()
Computed
本质是一个具备缓存的watcher
,依赖的属性发生变化就会更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理
<template>{{fullName}}</template>
export default {
data(){
return {
firstName: 'xie',
lastName: 'yu fei',
}
},
computed:{
fullName: function(){
return this.firstName + ' ' + this.lastName
}
}
}
watch
用于观察和监听页面上的vue实例,如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch
为最佳选择
Watch
没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true
选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch
手动注销
{{fullName}}
export default {
data(){
return {
firstName: 'xie',
lastName: 'xiao fei',
fullName: 'xie xiao fei'
}
},
watch:{
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
}
}
}
导航守卫
router.beforeEach
全局前置守卫
to: Route
: 即将要进入的目标(路由对象)from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve
这个钩子。(一定要用这个函数才能去到下一个路由,如果不用就拦截)next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
:取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由独享的守卫 你可以在路由配置上直接定义
beforeEnter
守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫你可以在路由组件内直接定义以下路由导航守卫
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用,我们用它来禁止用户离开
// 可以访问组件实例 `this`
// 比如还未保存草稿,或者在用户离开前,
将setInterval销毁,防止离开之后,定时器还在调用。
}
}
stop
:阻止事件的冒泡prevent
:阻止事件的默认行为once
:只触发一次self
:只触发自己的事件行为时,才会执行extend
是构造一个组件的语法器。Vue.component
你可以创建 ,也可以取组件。当时的思路是头部(Header)一般分为左、中、右三个部分,分为三个区域来设计,中间为主标题,每个页面的标题肯定不同,所以可以通过vue props的方式做成可配置对外进行暴露,左侧大部分页面可能都是回退按钮,但是样式和内容不尽相同,右侧一般都是具有功能性的操作按钮,所以左右两侧可以通过vue slot插槽的方式对外暴露以实现多样化,同时也可以提供default slot默认插槽来统一页面风格
Proxy的优势如下:
Object.defineProperty的优势如下:
兼容性好,支持IE9
响应式系统简述:
现代前端框架有两种方式侦测变化,一种是pull一种是push
考点: Vue的变化侦测原理
前置知识: 依赖收集、虚拟DOM、响应式系统
根本原因是Vue与React的变化侦测方式有所不同
diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.
准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.
快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n)
,Map
的时间复杂度仅仅为O(1)
.
代码层面:
v-if
和 v-show
computed
和 watch
的使用v-for
遍历为 item
添加 key
v-for
遍历避免同时使用 v-if
addEventListener
添加的事件在组件销毁时要用 removeEventListener
手动移除这些事件的监听SSR
服务端渲染,首屏加载速度快,SEO
效果好Webpack 层面优化:
CommonsChunkPlugin
插件提取公共代码SourceMap
webpack-bundle-analyzer
可视化分析工具
nextTick
可以让我们在下次DOM
更新循环结束之后执行延迟回调,用于获得更新后的DOM
nextTick
主要使用了宏任务和微任务。根据执行环境分别尝试采用
Promise
MutationObserver
setImmediate
setTimeout
定义了一个异步方法,多次调用
nextTick
会将方法存入队列中,通过这个异步方法清空当前队列
使用了函数劫持的方式,重写了数组的方法,
Vue
将data
中的数组进行了原型链重写,指向了自己定义的数组原型方法。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
接口请求一般放在
mounted
中,但需要注意的是服务端渲染时不支持mounted
,需要放到created
中
一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。如果
data
是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data
不冲突,data必须是一个函数
v-model
本质就是一个语法糖,可以看成是value + input
方法的语法糖。 可以通过model
属性的prop
和event
属性来进行自定义。原生的v-model
,会根据标签的不同生成不同的事件和属性
原生事件绑定是通过
addEventListener
绑定给真实元素的,组件事件绑定是通过Vue
自定义的$on
实现的
简单说,
Vue
的编译过程就是将template
转化为render
函数的过程。会经历以下阶段:
生成AST
树
优化
codegen
首先解析模版,生成AST
语法树(一种用JavaScript
对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue
的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的DOM也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST
树转换为可执行的代码
简单来说,
diff
算法有以下过程
同级比较,再比较子节点
先判断一方有子节点一方没有子节点的情况(如果新的children
没有子节点,将旧的子节点移除)
比较都有子节点的情况(核心diff
)
递归比较子节点
正常Diff
两个树的时间复杂度是O(n^3)
,但实际情况下我们很少会进行跨层级的移动DOM
,所以Vue
将Diff
进行了优化,从O(n^3) -> O(n)
,只有当新旧children
都为多个子节点时才需要用核心的Diff
算法进行同层级比较。
Vue2
的核心Diff
算法采用了双端比较的算法,同时从新旧children
的两端开始进行比较,借助key
值找到可复用的节点,再进行相关操作。相比React
的Diff
算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅
在创建VNode
时就确定其类型,以及在mount/patch
的过程中采用位运算来判断一个VNode
的类型,在这个基础之上再配合核心的Diff
算法,使得性能上较Vue2.x
有了提升
DOM
是很昂贵的。频繁的操作DOM
,会产生一定的性能问题。这就是虚拟Dom的产生原因Virtual DOM
本质就是用一个原生的JS对象去描述一个DOM
节点。是对真实DOM的一层抽象VirtualDOM
映射到真实DOM要经历VNode
的create
、diff
、patch
等阶段key的作用是尽可能的复用 DOM 元素
children
中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的children
的节点中保存映射关系,以便能够在旧 children
的节点中找到可复用的节点。key
也就是children
中节点的唯一标识加载渲染过程
父beforeCreate
->父created
->父beforeMount
->子beforeCreate
->子created
->子beforeMount
- >子mounted
->父mounted
子组件更新过程
父beforeUpdate
->子beforeUpdate
->子updated
->父updated
父组件更新过程
父 beforeUpdate
->父 updated
销毁过程
父beforeDestroy
->子beforeDestroy
->子destroyed
->父destroyed
SSR
也就是服务端渲染,也就是将Vue
在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
SSR
有着更好的SEO
、并且首屏加载速度更快等优点。不过它也有一些缺点,比如我们的开发条件会受到限制,服务器端渲染只支持beforeCreate
和created
两个钩子,当我们需要一些外部扩展库时需要特殊处理,服务端渲染应用程序也需要处于Node.js
的运行环境。还有就是服务器会有更大的负载需求
编码阶段
data
中的数据,data
中的数据都会增加getter
和setter
,会收集对应的watcher
v-if
和v-for
不能连用v-for
给每项元素绑定事件时使用事件代理SPA
页面采用keep-alive
缓存组件v-if
替代v-show
key
保证唯一SEO优化
SSR
打包优化
Tree Shaking/Scope Hoisting
cdn
加载第三方模块happypack
splitChunks
抽离公共文件sourceMap
优化用户体验
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启
gzip
压缩等。
HTML
模板+Json数据+Vue
实例组成assets
文件夹是放静态资源;components
是放组件;router
是定义路由相关的配置;view
视图;app.vue
是一个应用主组件;main.js
是入口文件query
方法传入的参数使用this.$route.query
接受params
方式传入的参数使用this.$route.params
接受
Vuex
是一个专为Vue.js
应用程序开发的状态管理模式。- 有 5 种,分别是
state
、getter
、mutation
、action
、module
Vuex
是一个专为 Vue.js
应用程序开发的状态管理模式。state
、getter
、mutation
、action
、module
vuex
的 store
是什么?vuex
就是一个仓库,仓库里放了很多对象。其中 state
就是数据源存放地,对应于一般 vue 对象里面的 datastate
里面存放的数据是响应式的,vue
组件从 store
读取数据,若是 store
中的数据发生改变,依赖这相数据的组件也会发生更新它通过 mapState
把全局的 state
和 getters
映射到当前组件的 computed
计算属性vuex 的 getter 是什么?
getter
可以对 state
进行计算操作,它就是 store
的计算属性虽然在组件内也可以做计算属性,但是 getters
可以在多给件之间复用如果一个状态只在一个组件内使用,是可以不用 getters
vuex 的 mutation 是什么?
Vuex
的store
中的状态的唯一方法是提交mutation
vuex 的 action 是什么?
action
类似于 muation
, 不同在于:action
提交的是 mutation
,而不是直接变更状态action
可以包含任意异步操作vue
中 ajax
请求代码应该写在组件的 methods
中还是 vuex
的 action
中vuex
的 module
是什么?面对复杂的应用程序,当管理的状态比较多时;我们需要将
vuex
的store
对象分割成模块(modules
)。
如果请求来的数据不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入
vuex
的state
里如果被其他地方复用,请将请求放入action
里,方便复用,并包装成promise
返回
将当前组件的
修改为
delete
只是被删除的元素变成了 empty/undefined
其他的元素的键值还是不变。Vue.delete
直接删除了数组 改变了数组的键值。var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
可以
<input type="text" :value="name" @input="onInput" @focus="onFocus" @blur="onBlur" />
v-on 常用修饰符
.stop
该修饰符将阻止事件向上冒泡。同理于调用 event.stopPropagation()
方法.prevent
该修饰符会阻止当前事件的默认行为。同理于调用 event.preventDefault()
方法.self
该指令只当事件是从事件绑定的元素本身触发时才触发回调.once
该修饰符表示绑定的事件只会被触发一次this.$parent.event
来调用父组件的方法$emit
向父组件触发一个事件,父组件监听这个事件就行了。babel-polyfill插件
以下方法调用会改变原始数组:
push()
,pop()
,shift()
,unshift()
,splice()
,sort()
,reverse()
,Vue.set( target, key, value )
Vue.set( target, key, value )
target
:要更改的数据源(可以是对象或者数组)key
:要更改的具体数据value
:重新赋的值在
mounted
注意 mounted
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick
替换掉 mounted
mounted: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been rendered
})
}
beforecreate
: 可以在这加个loading
事件,在加载实例时触发created
: 初始化完成时的事件写在这里,如在这结束loading
事件,异步请求也适宜在这里调用mounted
: 挂载元素,获取到DOM节点 updated
: 如果对数据统一处理,在这里写上相应函数beforeDestroy
: 可以做一个确认停止事件的确认框第一次加载会触发哪几个钩子
会触发
beforeCreate
,created
,beforeMount
,mounted
active
classname
,isActive
变量
<div :class="{ active: isActive }">div>