# Vue2源码解析系列-响应式原理

## 综述

Vue的响应式利用变化侦测实现。

所谓的变化侦测，就是在数据被访问的时候收集依赖，在改变的时候通知依赖更新。

总的来说就是Object是getter里收集依赖，setter触发依赖更新；Array是getter收集依赖，拦截器触发依赖更新。

其中数组和对象的实现思路略有区别，后面再详细分析。

上述是思路，具体实现我们还要解决以下问题

* 怎么监听数据被访问和被修改
* 什么是依赖？
* 如何收集依赖？
* 如何通知依赖更新

## Object的变化侦测

### **怎么监听数据被访问和修改**

前面说过所谓的变化侦测，就是在数据被访问的时候收集依赖，在改变的时候通知依赖更新，那么如何知道数据被访问或被修改了呢？

我们知道，对于`Object.defineProperty`可以声明对象属性，并且可以配置属性修饰符。而属性修饰符中的setter和getter访问器函数分别会在属性被修改和被访问时触发，因为我们可以利用`Object.defineProperty`来实现数据的监听。

```jsx
let obj = {}
Object.defineProperty(obj,'key',{
    value: "123",
    set(){
        console.log('数据被修改')
    },
    get(){
        console.log('数据被访问')
    }
})
obj.key
// 数据被访问
// 123
obj.key = 321
// 数据被修改
```

### **什么是依赖**

什么是依赖？我们可以这样理解：某个数据的值需要访问另一个数据，我们就可以说这个数据依赖了另一个数据，例如计算属性 `a(){return b + '123'}`,我们可以说a依赖b。

解决了什么是依赖，那么依赖应该存放在哪呢？我们很容易想到可以用一个数组存着，但是这样很耦合，我们抽象成一个类Dep，为这个类定义一些方法。

```jsx
// vue-src\core\observer\dep.js
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []  // 存放依赖
  }

  /*添加一个观察者对象*/
  addSub (sub: Watcher) {
  }

  /*移除一个观察者对象*/
  removeSub (sub: Watcher) {
  }

  /*依赖收集，当存在window.target的时候添加观察者对象*/
  depend () {
  }

  /*通知所有订阅者*/
  notify () {
  }
}
```

还有一个问题，前面我们说过，一个数据依赖另一个数据，那么就将这个依赖存起来，如果将来数据改变就通知依赖更新，那么所有依赖都要被收集吗？我们知道在methods中，如果一个方法里的响应式数据发生改变，这个方法却不会发送任何改变，而computed却会更新，这也就说明，Vue只会选择性的收集依赖，我们将这种会被当做依赖收集的数据抽象为类Watcher，收集只收集Watcher的实例。

现在可以回答前面的问题： 什么是依赖？依赖是Watcher。依赖收集后放哪里？放到Dep里。

### **如何收集依赖**

前面说到依赖收集到Dep，那么具体如何实现呢？

我们可以利用一个Dep和数据都能访问到的唯一变量。当创造Watcher实例时，将这个唯一变量指向实例自身，然后获取一下数据，数据的getter会将这个唯一变量收集到Dep中。

注意：上述的唯一变量在本例中是`window.target`，而实际上是静态变量`Dep.target`，两者效果相同，后者的优点是不会污染全局变量。

```jsx
// watcher将window.target指向自己，然后get一下数据
export default class Watcher{
      constructor (){
        if (typeof expOrFn === 'function') {
          this.getter = expOrFn
        } else {
          this.getter = parsePath(expOrFn)
        }
        this.get()
      }
     get(){
         const vm = this.vm
        // 将实例添加到全局唯一变量中
        window.target = this;
        // 调用数据的getter方法，一是获取数据的值，而是调用getter将watcher实例添加Dep中
        let value = this.getter.call(vm, vm)
        // 清空
        window.target = undefined;
    }
}
// 数据的getter调用后会将window.target添加到Dep之中
Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter() {
            /*如果原本对象拥有getter方法则执行*/
            const value = (property && property.get) ? getter.call(obj) : val;

            if (window.target) {
                /*进行依赖收集*/
                let dep = new Dep()
                dep.addSub(window.target)
            }
            return value;
        },
}
```

