48.vue響應(yīng)式系統(tǒng)構(gòu)建過程->initData

我的vue版本 -> Vue.js v2.6.11

概述:vue響應(yīng)式系統(tǒng)構(gòu)建過程主要是在Init階段烁设,在initState()方法中會對props檬某,methods撬腾,data螟蝙,computed恢恼,watcher等內(nèi)容進行初始化,在初始化data階段會對傳入的options中的data進行一些校驗胰默,接下來就是使用new Observer()data中的數(shù)據(jù)進行響應(yīng)化處理

1.src\core\instance\index.js -> vue源碼的入口文件

//入口
function Vue(options) {
  //初始化vue
  this._init(options);
}

2.src\core\instance\init.js -> 初始化文件场斑,主要關(guān)注initState()這個方法,其他的后面再做研究

export function initMixin(Vue: Class<Component>) {
  //在initMinxin里面定義Vue的原型方法_init
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this;
    // a uid
    vm._uid = uid++;

    let startTag, endTag;

    // a flag to avoid this being observed
    vm._isVue = true;
    //合并選項
    // merge options
    if (options && options._isComponent) {
      //判斷是不是一個組件牵署,是的話執(zhí)行initInternalComponent漏隐,否則合并options
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options);
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== "production") {
      initProxy(vm);
    } else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm; //把vm放在_self屬性上暴露出去
    initLifecycle(vm); //初始化生命周期:$parent,$root,$children,$refs
    initEvents(vm); //對父組件傳入事件添加監(jiān)聽,初始化事件
    initRender(vm); //生命$slots,$createElemnet,渲染相關(guān)的
    callHook(vm, "beforeCreate");
    initInjections(vm); // resolve injections before data/props,注入數(shù)據(jù)
    //數(shù)據(jù)初始化
    initState(vm); //初始化props奴迅、data青责、watch、methods取具、computed等屬性脖隶,因此在beforeCreate的鉤子函數(shù)中獲取不到前面的這些定義的屬性和方法
    initProvide(vm); // resolve provide after data/props,提供數(shù)據(jù)
    callHook(vm, "created");
    //最終掛載方法
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
  };
}

3.src\core\instance\state.js

export function initState(vm: Component) {
  vm._watchers = [];
  const opts = vm.$options;
  //初始化props
  if (opts.props) initProps(vm, opts.props);
  //初始化methods
  if (opts.methods) initMethods(vm, opts.methods);
  //初始化data
  if (opts.data) {
    initData(vm);
  } else {
    //沒有創(chuàng)建則創(chuàng)建一個空對象暇检,并設(shè)置位響應(yīng)式
    observe((vm._data = {}), true /* asRootData */);
  }
  //初始化computed
  if (opts.computed) initComputed(vm, opts.computed);
  //初始化watch
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

initData方法

可以看到我們平時常見的一些報錯提示信息

  • 校驗傳入的data是否是一個object類型
  • 遍歷所有的keys(校驗keys的合法性)
    • 是否有和methods重名的屬性
    • 是否有和props重名的屬性
  • 校驗通過調(diào)用proxy方法产阱,將所有的屬性都掛載到_data屬性上,vue中提供了使用$xxx直接訪問vue實例屬性的方法
  • 數(shù)據(jù)響應(yīng)化處理
function initData(vm: Component) {
  let data = vm.$options.data;
  data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
  //校驗data必須是一個對象块仆,其他的類似Array构蹬,F(xiàn)unction等也算是對象類型王暗,但是有著更精確的數(shù)據(jù)類型
  if (!isPlainObject(data)) {
    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 instance
  //獲取所有的data中的Key
  const keys = Object.keys(data);
  //獲取配置對象上的props屬性
  const props = vm.$options.props;
  //獲取配置對象上的methods屬性,所有的方法都在Methods對象中
  const methods = vm.$options.methods;
  let i = keys.length;
  while (i--) {
    const key = keys[i];
    if (process.env.NODE_ENV !== "production") {
      //校驗methods->Key的唯一性
      if (methods && hasOwn(methods, key)) {
        warn(
          `Method "${key}" has already been defined as a data property.`,
          vm
        );
      }
    }
    //校驗props->Key的唯一性
    if (props && hasOwn(props, key)) {
      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是否是使用$或者下劃線進行定義的庄敛,因為vue中定義了很多的$data俗壹,$el等來直接操作vue對象。把所有的key都代理到vm的_data屬性上面
      proxy(vm, `_data`, key);
        //此處的proxy代理和observe中的walk代理有什么區(qū)別铐姚?
        //proxy代理的是將自定義的data中的所有屬性定義到_data上策肝,而walk中的Object.defineProperty則是針對data的值
    }
  }
  // observe data
  observe(data, true /* asRootData */);
}

4.src\core\observer\index.js
關(guān)鍵一步:ob = new Observer(value),開始構(gòu)建observe對象

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
  ) {
    ob = new Observer(value)
  }
  if (asRootData && ob) {
    ob.vmCount++
  }
  return ob
}

