{"id":19069619,"url":"https://github.com/srect/mvvm","last_synced_at":"2026-05-17T04:30:16.703Z","repository":{"id":114172795,"uuid":"178222687","full_name":"sRect/MVVM","owner":"sRect","description":"简易版mvvm 数据劫持+发布订阅","archived":false,"fork":false,"pushed_at":"2019-04-02T06:47:28.000Z","size":58,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-01-02T15:45:37.025Z","etag":null,"topics":["defineproperty","hijack","mvvm","two-way-bindings","vue"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sRect.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-03-28T14:40:42.000Z","updated_at":"2019-04-02T08:33:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"d70239f9-210b-40e7-89d5-d2816d3fd5d1","html_url":"https://github.com/sRect/MVVM","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2FMVVM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2FMVVM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2FMVVM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2FMVVM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sRect","download_url":"https://codeload.github.com/sRect/MVVM/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240122573,"owners_count":19751142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["defineproperty","hijack","mvvm","two-way-bindings","vue"],"created_at":"2024-11-09T01:14:57.263Z","updated_at":"2026-05-17T04:30:16.673Z","avatar_url":"https://github.com/sRect.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"实现属于自己的MVVM\n---\n---\n[![license](https://img.shields.io/badge/LICENSE-MIT-blue.svg)]((https://github.com/sRect/MVVM/blob/master/LICENSE))\n\n\u003e　思路\n![avatar](https://github.com/sRect/MVVM/blob/master/images/%E6%80%9D%E8%B7%AF.png?raw=true)\n\n1. index.html\n```html\n\u003cbody\u003e\n  \u003cdiv id=\"app\"\u003e\n    \u003cinput type=\"text\" v-model=\"msg.a\"\u003e\n    \u003ch1 v-text=\"msg.a\"\u003e\u003c/h1\u003e\n    \u003cdiv\u003e\u003c/div\u003e\n    \u003cul\u003e\n      \u003cli\u003e1\u003c/li\u003e\n      \u003cli\u003e{{}}\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003e{{msg.a}} {{msg.b}}\u003c/p\u003e\n  \u003c/div\u003e\n\n  \u003cscript src=\"./watcher.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"./observer.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"./compile.js\"\u003e\u003c/script\u003e\n  \u003cscript src=\"./MVVM.js\"\u003e\u003c/script\u003e\n  \u003cscript\u003e\n    let vm = new MVVM({\n      el: \"#app\",\n      data: {\n        msg: {\n          a: \"hello world\",\n          b: \"你好\"\n        },\n        b: null,\n        c: {\n          c_1: \"wait...\"\n        }\n      }\n    })\n  \u003c/script\u003e\n\u003c/body\u003e\n```\n\n2. MVVM.js\n```javascript\nclass MVVM {\n  constructor(options) {\n    this.$el = options.el;\n    this.$data = options.data;\n\n    if (this.$el) {\n      // 编译之前，对数据进行劫持\n      new Observer(this.$data);\n      this.proxyData(this.$data)\n      // 用数据和元素进行编译\n      new Compile(this.$el, this);\n    }\n  }\n}\n```\n\n3. compile.js(模板的编译)\n```javascript\nclass Compile {\n  constructor(el, vm) {\n    this.$el = this.isElementNode(el) ? el : document.querySelector(el);\n    this.vm = vm;\n\n    // 元素存在进行编译\n    if (this.$el) {\n      // 1.把真实的DOM放入到内存中 fragment\n      let fragment = this.node2fragment(this.$el);\n      // 2.编译 元素节点 v-model  文本节点 {{}}\n      this.compile(fragment);\n      // 3.把编译好的fragment放回页面中\n      this.$el.appendChild(fragment);\n    }\n  }\n\n  // 节点转成文档碎片\n  node2fragment(el) {\n    let fragment = document.createDocumentFragment();\n    let firstChild = null;\n\n    while (firstChild = el.firstChild) {\n      fragment.appendChild(firstChild);\n    }\n\n    return fragment;\n  }\n\n  // 编译元素\n  compileElement(node) {\n    let attrs = node.attributes;\n\n    Array.from(attrs).forEach(attr =\u003e {\n      // 判断属性名字是否包含v-\n      let attrName = attr.name;\n      if (this.isDirective(attrName)) {\n        // 取到对应的值放到节点中 node this.vm.$data expr\n        let expr = attr.value;\n        // let type = attrName.slice(2);\n        let [, type] = attrName.split(\"-\");\n        CompileUtil[type](node, this.vm, expr);\n      }\n    })\n  }\n\n  // 编译文本\n  compileText(node) {\n    ...\n  }\n\n  // 进行编译\n  compile(fragment) {\n    let childNodes = fragment.childNodes;\n\n    [...childNodes].forEach(node =\u003e {\n      if (this.isElementNode(node)) {\n        // 元素节点，需要递归\n        this.compileElement(node);\n        this.compile(node);\n      } else {\n        // 文本\n        this.compileText(node);\n      }\n    })\n  }\n}\n\nCompileUtil = {\n  text(node, vm, expr) {\n    ...\n  },\n  model(node, vm, expr) {\n    let updateFn = this.updater['modelUpdater'];\n    updateFn \u0026\u0026 updateFn(node, this.getVal(vm, expr));\n  },\n  updater: {\n    // 文本更新\n    textUpdater(node, value) {\n      node.textContent = value;\n    },\n    // 输入框更新\n    modelUpdater(node, value) {\n      node.value = value;\n    }\n  }\n}\n```\n\n3. observer.js(数据劫持)\n```javascript\nclass Observer {\n  constructor(data) {\n    this.observer(data);\n  }\n\n  observer(data) {\n    // data不是对象或者为null,直接return\n    if (!data || typeof (data) !== 'object') {\n      return;\n    }\n\n    // 要将数据进行劫持\n    Object.keys(data).forEach((key) =\u003e {\n      this.defineReactive(data, key, data[key]);\n      this.observer(data[key]); // 递归深度劫持\n    })\n  }\n\n  // 定义响应式\n  defineReactive(obj, key, value) {\n    let _this = this;\n    Object.defineProperty(obj, key, {\n      enumerable: true,\n      configurable: true,\n      get() {\n        return value;\n      },\n      set(newVal) {\n        if (newVal !== value) {\n          _this.observer(newVal); // 如果设置的值是对象，还需要从进行劫持\n          value = newVal;\n        }\n      }\n    })\n  }\n}\n```\n\n4. watcher.js(观察者)\n```javascript\nclass Watcher {\n  constructor(vm, expr, cb) {\n    this.vm = vm;\n    this.expr = expr;\n    this.cb = cb;\n\n    // 先获取旧值\n    this.value = this.get();\n  }\n\n  getVal(vm, expr) { // 获取实例上对应的数据\n    return expr.split(\".\").reduce((prev, next) =\u003e {\n      return prev[next];\n    }, vm.$data)\n  }\n\n  get() {\n    let val = this.getVal(this.vm, this.expr);\n    return val;\n  }\n\n  // 对外暴露的方法\n  update() {\n    let newVal = this.getVal(this.vm, this.expr);\n    let oldVal = this.value;\n    if (newVal !== oldVal) {\n      this.cb(newVal); // 调用watch的callback\n    }\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrect%2Fmvvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrect%2Fmvvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrect%2Fmvvm/lists"}