proxy-mvvm

用proxy简单实现一个mvvm

Stars
15

ProxyReflect

Proxy Proxy ECMAScript 6 http://es6.ruanyifeng.com/#docs/proxy

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

getsetobj

obj.count = 1
//  setting count!
++obj.count
//  getting count!
//  setting count!
//  2
var proxy = new Proxy(target, handler);

target``handler

Proxy``Proxy``proxy

Reflect``Proxy ES6 API

Reflect``ProxyProxyReflect``ProxyReflectProxy``Reflect

http://es6.ruanyifeng.com/#docs/reflect

Proxy

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>mvvm</title>
</head>
<body>
<div id="app">
    <h1>{{language}}</h1>
    <h2></h2>
    <ul>
        <li>{{makeUp.one}}</li>
        <li>{{makeUp.two}}</li>
        <li>{{makeUp.three}}</li>
    </ul>
    <h2></h2>
    <p>{{describe}}</p>
    <p>{{sum}}</p>
    <input placeholder="123" v-module="language" />
</div>
<script>
// Vue
const mvvm = new Mvvm({
    el: '#app',
    data: {
        language: 'Javascript',
        makeUp: {
            one: 'ECMAScript',
            two: 'DOM',
            three: 'BOM'
        },
        describe: '',
        a: 1,
        b: 2
    },
    computed: {
        sum() {
        return this.a + this.b
    }
})
</script>
</body>
</html>

vue``Mvvm

Mvvm

Mvvm``options``options``el``data``computed~~

function Mvvm(options = {}) {
    // options this.$options
    this.$options = options
    // options.datathis._data
    let data = this._data = this.$options.data
    let vm = initVm.call(this)
    return this._vm
}

Mvvmoptions this.$options``options.data``this._data``initVm,call``thisinitVmthis._vm``initVm

initVm


function initVm () {
    this._vm = new Proxy(this, {
        // get
        get: (target, key, receiver) => {
            return this[key] || this._data[key] || this._computed[key]
        },
        // set
        set: (target, key, value) => {
            return Reflect.set(this._data, key, value)
        }
    })
    return this._vm
}

init``Proxy``this``Proxy``this._vm``this._vm``ProxyProxy

get``set``get``this``key``this._data``key``this._computed``key``set``this._data``key

mvvm

mvvm.b // 2
mvvm.a // 1
mvvm.language // "Javascript"

_vm``proxy``_data

Mvvm``initObserve

function Mvvm(options = {}) {
    this.$options = options
    let data = this._data = this.$options.data
    let vm = initVm.call(this)
+   initObserve.call(this, data) // dataObserve
    return this._vm
}

initObserve``this._data


function initObserve(data) {
    this._data = observe(data) // observe this._data
}

// 
function observe(data) {
    if (!data || typeof data !== 'object') return data // 
    return new Observe(data) // Observe
}

Observe

// Observe
class Observe {
    constructor(data) {
        this.dep = new Dep()
        for (let key in data) {
            data[key] = observe(data[key]) // 
        }
        return this.proxy(data)
    }
    proxy(data) {
      let dep = this.dep
      return new Proxy(data, {
        get: (target, key, receiver) => {
          return Reflect.get(target, key, receiver)
        },
        set: (target, key, value) => {
          const result = Reflect.set(target, key, observe(value)) // observe
          return result  
        }
      })
    }
  }

proxy``_data

htmlhtml

hmtl

html

<!-- <p>{{sum}}</p> -->

Mvvm

function Mvvm(options = {}) {
    this.$options = options
    let data = this._data = this.$options.data
    let vm = initVm.call(this)
+   new Compile(this.$options.el, vm) // 
    return this._vm
}

Compile``el``proxy``vm``vm

// 
class Compile {
    constructor (el, vm) {
        this.vm = vm // vm vm.a = 1 
        let element = document.querySelector(el) //  app 
        let fragment = document.createDocumentFragment() // fragment
        fragment.append(element) // app  fragment
        this.replace(fragment) // 
        document.body.appendChild(fragment) // body
    }
    replace(frag) {
        let vm = this.vm // vm
        // frag.childNodes
        Array.from(frag.childNodes).forEach(node => {
            let txt = node.textContent //  "{{language}}"
            let reg = /\{\{(.*?)\}\}/g // 
            if (node.nodeType === 3 && reg.test(txt)) {
            
                replaceTxt()
                
                function replaceTxt() {
                    // 
                    node.textContent = txt.replace(reg, (matched, placeholder) => {
                        return placeholder.split('.').reduce((obj, key) => {
                            return obj[key] // vm.makeUp.one
                        }, vm)
                    })
                }
            }
            // 0 
            if (node.childNodes && node.childNodes.length) {
                // 
                this.replace(node)
            }
        })
    }
}

{{xxx}}

watcher

:

push

let arr = [] 
let a = () => {console.log('a')}

arr.push(a) // a
arr.push(a) // a
arr.push(a) // a

arr.forEach(fn => fn()) // 

// a
// 
class Dep {
    constructor() {
        this.subs = [] // 
    }
    // 
    addSub(sub) {
        this.subs.push(sub)
    }
    // 
    notify() {
        this.subs.filter(item => typeof item !== 'string').forEach(sub => sub.update())
    }
}

watcher``watcher``Observe, .

... //
...
proxy(data) {
    let dep = this.dep
    return new Proxy(data, {
        // get
        get: (target, prop, receiver) => {
+           if (Dep.target) {
                // pushpush
                if (!dep.subs.includes(Dep.exp)) {
                    dep.addSub(Dep.exp) // Dep.exppushsub
                    dep.addSub(Dep.target) // Dep.targetpushsub
                }
+           }
            return Reflect.get(target, prop, receiver)
        },
        // set
        set: (target, prop, value) => {
            const result = Reflect.set(target, prop, observe(value))
+           dep.notify()
            return result  
        }
    })
}

watchersub.update()

watcher

watcher

// Watcher
class Watcher {
    constructor (vm, exp, fn) {
        this.fn = fn // fn
        this.vm = vm // vm
        this.exp = exp // exp "language""makeUp.one"
        Dep.exp = exp // Depexp
        Dep.target = this // Depwatcher
        let arr = exp.split('.')
        let val = vm
        arr.forEach(key => {
            val = val[key] // vm.proxyget()get()addSub
        })
        Dep.target = null // Dep.target
    }
    update() {
        // vm.proxy.setnotify
        // updateupdatethis.fn(val)
        let exp = this.exp
        let arr = exp.split('.')
        let val = this.vm
        arr.forEach(key => {
            val = val[key]
        })
        this.fn(val)
    }
}

Watcherwatcherfnupdatefn

watcher

Compile

...
...
function replaceTxt() {
    node.textContent = txt.replace(reg, (matched, placeholder) => {
+       new Watcher(vm, placeholder, replaceTxt);   // 
        return placeholder.split('.').reduce((val, key) => {
            return val[key]
        }, vm)
    })
}

()

html<input placeholder="123" v-module="language" />``v-module``language``Compile``replace

replace(frag) {
    let vm = this.vm
    Array.from(frag.childNodes).forEach(node => {
        let txt = node.textContent
        let reg = /\{\{(.*?)\}\}/g
        // nodeType
+       if (node.nodeType === 1) {
            const nodeAttr = node.attributes // 
            Array.from(nodeAttr).forEach(item => {
                let name = item.name // 
                let exp = item.value // 
                //  v-
                if (name.includes('v-')){
                    node.value = vm[exp]
                    node.addEventListener('input', e => {
                        // this.language
                        // setsetnotifynotifywatcherupdate
                        vm[exp] = e.target.value
                    })
                }
            });
+       }
        ...
        ...
    }
  }

input``input``input``set``set``notify``notify``watcher``update

<p>{{sum}}</p>initVmreturn this[key] || this._data[key] || this._computed[key]this._computedwatcher

function Mvvm(options = {}) {
    this.$options = options
    let data = this._data = this.$options.data
    let vm = initVm.call(this)
    initObserve.call(this, data)
+   initComputed.call(this) // this
    new Compile(this.$options.el, vm)
    return this._vm
}


function initComputed() {
    let vm = this
    let computed = this.$options.computed // computed
    vm._computed = {}
    if (!computed) return // 
    Object.keys(computed).forEach(key => {
        // sumthisthis._vmthis.athisb
        this._computed[key] = computed[key].call(this._vm)
        // Watcher
        new Watcher(this._vm, key, val => {
            // 
            this._computed[key] = computed[key].call(this._vm)
        })
    })
}

initComputed watcher

this._vm ---> vm.set() ---> notify() -->update()-->



### mounted

mounted

````js
// Vue
let mvvm = new Mvvm({
    el: '#app',
    data: {
        ...
        ...
    },
    computed: {
        ...
        ...
    },
    mounted() {
        console.log('i am mounted', this.a)
    }
})

````
new Mvvmmounted
function Mvvm

````js
function Mvvm(options = {}) {
    this.$options = options
    let data = this._data = this.$options.data
    let vm = initVm.call(this)
    initObserve.call(this, data)
    initComputed.call(this)
    new Compile(this.$options.el, vm)
+   mounted.call(this._vm) // mounted
    return this._vm
}

// mounted
+ function mounted() {
    let mounted = this.$options.mounted
    mounted && mounted.call(this)
+ }

````



````
i am mounted 1
````