一、数据与方法
1、当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
2、值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。
3、组件的复用 data 必须是一个函数
4、Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的。采用函数形式定义,在initData时会将其作为工厂函数返回全新的data对象,有效规避多实例之间状态污染问题。
而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况。
二、VUE双向绑定原理
vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
1、Object.defineProperty()
Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性;
Object.defineProperty(obj, prop, desc)
obj
要定义属性的对象。
prop
要定义或修改的属性的名称或 Symbol 。
descriptor
要定义或修改的属性描述符。
var temperature = null;
Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
}
});
存取描述符 --是由一对 getter、setter 函数功能来描述的属性
get:一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。
set:一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined。
通过Object.defineProperty( )对属性设置set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。
2、实现细节
实现数据的双向绑定:
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
(1) Observer 监听器
首先需要对定义的数据进行监听,其核心使用Object.defineProperty( ) 对设置的属性进行设置set get 函数,如果属性变化就通知订阅者Watcher是否需要更新。由于订阅者是多个,我们使用消息订阅器Dep;监听器Observer和订阅者Watcher之间进行统一管理;
(2) 消息订阅器Dep
用来容纳所有的“订阅者”。订阅器Dep主要负责收集订阅者,然后当数据变化的时候后执行对应订阅者的更新函数。就是典型的“发布订阅者”模式,数据变化为“发布者”,依赖对象为“订阅者”,
(3) 订阅者Watcher
订阅者Watcher在初始化的时候需要将自己添加进订阅器Dep中,已经知道监听器Observer是在get函数执行了添加订阅者Wather的操作的,所以我们只要在订阅者Watcher初始化的时候出发对应的get函数去执行添加订阅者操作即可;
(4) 解析器Compile
解析器Compile来做解析和绑定工作。解析器Compile实现步骤:
1.解析模板指令,并替换模板数据,初始化视图
2.将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。
三、 生命周期
1、beforeCreate
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
2、created
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。
3、beforeMount
在挂载开始之前被调用:相关的 render 函数首次被调用。
4、mounted
实例被挂载后调用,这时 el 被新创建的 vm.
e
l
替
换
了
。
如
果
根
实
例
挂
载
到
了
一
个
文
档
内
的
元
素
上
,
当
m
o
u
n
t
e
d
被
调
用
时
v
m
.
el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.
el替换了。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el 也在文档内。
注意 mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用
vm.$nextTick:
5、beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
6、updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
注意 updated 不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用
vm.$nextTick:updated: function () { this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered }) }
7、activated
被 keep-alive 缓存的组件激活时调用。
8、deactivated
被 keep-alive 缓存的组件停用时调用。
9、beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
10、destroyed
实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
11、errorCaptured
当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
你可以在此钩子中修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一个条件判断来绕过其它内容就很重要;不然该组件可能会进入一个无限的渲染循环
四、VUE通信
1、父子组件互相传值props和$emit
2、父组件和子孙组件传值 $attrs 和 $listeners
3、父组件 $children 操作子组件 和 子组件 $parent 访问父组件
4、非父子组件通信 中央事件总线 $emit / $on
5、provide / inject 跨级组件(非响应式,provide改变inject不变)
6、ref
7、VUEX
1、父子组件互相传值props和$emit
父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的.
1).父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件;
2).子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件
2、父组件和子孙组件传值 $attrs 和 $listeners
当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。
<!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) -->
<C v-bind="$attrs" v-on="$listeners"></C>
3、父组件 $children 操作子组件 和 子组件 $parent 访问父组件
使用 $children 操作子组件
this.$children.forEach(child => {
child.$data.colored = !child.$data.colored // 逐一控制子组件的 $data
})
子组件可通过 $parent 来修改父组件的 $data
this.$parent.$data.colored = !this.$parent.$data.colored
4、非父子组件通信 中央事件总线 $emit / $on
新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus. $on 监听触发的事件
具体步骤是创建一个 Vue 实例,然后 $on 监听事件,$emit 来派发事件
// src/eventBus.js
import Vue from 'vue'
let EventBus = new Vue();
export const $bus = EventBus;
首先创建并导出一个 Vue 实例
import bus from '@/eventbus'
export default {
// ...
methods: {
handleClick (e) {
bus.$emit('change-color')
}
}
}
后代元素 $emit 触发 eventBus 的事件
import bus from '@/eventbus'
export default {
// ...
mounted () {
bus.$on('change-color', () => {
this.colored = !this.colored
})
}
}
祖先元素 $on 方法监听 eventBus 的事件
5、provide/inject 依赖注入
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
在这里插入代码片
export default {
provide: {
name: '浪里行舟'
}
}
export default {
inject: ['name'],
mounted () {
console.log(this.name); // 浪里行舟
}
}
6、ref
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
7、 Vuex
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
五、Vue性能优化方法
1、路由懒加载
const router = new VueRouter({
routes: [
{path: '/foo',component: ()=>import('./Foo.vue')}
]
})
2、keep-alive缓存页面
<template>
<div id="app">
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>
3、使用v-show 复用DOM
4、利用Object.freeze()提升性能
纯粹的数据展示,不会有任何变化,就不需要做响应化
5、使用单文件组件预编译模板
六、vue中的diff算法
渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom
根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后会生成一个新的Vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode。
diff的过程就是调用名为patch的函数,比较新旧节点,一边比较一边给真实的DOM打补丁。
1、virtual DOM是将真实的DOM的数据抽取出来,以对象的形式模拟树形结构
var Vnode = {
tag: 'div',
children: [
{ tag: 'p', text: '123' }
]
}