上一章介绍了Vue.js内部的整体结构,知道了它会向构造函数添加一些属性和方法。本章中,我们将详细介绍它的实例方法和全局API的实现原理
在Vue.js内部,有这样一段代码
import {initMixin} from './init'
import {stateMixin} from './state'
import {renderMixin} from './render'
import {eventsMixin} from './events'
import {lifecycleMixin} from './liftcycle'
import {warn} from '../util/index'
function Vue (options){
if(process.env.NODE_ENV !== 'production' && !(this instanceof Vue)){
warn('Vue is a constructor and should be called with the `new` keyword)
}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
renderMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
export default(Vue)
其中定义了Vue构造函数,然后分别调用了initMixin,stateMixin,eventMixin,lifecycleMixin和renderMixin这五个函数,并将Vue构造函数当做参数传给了5个函数。
这5个函数的作用是向Vue的原型中挂载方法。
13.1 数据相关的实例方法
与数据相关的实例方法有3个,分别是vm.watch,vm.set,vm.$delete,它们是在stateMixin中挂载到Vue的原型上的,
import {set,del} from '../observer/index'
export function stateMixin(vue){
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.Prototype.$watch = function (expOrFn,cb,options){}
}
当stateMixin被调用时,会向Vue构造函数的prototype属性挂载这个3个实例方法
13.2 事件相关的实例方法
与事件相关的实例方法:vm.on,vm.once,vm.off和vm.emit。当eventMixin被调用时,会添加这个4个实例方法
13.2.1 vm.$on
vm.$on(event,callback)
这个和v-on 好像没关系,别搞混了。
用法 监听当前实例上的自定义事件,事件可以由vm.$emit触发。回调函数会接收所有传入事件所触发的函数的额外参数
vm.$on('test',function(msg){
console.log(msg)
})
vm.$emit('test','hi')
// => 'hi'
vm._events 是一个对象,用来存储事件。在代码中,我们使用事件名(event)从vm_events中取出事件列表,如果列表不存在,则使用空数组初始化,然后再将回调函数添加到事件列表中
这样事件就注册好了,vm._events哪来的?事实上,在执行new Vue()时,Vue会执行this.init 方法进行一系列初始化操作,其中Vue.js的实例上创建了一个_events属性,用来存储事件
vm.events = Object.create(null)
13.2.2 vm.$off
vm.$off([event,callback])
// 移除所有事件监听器
if(!arguments.length){
vm._events = Object.create(null)
return vm
}
this.off和vm.off 是同一个方法,vm是this的别名
13.2.3 vm.$once
vm.$once(event,callback) 用法:监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器
13.2.4 vm.$emit
vm.$emit(event,[...args]) 触发当前实例上的事件。附加参数都会传递给监听器回调
所有事件监听器回调函数都会存储在vm.events中,所以触发事件的实现思路是使用事件名从vm.events中取出对应的事件监听器回调函数列表。然后依次执行列表中的监听器回调并将参数传递给监听器回调。
13.3 生命周期相关的实例方法
与生命周期相关的实例方法有4个,分别是vm.mount,vm.forceUpdate,vm.nextTick和vm.destroy
vm.forceUpdate和vm.destroy是lifecyleMixin挂载的
vm.$nextTick 方法是从renderMixin挂载的
vm.$mount 是跨平台代码中挂载到Vue构造函数的prototype属性上的
vm.$forceUpdate
作用就是迫使Vue.js实例重新渲染
Vue.prototype.$forceUpdate = function(){
const vm = this
if(vm.watcher){
vm._watcher.update()
}
}
vm.watcher 就是Vue.js实例的watcher,每当组件内依赖的数据发生变化时,都会自动触发Vue.js实例中_watcher 的update方法
vm.$forceUpdate是手动通知Vue.js实例重新渲染
vm.$destroy
Vue.js 实例的$children属性存储了所有子组件
vm.$nextTick
nextTick 接收一个回调函数作为参数,它的作用是将回调延迟到下次DOM更新周期之后执行。
它与全局方法Vue.nextTick一样,不同的是回调的this自动绑定到调用它的实例上。如果没有提供回调且在支持promise的环境中,则返回一个Promise
new Vue({
methods:{
example:function(){
this.message = 'changed'
// DOM还没更新
this.$nextTick(function(){
//DOM 闲杂更新了
//this 绑定到当前实例
this.doSomethingElse()
})
}
}
})
有个问题: 下次DOM更新周期之后执行,是什么时候呢? 当状态发生变化时,watcher触发渲染的操作不是同步的,而是异步的。
1. 为什么Vue.js使用异步更新队列
react的setData()也是这个原理吧
Vue.js中有个队列,每当需要渲染时,会将watcher推送到这个队列中,在下一次事件循环中再让watcher触发渲染流程
如果在同一轮事件循环中有两个数据发生了变化,那么组件的watcher会收到两份通知,从而进行两次渲染。事实上,并不需要渲染两次,虚拟DOM会对整个组件进行渲染,所以只需要等所有状态修改完毕后,一次性将整个组件的DOM渲染到最新即可
要解决这个问题,收到通知watcher实例添加到队列中缓存起来,并判断队列是否已经存在相同的watcher。然后在下一次事件循环中,会让队列watcher触发渲染。这样保证了即便在同一事件循环中有两个状态发生改变,watcher最后也执行一次渲染流程
2.什么是事件循环
我们都知道JavaScript是一门单线程且非阻塞的脚本语言。
微任务:
- Promise.then
- MutationObserver
- Object.observe
- process.nextTick
宏任务
- setTimeout
- setInterval
- setImmediate
- MessageChannel
- requesAnimationFrame
- I/O
- UI 交互事件
3. 什么是执行栈
当我们执行了一个方法时,JavaScript会生成一个与这个方法对应的执行环境(context),又叫执行上下文。这个执行环境中有这个方法的私有作用域,上层作用域的指向,方法的参数,私有作用域中定义的变量以及this对象。这个执行环境会被添加到一个栈中,这个栈就是执行栈
new Vue({
methods: {
example:function(){
// 先使用nextTick 注册回调
this.$nextTick(function(){
//DOM 没有更新
})
// 然后修改数据
this.message = 'changed'
}
},
})
new Vue({
methods: {
example:function(){
// 先使用setTimeout向宏任务中注册回调
setTimeout(_ => {
// DOM 现在更新了
},0)
// 然后修改数据向微任务中注册回调 ---- watcher的更新是微任务,异步更新的
this.message = 'changed'
}
},
})
微任务优先级太高,也可能出现问题。也可强制使用宏任务的方法
vm.$mount
vm.$mount([elementOrSelector])
如果Vue.js实例在实例化时没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。
13.4 全局API的实现原理
现在我们已经了解了Vue.js实例方法的内部原理,接下来将介绍全局API的内部原理
13.4.1 Vue.extend
Vue.extend(options) 用法: 使用基础Vue构造器创建一个"子类",其参数是一个包含”组件选项“的对象。data选项是特例,在Vue.extend()中,它必须是函数:
var Profile = Vue.extend({
template:'<p>{{firstName}}{{lastName}} aka {{alias}}',
data:function(){
return {
firstName:'Walter',
lastName:'Whilte',
alias:'Heisenberg'
}
}
})
// 创建Profile实例,并挂载到一个元素上
new Profile().$mount('#mount-point')
全局API和实例方法不同,后者是在Vue的原型上挂载方法,也就是Vue.prototype上挂载方法,而前者是直接在Vue上挂载方法。
Vue.extend = function(extendOptions){
// 做点什么
}
总体来说,其实就是创建了一个Sub函数并继承了父级。如果直接使用Vue.extend,则Sub继承于Vue构造函数
13.4.2 Vue.nextTick
Vue.nextTick([callback,context])
vm.msg = 'Hello'
// DOM还没有更新
Vue.nextTick(function(){
// DOM 更新了
})
// 作为一个Promise 使用
Vue.nextTick().then(function(){ // DOM 更新了})
13.4.3 Vue.set
Vue.set(target,key,value) Vue.set和vm.$set 的实现原理相同
是Vue和组件的区别吗 为啥要设置两个呢?
13.4.4 Vue.delete
Vue.delete(target,key) 上同
13.4.5 Vue.directive
注册或获取全局指令 Vue.directive(id,[definition])
Vue.directive('my-directive',{
bind:function(){},
inserted:function(){},
update:function(){},
componentUpdated:function(){},
unbind:function(){}
})
// 注册(指令函数)
Vue.directive('my-directive',function(){
// 这里将会被bind和update调用
})
// getter 方法,返回已注册的指令
var myDirective = Vue.directive('my-directive')
虽然代码复用和抽象的主要形式是组件,但是有些情况下,仍需对普通DOM元素进行底层操作,这时就会用到自定义指令
13.4.6 Vue.filter
注册或获取全局过滤器 Vue.filter(id,[definition])
Vue.filter('my-filter',function(value){
// 返回处理后的值
})
// getter 方法,返回已注册的过滤器
var myFilter = Vue.filter('my-filter')
过滤器可以用在两个地方:双花括号插值和v-bind表达式
13.4.7 Vue.component
Vue.component(id,[definition]) 注册或获取全局组件。注册组件时,还会自动使用给定的id设置组件的名称。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!