深入浅出Vue.js(四) 整体流程
整体流程
vm.$set(target,key,val)
function set(target,key,val){
//有效的下标
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)){
return val
}
if(!ob){
target[key] = val
return val
}
defineReactive(ob.value,key,val)
ob.dep.notify()
return val
}
vm.$delete(target,key)
function del(target,key){
if(Array.isArray(target) && isValidArrayIndex(key)){
target.splice(key,1)
return
}
const ob = target.__ob__
if(target.isVue || (ob && ob.vmCount)){
return
}
// 如果key不是target自身的属性,则终止程序继续执行
if(!hasOwn(target,key)){
return
}
delete target[key]
// 如果ob不存在(判断target是不是一个响应式数据),则直接终止程序
if(!ob){
return
}
ob.dep.notify()
}
vm.$on(event,fn)
Vue.prototype.$on = function(event,fn){
const vm = this
if(Array.isArray(event)){
for(let i = 0,len = event.length;i < len;i++){
this.$on(event[i],fn)
}
}else{
(vm._events[event] || (vm._events[event] == [])).push(fn)
}
return vm
}
vm._events是一个对象,在执行new Vue()时,Vue会执行this._init()方法进行一系列的初始化操作,其中就会在Vue.js的实例上创建一个_events属性,用来存储事件。vm._events = Object.create(null)。
vm.$off(event,fn)
移除自定义事件监听器
- 如果没有提供参数,则移除所有的事件监听器。
- 如果只提供了事件,则移除该事件所有的监听器。
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
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,len = event.length;i < len;i++){
this.$off(event[i],fn)
}
return vm
}
const cbs = vm._events[event]
if(!cbs){
return vm
}
if(arguments.length == 1){
vm._events[event] = null
return vm
}
if(fn){
const cbs = vm._events[event]
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(event,fn)
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
}
vm.$emit(event[,...args])
Vue.prototype.$emit = function(event,...args){
const vm = this
let cbs = tis._events[event]
if(cbs){
for(let i = 0;i < cbs.length;i++){
try{
cbs[i].apply(vm,args)
}catch(err){
console.log(err)
}
}
}
}
vm..$forceUpdate()
Vue.prototype.$forceUpdate = function(){
const vm = this
if(vm._watcher){
wm._watcher.update()
}
}
vm.$destory()
function remove(arr,item){
if(arr.length){
const index = arr.indexOf(item)
if(index > -1){
arr.splice(index,1)
}
}
}
Vue.prototype.$destroy = function(){
const vm = this
if(vm._isBeingDestroyed){
return
}
callHook(vm,'beforeDestroy')
vm._isBeingDestroyed = true
const parent = vm.$parent
if(parent && !parent._isBeingDestroyed && !vm.$options.abstract){
remove(parent.$children,vm)
}
if(vm._watcher){
vm._watcher.teardown()
}
let i = vm._watchers.length
while(i--){
vm._watchers[i].teardown()
}
vm._isDestroyed = true
vm.__patch__(vm.vnode,null)
callHook(vm,'destroyed')
vm.$off()
}
vm.$nextTick([fn])
const callbacks = []
let pending = false
function flushCallbacks(){
pending = false
const copies = callbacks.splice(0)
callbacks.length = 0
for(let i = 0;i < copies.length;i++){
copies[i]()
}
}
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
if(typeof setImmediate !=='undefined' && isNative(setImmediate)){
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
}else if(typeof MessageChannel !=='undefined' && (isNative(MessageChannel) || MessageChannel.toString() === '[object MessageChannelConstructor]')){
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
}else{
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0);
}
}
if(typeof Promise !== 'undefined' && isNative(Promise)){
const p = Promise.resolve()
microTimerFunc = () => {
p.then(flushCallbacks)
}
}else{
microTimerFunc = macroTimerFunc
}
function withMacroTask(fn){
return fn._withTask || (fn._withTask = function(){
useMacroTask = true
const res = fn.apply(null,arguments)
useMacroTask = false
return res
})
}
function nextTick(cb,ctx){//参数:回调函数,上下文
let _resolve
callbacks.push(()=>{
if(cb){
cb.call(ctx)
}else if(_resolve){
_resolve(ctx)
}
})
if(!pending){
pending = true
if(useMacroTask){
macroTimerFunc()
}else{
microTimerFunc()
}
}
if(!cb && typeof Promise !=='undefined'){
return new Promise(resolve => {
_resolve = resolve
})
}
}
Vue.prototype.$nextTick = function(fn){
return nextTick(fn,this)
}
vm.$mount()
如果vue.js实例在实例化时没有收到el选项,则它处于未挂载状态,没有关联的DOM元素。我们可以使用vm.$mount手动挂载一个未挂载的实例。如果没有提供elementOrSelector参数,模板将被渲染为文档之外的元素,并且必须使用原生的DOM的API把它插入文档中。这个方法返回实例自身,因而可以链式调用其他实例方法。
完整版和只包含运行时版本之间的差异在于是否有编译器,而是否有编译器的差异主要在于vm.$mount方法的表现形式。在只包含运行时的构建版本中,vm.$mount的作用如前面介绍的那样。而在完整的构建版本中,vm.$mount的作用会稍有不同,它会首先检查template或el选项所提供的模板是否已经转换成渲染函数(render函数)。如果没有,则立即进入编译过程,将模板编译为渲染函数,完成之后再进入挂载与渲染的流程中。
只包含运行时的vm.$mount没有编译步骤,它会默认实例上已经存在的渲染函数,如果不存在,则会设置一个。并且,这个渲染函数在执行时会返回一个空节点VNode,以保证执行时不会因为函数不存在而报错。同时如果在开发环境下运行,vue.js会触发警告,提示当前使用的是只包含运行时版本,会让我们提供渲染函数,或者去使用完整的构建版本。
完整版vm.$mount的实现原理
function query(el){
if(typeof el === 'string'){
const selected = document.querySelector(el)
if(!selected){
return document.createElement('div')
}
return selected
}else{
return el
}
}
function getOuterHTML(el){
if(el.outerHTML){
return el.outerHTML
}else{
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
function idToTemplate(id){
const el = query(id)
return el && el.innerHTML
}
function createFunction(render){
return new Function(render)
}
function compileToFunctions(tempalte,options,vm){
options = extend({},options)
const key = options.delimiters ? String(options.delimiters) + tempalte : tempalte
if(cache[key]){
return cache[key]
}
const compiled = compile(tempalte,options)
const res = {}
res.render = createFunction(compiled.render)
return (cache[key] = res)
}
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(el){ //函数劫持
el = el && query(el)
const options = this.options
if(!options.render){
let template = options.tempalte
if(template){
if(typeof template == 'string'){
if(template.charAt(0) == '#'){
template = idToTemplate(template)
}
}else if(template.nodeType){
template = template.innerHTML
}else{
return this
}
}else if(el){
template = getOuterHTML(el)
}
if(template){
const {render} = compileToFunctions(template,{...},this)
options.render = render
}
}
return mount.call(this,el)
}
只包含运行时版本的vm.$mount的实现原理
function mountComponent(vm,el){
if(!vm.$options.render){
vm.$options.render = createEmptyVNode()
}
callHook(vm,'beforeMount')
vm._watcher = new Watcher(vm,() => {
vm._update(vm._render())
},noop)
callHook(vm,'mounted')
return vm
}
Vue.prototype.$mount = function(el){
el = el && inBrowser ? query(el) : undefined
return mountComponent(this,el)
}
Vue.directive方法接受两个参数id和definition,它可以注册或获取指令,这取决于definition参数是否存在。如果definition参数不存在,则使用id从this.options['directives']中读出指令并将其返回;如果definition参数存在,则说明是注册操作,那么近而判断definition参数的类型是否是函数。
如果是函数,则默认监听bind和update两个事件,所以代码中将definition函数分别赋值给对象中的bind和update这两个方法,并使用这个对象覆盖definition;如果definition不是函数则说明它是用户自定义的指令对象,此时不需要做任何操作。直接将用户提供的指令对象保存在this.options['directives']上即可。
Vue.options = Object.create(null)
Vue.options['directives'] = Object.create(null)
Vue.directive = function(id,definition){
if(!definition){
return this.options['directives'][id]
}else{
if(typeof definition == 'function'){
definition = {
bind:definition,
update:definition
}
}
this.options['directives'][id] = definition
return definition
}
}
生命周期
- new Vue()到created之间的阶段叫做初始化阶段。这个阶段的主要目的是在vue.js实例上初始化一些属性、事件以及响应式数据。如:props、methods、data、computed、watch、provide和inject等
- 在created钩子函数与beforeMount钩子函数之间的阶段是模板编译阶段。这个阶段的主要目的是将模板编译为渲染函数。
- beforeMount钩子函数到mounted钩子函数之间是挂载阶段。在这个阶段,vue.js会将其实例挂载到DOM元素上,在挂载的过程中,vue.js会开启watcher来持续追踪依赖的变化。
- 应用调用vm.$destroy方法后,vue.js的生命周期会进入卸载阶段。在这个阶段,vue.js会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器。
未完待续...
以自己现在的努力程度,还没有资格和别人拼天赋

浙公网安备 33010602011771号