带你从头梳理Vue2.x源码实现流程(二)

219 阅读2分钟

四.创建渲染watcher

1.初始化渲染Watcher

import {mountComponent} from './lifecycle'
Vue.prototype.$mount = function (el) {
    const vm = this;
    const options = vm.$options;
    el = document.querySelector(el);

    // 如果没有render方法
    if (!options.render) {
        let template = options.template;
        // 如果没有模板但是有el
        if (!template && el) {
            template = el.outerHTML;
        }

        const render= compileToFunctions(template);
        options.render = render;
    }
    mountComponent(vm,el);
}

lifecycle.js

export function lifecycleMixin() {
    Vue.prototype._update = function (vnode) {}
}
export function mountComponent(vm, el) {
    vm.$el = el;
    let updateComponent = () => {
        // 将虚拟节点 渲染到页面上
        vm._update(vm._render());
    }
    new Watcher(vm, updateComponent, () => {}, true);
}

render.js

export function renderMixin(Vue){
    Vue.prototype._render = function () {}
}

watcher.js

let id = 0;
class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm;
        this.exprOrFn = exprOrFn;
        if (typeof exprOrFn == 'function') {
            this.getter = exprOrFn;
        }
        this.cb = cb;
        this.options = options;
        this.id = id++;
        this.get();
    }
    get() {
        this.getter();
    }
}

export default Watcher;

先调用_render方法生成虚拟dom,通过_update方法将虚拟dom创建成真实的dom

2.生成虚拟dom

import {createTextNode,createElement} from './vdom/create-element'
export function renderMixin(Vue){
    Vue.prototype._v = function (text) { // 创建文本
        return createTextNode(text);
    }
    Vue.prototype._c = function () { // 创建元素
        return createElement(...arguments);
    }
    Vue.prototype._s = function (val) {
        return val == null? '' : (typeof val === 'object'?JSON.stringify(val):val);
    }
    Vue.prototype._render = function () {
        const vm = this;
        const {render} = vm.$options;
        let vnode = render.call(vm);
        return vnode;
    }
}

创建虚拟节点

export function createTextNode(text) {
    return vnode(undefined,undefined,undefined,undefined,text)
}
export function createElement(tag,data={},...children){
    let key = data.key;
    if(key){
        delete data.key;
    }
    return vnode(tag,data,key,children);
}
function vnode(tag,data,key,children,text){
    return {
        tag,
        data,
        key,
        children,
        text
    }
}

3.生成真实DOM元素

将虚拟节点渲染成真实节点

import {patch} './observer/patch'
export function lifecycleMixin(Vue){
    Vue.prototype._update = function (vnode) {
        const vm = this;
        vm.$el = patch(vm.$el,vnode);
    }
}
export function patch(oldVnode,vnode){
    const isRealElement = oldVnode.nodeType;
    if(isRealElement){
        const oldElm = oldVnode;
        const parentElm = oldElm.parentNode;
        
        let el = createElm(vnode);
        parentElm.insertBefore(el,oldElm.nextSibling);
        parentElm.removeChild(oldVnode)
   		return el;
    } 
}
function createElm(vnode){
    let {tag,children,key,data,text} = vnode;
    if(typeof tag === 'string'){
        vnode.el = document.createElement(tag);
        updateProperties(vnode);
        children.forEach(child => { 
            return vnode.el.appendChild(createElm(child));
        });
    }else{
        vnode.el = document.createTextNode(text);
    }
    return vnode.el
}
function updateProperties(vnode){
    let newProps = vnode.data || {}; // 获取当前老节点中的属性 
    let el = vnode.el; // 当前的真实节点
    for(let key in newProps){
        if(key === 'style'){ 
            for(let styleName in newProps.style){
                el.style[styleName] = newProps.style[styleName]
            }
        }else if(key === 'class'){
            el.className= newProps.class
        }else{ // 给这个元素添加属性 值就是对应的值
            el.setAttribute(key,newProps[key]);
        }
    }
}

五.生命周期的合并

1.Mixin原理

import {mergeOptions} from '../util/index.js'
export function initGlobalAPI(Vue){
    Vue.options = {};

    Vue.mixin = function (mixin) {
        // 将属性合并到Vue.options上
        this.options = mergeOptions(this.options,mixin);
        return this;
    }
}

2.合并生命周期

export const LIFECYCLE_HOOKS = [    'beforeCreate',    'created',    'beforeMount',    'mounted',    'beforeUpdate',    'updated',    'beforeDestroy',    'destroyed',]
const strats = {};
function mergeHook(parentVal, childValue) {
    if (childValue) {
        if (parentVal) {
            return parentVal.concat(childValue);
        } else {
            return [childValue]
        }
    } else {
        return parentVal;
    }
}
LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook
})
export function mergeOptions(parent, child) {
    const options = {}
    for (let key in parent) {
        mergeField(key)
    }
    for (let key in child) {
        if (!parent.hasOwnProperty(key)) {
            mergeField(key);
        }
    }
    function mergeField(key) {
        if (strats[key]) {
            options[key] = strats[key](parent[key], child[key]);
        } else {
            if (typeof parent[key] == 'object' && typeof child[key] == 'object') {
                options[key] = {
                    ...parent[key],
                    ...child[key]
                }
            }else{
                options[key] = child[key];
            }
        }
    }
    return options
}