src\core\observer\index.js
在vue2中不能直接處理數(shù)組的方法隐绵,需要重寫數(shù)組的方法之众,怎樣重寫數(shù)組的方法?看這里->監(jiān)聽數(shù)組變化的方法 原理是將常用的操作數(shù)組的方法列下來依许,替換原來數(shù)組的方法棺禾,同時也方便擴展。

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
    //創(chuàng)建依賴收集的容器
    this.dep = new Dep()
    this.vmCount = 0
    //設(shè)置一個__ob__屬性引用當(dāng)前observer實例
    def(value, '__ob__', this)
    //判斷類型
    if (Array.isArray(value)) {
      //如果是數(shù)組峭跳,替換數(shù)組對象的原型
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      //如果數(shù)組里面元素是對象還需要做響應(yīng)化處理
      this.observeArray(value)
    } else {
      //walk方法膘婶,將每個值進行響應(yīng)化處理
      this.walk(value)
    }
  }
  /**
   * Walk through all properties 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])
    }
  }
  /**
   * Observe a list of Array items.
   */
  observeArray(items: Array < any > ) {
    for (let i = 0, l = items.length; i < l; i++) {
      observe(items[i])
    }
  }
}

defineReactive()可以看到我們常說的Object.defineProperty了,至此響應(yīng)化data的部分就已經(jīng)完成了蛀醉。

export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter ? : ? Function,
  shallow ? : boolean
) {
  //和Key一一對應(yīng)
  const dep = new Dep()
  //childOb,屬性攔截悬襟,只要是對象類型都會返回childobj
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      //如果存在依賴
      if (Dep.target) {
        //依賴收集
        dep.depend()
        if (childOb) {
          //如果存在子obj,子obj也收集這個依賴拯刁?為什么要這么做脊岳?作用:Obj和父變了和子改變了都會通知進行更新。例:在訪問時{obj.foo}無論是Obj變了還是obj.foo的值變了都會通知進行更新垛玻。
          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()
    }
  })
}

以上是我對vue初始化data的理解割捅,有不對之處歡迎指證,共同學(xué)習(xí)帚桩!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亿驾,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子账嚎,更是在濱河造成了極大的恐慌莫瞬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郭蕉,死亡現(xiàn)場離奇詭異疼邀,居然都是意外死亡,警方通過查閱死者的電腦和手機恳不,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門檩小,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烟勋,你說我怎么就攤上這事规求】鸶叮” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵阻肿,是天一觀的道長瓦戚。 經(jīng)常有香客問我,道長丛塌,這世上最難降的妖魔是什么绎橘? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任刊愚,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闺属。我一直安慰自己捺僻,他們只是感情好浆劲,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布兵罢。 她就那樣靜靜地躺著,像睡著了一般彤敛。 火紅的嫁衣襯著肌膚如雪与帆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天墨榄,我揣著相機與錄音玄糟,去河邊找鬼。 笑死袄秩,一個胖子當(dāng)著我的面吹牛阵翎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播播揪,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贮喧,長吁一口氣:“原來是場噩夢啊……” “哼筒狠!你這毒婦竟也來了猪狈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤辩恼,失蹤者是張志新(化名)和其女友劉穎雇庙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灶伊,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡疆前,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了聘萨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竹椒。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖米辐,靈堂內(nèi)的尸體忽然破棺而出胸完,到底是詐尸還是另有隱情书释,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布赊窥,位于F島的核電站爆惧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏锨能。R本人自食惡果不足惜扯再,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望址遇。 院中可真熱鬧熄阻,春花似錦、人聲如沸倔约。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跺株。三九已至复濒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乒省,已是汗流浹背巧颈。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留砸泛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓唇礁,卻偏偏與公主長得像惨篱,于是被迫代替她去往敵國和親盏筐。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容