史上最全的vue.js源碼解析(一)

雖然vue3已經(jīng)出來很久了,但我覺得vue.js的源碼還是非常值得去學(xué)習(xí)一下。vue.js里面封裝的很多工具類在我們平時(shí)工作項(xiàng)目中也會(huì)經(jīng)常用到。所以我近期會(huì)對(duì)vue.js的源碼進(jìn)行解讀,分享值得去學(xué)習(xí)的代碼片段,這篇文章將會(huì)持續(xù)更新。

一、哪些值得我們學(xué)習(xí)的地方:

1.代碼嚴(yán)謹(jǐn),做了很多值和類型判斷的工具類.例如判斷已定義、未定義、判斷原始類型、判斷對(duì)象等等。
2.源碼中多處使用到call,bind,apply,所以學(xué)習(xí)call,apply,bind很重要,請(qǐng)閱讀我的另一篇文章可進(jìn)行學(xué)習(xí):(http://www.lxweimin.com/p/0e145af54212)

  1. Object.freeze凍結(jié)對(duì)象,可以提高性能。
    4. 高階函數(shù):(很多大廠面試可能會(huì)問到)

①cached函數(shù) 創(chuàng)建純函數(shù)的緩存版本。
高階函數(shù)cached函數(shù),輸入?yún)?shù)為函數(shù),返回值為函數(shù)。利用了閉包變量不會(huì)被回收的特點(diǎn),
可以緩存變量,下次再調(diào)用的時(shí)候可以從緩存中讀取,如果存在緩存就使用緩存,如果不存在就重新計(jì)
算下

②looseEqual

高級(jí)函數(shù) 對(duì)對(duì)象的淺相等進(jìn)行判斷 ES6有一個(gè)方法來判斷兩個(gè)對(duì)象是否相等 Object.is()
這個(gè)方法判斷的是a和b是不是同一個(gè)指針的對(duì)象
判斷a、b兩個(gè)集合是否相等,如果a包含于b,且b包含于a,則 A = B 判斷兩個(gè)對(duì)象相等 (判斷兩個(gè)對(duì)象鍵名與鍵值對(duì)應(yīng)相同 而非指引用地址相同)

③polyfillBind

高級(jí)函數(shù),簡單綁定polyfill,用于不支持它的環(huán)境,例如PhantomJS 1.x;
Polyfill是一個(gè)js庫,主要撫平不同瀏覽器之間對(duì)js實(shí)現(xiàn)的差異

④passive

passived主要用于優(yōu)化瀏覽器頁面滾動(dòng)的性能,Chrome提出的一個(gè)新的瀏覽器特性:Web開發(fā)者通過一個(gè)新的屬性passive來告訴瀏覽器,
當(dāng)前頁面內(nèi)注冊(cè)的事件監(jiān)聽器內(nèi)部是否會(huì)調(diào)用preventDefault函數(shù)來阻止事件的默認(rèn)行為,
以便瀏覽器根據(jù)這個(gè)信息更好地做出決策來優(yōu)化頁面性能。當(dāng)屬性passive的值為true的時(shí)候,
代表該監(jiān)聽器內(nèi)部不會(huì)調(diào)用preventDefault函數(shù)來阻止默認(rèn)滑動(dòng)行為,Chrome瀏覽器稱這類型的監(jiān)聽器為被動(dòng)
(passive)監(jiān)聽器。目前Chrome主要利用該特性來優(yōu)化頁面的滑動(dòng)性能,所以Passive Event Listeners特性
當(dāng)前僅支持mousewheel/touch相關(guān)事件
2.Passive Event Listeners特性是為了提高頁面的滑動(dòng)流暢度而設(shè)計(jì)的,頁面滑動(dòng)流暢度的提升, 直接影響到用戶對(duì)這個(gè)頁面最直觀的感受。
3.passive的簡單實(shí)現(xiàn) function handler(event) {
console.log(event.type); } document.addEventListener("mousewheel", handler, {passive:true});

二、vue核心

①數(shù)據(jù)監(jiān)聽最重要之一的 Dep

dep是一個(gè)可觀察的,可以有多個(gè)訂閱它的指令 ,是訂閱者Watcher對(duì)應(yīng)的數(shù)據(jù)依賴 , Dep 相當(dāng)于把
Observe 監(jiān)聽到的信號(hào)做一個(gè)收集 ,然后通過dep.notify()再通知到對(duì)應(yīng) Watcher ,從而進(jìn)行視圖更新。
在vue中我們要實(shí)現(xiàn)Dep和watcher Dep的主要作用是收集依賴 在vue中的每一個(gè)響應(yīng)屬性,都會(huì)創(chuàng)建一個(gè)dep對(duì)象負(fù)責(zé)手機(jī)依賴于該屬性的所有依賴 即訂閱者 并在數(shù)據(jù)更新時(shí)候發(fā)布通知,調(diào)用watcher對(duì)象中的update方法去更新視圖簡單說明就是在數(shù)據(jù)劫持監(jiān)聽中的get去添加依賴 在set中去發(fā)布通知

②視圖更新最重要的 VNode

VNode是基于面向?qū)ο筮M(jìn)行設(shè)計(jì)的 將template模板描述成 VNode,然后一系列操作之后通過VNode形成真實(shí)DOM進(jìn)行掛載
更新的時(shí)候?qū)Ρ扰f的VNode和新的VNode,只更新有變化的那一部分,提高視圖更新速度

④雙向綁定Observer:遍歷對(duì)象的所有屬性將其進(jìn)行雙向綁定

Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定。如果是對(duì)象則進(jìn)行深度遍歷,為每一個(gè)子對(duì)象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法。
如果是修改一個(gè)數(shù)組的成員,該成員是一個(gè)對(duì)象,那只需要遞歸對(duì)數(shù)組的成員進(jìn)行雙向綁定即可。
監(jiān)聽數(shù)組:從數(shù)組的原型新建一個(gè)Object.create(arrayProto)對(duì)象,通過修改此原型可以保證原生數(shù)組方法不被污染。如果當(dāng)前瀏覽器支持proto這個(gè)屬性的話就可以直接覆蓋該屬性則使數(shù)組對(duì)象具有了重寫后的數(shù)組方法。如果沒有該屬性的瀏覽器,則必須通過遍歷def所有需要重寫的數(shù)組方法

⑤派發(fā)更新:defineReactive 函數(shù)(響應(yīng)式的核心函數(shù))

