数据相关
通过 stateMixin 方法中挂载到 vue 原型上
1 2 3 4 5 export function stateMixin (Vue ) { Vue .prototype .$set = set; Vue .prototype .$delete = del; Vue .prototype .$watch = function (expOrFn, cb, options ) {}; }
vm.$set
全局 Vue.set 的别名,其用法相同。对象不能是 Vue 实例,或者 Vue 实例的根数据对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 export function set (target, key, val ) { if ( process.env .NODE_ENV !== "production" && (isUndef (target) || isPrimitive (target)) ) { warn ( `Cannot set reactive property on undefined, null, or primitive value: ${target} ` ); } if (Array .isArray (target) && isValidArrayIndex (key)) { target.length = Math .max (target.length , key); target.splice (key, 1 , val); return val; } if (key in target && !(key in Object .prototype )) { target[key] = val; return val; } const ob = target.__ob__ ; 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; } if (!ob) { target[key] = val; return val; } defineReactive (ob.value , key, val); ob.dep .notify (); return val; }
vm.$delete
全局 Vue.delete 的别名,其用法相同。删除对象的属性。如果对象是响应式的,确保删除能触发更新视图。这个方法主要用于避开 Vue 不能检测到属性被删除的限制
目标对象不能是一个 Vue 实例或 Vue 实例的根数据对象
原理基本与 set 相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 export function del (target, key ) { if ( process.env .NODE_ENV !== "production" && (isUndef (target) || isPrimitive (target)) ) { warn ( `Cannot delete reactive property on undefined, null, or primitive value: ${target} ` ); } if (Array .isArray (target) && isValidArrayIndex (key)) { target.splice (key, 1 ); return ; } const ob = target.__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 ; } if (!hasOwn (target, key)) { return ; } delete target[key]; if (!ob) { return ; } ob.dep .notify (); }
vm.$watch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 vm.$watch( "a.b.c" , function (newVal, oldVal ) { if (unwatch) { unwatch (); } }, { deep : true , immediate : true , } ); vm.$watch( function ( ) { return this .a + this .b ; }, function (newVal, oldVal ) { } ); var unwatch = vm.$watch("a" , cb);unwatch ();Vue .prototype .$watch = function (expOrFn, cb, options ) { const vm = this ; if (isPlainObject (cb)) { return createWatcher (vm, expOrFn, cb, options); } options = options || {}; options.user = true ; const watcher = new Watcher (vm, expOrFn, cb, options); if (options.immediate ) { cb.call (vm, watcher.value ); } return function unwatchFn ( ) { watcher.teardown (); }; vm.$watch( function ( ) { return this .a + this .b ; }, function (newVal, oldVal ) { } ); }; export default class Watcher { constructor ( ) { this .value = this .get (); } get ( ) { if (this .deep ) { traverse (value); } return value; } } const seenObjects = new Set ();export function traverse (val ) { _traverse (val, seenObjects); seenObjects.clear (); } function _traverse (val, seen ) { let i, keys; const isA = Array .isArray (val); if ( (!isA && !isObject (val)) || Object .isFrozen (val) || val instanceof VNode ) { return ; } if (val.__ob__ ) { const depId = val.__ob__ .dep .id ; if (seen.has (depId)) { return ; } seen.add (depId); } if (isA) { i = val.length ; while (i--) _traverse (val[i], seen); } else { keys = Object .keys (val); i = keys.length ; while (i--) _traverse (val[keys[i]], seen); } }
事件相关
通过 eventsMixin 方法中挂载到 vue 原型上
1 2 3 4 5 6 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
监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数 定义一个事件中心,通过 $on 订阅事件,将事件存储在事件中心里面,然后通过 $emit 触发事件中心里面存储的订阅事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Vue .prototype .$on = function (event, fn ) { const vm = this ; if (Array .isArray (event)) { for (let i = 0 , l = event.length ; i < l; i++) { this .$on(event[i], fn); } } else { (vm._events [event] || (vm._events [event] = [])).push (fn); } return vm; };
vm.$emit
触发当前实例上的事件。附加参数都会传给监听器回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Vue .prototype .$emit = function (event ) { const vm = this ; let cbs = vm._events [event]; if (cbs) { cbs = cbs.length > 1 ? toArray (cbs) : cbs; const args = toArray (arguments , 1 ); for (let i = 0 , l = cbs.length ; i < l; i++) { try { cbs[i].apply (vm, args); } catch (e) { handleError (e, vm, `event handler for "${event} "` ); } } } return vm; };
vm.$off
移除自定义事件监听器
如果没有提供参数,则移除所有的事件监听器
如果只提供了事件,则移除该事件所有的监听器
如果同时提供了事件与回调,则只移除这个回调的监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Vue .prototype .$off = function (event, fn ) { const vm = this ; if (!arguments .length ) { vm._events = Object .create (null ); return vm; } if (Array .isArray (event)) { for (let i = 0 , l = event.length ; i < l; i++) { this .$off(event[i], fn); } return vm; } const cbs = vm._events [event]; if (!cbs) { return vm; } if (!fn) { vm._events [event] = null ; return vm; } if (fn) { 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.$once
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除 定义一个子函数,用这个子函数来替换原本订阅事件所对应的回调,当触发订阅事件时,其实执行的是这个子函数,然后再子函数内部先把该订阅移除,再执行原本的回调,以此来达到只触发一次的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Vue .prototype .$once = function (event, fn ) { const vm = this ; function on ( ) { vm.$off(event, on); fn.apply (vm, arguments ); } on.fn = fn; vm.$on(event, on); return vm; };
生命周期相关 1 2 3 4 5 6 7 8 export function lifecycleMixin (Vue ) { Vue .prototype .$forceUpdate = function ( ) {}; Vue .prototype .$destroy = function (fn ) {}; } export function renderMixin (Vue ) { Vue .prototype .$nextTick = function (fn ) {}; }
vm.$mount
在跨平台的代码中挂载到 Vue 原型上的
vm.$mount([elementOrSelector]
),这个方法返回实例自身,因而可以链式调用其它实例方法
如果 Vue 实例在实例化时没有收到 el 选项,则它处于 未挂载 状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例
如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素,并且必须使用原生 DOM API 把它插入文档中
vm.$forceUpdate
在 lifecycleMixin 函数中挂载到 Vue 原型上的
迫使 Vue 实例重新渲染。注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件
实例 watcher 执行了 update 方法
1 2 3 4 5 6 Vue .prototype .$forceUpdate = function ( ) { const vm = this ; if (vm._watcher ) { vm._watcher .update (); } };
vm.$nextTick
在 renderMixin 函数中挂载到 Vue 原型上的
vm.$nextTick 是全局 Vue.nextTick 的别名,其用法相同
将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个事件队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到事件队列中一次
能力检测
内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替
宏任务耗费的时间是大于微任务的,在浏览器支持的情况下,优先使用微任务。如果浏览器不支持微任务,使用宏任务;但是,各种宏任务之间也有效率的不同,需要根据浏览器的支持情况,使用不同的宏任务
根据能力检测以不同方式执行回调队列,两个注意点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 (function ( ) { let timerFunc; 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 { timerFunc = () => { setTimeout (flushCallbacks, 0 ); }; } }); const callbacks = [];let pending = false ; function flushCallbacks ( ) { pending = false ; const copies = callbacks.slice (0 ); callbacks.length = 0 ; for (let i = 0 ; i < copies.length ; i++) { copies[i](); } } export function nextTick (cb, ctx ) { let _resolve; callbacks.push (() => { if (cb) { try { cb.call (ctx); } catch (e) { handleError (e, ctx, "nextTick" ); } } else if (_resolve) { _resolve (ctx); } }); if (!pending) { pending = true ; timerFunc (); } if (!cb && typeof Promise !== "undefined" ) { return new Promise ((resolve ) => { _resolve = resolve; }); } }
vm.$destory
在 lifecycleMixin 函数中挂载到 Vue 原型上的