虛擬DOM
為啥要用银舱?
DOM操作開(kāi)銷(xiāo)大,虛擬DOM提供一種方便的工具捆蜀,保證開(kāi)發(fā)效率,保證最小化DOM操作
用Js對(duì)象虛擬DOM樹(shù)結(jié)構(gòu)莹桅,當(dāng)頁(yè)面狀態(tài)發(fā)生變化需要操作DOM時(shí),先操作虛擬DOM計(jì)算出對(duì)真實(shí)DOM最小修改量,然后再修改真實(shí)DOM結(jié)構(gòu),保證最小化DOM操作
虛擬DOM是純粹JS對(duì)象瘤袖,操作高效,但最終會(huì)轉(zhuǎn)換成真實(shí)DOM昂验,因此需要一套高效的虛擬DOM diff算法
Vue的diff基于snabbdom捂敌,僅僅在同級(jí)的DOM間做diff, 遞歸的進(jìn)行同級(jí)DOM的diff昭娩,最終實(shí)現(xiàn)整個(gè)DOM樹(shù)更新
Vue的diff實(shí)現(xiàn)
oldStart+oldEnd, newStart+newEnd,分別對(duì)應(yīng)oldVdom和newVdom起止點(diǎn)黍匾。Vue不斷對(duì)虛擬DOM處理同時(shí)移動(dòng)指針直到其中任一對(duì)起點(diǎn)和終點(diǎn)相遇,處理過(guò)的節(jié)點(diǎn)會(huì)在oldVdom和newVdom中同時(shí)標(biāo)記為已處理呛梆,整個(gè)過(guò)程逐步找到更新前后vnode差異锐涯,然后將差異反應(yīng)到DOM樹(shù)(就是patch),Vue的patch是即時(shí),并非打包所有修改最后一起操作DOM(React是將更新放入隊(duì)列后集中處理)
1.優(yōu)先處理特殊場(chǎng)景填物,頭部同類(lèi)型節(jié)點(diǎn)纹腌,尾部同類(lèi)型節(jié)點(diǎn)
1.原地復(fù)用,Vue盡可能復(fù)用DOM滞磺,盡可能不發(fā)生DOM移動(dòng)(key管理可復(fù)用元素)
問(wèn)題:
1.內(nèi)存:虛擬DOM需要在內(nèi)存中維護(hù)一份DOM副本升薯,在DOM更新速度和使用內(nèi)存間取得平衡
2.適合一次大量更新虛擬DOM,但單一頻繁跟新击困,虛擬DOM會(huì)花費(fèi)更多時(shí)間處理計(jì)算工作涎劈。如頁(yè)面DOM節(jié)點(diǎn)相對(duì)少,用虛擬DOM可能會(huì)更慢阅茶,但對(duì)于大多數(shù)單頁(yè)應(yīng)用蛛枚,應(yīng)該會(huì)更快
實(shí)例生命周期
beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed
create, mount, update, destroy
beforeCreate:$el,$data全部undefined
created:$el,undefine, $data初始化
beforeMount: $el, $data初始化
Mount:
模板
模板編譯為虛擬DOM渲染函數(shù)(render函數(shù))
狀態(tài)改變,計(jì)算渲染組件最小代價(jià)更新DOM
v-bind:href="url"縮寫(xiě):href
v-on:click="funClick"縮寫(xiě)@click
計(jì)算屬性
var app = new Vue({
data:{
message:66
},
computed:{
myMessage:function(){return this.message},
myMessage2:{
get:function(){return this.message},
set:function(newValue){this.message = newValue;}
}
},
watch:{
message:function(newValue, oldValue){}
}
});
v-if元素惰性渲染(直到條件為true,才渲染)脸哀,更高切換開(kāi)銷(xiāo)
v-show元素總被渲染蹦浦,更高初始渲染開(kāi)銷(xiāo)
v-for默認(rèn)使用就地復(fù)用策略跟新已渲染元素列表,為追蹤每個(gè)節(jié)點(diǎn)撞蜂,需要為每項(xiàng)提供一個(gè)唯一key盲镶,優(yōu)先級(jí)高于v-if
<li v-for="(item, key, index) in items" :key="index">{{item + key +index}}</li>
<!-- 組件有自己獨(dú)立作用域,傳遞數(shù)據(jù)到組件要用props蝌诡,避免耦合溉贿,組件v-for必綁定key -->
<my-component v-for="item in items" :item="item" :key="item.id"></my-component>
數(shù)組更新檢測(cè)
Vue重寫(xiě)了數(shù)組的push, pop, shift, unshift, splice, sort, reverse會(huì)觸發(fā)視圖更新
filter,concat,slice等返回新數(shù)組,可用新數(shù)組賦值替換送漠,觸發(fā)視圖更新
//Vue不能檢測(cè)
vm.items[0] = 6;
vm.items.length = 0;
//替代方案
Vue.set(vm.items, 0, 6);
對(duì)象更新檢測(cè)
Vue.set(app.items, "third", 3);
Vue.delete(app.items, "third");
v-on監(jiān)聽(tīng)事件
<button @click="funClick($event)"></button>
事件修飾符:
.stop(阻止事件冒泡)
.prevent(阻止事件默認(rèn)動(dòng)作)
.capture(在捕獲階段監(jiān)聽(tīng)事件)
.self(跳過(guò)冒泡和捕獲事件顽照,只有直接作用在該元素上的事件才執(zhí)行)
.once(事件只觸發(fā)一次)
鍵盤(pán)修飾符:
<input @keyup.enter="submit" />
表單輸入綁定
v-model指令在表單元素上創(chuàng)建雙向數(shù)據(jù)綁定(忽略value, checked, selected初始值)
修飾符:
.lazy(v-model綁定值同步延遲到change事件)
.number(v-model綁定值轉(zhuǎn)換為Number)
.trim(v-model綁定值去首位空格)
組件
//全局組件
Vue.component('my-component', {});
//局部組件
new Vue({
components:{
'my-component': {}
}
});
<table>
<tr is="my-row"></tr>
</table>
為啥component的data被設(shè)計(jì)為一個(gè)函數(shù)?
返回一個(gè)唯一的對(duì)象闽寡,提示你避免和其他組件共用一個(gè)對(duì)象代兵,通過(guò)function(){return {count:0};}返回一個(gè)新對(duì)象
父子組件通信
父組件通過(guò)props(單向綁定,父組件屬性變化會(huì)傳給子組件爷狈,反之不會(huì))向下傳遞數(shù)據(jù)給子組件植影,子組件通過(guò)events給父組件發(fā)送消息
props可以指定驗(yàn)證規(guī)則
$on(eventName)
$emit(eventName)
v-model語(yǔ)法糖實(shí)際轉(zhuǎn)化為
<input :value="value"
@input="value=$event.target.value"/>
實(shí)現(xiàn)雙向數(shù)據(jù)綁定
非父子組件通信
簡(jiǎn)單場(chǎng)景使用一個(gè)空Vue實(shí)例做事件中轉(zhuǎn)
var bus = new Vue();
bus.$emit('my-event', 6);
bus.$on('my-event', function(arg){});
復(fù)制情況考慮Vuex(狀態(tài)管理)
slot
Vue.component('my-list', {
props: ['items'],
template: '<ul><slot name="listItem" v-for="item in items" :text="item"></slot></ul>'
});
動(dòng)態(tài)組件(通過(guò)is)
keep-alive保留切換出去的組件在內(nèi)存,避免重新渲染
<keep-alive>
<component :is="currentVie"></component>
</keep-alive>
<!-- ref為子組件指定索引,用來(lái)在javascript中直接訪問(wèn)子組件 -->
<my-component ref="my"></my-component>
//通過(guò)實(shí)例屬性$refs訪問(wèn)子組件
app.$refs.my.name;
異步組件
Vue.component('async-component', function(resolve, reject){resolve({template:''})});
混合
分發(fā)Vue組件中可復(fù)用功能
var mixin = {};
var app = new Vue({
mixins: [mixin]
});
指令
對(duì)純DOM元素進(jìn)行底層操作
鉤子函數(shù):bind(指令第一次綁定到元素調(diào)用涎永,只調(diào)用一次), inserted(綁定元素插入父節(jié)點(diǎn)), update, componentUpdated, unbind
//注冊(cè)全局指令
Vue.directive('focus', {
inserted:function(el){
el.focus();
}
});
//注冊(cè)局部指令
new Vue({
el:"#app",
directives:{
focus:{...}
}
});
渲染函數(shù)&JSX
Vue.component('myHeader', {
props:['level'],
//h為createElement別名為Vue慣例
render:function(h){
return h('h' + this.level);
//支持JSX
//return (<div>{this.level}</div>)
}
});
$mount
將Vue實(shí)例(邏輯應(yīng)用)思币,掛靠在某個(gè)DOM上
Vue實(shí)例渲染過(guò)程:
Vue構(gòu)造函數(shù)自動(dòng)運(yùn)行this._init(啟動(dòng)函數(shù))
new Vue();
hook beforeCreate();
Observe Data; //data變成發(fā)布者鹿响,watch變訂閱者
Init Events;
hook created();
Has el?No when vm.$mount("el") is called
Yes has template?
Complie template or Complie el as template
開(kāi)始編譯template模板生成的HTML
hook beforeMount();
create vm.$el and replace "el"
將編譯好的html替換掉el屬性所指向的dom對(duì)象或替換對(duì)應(yīng)HTML標(biāo)簽內(nèi)容
hook mounted();
Mounted;
when data changes;
hook beforeUpdate();
virtual DOM re-render and patch;
hook updated();
when vm.$destroy() is called;
hook beforeDestroy();
Teardown watchers, child components and event listeners
hook destroyed();
獨(dú)立構(gòu)建:
html template -> render函數(shù) -> vnode -> DOM
運(yùn)行時(shí)構(gòu)建(少一個(gè)模板編輯器):
render函數(shù) -> vnode -> DOM
$mount()手動(dòng)掛載
Vue實(shí)例沒(méi)有el屬性,該實(shí)例尚未掛載到某個(gè)DOM上谷饿,可以手動(dòng)調(diào)用vm.$mount()
<div id="app">{{val}}</div>
var app = new Vue({
data:{val:6}
});
//手動(dòng)掛載后{{val}}才顯示6
app.$mount("#app");
響應(yīng)式原理
通過(guò)Object.defineProperty(IE8以下不支持)將對(duì)象轉(zhuǎn)為getter/setter
因?yàn)閂ue在實(shí)例初始化時(shí)執(zhí)行g(shù)etter/setter轉(zhuǎn)換惶我,所以不能檢測(cè)后續(xù)對(duì)象屬性的添加刪除(受JavaScript限制),除非使用set方法
數(shù)據(jù)變化后立即使用Vue.nextTick(callback)在DOM更新后調(diào)用
SSR
解決SEO和首屏渲染性能
webpack插件prerender-spa-plugin添加預(yù)渲染
將一個(gè)組件渲染為服務(wù)器端HTML字符串博投,直接發(fā)送到瀏覽器绸贡,最后靜態(tài)標(biāo)記為“混合”,將它們變成響應(yīng)式毅哗,成為客戶端上完全交互的應(yīng)用
<div id="app" data-server-rendered="true">
data-server-rendered特殊屬性听怕,讓客戶端Vue知道標(biāo)記由服務(wù)器渲染,并應(yīng)該以混合模式掛載
app.$mount("#app");
框架對(duì)比
VS React
相同點(diǎn):
1.使用虛擬DOM
2.提供響應(yīng)式虑绵,組件化
不同:
1.Vue默認(rèn)使用Templates尿瞭,雖然Vue提供了render,支持JSX
2.Vue和Weex合作替代ReactNative
VS Angular
1.Vue更簡(jiǎn)單
2.Angular使用雙向綁定翅睛,Vue在不同組件間強(qiáng)制使用單向數(shù)據(jù)流
3.Vue將指令與組件分得更清晰
4.可能會(huì)有更好的性能声搁,不使用臟檢查,watcher越多性能越差
5.體積小
項(xiàng)目
http請(qǐng)求自定義頭部
Vue.http.headers.common["x-user-email"] = gon.user_email.toString();
事件分發(fā)
var app = new Vue({
el:"#app",
data:{
eventHub:new Vue()
}
});
//監(jiān)聽(tīng)事件
this.$root.eventHub.$on(eventName, handler);
//觸發(fā)事件
this.$root.eventHub.$emit(eventName, data);
自動(dòng)搜索
Vue.component('typeAhead', {
data:function(){
return {keyword:"", isLoading:false};
},
watch:{
keyword: function(){
this.isLoading = true;
this.search();
}捕发,
'data.isLoading':{
handler:function(val, oldVal){...},
//深度觀察酥艳,如果想監(jiān)控對(duì)象
deep:true
}
},
methods:{
search:_.debounce(function(){
var that = this;
Vue.http({url:this.filterUrl})
.then().catch(function(){
that.isLoading = false;
});
}, 500)
}
});
Vue.use
使用別人開(kāi)發(fā)的插件,第一步install, 第二步main.js引入爬骤,第三步Vue.use
- selfComponents
- loading
- index.js
- Loading.vue
自定義插件
- loading
<!-- Loading.vue -->
<template>
<div>loading...</div>
</template>
//index.js
import MyLoading from './Loading.vue'
const Loading = {
//install導(dǎo)出必須是一個(gè)component
install: function(Vue){
Vue.component('Loading', MyLoading);
}
};
export default Loading;
使用自定義插件
<template>
<Loading></Loading>
</template>
//在main.js中引入
import Loading from 'selfComponents/loading';
Vue.use(Loading);
extend
Vue構(gòu)造器創(chuàng)建一個(gè)子類(lèi)充石,返回一個(gè)擴(kuò)展實(shí)例構(gòu)造器(就是預(yù)設(shè)了某些選項(xiàng)的Vue實(shí)例構(gòu)造器)
new Vue({
el: '#app',
template: '<author></author>'
});
const author = Vue.extend({
template: "<div>author:{{name}}</div>",
data: function () {
return
},
props: ['name']
});
//掛載后顯示author:66
//通過(guò)propsData傳入?yún)?shù)
new author({propsData: {name: 66}}).$mount('author');