defineReactive 函數(shù)最開始初始化 Dep 對(duì)象的實(shí)例,接著拿到 obj 的屬性描述符, 然后對(duì)子對(duì)象遞歸調(diào)用 observe
方法,這樣就保證了無論 obj 的結(jié)構(gòu)多復(fù)雜, 它的所有子屬性也能變成響應(yīng)式的對(duì)象,這樣我們?cè)L問或修改 obj 中一個(gè)嵌套較深的屬性,
也能觸發(fā) getter 和 setter。最后利用 Object.defineProperty 去給 obj 的屬性 key 添加
getter 和 setter。 核心就是利用 Object.defineProperty 給數(shù)據(jù)添加了 getter 和
setter,目的就是 為了在我們?cè)L問數(shù)據(jù)以及寫數(shù)據(jù)的時(shí)候能自動(dòng)執(zhí)行一些邏輯:getter 做的事情是依賴收集,setter
做的事情是派發(fā)更新

三、1~1200行的代碼解讀:

//AMD規(guī)范和commonJS規(guī)范,都是為了模塊化
//AMD規(guī)范則是非同步加載模塊,允許指定回調(diào)函數(shù)。
//CommonJS規(guī)范加載模塊是同步的,也就是說,只有加載完成,才能執(zhí)行后面的操作。
//是個(gè)匿名函數(shù),該匿名函數(shù)并沒自執(zhí)行 設(shè)計(jì)參數(shù) window,并傳入window對(duì)象。不污染全局變量,也不會(huì)被別的代碼污染
(function (global, factory) {
//檢查 CommonJS,CommonJS模塊作用域,每一個(gè)文件就是一個(gè)模塊,擁有自己獨(dú)立的作用域,變量,以及方法等,對(duì)其他的模塊都不可見,
//所有代碼都運(yùn)行在模塊作用域,不會(huì)污染全局作用域,
//模塊可以多次加載,但是只會(huì)在第一次加載時(shí)運(yùn)行一次,然后運(yùn)行結(jié)果就被緩存了,以后再加載,就直接讀取緩存結(jié)果。
//要想讓模塊再次運(yùn)行,必須清除緩存。模塊加載的順序,按照其在代碼中出現(xiàn)的順序
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  //AMD異步模塊定義 檢查JavaScript依賴管理庫 require.js 的存在
  typeof define === 'function' && define.amd ? define(factory) : 
  (global = global || self, global.Vue = factory());
}(this, function () {
   'use strict';

  /*  */
  //凍結(jié)對(duì)象,一個(gè)被凍結(jié)的對(duì)象再也不能被修改。可以使用Object.freeze提升性能
  var emptyObject = Object.freeze({});

//判斷未定義
  function isUndef (v) {
    return v === undefined || v === null
  }
//判斷已定義
  function isDef (v) {
    return v !== undefined && v !== null
  }

  function isTrue (v) {
    return v === true
  }

  function isFalse (v) {
    return v === false
  }

  /**
   *  判斷為原始類型
   */
  function isPrimitive (value) {
    return (
      typeof value === 'string' ||
      typeof value === 'number' ||
      // $flow-disable-line
      typeof value === 'symbol' ||
      typeof value === 'boolean'
    )
  }

  // 判斷為對(duì)象
  function isObject (obj) {
    return obj !== null && typeof obj === 'object'
  }

  /**
   * 獲取值的原始類型字符串,例如,[對(duì)象]
   */
  var _toString = Object.prototype.toString;
// 切割引用類型得到后面的基本類型,例如:[object RegExp] 得到的就是 RegExp
  function toRawType (value) {
    return _toString.call(value).slice(8, -1)
  }

   //判斷純粹的對(duì)象:"純粹的對(duì)象",就是通過 {}、new Object()、Object.create(null) 創(chuàng)建的對(duì)象
  function isPlainObject (obj) {
    return _toString.call(obj) === '[object Object]'
  }
  // 判斷原生引用類型
  function isRegExp (v) {
    return _toString.call(v) === '[object RegExp]'
  }

  /**
   * 檢查val是否是有效的數(shù)組索引,驗(yàn)證是否是一個(gè)非無窮大的正整數(shù)。
   */
  function isValidArrayIndex (val) {
    var n = parseFloat(String(val));
    return n >= 0 && Math.floor(n) === n && isFinite(val)
  }
  // 判斷是否是Promise
  function isPromise (val) {
    return (
      isDef(val) &&
      typeof val.then === 'function' &&
      typeof val.catch === 'function'
    )
  }

  /**
   * 將值轉(zhuǎn)換為字符串。
   */
  function toString (val) {
    return val == null
      ? ''
      : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
        ? JSON.stringify(val, null, 2)
        : String(val) 
  }

  /**
  將輸入值轉(zhuǎn)換為數(shù)字以便持久化。如果轉(zhuǎn)換失敗,則返回原始字符串。
   */
  function toNumber (val) {
    var n = parseFloat(val);
    return isNaN(n) ? val : n
  }

  /**
   *  makeMap 方法將字符串切割,放到map中,用于校驗(yàn)其中的某個(gè)字符串是否存在(區(qū)分大小寫)于map中
   */
  function makeMap (
    str,
    expectsLowerCase
  ) {
    var map = Object.create(null);
    var list = str.split(',');
    for (var i = 0; i < list.length; i++) {
      map[list[i]] = true;
    }
    return expectsLowerCase
      ? function (val) { return map[val.toLowerCase()]; }
      : function (val) { return map[val]; }
  }

  /**
   * 檢查標(biāo)記是否為內(nèi)置標(biāo)記。
   */
  var isBuiltInTag = makeMap('slot,component', true);

  /**
   * 檢查屬性是否為保留屬性。
   */
  var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');

  /**
   * 從數(shù)組中刪除項(xiàng)
   */
  function remove (arr, item) {
    if (arr.length) {
      var index = arr.indexOf(item);
      if (index > -1) {
        return arr.splice(index, 1)
      }
    }
  }

  /**
   * 檢查對(duì)象是否具有該屬性。
   * hasOwnProperty() 方法會(huì)返回一個(gè)布爾值,指示對(duì)象自身屬性中是否具有指定的屬性(也就是,是否有指定的鍵)。
   */
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn (obj, key) {
    return hasOwnProperty.call(obj, key)
  }

  /**
   * 創(chuàng)建純函數(shù)的緩存版本。
   * 高階函數(shù)cached函數(shù),輸入?yún)?shù)為函數(shù),返回值為函數(shù)。利用了閉包變量不會(huì)被回收的特點(diǎn),
   * 可以緩存變量,下次再調(diào)用的時(shí)候可以從緩存中讀取,如果存在緩存就使用緩存,如果不存在就重新計(jì)算下
   */
  function cached (fn) {
    var cache = Object.create(null);
    return (function cachedFn (str) {
      var hit = cache[str];
      return hit || (cache[str] = fn(str))
    })
  }

  /**
   * 駝峰化一個(gè)連字符連接的字符串
   */
  var camelizeRE = /-(\w)/g;
  var camelize = cached(function (str) {
    return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
  });

  /**
   * 將字符串首字母大寫。
   */
  var capitalize = cached(function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
  });

  /**
   * 用字符號(hào)連接一個(gè)駝峰的字符串
   */
  var hyphenateRE = /\B([A-Z])/g;
  var hyphenate = cached(function (str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
  });

  /**
   * 高級(jí)函數(shù),簡單綁定polyfill,用于不支持它的環(huán)境,例如PhantomJS 1.x;
   * Polyfill是一個(gè)js庫,主要撫平不同瀏覽器之間對(duì)js實(shí)現(xiàn)的差異
   */

  /* istanbul ignore next */
  function polyfillBind (fn, ctx) {
    function boundFn (a) {
      var l = arguments.length;
      return l
        ? l > 1
          ? fn.apply(ctx, arguments)
          : fn.call(ctx, a)
        : fn.call(ctx)
    }

    boundFn._length = fn.length;
    return boundFn
  }

  function nativeBind (fn, ctx) {
    return fn.bind(ctx)
  }
  Function.prototype.bind()
// bind() 方法創(chuàng)建一個(gè)新的函數(shù),在 bind() 被調(diào)用時(shí),這個(gè)新函數(shù)的 this 被指定為 bind() 的第一個(gè)參數(shù),
// 而其余參數(shù)將作為新函數(shù)的參數(shù),供調(diào)用時(shí)使用。
  var bind = Function.prototype.bind
    ? nativeBind
    : polyfillBind;

  /**
   * 將類似數(shù)組的對(duì)象轉(zhuǎn)換為實(shí)數(shù)組
   */
  function toArray (list, start) {
    start = start || 0;
    var i = list.length - start;
    var ret = new Array(i);
    while (i--) {
      ret[i] = list[i + start];
    }
    return ret
  }

  /**
   *  將多個(gè)屬性插入目標(biāo)的對(duì)象
   */
  function extend (to, _from) {
    for (var key in _from) {
      to[key] = _from[key];
    }
    return to
  }

  /**
   * 將對(duì)象數(shù)組合并為單個(gè)對(duì)象。
   */
  function toObject (arr) {
    var res = {};
    for (var i = 0; i < arr.length; i++) {
      if (arr[i]) {
        extend(res, arr[i]);
      }
    }
    return res
  }

  /* eslint-disable no-unused-vars */

  /**
   * Perform no operation.
   * Stubbing args to make Flow happy without leaving useless transpiled code
   * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/).
   */
  function noop (a, b, c) {}

  /**
   * 總是返回false。
   */
  var no = function (a, b, c) { return false; };

  /* eslint-enable no-unused-vars */

  /**
   * 返回相同的值
   */
  var identity = function (_) { return _; };

  /**
   * 從編譯器模塊生成包含靜態(tài)鍵的字符串。
   */
  function genStaticKeys (modules) {
    return modules.reduce(function (keys, m) {
      return keys.concat(m.staticKeys || [])
    }, []).join(',')
  }

  /**高級(jí)函數(shù) 對(duì)對(duì)象的淺相等進(jìn)行判斷
   * ES6有一個(gè)方法來判斷兩個(gè)對(duì)象是否相等  Object.is()  這個(gè)方法判斷的是a和b是不是同一個(gè)指針的對(duì)象
   * 判斷a、b兩個(gè)集合是否相等,如果a包含于b,且b包含于a,則 A = B
   *判斷兩個(gè)對(duì)象相等 (判斷兩個(gè)對(duì)象鍵名與鍵值對(duì)應(yīng)相同 而非指引用地址相同)
   */
  function looseEqual (a, b) {
    //判斷是否恒相等
    if (a === b) { return true }
    //判斷是否為對(duì)象
    var isObjectA = isObject(a);
    var isObjectB = isObject(b);
    if (isObjectA && isObjectB) {
      try {
        // 當(dāng)a,b都是數(shù)組時(shí)
        var isArrayA = Array.isArray(a);
        var isArrayB = Array.isArray(b);
        if (isArrayA && isArrayB) {
          //遞歸判斷兩個(gè)數(shù)組中的每一項(xiàng)
          return a.length === b.length && a.every(function (e, i) {
            return looseEqual(e, b[i])
          })
          // 否則判斷a,b是否為Date類型,
        } else if (a instanceof Date && b instanceof Date) {
          //使a和b恒相等
          return a.getTime() === b.getTime()
          //當(dāng)a,b是對(duì)象時(shí),首先判斷l(xiāng)ength長度是否相同,長度相同再判斷每個(gè)屬性對(duì)應(yīng)的屬于值是否相同
        } else if (!isArrayA && !isArrayB) {
          var keysA = Object.keys(a);
          var keysB = Object.keys(b);
          return keysA.length === keysB.length && keysA.every(function (key) {
            return looseEqual(a[key], b[key])
          })
        } else {
          return false
        }
      } catch (e) {
        return false
      }
    } else if (!isObjectA && !isObjectB) {
      return String(a) === String(b)
    } else {
      return false
    }
  }

  /**
   * 返回索引,如果沒找到返回-1,否則執(zhí)行l(wèi)ooseEqual()
   */
  function looseIndexOf (arr, val) {
    for (var i = 0; i < arr.length; i++) {
      if (looseEqual(arr[i], val)) { return i }
    }
    return -1
  }

  /**
   * 確保函數(shù)只調(diào)用一次。
   */
  function once (fn) {
    var called = false;
    return function () {
      if (!called) {
        called = true;
        fn.apply(this, arguments);
      }
    }
  }

//   cached
// polyfillBind
// looseEqual
//閉包,類型判斷,函數(shù)之間的相互調(diào)用調(diào)用



//定義變量
  var SSR_ATTR = 'data-server-rendered';// 服務(wù)端渲染
  // 全局函數(shù) component、directive、filter
  var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
  ];
  // 生命周期
  var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    'serverPrefetch'
  ];

  /*  */


  // 全局配置
  var config = ({
    /**
     * 選項(xiàng)合并策略(用于core/util/options)
     */
    // $flow-disable-line
    optionMergeStrategies: Object.create(null),

    /**
     * 是否抑制警告
     */
    silent: false,

    /**
     * 啟動(dòng)時(shí)顯示生產(chǎn)模式提示消息?
     */
    productionTip: "development" !== 'production',

    /**
     * 是否啟用devtools
     */
    devtools: "development" !== 'production',

    /**
     * 是否記錄性能
     */
    performance: false,

    /**
     * 觀察程序錯(cuò)誤的錯(cuò)誤處理程序
     */
    errorHandler: null,

    /**
     * 觀察者警告的警告處理程序
     */
    warnHandler: null,

    /**
     * 忽略某些自定義元素
     */
    ignoredElements: [],

    /**
     * v-on的自定義用戶密鑰別名
     */
    // $flow-disable-line
    keyCodes: Object.create(null),

    /**
     * 檢查標(biāo)記是否已保留,以便無法將其注冊(cè)為組件。這取決于平臺(tái),可能會(huì)被覆蓋。
     */
    isReservedTag: no,

    /**
     檢查屬性是否已保留,以使其不能用作組件屬性。這取決于平臺(tái),可能會(huì)被覆蓋。
     */
    isReservedAttr: no,

    /**
     * 檢查標(biāo)記是否為未知元素。依賴于平臺(tái)。
     */
    isUnknownElement: no,

    /**
     * 獲取元素的命名空間
     */
    getTagNamespace: noop,

    /**
     * 解析特定平臺(tái)的真實(shí)標(biāo)記名。
     */
    parsePlatformTagName: identity,

    /**
     * 檢查屬性是否必須使用屬性綁定,例如valuePlatform dependent。
     */
    mustUseProp: no,

    /**
     * 異步執(zhí)行更新。打算由Vue Test Utils使用,如果設(shè)置為false,這將顯著降低性能。
     */
    async: true,

    /**
     * 因遺留原因暴露
     */
    _lifecycleHooks: LIFECYCLE_HOOKS
  });

  /*  */

  /**
  用于解析html標(biāo)記、組件名稱和屬性路徑的unicode字母。
  使用https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname 
   */
  var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/;

  /**
   * 檢查字符串是否以$或_開頭
   */
  function isReserved (str) {
    var c = (str + '').charCodeAt(0);
    return c === 0x24 || c === 0x5F
  }

  /**
   * 定義屬性。
   * 在一個(gè)對(duì)象上定義一個(gè)屬性的構(gòu)造函數(shù),其中 !!enumerable 強(qiáng)制轉(zhuǎn)換 boolean
   */
  function def (obj, key, val, enumerable) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true
    });
  }

  /**
   * 解析簡單路徑。
   */
  var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]"));
  function parsePath (path) {
    if (bailRE.test(path)) {
      return
    }
    var segments = path.split('.');
    return function (obj) {
      for (var i = 0; i < segments.length; i++) {
        if (!obj) { return }
        obj = obj[segments[i]];
      }
      return obj
    }
  }

  /*  */

  var hasProto = '__proto__' in {};

  // 判斷瀏覽器環(huán)境
  var inBrowser = typeof window !== 'undefined';
  // 運(yùn)行環(huán)境是微信
  var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
  var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
  //瀏覽器 UA 判斷
  var UA = inBrowser && window.navigator.userAgent.toLowerCase();
  // IE的內(nèi)核是trident
  var isIE = UA && /msie|trident/.test(UA);
  var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
  var isEdge = UA && UA.indexOf('edge/') > 0;
  // 判斷 android ios
  var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
  var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
  // 判斷chrome
  var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge;
  var isPhantomJS = UA && /phantomjs/.test(UA);
  var isFF = UA && UA.match(/firefox\/(\d+)/);

  // Firefox has a "watch" function on Object.prototype...
  var nativeWatch = ({}).watch;

