Vue双方向データバインドの原理(データハイジャックとパブリッシャ-サブスクライバ)
38239 ワード
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div class="app">
<form>
<label for="name">
:
<input id="name" type="text" v-model="name">
label>
<label for="carName">
:
<input id="carName" type="text" v-model="car.name">
label>
<label for="carColor">
:
<input id="carColor" type="text" v-model="car.color">
label>
<button @click="reset"> button>
form>
<h4>
:<span v-text="name">span>, <span v-html="car.color">span> <span v-text="car.name">span>
h4>
div>
<script>
/*
:
:
1. , Object.defineProperty ( ); , 。
2. DOM , , 。
:
MyVm: Vue
:
_el:
_data:
_methods:
_property: ( )
:
_listen:
_compile:
Watcher:
:
_el:
_vm:vm
_attr: DOM
_dataName:
:
_update:
*/
class MyVm {
constructor({
el,
data,
methods
}) {
this._el = document.querySelector(el)
this._data = data
this._methods = methods
this._property = {}
this._listen(this._data, this._property)
this._compile(this._el)
}
_listen(data, parent) {
const that = this
Object.keys(data).map(key => {
if (data.hasOwnProperty(key)) {
let value = data[key]
//
parent[key] = {
_subscriber: []
}
//
Object.defineProperty(data, key, {
get() {
return value
},
set(newValue) {
value = newValue
//
parent[key]._subscriber.map(watcher => watcher._update())
}
})
//
if (typeof value === 'object') {
this._listen(value, parent[key])
}
}
})
}
_compile(root) {
const nodeList = root.children
if (nodeList.length != 0) {
Object.values(nodeList).map(node => {
// v-html
if (node.hasAttribute('v-html')) {
//
const attr = node.getAttribute('v-html').split('.')
let _p = this._property
attr.map(a => {
_p = _p[a]
})
//
const watcher = new Watcher({
el: node,
vm: this,
attr: 'innerHTML',
dataName: attr
})
_p._subscriber.push(watcher)
}
// v-text
if (node.hasAttribute('v-text')) {
//
const attr = node.getAttribute('v-text').split('.')
let _p = this._property
attr.map(a => {
_p = _p[a]
})
//
const watcher = new Watcher({
el: node,
vm: this,
attr: 'innerText',
dataName: attr
})
_p._subscriber.push(watcher)
}
// v-model
if (node.hasAttribute('v-model') && (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA')) {
//
const attr = node.getAttribute('v-model').split('.')
let _p = this._property
attr.map(a => {
_p = _p[a]
})
//
const watcher = new Watcher({
el: node,
vm: this,
attr: 'value',
dataName: attr
})
_p._subscriber.push(watcher)
//
node.addEventListener('input', () => {
let _d = this._data
attr.map((a, i) => {
if (i < attr.length - 1) {
_d = _d[a]
} else {
_d[a] = node.value
}
})
})
}
//
Object.values(node.attributes).map(attr => {
if (attr.name.match(/^@.*/)) {
const type = attr.name.substring(1)
const method = node.getAttribute(attr.name)
//
node.addEventListener(type, this._methods[method].bind(this._data))
}
})
//
this._compile(node)
})
}
}
}
class Watcher {
constructor({
el,
vm,
attr,
dataName
}) {
this._el = el
this._vm = vm
this._attr = attr
this._dataName = dataName
//
this._update()
}
_update() {
this._dataName.map(a => {
let _d = this._vm._data
this._dataName.map((item, index) => {
_d = _d[item]
})
this._el[this._attr] = _d
})
}
}
const app = new MyVm({
el: '.app',
data: {
name: ' ',
car: {
name: ' ',
color: ' '
}
},
methods: {
reset() {
this.name = ' '
this.car.name = ' '
this.car.color = ' '
}
}
})
script>
body>
html>