new Vue發(fā)生了什么句喷?(2)

上一篇文章我們了解了Vue是怎么一步步變得豐富起來的镣典,從而形成我們的3大框架之一,接下來我們當(dāng)然是要了解這個(gè)Vue是用來干嘛的唾琼?怎么發(fā)生作用的兄春?本質(zhì)上Vue就是一個(gè)構(gòu)造函數(shù),所以我們通過new Vue來探究父叙,從這里也可以看出神郊,我們用Vue框架搭建的項(xiàng)目,本質(zhì)上是一個(gè)Vue實(shí)例趾唱。

new Vue

我們引入vue涌乳,然后new Vue創(chuàng)建應(yīng)用,那么甜癞,要探究發(fā)生了什么夕晓,我們要先找到Vue構(gòu)造函數(shù)定義的地方,上一篇我們知道豐富Vue的文件有4個(gè)悠咱,它正是在文件src\core\instance\index.js定義的蒸辆。

如果你還無法確定征炼,可以通過debugger來查看,像這樣:


debugger

一般我們像這樣創(chuàng)建vue實(shí)例,new Vue時(shí)傳入不同的選項(xiàng)躬贡,如el, data, mounted等谆奥,我們的思路之一可以是Vue都是怎么處理我們傳入的這些選項(xiàng)的。

var vm = new Vue({
            el: '#el',
            data: {
              ...
            },
            mounted() {
                ...
            },
            methods: {
               ....
            }
        .......
        })

接下來我們看Vue構(gòu)造函數(shù)

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

非常簡(jiǎn)單是不是拂玻,只要符合條件酸些,就執(zhí)行_init方法,還記得這個(gè)_init文件怎么來的嗎?上一篇我們已經(jīng)講過檐蚜,通過執(zhí)行initMixin(Vue)魄懂,接下來我們看看_init發(fā)生了什么?

this._init(options)

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
// 設(shè)置uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    // 性能相關(guān)的操作
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag) // 這里調(diào)用window.performance.mark:從navigetionStart事件發(fā)生時(shí)刻到記錄時(shí)刻間隔的毫秒數(shù)
    }

    // a flag to avoid this being observed
   // 添加_isVue標(biāo)記
    vm._isVue = true
    // merge options
    if (options && options._isComponent) {
      // 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 {
    // 合并options闯第,設(shè)置$options
      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
   // _self執(zhí)行自身
    vm._self = vm
    // 初始化生命周期
    initLifecycle(vm)
    // 初始化事件中心
    initEvents(vm)
    // 初始化渲染
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // 初始化 data市栗、props、computed咳短、watcher 等等
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    // 調(diào)用鉤子函數(shù)create
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)  // 跟上面的性能操作相對(duì)應(yīng)
    }

    if (vm.$options.el) {
      // 如果存在選擇器填帽,則執(zhí)行掛載方法
      vm.$mount(vm.$options.el)
    }
  }
}

好了,讓我們來總結(jié)如下:


this._init(options)

讓我們來看看用顏色圈出的操作具體是什么意思咙好?

1. vm._uid

2. vm._isVue = true

3. vm.$options

調(diào)用mergeOptions合并構(gòu)造函數(shù)的options盲赊,如Vue.options,返回的結(jié)果賦給vm.$options

4. initProxy(vm)

設(shè)置vm._renderProxy = new Proxy(vm, handlers), vm._renderProxy是干嘛用的敷扫,暫時(shí)不理會(huì)哀蘑。

5. vm._self = vm

6. initLifecycle(vm)

function initLifecycle (vm) {
    var options = vm.$options;

    // locate first non-abstract parent
    var parent = options.parent;
    if (parent && !options.abstract) {
      while (parent.$options.abstract && parent.$parent) {
        parent = parent.$parent;
      }
      parent.$children.push(vm);
    }
    
   // 掛載上它的父節(jié)點(diǎn),子節(jié)點(diǎn)葵第,根節(jié)點(diǎn)
    vm.$parent = parent;
    vm.$root = parent ? parent.$root : vm;
    // 對(duì)以下屬性初始化
    vm.$children = [];
    vm.$refs = {};

    vm._watcher = null;
    vm._inactive = null;
    vm._directInactive = false;
    vm._isMounted = false;
    vm._isDestroyed = false;
    vm._isBeingDestroyed = false;
  }