/*https://blog.csdn.net/dj0379/article/details/52883315
  1.passive是什么?passived主要用于優(yōu)化瀏覽器頁面滾動(dòng)的性能,Chrome提出的一個(gè)新的瀏覽器特性:Web開發(fā)者通過一個(gè)新的屬性passive來告訴瀏覽器,
  當(dāng)前頁面內(nèi)注冊(cè)的事件監(jiān)聽器內(nèi)部是否會(huì)調(diào)用preventDefault函數(shù)來阻止事件的默認(rèn)行為,
  以便瀏覽器根據(jù)這個(gè)信息更好地做出決策來優(yōu)化頁面性能。當(dāng)屬性passive的值為true的時(shí)候,
  代表該監(jiān)聽器內(nèi)部不會(huì)調(diào)用preventDefault函數(shù)來阻止默認(rèn)滑動(dòng)行為,Chrome瀏覽器稱這類型的監(jiān)聽器為被動(dòng)
  (passive)監(jiān)聽器。目前Chrome主要利用該特性來優(yōu)化頁面的滑動(dòng)性能,所以Passive Event Listeners特性
  當(dāng)前僅支持mousewheel/touch相關(guān)事件
  2.Passive Event Listeners特性是為了提高頁面的滑動(dòng)流暢度而設(shè)計(jì)的,頁面滑動(dòng)流暢度的提升,
  直接影響到用戶對(duì)這個(gè)頁面最直觀的感受。
  3.passive的簡單實(shí)現(xiàn)
  function handler(event) {
    console.log(event.type);
  }
  document.addEventListener("mousewheel", handler, {passive:true});
*/


  var supportsPassive = false;
  if (inBrowser) {
    try {
      var opts = {};
      Object.defineProperty(opts, 'passive', ({
        get: function get () {
          /* istanbul ignore next */
          supportsPassive = true;
        }
      })); // https://github.com/facebook/flow/issues/285
      window.addEventListener('test-passive', null, opts);
    } catch (e) {}
  }

  // 視圖服務(wù)器渲染器可以設(shè)置視圖環(huán)境
  var _isServer;
  var isServerRendering = function () {
    if (_isServer === undefined) {
      if (!inBrowser && !inWeex && typeof global !== 'undefined') {
        // 檢測(cè)是否存在vue服務(wù)器呈現(xiàn)程序并避免網(wǎng)頁包填充進(jìn)程
        _isServer = global['process'] && global['process'].env.VUE_ENV === 'server';
      } else {
        _isServer = false;
      }
    }
    return _isServer
  };

  var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;

  // 判斷一個(gè)函數(shù)是否為 JavaScript 內(nèi)置方法的方法
  function isNative (Ctor) {
    return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
  }
  // 判斷是否含有 Symbol 類型
  var hasSymbol =
    typeof Symbol !== 'undefined' && isNative(Symbol) &&
    typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);

  var _Set;
  //判斷Set是否為內(nèi)置對(duì)象
  if (typeof Set !== 'undefined' && isNative(Set)) {
    // use native Set when available.
    _Set = Set;
  } else {
    // a non-standard Set polyfill that only works with primitive keys.
    _Set = /*@__PURE__*/(function () {
      function Set () {
        this.set = Object.create(null);
      }
      // Set.prototype.has()方法接受一個(gè)參數(shù),返回一個(gè)布爾值,表明該參數(shù)是否存在于Set對(duì)象中
      Set.prototype.has = function has (key) {
        return this.set[key] === true
      };
      // Set.prototype.add()方法用于向Set對(duì)象末尾添加一個(gè)指定的值,并返回Set對(duì)象本身。如果值已存在,則添加不會(huì)成功
      Set.prototype.add = function add (key) {
        this.set[key] = true;
      };
      // 用來清空一個(gè)Set對(duì)象的所有元素,沒有返回值
      Set.prototype.clear = function clear () {
        this.set = Object.create(null);
      };

      return Set;
    }());
  }

  /* 日志信息模塊 */

  var warn = noop;
  var tip = noop;
  //generateComponentTrace這個(gè)function,這個(gè)方法初始化的值也是noop,什么都沒有做,目的是解決流量檢查問題
  var generateComponentTrace = (noop);
  var formatComponentName = (noop);
  {
    var hasConsole = typeof console !== 'undefined';
    // 這個(gè)正則就是把連接符轉(zhuǎn)換成的駝峰寫法, 并且第一個(gè)字符大寫^|[-_]的意思是字符串的開頭, 或者 -_ 后面的一個(gè)字符
    var classifyRE = /(?:^|[-_])(\w)/g;
    var classify = function (str) { return str
      .replace(classifyRE, function (c) { return c.toUpperCase(); })
      .replace(/[-_]/g, ''); };
    //控制臺(tái)打印錯(cuò)誤提示
    warn = function (msg, vm) {
      var trace = vm ? generateComponentTrace(vm) : '';
      //如果配置的console.silent, 就不會(huì)打印錯(cuò)誤日志
      if (config.warnHandler) {
        config.warnHandler.call(null, msg, vm, trace);
      } else if (hasConsole && (!config.silent)) {
        console.error(("[Vue warn]: " + msg + trace));
      }
    };
    //控制臺(tái)打警告提示
    tip = function (msg, vm) {
      if (hasConsole && (!config.silent)) {
        console.warn("[Vue tip]: " + msg + (
          vm ? generateComponentTrace(vm) : ''
        ));
      }
    };
    //格式化組件名稱
    formatComponentName = function (vm, includeFile) {
      //如果是根組件, 它會(huì)有一個(gè)屬性.$root 指向它自己
      if (vm.$root === vm) {
        return '<Root>'
      }
      // 先判斷option有沒自定義name,如果沒有就用vm.name, 這個(gè)name應(yīng)該是vue自己配置的一個(gè)隨機(jī)數(shù)
      var options = typeof vm === 'function' && vm.cid != null
        ? vm.options
        : vm._isVue
          ? vm.$options || vm.constructor.options
          : vm;
      var name = options.name || options._componentTag;
      var file = options.__file;
      if (!name && file) {
        //如果沒有name,但存在此文件,則取文件名
        var match = file.match(/([^/\\]+)\.vue$/);
        name = match && match[1];
      }

      return (
        //返回駝峰組件名稱
        (name ? ("<" + (classify(name)) + ">") : "<Anonymous>") +
        //提示文件路徑出錯(cuò)
        (file && includeFile !== false ? (" at " + file) : '')
      )
    };
    //返回一個(gè)str循環(huán)n次的字符串,例如str="a",n=5,則返回"aaaaa"
    var repeat = function (str, n) {
      var res = '';
      while (n) {
        if (n % 2 === 1) { res += str; }
        if (n > 1) { str += str; }
        n >>= 1;
      }
      return res
    };
      //生成組件跟蹤路徑(組件數(shù)規(guī)則)
    generateComponentTrace = function (vm) {
      if (vm._isVue && vm.$parent) {
        var tree = [];
        var currentRecursiveSequence = 0;
        while (vm) {
          if (tree.length > 0) {
            var last = tree[tree.length - 1];
            if (last.constructor === vm.constructor) {
              currentRecursiveSequence++;
              vm = vm.$parent;
              continue
            } else if (currentRecursiveSequence > 0) {
              tree[tree.length - 1] = [last, currentRecursiveSequence];
              currentRecursiveSequence = 0;
            }
          }
          tree.push(vm);
          vm = vm.$parent;
        }
        return '\n\nfound in\n\n' + tree
          .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm)
              ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)")
              : formatComponentName(vm))); })
          .join('\n')
      } else {
        return ("\n\n(found in " + (formatComponentName(vm)) + ")")
      }
    };
  }
  var uid = 0;
  // Vue核心:數(shù)據(jù)監(jiān)聽最重要之一的 Dep
  // dep是一個(gè)可觀察的,可以有多個(gè)訂閱它的指令
  // Dep是訂閱者Watcher對(duì)應(yīng)的數(shù)據(jù)依賴
  // Dep 相當(dāng)于把 Observe 監(jiān)聽到的信號(hào)做一個(gè)收集
  // 然后通過dep.notify()再通知到對(duì)應(yīng) Watcher ,從而進(jìn)行視圖更新。
  // 要實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)機(jī)制 即數(shù)據(jù)變化 視圖變化
