# Vue数据双向绑定与响应式

# Object.definProperty

# 描述符可拥有的键值

configurable enumerable value writable get set
数据描述符 可以 可以 可以 可以 不可以 不可以
存取描述符 可以 可以 不可以 不可以 可以 可以

如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常。

即一个描述符不可同时拥有value 或 writable 和 get 或 set 键(不可同时拥有数据描述符和存取描述符)

即一个描述符要么有value和writable属性,要么有get和set属性。

const data = {}
Object.defineProperty(data, 'name', {
  configurable: false,
  enumerable: false,
  value: 'ls', // 值,默认为undefined
  writable: true // 是否可写,默认false
})

data.name = 'zs'
console.log(data.name)
1
2
3
4
5
6
7
8
9
10
const data = {}
let temp = 'zs'
Object.defineProperty(data, 'name', {
  configurable: false,
  enumerable: false,
  get() {
    console.log('get value')
    return temp
  },
  set(newVal) {
    console.log('set newVal')
    temp = newVal
  }
})

data.name = 'ls'
console.log(data.name)
// set newVal
// get value
// ls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 双向绑定MVVM

  • 数据层(Model):应用的数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

# 理解ViewModel

它的主要职责就是:

  • 数据变化后更新数据
  • 视图变化后更新数据

第一点通过事件监听很容易实现

第二点需要解决几个问题:

  1. 当数据改变时,视图如何知道数据发生了改变
  2. 数据改变时,我们如何知道应该更新哪个视图的数据 这里我们通过订阅发布模式来解决这个问题

此外,它还有两个主要部分组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

# 实现观察者Observer

因为definProperty只能监听某个对象的某个属性,所以我们借助递归来实现监听整个对象

/**
 * dep数组用于通知
 * subs: subs数组,存储订阅者
 * notify(): 通知订阅者数据发生改变
 * addSub(): 添加订阅者到数组
 */
class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  // 通知每个订阅者更新
  notify() {
    this.subs.forEach((item) => {
      item.update()
    })
  }
}
/**
 * 订阅者:监听数据变化,将每个订阅者存入Dep数组中
 */
class Observer {
  constructor(data) {
    this.dep = new Dep()
    this.observerAll(data)
  }
  // 监听一个值
  observer(data, key, value) {
    this.observerAll(value)
    Object.defineProperty(data, key, {
      configurable: true,
      enumerable: true,
      get() {
        // 将订阅者存入数组
        if (Dep.target) {
          this.dep.addSub(Dep.target)
        }
        console.log('get ' + value)
        return value
      },
      set(newValue) {
        console.log('set ' + newValue)
        value = newValue
        // 数据变化,通知订阅者
        this.dep.notify()
      },
    })
  }
  // 递归监听对象
  observerAll(data) {
    if (Object.prototype.toString.call(data) !== '[object Object]') {
      return
    }
    Object.keys(data).forEach((key) => {
      this.observer(data, key, data[key])
    })
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

# 订阅者Watcher

const { Dep } = require('./Observer')
/**
 * vm: 监听的对象
 * exp: 监听的属性名
 * cb: 数据改变时执行的回调函数
 */
class Watcher {
  constructor(vm, exp, cb) {
    this.vm = vm
    this.exp = exp
    this.cb = cb
    this.value = this.get()
  }
  update() {
    this.run()
  }
  run() {
    let oldValue = this.value
    if (oldValue !== this.vm[this.exp]) {
      this.value = this.vm[this.exp]
      this.cb(this.value)
    }
  }
  // 返回vm[exp]的值
  get() {
    Dep.target = this
    let value = this.vm[this.exp]
    Dep.target = null
    return value
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

参考:

https://juejin.cn/post/6844904084114259981#heading-2

https://segmentfault.com/a/1190000016208088