实际代码加上了很多处理和判断，更复杂，这里进行了简化，理解核心思路即可。

### **如何通知依赖更新**

在setter中通知Dep,Dep遍历存储依赖的数组，依次调用更新方法。

```jsx
Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    set: function reactiveSetter(newVal) {
        if (setter) {
            /*如果原本对象拥有setter方法则执行setter*/
            setter.call(obj, newVal);
        } else {
            val = newVal;
        }
        /*新的值需要重新进行observe，保证数据响应式*/
        childOb = observe(newVal);
        /*dep对象通知所有的观察者*/
        dep.notify();
    },
});
export default class Dep {
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;

  constructor () {
    this.id = uid++  // 设置Dep编号
    this.subs = []
  }
  /*通知所有订阅者*/
  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update() // 调用watcher的更新方法
    }
  }
}
export default class Watcher {
  update () {
    // 调用对应的更新方法
  }
}
```

### **总结**

Object的变化侦测在getter收集依赖，存到Dep中，setter里通知Dep中的依赖进行更新操作，依赖就是watcher，Vue在需要依赖响应式数据的地方创造watcher实例，比如计算属性、模板语法或用户的watch。

## Array的变化侦测

我们知道Array其实本质上也是对象，因此也可以使用之前说的`Object.defineProperty`方法，但是实际上Vue并没有采用这种方法，请看下面代码。

```jsx
var obj = []
Object.defineProperty(obj,0,{
    set(){
        console.log('数据被修改')
    }
}
obj[0] = 1
// 数据被修改
// 1
obj.push(2)
// 2
```

可以看到，如果调用`Object.defineProperty`声明数组元素，固然使用中括号方法改变属性时可以执行setter，但是如果使用`push`这类数组方法，却无法触发setter。

事实上，这样写同样也会遇到性能问题，数组有些情况是需要存放大量数据的，如果每个元素都设置setter，对性能会有一定影响，因此Vue采用了另一种巧妙的方式实现数组的响应式。

### **拦截器**

什么是拦截器？举个例子。

```jsx
class A {
    toString() {
        return "我是A";
    }
}
class B extends A {}

let a = new A();
let b = new B();
console.log(a.toString(), b.toString());
// 我是A 我是A
```

类A里声明了`toString`方法，而类B继承于A，自然也有`toString`方法，并且A和B的`toString`方法相同。

这个时候如果我们在类B中再声明一个`toString`方法，那么实例b调用的就是类B自身的`toString`方法，而不是继承过来的类A的`toString`，我们可以理解类B的`toString`被拦截了。

```jsx
class A {
    toString() {
        return "我是A";
    }
}
class B extends A {
    // 新增
    toString() {
        return "我是B";
    }
}

let a = new A();
let b = new B();
console.log(a.toString(), b.toString());
// 我是A 我是B
```

同理，我们声明的数组是Array的实例，调用的`push`等方法其实是调用数组原型对象上的`push`方法，因此我们只需要在我们声明的数组里添加拦截器，那么调用`push`方法就是调用我们自定义的`push`方法，而不是`Array.prototype.push`。