// 在vue的響應(yīng)機(jī)制中 我們要使用觀察模式來監(jiān)聽數(shù)據(jù)的變化 
// 因此 在vue中我們要實(shí)現(xiàn)Dep和watcher  Dep的主要作用是收集依賴 在vue中的每一個(gè)響應(yīng)屬性 
// 都會(huì)創(chuàng)建一個(gè)dep對(duì)象 負(fù)責(zé)手機(jī)依賴于該屬性的所有依賴 即訂閱者 并在數(shù)據(jù)更新時(shí)候發(fā)布通知 
// 調(diào)用watcher對(duì)象中的update方法去更新視圖 簡單說明就是在數(shù)據(jù)劫持監(jiān)聽中的get去添加依賴 在set中去發(fā)布通知 
  var Dep = function Dep () {
    this.id = uid++;
    this.subs = [];
  };
  //向Dep實(shí)例中的subs數(shù)組中添加監(jiān)視器對(duì)象
  Dep.prototype.addSub = function addSub (sub) {
    this.subs.push(sub);
  };
//刪除Dep實(shí)例中subs數(shù)組中指定的監(jiān)視器對(duì)象
  Dep.prototype.removeSub = function removeSub (sub) {
    remove(this.subs, sub);
  };
//設(shè)置某個(gè)Watcher的依賴
//這里添加了Dep.target是否存在的判斷,目的是判斷是不是Watcher的構(gòu)造函數(shù)調(diào)用
//也就是說判斷他是Watcher的this.get調(diào)用的,而不是普通調(diào)用
//如果target存在就會(huì)繼續(xù)監(jiān)聽
  Dep.prototype.depend = function depend () {
    if (Dep.target) {
      Dep.target.addDep(this);
    }
  };
  //通知監(jiān)聽者
  // 變量Dep的subs數(shù)組中的監(jiān)視器并調(diào)用其的update方法
  Dep.prototype.notify = function notify () {
    var subs = this.subs.slice();
    if (!config.async) {
      // 如果未運(yùn)行異步,則不會(huì)在調(diào)度程序中對(duì)sub進(jìn)行排序
      subs.sort(function (a, b) { return a.id - b.id; });
    }
     //通知所有綁定 Watcher。調(diào)用watcher的update()
    for (var i = 0, l = subs.length; i < l; i++) {
      subs[i].update();
    }
  };

  // 正在評(píng)估的當(dāng)前目標(biāo)觀察程序。
  Dep.target = null;
  var targetStack = [];
  // 如果Dep實(shí)例的target的值為真向targetStack數(shù)組尾部添加此監(jiān)視器,設(shè)置當(dāng)前Dep實(shí)例的target為傳入的監(jiān)視器。
  function pushTarget (target) {
    targetStack.push(target);
    Dep.target = target;
  }
  // 從targetStack數(shù)組中的頭部取出一個(gè)監(jiān)視器對(duì)象賦值給Dep實(shí)例的target屬性。
  function popTarget () {
    targetStack.pop();
    Dep.target = targetStack[targetStack.length - 1];
  }

  // VNode是基于面向?qū)ο筮M(jìn)行設(shè)計(jì)的
  // 視圖更新最重要的VNode
  // 將template模板描述成 VNode,然后一系列操作之后通過VNode形成真實(shí)DOM進(jìn)行掛載
  // 更新的時(shí)候?qū)Ρ扰f的VNode和新的VNode,只更新有變化的那一部分,提高視圖更新速度
  var VNode = function VNode (
    tag,
    data,
    children,
    text,
    elm,
    context,
    componentOptions,
    asyncFactory
  ) {
    this.tag = tag; //標(biāo)簽屬性
    this.data = data;//渲染成真實(shí)DOM后,節(jié)點(diǎn)上到class attr style 事件等
    this.children = children;//子節(jié)點(diǎn),也上vnode
    this.text = text; // 文本
    this.elm = elm;//對(duì)應(yīng)著真實(shí)的dom節(jié)點(diǎn)
    this.ns = undefined;//當(dāng)前節(jié)點(diǎn)的namespace(命名空間)
    this.context = context;//編譯的作用域
    this.fnContext = undefined;// 函數(shù)化組件上下文
    this.fnOptions = undefined;// 函數(shù)化組件配置項(xiàng)
    this.fnScopeId = undefined;// 函數(shù)化組件ScopeId
    this.key = data && data.key;//只有綁定數(shù)據(jù)下存在,在diff的過程中可以提高性能
    this.componentOptions = componentOptions;// 通過vue組件生成的vnode對(duì)象,若是普通dom生成的vnode,則此值為空
    this.componentInstance = undefined;//當(dāng)前組件實(shí)例
    this.parent = undefined;//vnode、組件的占位節(jié)點(diǎn)
    this.raw = false; //是否為原生HTML或只是普通文本
    this.isStatic = false;  //靜態(tài)節(jié)點(diǎn)標(biāo)識(shí) || keep-alive
    this.isRootInsert = true; // 是否作為根節(jié)點(diǎn)插入
    this.isComment = false;// 是否為注釋節(jié)點(diǎn)
    this.isCloned = false; //是否為克隆節(jié)點(diǎn)
    this.isOnce = false; //是否為v-once節(jié)點(diǎn)
    this.asyncFactory = asyncFactory;// 異步工廠方法
    this.asyncMeta = undefined; //異步Meta
    this.isAsyncPlaceholder = false;//是否為異步占位
  };

  var prototypeAccessors = { child: { configurable: true } };
  prototypeAccessors.child.get = function () {
    return this.componentInstance
  };
  // 通過 Object.defineProperties 為 VNode 的原型綁定了對(duì)象 prototypeAccessors ,
  // prototypeAccessors 設(shè)置 child 是可修改的狀態(tài)。
  Object.defineProperties( VNode.prototype, prototypeAccessors );
  /*創(chuàng)建一個(gè)空VNode節(jié)點(diǎn)*/
  var createEmptyVNode = function (text) {
    if ( text === void 0 ) text = '';

    var node = new VNode();
    node.text = text;
    node.isComment = true;
    return node
  };
  /*創(chuàng)建一個(gè)文本節(jié)點(diǎn)*/
  function createTextVNode (val) {
    return new VNode(undefined, undefined, undefined, String(val))
  }

  // cloneVNode 克隆VNode節(jié)點(diǎn)
  function cloneVNode (vnode) {
    var cloned = new VNode(
      vnode.tag,
      vnode.data,
      // 克隆子數(shù)組以避免在克隆時(shí)突變?cè)紨?shù)組的子數(shù)組
      vnode.children && vnode.children.slice(),
      vnode.text,
      vnode.elm,
      vnode.context,
      vnode.componentOptions,
      vnode.asyncFactory
    );
    cloned.ns = vnode.ns;
    cloned.isStatic = vnode.isStatic;
    cloned.key = vnode.key;
    cloned.isComment = vnode.isComment;
    cloned.fnContext = vnode.fnContext;
    cloned.fnOptions = vnode.fnOptions;
    cloned.fnScopeId = vnode.fnScopeId;
    cloned.asyncMeta = vnode.asyncMeta;
    cloned.isCloned = true;
    return cloned
  }

