zoukankan      html  css  js  c++  java
  • 从一个最简单例子写一个极简双向绑定

    目标

    
    html
    
    <div id="app">
        <div>{{ someStr }}</div>
    </div>
    
    js
    
    let myMvvm = new Mvvm({
        el: document.getElementById('app'),
        data: {
            someStr: '你好'
        }
    })
    

    上面是最常见的vue的用法, 现在我就只实现一件事

    
    myMvvm.someStr = '改变'   // 执行这一句时, 页面会及时更新
    

    开始动工

    1、 第一步, 先声明一个Mvvm类

    
    class Mvvm {
        constructor (option) {
            this.$option = option || {}
        }
    }
    

    我们一开始定义的 someStr属性是定义在option.data中的, 我们想要 myMvvm.someStr这样赋值的时候和option的data相关联, 需要中间做一个代理,修改代码

    
    class Mvvm {
        constructor (option) {
            this.$option = option || {}
            this._proxyData(option.data, this) // 执行函数实现代理
        }
        _proxyData (obj, context) { 
            Object.keys(obj).forEach(key => {
                Object.defineProperty(context, key, {
                    configurable: false,
                    enumerable: true,
                    get () {
                        return obj[key]
                    },
                    set (val) {
                        obj[key] = val
                    }
                })
            })
        }
    }
    

    2、观察option的data属性
    要想实现 myMvvm.someStr = 1 这样赋值的时候,页面能及时更新,那么我们就要对someStr的赋值过程做一个监听才行, 开心的是 , Object.defineProperty可以轻易做到这点
    写一个observe类

    
    class Observe {
        constructor (obj) {
            Object.keys(obj).forEach(key => {
                this.defineReactive(obj, key, obj[key])
            })
        }
        defineReactive (obj, key, val) {
            let initVal = val
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: false,
                get () {
                    return initVal
                },
                set (val) { // 每一次的复制我们都可以在这里获知,自然可以为所欲为了
                    initVal = val
                    return initVal
                }
            })
        }
    }
    然后修改一下Mvvm这个类的constructor
    constructor (option) {
        this.$option = option || {}
        this._proxyData(option.data, this)
        new Observe(option.data)
    }
    

    3、实现元素的实时更新
    现在为止, 还只是显示 一个 {{someStr}} 而已, 我们现在需要做的是让能变成 你好 这个值
    写一个Compile类

    
    {{someStr}}是一个文本节点,先声明一个可以渲染文本节点的函数
    
    let compileText = function (node, vm, str) {
        let val = vm[str]
        if (val) {
            node.nodeValue = val 
        }
    }
    
    class Compile {
        constructor(el, vm) { // el 是 #app这个元素  vm是Mvvm这个实例
            let frag = this.node2Fragment(el)
            this.vm = vm
            this.compileElement(frag) // 读取子节点进行渲染
            el.appendChild(frag)
        }
        node2Fragment(el) { // 创建一个文档片段把#app元素的子节点拷贝
            let frag = document.createDocumentFragment()
            let child
            while (child = el.firstChild) {
                frag.appendChild(child)
            }
            return frag
        }
        compileElement(el) { // 渲染节点
            let childNodes = el.childNodes;
            [].forEach.call(childNodes, (node) => { // 遍历所有的子节点
                if (this.isElementNode(node)) { // 如果是元素节点, 重复便利
                    this.compileElement(node)
                } else if (this.isTextNode(node)) { // 如果是文本节点
                    let matchStr = this.isMustache(node.nodeValue) // 判断这个文本值是不是 {{}} 这种类型
                    if (matchStr) { // 如果有匹配到
                        compileText(node, this.vm, matchStr)
                    }
                }
            })
        }
        isElementNode(node) { // 元素节点
            return node.nodeType === 1
        }
        isTextNode(node) { // 文本节点
            return node.nodeType === 3
        }
        isMustache(str) {
            if (!str) {
                return null
            }
            let reg = /{{(.*)}}/
            let arr = str.match(reg)
            return arr ? arr[1].replace(/s/g, '') : null
        }
    
    }
    现在修改一下 Mvvm这个类的constructor函数
    constructor(option) {
        this.$option = option || {}
        this._proxyData(option.data, this)
        new Observe(option.data)
        new Compile(option.el, this)
    }
    

    现在你好这个值终于是被渲染出来, 我们踏出了第一步, 现在开始实现 myMvvm.someStr = 1 也能及时更新

    在实现complie的时候, 我们知道渲染的时候调用了compileText函数,那么我们现在更改someStr时及时渲染,就只要再执行这个函数就可以了, 我们可以把这个更新函数放到一个队列里, 每次更新someStr的时候, 把这个队列里的更新函数执行一遍就可以了
    我们实现一个Dep类

    
    // 这里声明两个变量待会使用
    let updateFn
    let canMount
    class Dep {
        constructor () {
            this.queue = []
        }
        mount () {
            this.queue.push(updateFn)
        }
        notify () {
            this.queue.forEach(fn => fn())
        }
    }
    
    然后修改一下Observe类的defineReactive函数
    
    defineReactive(obj, key, val) {
        let initVal = val
        let dep = new Dep()
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: false,
            get() {
                if (canMount) { // 防止每次get都会执行这里
                    dep.mount()
                }
                return initVal
            },
            set(val) { // 每一次的复制我们都可以在这里获知,自然可以为所欲为了
                if (val !== initVal) {
                    initVal = val
                    dep.notify()
                }
                return initVal
            }
        })
    }
    
    实现一个生成更新函数 的方法
    let bindTextUpdater = function (node, vm, matchStr) {
        canMount = true
        updateFn = compileText.bind(null, node, vm, matchStr)
        updateFn()
        canMount = false
    }
    
    然后最后一步
    修改一下Complie类的compileElement方法
    let childNodes = el.childNodes;
            [].forEach.call(childNodes, (node) => { // 遍历所有的子节点
                if (this.isElementNode(node)) { // 如果是元素节点, 重复便利
                    this.compileElement(node)
                } else if (this.isTextNode(node)) { // 如果是文本节点
                    let matchStr = this.isMustache(node.nodeValue) // 判断这个文本值是不是 {{}} 这种类型
                    if (matchStr) { // 如果有匹配到
                        bindUpdater(node, this.vm, matchStr) // 绑定更新函数
                    }
                }
            })
    

    现在执行 myMvvm.someStr = 155 会发现简单的例子实现了

    来源:https://segmentfault.com/a/1190000016099104

  • 相关阅读:
    [转]java中的匿名内部类总结
    linux 命令总结
    [转载]nohub java -jar xx.jar >/dev/null 2>&1 &
    Java正则表达式Pattern和Matcher类详解
    spark基础知识介绍(包含foreachPartition写入mysql)
    spark 运行架构
    spark核心原理
    行动操作
    控制操作
    键值转换操作
  • 原文地址:https://www.cnblogs.com/datiangou/p/10179779.html
Copyright © 2011-2022 走看看