Vueデータの双方向バインドの原理と実現

9108 ワード

前言
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!' } } })