/*取得原生數(shù)組的原型*/
  var arrayProto = Array.prototype;
  /*創(chuàng)建一個(gè)新的數(shù)組對(duì)象,修改該對(duì)象上的數(shù)組的七個(gè)方法,防止污染原生數(shù)組方法*/
  var arrayMethods = Object.create(arrayProto);

  /*這里重寫了數(shù)組的這些方法,在保證不污染原生數(shù)組原型的情況下重寫數(shù)組的這些方法,
  截獲數(shù)組的成員發(fā)生的變化,執(zhí)行原生數(shù)組操作的同時(shí)dep通知關(guān)聯(lián)的所有觀察者進(jìn)行響應(yīng)式處理*/
  var methodsToPatch = [
    'push',
    'pop',
    'shift',
    'unshift',
    'splice',
    'sort',
    'reverse'
  ];

  // 數(shù)組?較特別,它的操作?法不會(huì)觸發(fā)setter,需要特別處理。修改數(shù)組7個(gè)變更?法使其可以發(fā)送更新通知
  methodsToPatch.forEach(function (method) {
    /*將數(shù)組的原生方法緩存起來*/
    var original = arrayProto[method];
    def(arrayMethods, method, function mutator () {
      var args = [], len = arguments.length;
      while ( len-- ) args[ len ] = arguments[ len ];
       /*調(diào)用原生的數(shù)組方法*/
      var result = original.apply(this, args);
      /*數(shù)組新插入的元素需要重新進(jìn)行observe才能響應(yīng)式*/
      var ob = this.__ob__;
      var inserted;
      switch (method) {
        case 'push':
        case 'unshift':
          inserted = args;
          break
        case 'splice':
          inserted = args.slice(2);
          break
      }
      // 方法用于異步監(jiān)視數(shù)組發(fā)生的變化,類似于針對(duì)對(duì)象的 Object.observe() 。
      if (inserted) { ob.observeArray(inserted); }
      /*dep通知所有注冊(cè)的觀察者進(jìn)行響應(yīng)式處理*/
      ob.dep.notify();
      return result
    });
  });

  // 一般也是用來獲取一個(gè)JSON對(duì)象中所有屬性,
  // getOwnPropertyNames 獲取對(duì)象自身的可枚舉和不可枚舉屬性,不包括屬性名為Symbol值的屬性 
  var arrayKeys = Object.getOwnPropertyNames(arrayMethods);

  /**
   * In some cases we may want to disable observation inside a component's
   * update computation.
   */
  var shouldObserve = true;
  // 這個(gè)方法是vue內(nèi)部對(duì)邏輯的一個(gè)優(yōu)化,如果當(dāng)前組件是根組件,那么根組件是不應(yīng)該有props的。
  // toggleObserving就是禁止掉根組件 props的依賴收集
  function toggleObserving (value) {
    shouldObserve = value;
  }

  // 附加到每個(gè)被觀察對(duì)象的觀察者類。一旦附加,觀察者將目標(biāo)對(duì)象的屬性鍵轉(zhuǎn)換為getter/setter,用于收集依賴項(xiàng)和分派更新。
  // Observer為數(shù)據(jù)加上響應(yīng)式屬性進(jìn)行雙向綁定。如果是對(duì)象則進(jìn)行深度遍歷,為每一個(gè)子對(duì)象都綁定上方法,如果是數(shù)組則為每一個(gè)成員都綁定上方法。
