7. mixin的实现原理

mixin的实现原理

在Vue.mixin()中的内容会被维护到Vue.options静态属性里面

然后通过mergeOptions以特定的合并策略将全局的属性和用户属性合并起来

在获取用户选项的时候, 调用mergeOptions合并全局Vue.mixin()里面的内容

就能使用Vue.mixin()里面拓展的功能了

新增一个全局api, 传入Vue

src/index.js

import { initGlobalApi } from "./globalApi"

initGlobalApi(Vue)

实现方法, 新增文件 src/globalApi.js

import { mergeOptions } from "./utils"

export function initGlobalApi(Vue) {


    // 静态方法 或属性
    Vue.options = {}


    Vue.mixin = function (mixin) {
        // console.log('this:minxi', this)   // 静态方法里面的this指向Vue的构造函数
        // 将用户的选项和全局的options进行合并
        // 第一次 {} 和 用户的 created 合并  created: [fn]
        // 第二次: 上一次的结果和 created 合并 , created: [fn, fn]
        this.options = mergeOptions(this.options, mixin)    // 这里的this指向Vue的构造函数
        return this
    }
}

新增文件 src/utils.js



const strats = {} // 使用策略
const LIFECYCLE = ['brforeCreate', 'created']  // 这里先讨论生命周期, data比较复杂

LIFECYCLE.forEach(hook => {
    strats[hook] = function (p, c) {     // p 和 c 指parent 和children
        if (c) {
            if (p) {     // 如果父子都有, 注意, 如果p 有, 则p是一个数组
                return p.concat(c)
            } else {
                return [c]
            }
        } else {
            return p
        }
    }
})


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 {
            options[key] = child[key] || parent[key]
        }
    }



    return options

}

在init.js里面初始化的时候合并选项


    Vue.prototype._init = function(options) {
        // 获取vue实例, 这里的this指向vue实例
        const vm = this
        // 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
        // vm.$options = options
        vm.$options = mergeOptions(this.constructor.options, options)       // 合并全局的mixin
        
        ...
    }

在html文件vm实例的上方添加内容

Vue.mixin({
            created() {
                console.log('minix---1')
            },
            a:1,
            b:1

        })
        Vue.mixin({
            created() {
                console.log('minix---2')
            },
            a: 2,
            c: 2

        })
        // 最终上面的created会被维护成一个数据, 放在 Vue.options.created中
        console.log(Vue.options)
        const vm = new Vue({...})

在lifecycle.js中添加生命周期执行的方法

// 调用哪个实例上的哪个hook
export function callHook(vm, hook) {
    const handlers = vm.$options[hook]
    if(handlers) {
        handlers.forEach(hanlder => hanlder.call(vm))   // 生命周期的钩子指向实例
    }
}

在init.js里面使用

 Vue.prototype._init = function(options) {
        // 获取vue实例, 这里的this指向vue实例
        const vm = this
        // 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
        // vm.$options = options
        vm.$options = mergeOptions(this.constructor.options, options)       // 合并全局的mixin
    

        callHook(vm, 'beforeCreate')

        // 初始化状态
        initState(vm)

        callHook(vm, 'created')

        // 如果有元素的话, 执行挂载方法,然后添加该方法
        if(options.el) {
            vm.$mount(options.el)
        }
    }

注意 Vue.mixin()的使用位置

完整文件

dist/5.mixin.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>mixin</title>
</head>

<body>
    <div id="app" style="color:yellow;backgroundColor:blue;">
            {{name}} hello {{name}}  {{age}} {{name}}
    </div>
    <script src="vue.js"></script>
    <script>
        Vue.mixin({
            created() {
                console.log('minix---1')
            },
            a:1,
            b:1

        })
        Vue.mixin({
            created() {
                console.log('minix---2')
            },
            a: 2,
            c: 2

        })
        // 最终上面的created会被维护成一个数据, 放在 Vue.options.created中
        console.log(Vue.options)
        const vm = new Vue({
            data() {
                return {
                    name: 'ywj',
                    age: 18,
                    address: {
                        num: 20
                    },
                    hobby: ['eat', 'drink']
                }
            },
            el: '#app',  // 将数据解析到el元素上
            created() {
                console.log('created:', this.xxx)       // 数据来源不明确
            }
        })
    </script>
