Vue2核心原理(簡易) - 視圖更新(初次渲染)筆記

前言

  • 當數據發生變化 視圖也在發生變化
  • 每一個組件都有一個渲染watchernew Watch()(觀察者模式)
  • 在new Watche()組件開始渲染組件,在render函數生成vnode時,調用了劫持數據getter
  • 這個時候每一個屬性能夠訂閱一個渲染watcher, 在數據發生變化時就會調用劫持數據的setter
  • 在setter裏去經過一個notify去調用渲染watcher中的updateComponent方法去更新視圖

html 和 javascript模板

<div id="app">{{ message }} - {{ arr }}</div><script>var vm = new Vue({data: {message: 'Hello Vue!',arr: [[0]]
    }
})

vm.$mount('#app')setTimeout(() => {
    vm.message = 'vue updater'vm.arr.push(100)
}, 2000)</script>複製代碼

render函數生成vnode 方法

/**
 * @description 建立標籤vnode
 */export function createElement(vm, tag, data = {}, ...children) {return vnode(vm, tag, data, data.key, children, undefined);
}/**
 * @description 建立文本的vnode
 */export function createTextElement(vm, text) {return vnode(vm, undefined, undefined, undefined, undefined, text);
}/** 核心方法 *//**
 * @description 套裝vnode
 */function vnode(vm, tag, data, key, children, text) {return {
        vm,
        tag,
        data,
        key,
        children,
        text,// .....}
}複製代碼
export function renderMixin(Vue){
    Vue.prototype._c = function() {return createElement(this,...arguments)
    }

    Vue.prototype._v = function(text) {return createTextElement(this,text)
    }

    Vue.prototype._s = function(val) {if(typeof val == 'object') return JSON.stringify(val)return val;
    }

    Vue.prototype._render = function(){       const vm = this   let render = vm.$options.render       let vnode = render.call(vm)       return vnode
    }
}複製代碼

初始化

function Vue (options) {/** 初始化 */this._init(options)
}/**
 * @description 擴展原型
 */initMixin(Vue)
renderMixin(Vue)複製代碼
export function initMixin (Vue) {    
    Vue.prototype._init = function (options) {
        vm.$options = options/** 數據初始化 */initState(vm)/** compile */if(vm.$options.el){
            vm.$mount(vm.$options.el);
        } 
    }

    Vue.prototype.$mount = function (el) {const vm = this;const options = vm.$options
        el = document.querySelector(el);
        vm.$el = el;if(!options.render) {let template = options.template;if(!template && el) {
                template = el.outerHTML;
            }// 生成的render函數let render = compileToFunction(template);
            options.render = render;
        }/** 組件掛載 */mountComponent(vm,el)
    }
}複製代碼

組件掛載,初始化渲染watcher

mountComponent文件

import { patch } from './vdom/patch'import Watcher from './observer/watcher'/** 生命週期 */export function lifecycleMixin(Vue) {
  Vue.prototype._update = function(vnode) {const vm = this// patch是將老的vnode和新的vnode作比對 而後生成真實的domvm.$el = patch(vm.$el, vnode)
 	 }
}export function mountComponent(vm, el) {// 更新函數 數據變化後 會再次調用此函數let updateComponent = () => {
        vm._update(vm._render())
    }new Watcher(vm, updateComponent, () => {console.log('視圖更新了')
    }, true)
}複製代碼

patch文件 將vnode生成真實dom

/** patch 文件 這裏只是簡單處理 直接生成真實的dom */export function patch(oldVnode, vnode) {if (oldVnode.nodeType == 1) {const parentElm = oldVnode.parentNodelet elm = createElm(vnode)// 在第一次渲染後 刪除掉節點parentElm.insertBefore(elm, oldVnode.nextSibling)
        parentElm.removeChild(oldVnode)return elm
    }
}function createElm(vnode) {let { tag, data, children, text, vm } = vnodeif (typeof tag === 'string') {
        vnode.el = document.createElement(tag)
        children.forEach(child => {
            vnode.el.appendChild(createElm(child))
        })
    } else {
        vnode.el = document.createTextNode(text)
    }return vnode.el
}複製代碼

Watcher 和 Dep (重點)

Watcher 與 Dep 關聯起來 是靠當組件渲染時 會走數據 數據已劫持 在getter中 每一個屬性 都有Dep 在Dep上加類屬性 Dep.target 在渲染時將Dep.target指向這個渲染Watcher(神來之筆)javascript

Watcher 類