// 如果是修改一個(gè)數(shù)組的成員,該成員是一個(gè)對(duì)象,那只需要遞歸對(duì)數(shù)組的成員進(jìn)行雙向綁定即可。
  export class Observer {
  var Observer = function Observer (value) {
    this.value = value;//這個(gè)就是傳入的要被監(jiān)聽的對(duì)象
    this.dep = new Dep();// 保存新的Dep實(shí)例
    this.vmCount = 0;// 設(shè)置vmCount的值為0
    // 為被監(jiān)聽對(duì)象定義了一個(gè) __ob__ 屬性,這個(gè)屬性的值就是當(dāng)前 Observer 實(shí)例對(duì)象
    // 其中 def 函數(shù)其實(shí)就是 Object.defineProperty 函數(shù)的簡單封裝
    //之所以這里使用 def 函數(shù)定義 __ob__ 屬性是因?yàn)檫@樣可以定義不可枚舉的屬性,
    // 這樣后面遍歷數(shù)據(jù)對(duì)象的時(shí)候就能夠防止遍歷到 __ob__ 屬性
    def(value, '__ob__', this);
      /*
          如果是數(shù)組,將修改后可以截獲響應(yīng)的數(shù)組方法替換掉該數(shù)組的原型中的原生方法,達(dá)到監(jiān)聽數(shù)組數(shù)據(jù)變化響應(yīng)的效果。
          這里如果當(dāng)前瀏覽器支持__proto__屬性,則直接覆蓋當(dāng)前數(shù)組對(duì)象原型上的原生數(shù)組方法,如果不支持該屬性,
          則直接覆蓋數(shù)組對(duì)象的原型。
      */
    if (Array.isArray(value)) {
      if (hasProto) {
        /*直接覆蓋原型的方法來修改目標(biāo)對(duì)象*/
        protoAugment(value, arrayMethods);
      } else {
        /*定義(覆蓋)目標(biāo)對(duì)象或數(shù)組的某一個(gè)方法*/
        copyAugment(value, arrayMethods, arrayKeys);
      }
      /*如果是數(shù)組則需要遍歷數(shù)組的每一個(gè)成員進(jìn)行observe*/
      this.observeArray(value);
    } else {
      // 如果是對(duì)象,遍歷執(zhí)行響應(yīng)式的操作
      this.walk(value);
    }
  };

  // 遍歷每個(gè)屬性并將它們轉(zhuǎn)換為getter/setter。此方法只應(yīng)在值類型為Object時(shí)調(diào)用。
  Observer.prototype.walk = function walk (obj) {
    var keys = Object.keys(obj);
    for (var i = 0; i < keys.length; i++) {
      defineReactive$$1(obj, keys[i]);
    }
  };

  // 觀察數(shù)組項(xiàng)的列表,如果是數(shù)組,遍歷每一項(xiàng),對(duì)每一項(xiàng)進(jìn)行一次observe
  Observer.prototype.observeArray = function observeArray (items) {
    for (var i = 0, l = items.length; i < l; i++) {
      observe(items[i]);
    }
  };

  // helpers

// 直接指定原型
  function protoAugment (target, src) {
    /* eslint-disable no-proto */
    target.__proto__ = src;
    /* eslint-enable no-proto */
  }

  //遍歷keys,定義(覆蓋)目標(biāo)對(duì)象或數(shù)組的某一個(gè)方法
  function copyAugment (target, src, keys) {
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i];
      def(target, key, src[key]);
    }
  }