7. initEvents(vm)

function initEvents (vm) {
   // 初始化vm._events, vm._hasHookEvent
    vm._events = Object.create(null);
    vm._hasHookEvent = false;
    // init parent attached events
    // 如果listeners存在绘迁,則進(jìn)行更新
    var listeners = vm.$options._parentListeners;
    if (listeners) {
      updateComponentListeners(vm, listeners);
    }
  }

其實(shí)它后面是對(duì)新舊listeners進(jìn)行更新,這里暫時(shí)不討論卒密。

8. initRender(vm)

這一步就跟渲染有關(guān)了缀台,例如虛擬DOM,創(chuàng)建元素的方法哮奇,$slots等膛腐,另外,還設(shè)置了$attrs$listeners,并將它們?cè)O(shè)置為響應(yīng)式

function initRender (vm) {
    vm._vnode = null; // the root of the child tree
    vm._staticTrees = null; // v-once cached trees
    var options = vm.$options;
    var parentVnode = vm.$vnode = options._parentVnode; // the placeholder node in parent tree
    var renderContext = parentVnode && parentVnode.context;
    vm.$slots = resolveSlots(options._renderChildren, renderContext);
    vm.$scopedSlots = emptyObject;
    // bind the createElement fn to this instance
    // so that we get proper render context inside it.
    // args order: tag, data, children, normalizationType, alwaysNormalize
    // internal version is used by render functions compiled from templates
    vm._c = function (a, b, c, d) { return createElement(vm, a, b, c, d, false); };
    // normalization is always applied for the public version, used in
    // user-written render functions.
    vm.$createElement = function (a, b, c, d) { return createElement(vm, a, b, c, d, true); };

    // $attrs & $listeners are exposed for easier HOC creation.
    // they need to be reactive so that HOCs using them are always updated
    var parentData = parentVnode && parentVnode.data;

    /* istanbul ignore else */
    {
      defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, function () {
        !isUpdatingChildComponent && warn("$attrs is readonly.", vm);
      }, true);
      defineReactive(vm, '$listeners', options._parentListeners || emptyObject, function () {
        !isUpdatingChildComponent && warn("$listeners is readonly.", vm);
      }, true);
    }
  }

9. callHook(vm, 'beforeCreate')

調(diào)用生命周期方法beforeCreate,在這里可以看到beforeCreate之前都做了哪些操作

10. initInjections(vm)

function initInjections (vm) {
    var result = resolveInject(vm.$options.inject, vm);
    if (result) {
      toggleObserving(false);
      Object.keys(result).forEach(function (key) {
        /* istanbul ignore else */
        {
          defineReactive(vm, key, result[key], function () {
            warn(
              "Avoid mutating an injected value directly since the changes will be " +
              "overwritten whenever the provided component re-renders. " +
              "injection being mutated: \"" + key + "\"",
              vm
            );
          });
        }
      });
      toggleObserving(true);
    }
  }

這里對(duì)我們傳入的選項(xiàng)inject進(jìn)行處理鼎俘。

11. initState(vm)

