# MVVM **Repository Path**: srect/MVVM ## Basic Information - **Project Name**: MVVM - **Description**: 简易版mvvm 数据劫持+发布订阅 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-08-07 - **Last Updated**: 2021-08-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 实现属于自己的MVVM --- --- [![license](https://img.shields.io/badge/LICENSE-MIT-blue.svg)]((https://github.com/sRect/MVVM/blob/master/LICENSE)) > 思路 ![avatar](https://github.com/sRect/MVVM/blob/master/images/%E6%80%9D%E8%B7%AF.png?raw=true) 1. index.html ```html

{{msg.a}} {{msg.b}}

``` 2. MVVM.js ```javascript class MVVM { constructor(options) { this.$el = options.el; this.$data = options.data; if (this.$el) { // 编译之前,对数据进行劫持 new Observer(this.$data); this.proxyData(this.$data) // 用数据和元素进行编译 new Compile(this.$el, this); } } } ``` 3. compile.js(模板的编译) ```javascript class Compile { constructor(el, vm) { this.$el = this.isElementNode(el) ? el : document.querySelector(el); this.vm = vm; // 元素存在进行编译 if (this.$el) { // 1.把真实的DOM放入到内存中 fragment let fragment = this.node2fragment(this.$el); // 2.编译 元素节点 v-model 文本节点 {{}} this.compile(fragment); // 3.把编译好的fragment放回页面中 this.$el.appendChild(fragment); } } // 节点转成文档碎片 node2fragment(el) { let fragment = document.createDocumentFragment(); let firstChild = null; while (firstChild = el.firstChild) { fragment.appendChild(firstChild); } return fragment; } // 编译元素 compileElement(node) { let attrs = node.attributes; Array.from(attrs).forEach(attr => { // 判断属性名字是否包含v- let attrName = attr.name; if (this.isDirective(attrName)) { // 取到对应的值放到节点中 node this.vm.$data expr let expr = attr.value; // let type = attrName.slice(2); let [, type] = attrName.split("-"); CompileUtil[type](node, this.vm, expr); } }) } // 编译文本 compileText(node) { ... } // 进行编译 compile(fragment) { let childNodes = fragment.childNodes; [...childNodes].forEach(node => { if (this.isElementNode(node)) { // 元素节点,需要递归 this.compileElement(node); this.compile(node); } else { // 文本 this.compileText(node); } }) } } CompileUtil = { text(node, vm, expr) { ... }, model(node, vm, expr) { let updateFn = this.updater['modelUpdater']; updateFn && updateFn(node, this.getVal(vm, expr)); }, updater: { // 文本更新 textUpdater(node, value) { node.textContent = value; }, // 输入框更新 modelUpdater(node, value) { node.value = value; } } } ``` 3. observer.js(数据劫持) ```javascript class Observer { constructor(data) { this.observer(data); } observer(data) { // data不是对象或者为null,直接return if (!data || typeof (data) !== 'object') { return; } // 要将数据进行劫持 Object.keys(data).forEach((key) => { this.defineReactive(data, key, data[key]); this.observer(data[key]); // 递归深度劫持 }) } // 定义响应式 defineReactive(obj, key, value) { let _this = this; Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { return value; }, set(newVal) { if (newVal !== value) { _this.observer(newVal); // 如果设置的值是对象,还需要从进行劫持 value = newVal; } } }) } } ``` 4. watcher.js(观察者) ```javascript class Watcher { constructor(vm, expr, cb) { this.vm = vm; this.expr = expr; this.cb = cb; // 先获取旧值 this.value = this.get(); } getVal(vm, expr) { // 获取实例上对应的数据 return expr.split(".").reduce((prev, next) => { return prev[next]; }, vm.$data) } get() { let val = this.getVal(this.vm, this.expr); return val; } // 对外暴露的方法 update() { let newVal = this.getVal(this.vm, this.expr); let oldVal = this.value; if (newVal !== oldVal) { this.cb(newVal); // 调用watch的callback } } } ```