{"id":20916174,"url":"https://github.com/emosheeep/mvvm","last_synced_at":"2026-04-24T11:04:01.098Z","repository":{"id":41730120,"uuid":"235314090","full_name":"emosheeep/MVVM","owner":"emosheeep","description":"参考许多资料，归纳整理后，仿照Vue实现的一个MVVM框架。注重原理，用于学习。","archived":false,"fork":false,"pushed_at":"2022-12-11T21:26:34.000Z","size":1156,"stargazers_count":1,"open_issues_count":20,"forks_count":0,"subscribers_count":0,"default_branch":"defineProperty","last_synced_at":"2025-01-19T16:27:03.623Z","etag":null,"topics":["defineproperty","mvvm","vue"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emosheeep.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-21T10:18:32.000Z","updated_at":"2020-03-12T11:12:32.000Z","dependencies_parsed_at":"2023-01-27T07:16:22.607Z","dependency_job_id":null,"html_url":"https://github.com/emosheeep/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/emosheeep%2FMVVM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emosheeep%2FMVVM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emosheeep%2FMVVM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emosheeep%2FMVVM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emosheeep","download_url":"https://codeload.github.com/emosheeep/MVVM/tar.gz/refs/heads/defineProperty","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243308597,"owners_count":20270447,"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","mvvm","vue"],"created_at":"2024-11-18T16:20:13.159Z","updated_at":"2025-12-28T11:43:40.950Z","avatar_url":"https://github.com/emosheeep.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# MVVM框架实现原理\n[【掘金】MVVM框架响应式原理解析+实现](https://juejin.im/post/5e255e7df265da3dee21fc5d)\n\n# 运行\n```\n// 构建项目并运行到8080端口\nnpm run dev\n```\n\n# 框架实现\n\u003e vue.js 是采用数据劫持结合发布/订阅模式，通过Object.defineProperty()劫持各个属性的setter，getter，在数据变动时发布消息给订阅者，触发相应的监听函数\n\n框架实现主要包括以下三方面内容：\n1. 数据劫持（代理）\n2. 发布/订阅\n3. 模板编译\n\n前两点实现响应式，第三点解析自定义dom结构，解析自定义指令，添加订阅实现响应等，具体提到了再说。\n\n# 数据代理/劫持\n笔者目前了解到的监听对象属性的方法大都依赖于其一：\n1. Object.defineProperty()定义访问器属性（访问器属性不能直接定义）\n2. Proxy对象实现代理\n\n本文采用第一种方法，关于Proxy和Object.defineProperty的优劣区别对比，感兴趣的可以自行了解，这里不做过多展开。如果你还不了解上述方法，建议先收藏本文，然后等弄懂它们之后再来看，否则文章可能不太友好==。\n\n## Observer类\n首先我们创建一个Observer类用来为数据定义访问器属性。主要功能是为传入对象的每个属性定义访问器属性，这是实现响应式的第一步。\n\n```\nclass Observer {\n    constructor (data) {\n        this.observe(data)\n    }\n    // 观测对象\n    observe (data) {\n        // 注重原理，这里目的是判定数据是否为对象，更精确的还有Object.prototype.toString.call()\n        if(typeof data === 'object') {\n            // 遍历 data 对象属性，调用 defineReactive 方法\n            for(let key in data) {\n                this.defineReactive(data, key, data[key])\n            }\n        }\n    }\n    // defineReactive方法仅仅是将data的属性转换为访问器属性\n    defineReactive (data, key, val) {\n        // 递归观测子属性\n        this.observe(val)\n        // *****   重点    *****\n        // 为每一个属性都添加一个订阅\n        // 这里参且记住，下面讲到Dep类的时候会详细阐述\n        let dep = new Dep() \n        // 定义访问器属性，响应式基础\n        Object.defineProperty(data, key, {\n            enumerable: true,\n            configurable: true,\n            get () {\n                // 这里暂且记住，下面讲到Dep类的时候会详细阐述\n                // 添加订阅者（Watcher），通过Dep.target获取\n                // 闭包的缘故，这里的每一个dep实例都是独一无二的，都会被存储用来监听对应的属性\n                Dep.target \u0026\u0026 dep.add(Dep.target)\n                return val \n                // 这里getter的返回值就是读取属性时获取到的值\n                // 为什么这里没有return data[key] ？\n                // 因为data[key]是读取操作，会再次触发getter属性，造成死循环，最终使得堆栈溢出\n                // 这其实和Proxy对象代理对象需要有一个target一样，总不能自己代理自己呀\n            },\n            set: newVal =\u003e {\n                // 和旧的值比对\n                // val是调用函数时传进来的对应属性的原始值\n                // 由于闭包，每个值都停留在内存中且互不影响\n                // newVal 是赋值操作时等号右边的值\n                if(val !== newVal){\n                    // 对新值进行观测，同样是为了实现响应式\n                    this.observe(newVal)\n                    // 更新闭包中的值否则get属性返回的值还是原始值\n                    val = newVal\n                    // 当数据改变后通知所有Watcher，执行回调函数实现对应的功能\n                    // 所以最后的结果就是，每一个属性都由自己的一个dep对象监听着\n                    // 这点很重要，因为在后面的框架中会添加很多的Watcher到dep对象中\n                    // 同一个属性在页面多处使用到了，那么每一处都有一个Watcher订阅\n                    // 数据改变后，dep通知所有Watcher\n                    // 订阅了该属性的所有Watcher将新数据渲染到页面完成刷新\n                    dep.notify() // 通知订阅者更新\n                }\n            }\n        })\n    }\n}\n```\n由于语言层面的限制，我们只能监听到data对象的常规属性。而对象不同于常规类型，属于引用类型的数据，保存在堆内存中，它的比较更为复杂，无法直接利用等号完成，这也就是为什么Vue中如果修改对象某一个属性时可能会出现没有响应到的情况，这时就需要使用Vue.set()函数强制更新，或者使用整体替换的方式，新建一个对象覆盖原对象。\n\n# 事件的发布/订阅\n到这里，我们实现了Observer类，访问器属性准备就绪.但是光有基础还不够，我们还差一些调度——**Dep类和Watcher类。**\n\nDep是Dependency的缩写，意为依赖，是用来收集依赖并将访问器属性与Watcher连接起来的桥梁\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femosheeep%2Fmvvm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femosheeep%2Fmvvm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femosheeep%2Fmvvm/lists"}