We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
测试html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> --> <script src="./vue.js"></script> <body> <div id="app"> <form> <input type="text" v-model="number"> <button type="button" @click="increment">增加</button> </form> <h3 v-text="number"></h3> </div> <script> const vm = new Vue({ el:'#app', data: { number: 0 }, methods: { increment: function() { this.number ++; }, } }) </script> </body> </html>
Vue双向绑定实现:
class Observer{ constructor(data){ this.$data = data; this.observer(this.$data); } observer(obj){ if(typeof obj !== 'object') return; Object.keys(obj).forEach(key =>{ this.defineReactive(obj, key, obj[key]); }) } defineReactive(obj, key, value){ if(typeof value === 'object') this.observer(value); let dep = new Dep(); Object.defineProperty(obj, key, { get(){ if(window.target){ dep.addSubs(); } return value; }, set: (newVal) =>{ if(value === newVal) return; // 防止 newVal为对象的情况,需要重新将对象中的属性变为响应式 this.observer(); value = newVal; dep.notify(); } }) } } class Dep{ constructor(){ this.subs = []; } addSubs(){ this.subs.push(window.target); } notify(){ this.subs.forEach(watcher => watcher.update()); } } class Watcher{ constructor(vm, expr, cb){ this.$vm = vm; this.expr = expr; this.cb = cb; this.getter(); } update(){ let newVal; if(typeof this.expr === 'function'){ newVal = this.expr(); } else { newVal = compileUtil.getValue(this.expr, this.$vm); } // let newVal = compileUtil.getValue(this.expr, this.$vm); if(this.value === newVal) return; this.value = newVal; this.cb(); } getter(){ window.target = this; if(typeof this.expr === 'function'){ this.value = this.expr(); } else { this.value = compileUtil.getValue(this.expr, this.$vm); } window.target = null; } } // Class版 class Vue{ constructor(options){ this.$data = options.data; this.$el = options.el; this.$option = options; if(this.$el){ // 将数据变为响应式 new Observer(this.$data) // 代理$data this.proxyVm(this.$data) // 代理computed this.proxyVm(this.$option.computed) // 编译模板 new Compile(this.$el, this) } } proxyVm(data) { for(let key in data){ Object.defineProperty(this, key, { get(){ return data[key]; }, set(newVal){ data[key] = newVal; } }) } } } class Compile{ constructor(el, vm){ this.$el = this.isElementNode(el) ? el: document.querySelector(el); this.$vm = vm; // 在内存中创建一个和 $el相同的元素节点 let fragment = this.node2fragment(this.$el); // 解析模板($el节点) this.compile(fragment); // 将解析后的节点重新挂载到DOM树上 this.$el.appendChild(fragment); } // 判断node是否为元素节点 isElementNode(node) { return node.nodeType === 1; } // 判断是否为v-开头的Vue指令 isDirective(attr) { return attr.startsWith('v-'); } isSpecialisDirective(attr){ return attr.startsWith('@'); } compile(fragment){ // 获取根节点的子节点 let childNodes = fragment.childNodes; [...childNodes].forEach(child =>{ if(this.isElementNode(child)){ // 解析元素节点的属性,查看是否存在Vue指令 this.compileElement(child); // 如果子节点也是元素节点,则递归执行该函数 this.compile(child); }else{ // 解析文本节点,查看是否存在"{{}}" this.compileText(child); } }) } // 编译元素 compileElement(node){ // 获取元素节点的所有属性 let attrs = node.attributes; // 遍历所有属性,查找是否存在Vue指令 [...attrs].forEach(attr =>{ // name: 属性名, expr: 属性值 let {name, value:expr} = attr; // 判断是不是指令 if(this.isDirective(name)){ let [,directive] = name.split('-'); // 如果为指令则去设置该节点的响应式函数 compileUtil[directive](node, expr, this.$vm); } if(this.isSpecialisDirective(name)){ let eventName = name.substr(1); compileUtil['on'](node, eventName, expr, this.$vm); } }) } // 编辑文本 compileText(node){ let content = node.textContent; // 匹配 {{xxx}} if(/\{\{(.+?)\}\}/.test(content)){ compileUtil['contentText'](node, content, this.$vm); } } // 把节点移动到内存中 node2fragment(node){ // 创建文档碎片 let fragment = document.createDocumentFragment(); let firstChild; while(firstChild = node.firstChild){ // appendChild具有移动性 fragment.appendChild(firstChild); } return fragment; } } const compileUtil = { getValue(expr, vm){ let valOrFn = expr.split('.').reduce((totalValue, key) =>{ if(!totalValue[key]) return null; return totalValue[key]; }, vm) return typeof valOrFn === 'function' ? valOrFn.call(vm) : valOrFn; }, setValue(expr, vm, value){ return expr.split('.').reduce((totalValue, key, index, arr) =>{ if(index === arr.length - 1) totalValue[key] = value; return totalValue[key]; }, vm.$data) }, getContentValue(content, vm){ return content.replace(/\{\{(.+?)\}\}/g, (...args) =>{ return this.getValue(args[1], vm); }) }, contentText(node, content, vm){ let fn = () =>{ this.textUpdater(node, this.getContentValue(content, vm)); } let resText = content.replace(/\{\{(.+?)\}\}/g, (...args) =>{ // args[1] 为{{xxx}}中的xxx new Watcher(vm, args[1], fn); return this.getValue(args[1], vm); }); // 首次解析直接替换文本内容 this.textUpdater(node, resText); }, text(node, expr, vm){ let value = this.getValue(expr, vm); this.textUpdater(node, value); let fn = () =>this.textUpdater(node, this.getValue(expr, vm)); new Watcher(vm, expr, fn); }, textUpdater(node, value){ node.textContent = value; }, html(node, expr, vm){ let value = this.getValue(expr, vm); this.htmlUpdater(node, value); let fn = () =>this.htmlUpdater(node, this.getValue(expr, vm)); new Watcher(vm, expr, fn); }, htmlUpdater(node, value){ node.textContent = value; }, model(node, expr, vm){ let value = this.getValue(expr, vm); this.modelUpdater(node, value); let fn = () => this.modelUpdater(node, this.getValue(expr, vm)); node.addEventListener('input', ()=>{ this.setValue(expr, vm, node.value); }) new Watcher(vm, expr, fn) }, modelUpdater(node, value){ node.value = value; }, on(node, eventName, expr, vm){ // 改变this为vm实例 let fn = vm.$option.methods[expr].bind(vm); // 添加事件 node.addEventListener(eventName, fn); } }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
测试html:
Vue双向绑定实现:
The text was updated successfully, but these errors were encountered: