Vue源碼分析(4)--實(shí)例的初始化過(guò)程

前言

本文是vue2.x源碼分析的第四篇,主要講解vue實(shí)例的初始化過(guò)程init*系列!

先看調(diào)用形式

    initLifecycle(vm);
    initEvents(vm);
    initRender(vm);
    callHook(vm, 'beforeCreate');
    initInjections(vm); // 在data/props之前處理注入,暫不清楚作用Unknown4.1,本節(jié)不分析
    initState(vm);      //最主要的函數(shù)
    initProvide(vm);    // 在data/props之后處理provide,暫不清楚作用Unknown4.2,本節(jié)不分析
    callHook(vm, 'created');

1、分析initLifecycle(vm),initEvents(vm),initRender(vm)

1.1、initLifecycle(vm)

function initLifecycle (vm) {
  var options = vm.$options;
  // 定位第一個(gè)非抽象的parent,記為Unknown4.1
  var parent = options.parent;
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent;
    }
    parent.$children.push(vm);
  }
  //給vm實(shí)例添加如下屬性
  vm.$parent = parent;
  vm.$root = parent ? parent.$root : vm;
  vm.$children = [];
  vm.$refs = {};
  vm._watcher = null;
  vm._inactive = null;
  vm._directInactive = false;
  vm._isMounted = false;
  vm._isDestroyed = false;
  vm._isBeingDestroyed = false;
}

1.2、initEvents(vm)

function initEvents (vm) {
  vm._events = Object.create(null);
  vm._hasHookEvent = false;
  // 處理父元素中的events,Unknown4.2
  var listeners = vm.$options._parentListeners;
  if (listeners) {
    updateComponentListeners(vm, listeners);
  }
}

1.3、initRender(vm)

function initRender (vm) {
  vm.$vnode = null; // the placeholder node in parent tree,在父樹中的位置
  vm._vnode = null; // the root of the child tree,子樹的根
  vm._staticTrees = null;//靜態(tài)樹
  var parentVnode = vm.$options._parentVnode;
  var renderContext = parentVnode && parentVnode.context;
  vm.$slots = resolveSlots(vm.$options._renderChildren, renderContext);//處理slot
  vm.$scopedSlots = emptyObject;
  // 將createElement函數(shù)綁定到該實(shí)例,參數(shù)順序:tag, data, children, normalizationType, alwaysNormalize.
  vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };//內(nèi)部調(diào)用版
  vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };外部調(diào)用版
}

之后,beforeCreate生命周期函數(shù)被調(diào)用:callHook(vm, 'beforeCreate');

2、initState(vm)

function initState (vm) {
  vm._watchers = [];                         //用于存放所用的watcher實(shí)例
  var opts = vm.$options;
  if (opts.props) {
    initProps(vm, opts.props);               //對(duì)props各項(xiàng)進(jìn)行驗(yàn)證
  }
  if (opts.methods) {
    initMethods(vm, opts.methods);           //將methods中各項(xiàng)添加到vm上
  }
  if (opts.data) {
    initData(vm);                            //對(duì)data進(jìn)行觀測(cè)
  } else {
    observe(vm._data = {}, true /* asRootData */);
  }
  if (opts.computed) {
    initComputed(vm, opts.computed);         //將computed屬性添加到vm,并定義響應(yīng)式
  }
  if (opts.watch) {
    initWatch(vm, opts.watch);               //對(duì)watch屬性進(jìn)行處理...
  }
}

2.1、initProps(vm,propsOptions)

function initProps (vm, propsOptions) {
  var propsData = vm.$options.propsData || {};
  var props = vm._props = {};
  // 對(duì)props中key進(jìn)行緩存以減少遍歷
  var keys = vm.$options._propKeys = [];
  var isRoot = !vm.$parent; //是否是根節(jié)點(diǎn)
  // 根實(shí)例的props需要被轉(zhuǎn)換
  observerState.shouldConvert = isRoot; //observeState={isSettingProps:false,shouldConvert:false}
  var loop = function ( key ) {
    keys.push(key);
    var value = validateProp(key, propsOptions, propsData, vm);
    {
      if (isReservedProp[key]) {  //判斷是否是保留的prop(不能是key,slot,ref)
        warn(
          ("\"" + key + "\" is a reserved attribute and cannot be used as component prop."),
          vm
        );
      }
      //將props的key定義為響應(yīng)式,這里的匿名函數(shù)是作為customSetter用的,defineReactive$$1函數(shù)后面會(huì)分析
      defineReactive$$1(props, key, value, function () {
        if (vm.$parent && !observerState.isSettingProps) {
          warn(
            "Avoid mutating a prop directly since the value will be " +
            "overwritten whenever the parent component re-renders. " +
            "Instead, use a data or computed property based on the prop's " +
            "value. Prop being mutated: \"" + key + "\"",
            vm
          );
        }
      });
    }
    /* 靜態(tài)props在Vue.extend()時(shí)已經(jīng)被代理,這里只需對(duì)實(shí)例上的props進(jìn)行代理,代理的目的很簡(jiǎn)單,
    就是使props中的屬性可以用vm.xxx訪問(wèn),而不必vm.props.xxx,實(shí)現(xiàn)很簡(jiǎn)單,就是使用API:Object.defineProperty*/
    if (!(key in vm)) {
      proxy(vm, "_props", key);
    }
  };
  for (var key in propsOptions) loop( key );
  observerState.shouldConvert = true;
}

主要分析下validateProp(key, propsOptions, propsData, vm)

//該函數(shù)就是對(duì)prop進(jìn)行驗(yàn)證,如type、default、required、validator
function validateProp (
  key,
  propOptions,
  propsData,
  vm
) {
  var prop = propOptions[key];
  var absent = !hasOwn(propsData, key);
  var value = propsData[key];
  // 處理布爾類型的prop
  if (isType(Boolean, prop.type)) {
    if (absent && !hasOwn(prop, 'default')) {
      value = false;
    } else if (!isType(String, prop.type) && (value === '' || value === hyphenate(key))) {
      value = true;
    }
  }
  if (value === undefined) {
    value = getPropDefaultValue(vm, prop, key);//當(dāng)type為object時(shí),default必須是函數(shù)
    // 觀測(cè)value
    var prevShouldConvert = observerState.shouldConvert;
    observerState.shouldConvert = true;
    observe(value);
    observerState.shouldConvert = prevShouldConvert;
  }
  {
    assertProp(prop, key, value, vm, absent);//檢查是否有required和validator
  }
  return value
}

2.2、initMethods(vm, opts.methods)

function initMethods (vm, methods) {
  var props = vm.$options.props;
  for (var key in methods) {
    //遍歷methods,將methods中的方法掛在vm實(shí)例上,注意這里調(diào)用了bind函數(shù),故methods中所有方法的this都是vm對(duì)象
    vm[key] = methods[key] == null ? noop : bind(methods[key], vm);
    {
      if (methods[key] == null) {
        warn(
          "method \"" + key + "\" has an undefined value in the component definition. " +
          "Did you reference the function correctly?",
          vm
        );
      }
      //檢測(cè)props中是否與methods中有同名屬性
      if (props && hasOwn(props, key)) {
        warn(
          ("method \"" + key + "\" has already been defined as a prop."),
          vm
        );
      }
    }
  }
}

2.3、initData(vm)