4.调用生命周期

export function callHook(vm, hook) {
    const handlers = vm.$options[hook];
    if (handlers) {
        for (let i = 0; i < handlers.length; i++) {
            handlers[i].call(vm);
        }
    }
}

5.初始化流程中调用生命周期

Vue.prototype._init = function (options) {
    const vm = this;
    vm.$options = mergeOptions(vm.constructor.options,options);
    // 初始化状态
    callHook(vm,'beforeCreate');
    initState(vm);
    callHook(vm,'created');
    if (vm.$options.el) {
    	vm.$mount(vm.$options.el);
    }
}

六.依赖收集

每个属性都要有一个dep,每个dep中存放着watcher,同一个watcher会被多个dep所记录。

1.在渲染时存储watcher

class Watcher{
    // ...
    get(){
        pushTarget(this);
        this.getter();
        popTarget();
    }
}
let id = 0;
class Dep{
    constructor(){
        this.id = id++;
    }
}
let 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;

2.对象依赖收集

let dep = new Dep();
Object.defineProperty(data, key, {
    get() {
        if(Dep.target){ // 如果取值时有watcher
            dep.depend(); // 让watcher保存dep,并且让dep 保存watcher
        }
        return value
    },
    set(newValue) {
        if (newValue == value) return;
        observe(newValue);
        value = newValue;
        dep.notify(); // 通知渲染watcher去更新
    }
});

Dep实现

class Dep{
    constructor(){
        this.id = id++;
        this.subs = [];
    }
    depend(){
        if(Dep.target){
            Dep.target.addDep(this);// 让watcher,去存放dep
        }
    }
    notify(){
        this.subs.forEach(watcher=>watcher.update());
    }
    addSub(watcher){
        this.subs.push(watcher);
    }
}

watcher

constructor(){
	this.deps = [];
	this.depsId = new Set();
}
addDep(dep){
    let id = dep.id;
    if(!this.depsId.has(id)){
        this.depsId.add(id);
        this.deps.push(dep);
        dep.addSub(this);
    }
}
update(){
    this.get();
}

3.数组的依赖收集

this.dep = new Dep(); // 专门为数组设计的
if (Array.isArray(value)) {
	value.__proto__ = arrayMethods;
	this.observeArray(value);
} else {
	this.walk(value);
}	

function defineReactive(data, key, value) {
    let childOb = observe(value);
    let dep = new Dep();
    Object.defineProperty(data, key, {
        get() {
            if(Dep.target){
                dep.depend();
                if(childOb){ 
                    childOb.dep.depend(); // 收集数组依赖
                }
            }
            return value
        },
        set(newValue) {
            if (newValue == value) return;
            observe(newValue);
            value = newValue;
            dep.notify();
        }
    })
}


arrayMethods[method] = function (...args) {
    	// ...
        ob.dep.notify()
        return result;
}

递归收集数组依赖

if(Dep.target){
    dep.depend();
    if(childOb){
        childOb.dep.depend(); // 收集数组依赖
        if(Array.isArray(value)){ // 如果内部还是数组
            dependArray(value);// 不停的进行依赖收集
        }
    }
}
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)
        }
    }
}

七.实现Vue异步更新之nextTick

1.实现队列机制

update(){
    queueWatcher(this);
}

scheduler

import {
    nextTick
} from '../util/next-tick'
let has = {};
let queue = [];

function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        let watcher = queue[i];
        watcher.run()
    }
    queue = [];
    has = {}
}
let pending = false
export function queueWatcher(watcher) {
    const id = watcher.id;
    if (has[id] == null) {
        has[id] = true;
        queue.push(watcher);
        if(!pending){
            nextTick(flushSchedulerQueue)
            pending = true;
        }
    }
}

2.nextTick实现原理

util/next-tick.js

let callbacks = [];
function flushCallbacks() {
    callbacks.forEach(cb => cb());
}
let timerFunc;
if (Promise) { // then方法是异步的
    timerFunc = () => {
        Promise.resolve().then(flushCallbacks)
    }
}else if (MutationObserver) { // MutationObserver 也是一个异步方法
    let observe = new MutationObserver(flushCallbacks); // H5的api
    let textNode = document.createTextNode(1);
    observe.observe(textNode, {
        characterData: true
    });
    timerFunc = () => {
        textNode.textContent = 2;
    }
}else if (setImmediate) {
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
}else{
    timerFunc = () => {
        setTimeout(flushCallbacks, 0);
    }
}
export function nextTick(cb) {
    callbacks.push(cb);
    timerFunc();
}