目标:
- 完成数据的双向绑定
- 完成v-bind
- 完成v-on
- 完成v-if
学习Proxy,掌握其原理
let object = { num: 0, name: 'liming' };// 根据observed包装后,在get的时候获取值,set的时候修改值function reactive(obj) {let observed = new Proxy(obj, {get: function (obj, prop) {// obj代表所以对象 {name: "张三", age:12} props 代表获取谁console.log(obj, prop);return obj[prop];},set: function (obj, prop, value) {// obj代表所以对象 {name: "张三", age:12} props 代表替换谁 value 代表要替换的值console.log(obj, prop, value);obj[prop] = value;return true;},});return observed;}let result = reactive(object);console.log(result.num, '///');result.name = 'xiaohua';console.log(object, 'object');
实现简化版的RockVue
<!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><body><!-- <div id="app">{{message}}<input v-model="message" /></div> --><!-- <div id="app-2"><span v-bind:title="message">鼠标悬停几秒钟查看此处动态绑定的提示信息!</span></div> --><!-- <div id="app-5"><p>{{ message }}</p><button v-on:click="reverseMessage">反转消息</button></div> --><div id="app-3"><p v-if="seen">现在你看到我了</p><button v-on:click="reverseSeen">反转消息</button></div></body><script type="module">import { RockVue as Vue } from './rockVue.js';new Vue({el: '#app-3',data: {seen: false,},methods: {reverseMessage: function () {this.message = this.message.split('').reverse().join('');},reverseSeen: function () {this.seen = !this.seen;// console.log(this.seen, ' this.seen');},},});</script></html>
export class RockVue {constructor(config) {this.template = document.querySelector(config.el);this.data = reactive(config.data);// 处理method的方法for (const name in config.methods) {this[name] = () => {config.methods[name].apply(this.data);};}this.traversal(this.template);}traversal(node) {if (node.nodeType === Node.TEXT_NODE) {if (node.textContent.trim().match(/^{{([\s\S]+)}}$/)) {let name = RegExp.$1.trim();effect(() => (node.textContent = this.data[name]));}}if (node.nodeType === Node.ELEMENT_NODE) {let attributes = node.attributes;console.log(attributes, 'attributes');for (let attribute of attributes) {// v-modelif (attribute.name === 'v-model') {let name = attribute.value;effect(() => (node.value = this.data[name]));node.addEventListener('input', (event) => {this.data[name] = node.value;});}// v-bind titleif (attribute.name.match(/^v\-bind:([\s\S]+)$/)) {let attrname = RegExp.$1;let name = attribute.value;effect(() => node.setAttribute(attrname, this.data[name]));}// v-on 事件处理if (attribute.name.match(/^v\-on:([\s\S]+)$/)) {let eventName = RegExp.$1;let fnname = attribute.value;node.addEventListener(eventName, this[fnname]);}// v-if 条件处理if (attribute.name === 'v-if') {let value = attribute.value;effect(() => {node.style.display = this.data[value] ? 'block' : 'none';});}}}if (node.childNodes && node.childNodes.length) {for (let child of node.childNodes) {this.traversal(child);}}}}let effects = new Map();let currentEffect = null;function effect(fn) {currentEffect = fn;fn();currentEffect = null;}function reactive(obj) {let observed = new Proxy(obj, {get: function (obj, prop) {// obj代表所以对象 {name: "张三", age:12} props 代表获取谁// console.log(obj, prop);if (currentEffect) {if (!effects.has(obj)) effects.set(obj, new Map());if (!effects.get(obj).has(prop))effects.get(obj).set(prop, new Array());effects.get(obj).get(prop).push(currentEffect);}return obj[prop];},set: function (obj, prop, value) {// obj代表所以对象 {name: "张三", age:12} props 代表替换谁 value 代表要替换的值// console.log(obj, prop, value);window.obj = obj;obj[prop] = value;if (effects.has(obj) && effects.get(obj).has(prop)) {for (let effect of effects.get(obj).get(prop)) {effect();}}return true;},});return observed;}// console.log(effects, 'effects');// window.effects = effects;
原理说明
主要通过Proxy对对象进行绑定监听处理,通过new Map对对象的属性操作进行处理,将要执行的函数匹配到存到对应的prop上面,通过每次的访问触发get方法,进行存方法的操作,通过修改触发set的方法,此时执行回调监听的函数,这样达到修改数据和视图的