</body>

</html>

src/index.js

// Vue 类是通过构造函数来实现的
// 如果通过 class来实现, 里面的类和方法就会有很多, 不利于维护
// 1. 新建一个Vue构造函数, 默认导出, 这样就有了全局 Vue 
// 2. Vue中执行一个初始化方法, 参数是用户的选项
// 3. 在Vue的原型上添加这个方法, (注意: 添加的这个方法在引入vue的时候就执行了, 而不是在new Vue()的时候执行的)

import { initGlobalApi } from "./globalApi"
import { initMixin } from "./init"
import { initLifeCycle } from "./lifecycle"
import { nextTick } from "./observe/watcher"



function Vue(options) {
    this._init(options)
}

initMixin(Vue)
initLifeCycle(Vue)
initGlobalApi(Vue)


Vue.prototype.$nextTick = nextTick

export default Vue

src/globalApi.js

import { mergeOptions } from "./utils"

export function initGlobalApi(Vue) {


    // 静态方法 或属性
    Vue.options = {}


    Vue.mixin = function (mixin) {
        // console.log('this:minxi', this)   // 静态方法里面的this指向Vue的构造函数
        // 将用户的选项和全局的options进行合并
        // 第一次 {} 和 用户的 created 合并  created: [fn]
        // 第二次: 上一次的结果和 created 合并 , created: [fn, fn]
        this.options = mergeOptions(this.options, mixin)    // 这里的this指向Vue的构造函数
        return this
    }
}

src/utils.js



const strats = {} // 使用策略
const LIFECYCLE = ['brforeCreate', 'created']  // 这里先讨论生命周期, data比较复杂

LIFECYCLE.forEach(hook => {
    strats[hook] = function (p, c) {     // p 和 c 指parent 和children
        if (c) {
            if (p) {     // 如果父子都有, 注意, 如果p 有, 则p是一个数组
                return p.concat(c)
            } else {
                return [c]
            }
        } else {
            return p
        }
    }
})


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 {
            options[key] = child[key] || parent[key]
        }
    }



    return options

}

src/lifecycle.js

import { Watcher } from "./observe/watcher"
import { createElementVNode, createTextVNode } from "./vdom"


export function mountComponent(vm, el) {
    // 将挂载的元素也放到实例上
    vm.$el = el
    // 1. 调用render方法产生虚拟节点
    // vm._render() 生成虚拟节点  vm._update 生成真实节点  需要先扩展这两个方法
    // vm._update(vm._render())
    // 2. 虚拟dom产生真实dom
    // 3. 插入到el元素中

    // const vdom = vm._render()

    // vm._update(vm._render())
    // 将渲染方法封装到updateComponent里面
    const updateComponent = () => {
        vm._update(vm._render())
    }

    // 生成一个渲染watcher的实例, true表示渲染watcher
    new Watcher(vm, updateComponent, true)
}

export function initLifeCycle(Vue) {
    Vue.prototype._render = function() {
        const vm = this
        // debugger
        // 返回的结果是虚拟dom
        // 注意this的指向, 需要call this
        // 就是执行$options里面的render方法
        // 需要拓展 _s _v _c方法
        return vm.$options.render.call(vm)
    }
    Vue.prototype._c = function() {
        // 返回一个元素的虚拟节点
        return createElementVNode(this, ...arguments)
    }
    // _v(text)
    Vue.prototype._v = function() {
        // 返回一个文本的虚拟节点
        return createTextVNode(this, ...arguments)
    }
    // 将数据转换成字符串
    Vue.prototype._s = function(value) {
        // 如果不是对象的话, 就直接返回, 不然字符串可会被加上""
        if(typeof value !== 'object') return value
        return JSON.stringify(value)
    }

    Vue.prototype._update = function(vnode) {

        const vm  = this
        const el = vm.$el

        vm.$el = patch(el, vnode)

    }
}


