<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>複製代碼
/** * @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) } }複製代碼
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 文件 這裏只是簡單處理 直接生成真實的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 關聯起來 是靠當組件渲染時 會走數據 數據已劫持 在getter中 每一個屬性 都有Dep 在Dep上加類屬性 Dep.target 在渲染時將Dep.target指向這個渲染Watcher(神來之筆)javascript
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複製代碼
/** 每一個劫持的屬性 加上惟一的標識 */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
- 多層數組要進行遞歸操做
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) }複製代碼
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() } })複製代碼