实现一个简单版 Vue2 双向数据绑定
实现一个简单版本 Vue,仅实现了 数据响应式、依赖收集、compile编译中的html和文本编译,起名为nvue,即新 vue。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> </head> <body> <div id="app"> <p n-text="counter"></p> <p n-html="desc"></p> <div>{{desc}}</div> </div> <script src="./nvue.js"></script> <script> const app = new NVue({ el: "#app", data: { counter: 1, desc: '<p>这是一个新 vue<span style="color:red">demo</span></p>', name: "xiaohong", }, methods: { add() { this.counter++; }, }, }); setInterval(() => { app.counter++; }, 2000); </script> </body> </html>
nvue.js
// 一共需要实现: // 1. Vue 类 // 2. Dep 类 // 3. Watcher 类
// 4. Compile 类 编译类
// 5. observer 函数 // 6. proxy 函数 // 7. reactive函数 // 一个对象代表一个 Observer // 一个 key 代表 dep 实例 // 一个对象 key 的使用代表一个 watcher // 实现数据响应式 function defineReactive(obj, key, val) { // key如果是对象,则需要递归绑定响应式 observer(val); // 一个 key 代表一个 dep,这里是给每一个 key 做响应式,所以创建一个 dep 实例对象 let dep = new Dep(); Object.defineProperty(obj, key, { get() { console.log("get", key); // get 获取值,则创建 dep,传入的为 watcher,即 Dep.target Dep.target && dep.addDep(Dep.target); return val; }, set(v) { if (v != val) { // 可能设置的还是为对象,需要递归传入做响应式 observer(v); // set 修改值,则通知 dep 修改 val = v; console.log("set", key, v); // 通知 dep 更新 dep.notify(); } }, }); } function observer(data) { // 保证仅对对象做响应式 if (typeof data !== "object" || !data) { return data; } Object.keys(data).forEach((key) => { defineReactive(data, key, data[key]); }); } // 给 vm 实例设置代理,可以通过 vue 实例直接获取到 data 对象属性 function proxy(vm) { // 因为仅对 vm.$data 上的 key 做拦截,所以需要遍历 vm.$data 的 keys Object.keys(vm.$data).forEach((key) => { // 拦截 vm Object.defineProperty(vm, key, { get() { return vm.$data[key]; }, set(v) { if (v !== vm.$data[key]) { vm.$data[key] = v; } }, }); }); } class NVue { // 传入 el,及配置对象 constructor(options) { this.el = options.el; this.$options = options; // 保存当前配置对象 this.$data = options.data; // 保存 当前data this.$vm = this; // 保存当前 vue 实例 // 创建 Dep 的实例 // this.dep = new Dep(); // Dep.target && this.dep.addDep(this) // 1.将所有 data 对象 变为响应式 observer(this.$data); proxy(this); // 2.为 当前 vue 实例 this 增加代理,让用户访问的 data 可以直接从vue 实例上获取,而不是必须从 this.$data上获取 // 3.data对象已变为响应式, 一切准备就绪,进行模版编译 new Compile(this.el, this.$vm); } } class Compile { constructor(el, vm) { this.el = el; this.vm = vm; this.compile(document.querySelector(el)); } // 编译函数 compile(el) { // 传入的节点一定有 childNodes 子节点,对子节点进行遍历 el.childNodes.forEach((node) => { // 元素 if (node.nodeType === 1) { // 如果 nodeType=1 则为元素节点 this.compileElements(node); // 如果 node 节点有 子节点 if (node.childNodes.length > 0) { // 递归编译 this.compile(node); } } else if (this.isInterText(node)) { // 如果 nodeTyppe=3 则为文本节点 this.compileText(node); } }); } isInterText(node) { // 判断插值文本,还要通过正则表达式验证 {{}} 格式 return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent); } // 编译元素节点 compileElements(node) { const nodeAttrs = node.attributes; Array.from(nodeAttrs).forEach((attr) => { const key = attr.name; // 获取属性 name const exp = attr.value; // 获取属性值 // 如果属性 key 是以 n-开头,说明为指令表达式 if (key.startsWith("n-")) { // 获取指令 const dir = key.substring("2"); this[dir] && this[dir](node, exp); } }); } // 编译文本节点 compileText(node) { // 注意:RegExp.$1,这里是临时取法,如果有别的正则不可以直接这样获取 this.update(node, RegExp.$1, "text"); } // 统一更新函数,在这里创建 watcher update(node, exp, dir) { const fn = this[dir + "Updater"]; console.log("update", this.vm[exp]); fn && fn(node, this.vm[exp]); new Watcher(this.vm, exp, (val) => { fn && fn(node, val); }); } // 文本更新函数 textUpdater(node, val) { node.textContent = val; } // html 更新函数 htmlUpdater(node, val) { node.innerHTML = val; } // 文本 text(node, exp) { this.update(node, exp, "text"); } // html html(node, exp) { this.update(node, exp, "html"); } } // 依赖类,一个 watcher 实例代表一个依赖 class Watcher { constructor(vm, key, updateFn) { this.vm = vm; // 通过 key 和 vm 来获取最新的 value 值 this.key = key; // 传入 updateFn 更新函数,需要再依赖被更新的时候调用,并传入最新的 value 值 this.updateFn = updateFn; // 全局变量设为 Dep.target Dep.target = this; // 获取 vm[key],触发 对象 key get 方法,进行依赖收集 this.vm[key]; // 依赖收集后将 Dep.target 置空 Dep.target = null; } update() { this.updateFn.call(this.vm, this.vm[this.key]); } } // 依赖收集类 class Dep { constructor() { // 定义依赖数组,存放依赖,每个依赖就是一个 watcher this.deps = []; } addDep(watcher) { // 依赖收集,即 watcher 收集 this.deps.push(watcher); } notify() { // 触发依赖更新,将依赖中所有的 watcher 的 update 方法都遍历一遍 this.deps.forEach((dep) => { dep.update(); }); } }
本代码参考自前端杨村长。
转载请注明出处:https://www.cnblogs.com/beileixinqing/p/16661753.html