//  嘗試創(chuàng)建一個(gè)Observer實(shí)例(__ob__),如果成功創(chuàng)建Observer實(shí)例則返回新的Observer實(shí)例,
//  如果已有Observer實(shí)例則返回現(xiàn)有的Observer實(shí)例。
// Vue的響應(yīng)式數(shù)據(jù)都會(huì)有一個(gè)__ob__的屬性作為標(biāo)記,里面存放了該屬性的觀察器,也就是Observer的實(shí)例,防止重復(fù)綁定。
  function observe (value, asRootData) {
    /*判斷是否是一個(gè)對(duì)象或者是否是一個(gè)虛擬節(jié)點(diǎn)*/
    if (!isObject(value) || value instanceof VNode) {
      return
    }
    var ob;
    /*這里用__ob__這個(gè)屬性來判斷是否已經(jīng)有Observer實(shí)例,如果沒有Observer實(shí)例則會(huì)
    新建一個(gè)Observer實(shí)例并賦值給__ob__這個(gè)屬性,如果已有Observer實(shí)例則直接返回該Observer實(shí)例*/
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
      ob = value.__ob__;
    } else if (
       /*這里的判斷是為了確保value是單純的對(duì)象,而不是函數(shù)或者是Regexp等情況。*/
      shouldObserve &&
      !isServerRendering() &&
      (Array.isArray(value) || isPlainObject(value)) &&
      Object.isExtensible(value) &&
      !value._isVue
    ) {
      ob = new Observer(value);
    }
    /*如果是根數(shù)據(jù)則計(jì)數(shù),后面Observer中的observe的asRootData非true*/
    if (asRootData && ob) {
      ob.vmCount++;
    }
    return ob
  }