function initData (vm) {
  //這里的data通過(guò)策略合并對(duì)象變成了函數(shù)mergedInstanceDataFn
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm) //getData就是執(zhí)行mergedInstanceDataFn函數(shù),返回data對(duì)象
    : data || {};
  //當(dāng)data函數(shù)返回的不是對(duì)象時(shí)
  if (!isPlainObject(data)) {
    data = {};
    "development" !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // 在實(shí)例vm上代理data中各項(xiàng),作用同props的代理
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var i = keys.length;
  while (i--) {
    //data中屬性不能和props中同名
    if (props && hasOwn(props, keys[i])) {
      "development" !== 'production' && warn(
        "The data property \"" + (keys[i]) + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(keys[i])) { //data中屬性不能以_或$開頭
      proxy(vm, "_data", keys[i]);
    }
  }
  // 對(duì)data進(jìn)行觀測(cè)
  observe(data, true /* asRootData */);
}

這里主要分析observe(data, true)

//給value添加一個(gè)observer,保存在value.__ob__屬性上。
function observe (value, asRootData) {
  //不對(duì)普通類型觀測(cè)
  if (!isObject(value)) {
    return
  }
  var ob;
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    observerState.shouldConvert &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue //當(dāng)有_isVue屬性時(shí),該value不會(huì)被觀測(cè)
  ) {
    ob = new Observer(value); //主要函數(shù),實(shí)例化一個(gè)Observer
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob
}

來(lái)看看Observer(value)

var Observer = function Observer (value) {
  this.value = value;
  this.dep = new Dep();  //每個(gè)observer都實(shí)例化一個(gè)Dep,用于收集依賴
  this.vmCount = 0;
  def(value, '__ob__', this); //將__ob__放在value上,值為observer對(duì)象
  if (Array.isArray(value)) { //當(dāng)value是數(shù)組
    var augment = hasProto
      ? protoAugment
      : copyAugment;
    augment(value, arrayMethods, arrayKeys);
    this.observeArray(value);
  } else {
    this.walk(value);          //當(dāng)value是對(duì)象
  }
};

來(lái)看看walk(value)

Observer.prototype.walk = function walk (obj) {
  var keys = Object.keys(obj); //這里__ob__不會(huì)出現(xiàn)在keys里
  for (var i = 0; i < keys.length; i++) {
    defineReactive$$1(obj, keys[i], obj[keys[i]]); //對(duì)obj中每一項(xiàng)進(jìn)行響應(yīng)式定義
  }
};

來(lái)看看defineReactive$$1(obj, keys[i], obj[keys[i]])

function defineReactive$$1 (obj,key,val,customSetter) {
  var dep = new Dep();
  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }
  // 引用預(yù)先定義的getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
 //val可能是對(duì)象,故繼續(xù)觀測(cè)
  var childOb = observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        //子對(duì)象也收集父對(duì)象的依賴
        if (childOb) {
          childOb.dep.depend();
        }
        //對(duì)數(shù)組的依賴處理
        if (Array.isArray(value)) {
          dependArray(value);
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if ("development" !== 'production' && customSetter) {
        customSetter();//報(bào)錯(cuò)用
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = observe(newVal); //設(shè)置新值后,對(duì)新值進(jìn)行觀測(cè)
      dep.notify();  //觸發(fā)觀測(cè)器的回調(diào)或get函數(shù)
    }
  });
}

2.4、initComputed(vm, opts.computed)

function initComputed (vm, computed) {
  var watchers = vm._computedWatchers = Object.create(null);//computed中watcher存放在vm._computedWatchers
  for (var key in computed) {
    var userDef = computed[key];
    var getter = typeof userDef === 'function' ? userDef : userDef.get;
    {
      if (getter === undefined) {
        warn(
          ("No getter function has been defined for computed property \"" + key + "\"."),
          vm
        );
        getter = noop;
      }
    }
    // 對(duì)computed中每一個(gè)屬性創(chuàng)建Watcher.
    watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions);
    // 將computed中的屬性定義到vm上,跟代理差不多,使vm.xx等價(jià)于vm.computed.xx
    if (!(key in vm)) { //computed中屬性若和vm上同名會(huì)被vm上的覆蓋
      defineComputed(vm, key, userDef);
    }
  }
}

來(lái)看看Watcher(vm, getter, noop, computedWatcherOptions)

var Watcher = function Watcher (vm,expOrFn,cb,options) {
  this.vm = vm;
  vm._watchers.push(this);      //vm._watchers存放所有的watcher實(shí)例
  // 處理options,在initComputed中,options={lazy:true},即lazy watchers;
  // 在initWatch中,options={user:true}
  if (options) {
    this.deep = !!options.deep;
    this.user = !!options.user;
    this.lazy = !!options.lazy;
    this.sync = !!options.sync;
  } else {
    this.deep = this.user = this.lazy = this.sync = false;
  }
  this.cb = cb;      //存放回調(diào)函數(shù)cb
  this.id = ++uid$2; // uid for batching
  this.active = true;
  this.dirty = this.lazy; // for lazy watchers
  this.deps = [];
  this.newDeps = [];
  this.depIds = new _Set();
  this.newDepIds = new _Set();
  this.expression = expOrFn.toString();  //存放傳入的expOrFn
  if (typeof expOrFn === 'function') {
    // 當(dāng)expOrFn是函數(shù),initComputed從這兒走
    this.getter = expOrFn;
  } else {
    // 當(dāng)expOrFn是字符串,initWatch從這兒走
    this.getter = parsePath(expOrFn);//字符串若有分隔,只能用'.'號(hào),不能用空格,'-'等
    if (!this.getter) {
      this.getter = function () {};
      "development" !== 'production' && warn(
        "Failed watching path: \"" + expOrFn + "\" " +
        'Watcher only accepts simple dot-delimited paths. ' +
        'For full control, use a function instead.',
        vm
      );
    }
  }
  this.value = this.lazy  //initComputed中l(wèi)azy是true,故不調(diào)用get函數(shù);initWatcher中會(huì)調(diào)用get
    ? undefined
    : this.get();
};

