vue 源码解析(实例化前) - 初始化全局 API(最终章)
栏目: JavaScript · 发布时间: 5年前
内容简介:上一章的最后,总结了这篇文章,会涉及到组件更新的实现:
上一章的最后,总结了 Watcher
的实现,对于 vue
实例化前要做的事情,在这一章,就要终结了,所以这一篇,也就是 vue
实例化前的最终章。
这篇文章,会涉及到 vue
一些事件的实现:
$on
、
$once
、
$off
、
$emit
;
组件更新的实现:
updated
、
$forceUpdate
、
$destroy
;
渲染 dom
的实现:
$nextTick
、
render
。
实例方法 / 事件
eventsMixin(Vue); 复制代码
在该函数里面,,就是 $on
、 $once
、 $off
、 $emit
的实现,只是在这几个方法实现的前面,有一个正则:
var hookRE = /^hook:/; 复制代码
用来判断是否是以 hook:
开头的事件。
$on
对于
$on
的实现,其实就是一个发布订阅关系中,一个充当订阅的角色,和 $emit
是配合使用:
Vue.prototype.$on = function (event, fn) { var vm = this; if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { vm.$on(event[i], fn); } } else { (vm._events[event] || (vm._events[event] = [])).push(fn); if (hookRE.test(event)) { vm._hasHookEvent = true; } } return vm }; 复制代码
在 $on
当中,一开始的时候会保存当前的 this
指针,然后检查在调用 $on
方法时候,接收到的 event
参数是否是数组,如果是数组,就循环调用 $on
方法,一直到发现 event
不是数组为止;
然后检查 vue
的构造函数下的 _events
对象是否存在当前的事件,不存在就创建一个数组,存在的话,把订阅的回调 fn
添加到 _events
的当前事件属性的数组当中;
检查当前的事件是否是以 hook:
开头的事件,如果是的话,就设置当前 vue
的 _hasHookEvent
的状态为 true
。
$once
$once
就是用来监听一个自定义事件,但是只触发一次,在第一次触发之后移除监听器。
Vue.prototype.$once = function (event, fn) { var vm = this; function on() { vm.$off(event, on); fn.apply(vm, arguments); } on.fn = fn; vm.$on(event, on); return vm }; 复制代码
这里就比较简单了, $once
接收到的参数,和 $on
一样,其实在 $once
当中,直接绑定的也是 $on
方法;
在发布订阅的时候,直接执行的是在 $once
内部的 on
方法;
在 on
方法中,调用 $off
移除了事件监听器;
最后把 $once
接收到的回调函数 fn
的 this
指向 vue
构造函数,把在 $on
接收到的参数,传给 $once
的回调函数。
$off
$off
用来移除自定义事件监听器。
Vue.prototype.$off = function (event, fn) { var vm = this; // all if (!arguments.length) { vm._events = Object.create(null); return vm } // array of events if (Array.isArray(event)) { for (var i = 0, l = event.length; i < l; i++) { vm.$off(event[i], fn); } return vm } // specific event var cbs = vm._events[event]; if (!cbs) { return vm } if (!fn) { vm._events[event] = null; return vm } if (fn) { var cb; var i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break } } } return vm }; 复制代码
当 $off
不接收任何参数的时候,代表要把 vue
构造函数内的所有事件监听器全部卸载;
如果接收到的 event
是数组,那就循环调用 $off
去分别卸载每一个数组内的事件监听器;
如果当前的事件不存在,就直接返回;
如果不存在回调函数的话,直接把当前事件给移除;
如果存在回调的话,检查当前的订阅数组,删除当前回调函数,并退出。
$emit
Vue.prototype.$emit = function (event) { var vm = this; { var 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 + "\"." ); } } var cbs = vm._events[event]; if (cbs) { cbs = cbs.length > 1 ? toArray(cbs) : cbs; var args = toArray(arguments, 1); for (var 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 }; 复制代码
在发布订阅的时候,要检查当前发布事件的命名问题;
如果当前的要发布的事件,存在回调,就依次发布事件到订阅的事件里面。
知识点:可以通过源码发现,上面的所有事件,都支持链式调用
组件更新的实现
updated
_update
用来更新组件信息
Vue.prototype._update = function (vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; var restoreActiveInstance = setActiveInstance(vm); vm._vnode = vnode; if (!prevVnode) { vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false); } else { vm.$el = vm.__patch__(prevVnode, vnode); } restoreActiveInstance(); if (prevEl) { prevEl.__vue__ = null; } if (vm.$el) { vm.$el.__vue__ = vm; } if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el; } }; 复制代码
把当前的 $el
和 _vnode
保存起来;
调用 restoreActiveInstance
返回一个结果,保存到 restoreActiveInstance
变量当中;
var activeInstance = null; function setActiveInstance(vm) { var prevActiveInstance = activeInstance; activeInstance = vm; return function () { activeInstance = prevActiveInstance; } } 复制代码
实现的就是在更新的时候,使用接收到的 vue
实例,使用完毕后调用 return
回去的函数,替换回原来的实例对象;
检查当前的 vNode
是否被渲染,如果没渲染过,就初始化渲染,否则就做更新;
执行 restoreActiveInstance
把 activeInstance
换成原来的值;
其实就是更新完 node
后,把 activeInstance
置空。
如果存在渲染节点,那么就给当前的 vm.$el
添加一个 __vue__
属性,默认值为 null
;
__vue__
指向更新时接收到的 vue
实例;
如果当前实例的父级 $parent
是 HOC,那么也更新其 $el
$forceUpdate
$forceUpdate
迫使 Vue 实例重新渲染。
注意它仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
Vue.prototype.$forceUpdate = function () { var vm = this; if (vm._watcher) { vm._watcher.update(); } }; 复制代码
这里比较简单了,就是一个更新当前实例的监听, watcher
的实现,在上一章写过,入口: Vue 源码解析(实例化前) - 初始化全局API(三)
,这里介绍了 watcher
所有的实现。
$destroy
$destroy
完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
触发 beforeDestroy
和 destroyed
的钩子。
Vue.prototype.$destroy = function () { var vm = this; if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy'); vm._isBeingDestroyed = true; var parent = vm.$parent; if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm); } if (vm._watcher) { vm._watcher.teardown(); } var i = vm._watchers.length; while (i--) { vm._watchers[i].teardown(); } if (vm._data.__ob__) { vm._data.__ob__.vmCount--; } vm._isDestroyed = true; vm.__patch__(vm._vnode, null); callHook(vm, 'destroyed'); vm.$off(); if (vm.$el) { vm.$el.__vue__ = null; } if (vm.$vnode) { vm.$vnode.parent = null; } }; 复制代码
检查当前的 vue
实例是否正在卸载;
注册一个 beforeDestroy
钩子:
function callHook(vm, hook) { pushTarget(); var handlers = vm.$options[hook]; if (handlers) { for (var i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm); } catch (e) { handleError(e, vm, (hook + " hook")); } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook); } popTarget(); } 复制代码
检查当前的实例化对象中,有没有当前的 hook
钩子,如果在实例化 Vue
构造函数的时候,配置属性里面没有当前钩子,就跳过;如果有的话,执行。
执行完 beforeDestroy
后,开始从当前实例化对象的父级去移除当前对象;
卸载当前实例上的 watcher
对象;
从数据对象中移除引用冻结对象可能没有观察者;
在当前渲染树上调用销毁钩子;
执行 destroyed
钩子;
卸载所有的事件监听器;
把和当前有关系的一些属性,全设为 null
。
组件渲染
这里最主要做的就是有关组件渲染的方法, $nextTick
和 组件的 render
钩子。
$nextTick
将回调延迟到下次 DOM
更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM
更新。它跟全局方法 Vue.nextTick
一样,不同的是回调的 this
自动绑定到调用它的实例上。
2.1.0 起新增:如果没有提供回调且在支持 Promise
的环境中,则返回一个 Promise
。请注意 Vue
不自带 Promise
的 polyfill
,所以如果你的目标浏览器不是原生支持 Promise
(IE:你们都看我干嘛),你得自行 polyfill
。
Vue.prototype.$nextTick = function (fn) { return nextTick(fn, this) }; 复制代码
nextTick
在之前 Vue 源码解析(实例化前) - 初始化全局API(二)
章节中,做过讲解,不了解的大家可以过去看一下。
render
Vue.prototype._render = function () { var vm = this; var ref = vm.$options; var render = ref.render; var _parentVnode = ref._parentVnode; if (_parentVnode) { vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject; } vm.$vnode = _parentVnode; var vnode; try { vnode = render.call(vm._renderProxy, vm.$createElement); } catch (e) { handleError(e, vm, "render"); if (vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e); } catch (e) { handleError(e, vm, "renderError"); vnode = vm._vnode; } } else { vnode = vm._vnode; } } if (!(vnode instanceof VNode)) { if (Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ); } vnode = createEmptyVNode(); } // set parent vnode.parent = _parentVnode; return vnode }; 复制代码
检查配置的属性当中,是否存在 _parentVnode
属性,如果存在就把他的 data.scopedSlots
指向实例化对象的 $scopedSlots
;
点击查看
$scopedSlots
的使用
设置父 parent Vnode
,这允许渲染函数访问占位符节点上的数据;
把当前的 render
的 this
指向 vm._renderProxy
并把 vm.$createElement
当做参数传给 render
;
当然,在渲染的过程当中,如果报错,那么就返回错误呈现结果或以前的Vnode,以防止呈现错误导致空白组件。
结束语
这一篇,基本上会讲解的就是这部分内容,在这一篇文章写完后,会总结一篇 vue 生命周期方法的实现
的文章和一篇 vue 实例化前的源码汇总
,由于涉及的知识点太多,分开了很多章去写,理解和学习起 vue
的实现理念来,不是很方便,但是如果大家想了解作者到底是怎么实现的 vue
还是建议大家挨个文章就看一下。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Vue 源码解析(实例化前) - 初始化全局API(二)
- Vue 源码解析(实例化前) - 初始化全局API(三)
- C++ 的一大误区——深入解释直接初始化与复制初始化的区别
- 初始化监听端口
- 类初始化导致死锁
- nodejs源码—初始化
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Beginning Google Maps API 3
Gabriel Svennerberg / Apress / 2010-07-27 / $39.99
This book is about the next generation of the Google Maps API. It will provide the reader with the skills and knowledge necessary to incorporate Google Maps v3 on web pages in both desktop and mobile ......一起来看看 《Beginning Google Maps API 3》 这本书的介绍吧!