用proxy简单实现一个mvvm
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``Proxy
ProxyReflect``Proxy
ReflectProxy``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``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``this
initVmthis._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``Proxy
Proxy
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
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
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
````