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()

    }
})复制代码

相关文章
相关标签/搜索