import { popTarget, pushTarget } from './dep'import { queueWatcher } from './scheduler'let id = 0class Watcher {constructor(vm,exprOrFn,cb,options){this.vm = vmthis.exprOrFn = exprOrFnthis.cb = cbthis.options = optionsthis.id = id++// 視圖更新 就是上面的updateComponent方法this.getter = exprOrFn this.deps = [] this.depsId = new Set()this.get()
    }get(){// 將渲染wather指向給dep 在數據getter時 依賴起來 將dep與watcher關聯起來(神來之筆)pushTarget(this)this.getter()// 頁面渲染後將 Dep.target = nullpopTarget()
    }update(){      // 異步更新操做 就是將更新緩存起來(作一些去重, 防抖)而後一塊兒調用,最後仍是調用下方run方法   queueWatcher(this)
    }run(){this.get()
    }    /**
     * @description 將watcher 存儲dep dep也存儲watcher實現雙向雙向存儲, 並作去重處理
     * @description 給每一個屬性都加了個dep屬性,用於存儲這個渲染watcher (同一個watcher會對應多個dep)
     * @description 每一個屬性可能對應多個視圖(多個視圖確定是多個watcher) 一個屬性也要對應多個watcher
     */addDep(dep){let id = dep.id;if(!this.depsId.has(id)){this.depsId.add(id);this.deps.push(dep);
            dep.addSub(this)
        }
    }
}export default Watcher複製代碼

Dep 類

/** 每一個劫持的屬性 加上惟一的標識 */let id = 0/**
 * @description 每一個劫持的屬性 new Dep
 */class Dep {constructor() {this.id = id++this.subs = []
    }/** dep傳給watcher */depend() {if (Dep.target) {
            Dep.target.addDep(this)
        }
    }addSub(watcher) {this.subs.push(watcher)
    }notify() {this.subs.forEach(watcher => watcher.update())
    }

}

Dep.target = nulllet stack = []export function pushTarget(watcher) {
    Dep.target = watcher
    stack.push(watcher)
}export function popTarget() {
    stack.pop()
    Dep.target = stack[stack.length - 1]
}export default Dep複製代碼

數據劫持

  • 是普通的對象, 直接dep.append()
  • 是數組或者對象,再起上單獨增長一個Dep
  • 多層數組要進行遞歸操做

Observer 類(主文件)

import { isObject } from '../utils'import { arrayMethods } from './array'import Dep from './dep'class Observer {constructor(data) {// 看這裏 數據 加了個Depthis.dep = new Dep()Object.defineProperty(data, '__ob__', {value: this,enumerable: false})        /** 數據是數組 */if (Array.isArray(data)) {// 針對數組中使用的方法 如push splice... 修改原數組增長的元素(是對象)進行劫持data.__proto__ = arrayMethods// 初始化 劫持數組中的每一個元素 若是是對象進行劫持this.observeArray(data)return}/** 數據是對象 */this.walk(data)
    }walk(data) {Object.keys(data).forEach(key => {
            defineReactive(data, key, data[key])
        })
    }observeArray(data) {
        data.forEach(item => observe(item))
    }
}/**
 * @description 看這裏 劫持數據只劫持對象 不劫持數組 經過current.__ob__.dep依賴watcehr
 * @description 多層數組 依賴收集 watcher
 */function dependArray(value) {for (let i = 0; i < value.length; i++) {let current = value[i]
        current.__ob__ && current.__ob__.dep.depend()if (Array.isArray(current)) {
            dependArray(current)
        }
    }
}/** 核心方法 *//**
 * @description 劫持對象數據
 */function defineReactive(data, key, value) {let childOb = observe(value)let dep = new Dep()Object.defineProperty(data, key, {get() {            if (Dep.target) {
                dep.depend()// 看這裏 數組進行依賴收集watcherif (childOb) {
                    childOb.dep.depend()// 看這裏 多層數組[[[]]] if (Array.isArray(value)) { dependArray(value) }

                }

            }return value
        },set(newValue) {if (newValue !== value) {
                observe(newValue)
                value = newValue
                dep.notify()
            }
        }
    })
}export function observe(data) {if (!isObject(data)) returnif (data.__ob__) return data.__ob__return new Observer(data)
}複製代碼

arrayMethods調用數組的七個方法時 直接notify()

const oldArrayPrototype = Array.prototypeexport let arrayMethods = Object.create(oldArrayPrototype)/**
 * @description 改變原數組的方法
 */const methods = ['push','pop','unshift','shift','reverse','sort','splice']

methods.forEach(method => {
    arrayMethods[method] = function (...args) {
        oldArrayPrototype[method].call(this, ...args)        let ob = this.__ob__let insertedswitch (method) {case 'push':case 'unshift':
                inserted = argsbreak;case 'splice':
                inserted = args.slice(2)break;default:break;
        }if (inserted) ob.observeArray(inserted)// 看這裏ob.dep.notify()

    }
})複製代碼

相關文章
相關標籤/搜索