中国建设行业峰会网站,电子商务网站开发项目设计报告,自身网站的建设和推广力度不足,wordpress 付费主题 高级功能编辑器前言
双向数据绑定人人都会背了#xff0c;已经没什么新奇了。 但是如果遇到XX喜欢问源码之类的#xff0c;或者问你设计思路你又该如何应对呢#xff0c;所以下面这篇文章主要是为了记录双向数据绑定的一个实现#xff0c;采用了类的方式#xff0c;积极向面向对象编程靠…前言
双向数据绑定人人都会背了已经没什么新奇了。 但是如果遇到XX喜欢问源码之类的或者问你设计思路你又该如何应对呢所以下面这篇文章主要是为了记录双向数据绑定的一个实现采用了类的方式积极向面向对象编程靠拢。
这里采用的是vue2的数据劫持方式vue3可以参考 此处。 难点
1. Dep跟Watcher分别对应什么呢
一个Dep对应一个数据劫持属性一个Watcher对应模板一个双向绑定的变量或变量属性-- {{xxx.xxx}}或者v-model。
Dep是发布者从Observer类中可以看出Dep对应的劫持到的data或者data的某一个属性。即如果该值发生变化就会触发数据劫持set操作从而执行通知操作dep.notify()遍历执行watcher.update()从而更新视图。Watcher是观察者从Compiler类中可以看出解析对应的模板会读取数据触发数据劫持get操作从而触发dep.addSub(watcher)。 源码
!--* Author: Penk* LastEditors: Penk* LastEditTime: 2021-07-12 00:23:30* FilePath: \temp\myVue.html
--
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta http-equivX-UA-Compatible contentIEedge /meta nameviewport contentwidthdevice-width, initial-scale1.0 /titleDocument/titlestyle#app {text-align: center;margin: 100px auto auto auto;}/style/headbodydiv idappdiv v-htmlmsg/divinput v-modelauthor.name stylemargin-bottom: 20px /br /姓名{{author.name}}br /计算属性变大写{{toUpperCaseName}}br /br /button v-on:clickchange(author.name,自定义参数)test/button/div!-- script srchttps://cdn.jsdelivr.net/npm/vue/dist/vue.js/script --!-- script src./script.js/script --scriptclass Penk {constructor(options) {this.$el options.el;this.$data options.data;let methods options.methods;let computed options.computed;if (this.$el) {// 数据劫持初次劫持并没有触发new Dep()!!!new Observer(this.$data);// 设置代理过滤$data可直接访问data() 中的数据 this.xxxthis.proxyData(this.$data);// 设置methods同上this.proxyMethods(methods);// 设置computed同上this.proxyComputed(computed);// 将模板转化成对象进行解析// 默认会执行数据的get操作触发数据劫持并设置发布订阅模式new Compiler(this.$el, this);// 执行挂载mounted并且作用域指向dataoptions.mounted.call(this.$data);}}proxyData(data) {for (let key in data) {Object.defineProperty(this, key, {enumerable: true,get() {return this.$data[key];},set(newVal) {if (this.$data[key] ! newVal) {this.$data[key] newVal;}}});}}proxyMethods(methods) {for (let key in methods) {this.$data[key] methods[key];}}proxyComputed(computed) {for (let key in computed) {// this.$data[key] computed[key].call(this);Object.defineProperty(this.$data, key, {get: () {return computed[key].call(this);}});}}}// 观察者class Watcher {constructor(vm, expr, cb) {this.vm vm;this.expr expr;this.cb cb;this.oldValue this.get();}get() {Dep.target this;let val CompileUtils.getVal(this.expr, this.vm);Dep.target null;return val;}update() {let newVal CompileUtils.getVal(this.expr, this.vm);if (this.oldValue ! newVal) {this.cb(newVal);}}}// 订阅者class Dep {constructor() {this.subs [];}// 订阅addSub(watcher) {this.subs.push(watcher);}// 发布notify() {this.subs.forEach((watcher) watcher.update());}}// 编译者class Compiler {constructor(el, vm) {this.el this.getElementByEl(el);this.vm vm;// 获取dom节点let fragment this.node2fragment(this.el);// 编译模板 用数据编译this.compile(fragment);// 把内容塞到页面中this.el.appendChild(fragment);}// 核心编译方法compile(node) {let childNodes node.childNodes;[...childNodes].forEach((e) {if (e.nodeType 1) {this.compileElement(e);} else if (e.nodeType 3) {this.compileText(e);}});}// 编译文本compileText(node) {let text node.textContent;if (/\{\{(.*)\}\}/.test(text)) CompileUtils.text(node, text, this.vm);}// 编译元素compileElement(node) {this.compile(node);let attributes node.attributes;[...attributes].forEach((attr) {let { name, value } attr;if (this.isDirective(name)) {let [, directive] name.split(-);let [directiveName, eventName] directive.split(:);CompileUtils[directiveName](node, value, this.vm, eventName);}});}// 判断是否指令isDirective(attrName) {return attrName.startsWith(v-);}// 节点转片段node2fragment(el) {let fragment document.createDocumentFragment();let node;while ((node el.firstChild)) {fragment.appendChild(node);}return fragment;}// 获取元素getElementByEl(el) {if (el.nodeType 1) return el;return document.querySelector(el);}}// 编译工具var CompileUtils {getVal(expr, vm) {let data vm.$data;expr.split(.).forEach((e) {data data[e];});return data;},setVal(expr, vm, val) {let data vm.$data;expr.split(.).reduce((total, currentValue, index, arr) {if (index arr.length - 1) {total[currentValue] val;return;}return total[currentValue];}, data);},getContentValue(expr, vm) {let value expr.replace(/\{\{(.*)\}\}/g, (...args) {return this.getVal(args[1], vm);});return value;},getMethodObj(expr, vm) {console.log(expr);let leftIndex expr.indexOf(();let method expr.slice(0, leftIndex);let params expr.slice(leftIndex 1, expr.length - 1).split(,);vm.$data[method]().call(this, ...params);return {method};},// 指令model(node, expr, vm) {let value this.getVal(expr, vm);let fn this.update.modelUpdater;fn(node, value);new Watcher(vm, expr, (newVal) {fn(node, newVal);});node.addEventListener(input, (e) {let val e.target.value;this.setVal(expr, vm, val);});},html(node, expr, vm) {let value this.getVal(expr, vm);let fn this.update.htmlUpdater;fn(node, value);new Watcher(vm, expr, (newVal) {fn(node, newVal);});},// 事件绑定on(node, expr, vm, eventName) {node.addEventListener(eventName, () {let leftIndex expr.indexOf(();let method expr.slice(0, leftIndex);let params expr.slice(leftIndex 1, expr.length - 1).split(,);let temParams [];params.forEach((param) {if (param.indexOf() 0 || param.indexOf() 0) {param;temParams.push(param.slice(1, param.length - 1));} else {temParams.push(this.getVal(param, vm));}});vm.$data[method].call(this, ...temParams);});},text(node, expr, vm) {let fn this.update.textUpdater;let value expr.replace(/\{\{(.*)\}\}/g, (...args) {new Watcher(vm, args[1], (newVal) {fn(node, this.getContentValue(expr, vm));});return this.getVal(args[1], vm);});fn(node, value);},// 更新视图方法update: {modelUpdater(node, value) {node.value value;},htmlUpdater(node, value) {node.innerHTML value;},textUpdater(node, value) {node.textContent value;}}};// 数据劫持class Observer {constructor(data) {//初始化时候劫持数据this.observer(data);}observer(data) {if (data typeof data object) {for (let key in data) {this.defineReactive(data, key, data[key]);}}}defineReactive(obj, key, val) {this.observer(val);let dep new Dep();Object.defineProperty(obj, key, {enumerable: true,get() {Dep.target dep.addSub(Dep.target);return val;},set: (newVal) {if (val newVal) return;val newVal;// 重新赋值的时候劫持数据this.observer(newVal);dep.notify();}});}}/scriptscriptlet vm new Penk({el: #app,data: {author: {name: penk,age: 18,a: { aa: 1 }},msg: h1v-html/h1},methods: {change(...data) {alert(method,带参~ data);}},mounted() {// this.change(mounted);},computed: {toUpperCaseName() {return this.author.name.toUpperCase();}}});/script/body
/html 效果
待发…