资讯专栏INFORMATION COLUMN

从一个最简单例子写一个极简双向绑定

Bowman_han / 2134人阅读

摘要:目标你好上面是最常见的的用法现在我就只实现一件事改变执行这一句时页面会及时更新开始动工第一步先声明一个类我们一开始定义的属性是定义在中的我们想要这样赋值的时候和的相关联需要中间做一个代理修改代码执行函数实现代理观察的属性要想实现这样赋值的时

目标
html

{{ someStr }}
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://www.ucloud.cn/yun/52897.html

相关文章

  • 一个简单例子一个极简双向绑定

    摘要:目标你好上面是最常见的的用法现在我就只实现一件事改变执行这一句时页面会及时更新开始动工第一步先声明一个类我们一开始定义的属性是定义在中的我们想要这样赋值的时候和的相关联需要中间做一个代理修改代码执行函数实现代理观察的属性要想实现这样赋值的时 目标 html {{ someStr }} js let myMvvm = new Mvvm({ el: documen...

    zhaochunqi 评论0 收藏0
  • 一个简单例子一个极简双向绑定

    摘要:目标你好上面是最常见的的用法现在我就只实现一件事改变执行这一句时页面会及时更新开始动工第一步先声明一个类我们一开始定义的属性是定义在中的我们想要这样赋值的时候和的相关联需要中间做一个代理修改代码执行函数实现代理观察的属性要想实现这样赋值的时 目标 html {{ someStr }} js let myMvvm = new Mvvm({ el: documen...

    niceforbear 评论0 收藏0
  • 160行代码仿Vue实现极简双向绑定[详细注释]

    摘要:兼容性更详细的可以看一下实现思路系列的双向绑定,关键步骤实现数据监听器,用重写数据的,值更新就在中通知订阅者更新数据。 showImg(https://segmentfault.com/img/remote/1460000015375220?w=640&h=426); 前言 现在的前端面试不管你用的什么框架,总会问你这个框架的双向绑定机制,有的甚至要求你现场实现一个双向绑定出来,那对于...

    endiat 评论0 收藏0
  • Vue.js 官方示例初探(ES6 改

    摘要:双叹号强制类型转换为布尔值。官方示例代码用注册了全局组件,会把自动注册为属性,所以没有手动写属性。如果对象是响应的,将触发视图更新。这是用来布尔值,又学了一招和分别代表单击和双击事件绑定。 如果觉得有帮助,欢迎 star哈~ https://github.com/jiangjiu/blog-md/issues/11 感谢作者 @尤小右 大大边写的超级带感的 Vue.js 前端框架,赠送...

    Jason 评论0 收藏0
  • Vue源码解析:双向绑定原理

    摘要:无论是还是都提倡单向数据流管理状态,那我们今天要谈的双向绑定是否和单向数据流理念有所违背我觉得不是,从上篇文章语法树转函数了解到,双向绑定,实质是的单向绑定和事件侦听的语法糖。源码解析今天涉及到的代码全在文件夹下。 通过对 Vue2.0 源码阅读,想写一写自己的理解,能力有限故从尤大佬2016.4.11第一次提交开始读,准备陆续写: 模版字符串转AST语法树 AST语法树转rend...

    oliverhuang 评论0 收藏0

发表评论

0条评论

Bowman_han

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<