function initState (vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if (opts.props) { initProps(vm, opts.props); }
    if (opts.methods) { initMethods(vm, opts.methods);  // 使用bind方法把methods里面的方法和vm綁定在一起哲身,于是vm上有了這個(gè)方法,我們就可以用this.xxx()調(diào)用}
    if (opts.data) {
      initData(vm); // 1. 將data里面的數(shù)據(jù)定義在vm上贸伐,于是我們可以使用this.xxx訪問屬性勘天;2. observe(data, true)將遞歸遍歷data里面的轉(zhuǎn)為可響應(yīng)式數(shù)據(jù),遞歸遍歷到的數(shù)據(jù)如果是對(duì)象或數(shù)組,會(huì)被打上_ob_標(biāo)記
    } else {
      // 這里將數(shù)據(jù)可響應(yīng)化
      observe(vm._data = {}, true /* asRootData */);
    }
    if (opts.computed) { initComputed(vm, opts.computed); }
    if (opts.watch && opts.watch !== nativeWatch) {
      initWatch(vm, opts.watch);
    }
  }

從代碼可知脯丝,這一步初始化vm.watchers = []商膊,初始化選項(xiàng)computed,watch宠进,props,methods, data并將data里面的數(shù)據(jù)轉(zhuǎn)為響應(yīng)式的晕拆,主要處理數(shù)據(jù)相關(guān)的

12. initProvide(vm)

function initProvide (vm) {
    var provide = vm.$options.provide;
    if (provide) {
      vm._provided = typeof provide === 'function'
        ? provide.call(vm)
        : provide;
    }
  }

非常明顯,處理我們傳入的provide選項(xiàng)材蹬。

13. callHook(vm, 'created')

調(diào)用生命周期鉤子函數(shù)created, 說明這時(shí)已經(jīng)初始化好數(shù)據(jù)潦匈,但是并沒有把模板正在掛載在元素上,下一步$mount才開始掛載赚导。

14. vm.$mount(vm.$options.el)

如果我們有傳入el選項(xiàng),則會(huì)Vue會(huì)自動(dòng)幫我們調(diào)用$mount方法赤惊,否則需要我們手動(dòng)去調(diào)用vm.$mount()吼旧。

var mount = Vue.prototype.$mount;
  Vue.prototype.$mount = function (
    el,
    hydrating
  ) {
    el = el && query(el);

    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
       warn(
        "Do not mount Vue to <html> or <body> - mount to normal elements instead."
      );
      return this
    }

    var options = this.$options;
    // resolve template/el and convert to render function
    if (!options.render) {
      var template = options.template;
      if (template) {
        if (typeof template === 'string') {
          if (template.charAt(0) === '#') {
            template = idToTemplate(template);
            /* istanbul ignore if */
            if ( !template) {
              warn(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) {
          template = template.innerHTML;
        } else {
          {
            warn('invalid template option:' + template, this);
          }
          return this
        }
      } else if (el) {
        template = getOuterHTML(el);
      }
      if (template) {
        /* istanbul ignore if */
        if ( config.performance && mark) {
          mark('compile');
        }
        // 將模板轉(zhuǎn)render函數(shù)
        var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines,
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        }, this);
        var render = ref.render;
        var staticRenderFns = ref.staticRenderFns;
        options.render = render;
        options.staticRenderFns = staticRenderFns;

        /* istanbul ignore if */
        if ( config.performance && mark) {
          mark('compile end');
          measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
        }
      }
    }
    return mount.call(this, el, hydrating)
  };

很明顯,這里重寫了$mount未舟,這個(gè)在上一篇中我們已經(jīng)提及到了圈暗,具體$mount做了什么?由于是重點(diǎn)內(nèi)容裕膀,我們將在下一篇繼續(xù)探討员串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市昼扛,隨后出現(xiàn)的幾起案子寸齐,更是在濱河造成了極大的恐慌,老刑警劉巖抄谐,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渺鹦,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蛹含,警方通過查閱死者的電腦和手機(jī)毅厚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浦箱,“玉大人吸耿,你說我怎么就攤上這事】峥” “怎么了咽安?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蓬推。 經(jīng)常有香客問我板乙,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任募逞,我火速辦了婚禮蛋铆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘放接。我一直安慰自己刺啦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布纠脾。 她就那樣靜靜地躺著玛瘸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苟蹈。 梳的紋絲不亂的頭發(fā)上糊渊,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音慧脱,去河邊找鬼渺绒。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菱鸥,可吹牛的內(nèi)容都是我干的宗兼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼氮采,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼殷绍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鹊漠,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤主到,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后躯概,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镰烧,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年楞陷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了怔鳖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡固蛾,死狀恐怖结执,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艾凯,我是刑警寧澤献幔,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站趾诗,受9級(jí)特大地震影響蜡感,放射性物質(zhì)發(fā)生泄漏蹬蚁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一郑兴、第九天 我趴在偏房一處隱蔽的房頂上張望犀斋。 院中可真熱鬧,春花似錦情连、人聲如沸叽粹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虫几。三九已至,卻和暖如春挽拔,著一層夾襖步出監(jiān)牢的瞬間辆脸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工螃诅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啡氢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓州刽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親浪箭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子穗椅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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