实例方法
实例方法是在Vue
的原型Vue.prototype
上挂载方法
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
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)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
数据相关的
与数据相关的实例方法主要有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){}
}
vm.$watch
用法:用于观察一个表达式或computed函数在Vue.js实例上的变化。回调函数调用时,会从参数得到新数据和旧数据。
参数:{string|Function}expOrFn
{Function|Object}callback
{Object}[options] ({boolean}deep {boolean}immediate )
返回值:{Function}unwatch
示例:
vm.$watch('a.b.c',function(newVal,oldVal){ })
var unwatch=vm.$watch('a',(newVal,oldVal){ })
unwatch()//取消观察
vm.$watch('a',callback,{
deep:true,//观察对象内部值的变化
immediate:true //将表达式的当前值触发回调
})
原理:
选项deep的原理主要是在Watcher的代码里实现,就是除了触发当前被监听的数据收集依赖之外,还要把这个值在内的所有子值都触发一遍收集依赖逻辑。immediate原理就是把回调函数触发一遍
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
//immediate为true,cb立刻执行一次
if (options.immediate) {
const info = `callback for immediate watcher "${watcher.expression}"`
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
//unwatch就是调用Watcher的teardown方法,将Watcher从它的Dep列表中移除
return function unwatchFn () {
watcher.teardown()
}
}
vm.$set
用法:在object上设置一个属性,如果object是响应式,Vue.js会保证属性被创建后也是响应式,并且触发视图更新。(这个方法主要是用来避开Vue.js不能侦测属性被添加的限制)
参数:{Object | Array} target
{string | number} key
{any} value
返回值:{Function }unwatch
原理:
原理主要就是分情况讨论,也没有很难懂~~
export function set (target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
//如果target是一个数组而且key是一个合理的索引值
if (Array.isArray(target) && isValidArrayIndex(key)) {
//如果传递的索引值大于数组的length就让target的length为key
target.length = Math.max(target.length, key)
//使用splice方法把val设置到target上,这样数组拦截器会侦测到target发生了变化,会自动把新增
//的val转换为响应式
target.splice(key, 1, val)
return val
}
//key已经存在于target中,直接修改就好了
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
//target不能是Vue.js实例或Vue.js实例的根数据对象
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
//没有__ob__,数据不是响应式,只需直接设置即可
if (!ob) {
target[key] = val
return val
}
//如果以上条件都不满足,说明用户是在响应式数据上新增一个属性,所以要用defineReactive将新属性
//转换成getter/setter形式
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
vm.$delete
用法:对象删除一个属性或者数组删除一个元素,它删除后会自动想依赖发送消息
原理:
原理主要就是分情况讨论,也没有很难懂~~
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
//处理数组情况,splice删除元素,数组拦截器会自动向依赖发送通知
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.'
)
return
}
//如果key不是target自身的属性,直接return
if (!hasOwn(target, key)) {
return
}
delete target[key]
//非响应式直接返回
if (!ob) {
return
}
//响应式发通知
ob.dep.notify()
}
事件相关的
目录src/score/instance/events.js
与事件相关的实例方法主要有4个vm.$on、vm.$off、vm.$once、vm.$emit
,它们是在eventsMixin
中挂载到Vue
原型上的
export function eventsMixin (Vue) {
Vue.prototype.$on = function (event, fn){
}
Vue.prototype.$once = function (event, fn) {
}
Vue.prototype.$off = function (event, fn){
}
Vue.prototype.$emit = function (event) {
}
}
vm.$on(event,callback)
用法:主要用于监听当前实例上的自定义事件,事件可以由vm.$emit
触发。回调函数会接收所有传入事件所触发的函数的额外参数
参数:{string|Array<string>}event
{Function}callback
示例:
vm.$on('try',function(msg){console.log(msg)}
//触发
vm.$emit('try','hello')
源码:在注册事件的时候将回调函数收集起来,在触发事件的时候将收集起来的回调函数依次调用
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
vm._events是一个对象,用来存储事件。当event不为数组时,直接通过vm._events中取出事件列表
vm._events在new Vue()
的时候,调用this._init
方法初始化创建的,用来存储事件
export function initEvents (vm: Component) {
//这句创建vm._events
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
vm.$off([event,callback])
用法:移除自定义事件监听器(若无提供参数,则移除所有事件监听器;若只提供了事件,则移除该事件的监听器;若同时提供了事件和回调,则只移除这个回调的监听器)
参数:{string|Array<string>}event
{Function}callback
源码:
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all 移除所有事件
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events 多个事件,遍历取消监听
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event 没有找到事件的监听器,直接退出
const cbs = vm._events[event]
if (!cbs) {
return vm
}
//只有事件没有回调,直接置为空
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler 有回调则移除对应的回调函数
/*
这里还有一个重要的问题是,遍历回调函数是从后向前遍历的,因为从前向后遍历的话会导致数组
向前移动,而i并没有回退,会跳过一个元素
*/
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
vm.$emit(event,[…args])
用法:用于触发当前实例上的事件,附加的参数都会传给监听器回调
参数:{string}event
[...args]
源码:实现原理就是从vm._events中取出事件监听器的回调函数列表赋值给cbs,若列表存在即调用每个监听器回调并将参数传给监听器回调
//invokeWithErrorHandling(cbs[i], vm, args, vm, info)后面补充。。。。。
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
/*
toArray是将类似数组数据转换成真正数组,第二个参数是起始位置,即args是一个除了第一个
参数之外的所有参数
*/
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
vm.$once(event,callback)
用法:监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器
参数:{string|Array<string>}event
{Function}callback
源码:
实现的过程是首先将函数on注册到事件中。当事件被触发时,会执行函数on,on函数里会使用vm.$off移除自定义事件,然后再通过apply执行函数fn,并将参数传递给函数fn
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
还有个问题是注册的函数是on函数,但是用户触发的函数是fn,在用vm.$off移除事件监听器时会失效。源码上通过on.fn=fn
来解决,vm.$off
源码上用 if (cb === fn || cb.fn === fn) { }
来判断
生命周期相关的
与生命周期相关的实例方法主要有4个vm.$mount、vm.$forceUpdate、vm.$nextTick、vm.$destroy
,vm.$forceUpdate
和vm.$destroy
是在lifecycleMixin
中挂载到Vue
原型上的,vm.$nextTick
是在renderMixin
中挂载到Vue
原型上的,vm.$mount
是在跨平台的代码中挂载到Vue
原型上的
// 目录 src/score/instance/lifecycle.js
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype.$forceUpdate = function (){
}
Vue.prototype.$destroy = function (event, fn) {
}
}
// 目录 src/score/instance/render.js
export function renderMixin (Vue: Class<Component>) {
Vue.prototype.$nextTick = function (fn: Function) {
return nextTick(fn, this)
}
}
//运行时版(以web平台为例) src/platforms/web/runtime/index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
}
//完整版(以web平台为例) src/platforms/web/entry-runtime-with-compiler.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
}
vm.forceUpdate()
作用:迫使Vue.js实例重新渲染(注意它仅仅影响实例本身以及插入插槽内容的子组件,而不是所有子组件)
源码:实现该功能只需手动执行一下当前实例watcher的update()方法。因为Vue.js的每一个实例都有一个watcher(Vue.js的一个实例其实就相当于一个组件,而当状态发生变化的时候,会通知到组件级别,然后在组件内部使用虚拟DOM进行重新渲染)
Vue.prototype.$forceUpdate = function () {
const vm: Component = this
if (vm._watcher) {
vm._watcher.update()
}
}
vm.destroy
作用:完全销毁一个实例,它会清理改实例与其他实例的连接,并解绑其全部指令及生命周期,同时会触发beforeDestory和destroyed的钩子函数
源码:
Vue.prototype.$destroy = function () {
const vm: Component = this
//判断实例是否正在销毁,因为销毁只能销毁一次,不能反复
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy')
vm._isBeingDestroyed = true
/*
为什么只从一个父组件中移除?
因为虽然一个组件可以被多个父组件引入,当时其实子组件在不同的父组件中是不同的Vue.js实例,
所以一个子组件实例其实只有一个父组件
*/
// remove self from parent 清除当前组件和父组件之间的连接
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// teardown watchers
/*
从watcher监听的所有状态的依赖列表中移除watcher
vm._watcher是在执行new Vue()初始化时创建的,它是监听当前组件用到的所有状态,即这个组
件内用到的所有状态的依赖列表。当这些状态发生变化时,就会通知vm._watcher,再调用虚拟DOM
进行重新渲染
*/
if (vm._watcher) {
vm._watcher.teardown()
}
//这里销毁的是用户使用vm.$watch所创建的watcher实例
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
//这里要研究一下。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
// remove reference from data ob
// frozen object may not have observer.
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--
}
// call the last hook...
vm._isDestroyed = true
// invoke destroy hooks on current rendered tree
vm.__patch__(vm._vnode, null)
// fire destroyed hook
callHook(vm, 'destroyed')
// turn off all instance listeners.移除事件监听器
vm.$off()
// remove __vue__ reference
if (vm.$el) {
vm.$el.__vue__ = null
}
// release circular reference (#6759)
if (vm.$vnode) {
vm.$vnode.parent = null
}
}
/*
每创建一个watcher实例时,都会将watcher实例添加到vm._watchers中,vm._watchers是在
new Vue()初始化时创建的,所以每当用户使用vm.$watch时,就会向vm._watchers添加一个watcher
实例。所以现在只需遍历vm._watchers,执行每一个watch实例的teardown方法即可
*/
export default class Watcher{
constructor(vm,expOrFn,cb){
vm._watchers.push(this)
...
}
}
vm.$mount
用法:如果Vue.js实例在实例化的时候没有收到el选项,则它处于“未挂载”状态,没有关联的DOM元素。可以使用vm.$mount手动挂载一个未挂载的实例。
参数:{Element|string}[elementOrSelector]
返回值:vm,即实例本身
示例:
var MyComponent=Vue.extend({
template:'<div>hello</div>'
})
//创建并挂载到#app
new MyComponent().$mount('#app')
//创建并挂载到#app
new MyComponent({el:'#app'})
//没有传入参数,模板会被渲染为文档之外的元素,可以使用DOM的API把它插入文档中
var mycomponent=new MyComponent().$mount()
document.getElementById('app').appendChild(component.$el)
源码:
完整版(Vue.js)和运行时版(Vue.runtime.js)的差异是是否有编译器,而是否有编译器的差异主要就在vm.$mount方法的表现形式。
完整版
在完整版中,vm.$mount会首先检查template或el选项所提供的模板是否已经转换为渲染函数(render函数),如果没有,则立即进入编译过程,将模板编译成渲染函数,完成后再进入挂载与渲染的流程中
/*
这种先保存Vue原型的prototype方法,然后用一个新的方法覆盖,在新方法中调用原始方法的做法叫
函数劫持
*/
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
...
return mount.call(this, el, hydrating)
}
//获取DOM元素
export function query (el: string | Element): Element {
if (typeof el === 'string') {
const selected = document.querySelector(el)
if (!selected) {
process.env.NODE_ENV !== 'production' && warn(
'Cannot find element: ' + el
)
//获取不到,则创建一个空元素
return document.createElement('div')
}
return selected
} else {
//如果不是字符串,则认为el是元素类型,直接返回
return el
}
}
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
//主要代码
function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
//将options属性混合到空对象中,目的是让options成为可选参数
options = extend({}, options)
// check cache 检查缓存,若已经编译则直接返回
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// compile 将template编译成代码字符串并储存在compiled的render属性中
const compiled = compile(template, options)
// turn code into functions 将代码字符串转换为函数
const res = {}
res.render = createFunction(compiled.render, fnGenErrors)
return (cache[key] = res)
}
}
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
//options是初始化时创建的,它可以访问到实例化Vue.js时,用户设置的一些参数,例如template和render
const options = this.$options
//如果没有render选项,需要获取模板并将模板编译成render函数赋值给render选项
if (!options.render) {
let template = options.template
if (template) {
//template选项可以是字符串模板、#开头的选项符、DOM元素
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
//如果是#开头的选项符,则调用共idToTemplate获取模板
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
//如果没有template选项,则使用getOuterHTML方法从用户提供的el选项中获取模板
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
//compileToFunctions可以将模板编译成渲染函数并设置在this.$options上
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`vue ${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
运行时版
运行时版的vm.$mount没有编译步骤,它会默认实例上已存在渲染函数,如果不存在会设置一个。并且这个渲染函数在执行时会返会一个空节点VNode,以保证执行时不会因为函数不存在而报错
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
//将Vue.js实例挂载到DOM元素上,当数据(状态)发生变化时依然可以渲染到指定DOM元素中
export function mountComponent (
vm: Component,
el: ?Element,
hydrating?: boolean
): Component {
vm.$el = el
if (!vm.$options.render) {
//实例不存在渲染函数,设置默认渲染函数createEmptyVNode,该函数执行后会返回一个注释类型的VNode节点
vm.$options.render = createEmptyVNode
if (process.env.NODE_ENV !== 'production') {
//开发环境下发出警告
}
}
//触发生命周期钩子
callHook(vm, 'beforeMount')
let updateComponent
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () => {
/*
vm._render 执行渲染函数,得到一份最新的VNode节点树
vm._update 调用虚拟DOM的patch方法来执行节点的对比与渲染操作
这一句其实就是一次渲染操作
*/
vm._update(vm._render(), hydrating)
}
}
/*
在介绍Watcher那一节中,如果Watcher的第二个参数是一个函数,则会同时观察函数中所读取的Vue.js
实例上的实例数据
*/
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted')
}
return vm
}
vm.$nextTick
前置:在 Vue 之中,当状态发生变化,会通知依赖这个状态的所有 watcher,然后触发虚拟 DOM 渲染流程。在 watcher 触发渲染这个操作并不是同步的,它是异步的。Vue 在内部有一个队列,每当需要渲染时,就将要渲染的 watcher 推送到这个队列,下一次事件循环再统一清空队列。
用法:将回调延迟到下次DOM更新周期之后执行。(主要就是开发中有一种场景,就是更新了数据之后,需要对新的DOM做一些操作,但是这时获取不到更新后的DOM)
注意,这个DOM更新周期之后的意思是指下次微任务执行时更新DOM,vm.$nextTick其实就是将回调添加到微任务中(只有在特殊情况下才会降级成宏任务,默认是添加到微任务),所以如果使用vm.$nextTick来获取更新后的DOM,要注意顺序问题,无论是DOM更新后的回调还是是用vm.$nextTick注册的回调,都是向微任务中添加任务,所以是先来先服务。
事实上,更新DOM的回调也是使用vm.$nextTick来注册到微任务里的
示例:
new Vue({
...
methods:{
example:function(){
//修改数据
this.message='change'
//DOM还没有更新,要在更改数据后才能使用vm.$nextTick注册回调
//如果是先使用vm.$nextTick注册回调,再修改数据,是获取不到最新的DOM的
this.$nextTick(function(){
//DOM更新了
//this绑定到当前实例
this.doSomething()
})
}
}
})
//宏任务执行比微任务晚,所以setTimeout里可以获取到最新的DOM
new Vue({
...
methods:{
example:function(){
//使用setTimeout向宏任务注册回调
setTimeout(_ =>{
//DOM更新了
},0)
//修改数据
this.message='change'
}
}
})
原理:
当调用nextTick时,回调函数被添加到callbacks,然后判断任务队列中是否已经添加任务,没添加就需要执行timerFunc向任务队列里添加任务,如果已经添加就无需再添加,无需添加重复任务(被添加到任务队列的任务只需执行一次),然后当任务被执行时,就会依次执行callbacks中的所有回调
//isUsingMicroTask来判断是否使用微任务方法
export let isUsingMicroTask = false
//callbacks存储用户注册的回调
const callbacks = []
//pending来保证每次只向任务队列添加一个任务
let pending = false
//清空callbacks
function flushCallbacks () {
pending = false
const copies = callbacks.slice(0)
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]()
}
}
//异步执行函数
let timerFunc
/*
有Promise、MutationObserver以及setImmediate、setTimeout尝试得到timerFunc的方法,
优先使用Promise,在Promise不存在的情况下使用MutationObserver,这两个方法都会在
microtask中执行,会比宏任务更早执行,所以优先使用。如果上述两种方法都不支持的环境
则会使用setImmediate、setTimeout,这两种属于宏任务,比微任务慢执行
*/
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
//ctx是上下文
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
//cb回调函数push进callbacks函数
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
//pending为false,说明本轮事件循环还没执行过timerFunc()
if (!pending) {
pending = true
timerFunc()
}
// $flow-disable-line 没传入回调函数
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
全局API
全局API是直接在Vue
上挂载方法
Vue.extend = function (extendOptions: Object): Function {
...
}
Vue.extend(options)
用法:使用基础Vue构建一个“子类”,参数是一个包含组件选项”的对象
参数:{Object}options
示例:
<div id="test"></div>
//创建构造器
var Profile=Vue.extend({
template:'<p>{{firstName}}-{{lastName}}</p>',
data:function(){
return {
firstName:'chen',
lastName:'yiwei'
}
}
})
//创建Profile实例,并挂载在一个元素上
new Profile().$mount('#test')
//结果
<p>chen--yiwei</p>
实现原理:
其实原理并不复杂,就是创建一个Sub函数继承父类,利用了父类的cid缓存子类,依次继承父类的原型、父类的option选项,若有props或者computed就将它们初始化
export function initExtend (Vue: GlobalAPI) {
/**
* Each instance constructor, including Vue, has a unique
* cid. This enables us to create wrapped "child
* constructors" for prototypal inheritance and cache them.
*/
Vue.cid = 0
let cid = 1
/**
* Class inheritance
*/
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
//如果已经缓存过了,就直接返回
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
const name = extendOptions.name || Super.options.name
if (process.env.NODE_ENV !== 'production' && name) {
validateComponentName(name)
}
const Sub = function VueComponent (options) {
//到时回过头看看 这一句,this._init是new Vue()的时候初始化用的
this._init(options)
}
//这三句是实现子类继承父类的原型
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
/*
* 这两句是实现父类的options选项继承到子类中
* mergeOptions方法是将两个选项合成一个
*/
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub['super'] = Super
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub)
}
if (Sub.options.computed) {
initComputed(Sub)
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend
Sub.mixin = Super.mixin
Sub.use = Super.use
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type]
})
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options
Sub.extendOptions = extendOptions
Sub.sealedOptions = extend({}, Sub.options)
// cache constructor 缓存Sub,因为反复调用Vue.extend应该返回同一个结果
cachedCtors[SuperId] = Sub
return Sub
}
}
//初始化props的作用是将key代理到_props
//比如vm.name实际上访问的是Sub.prototype._props.name
function initProps (Comp) {
const props = Comp.options.props
for (const key in props) {
proxy(Comp.prototype, `_props`, key)
}
}
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
//初始化computed就是遍历computed对象,将对象里每一项都定义一遍
function initComputed (Comp) {
const computed = Comp.options.computed
for (const key in computed) {
defineComputed(Comp.prototype, key, computed[key])
}
}
这个 defineComputed后面补充。。。。。。。
Vue.nextTick([callback,context])
用法:在下次DOM
更新循环结束之后执行延迟回调,修改数据之后立即使用这个方法获取更新后的DOM
参数:{Function}[callback]
{Object}[context]
示例:
//修改数据
vm.msg='hello'
//DOM还没有更新
Vue.nextTick(function(){
//DOM更新后的操作~~
})
//也可以作为一个Promise使用
Vue.nextTick().then(function(){ //DOM更新后的操作~~ })
原理:Vue.nextTick
实现原理和vm.$nextTick
是一样的
import {nextTick} from '../util/index'
Vue.nextTick=nextTick
Vue.set(target,key,value)
用法:设置对象属性。如果对象是响应式的,确保属性被创建后也是响应式的,同时触发视图更新。这个方法主要用于避开Vue不能检测到属性被添加的限制(注意这个对象不能是Vue.js实例或者Vue.js实例的根数据对象?????)
参数:{Object|Array}target
{string|number}key
{any}value
返回值:设置的值
原理:和vm.delete的实现原理相同
import { set } from '../observer/index'
Vue.set = set
Vue.delete(target,key)
用法:删除对象属性。如果对象是响应式的,确保属性被删除后能触发视图更新。这个方法主要用于避开Vue不能检测到属性被删除的限制
import { delete } from '../observer/index'
Vue.delete = delete
Vue.directive/filter/component(id,[definition])
用法:注册或获取全局指令、全局过滤器、全局组件(注册组件时,还会自动使用给定的id设置组件的名称),注意比如全局指令,这里只是注册或者获取全局指令,并不是让其生效,其他两个API也是同样的道理。
参数:{string}id
{Function|Object}[definition]
示例:
//Vue.directive
//注册指令
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')
//Vue.filter
//注册
Vue.filter('my-filter',function(value){ //返回处理后的值 })
//getter方法,返回已注册的过滤器
var myFilter=Vue.filter('my-filter')
//过滤器可以用在双括号插值和v-bind表达式
{{message|capitalize}}
<div v-bind:id="rawId|formatId"></div>
//Vue.component
//注册组件,传入一个扩展过的构造器
Vue.component('my-component',Vue.extend({ }))
//注册组件,传入一个选项对象(自动调用Vue.extend)
Vue.component('my-component',{ })
//获取注册组件(返回构造器)
var myComponent=Vue.component('my-component')
原理:Vue.directive、Vue.filter、Vue.component
的源码
// shared/constants.js
export const ASSET_TYPES = [
'component',
'directive',
'filter'
]
// src/core/global-api/index.js
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
Vue.options._base = Vue
initAssetRegisters(Vue)
// src/core/global-api/assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string,
definition: Function | Object
): Function | Object | void {
//definition参数不存在,是注册操作
if (!definition) {
return this.options[type + 's'][id]
} else {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
//如果是Object类型,就调用Vue.extend变成Vue的子类
if (type === 'component' && isPlainObject(definition)) {
definition.name = definition.name || id
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
Vue.use(plugin)
用法:安装Vue.js插件。
参数:{Object|Function}plugin
原理:
如果插件是一个对象则必须提供install方法,如果插件是一个函数,它会被作为install方法。调用install方法时,会将Vue作为参数传入,install方法被同一个插件多次使用,插件也只会安装一次。
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
//不重复安装插件
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// toArray方法就是将类数组转成真正的数组,并且得到除了第一个参数外的其他参数
const args = toArray(arguments, 1)
//把Vue作为args第一个参数
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
Vue.mixin(mixin)
用法:全局注册一个混入,影响注册之后创建的每个Vue.js实例。插件作者可以使用混入向组件注入自定义行为。(不推荐在应用代码中使用)
参数:{Object}mixin
示例:
Vue.mixin({
created:function(){
var myOption=this.$option.myOption,
if(myOption)
console.log(myOption)
}
}
})
new Vue({
myOption:'hello'
})
//hello
原理:
实现原理只是将mixin
与Vue
自身的options
属性合并在一起了
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
Vue.compile
作用:编译模板字符串并返回包含渲染函数的对象。(只在完整版中才有效,只有完整版才包含编译器)
参数:{string}template
示例:
var res=Vue.compile('<div><span>{{msg}}</span></div>')
new Vue({
data:{
msg:'hello'
},
render:res.render
})
源码:
Vue.compile=compileToFunctions
Vue.version
作用:提供字符串形式的Vue.js安装版本号。
示例:
var version=Number(Vue.version.split('.')[0])
if(version===2){
//2.x.x版本
}else if(version===1){
//1.x.x版本
}else{
//不支持的Vue.js版本
}