function patch(oldVNode, vnode) {
    // 现在是初次渲染
    // 需要判断是不是真实节点
    const isRealElement = oldVNode.nodeType     // nodeType是原生
    if(isRealElement) {
        const elm = oldVNode    // 获取真实元素
        const parentElm = elm.parentNode    // 拿到父元素
        // 创建真实元素
        let newElm = createElm(vnode)
        parentElm.insertBefore(newElm, elm.nextSibling)
        parentElm.removeChild(oldVNode)

        return newElm       // 如果是真实dom, 先返回一个新的dom,  暂时
    } else {
        // diff算法
    }
}


function createElm(vnode) {
    let {tag, data, children, text} = vnode 
    if(typeof tag === 'string') {   // 如果tag是string, 说明是一个标签, 如div
        vnode.el = document.createElement(tag)  // 生成一个真实节点, 并将真实节点挂载到虚拟节点上. 将虚拟节点和真实节点意义对应, 后续如果修改了属性, 可以直接找到虚拟节点对应的真实节点

        // 更新属性, 属性在data里面
        patchProps(vnode.el, data)


        // 标签会有儿子, 要处理儿子
        children.forEach(child => {
            // 同样生成元素并且插入到父元素的真实节点中, 递归调用
            vnode.el.appendChild(createElm(child))
        })

    } else {    // 不是元素就是文本
        vnode.el = document.createTextNode(text)    // 创建文本
    }
    // 这里返回一个真实dom是为了方便递归调用, 并且使用dom的方法
    return vnode.el
}

/**
 * 
 * @param {真实元素} el 
 * @param {属性} props 是一个对象
 */
 function patchProps(el, props) {
    for(let key in props) {

        // style单独处理
        if(key === 'style') {
            for(let styleName in props.style) {
                el.style[styleName] = props.style[styleName]
            }
        } else {
            el.setAttribute(key, props[key])
        }
    }
}


// 调用哪个实例上的哪个hook
export function callHook(vm, hook) {
    const handlers = vm.$options[hook]
    if(handlers) {
        handlers.forEach(hanlder => hanlder.call(vm))   // 生命周期的钩子指向实例
    }
}

src/init.js

import { compileToFunction } from "./compiler"
import { callHook, mountComponent } from "./lifecycle"
import { initState } from "./state"
import { mergeOptions } from "./utils"


export function initMixin(Vue) {

    Vue.prototype._init = function(options) {
        // 获取vue实例, 这里的this指向vue实例
        const vm = this
        // 获取用户选项, 方便后续获取参数, 很多地方都是挂载到vue上面的
        // vm.$options = options
        vm.$options = mergeOptions(this.constructor.options, options)       // 合并全局的mixin
        // 初始化状态, 也就是data里面的数据, vue的实例暂时长这样
        // const vm = new Vue({
        //     data() {
        //         return {
        //             name: 'jerry',
        //             age: '杨'
        //         }
        //     }
        // })

        callHook(vm, 'beforeCreate')

        // 初始化状态
        initState(vm)

        callHook(vm, 'created')

        // 如果有元素的话, 执行挂载方法,然后添加该方法
        if(options.el) {
            vm.$mount(options.el)
        }
    }

    // 挂载方法
    Vue.prototype.$mount = function(el) {
        // 获取实例
        const vm = this
        // 将el变成一个真实的元素
        el = document.querySelector(el)
        // 获取options
        let ops = vm.$options
        // 获取render方法, 没有就生成, 如果没有, 先获取template, 有template生成render方法
        if(!ops.render) {
            let template
            if(!ops.template && el) {
                template = el.outerHTML
            } else {
                if(el) {
                    template = ops.template
                }
            }
            

            // 将template转化为render方法
            if(template) {
                // 新建文件compiler/index.js文件, 添加compileToFunction方法
                const render = compileToFunction(template)

                ops.render = render


                // 有了render之后, 挂载组件   
                // 就是执行一个render方法, 产生虚拟dom, 然后挂载到el中
                mountComponent(vm, el)
            }
        }

    }
}

posted @ 2022-06-27 00:13  littlelittleship  阅读(135)  评论(0编辑  收藏  举报