2.4、initWatch(vm, opts.watch)

function initWatch (vm, watch) {
  for (var key in watch) {
    var handler = watch[key];
    if (Array.isArray(handler)) { //當(dāng)handler是數(shù)組時(shí),分別調(diào)用createWatcher
      for (var i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i]);
      }
    } else {
      createWatcher(vm, key, handler);
    }
  }
}

來(lái)看看createWatcher(vm, key, handler)

function createWatcher (vm, key, handler) {
  var options;
  if (isPlainObject(handler)) {  //當(dāng)handler是數(shù)組時(shí)應(yīng)該提供handler屬性
    options = handler;
    handler = handler.handler;
  }
  if (typeof handler === 'string') {   //當(dāng)handler是字符串,從vm上找同名屬性
    handler = vm[handler];
  }
  vm.$watch(key, handler, options);  //主要函數(shù)
}

來(lái)看看vm.$watch(key,handler,options)

Vue.prototype.$watch = function (expOrFn,cb,options) {
    var vm = this;
    options = options || {};
    options.user = true;  //user表示是否是用戶自定義的watcher
    var watcher = new Watcher(vm, expOrFn, cb, options); //回到2.3中再看一遍,會(huì)發(fā)現(xiàn)最后調(diào)用get函數(shù)
    if (options.immediate) {
      cb.call(vm, watcher.value);
    }
    return function unwatchFn () {  //返回解除watcher的函數(shù)
      watcher.teardown();
    }
};

來(lái)看看get函數(shù)

Watcher.prototype.get = function get () {
  pushTarget(this);//內(nèi)部執(zhí)行了Dep.target = this,并將this存入棧targetStack;
  var value;
  var vm = this.vm;
  if (this.user) {
    try {
      value = this.getter.call(vm, vm);//調(diào)用getter,在initWatch中即調(diào)用如下函數(shù):
      // function (obj) {  //這里的obj=vm
      //   for (var i = 0; i < segments.length; i++) {  //segments是對(duì)觀測(cè)表達(dá)式進(jìn)行的分割
      //     if (!obj) { return }
      //     obj = obj[segments[i]]; //當(dāng)segments.length>1時(shí),第一次從vm上找同名屬性返回,后面就從obj上找,例如
      //                             //當(dāng)觀測(cè)表達(dá)式是'article.title',那么segments=['article','title']
      //                             //先找vm.article,再在article上找title作為結(jié)果返回
      //   }
      //   return obj
      // }
    } catch (e) {
      handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
    }
  } else {
    value = this.getter.call(vm, vm);
  }
  // "touch" every property so they are all tracked as
  // dependencies for deep watching
  if (this.deep) {  //是否進(jìn)行深度觀測(cè)
    traverse(value);
  }
  popTarget();      //從targetStack棧中彈出this,將Deep.target仍為this
  this.cleanupDeps();  //暫不清楚,Unknown4.1
  return value
};

當(dāng)initState調(diào)用完成后,created生命周期函數(shù)被調(diào)用:callHook(vm, 'created');

3、小結(jié)

??由于還沒到渲染環(huán)節(jié),故本節(jié)中定義的響應(yīng)式屬性還不能起作用,不太好理解,下一節(jié)將會(huì)看到
這些響應(yīng)式屬性是怎么起作用的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評(píng)論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,673評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,610評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評(píng)論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,668評(píng)論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,004評(píng)論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評(píng)論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,173評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,705評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,426評(píng)論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,656評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評(píng)論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,833評(píng)論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,371評(píng)論 3 400
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,621評(píng)論 2 380

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