//   defineReactive 函數(shù)最開始初始化 Dep 對(duì)象的實(shí)例,接著拿到 obj 的屬性描述符,
//   然后對(duì)子對(duì)象遞歸調(diào)用 observe 方法,這樣就保證了無論 obj 的結(jié)構(gòu)多復(fù)雜,
//   它的所有子屬性也能變成響應(yīng)式的對(duì)象,這樣我們?cè)L問或修改 obj 中一個(gè)嵌套較深的屬性,
//   也能觸發(fā) getter 和 setter。最后利用 Object.defineProperty 去給 obj 的屬性 key 添加 getter 和 setter。
// 核心就是利用 Object.defineProperty 給數(shù)據(jù)添加了 getter 和 setter,目的就是
// 為了在我們?cè)L問數(shù)據(jù)以及寫數(shù)據(jù)的時(shí)候能自動(dòng)執(zhí)行一些邏輯:getter 做的事情是依賴收集,setter 做的事情是派發(fā)更新
  function defineReactive$$1 (
    obj,
    key,
    val,
    customSetter,
    shallow
  ) {
    // 這個(gè) dep 常量所引用的 Dep 實(shí)例對(duì)象
 // 每一個(gè)數(shù)據(jù)字段都通過閉包引用著屬于自己的 dep 常量
 // 每次調(diào)用 defineReactive 定義訪問器屬性時(shí),該屬性的 setter/getter 都閉包引用了一個(gè)屬于自己的“筐
    var dep = new Dep();
     // 不可配置的直接返回
  // 獲取該字段可能已有的屬性描述對(duì)象
    var property = Object.getOwnPropertyDescriptor(obj, key);
     // 判斷該字段是否是可配置的
  // 一個(gè)不可配置的屬性是不能使用也沒必要使用 Object.defineProperty 改變其屬性定義的。
    if (property && property.configurable === false) {
      return
    }
// 保存了來自 property 對(duì)象的 get 和 set 
  // 避免原有的 set 和 get 方法被覆蓋
    var getter = property && property.get;
    var setter = property && property.set;
    if ((!getter || setter) && arguments.length === 2) {
      val = obj[key];
    }
  // 遞歸響應(yīng)式處理 給每一層屬性附加一個(gè)Obeserver實(shí)例
  // shallow不存在時(shí)代表沒有__ob__屬性 將val進(jìn)行observe返回一個(gè)ob實(shí)例賦值給childO
  // 如果是對(duì)象繼續(xù)調(diào)用 observe(val) 函數(shù)觀測(cè)該對(duì)象從而深度觀測(cè)數(shù)據(jù)對(duì)象
  // walk 函數(shù)中調(diào)用 defineReactive 函數(shù)時(shí)沒有傳遞 shallow 參數(shù),所以該參數(shù)是 und
  // 默認(rèn)就是深度觀測(cè)
    var childOb = !shallow && observe(val);
    // 數(shù)據(jù)攔截
  // 通過Object.defineProperty對(duì)obj的key進(jìn)行數(shù)據(jù)攔截
    Object.defineProperty(obj, key, {
      enumerable: true,
      configurable: true,
      // 進(jìn)行依賴收集
      get: function reactiveGetter () {
        var value = getter ? getter.call(obj) : val;
        // 判斷是否有Dep.target 如果有就代表Dep添加了Watcher實(shí)例化對(duì)象
        if (Dep.target) {
        // 加入到dep去管理watcher 
          dep.depend();
          if (childOb) {
            childOb.dep.depend();
            if (Array.isArray(value)) {
            // 循環(huán)添加watcher
              dependArray(value);
            }
          }
        }
        return value
      },
      set: function reactiveSetter (newVal) {
      // 獲取value值 觸發(fā)依賴收集
        var 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 (customSetter) {
          customSetter();
        }
        // 對(duì)于沒有setter的訪問器屬性 返回
        if (getter && !setter) { return }
        if (setter) {
        // 設(shè)置新值
          setter.call(obj, newVal);
        } else {
        // 如果沒有setter ,直接給新值
          val = newVal;
        }
        // 遞歸,對(duì)新來的值 對(duì)新值進(jìn)行observe 返回ob實(shí)例
        childOb = !shallow && observe(newVal);
        // 當(dāng)set時(shí)觸發(fā)通知
        dep.notify();
      }
    });
  }
  // 給對(duì)象設(shè)置一個(gè)屬性,添加新屬性和添加觸發(fā)更改通知(dep.notify),如果這個(gè)屬性不是早已存在
  function set (target, key, val) {
    // 判斷數(shù)據(jù) 是否是undefined或者null
    // 判斷數(shù)據(jù)類型是否是string,number,symbol,boolean
    if (isUndef(target) || isPrimitive(target)
    ) {
      // target必須是對(duì)象或者數(shù)組,否則發(fā)出警告
      warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target))));
    }
    // 如果是數(shù)組 并且檢查key是否是有效的數(shù)組索引
    if (Array.isArray(target) && isValidArrayIndex(key)) {
      // 設(shè)置數(shù)組長度
      target.length = Math.max(target.length, key);
      // 像數(shù)組尾部添加一個(gè)新數(shù)據(jù),相當(dāng)于push
      target.splice(key, 1, val);
      return val
    }
    // 如果key在target上 并且不是通過原型鏈查找的,就直接賦值
    if (key in target && !(key in Object.prototype)) {
      target[key] = val;
      return val
    }
    // 聲明一個(gè)對(duì)象ob 值為該target對(duì)象中的原型上面的所有方法和屬性,表明該數(shù)據(jù)加入過觀察者中
    var ob = (target).__ob__;
    // 如果是vue 或者  檢測(cè)vue被實(shí)例化的次數(shù) vmCount
    if (target._isVue || (ob && ob.vmCount)) {
      warn(
        'Avoid adding reactive properties to a Vue instance or its root $data ' +
        'at runtime - declare it upfront in the data option.'
      );
      return val
    }
      // 如果ob不存在,證明沒有添加觀察者,不是相應(yīng),直接賦值返回
    if (!ob) {
      target[key] = val;
      return val
    }
    // 通過defineReactive將ob.value加入的觀察者
    defineReactive$$1(ob.value, key, val);
    // 觸發(fā)通知更新,通知訂閱者obj.value更新數(shù)據(jù)
    ob.dep.notify();
    return val
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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