日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > vue >内容正文

vue

vue源码之响应式数据

發(fā)布時間:2023/12/20 vue 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 vue源码之响应式数据 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

分析vue是如何實現(xiàn)數(shù)據(jù)響應(yīng)的.

前記

現(xiàn)在回顧一下看數(shù)據(jù)響應(yīng)的原因. 之前看了vuex和vue-i18n的源碼, 他們都有自己內(nèi)部的vm, 也就是vue實例. 使用的都是vue的響應(yīng)式數(shù)據(jù)特性及$watchapi. 所以決定看一下vue的源碼, 了解vue是如何實現(xiàn)響應(yīng)式數(shù)據(jù).

本文敘事方式為樹藤摸瓜, 順著看源碼的邏輯走一遍, 查看的vue的版本為2.5.2.

目的

明確調(diào)查方向才能直至目標(biāo), 先說一下目標(biāo)行為:

  • vue中的數(shù)據(jù)改變, 視圖層面就能獲得到通知并進行渲染.
  • $watchapi監(jiān)聽表達(dá)式的值, 在表達(dá)式中任何一個元素變化以后獲得通知并執(zhí)行回調(diào).
  • 那么準(zhǔn)備開始以這個方向為目標(biāo)從vue源碼的入口開始找答案.

    入口開始

    來到src/core/index.js, 調(diào)了initGlobalAPI(), 其他代碼是ssr相關(guān), 暫不關(guān)心.

    進入initGlobalAPI方法, 做了一些暴露全局屬性和方法的事情, 最后有4個init, initUse是Vue的install方法, 前面vuex和vue-i18n的源碼分析已經(jīng)分析過了. initMixin是我們要深入的部分.

    在initMixin前面部分依舊做了一些變量的處理, 具體的init動作為:

    vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created')

    vue啟動的順序已經(jīng)看到了: 加載生命周期/時間/渲染的方法 => beforeCreate鉤子 => 調(diào)用injection => 初始化state => 調(diào)用provide => created鉤子.

    injection和provide都是比較新的api, 我還沒用過. 我們要研究的東西在initState中.

    來到initState:

    export function initState (vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) initProps(vm, opts.props)if (opts.methods) initMethods(vm, opts.methods)if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */) // 如果沒有data, _data效果一樣, 只是沒做代理}if (opts.computed) initComputed(vm, opts.computed)if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)} }

    做的事情很簡單: 如果有props就處理props, 有methods就處理methods, …, 我們直接看initData(vm).

    initData

    initData做了兩件事: proxy, observe.

    先貼代碼, 前面做了小的事情寫在注釋里了.

    function initData (vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function' // 如果data是函數(shù), 用vm作為this執(zhí)行函數(shù)的結(jié)果作為data? getData(data, vm): data || {}if (!isPlainObject(data)) { // 過濾亂搞, data只接受對象, 如果亂搞會報警并且把data認(rèn)為是空對象data = {}process.env.NODE_ENV !== 'production' && warn('data functions should return an object:\n' +'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',vm)}// proxy data on instanceconst keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) { // 遍歷dataconst key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) { // 判斷是否和methods重名warn(`Method "${key}" has already been defined as a data property.`,vm)}}if (props && hasOwn(props, key)) { // 判斷是否和props重名process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` +`Use prop default value instead.`,vm)} else if (!isReserved(key)) { // 判斷key是否以_或$開頭proxy(vm, `_data`, key) // 代理data}}// observe dataobserve(data, true /* asRootData */) }

    我們來看一下proxy和observe是干嘛的.

    proxy的參數(shù): vue實例, _data, 鍵.

    作用: 把vm.key的setter和getter都代理到vm._data.key, 效果就是vm.a實際實際是vm._data.a, 設(shè)置vm.a也是設(shè)置vm._data.a.

    代碼是:

    const sharedPropertyDefinition = {enumerable: true,configurable: true,get: noop,set: noop } export function proxy (target: Object, sourceKey: string, key: string) {// 在initData中調(diào)用: proxy(vm, `_data`, key)// target: vm, sourceKey: _data, key: key. 這里的key為遍歷data的key// 舉例: data為{a: 'a value', b: 'b value'}// 那么這里執(zhí)行的target: vm, sourceKey: _data, key: asharedPropertyDefinition.get = function proxyGetter () {return this[sourceKey][key] // getter: vm._data.a}sharedPropertyDefinition.set = function proxySetter (val) {this[sourceKey][key] = val // setter: vm._data.a = val}Object.defineProperty(target, key, sharedPropertyDefinition) // 用Object.defineProperty來設(shè)置getter, setter// 第一個參數(shù)是vm, 也就是獲取`vm.a`就獲取到了`vm._data.a`, 設(shè)置也是如此. }

    代理完成之后是本文的核心, initData最后調(diào)用了observe(data, true),來實現(xiàn)數(shù)據(jù)的響應(yīng).

    observe

    observe方法其實是一個濾空和單例的入口, 最后行為是創(chuàng)建一個observe對象放到observe目標(biāo)的__ob__屬性里, 代碼如下:

    /*** Attempt to create an observer instance for a value,* returns the new observer if successfully observed,* or the existing observer if the value already has one.*/ export function observe (value: any, asRootData: ?boolean): Observer | void {if (!isObject(value) || value instanceof VNode) { // 只能是監(jiān)察對象, 過濾非法參數(shù)return}let ob: Observer | voidif (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {ob = value.__ob__ // 如果已被監(jiān)察過, 返回存在的監(jiān)察對象} else if ( // 符合下面條件就新建一個監(jiān)察對象, 如果不符合就返回undefinedobserverState.shouldConvert &&!isServerRendering() &&(Array.isArray(value) || isPlainObject(value)) &&Object.isExtensible(value) &&!value._isVue) {ob = new Observer(value)}if (asRootData && ob) {ob.vmCount++}return ob }

    那么關(guān)鍵是new Observer(value)了, 趕緊跳到Observe這個類看看是如何構(gòu)造的.

    以下是Observer的構(gòu)造函數(shù):

    constructor (value: any) {this.value = value // 保存值this.dep = new Dep() // dep對象this.vmCount = 0def(value, '__ob__', this) // 自己的副本, 放到__ob__屬性下, 作為單例依據(jù)的緩存if (Array.isArray(value)) { // 判斷是否為數(shù)組, 如果是數(shù)組的話劫持一些數(shù)組的方法, 在調(diào)用這些方法的時候進行通知.const augment = hasProto? protoAugment: copyAugmentaugment(value, arrayMethods, arrayKeys)this.observeArray(value) // 遍歷數(shù)組, 繼續(xù)監(jiān)察數(shù)組的每個元素} else {this.walk(value) // 直到不再是數(shù)組(是對象了), 遍歷對象, 劫持每個對象來發(fā)出通知}}

    做了幾件事:

    • 建立內(nèi)部Dep對象. (作用是之后在watcher中遞歸的時候把自己添加到依賴中)
    • 把目標(biāo)的__ob__屬性賦值成Observe對象, 作用是上面提過的單例.
    • 如果目標(biāo)是數(shù)組, 進行方法的劫持. (下面來看)
    • 如果是數(shù)組就observeArray, 否則walk.

    那么我們來看看observeArray和walk方法.

    /*** Walk through each property and convert them into* getter/setters. This method should only be called when* value type is Object.*/walk (obj: Object) {const keys = Object.keys(obj)for (let i = 0; i < keys.length; i++) {defineReactive(obj, keys[i], obj[keys[i]]) // 用'obj[keys[i]]'這種方式是為了在函數(shù)中直接給這個賦值就行了}}/*** Observe a list of Array items.*/observeArray (items: Array<any>) {for (let i = 0, l = items.length; i < l; i++) {observe(items[i])}}

    我們發(fā)現(xiàn), observeArray的作用是遞歸調(diào)用, 最后調(diào)用的方法是defineReactive, 可以說這個方法是最終的核心了.

    下面我們先看一下數(shù)組方法劫持的目的和方法, 之后再看defineReactive的做法.

    array劫持

    之后會知道defineReactive的實現(xiàn)劫持的方法是Object.defineProperty來劫持對象的getter, setter, 那么數(shù)組的變化不會觸發(fā)這些劫持器, 所以vue劫持了數(shù)組的一些方法, 代碼比較零散就不貼了.

    最后的結(jié)果就是: array.prototype.push = function () {…}, 被劫持的方法有['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'], 也就是調(diào)用這些方法也會觸發(fā)響應(yīng). 具體劫持以后的方法是:

    def(arrayMethods, method, function mutator (...args) {const result = original.apply(this, args) // 調(diào)用原生的數(shù)組方法const ob = this.__ob__ // 獲取observe對象let insertedswitch (method) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':inserted = args.slice(2)break}if (inserted) ob.observeArray(inserted) // 繼續(xù)遞歸// notify changeob.dep.notify() // 出發(fā)notifyreturn result})

    做了兩件事:

  • 遞歸調(diào)用
  • 觸發(fā)所屬Dep的notify()方法.
  • 接下來就說最終的核心方法, defineReactive, 這個方法最后也調(diào)用了notify().

    defineReactive

    這里先貼整個代碼:

    /*** Define a reactive property on an Object.*/ export function defineReactive (// 這個方法是劫持對象key的動作// 這里還是舉例: 對象為 {a: 'value a', b: 'value b'}, 當(dāng)前遍歷到aobj: Object, // {a: 'value a', b: 'value b'}key: string, // aval: any, // value acustomSetter?: ?Function,shallow?: boolean ) {const dep = new Dep()const property = Object.getOwnPropertyDescriptor(obj, key)if (property && property.configurable === false) { // 判斷當(dāng)前key的操作權(quán)限r(nóng)eturn}// cater for pre-defined getter/setters// 獲取對象本來的getter setterconst getter = property && property.getconst setter = property && property.setlet childOb = !shallow && observe(val) // childOb是val的監(jiān)察對象(就是new Observe(val), 也就是遞歸調(diào)用)Object.defineProperty(obj, key, {enumerable: true,configurable: true,get: function reactiveGetter () {const value = getter ? getter.call(obj) : val // 如果本身有g(shù)etter, 先調(diào)用if (Dep.target) { // 如果有dep.target, 進行一些處理, 最后返回value, if里的代碼我們之后去dep的代碼中研究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 // 如果本身有g(shù)etter, 先調(diào)用/* eslint-disable no-self-compare */if (newVal === value || (newVal !== newVal && value !== value)) { // 如果值不變就不去做通知了, (或是某個值為Nan?)return}/* eslint-enable no-self-compare */if (process.env.NODE_ENV !== 'production' && customSetter) {customSetter() // 根據(jù)"生產(chǎn)環(huán)境不執(zhí)行"這個行為來看, 這個方法可能作用是log, 可能是保留方法, 還沒地方用?}if (setter) { // 如果本身有setter, 先調(diào)用, 沒的話就直接賦值setter.call(obj, newVal)} else {val = newVal // 因為傳入?yún)?shù)的時候其實是'obj[keys[i]]', 所以就等于是'obj[key] = newVal'了}childOb = !shallow && observe(newVal) // 重新建立子監(jiān)察dep.notify() // 通知, 可以說是劫持的核心步驟}}) }

    解釋都在注釋中了, 總結(jié)一下這個方法的做的幾件重要的事:

    • 建立Dep對象. (下面會說調(diào)用的Dep的方法的具體作用)
    • 遞歸調(diào)用. 可以說很大部分代碼都在遞歸調(diào)用, 分別在創(chuàng)建子observe對象, setter, getter中.
    • getter中: 調(diào)用原來的getter, 收集依賴(Dep.depend(), 之后會解釋收集的原理), 同樣也是遞歸收集.
    • setter中: 調(diào)用原來的setter, 并判斷是否需要通知, 最后調(diào)用dep.notify().

    總結(jié)一下, 總的來說就是, 進入傳入的data數(shù)據(jù)會被劫持, 在get的時候調(diào)用Dep.depend(), 在set的時候調(diào)用Dep.notify(). 那么Dep是什么, 這兩個方法又干了什么, 帶著疑問去看Dep對象.

    Dep

    Dep應(yīng)該是dependencies的意思. dep.js整個文件只有62行, 所以貼一下:

    /*** A dep is an observable that can have multiple* directives subscribing to it.*/ export default class Dep {static target: ?Watcher;id: number;subs: Array<Watcher>;constructor () {this.id = uid++this.subs = []}addSub (sub: Watcher) {this.subs.push(sub)}removeSub (sub: Watcher) {remove(this.subs, sub)}depend () {if (Dep.target) {Dep.target.addDep(this)}}notify () {// stabilize the subscriber list firstconst subs = this.subs.slice()for (let i = 0, l = subs.length; i < l; i++) {subs[i].update()}} }// the current target watcher being evaluated. // this is globally unique because there could be only one // watcher being evaluated at any time. // 這是一個隊列, 因為不允許有多個watcher的get方法同時調(diào)用 Dep.target = null const targetStack = []export function pushTarget (_target: Watcher) {// 設(shè)置target, 把舊的放進stackif (Dep.target) targetStack.push(Dep.target)Dep.target = _target }export function popTarget () {// 從stack拿一個作為當(dāng)前的Dep.target = targetStack.pop() }

    首先來分析變量:

    • 全局Target. 這個其實是用來跟watcher交互的, 也保證了普通get的時候沒有target就不設(shè)置依賴, 后面會解釋.
    • id. 這是用來在watcher里依賴去重的, 也要到后面解釋.
    • subs: 是一個watcher數(shù)組. sub應(yīng)該是subscribe的意思, 也就是當(dāng)前dep(依賴)的訂閱者列表.

    再來看方法:

    • 構(gòu)造: 設(shè)uid, subs. addSub: 添加wathcer, removeSub: 移除watcher. 這3個好無聊.
    • depend: 如果有Dep.target, 就把自己添加到Dep.target中(調(diào)用了Dep.target.addDep(this)).

      那么什么時候有Dep.target呢, 就由pushTarget()和popTarget()來操作了, 這些方法在Dep中沒有調(diào)用, 后面會分析是誰在操作Dep.target.(這個是重點)

    • notify: 這個是setter劫持以后調(diào)用的最終方法, 做了什么: 把當(dāng)前Dep訂閱中的每個watcher都調(diào)用update()方法.

    Dep看完了, 我們的疑問都轉(zhuǎn)向了Watcher對象了. 現(xiàn)在看來有點糊涂, 看完Watcher就都明白了.

    Watcher

    watcher非常大(而且打watcher這個單詞也非常容易手誤, 心煩), 我們先從構(gòu)造看起:

    constructor (vm: Component,expOrFn: string | Function,cb: Function,options?: Object) {this.vm = vm // 保存vmvm._watchers.push(this) // 把watcher存到vm里// options// 讀取配置 或 設(shè)置默認(rèn)值if (options) {this.deep = !!options.deepthis.user = !!options.userthis.lazy = !!options.lazythis.sync = !!options.sync} else {this.deep = this.user = this.lazy = this.sync = false}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.lazy // for lazy watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()this.expression = process.env.NODE_ENV !== 'production' // 非生產(chǎn)環(huán)境就記錄expOrFn? expOrFn.toString(): ''// parse expression for getter// 設(shè)置getter, parse字符串, 并濾空濾錯if (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)if (!this.getter) {this.getter = function () {}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)}}// 調(diào)用get獲得值this.value = this.lazy? undefined: this.get()}

    注釋都寫了, 我來高度總結(jié)一下構(gòu)造器做了什么事:

    • 處理傳入的參數(shù)并設(shè)置成自己的屬性.
    • parse表達(dá)式. watcher表達(dá)式接受2種: 方法/字符串. 如果是方法就設(shè)為getter, 如果是字符串會進行處理:
    /*** Parse simple path.*/const bailRE = /[^\w.$]/export function parsePath (path: string): any {if (bailRE.test(path)) {return}const segments = path.split('.')// 這里是vue如何分析watch的, 就是接受 '.' 分隔的變量.// 如果鍵是'a.b.c', 也就等于function () {return this.a.b.c}return function (obj) {for (let i = 0; i < segments.length; i++) {if (!obj) returnobj = obj[segments[i]]}return obj}}

    處理的效果寫在上面代碼的注釋里.

    • 調(diào)用get()方法.

    下面說一下get方法. get()方法是核心, 看完了就能把之前的碎片都串起來了. 貼get()的代碼:

    /*** Evaluate the getter, and re-collect dependencies.*/get () {pushTarget(this)// 進入隊列, 把當(dāng)前watcher設(shè)置為Dep.target// 這樣下面調(diào)用getter的時候出發(fā)的dep.append() (最后調(diào)用Dep.target.addDep()) 就會調(diào)用這個watcher的addDep.let valueconst vm = this.vmtry {value = this.getter.call(vm, vm)// 調(diào)用getter的時候會走一遍表達(dá)式,// 如果是 this.a + this.b , 會在a和b的getter中調(diào)用Dep.target.addDep(), 最后結(jié)果就調(diào)用了當(dāng)前watcher的addDep,// 當(dāng)前watcher就有了this.a的dep和this.b的dep// addDep把當(dāng)前watcher加入了dep的sub(subscribe)里, dep的notify()調(diào)用就會運行本watcher的run()方法.} 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// 走到這里已經(jīng)通過了getter獲得到了value, 或者失敗為undefined, 這個值返回作為watcher的valule// 處理deep選項 (待看)if (this.deep) {traverse(value)}popTarget() // 移除隊列this.cleanupDeps() // 清理依賴(addDep加到newDep數(shù)組, 這步做整理動作)}return value}

    注釋都在代碼中了, 這段理解了就對整個響應(yīng)系統(tǒng)理解了.

    我來總結(jié)一下: (核心, 非常重要)

    • dep方面: 傳入vue參數(shù)的data(實際是所有調(diào)用defineReactive的屬性)都會產(chǎn)生自己的Dep對象.
    • Watcher方面: 在所有new Watcher的地方產(chǎn)生Watcher對象.
    • dep與Watcher關(guān)系: Watcher的get方法建立了雙方關(guān)系:

      把自己設(shè)為target, 運行watcher的表達(dá)式(即調(diào)用相關(guān)數(shù)據(jù)的getter), 因為getter有鉤子, 調(diào)用了Watcher的addDep, addDep方法把dep和Watcher互相推入互相的屬性數(shù)組(分別是deps和subs)

    • dep與Watcher建立了多對多的關(guān)系: dep含有訂閱的watcher的數(shù)組, watcher含有所依賴的變量的數(shù)組
    • 當(dāng)dep的數(shù)據(jù)調(diào)動setter, 調(diào)用notify, 最終調(diào)用Watcher的update方法.
    • 前面提到dep與Watcher建立關(guān)系是通過get()方法, 這個方法在3個地方出現(xiàn): 構(gòu)造方法, run方法, evaluate方法. 也就是說, notify了以后會重新調(diào)用一次get()方法. (所以在lifycycle中調(diào)用的時候把依賴和觸發(fā)方法都寫到getter方法中了).

    那么接下來要看一看watcher在什么地方調(diào)用的.

    找了一下, 一共三處:

    • initComputed的時候: (state.js)

      ``` watchers[key] = new Watcher( vm, getter || noop, noop, computedWatcherOptions ) ```
    • $watch api: (state.js)

      ``` new Watcher(vm, expOrFn, cb, options) ```
    • lifecycle的mount階段: (lifecycle.js)

      ``` new Watcher(vm, updateComponent, noop) ```

    總結(jié)

    看完源碼就不神秘了, 寫得也算很清楚了. 當(dāng)然還有很多細(xì)節(jié)沒寫, 因為沖著目標(biāo)來.

    總結(jié)其實都在上一節(jié)的粗體里了.

    甜點

    我們只從data看了, 那么props和computed應(yīng)該也是這樣的, 因為props應(yīng)該與組建相關(guān), 下回分解吧, 我們來看看computed是咋回事吧.

    const computedWatcherOptions = { lazy: true }function initComputed (vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)// computed properties are just getters during SSRconst isSSR = isServerRendering()for (const key in computed) {// 循環(huán)每個computed// ------------// 格式濾錯濾空const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (process.env.NODE_ENV !== 'production' && getter == null) {warn(`Getter is missing for computed property "${key}".`,vm)}if (!isSSR) {// create internal watcher for the computed property.// 為computed建立wathcerwatchers[key] = new Watcher(vm,getter || noop,noop,computedWatcherOptions)}// component-defined computed properties are already defined on the// component prototype. We only need to define computed properties defined// at instantiation here.// 因為沒有被代理, computed屬性是不能通過vm.xx獲得的, 如果可以獲得說明重復(fù)定義, 拋出異常.if (!(key in vm)) {defineComputed(vm, key, userDef)} else if (process.env.NODE_ENV !== 'production') {if (key in vm.$data) {warn(`The computed property "${key}" is already defined in data.`, vm)} else if (vm.$options.props && key in vm.$options.props) {warn(`The computed property "${key}" is already defined as a prop.`, vm)}}} }

    已注釋, 總結(jié)為:

    • 遍歷每個computed鍵值, 過濾錯誤語法.
    • 遍歷每個computed鍵值, 為他們建立watcher, options為{ lazy: true}.
    • 遍歷每個computed鍵值, 調(diào)用defineComputed.

    那么繼續(xù)看defineComputed.

    export function defineComputed (target: any,key: string,userDef: Object | Function ) {const shouldCache = !isServerRendering()// 因為computed除了function還有g(shù)et set 字段的語法, 下面的代碼是做api的兼容if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): userDefsharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): userDef.get: noopsharedPropertyDefinition.set = userDef.set? userDef.set: noop}// 除非設(shè)置setter, computed屬性是不能被修改的, 拋出異常 (evan說改變了自由哲學(xué), 要控制低級用戶)if (process.env.NODE_ENV !== 'production' &&sharedPropertyDefinition.set === noop) {sharedPropertyDefinition.set = function () {warn(`Computed property "${key}" was assigned to but it has no setter.`,this)}}// 其實核心就下面這步... 上面步驟的作用是和data一樣添加一個getter, 增加append動作. 現(xiàn)在通過vm.xxx可以獲取到computed屬性啦!Object.defineProperty(target, key, sharedPropertyDefinition) }function createComputedGetter (key) {return function computedGetter () {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {if (watcher.dirty) {watcher.evaluate()}if (Dep.target) {watcher.depend()}return watcher.value}} }

    因為computed可以設(shè)置getter, setter, 所以computed的值不一定是function, 可以為set和get的function, 很大部分代碼是做這些處理, 核心的事情有2件:

    • 使用Object.defineProperty在vm上掛載computed屬性.
    • 為屬性設(shè)置getter, getter做了和data一樣的事: depend. 但是多了一步: watcher.evalueate().

    看到這里, computed注冊核心一共做了兩件事:

  • 為每個computed建立watcher(lazy: true)
  • 建立一個getter來depend, 并掛到vm上.
  • 那么dirty成了疑問, 我們回到watcher的代碼中去看, lazy和dirty和evaluate是干什么的.

    精選相關(guān)代碼:

    • (構(gòu)造函數(shù)中) this.dirty = this.lazy
    • (構(gòu)造函數(shù)中) this.value = this.lazy ? undefined : this.get()
    • (evaluate函數(shù))

      ``` evaluate () { this.value = this.get() this.dirty = false } ```

    到這里已經(jīng)很清楚了. 因為還沒設(shè)置getter, 所以在建立watcher的時候不立即調(diào)用getter, 所以構(gòu)造函數(shù)沒有馬上調(diào)用get, 在設(shè)置好getter以后調(diào)用evaluate來進行依賴注冊.

    總結(jié): computed是watch+把屬性掛到vm上的行為組合.

    原文地址:https://segmentfault.com/a/1190000012559684

    轉(zhuǎn)載于:https://www.cnblogs.com/lalalagq/p/9960402.html

    總結(jié)

    以上是生活随笔為你收集整理的vue源码之响应式数据的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。