一. 原理

  1. 数据响应式核心原理-vue2
    Object.defineProperty(obj, key, {})
  2. 数据响应式核心原理-vue3
    new Proxy(obj, {})

    二. 原理流程

    image.png

三. 简版Vue响应式代码实现

1. index.html 代码

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>mini-vue</title>
  8. </head>
  9. <body>
  10. <div id="app">
  11. <p>{{msg}}</p>
  12. <p>{{count}}</p>
  13. <p>v-text</p>
  14. <p v-text="msg"></p>
  15. <p>v-module</p>
  16. <input type="text" v-module="msg">
  17. <input type="text" v-module="count">
  18. </div>
  19. <script src="./dep.js"></script>
  20. <script src="./watcher.js"></script>
  21. <script src="./observer.js"></script>
  22. <script src="./compiler.js "></script>
  23. <script src="./vue.js"></script>
  24. <script>
  25. const vm = new Vue({
  26. el: "#app",
  27. data: {
  28. msg: "hello vue",
  29. count: 90,
  30. info: { height: 170 }
  31. }
  32. })
  33. </script>
  34. </body>
  35. </html>

2. Dep.js代码, 观察者模式中的发布者

  1. class Dep {
  2. constructor() {
  3. // 记录所有的订阅者
  4. this.subs = []
  5. }
  6. // 添加订阅者
  7. addSub(sub) {
  8. if(sub && sub.update){
  9. this.subs.push(sub)
  10. }
  11. }
  12. // 发布通知
  13. notify() {
  14. this.subs.forEach( sub => {
  15. sub.update()
  16. })
  17. }
  18. }

3. Watcher.js代码

  1. class Watcher {
  2. constructor(vm, key, cb) {
  3. this.vm = vm
  4. this.key = key
  5. // 回调函数负责更新视图
  6. this.cb = cb
  7. // 把watcher对象记录到Dep类到静态属性target
  8. Dep.target = this
  9. // 触发get方法, 在get方法中会调用addSub
  10. this.oldValue = vm[key]
  11. Dep.target = null
  12. }
  13. // 当数据发生变化的时候更新视图
  14. update () {
  15. const newValue = this.vm[this.key]
  16. if (this.oldValue === newValue) {
  17. return
  18. }
  19. this.cb(newValue)
  20. }
  21. }

4. Observer.js 代码

  1. class Observer {
  2. constructor(data) {
  3. this.walk(data)
  4. }
  5. walk(data) {
  6. if (!data || typeof data !== "object") {
  7. return
  8. }
  9. // 遍历data对象的所有属性, 为每一个属性设置getter, setter
  10. Object.keys(data).forEach(key => {
  11. this.defineReactive(data, key, data[key])
  12. })
  13. }
  14. defineReactive(obj, key, val) {
  15. // 负责收集依赖, 并发送通知
  16. const dep = new Dep()
  17. // 当data的属性值为对象的时候再调用walk为其设置getter和setter
  18. this.walk(val)
  19. const that = this
  20. Object.defineProperty(obj, key, {
  21. enumerable: true,
  22. configurable: true,
  23. get() {
  24. // 收集依赖
  25. Dep.target && dep.addSub(Dep.target)
  26. return val
  27. },
  28. set(newValue) {
  29. if (newValue === val) {
  30. return
  31. }
  32. val = newValue
  33. // 当将data里当值重新赋值为对象的时候,再次调用walk为其设置getter和setter
  34. that.walk(newValue)
  35. // 发送通知
  36. dep.notify()
  37. }
  38. })
  39. }
  40. }

5. Compiler.js 代码

  1. class Compiler {
  2. constructor (vm) {
  3. this.el = vm.$el
  4. this.vm = vm
  5. this.compile(this.el)
  6. }
  7. // 编译模版,处理文本节点和元素节点
  8. compile (el) {
  9. const childNodes = el.childNodes
  10. Array.from(childNodes).forEach(node => {
  11. if (this.isElementNode(node)) { // 元素节点
  12. this.compileElement(node)
  13. } else if (this.isTextNode(node)) { // 文本节点
  14. this.compileText(node)
  15. }
  16. // 如果有子节点,递归调用compile
  17. if (node.childNodes && node.childNodes.length) {
  18. this.compile(node)
  19. }
  20. })
  21. }
  22. // 编译元素节点,处理指令
  23. compileElement (node) {
  24. Array.from(node.attributes).forEach(attr => {
  25. let attrName = attr.name
  26. if (this.isDirective(attrName)) {
  27. // v-text ==> text
  28. attrName = attrName.substr(2)
  29. const key = attr.value
  30. this.update(node, key, attrName)
  31. }
  32. })
  33. }
  34. update (node, key, attrName) {
  35. const updateFn = this[attrName + "Updater"]
  36. updateFn && updateFn.call(this, node, this.vm[key], key)
  37. }
  38. // 处理 v-text 指令
  39. textUpdater (node, value, key) {
  40. node.textContent = value
  41. new Watcher(this.vm, key, newValue => {
  42. node.textContent = newValue
  43. })
  44. }
  45. // 处理v-model指令
  46. moduleUpdater (node, value, key) {
  47. node.value = value
  48. new Watcher(this.vm, key, newValue => {
  49. node.value = newValue
  50. })
  51. // 双向绑定
  52. node.addEventListener("input", () => {
  53. this.vm[key] = node.value
  54. })
  55. }
  56. // 编译文本节点,处理差值表达式
  57. compileText (node) {
  58. // {{ msg }}
  59. const reg = /\{\{(.+?)\}\}/
  60. const value = node.textContent
  61. if (reg.test(value)) {
  62. const key = RegExp.$1.trim()
  63. node.textContent = value.replace(reg, this.vm[key])
  64. new Watcher(this.vm, key, newValue => {
  65. node.textContent = newValue
  66. })
  67. }
  68. }
  69. // 判断属性是否是指令
  70. isDirective (attrName) {
  71. return attrName.startsWith("v-")
  72. }
  73. // 判断节点是否文本节点
  74. isTextNode (node) {
  75. return node.nodeType === 3
  76. }
  77. // 判断是否元素节点
  78. isElementNode (node) {
  79. return node.nodeType === 1
  80. }
  81. }

