index.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文件
// 定义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 用途:编译器
// 遍历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') } }