Vue
vue的特点
- 简单易学:国⼈开发,中⽂⽂档,不存在语⾔障碍 ,易于理解和学习;
- 灵活:它可以与已知第三方工具(vuex),第三方Ui库(elementUi)(vant),结合使用
- 高效:虚拟dom,diff算法,大大的提升了页面的运行效率
- 轻量级框架:只关注视图层,是⼀个构建数据的视图集合,⼤⼩只有⼏⼗kb
- 双向数据绑定:在数据操作方面更为简单
- 组件化:实现了html的封装和重⽤,在构建单⻚⾯应⽤⽅⾯有着独特的优势;
- 视图,数据,结构分离:使数据的更改更为简单,不需要进⾏逻辑代码的修改,只需要操作数据就能完成相关操作;
vue的核心概念
数据驱动:ViewModel,保证数据和视图的一致性
组件系统:应用类UI可以看作全部是由组件树构成的
MVVM / Vue的三要素
响应式:vue 如何监听到 data 的每个属性变化?
模板引擎:vue 的模板如何被解析,指令如何处理?
渲染:vue 的模板如何被渲染成 html ?以及渲染过程
MVVM
单向绑定:把Model绑定到View,用JavaScript代码更新Model时,View就会自动更新。不需要进行额外的DOM操作,只需要进行Model的操作就可以实现视图的联动更新。
双向绑定:把Model绑定到View的同时也将View绑定到Model上,这样就既可以通过更新Model来实现View的自动更新,也可以通过更新View来实现Model数据的更新。
MVVM:分离由js对象表示的model模型层和view视图层,两者之间通过viewmodel交互,Model 和 ViewModel 之间的交互是双向的,通过数据来驱动视图,只需要关心数据变化,DOM操作被封装。因此View的变化会同步到Model中,而Model的变化也会立即反应到View 上。
MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新。
响应式原理
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter在数据变动时发布消息给订阅者,触发相应的监听回调,当属性被访问或改变时通知变化,而产生更新数据和视图。
data属性定义了getter、setter对属性进行劫持,当属性值改变就会notify通知watch对象,而watch对象则会重新触发组件呈现功能,继而更新view上的DOM节点树。
反之,view上输入数据时,也会触发data变更,也会触发订阅者watch更新,这样子model数据就可以实时更新view上的数据变化。这样一个过程就是vue的数据双向绑定了
、
双向绑定
采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter重新定义了对象获取属性值get()和设置属性值set()的操作来实现的。在数据变动时发布消息给订阅者,触发相应的监听回调,当属性被访问或改变时通知变化,而产生更新数据和视图。
- 需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter
这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化 - compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
- Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁,主要做的事情是:在自身实例化时往属性订阅器(dep)里面添加自己。自身必须有一个 update()方法。待属性变动 dep.notice()通知时,能调用自身的 update() 方法,并触发 Compile 中绑定的回调,则功成身退。
- MVVM 作为数据绑定的入口,整合 Observer、Compile 和 Watcher 三者,通过 Observer 来监听自己的model 数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据 model 变更的双向绑定效果。
Vue的系统指令
- v-text可以将一个变量的值渲染到指定的元素中,如果数据中有HTML标签会将html标签一并输出
- v-text是纯文本,而v-html可以将HTML片段填充到标签中
- v-bind:用于绑定属性并响应地更新 HTML 属性。只能实现数据的单向绑定,从 M 自动绑定到 V
- v-on:事件绑定机制
- v-model:双向数据绑定
- v-for:根据数组中的元素遍历指定模板内容生成内容。
- v-if:设置元素的显示和隐藏(添加/删除DOM元素)
作用:根据表达式的值的真假条件,来决定是否渲染元素,如果为false则不渲染(达到隐藏元素的目的),如果为true则渲染。 - v-show:设置元素的显示和隐藏(在元素上添加/移除style="display:none"属性)
- v-pre:显示原始信息跳过编译过程,跳过这个元素和它的子元素的编译过程。一些静态的内容不需要编译加这个指令可以加快渲染
- v-once:执行一次性的插值【当数据改变时,插值处的内容不会继续更新】
v-if/f-for的优先级,v-for就地复用原理
v-for和v-if不应该一起使用,必要情况下应该替换成computed属性。
<li v-for="item in Nums" v-if="item%2==0">{{item}}->偶数</li>
原因:v-for比v-if优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。会影响性能增加渲染时长,我们可以在渲染之前把数据处理好:
filters: {
Nums2: function(value) {
return value.filter(i => i%2===0)
}
}
// 或者
computed: {
newNums() {
return Nums.filter(i => i%2===0)
}
}
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略。如果数据项的顺序被改变,Vue将不是移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
这个默认的模式是有效的,但是只适用于不依赖子组件状态或临时 DOM 状态(例如:表单输入值)的列表渲染输出。
v-if与v-show的区别
v-if和v-show都能够实现对一个元素的隐藏和显示操作。
区别:
- v-if:每次都会重新添加/删除DOM元素
- v-show:每次不会重新进行DOM的添加/删除操作,只是在这个元素上添加/移除style="display:none"属性,表示节点的显示和隐藏。
优缺点:
- v-if:有较高的切换性能消耗。这个很好理解,毕竟每次都要进行dom的添加/删除操作。
- v-show:有较高的初始渲染消耗。也就是说,即使一开始v-show=“false”,该节点也会被创建,只是隐藏起来了。而v-if="false"的节点,根本就不会被创建。v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
总结:
- 如果元素涉及到频繁的切换,最好不要使用 v-if, 而是推荐使用 v-show
- 如果元素可能永远也不会被显示出来被用户看到,则推荐使用 v-if
Vue 允许为 v-on 在监听键盘事件时添加按键修饰符
常用的按键修饰符
.enter => enter键
.tab => tab键
.delete (捕获“删除”和“退格”按键) => 删除键
.esc => 取消键
.space => 空格键
.up => 上
.down => 下
.left => 左
.right => 右
Vue 中为什么使用key?
- 第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当我们使用v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此我们可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。
- 第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元 素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM
用key来给每个节点做一个唯一标识
Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点,可以高效的更新虚拟DOM。
vue生命周期钩子函数
Vue实例从创建 到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数
创建期间的生命周期函数
- beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
- created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始编译模板。我们可以在这里进行Ajax请求。
- beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
- mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示。(mounted之后,表示真实DOM渲染完了,可以操作DOM了)
运行期间的生命周期函数
- beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点
- updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了。
销毁期间的生命周期函数
- beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
- destroyed:Vue 实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
什么时候需要使用beforeDestroy
- 可能在当前页面中使用了$on方法,那需要在组件销毁前解绑
- 清除自己定义的定时器
- 解绑事件的绑定srcollmousemove…
vue中created、mounted、computed、methods区别与执行顺序
- computed是在DOM执行完成后立马执行(如:赋值)
- created执行时挂载阶段还没有开始,模版还没有渲染成html,所以无法获取元素。created钩子函数主要用来初始化数据。
- mounted钩子函数一般用来向后端发起请求,拿到数据后做一些业务处理。该函数在模版渲染完成后才被调用。DOM操作一般是mounted钩子函数中进行。
- methods方法有一定的触发条件,如click等。
- watch用于检测vue实例上数据的变动
- 默认加载的时候先computed再watch,不执行methods;等触发某一事件后,则是:先methods再watch。所有方法都应该在methods里定义,在mounted或created里面使用this调用,用这种方法实现初始化
1.beforeCreate
在这个钩子函数前主要是对Vue实例进行属性的初始化
生命周期状态、监听器的初始化,这些与业务逻辑无关
处理父组件对本组件的自定义事件的监听,将其放到**_events**中
对创建VNode函数进行绑定,在Vue实例对象中,是不会存在直接对DOM进行操作的,而是通过创建VNode,然后通过patch的方式对DOM节点进行操作,操作的工具方法什么的跟原生一样的,Vue工具类里面包含对这些方法的封装。
对父组件传进来的属性(props)以及监听事件(v-on)进行深度观察
2.created
在这个钩子函数之前主要是对数据进行初始化并且进行监听操作:
Injection、provide选项进行初始化
对于用户定义的属性进行初始化,以下是按顺序来初始化,如果后面的属性名与前面相同的话,会报错:
props:传进来的属性是父组件过来的,这个属性不需要在子组件里面进行观察(因为父组件已经观察它了)
method:对设定的方法挂载到vm实例中,并且使用bind方法绑定函数上下文为vm对象,这也就是为什么我们使用箭头函数的方式的时候,也是能够通过this访问vm实例。
data:在选项的合并的时候将data处理成一个可执行的函数,这里的话,是将这个函数进行执行,得到组件的数据,并且将所有数据进行深度观测。
computed:创建一个lazy型的观察者,这个观察者是不会主动去触发的(如果计算属性是函数的话,每次需要拿到值的时候进行调用函数,如果是普通值的话,则会直接获取普通值)。这个计算属性是不能直接赋值操作的(getter方法被设置成一个空函数,所以怎么进行赋值也没有用),所以是个lazy型的观察者。computed的值只有在获取的时候才会执行然后拿到最新的值,所以它是一个懒求值。
watch:对传进来观察的对象的路径进行解析,拿到对应的属性。创建一个观察者,对这个属性进行观察,一旦属性发生变化的时候,会触发这个观察者,调用传进来的函数。
3.beforeMount
在挂载前的生命钩子前会进行元素绑定以及渲染函数的操作:
vm.$el属性赋值
渲染函数如果为空,进行创建空的方法
4.mounted
在本生命周期前,会进行渲染函数观察者的设定,当属性改变的时候,会直接反映到页面数据中。渲染函数观察者为什么要在数据观察者设定后进行创建的话,这个等到后面的讲述Vue的观察者篇章的时候再说。
这里要注意到一点,在创建观察者的时候,我们可以通过传给观察者一个before方法,这样执行观察者的函数前调用before方法。渲染函数观察者会传进一个before方法,这个方法只有一句话,就是将本组件的声明周期改为beforeUpdate,也就是说在修改数据的时候,反映到页面之前,组件会先进入到beforeUpdate生命周期。
5.beforeUpdate
上面一点也说明了,在beforeUpdate前是用户的各项操作,只要修改了某个属性值,那么就会触发这个生命周期钩子。
6.updated
触发beforeUpdate后,这时候是在清空调用观察者队列(queue是存放现在时间所有需要进行回调的观察者的一个队列,当然渲染函数观察者也在里面)。整个队列执行完毕后会进行调用这个updated生命周期函数,在调用这个生命周期函数的时候,页面重新渲染已经结束了,所以在updated生命周期钩子函数前会做以下事情:
打补丁(patch函数),这个函数首先是使用diff算法实现对dom操作的最小化,然后diff算法完成后,使用方法来针对于VNode修改dom节点内容。
重新绑定修改后的dom节点,你们可以在绑定vm对象的dom节点的属性上看到**__vue__这个属性,这个就是指向绑定的vm**。
7.beforeDestroy
仅仅是判断是否正在被destroy,使用变量锁(单线程下变量即可作为锁,无需使用原子操作)防止重复操作。
8.destroyed
这里主要是对事件、观察者的卸载,在这个生命周期钩子函数前会执行以下事情:
观察者的卸载(computed、watch、render)三个观察者进行卸载。
对根数据的观察者的卸载。
对VNode进行卸载,VNode的卸载会引发dom节点的删除。
9.destroyed之后
这里对于组件的产生的事件、监听的事情进行卸载,以及将对应的dom节点的**vue**属性设空,最后对父亲节点的解绑。。
组件传值
父组件向子组件传值
- 父组件发送的形式是以属性的形式绑定值到子组件身上。
- 然后子组件用属性props接收
- 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
<div id="app">
<div>{{pmsg}}</div>
<!--1、menu-item 在 APP中嵌套着 故 menu-item 为 子组件 -->
<!-- 给子组件传入一个静态的值 -->
<menu-item title='来自父组件的值'></menu-item>
<!-- 2、 需要动态的数据的时候 需要属性绑定的形式设置 此时 ptitle 来自父组件data 中的数据 .
传的值可以是数字、对象、数组等等
-->
<menu-item :title='ptitle' content='hello'></menu-item>
</div>
<script type="text/javascript">
Vue.component('menu-item', {
// 3、 子组件用属性props接收父组件传递过来的数据
props: ['title', 'content'],
data: function() {
return {
msg: '子组件本身的数据'
}
},
template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
ptitle: '动态绑定属性'
}
});
</script>
子组件向父组件传值 $emit+ v-on
- 子组件用
$emit()
触发事件 $emit()
第一个参数为 自定义的事件名称 第二个参数为需要传递的数据- 父组件用v-on 监听子组件的事件
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<!-- 2 父组件用v-on 监听子组件的事件
这里 enlarge-text 是从 $emit 中的第一个参数对应 handle 为对应的事件处理函数
-->
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
子组件向父组件传值-携带参数
*/
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
### 1、子组件用$emit()触发事件
### 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
<button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple','orange','banana'],
fontSize: 10
},
methods: {
handle: function(val){
// 扩大字体大小
this.fontSize += val;
}
}
});
</script>
兄弟之间的传递 通过eventBus向中心事件发送或者接收事件,所有事件都可以共用事件中心
- 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
- 提供事件中心 var hub = new Vue()
- 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
- 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
- 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
/*
兄弟组件之间数据传递
*/
//1、 提供事件中心
var hub = new Vue();
Vue.component('test-tom', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function(){
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件
hub.$emit('jerry-event', 2);
}
},
mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on(方法名
hub.$on('tom-event', (val) => {
this.num += val;
});
}
});
Vue.component('test-jerry', {
data: function(){
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function(){
//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据) 触发兄弟组件的事件
hub.$emit('tom-event', 1);
}
},
mounted: function() {
// 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
hub.$on('jerry-event', (val) => {
this.num += val;
});
}
});
var vm = new Vue({
el: '#app',
data: {
},
methods: {
handle: function(){
//4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
hub.$off('tom-event');
hub.$off('jerry-event');
}
}
});
</script>
Vuex
是一个专为 Vue.js 应用程序开发的状态管理器,采用集中式存储管理应用的所有组件的状态,主要是为了多页面、多组件之间的通信。
Vuex有5个重要的属性,分别是 State、Getter、Mutation、Action、Module,由 view 层发起一个 Action 给 Mutation,在 Mutation 中修改状态,返回新的状态,通过 Getter暴露给 view层的组件或者页面,页面监测到状态改变于是更新页面。如果你的项目很简单,最好不要使用 Vuex,对于大型项目,Vuex 能够更好的帮助我们管理组件外部的状态,一般可以运用在购物车、登录状态、播放等场景中。
mutation action区别
Mutation:专注于修改State,理论上是修改State的唯一途径。必须同步执行
Action:业务代码、异步请求。可以异步,但不能直接操作State。
computed和watch有什么区别
- computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。
- watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
- computed 是计算一个新的属性,并将该属性挂载到 Vue 实例上
- 而 watch 是监听已经存在且已挂载到 Vue 实例上的数据,所以用 watch 同样可以监听 computed 计算属性的变化。
- 只有当依赖变化后,第一次访问computed 属性,才会计算新的值。
- 而 watch 则是当数据发生变化便会调用执行函数。
- computed 适用一个数据被多个数据影响
- watch 适用一个数据影响多个数据
1.解释vue虚拟dom原理
(js操作dom速度很慢,操作js对象速度很快,把节点映射为一个js对象,此时js对象为虚拟dom,真正的dom位于哪个节点上,虚拟dom为映射的一个js对象,然后统一的操作dom。 )
每一个vnode都映射到一个真实的dom节点上。其中几个比较重要的属性:
tag 属性即这个vnode的标签属性
data 属性包含了最后渲染成真实dom节点后,节点上的class,attribute,style以及绑定的事件
children 属性是vnode的子节点
text 属性是文本属性
elm 属性为这个vnode对应的真实dom节点
key 属性是vnode的标记,在diff过程中可以提高diff的效率,后文有讲解
比如,我定义了一个vnode,它的数据结构是:
{
tag: 'div'
data: {
id: 'app',
class: 'page-box'
},
children: [
{
tag: 'p',
text: 'this is demo'
}
]
}
最后渲染出的实际的dom结构就是:
<div id="app" class="page-box">
<p>this is demo</p>
</div>
2.解释双向数据绑定
首先要对数据进行劫持监听,所以我们首先要设置一个监听器Observer,用来监听所有的属性,当属性变化时,就需要通知订阅者Watcher,看是否需要更新.因为属性可能是多个,所以会有多个订阅者,故我们需要一个消息订阅器Dep来专门收集这些订阅者,并在监听器Observer和订阅者Watcher之间进行统一的管理.以为在节点元素上可能存在一些指令,所以我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令初始化成一个订阅者Watcher,并替换模板数据并绑定相应的函数,这时候当订阅者Watcher接受到相应属性的变化,就会执行相对应的更新函数,从而更新视图.
整理上面的思路,我们需要实现三个步骤,来完成双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
3.解释render作用
在vue中我们使用模板HTML语法组建页面的,使用render函数我们可以用js语言来构建DOM
4.解释slot作用
目的是用在内容分发
在组件添加一个占位的空间,在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动替换组件模板中
6.VUE生命周期解释
每一个组件或者实例都会经历一个完整的生命周期,总共分为三个阶段:初始化、运行中、销毁。
实例、组件通过new Vue() 创建出来之后会初始化事件和生命周期,然后就会执行beforeCreate钩子函数,这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据和真实的dom,一般不做操作
挂载数据,绑定事件等等,然后执行created函数,这个时候已经可以使用到数据,也可以更改数据,在这里更改数据不会触发updated函数,在这里可以在渲染前倒数第二次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取
接下来开始找实例或者组件对应的模板,编译模板为虚拟dom放入到render函数中准备渲染,然后执行beforeMount钩子函数,在这个函数中虚拟dom已经创建完成,马上就要渲染,在这里也可以更改数据,不会触发updated,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取
接下来开始render,渲染出真实dom,然后执行mounted钩子函数,此时,组件已经出现在页面中,数据、真实dom都已经处理好了,事件都已经挂载好了,可以在这里操作真实dom等事情…
当组件或实例的数据更改之后,会立即执行beforeUpdate,然后vue的虚拟dom机制会重新构建虚拟dom与上一次的虚拟dom树利用diff算法进行对比之后重新渲染,一般不做什么事儿
当更新完成后,执行updated,数据已经更改完成,dom也重新render完成,可以操作更新后的虚拟dom
当经过某种途径调用$destroy方法后,立即执行beforeDestroy,一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等等
组件的数据绑定、监听…去掉后只剩下dom空壳,这个时候,执行destroyed,在这里做善后工作也可以
深入理解响应式原理:
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
vue组件中的data为什么必须是一个函数?
如果不用函数的话,实例化的组件是共享同样的一个data对象,当你修改一个属性的时候,data也改变。
当data是一个函数的时候,每个实例的data属性都是独立的,不会相互影响。
虚拟DOM
首先对我们将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后我们将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。
当页面的状态发生改变,我们需要对页面的 DOM 的结构进行调整的时候,我们首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。
最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。
vue常用的修饰符
.prevent: 提交事件不再重载页面
.stop: 阻止单击事件冒泡
.self: 当事件发生在该元素本身而不是子元素的时候会触发
.capture: 事件侦听,事件发生的时候会调用
Vue的渲染过程
1、把模板编译为render函数
2、实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
3、对比虚拟dom,渲染到真实dom
4、组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3
Vue中的模板编译原理
将template转化成render函数
对 Vue 项目可以进行哪些优化?
代码层面的优化:
v-if 和 v-show 区分使用场景
computed 和watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
Webpack 层面的优化:
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化
基础的 Web 技术的优化:
1.开启gzip 压缩
2.浏览器缓存
3.CDN的使用
4.使用 Chrome Performance 查找性能瓶颈