6. Vue.js 代码

  1. class Vue {
  2. constructor(options) {
  3. // 1 通过属性保存选项的数据
  4. this.$options = options || {}
  5. this.$el = typeof options.el === "string"
  6. ? document.querySelector(options.el)
  7. : options.el
  8. this.$data = options.data || {}
  9. // 2 把data中的成员转换成getter和setter, 注入到vue实例中
  10. this._proxyData(this.$data)
  11. // 3 调用observer对象,监听数据的变化
  12. new Observer(this.$data)
  13. // 4 解析指令
  14. new Compiler(this)
  15. }
  16. _proxyData(data) {
  17. Object.keys(data).forEach(key => {
  18. Object.defineProperty(this, key, {
  19. enumerable: true,
  20. configurable: true,
  21. get() {
  22. return data[key]
  23. },
  24. set(newValue){
  25. if (newValue === data[key]) {
  26. return
  27. }
  28. data[key] = newValue
  29. }
  30. })
  31. })
  32. }
  33. }

四. 发布订阅模式, Vue事件(如:自定义事件)处理机制

1. 发布订阅模式的理解

image.png
图解: 老师(信息发布者), 家长(信息订阅者, 订阅自己孩子的各科成绩), 学生成绩, 三个对象, 只要老师在同学群里发布学生的学习成绩, 在学生群里的家长(订阅者)就能收到孩子的成绩, 而不用每天追着问孩子

2. Vue自定义事件的实现如下

  1. // 事件触发器
  2. class EventEmitter {
  3. constructor() {
  4. // 学生群
  5. this.subs = Object.create(null)
  6. }
  7. // 注册事件, 学生家长加入群
  8. $on(eventType, handler){
  9. this.subs[eventType] = this.subs[eventType] || []
  10. this.subs[eventType].push(handler)
  11. }
  12. // 触发事件
  13. $emit(eventType) {
  14. if(this.subs[eventType]){
  15. this.subs[eventType].forEach(handler => {
  16. // 每个老师发布成绩这个动作
  17. handler()
  18. })
  19. }
  20. }
  21. }
  22. // test
  23. // 先建一个微信群(当然这个群应该是班主任建的,因为只有班主任更熟悉各科任课老师和每位同学的家长)
  24. const ee = new EventEmitter()
  25. // 第一个同学的家长入群了(订阅孩子成绩)
  26. ee.$on("click", () => {
  27. console.log("click1")
  28. })
  29. // 又一个同学的家长入群了(订阅孩子成绩)
  30. ee.$on("click", () => {
  31. console.log("click2")
  32. })
  33. // 老师发布成绩
  34. ee.$emit("click")

五. 观察者模式

1. 观察者模式的理解

image.png

2. 观察者模式与发布订阅模式的区别

  • 观察者模式 是由发布者调度, 比如当事件触发, Dep就会去调用观察者的方法, 所以观察者的发布者和观察者之间是存在依赖的.
  • 发布订阅模式 由统一调度中心调用, 因此发布者和订阅者不需要知道对方的存在.

    3. 观察者模式的实现如下

    ```javascript // 发布者 class Dep { constructor() { // 记录所有的观察者 this.subs = [] } // 添加观察者 addSub(sub) { if(sub && sub.update) {

    1. this.subs.push(sub)

    } }

    // 发布通知 notify() { this.subs.forEach(sub => {

    1. sub.update()

    }) } } // 观察者 class Watcher { update() { console.log(“update”) } } // 观察者 class Watcher2 { update() { console.log(“update 2”) } }

// test const dep = new Dep() const watcher = new Watcher() const watcher2 = new Watcher2() dep.addSub(watcher) // 添加观察者 dep.addSub(watcher2) // 添加观察者 dep.notify() // 发布者发布通知 // update update2 ```