美文网首页
v-model demo

v-model demo

作者: _旁观者_ | 来源:发表于2018-11-13 16:26 被阅读0次

代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <form>
            <input type="text" v-model="number">
            <button type="button" v-click="increment">add</button>
        </form>
        <h3 v-bind="number"></h3>
    </div>
    <script>
        // this指向问题
        // 添加事件的3种方法
        // Object.defineProperty
        // hasOwnProperty
        // hasAttribute
        // js原生node方法
        function Vue(option) {
            this._init(option) // 初始化
        }
        Vue.prototype._init = function (option){
            // 初始化vue中数据,方法
            this.$option = option
            this.$el = document.querySelector(option.el)
            this.$data = option.data
            this.$methods = option.methods

            // 为data数据的每一项添加绑定事件
            this._binding = {}
            
            // 建立监听, 编译html
            this._obsever(this.$data)
            this._compile(this.$el)
        }

        Vue.prototype._obsever = function (obj) {
            // 指针缓存
            let _this = this
            // 循环data数据的每一项key,为每一个key添加指令 
            Object.keys(obj).forEach((key) => {
                // hasOwnProperty 只监听自身的数据 (非原型上的属性)
                if(obj.hasOwnProperty(key)){
                    // 为每一个key添加指令 
                    _this._binding[key] = {
                        directives: []
                    }
                }
                let value = obj[key]
                // 如果value为对象,递归循环
                if (typeof value === 'Object') {
                    _this._obsever(value)
                }
                // 缓存key的指令,下边set用的,可以不写
                let binding = _this._binding[key]
                // Object.defineProperty vue实现双向绑定的原理
                // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
                Object.defineProperty(_this.$data, key, {
                    enumerable: true,
                    configurable: true,
                    get: function () {
                        console.log('取值')
                        return value
                    },
                    set: function (newValue) {
                        if (value !== newValue) {
                            console.log('赋值')
                            value = newValue
                            binding.directives.forEach((item) => {
                                item.update()
                            })
                        }
                    }
                })
            })
        }

        Vue.prototype._compile = function (root) {
            let _this = this
            // 获取根dom的子元素
            let nodes = root.children
            // 循环子元素
            for (var i = 0; i < nodes.length; i++) {
                let node = nodes[i]
                // 判断子元素有没有子元素,有就递归循环
                if (node.children.length > 0) {
                    _this._compile(node)
                }
                // 拿到'v-click'的方法名,给dom添加方法
                if (node.hasAttribute('v-click')) {
                    let attrVal = node.getAttribute('v-click')
                    // bind(_this.$data)更改this指向 (否则现在的this是dom)
                    node.onclick = _this.$methods[attrVal].bind(_this.$data)
                }
                // 同上
                if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
                    let attrVal = node.getAttribute('v-model')
                    _this._binding[attrVal].directives.push(new Watcher(
                        'input',
                        node,
                        _this,
                        attrVal,
                        'value'
                    ))
                    // 添加事件 更新dom
                    node.addEventListener('input', () => {
                        _this.$data[attrVal] = node.value
                    })
                }
                // 同上
                if (node.hasAttribute('v-bind')) {
                    let attrVal = node.getAttribute('v-bind')
                    _this._binding[attrVal].directives.push(new Watcher(
                        'text',
                        node,
                        _this,
                        attrVal,
                        'innerHTML'
                    ))
                }
            }
        }

        function Watcher (name, el, vm, exp, attr) {
            this.name= name
            this.el = el
            this.vm = vm
            this.exp = exp
            this.attr = attr
            // 为数据data赋值,但是dom未更新
            this.update()
        }
        Watcher.prototype.update = function () {
            this.el[this.attr] = this.vm.$data[this.exp]
        }

        window.onload = ()=> {
            new Vue({
                el: '#app',
                data: {
                    number: 0,
                    count: 0
                },
                methods: {
                    increment () {
                        this.number++;
                    },
                    incre () {
                        this.count++;
                    }
                }
            })
        }
    </script>
</body>
</html>

数据初始化

init方法中初始化对象的属性,定义了数据data,方法methods,元素 el,在初始化中调用了 _obsever_compile


Object.defineProperty
Object.defineProperty方法是作为双向绑定实现的基础,在Object.defineProperty方法中有两个属相 setget,详细功能可查看MDN文档。



我们从后台获取数据后,赋值给data后,html会自动更新,就相当于调用了对应数据的get方法。

obsever

_obsever 方法中首先遍历vue对象中的data的所有数据,并对数据添加了Object.defineProperty方法,此方法中设置了getset属性

tips: 在每个数据添加方法 Object.defineProperty后,getset 属性会在页面中一直缓存,getset 可以理解为闭包


_compile

_compile方法首先遍历所有的dom节点,将v-click指令转化为原生事件,编译完成后添加watch函数

简述一下实现原理

  • 数据绑定
    • 主要是监听 data 数据的值 设置回调方法 set get
    • get 在数据被调用时 触发 (一般用在 添加 观察者
    • set 在数据赋值时调用 (调用后会判断数是否更改 来 重新编译模板 渲染dom)
  • 模板编译
    • 主要是把 .vue 文件中的 html 编译成 vdom 然后展示在页面上
    • 绑定的数据 会触发 get -> get 回调中添加了一个观察者
    • 用 js 实现 事件监听
  • 请求接口后改变 数据 ,触发 set -> set 回调 比较数据 编译模板(里面有 vdom 的 diff patch 算法)
  • 事件触发 触发绑定的 事件 -> 事件在改变数据 -> 改变数据在调set 方法 -> 比较后更新数据

相关文章

网友评论

      本文标题:v-model demo

      本文链接:https://www.haomeiwen.com/subject/qgxcfqtx.html