Vue响应式Q&A

Vue

# 简答题

# 一、当我们点击按钮的时候动态给 data 增加的成员是否是响应式数据,如果不是的话,如何把新增成员设置成响应式数据,它的内部原理是什么。

let vm = new Vue({
 el: '#el'
 data: {
  o: 'object',
  dog: {}
 },
 method: {
  clickHandler () {
   // 该 name 属性是否是响应式的
   this.dog.name = 'Trump'
  }
 }
})
1
2
3
4
5
6
7
8
9
10
11
12
13

答:增加的成员不是响应式的,要把新增的成员设置成响应式成员。

第一种:可以在data对象中给name进行初始化,初始值设置为空。原理是在页面第一个渲染的时候,Observer类会初始化进行数据劫持,将data添加getter和setter,转化为响应式对象,name如果在开始的时候进行注册,这样在之后修改name的时候,也是响应式的值。

第二种:官网也建议使用Vue.set(object, propertyName, value)方法对新增的属性进行响应式绑定,本质也是使用Object.defineProperty方法,给属性添加getter和setter进行响应式对象的转化。

# 二、给属性重新赋值成对象,是否是响应式的?给 Vue 实例新增一个成员是否是响应式的?

给属性重新赋值成对象是响应式的。赋值过程调用Observer的set方法,里面会调用walk方法遍历添加响应式数据。

给 Vue 实例新增一个成员不是响应式的,所有的成员是在首页渲染的时候创建的响应式数据,而vm.test = 123 是在后来的设置中创建的,并没有再调用响应式数据的函数。

vue官网 (opens new window) 也是这么说的,不允许动态添加根级别的响应式属性,官网的解决方法是Vue.set(),应该是调用了defineReactive方法将属性转换成了getter/setter。

# 三、Vue响应式那个函数去更新视图?

首次渲染是Compiler更新视图,更新变化的时候是Watcher更新视图

具体过程:

  • Vue
    • 记录传入的选项,设置 $data/$el
    • 把 data 的成员注入到 Vue 实例
    • 负责调用 Observer 实现数据响应式处理(数据劫持)
    • 负责调用 Compiler 编译指令/插值表达式等
  • Observer
    • 数据劫持
    • 负责把 data 中的成员转换成 getter/setter
    • 负责把多层属性转换成 getter/setter
    • 如果给属性赋值为新对象,把新对象的成员设置为 getter/setter
    • 添加 Dep 和 Watcher 的依赖关系
    • 数据变化发送通知
  • Compiler
    • 负责编译模板,解析指令/插值表达式
    • 负责页面的首次渲染过程
    • 当数据变化后重新渲染
  • Dep
    • 收集依赖,添加订阅者(watcher)
    • 通知所有订阅者
  • Watcher
    • 自身实例化的时候往dep对象中添加自己
    • 当数据变化dep通知所有的 Watcher 实例更新视图

# 四. 阐述一下 eventbus 的原理,讲述 eventbus 在 Vue 中的实践 (猿辅导)

eventbus 就是事件发布/订阅模型具体实现。

# 知识点

• 发布/订阅模式

# 参考答案

EventBus 是数据通信的一种方式,基于一个通信中心,订阅和发布消息的模式,也叫观察者模式。 观察者模式的核心 API 如下:

  1. on('事件名称', 处理函数)
  2. emit('事件名称', 可选参数)

下面是它的一个简单实现:

class EventEmitter {
  constructor () {
    this.handlers = {
      // foo: [事件函数, 事件函数],
      // abc: [事件函数]
    }
  }

  // 订阅事件
  on (eventName, callback) {
    // foo
    const callbacks = this.handlers[eventName]
    if (callbacks) {
      callbacks.push(callback)
    } else {
      this.handlers[eventName] = []
      this.handlers[eventName].push(callback)
    }
  }

  // 发布事件
  // foo, [1, 2, 3, 4] 剩余操作符,把所有剩余的参数收集到一个数组中
  emit (eventName, ...args) {
    const callbacks = this.handlers[eventName]
    if (callbacks) {
      callbacks.forEach(cb => {
        cb(...args) // 数组的展开操作符,一个一个的拿出来传递给函数
      })
    }
  }
}
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

# 五、请简述 Vue 响应式原理

答:Vue响应式指的是Vue数据响应式,Vue数据响应式指的是数据驱动视图,在数据发生变化的时候自动更新视图,不需要手动操作DOM。

源码中在数据初始化的时候initData中,是通过observe函数给数据data创建响应式对象的,这个函数的功能是通过创建一个Observer构造函数,将数据data的所有属性转化成getter和setter。

具体的做法:

  1. 在Observer构造函数里面,添加了dep属性,还遍历所有的属性用defineReactive方法将其转换成setter和getter

  2. 在defineReactive内部,调用了Object.defineProperty,在get方法中获取值并且去收集依赖,如果有子对象就给子对象收集依赖 ; 在set方法中,判断如果设置的新值与旧值不相等,将新值赋值给旧值,然后调用dep.notify方法派发通知。

  3. 具体如何收集依赖?初始化的时候在$mount方法中调用了mountComponent方法,这个方法内部创建了Watcher对象,在Watcher对象中首先将自己存储到了Dep对象的target属性中,然后调用了updateComponent方法,这个方法将render函数渲染到页面上,渲染过程中在访问每个属性的时候,就会进入属性的get方法,在get方法中进行依赖收集,将Watcher对象添加到Dep的subs数组中,并且将dep对象添加到Watcher对象的newDeps数组中。

  4. 具体如何发布通知?当值进行修改的时候,会触发属性的set方法,这个时候会调用dep.notify()方法,Dep对象会遍历其subs数组,每个元素都是一个Watcher对象,并调用其update方法,然后其会调用updateComponent方法更新视图。

# 编程题

# 一、在模拟 Vue.js 响应式源码的基础上实现 v-html 指令,以及 v-on 指令。

见code/vue-data文件夹 v-on只实现了methods中定义函数,未处理参数部分。

更新时间: 2022-01-26 22:04