Vue數據劫持,雙向綁定與computed原理實現

 Mvvm.js

function Mvvm(options = {}) {
    this.options = options
    let _data = this._data = this.options.data
    observe(_data)
    //this本身代理this._data
    for (let key in _data) {
        Object.defineProperty(this, key, {
            enumerable: true,
            get() {
                return this._data[key]
            },
            set(newVal) {
                this._data[key] = newVal
            }
        })
    }
    initComputed.call(this)//實現computed
    new Compile(options.el, this)

}

function initComputed() {
    let vm = this
    let computed = this.options.computed
    Object.keys(computed).forEach(key => {//給對象掛載塑像,即映射
        Object.defineProperty(vm, key, {
            get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
            // get:function(){
            //     return 11+2
            // },
            set() {

            }
        })
    })
}
//把原先data的屬性重新寫,使其可以數據劫持
function Observe(_data) {
    let dep = new Dep()
    for (let key in _data) {
        let val = _data[key]
        observe(val);
        Object.defineProperty(_data, key, {
            enumerable: true,
            configurable: true,
            get() {
                //存在就添加
                if (Dep.target) {
                    dep.addSub(Dep.target)
                }

                return val
            },
            set(newVal) {
                if (newVal === val) {
                    return
                }
                val = newVal
                observe(val)//如果設置的值是對象,則需要繼續劫持
                // console.log("aaa",dep.subs[0])
                dep.notify()//執行裏面的方法
            }
        })
    }

}
//調用數據而已
function observe(_data) {
    if (typeof _data != "object") return
    return new Observe(_data)
}

//編譯模板
function Compile(el, vm) {
    var child = null;
    vm.$el = document.querySelector(el)
    var fragment = document.createDocumentFragment()
    while (child = vm.$el.firstChild) {//把節點加載到內存中
        fragment.appendChild(child)
    }
    replace(fragment)
    //替換大括號的數據
    function replace(fragment) {
        //先把爲數組變成數組
        Array.from(fragment.childNodes).forEach((node) => {
            let text = node.textContent
            let reg = /\{\{(.*)\}\}/
            if (node.nodeType == 3 && reg.test(text)) {
                let arr = RegExp.$1.split('.')
                let val = vm;
                arr.forEach((key) => { //把this.a的值獲取出來
                    val = val[key]
                })

                new Watch(vm, RegExp.$1, (newVal) => { //RegExp.$1這個值不能傳錯
                    // console.log(node,newVal)
                    node.textContent = text.replace(reg, newVal)
                })
                //替換更新
                node.textContent = text.replace(reg, val)
            }

            //input雙向數據綁定實現
            if (node.nodeType == 1) {
                let nodeAttrs = node.attributes
                Array.from(nodeAttrs).forEach(item => {
                    let exp = item.value
                    let name = item.name
                    if (name.indexOf('v-model') == 0) {  //v-model
                        node.value = vm[exp]
                    }
                    //訂閱一下
                    new Watch(vm, exp, (newVal) => {
                        node.value = newVal
                    })

                    //觸發
                    node.addEventListener('input', function (e) {
                        let newVal = e.target.value
                        vm[exp] = newVal
                    })


                })
            }
            if (node.childNodes) {
                replace(node)
            }
        })
    }
    vm.$el.appendChild(fragment)
}

//發佈訂閱
function Dep() {
    this.subs = []
}
//訂閱
Dep.prototype.addSub = function (sub) {
    this.subs.push(sub)
}
//執行
Dep.prototype.notify = function () {
    this.subs.forEach((item) => {
        item.update()
    })
}
//watch
function Watch(vm, reg, fn) {
    this.vm = vm
    this.reg = reg
    this.fn = fn
    Dep.target = this
    let val = vm
    let arr = reg.split(".")
    arr.forEach((k) => {
        val = val[k] //這個會執行get方法,先執行
    })
    Dep.target = null
}
Watch.prototype.update = function () {
    let val = this.vm
    let arr = this.reg.split(".")
    arr.forEach((k) => {
        val = val[k] //這個會執行get方法,先執行
    })
    this.fn(val)
}

index.html

<!DOCTYPE html>
<html>

<head>
<script src="mvvm.js"></script>
</head>

<body>
    <div id="app">
        <p>a的值:{{a}}</p>
        <p>b的值:{{b.b}}</p>
        <input type="text" v-model="a">
        <p>{{sum}}</p>
    </div>
</body>
<script>
    var vm = new Mvvm({
        el: "#app",
        data: {
            a: 9527,
            b:{b:"hahah"}
        },
        computed:{
            sum(){
                return this.a+this.b.b
            }
        }
    })
</script>

</html>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章