Vueデータの双方向バインドの原理と実現
9108 ワード
前言
Vueを用いてしばらく経ったので,Vueのコア機能である双方向のデータバインド原理を研究し実現した.次は直接コードをつけます.コードには基本的に注釈がはっきり書いてあるので、くだらないことは言いません.(地元まで直結して運行可能)お料理を出します~~
本文
Vueを用いてしばらく経ったので,Vueのコア機能である双方向のデータバインド原理を研究し実現した.次は直接コードをつけます.コードには基本的に注釈がはっきり書いてあるので、くだらないことは言いません.(地元まで直結して運行可能)お料理を出します~~
本文
Document
{{text}}
//vue
//1.observe
//2.Dep ( , )
//3.Watcher ( )
//4.Compile HTML
//5.Vue
//1.observe
function observe (data) {
if(!data || typeof data !== 'object'){
return
}
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
})
}
function defineReactive (data, key, value) {
// object
observe(value)
// key dep
let dep = new Dep()
Object.defineProperty(data, key, {
configurable: false,
enumerable: true,
get: function() {
// Watcher get
// watcher subs
if(Dep.target){
dep.addSub(Dep.target);
}
return value
},
set: function(newValue) {
if(value === newValue){
return
}
value = newValue
dep.notice()
}
})
}
//2.Dep ( , )
function Dep () {
this.subs = []
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub)
},
notice: function() {
this.subs.forEach(function(sub) {
sub.update()
})
}
}
// watcher
Dep.target = null
//3. Watcher
function Watcher (vm, key, callback) {
this.callback = callback
this.vm = vm
this.key = key
// get ,
this.value = this.get()
}
Watcher.prototype = {
update: function() {
this.run()
},
run: function() {
// get
//
let value = this.vm[this.key]
let oldValue = this.value
if(oldValue !== value){
this.callback.call(this.vm, value, oldValue)
}
},
get: function() {
// watcher
Dep.target = this
// new Watcher
// get , watcher sub
let value = this.vm[this.key]
//
Dep.target = null
return value
}
}
//4.compile
function Compile (el, vm) {
this.vm = vm
this.$el = document.querySelector(el)
if(this.$el){
// dom , dom
this.$fragment = this.createFragment(this.$el)
//
this.init()
this.$el.appendChild(this.$fragment)
}
}
Compile.prototype = {
createFragment: function (el) {
let fragment = document.createDocumentFragment()
let child
while (child = el.firstChild) {
// firstChild firstElementChild
// , ,
fragment.appendChild(child)
}
return fragment
},
init: function () {
this.compileElement(this.$fragment)
},
compileElement: function (el) {
// childNodes ,children
let childNodes = el.childNodes
let self = this
Array.from(childNodes).forEach(function (node) {
let text = node.textContent
let reg = /\{\{(.*)\}\}/
if (node.nodeType === 1){
//
self.compile(node)
}else if (node.nodeType === 3 && reg.test(text)) {
//
self.compileText(node, RegExp.$1)
}
})
},
compileText: function (node, exp) {
let text = this.vm[exp]
//
node.textContent = text
// , model => view
new Watcher(this.vm, exp, function (value) {
node.textContent = value
})
},
compile: function (node) {
//
let nodeAttrs = node.attributes
Array.from(nodeAttrs).forEach((attr) => {
let attrName = attr.name
// ,v-
if(this.isDireactive(attrName)){
let exp = attr.value
let dir = attrName.slice(2)
// , ?
if(this.isEventDireactive(dir)){
//
compileUtil.eventHander(node, this.vm, exp, dir)
} else {
//
//
// v-model
compileUtil[dir](node, this.vm, exp)
}
}
})
//
this.compileElement(node)
},
isDireactive: function (attrName) {
if(attrName.includes('v-')){
return true
}
return false
},
isEventDireactive: function (dir) {
if(dir.includes('on')){
return true
}
return false
}
}
let compileUtil = {
model: function (node, vm, exp) {
//
node.value = vm[exp]
//view => model
node.addEventListener('input', function (event) {
vm[exp] = event.target.value
})
//modal => view
new Watcher(vm, exp, function (value) {
node.value = value
})
// get ,
node.nodeValue = vm[exp]
},
eventHander: function (node, vm, exp, dir) {
// ,click
let eventName = dir.slice(3)
//
node.addEventListener(eventName, function (event) {
vm[exp]()
})
}
}
//5.Vue
function Vue (options) {
// new Vue
if (!this instanceof Vue) {
// ,this=>window
alert('please use Vue by "new" key word!')
}
let vm = this
vm.$data = options.data
vm.$methods = options.methods
let data = options.data
let methods = options.methods
// vm.key vm.$data.key
Object.keys(data).forEach(function (key) {
vm.proxyKey(vm.$data, key)
})
// vm.key vm.$methods.key
Object.keys(methods).forEach(function (key) {
vm.proxyKey(vm.$methods, key)
})
observe(data)
vm.$compile = new Compile(options.el, vm)
}
Vue.prototype = {
proxyKey: function (targetObj, key) {
let vm = this
Object.defineProperty(vm, key, {
configurable: false,
enumerable: true,
get: function () {
return targetObj[key]
},
set: function (newValue) {
targetObj[key] = newValue
}
})
}
}
let vm = new Vue({
el: '#app',
data: {
text: 'hello world!'
},
methods: {
changeText: function () {
this.$data.text = 'hello vue world!'
}
}
})