![https://vue-js.com/learn-vue/assets/img/2.b446ab83.png](https://vue-js.com/learn-vue/assets/img/2.b446ab83.png)

那么什么方法才需要使用拦截器？很明显，能够改变数组自身数据的方法都需要拦截，整理后有以下几种：

* push
* pop
* shift
* unshift
* splice
* sort
* reverse

明白这些之后，我们就可以开始写拦截器了。

```jsx
// vue-src\core\observer\array.js
/*取得原生数组的原型*/
const arrayProto = Array.prototype
/*创建一个新的数组对象，修改该对象上的数组的七个方法，防止污染原生数组方法*/
export const arrayMethods = Object.create(arrayProto);

[
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
.forEach(function (method) {
  /*将数组的原生方法缓存起来，实际操作数组的时候还是需要调用原生方法*/
  const original = arrayProto[method]
  // def函数的对Object.defineProperty的封装
  def(arrayMethods, method, function mutator () {
    let i = arguments.length
    const args = new Array(i)
    while (i--) {
      args[i] = arguments[i]
    }
    /*调用原生的数组方法*/
    const result = original.apply(this, args)

    /*ob指向数据的Observer实例，后面再讲*/
    const ob = this.__ob__
    /* 以下方法会新增元素，将新增的元素存起来，然后执行observeArray，
        这是为了把新增元素也变成响应式的
    */
    let inserted
    switch (method) {
      case 'push':
        inserted = args
        break
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)

    // notify change
    /*dep通知所有注册的观察者进行响应式处理*/
    ob.dep.notify()
    return result
  })
})

export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  })
}
```

现在我们得到了一个数组方法拦截器。

### **挂载拦截器**

写好拦截器后下一步就是要将拦截器挂载到响应式数组上。

拦截器本质上是个包含七个特殊方法的对象，并且继承了Array的原型，因此拦截器的挂载可以直接将拦截器作为响应式数组的`prototype`，在浏览器环境下对象的原型被暴露为`__proto__`属性，因此我们可以：

```
// 判断浏览器是否支持__proto__属性
const hasProto = '__proto__' in {}
if(hasProto){
   	target.__proto__ = arrayMethods;
}else {
    // 如果不支持，则调用Object.defineProperty将方法覆盖到目标上
	for (let i = 0, l = arrayMethods.length; i < l; i++) {
		const key = arrayMethods[i];
		def(target, key, src[key]);
	}
}
```

### **收集和通知依赖**

拦截器解决了数组触发依赖更新的问题，那么我们如何收集依赖呢？

答案是getter。请看以下代码：

```jsx
let obj = {
	arr: [1, 2, 3],
};
Object.defineProperty(obj, "_arr", {
	enumerable: true,
	configurable: true,
	get() {
		console.log("数据被访问");
        // 收集依赖
        let dep = new Dep()
        dep.depend();

		return obj.arr;
	},
});

obj._arr[0]; // 数据被访问
obj._arr.pop(); // 数据被访问
```

以上代码，我们首先声明一个包含数组arr的对象(obj)，这是模拟Vue中data的写法，同时配置好修饰符，在getter中收集依赖后返回真实的数据，最后测试使用数组方法和中括号访问是否能触发，很明显这种方法效果很好。

## Vue中的watcher

前面说到vue会将响应式状态的依赖封装成Watcher，那么我们现在就来看看Vue中有哪些Watcher。

```jsx
// lifecycle
export function mountComponent(){
    // ...
    new Watcher(
        vm,
        updateComponent,
        noop,
        {
            before() {
                if (vm._isMounted && !vm._isDestroyed) {
                    callHook(vm, "beforeUpdate");
                }
            },
        },
        true /* isRenderWatcher */
    );
}
// state.js
Vue.prototype.$watch = function(){
	// ...
	const watcher = new Watcher(vm, expOrFn, cb, options);
}
function initComputed(){
    // ...
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    );
}
```

可以看到，vue使用响应式的地方主要有

* 组件
* computed
* $watch()
* 其他

### **组件**

事实上，组件本身其实也是响应式的，例如你使用了一个文本插值（即`{{}}`），然后更新响应式状态，此时UI跟着改变。

```jsx
<template>
	<div>
        {{a}}
    </div>
</template>
<script>
	export default{
        data(){
            return {
                a: 1
            }
        },
        mounted() {
            setInterval(() => {
                this.a++
            }, 1000)
        }
    }
</script>
```

我们来分析一下这个过程。

* 首先vue解析文本插值语法，真正的值取出并渲染
* 当文本插值的响应式状态改变时，通知依赖(组件)更新(即重新渲染)。

而要达到当响应式状态改变通知组件更新这一目的，就需要将组件转成watcher，作为状态的依赖。

* 首先将组件封装成Watcher，将渲染函数作为更新时的回调传入
* 组件第一次渲染时会获取响应式状态的值，这个时候就会将组件作为依赖加入到状态的dep中。
* 当更改响应式状态时，会触发dep.notify()，通知状态的所有依赖更新，而组件的更新回调就是组件的渲染函数，因此就能达到重新渲染UI的目的。

在代码中的流程是这样的(省略了不重要的代码)

```jsx
// 首先将组件 new Watcher，并且标记这是一个RenderWatcher
// updateComponent是组件渲染函数
// 在watcher更新执行，会执行所有beforeUpdate函数(生命周期函数)
new Watcher(
    vm,
    updateComponent,
    noop,
    {
        before() {
            if (vm._isMounted && !vm._isDestroyed) {
                callHook(vm, "beforeUpdate");
            }
        },
    },
    true /* isRenderWatcher */
);

```

接着会将组件会执行渲染后函数`updateComponent`

```jsx
export default class Watcher {
    constructor (
    vm: Component,
    expOrFn: string | Function,
    cb: Function,
    options?: ?Object,
    isRenderWatcher?: boolean)
    {
        this.vm = vm
        if (isRenderWatcher) {
           vm._watcher = this
        }
        vm._watchers.push(this)
        if (typeof expOrFn === 'function') {
          // getter指向了组件的渲染函数
          this.getter = expOrFn
        }
        // 执行get方法
        this.value = this.lazy
          ? undefined
          : this.get()
    }
    get(){
        pushTarget(this)
		let value
    	const vm = this.vm
        // 这里首次执行了渲染函数，vue将模板渲染成UI
      	value = this.getter.call(vm, vm)
        return value
    }

}
// pushTarget方法在dep.js里
export function pushTarget(target: ?Watcher) {
  targetStack.push(target);
  Dep.target = target;
}
```

先将Dep.target执行组件的watcher，然后执行`updateComponent`来触发状态的getter，状态的getter函数会将`Dep.target`加入到状态的deps中，这样就成功的将组件作为依赖传入到所有用到的状态的deps中。如果有某一个状态改变，就会触发setter，然后触发deps.notify来通知所有依赖执行回调，对于组件的wather来说，回调就是渲染函数，因此就会重新执行渲染。

### **computed**

computed可以看做是一个特殊的响应式状态，它的值取决于对其他的响应式状态的计算，computed可以是一个函数，也可以是一个包含set或get的对象，如果是函数，那么等同于包含get函数的对象。computed真正强大之处在于它的值会被缓存，只有当其他响应式状态改变时才会重新计算，这对性能很有好处。

vue首先会在`initComputed`中为每个computed创建watcher

```jsx
function initComputed(vm: Component, computed: Object) {
    const watchers = (vm._computedWatchers = Object.create(null));
    for (const key in computed) {
        const userDef = computed[key];
        const getter = typeof userDef === "function" ? userDef : userDef.get;
        if (!isSSR) {
          // 为每个computed创建watcher
          watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            {lazy: true}   // 开启缓存
          );
        }
        defineComputed(vm, key, userDef);
    }
}
```

然后会使用一个中间层函数`createComputedGetter`，这个函数会判断是否需要重新计算，如果需要才会调用computed真实的函数。`createGetterInvoker`则是在无法使用缓存的情况下使用。

```jsx
const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop,
};
export function defineComputed(
  target: any,
  key: string,
  userDef: Object | Function
) {
  // SSR
  const shouldCache = !isServerRendering();
  if (typeof userDef === "function") {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef);
    sharedPropertyDefinition.set = noop;
  } else {
    // 根据shouldCache && userDef.cache !== false条件
    // 来判断是否要使用缓存
    sharedPropertyDefinition.get = userDef.get
      ? (shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get))
      : noop;
    sharedPropertyDefinition.set = userDef.set || noop;
  }

  Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter(key) {
  return function computedGetter() {
    const watcher = this._computedWatchers && this._computedWatchers[key];
    if (watcher) {
      // watcher.dirty是一个用于判断数据是否发生了变化的标志
      if (watcher.dirty) {
        // 执行watcher.get()方法，并设置dirty为false
        watcher.evaluate();
      }
      // 收集依赖
      if (Dep.target) {
        watcher.depend();
      }
      return watcher.value;
    }
  };
}
function createGetterInvoker(fn) {
  return function computedGetter() {
    return fn.call(this, this);
  };
}
```

vue对computed的处理和对state的处理很类似，具体流程是：首先vue将每个computed创建watcher(传递lazy来表明要使用缓存)，以便作为依赖被使用到的响应式状态添加到deps中，接着通过Object.defineProperty创建属性，但是get函数加了一个中间层来判断是否要重新计算(即createComputedGetter函数)。

computed每次获取时都会触发getter，也就是 `createComputedGetter`函数，这里会收集依赖，并且会通过`watcher.dirty`标记来判断是否要重新计算。如果computed的依赖更新，那就会通知依赖(也就是computed的watcher)进行更新，此时就会将`watcher.dirty`标记设为true，那么就会重新计算。

### **$watch()**

`$watch()`方法实际上就是将Watcher主动暴露给用户使用，用户指定监听的响应式状态和回调，然后该状态会将watcher作为依赖收集到deps，更新时就会触发执行回调函数。

## 响应式API

Vue2.0中有三个常用响应式API，分别是`vm.#watch`、`vm.$set`、`vm.$delete`

### **Vue.$watch**

`vm.$watch` 能监听一个表达式或computed函数，触发时执行特定函数。本质上也是利用Watcher。在使用vm.$watch时会为目标数据创建一个watcher，watcher的构造函数里会读取一下数据，这样就将这个watcher添加到dep中了，如果数据更新了，就会触发对应的回调函数。

```jsx
// vue-src\core\instance\state.js
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: Function,
    options?: Object
  ): Function {
    const vm: Component = this
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    /*有immediate参数的时候会立即执行*/
    if (options.immediate) {
      cb.call(vm, watcher.value)
    }
    /*返回一个取消观察函数，用来停止触发回调*/
    return function unwatchFn () {
      /*将自身从所有依赖收集订阅列表删除*/
      watcher.teardown()
    }
  }
```

`vm.$watch`第一个参数能接收表达式或computed函数

如果是函数，直接作为getter，如果是keypath，则需要处理

```jsx
if (typeof expOrFn === 'function') {
    this.getter = expOrFn
} else {
    this.getter = parsePath(expOrFn)
}

// 下面是处理keypath的函数，其实就是遍历一层一层的查找
const bailRE = /[^\w.$]/
export function parsePath (path: string): any {
  if (bailRE.test(path)) {
    return
  }
  const segments = path.split('.')
  return function (obj) {
    for (let i = 0; i < segments.length; i++) {
      if (!obj) return
      obj = obj[segments[i]]
    }
    return obj
  }
}
```

`vm.$watch`会返回一个函数，执行函数会取消监听。我们看到前面代码里返回了一个函数，函数里就执行了`watcher.teardown()`语句，因此取消监听重点是在`watcher.teardown()`上，它的作用是将watcher自身从所有依赖收集订阅列表删除。

要实现将watcher自身从所有依赖收集订阅列表删除，首先需要在Watcher中记录自己都订阅了谁，也就是watcher实例被收集进了哪些Dep里。然后当Watcher不想继续订阅这些Dep时，循环自己记录的订阅列表来通知它们（Dep）将自己从它们（Dep）的依赖列表中移除掉。

这个简单，我们首先为每个dep定义一个编号，每次将watcher添加到dep时就将这个dep的编号存到一个数组里。

```jsx
class Watcher{
  /*添加一个依赖关系到Deps集合中*/
  addDep (dep: Dep) {
    const id = dep.id
    // 如果当前Watcher已经订阅了这个Dep则跳过
    if (!this.depIds.has(id)) {
        // 记录当前Watcher已经订阅了这个Dep
        this.depIds.add(id)
        // 记录自己都订阅了哪些Dep
        this.deps.push(dep)
        // 将自己订阅到Dep
        dep.addSub(this)
    }
  }
}
```

然后再看`watcher.teardown()`

```jsx
class Watcher{
/*将自身从所有依赖收集订阅列表删除*/
  teardown () {
    // this.active表示当前watcehr实例是否正在被使用
    if (this.active) {
      /*从vm实例的观察者列表中将自身移除,由于该操作比较耗费资源，所以如果vm实例正在被销毁则跳过该步骤。*/
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)
      }
      this.active = false
    }
  }
}

class Dep{
  /*移除一个观察者对象*/
  removeSub (sub: Watcher) {
    if (this.subs) {
        const index = this.subs.indexOf(sub)
        if (index > -1) {
          return this.subs.splice(index, 1)
        }
  }
}
```

`watcher.teardown()`会遍历`this.deps`依次执行`removeSub`方法，将自身从dep上删除。

### **Vue.$set**

```jsx
Vue.prototype.$set = set
export function set(target: Array<any> | Object, key: any, val: any): any {
	/*如果传入数组则在指定位置插入val*/
	if (Array.isArray(target) && typeof key === "number") {
		target.length = Math.max(target.length, key);
		target.splice(key, 1, val);
		/*因为数组不需要进行响应式处理，数组会修改七个Array原型上的方法来进行响应式处理*/
		return val;
	}
	/*如果是一个对象，并且已经存在了这个key则直接返回*/
	if (hasOwn(target, key)) {
		target[key] = val;
		return val;
	}
	/*获得target的Oberver实例*/
	const ob = (target: any).__ob__;
	/*
    _isVue 一个防止vm实例自身被观察的标志位 ，_isVue为true则代表vm实例，也就是this
    vmCount判断是否为根节点，存在则代表是data的根节点，Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)
  */
	if (target._isVue || (ob && ob.vmCount)) {
		/*
      Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)。
      https://cn.vuejs.org/v2/guide/reactivity.html#变化检测问题
    */
		return val;
	}
	if (!ob) {
		target[key] = val;
		return val;
	}
	/*为对象defineProperty上在变化时通知的属性*/
	defineReactive(ob.value, key, val);
	ob.dep.notify();
	return val;
}
```

### **Vue.$delete**

```jsx
Vue.prototype.$delete = del
export function del(target: Array<any> | Object, key: any) {
	if (Array.isArray(target) && typeof key === "number") {
		target.splice(key, 1);
		return;
	}
	const ob = (target: any).__ob__;
    // Vue 不允许在已经创建的实例上动态添加新的根级响应式属性(root-level reactive property)。
	if (target._isVue || (ob && ob.vmCount)) {
		return;
	}
    // 如果没有这个属性则跳过
	if (!hasOwn(target, key)) {
		return;
	}
	delete target[key];
    // 如果数据不是响应式的则结束，否则还要通知依赖
	if (!ob) {
		return;
	}
	ob.dep.notify();
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://1425816423.gitbook.io/my-knowledge-base/yuan-ma-jie-xi/vue-yuan-ma-jie-xi/vue2-yuan-ma-jie-xi-xi-lie-xiang-ying-shi-yuan-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
