index.html文件html
<div id="app"> <p>{{name}}</p> <p k-text="name"></p> <p>{{age}}</p> <input type="text" k-model="name"> <button @click="changeName">呵呵</button> <div k-html="html"></div> </div> <script src="kvue.js"></script> <script src='compile.js'></script> <script> const kaikeba = new KVue({ el: '#app', data: { name: "I am test.", age: 12, html: '<button>這是⼀個按鈕</button>' }, created() { console.log('開始啦') setTimeout(() => { this.name = '我是測試' }, 1500) }, methods: { changeName() { this.name = '哈嘍,開課吧' this.age = 1 } } }) </script>
kvue.js文件 --> 相似vue.js文件vue
// 定義KVue構造函數 class KVue { constructor(options){ // 保存選項 this.$options = options // 傳入data this.$data = options.data // 響應化處理--數據的攔截處理 this.observe(this.$data) // new Watcher(this, 'foo') // this.foo // 讀一次,觸發依賴收集 // new Watcher(this, 'bar.mua') // this.bar.mua new Compile(options.el, this) if(options.created){ options.created.call(this) } } observe(value){ if(!value || typeof value !== 'object'){ return } // 遍歷 Object.keys(value).forEach(key => { // 響應式處理 this.defineReactive(value, key, value[key]) // 代理data中的數據到vue根上 this.proxyData(key) }) } defineReactive(obj, key, val){ // 遞歸 this.observe(val) // 定義了一個Dep const dep = new Dep() // 每一個dep的實例和data中每一個key有一對一關係 // 給obj的每個key定義攔截 Object.defineProperty(obj, key, { get(){ // 依賴收集 Dep.target && dep.addDep(Dep.target) return val }, set(newVal){ if(newVal !== val){ val = newVal // console.log(key + ':屬性更新了'); dep.notify() } } }) } // 在vue根上定義屬性代理data中的數據 proxyData(key){ // this指的就是KVue的實例 Object.defineProperty(this, key, { get(){ return this.$data[key] }, set(newVal){ this.$data[key] = newVal } }) } } // 建立Dep:管理全部Watcher class Dep { constructor(){ // 存儲全部依賴 this.watcher = [] } addDep(watcher){ this.watcher.push(watcher) } notify(){ this.watcher.forEach(watcher => watcher.update()) } } // 建立Watcher:保存data中數值和頁面中的掛鉤關係 class Watcher{ constructor(vm, key, cb){ // 建立實例時,馬上將該實例指向Dep.target便於依賴收集 Dep.target = this this.vm = vm this.key = key this.cb = cb Dep.target = this this.vm[this.key] // 觸發依賴收集 Dep.target = null } // 更新 update() { // console.log(this.key + '更新了!'); this.cb.call(this.vm, this.vm[this.key]) } }
compile.js 用途:編譯器node
// 遍歷dom結構,解析指令和插值表達式 class Compile { // el->待編譯的模板,vm->KVue實例 constructor(el, vm){ this.$vm = vm this.$el = document.querySelector(el) // 把模板中的內容移到片斷中去操做 this.$fragment = this.node2Fragment(this.$el) // 執行編譯 this.compile(this.$fragment) // 放回$el中 this.$el.appendChild(this.$fragment) } node2Fragment(el){ // 建立片斷 const fragment = document.createDocumentFragment() // let child while(child = el.firstChild) { fragment.appendChild(child) } return fragment } compile(el){ const childNodes = el.childNodes // childNodes是一個類數組 Array.from(childNodes).forEach(node => { if(node.nodeType == 1){ // 元素 // console.log('編譯元素'+node.nodeName); // 編譯元素 this.compileElement(node) }else if(this.isInter(node)){ // 只關心{{xxx}} // console.log('編譯插值文本' + node.textContent); // 編譯文本 this.compileText(node) } // 遞歸子節點 if(node.children && node.childNodes.length > 0){ this.compile(node) } }) } isInter(node){ return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent) } // 文本替換 compileText(node){ console.log(RegExp.$1); console.log(this.$vm[RegExp.$1]); // 表達式 const exp = RegExp.$1 this.update(node, exp, 'text') // 等同於t-text } compileElement(node){ // 關心屬性 const nodeAttrs = node.attributes Array.from(nodeAttrs).forEach(attr => { // 規定:k-xxx="yyy" const attrName = attr.name // k-xxx const exp = attr.value // yyy if(attrName.indexOf('k-') == 0){ // 指令 const dir = attrName.substring(2) // xxx // 執行 this[dir] && this[dir](node, exp) } }) } update(node, exp, dir){ const updator = this[dir+'Updator'] updator && updator(node, this.$vm[exp]) // 首次初始化 // 建立Watcher實例,依賴收集完成了 new Watcher(this.$vm, exp, function(value){ updator && updator(node, value) }) } textUpdator(node, value){ node.textContent = value } text(node, exp){ this.update(node, exp, 'text') } }