虚拟DOM
为啥要用?
DOM操作开销大,虚拟DOM提供一种方便的工具,保证开发效率,保证最小化DOM操作
用Js对象虚拟DOM树结构,当页面状态发生变化需要操作DOM时,先操作虚拟DOM计算出对真实DOM最小修改量,然后再修改真实DOM结构,保证最小化DOM操作
虚拟DOM是纯粹JS对象,操作高效,但最终会转换成真实DOM,因此需要一套高效的虚拟DOM diff算法
Vue的diff基于snabbdom,仅仅在同级的DOM间做diff, 递归的进行同级DOM的diff,最终实现整个DOM树更新
Vue的diff实现
oldStart+oldEnd, newStart+newEnd,分别对应oldVdom和newVdom起止点。Vue不断对虚拟DOM处理同时移动指针直到其中任一对起点和终点相遇,处理过的节点会在oldVdom和newVdom中同时标记为已处理,整个过程逐步找到更新前后vnode差异,然后将差异反应到DOM树(就是patch),Vue的patch是即时,并非打包所有修改最后一起操作DOM(React是将更新放入队列后集中处理)
1.优先处理特殊场景,头部同类型节点,尾部同类型节点
1.原地复用,Vue尽可能复用DOM,尽可能不发生DOM移动(key管理可复用元素)
问题:
1.内存:虚拟DOM需要在内存中维护一份DOM副本,在DOM更新速度和使用内存间取得平衡
2.适合一次大量更新虚拟DOM,但单一频繁跟新,虚拟DOM会花费更多时间处理计算工作。如页面DOM节点相对少,用虚拟DOM可能会更慢,但对于大多数单页应用,应该会更快
实例生命周期
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渲染函数(render函数)
状态改变,计算渲染组件最小代价更新DOM
v-bind:href="url"缩写:href
v-on:click="funClick"缩写@click
计算属性
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,才渲染),更高切换开销
v-show元素总被渲染,更高初始渲染开销
v-for默认使用就地复用策略跟新已渲染元素列表,为追踪每个节点,需要为每项提供一个唯一key,优先级高于v-if
<li v-for="(item, key, index) in items" :key="index">{{item + key +index}}</li>
<!-- 组件有自己独立作用域,传递数据到组件要用props,避免耦合,组件v-for必绑定key -->
<my-component v-for="item in items" :item="item" :key="item.id"></my-component>
数组更新检测
Vue重写了数组的push, pop, shift, unshift, splice, sort, reverse会触发视图更新
filter,concat,slice等返回新数组,可用新数组赋值替换,触发视图更新
//Vue不能检测
vm.items[0] = 6;
vm.items.length = 0;
//替代方案
Vue.set(vm.items, 0, 6);
对象更新检测
Vue.set(app.items, "third", 3);
Vue.delete(app.items, "third");
v-on监听事件
<button @click="funClick($event)"></button>
事件修饰符:
.stop(阻止事件冒泡)
.prevent(阻止事件默认动作)
.capture(在捕获阶段监听事件)
.self(跳过冒泡和捕获事件,只有直接作用在该元素上的事件才执行)
.once(事件只触发一次)
键盘修饰符:
<input @keyup.enter="submit" />
表单输入绑定
v-model指令在表单元素上创建双向数据绑定(忽略value, checked, selected初始值)
修饰符:
.lazy(v-model绑定值同步延迟到change事件)
.number(v-model绑定值转换为Number)
.trim(v-model绑定值去首位空格)
组件
//全局组件
Vue.component('my-component', {});
//局部组件
new Vue({
components:{
'my-component': {}
}
});
<table>
<tr is="my-row"></tr>
</table>
为啥component的data被设计为一个函数?
返回一个唯一的对象,提示你避免和其他组件共用一个对象,通过function(){return {count:0};}返回一个新对象
父子组件通信
父组件通过props(单向绑定,父组件属性变化会传给子组件,反之不会)向下传递数据给子组件,子组件通过events给父组件发送消息
props可以指定验证规则
$on(eventName)
$emit(eventName)
v-model语法糖实际转化为
<input :value="value"
@input="value=$event.target.value"/>
实现双向数据绑定
非父子组件通信
简单场景使用一个空Vue实例做事件中转
var bus = new Vue();
bus.$emit('my-event', 6);
bus.$on('my-event', function(arg){});
复制情况考虑Vuex(状态管理)
slot
Vue.component('my-list', {
props: ['items'],
template: '<ul><slot name="listItem" v-for="item in items" :text="item"></slot></ul>'
});
动态组件(通过is)
keep-alive保留切换出去的组件在内存,避免重新渲染
<keep-alive>
<component :is="currentVie"></component>
</keep-alive>
<!-- ref为子组件指定索引,用来在javascript中直接访问子组件 -->
<my-component ref="my"></my-component>
//通过实例属性$refs访问子组件
app.$refs.my.name;
异步组件
Vue.component('async-component', function(resolve, reject){resolve({template:''})});
混合
分发Vue组件中可复用功能
var mixin = {};
var app = new Vue({
mixins: [mixin]
});
指令
对纯DOM元素进行底层操作
钩子函数:bind(指令第一次绑定到元素调用,只调用一次), inserted(绑定元素插入父节点), update, componentUpdated, unbind
//注册全局指令
Vue.directive('focus', {
inserted:function(el){
el.focus();
}
});
//注册局部指令
new Vue({
el:"#app",
directives:{
focus:{...}
}
});
渲染函数&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实例(逻辑应用),挂靠在某个DOM上
Vue实例渲染过程:
Vue构造函数自动运行this._init(启动函数)
new Vue();
hook beforeCreate();
Observe Data; //data变成发布者,watch变订阅者
Init Events;
hook created();
Has el?No when vm.$mount("el") is called
Yes has template?
Complie template or Complie el as template
开始编译template模板生成的HTML
hook beforeMount();
create vm.$el and replace "el"
将编译好的html替换掉el属性所指向的dom对象或替换对应HTML标签内容
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();
独立构建:
html template -> render函数 -> vnode -> DOM
运行时构建(少一个模板编辑器):
render函数 -> vnode -> DOM
$mount()手动挂载
Vue实例没有el属性,该实例尚未挂载到某个DOM上,可以手动调用vm.$mount()
<div id="app">{{val}}</div>
var app = new Vue({
data:{val:6}
});
//手动挂载后{{val}}才显示6
app.$mount("#app");
响应式原理
通过Object.defineProperty(IE8以下不支持)将对象转为getter/setter
因为Vue在实例初始化时执行getter/setter转换,所以不能检测后续对象属性的添加删除(受JavaScript限制),除非使用set方法
数据变化后立即使用Vue.nextTick(callback)在DOM更新后调用
SSR
解决SEO和首屏渲染性能
webpack插件prerender-spa-plugin添加预渲染
将一个组件渲染为服务器端HTML字符串,直接发送到浏览器,最后静态标记为“混合”,将它们变成响应式,成为客户端上完全交互的应用
<div id="app" data-server-rendered="true">
data-server-rendered特殊属性,让客户端Vue知道标记由服务器渲染,并应该以混合模式挂载
app.$mount("#app");
框架对比
VS React
相同点:
1.使用虚拟DOM
2.提供响应式,组件化
不同:
1.Vue默认使用Templates,虽然Vue提供了render,支持JSX
2.Vue和Weex合作替代ReactNative
VS Angular
1.Vue更简单
2.Angular使用双向绑定,Vue在不同组件间强制使用单向数据流
3.Vue将指令与组件分得更清晰
4.可能会有更好的性能,不使用脏检查,watcher越多性能越差
5.体积小
项目
http请求自定义头部
Vue.http.headers.common["x-user-email"] = gon.user_email.toString();
事件分发
var app = new Vue({
el:"#app",
data:{
eventHub:new Vue()
}
});
//监听事件
this.$root.eventHub.$on(eventName, handler);
//触发事件
this.$root.eventHub.$emit(eventName, data);
自动搜索
Vue.component('typeAhead', {
data:function(){
return {keyword:"", isLoading:false};
},
watch:{
keyword: function(){
this.isLoading = true;
this.search();
},
'data.isLoading':{
handler:function(val, oldVal){...},
//深度观察,如果想监控对象
deep:true
}
},
methods:{
search:_.debounce(function(){
var that = this;
Vue.http({url:this.filterUrl})
.then().catch(function(){
that.isLoading = false;
});
}, 500)
}
});
Vue.use
使用别人开发的插件,第一步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导出必须是一个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构造器创建一个子类,返回一个扩展实例构造器(就是预设了某些选项的Vue实例构造器)
new Vue({
el: '#app',
template: '<author></author>'
});
const author = Vue.extend({
template: "<div>author:{{name}}</div>",
data: function () {
return
},
props: ['name']
});
//挂载后显示author:66
//通过propsData传入参数
new author({propsData: {name: 66}}).$mount('author');