你們都比較清楚 Vue 中包含了一個非侵入性的響應式系統,能夠說這是 Vue 的最基礎最核心的一個特性了,基於這套系統,咱們才實現了修改數據視圖就會跟着響應進行更新,很直接、很天然,符合咱們的直覺。html
咱們以 Vue 最新的 v2.6.14 版原本分析,響應式相關最核心的代碼都在 github.com/vuejs/vue/t… 這個文件夾下。前端
這裏咱們所說的響應式究竟指的是什麼呢?拿 Vue 官方的文檔來看,cn.vuejs.org/v2/guide/in…vue
其實就是可以將咱們生命的普通(原始)對象數據轉變爲能夠被視圖響應的對象,使得咱們能夠追蹤他們的變化,進而針對於這些變化自動作出響應處理。react
如同上邊的示例所講的,當咱們對這些值進行更新的時候,視圖會進行從新渲染,即自動響應了數據的變化。git
Vue 官方文檔中,爲了讓你們更好的理解響應式,有專門的篇幅來說了響應式的原理,cn.vuejs.org/v2/guide/re…github
咱們截取一段最核心的簡述:express
那在底層上,Vue 是怎麼實現這樣的一套機制呢,咱們一塊兒來看下源碼,進行下分析。設計模式
在 Vue 初始化的過程當中,會對 data props 啊等數據進行 observe 處理,也就是咱們下文中要開始分析的 observe 函數。數組
因爲涉及到的部分比較多,咱們能夠將整個過程分開來看,也是對應到上邊圖片上所展現的核心部分:瀏覽器
在 github.com/vuejs/vue/b… 文件中,有一個暴露出來的 observe
函數:
/** * 爲一個對象 value 建立一個 Observer 實例 */
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 重點就是這裏,咱們建立了一個 Observer 實例
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
複製代碼
那接下來的重點就是這個 Observer
類了:
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
// 咱們的原始對象
this.value = value
// 先忽略
this.dep = new Dep()
this.vmCount = 0
// 給 value 定義一個 __ob__ 屬性,值指向當前的 Observer 實例
// 這個和上邊的 observe 函數中第9行的判斷強相關,若是一個原始對象已經建立(關聯)過了 Observer 實例,就不須要再次建立了
def(value, '__ob__', this)
// 數組場景 咱們能夠先忽略
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
// 重點看這個 針對於對象的場景
this.walk(value)
}
}
/** * 遍歷原始對象,將這個對象全部的屬性都利用 defineReactive 定義一次 */
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/** * 先忽略 */
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
複製代碼
看起來這個 defineReactive
作了很核心的事情:
/** * 作的核心,將一個對象的屬性定義爲響應式的 */
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
// 咱們的這個屬性對應的值 很重要的!
val = obj[key]
}
let childOb = !shallow && observe(val)
// 最核心的 藉助於 defineProperty 從新定義這個對象的屬性的取值和寫值行爲
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// 先忽略 getter
// 咱們的目標值 就是 val 的值,即 obj[key]
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
// 返回
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
// 更新 val 的值!
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
複製代碼
其餘的一些細節,咱們能夠先不關注,能夠看出呢,咱們將一個對象變爲一個響應式對象,其實就是遍歷這個對象的全部屬性,利用 defineProperty 從新定義這個對象的屬性的取值和寫值行爲。
在官方文檔中,也有說明,咱們再來看下:
一句話總結來看:根據響應式對象Data中的 getter 來收集依賴,在更新的時候(setter行爲)通知咱們的依賴。
真的是一個很巧妙的設計,利用 getter 和 setter 完美實現瞭如何收集依賴和什麼時候通知依賴的能力。
重點回到咱們剛剛大概分析了的 defineReactive 這裏:
export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) {
// 建立 Dep 實例
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 先忽略值自己又是一個對象的狀況
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// 若是 Dep.target 存在
if (Dep.target) {
// 依賴添加
dep.depend()
// 先忽略
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
// 依賴通知
dep.notify()
}
})
}
複製代碼
能夠看出來,這裏存在一個很重要的 Dep 須要咱們仔細研究,從命名上也比較清楚,依賴。在 getter 和 setter 中,也是藉助於 dep 來實現了收集(添加)依賴和依賴通知的能力。
接下來就來仔細分析下 Dep,源碼地址 github.com/vuejs/vue/b…
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/** * Dep 就是一個橋樑-鏈接器,用來鏈接響應式對象(觀察者目標對象 Subject)和其觀察者(訂閱者 Watcher) * 當目標對象變動的時候,藉助於 Dep 告訴 全部的訂閱者 更新 * * Dep 自己的含義,依賴,是相對於 Watcher 來說的,由於 Watcher 中取表達式的值的時候,就是一次收集依賴的過程 */
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
// 全部的訂閱者,都是 Watcher
this.subs = []
}
// 添加訂閱者 watcher
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除訂閱者 watcher
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
// 訂閱者添加當前依賴
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
// 通知全部的訂閱者更新
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
// 當前的目標訂閱者
Dep.target = null
// 目標訂閱者棧
// 由於訂閱者之間會存在嵌套關係,因此須要一個棧來維護他們的層級關係,後邊結合示例來理解,這裏能夠基本忽略
const targetStack = []
// 入棧 這裏能夠簡化理解爲直接設置 目標訂閱者 Dep.target
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
// 設置 目標訂閱者
Dep.target = target
}
// 出棧
export function popTarget () {
targetStack.pop()
// 目標訂閱者設置爲上一次的訂閱者
Dep.target = targetStack[targetStack.length - 1]
}
複製代碼
Dep 的做用也是比較明確的,他和咱們的響應式對象實際上是1對1的關係,而和觀察者也是1對多的關係,觀察者都存放在 subs 中。
經過上邊的分析,發現裏邊有一個不可或缺的部分,即訂閱者 Watcher,咱們接下來詳細看下。
import {
warn,
remove,
isObject,
parsePath,
_Set as Set,
handleError,
invokeWithErrorHandling,
noop
} from '../util/index'
import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'
import type { SimpleSet } from '../util/index'
let uid = 0
/** * Watcher 觀察者 就是解析表達式,收集其中的依賴 dep * 當表達式的值更新的時候,觸發其回調 cb */
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor ( vm: Component, expOrFn: string | Function, cb: Function, options?: ?Object, isRenderWatcher?: boolean ) {
// ViewModel 實例,在 Vue 中,咱們能夠直觀理解爲 組件實例 或者 Vue 實例
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
// 注意這裏的 deps 依賴列表
// 保存了全部的依賴,即 Dep 實例,那麼和 Dep 實例相對應的會存在一個映射的響應式對象
// deps 和 newDeps 都是,先把他倆同等對待
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
// 調用 get 獲取(表達式的)初始值
this.value = this.lazy
? undefined
: this.get()
}
/** * Evaluate the getter, and re-collect dependencies. */
get () {
// 注意這裏調用 pushTarget,約等於設置好了 Dep 中的目標觀察者對象 watcher
pushTarget(this)
let value
const vm = this.vm
try {
// 這裏一個核心點,至關於執行了咱們的表達式
// 裏邊就會讀取響應式對象的值,也就是會觸發其 getter
// 即 在 defineReactive 中,利用 defineProperty 定義好的屬性 getter
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
// 執行完畢,Dep 中的目標觀察者對象爲上一次的觀察者對象 watcher 簡單場景目前能夠認爲 Dep.target = null
popTarget()
this.cleanupDeps()
}
return value
}
/** * Add a dependency to this directive. */
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
// 給依賴列表加上依賴 dep
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 一樣的,依賴也須要添加當前 watcher 爲訂閱者,約等於這個 watcher 在觀測這個 dep 映射的響應式對象
dep.addSub(this)
}
}
}
/** * Clean up for dependency collection. */
cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
/** * Subscriber interface. * Will be called when a dependency changes. */
update () {
// 當依賴dep通知更新的時候,被調用了!
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
// 同步狀況下 直接 run
// 咱們先把關注點放在這裏便可
this.run()
} else {
queueWatcher(this)
}
}
/** * Scheduler job interface. * Will be called by the scheduler. */
run () {
if (this.active) {
// 由於依賴的響應式對象的值更新了,因此須要從新計算表達式 獲取表達式新的值
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// 值發生了變化
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
// 觸發回調 cb,傳入新值和舊值
this.cb.call(this.vm, value, oldValue)
}
}
}
}
/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */
evaluate () {
this.value = this.get()
this.dirty = false
}
/** * Depend on all deps collected by this watcher. */
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
/** * Remove self from all dependencies' subscriber list. */
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
複製代碼
名字上也比較容易理解,觀察者 Watcher。若是單純的從Watcher來看,他和Dep之間實際上是一個1對多的關係,可是咱們在分析 Dep 的時候也得出了Dep 和 Watcher 是1對多的關係,因此這裏能夠進一步得出結論,在複雜一點的場景,他們之間實際上是一個多對多的關係。
這種多對多的關係,能夠這樣舉例理解:
那麼,正確的理解順序就是從 Watcher 實例化開始,咱們以 w1 初始化舉例:
這樣對於 w1 而言,w1的依賴項包含了 dep1 和 dep2,而 dep1 和 dep2 的 subs 中都包含 w1。
此時,把 w2 考慮進來,w2的依賴項包含了 dep2,而 dep2 的 subs 中也包含了 w2。
若是後續o2的值發生了更新,那麼就會藉助於 dep2 通知其全部的 subs:w1和w2 進行更新 update。可是若是是o1發生了更新,那麼就會藉助於 dep1 通知其全部的subs: w1 進行更新 update。
相信經過這個例子,你已經基本理解了整個追蹤響應式對象的變化的過程。可能看上述完整的代碼仍是有一些吃力,這裏咱們也對實現進行一個簡化,方便你理解:
class Dep {
static target = null
constructor () {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
class Watcher {
constructor (getter, cb) {
this.cb = cb
this.deps = []
this.getter = getter
this.value = this.get()
}
get () {
Dep.target = this
let value
try {
value = this.getter()
} catch (e) {
// catch
} finally {
Dep.target = null
}
return value
}
addDep (dep) {
this.deps.push(dep)
dep.addSub(this)
}
update () {
this.run()
}
run () {
const value = this.get()
if (value !== this.value) {
const oldValue = this.value
this.value = value
this.cb(value, oldValue)
}
}
}
function observe (value) {
const ob = new Observer(value)
return ob
}
class Observer {
constructor (value) {
this.value = value
this.walk(value)
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
function defineReactive (obj, key, val) {
const dep = new Dep()
if (arguments.length === 2) {
val = obj[key]
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if (Dep.target) {
dep.depend()
}
return val
},
set: function reactiveSetter (newVal) {
val = newVal
dep.notify()
}
})
}
複製代碼
若是咱們使用的話,會按照以下方式:
const o1 = observe({value: 'o1'})
const o2 = observe({value: 'o2'})
const w1 = new Watcher(() => {
return o1.value.value + o2.value.value
}, (val, oldVal) => {
console.log('w1 watch value changed:', val, oldVal)
})
const w2 = new Watcher(() => {
return o2.value.value
}, (val, oldVal) => {
console.log('w2 watch value changed:', val, oldVal)
})
o1.value.value = 'o1o1'
// log:
// w1 watch value changed: o1o1o2 o1o2
o2.value.value = 'o2o2'
// log:
// w1 watch value changed: o1o1o2o2 o1o1o2
// w2 watch value changed: o2o2 o2
複製代碼
實現會有一些不一致的地方,這裏簡化處理,也能夠看出咱們上文中的有些描述是不精確的,例如 o1 對應的 Dep 實例 dep1,其實咱們這裏真正的對應關係是 o1 這個對象的屬性 value 所對應的 Dep 實例纔是咱們想要的 dep1。
更多的是想要方便你們理解,正確認識他們。
那 Vue 爲什麼作了一套響應式系統呢?不作可不能夠。要想回答好這個問題,估計只有 Vue 做者尤大本身了。可是仍是能夠從一些方面作一些簡單的猜想。
大概是在10年左右,MVX框架在漸漸出現,基本是將其餘領域的一些優秀的思想(主要仍是來源自GUI,PC類的應用開發)和設計融入到前端領域,因此陸陸續續出來了不少的框架,比較出名的可能會有一些:Backbone、Knockout.js、angular.js、ember 等等,也出現了著名的幫你作選擇的經典的 TodoMVC 項目。
在MVVM(非嚴格意義)框架中,一個核心思想就是雙向綁定,意味着視圖和View之間能夠自動作到同步。這個過程當中,受限於當時的兼容性要求,在JS中還不能很好的支持 Object.defineProperty,因此當年如 angular.js 這樣超級火爆的新星,所用的就是髒檢查的邏輯,來肯定用戶到底更新了哪些數據,依次來去作到數據變動了自動更新視圖。而另外一個框架 Knockout.js 則是須要用戶顯式的調用 get 和 set 這樣的API來達到通知的目的,進而去更新視圖。
尤大當時在Google,也是很是喜歡 Angular.js,可是不夠輕量,並且性能也很差,開發者的上手成本也比較高,因此他就吸取了Angular.js中比較精華的部分,建立出了Vue,直接放棄掉了對於陳舊瀏覽器的支持,直接使用原生的Object.defineProperty實現了對數據的偵測,即核心的非侵入式的響應式系統,也不須要像 Knockout.js 那樣顯式調用一些讀寫操做。
有了這套響應式系統,在Vue中,直接操做數據去影響視圖,並且很是直觀,而且在這個基礎上,Vue還提供了watch和computed這樣很是好用的響應式相關的特性。
咱們上邊已經分析了最核心的整個響應式系統,固然,其實仍是有不少的設計細節,咱們這裏並無額外的去講,並非說這些邏輯和細節不重要,而是咱們把最核心的部分拿出來幫助你們理解Vue的reactivity響應式系統,因此基本只是考慮了最簡單的case。
那從中咱們能夠學到些什麼呢?
響應式相關最核心的代碼都在 github.com/vuejs/vue/t… 這個文件夾下,咱們能夠看到下面對應的文件結構:
index.js 是入口文件,也就是咱們上邊最先分析 observe 函數定義的地方;剩下的根據各個模塊的職責不一樣,作了很好的拆分,每一個模塊一個文件。
固然,在整個Vue中,目錄的劃分和文件模塊拆分仍是很合理和清楚的,很值得咱們完備的去學習,整個能夠單獨一篇整理。
能夠看到咱們上邊一直在說觀察者、訂閱者這些名詞,那這個和咱們傳統認知的設計模式之一——觀察者模式——是可以對應起來的嗎?答案多是Yes,也能夠是No。
觀察者模式,參考維基百科 zh.wikipedia.org/zh-hans/%E8…
Yes的緣由是,他基本符合了觀察者模式中的要素的定義:觀察者目標對象 Subject — 響應式對象,也有觀察者 Watcher。當目標對象發生變動的時候,去通知觀察者更新。這樣的一套邏輯在這裏是很完備的。
No的緣由呢,按照嚴格定義,觀察者模式是一種一對多的關係,即一個目標對象對應多個觀察者,從這個點出發,通過咱們前邊的分析,咱們知道 Dep 和 Watcher 之間是多對多的關係,且嚴格意義上講,觀察者的目標對象應該是咱們的響應式對象纔對,而不是 Dep,可是 Dep 和響應式對象是1對1的關係。
因此,這裏我的更傾向因而觀察者模式思想的靈活運用,固然,這個也是體現出了做者自身充分理解了觀察者模式,而且作到了活學活用的程度,很完美的解決了依賴收集的問題。
Respect!整篇來看 Vue 中的話,其實還有不少模式的運用,都是很好的範例,值得咱們去學習,更值得咱們去思考&深入理解,讓咱們自身也能夠作到活學活用。
世界上本沒有模式,」走「的人多了,就有了模式 O(∩_∩)O
在 dep.js 中,最底部 pushTarget 和 popTarget,咱們其實看到了對於棧的運用(這裏一樣用數組模擬),在一些樹狀嵌套或者遞歸場景中,棧這種數據結構可以幫助咱們很好的解決問題。
雖然很基礎,可是卻很好用,你能夠想一想,本身曾經在什麼地方有用到棧嗎?是在什麼場景下、要解決什麼問題?
相對應的其實就是 Dep.target 這裏的運用,十分的巧妙,進行了很好的解耦。核心的緣由就是由於 JS 是單線程的,在執行的時候,必定不會出現兩個 watcher 的邏輯同時在執行。
咱們平常之中,也是能夠很靈活運用這個優點的。甚至於說,你能夠發現,在 Vue 3 中,這種技巧依舊是在使用,且使用的更多了。
不要說 Worker 線程😯
在 observe 函數中,針對於目標原始對象,爲了不重複對其作邏輯的處理,咱們保存了一個私有屬性 ob,依次來判斷是不是已經進行了響應式相關的邏輯處理。本質上來說,這就是一種緩存策略。
有不少時候,在咱們實際的業務場景中,也會存在這種狀況,但可能咱們都忽略了,可能進行了重複處理。尤爲是遇到一些耗時的計算或者頻繁的處理,咱們是能夠考慮加上緩存策略,避免重複進行邏輯計算。
滴滴前端技術團隊的團隊號已經上線,咱們也同步了必定的招聘信息,咱們也會持續增長更多職位,有興趣的同窗能夠一塊兒聊聊。