vue源码阅读写注释 第二篇

384 阅读7分钟

为什么开始?

很早就有阅读vue源码的想法,
迫于入行不久,功力太过浅认为啃不太动就一直搁置

年后有段时间看了lodash的部分源码
发现源码也不是完全看不懂
不过,当时看得懂,没做过什么笔记过段时间看又忘了

之前在网上断断续续的看过其他道友发的vue的源码理解,也是看完没有印象了,
索性这次直接给他看的时候加上备注和理解,
方便下次看的时候更快理解吧

  • 看到现在有一些地方还是有些迷迷糊糊的

每天看个一点点,日拱一卒。 耶巴蒂莱维贝贝!!!!

特别提醒

每个方法都有备注 部分有理解

 var SSR_ATTR = 'data-server-rendered';
    // 全局函数
    var ASSET_TYPES = [
      'component',
      'directive',
      'filter'
    ];
    // 声明周期
    var LIFECYCLE_HOOKS = [
      'beforeCreate',
      'created',
      'beforeMount',
      'mounted',
      'beforeUpdate',
      'updated',
      'beforeDestroy',
      'destroyed',
      'activated',
      'deactivated',
      'errorCaptured',
      'serverPrefetch'
    ];
  
    /*  */
  
  
    // vue的全局配置
    var config = ({
      /**
       * Option merge strategies (used in core/util/options)
       */
      // $flow-disable-line
      optionMergeStrategies: Object.create(null),
  
      /**
       * Whether to suppress warnings.
       * 是否关闭警告
       */
      silent: false,
  
      /**
       * Show production mode tip message on boot?
       * 开发按摩时下控制台显示生产提示
       */
      productionTip: "development" !== 'production',
  
      /**
       * Whether to enable devtools
       * 浏览器vue插件调试工具检查代码
       */
      devtools: "development" !== 'production',
  
      /**
       * Whether to record perf
       * 性能追踪
       */
      performance: false,
  
      /**
       * Error handler for watcher errors
       * ;指定组件的渲染和观察期间未捕获错误的处理函数。这个处理函数被调用时,可获取错误信息和 Vue 实例。
       */
      errorHandler: null,
  
      /**
       * Warn handler for watcher warns
       * Vue 的运行时警告赋予一个自定义处理函数
       */
      warnHandler: null,
  
      /**
       * Ignore certain custom elements
       * 忽略某些自定义元素
       */
      ignoredElements: [],
  
      /**
       * Custom user key aliases for v-on
       * ;给v-on 自定义键位别名
       */
      // $flow-disable-line
      keyCodes: Object.create(null),
  
      /**
       * Check if a tag is reserved so that it cannot be registered as a
       * component. This is platform-dependent and may be overwritten.
       * ;保留标签,如有,则这些标签不能注册成为组件
       */
      isReservedTag: no,
  
      /**
       * Check if an attribute is reserved so that it cannot be used as a component
       * prop. This is platform-dependent and may be overwritten.
       */
      isReservedAttr: no,
  
      /**
       * Check if a tag is an unknown element.
       * Platform-dependent.
       */
      isUnknownElement: no,
  
      /**
       * Get the namespace of an element
       */
      getTagNamespace: noop,
  
      /**
       * Parse the real tag name for the specific platform.
       */
      parsePlatformTagName: identity,
  
      /**
       * Check if an attribute must be bound using property, e.g. value
       * Platform-dependent.
       */
      mustUseProp: no,
  
      /**
       * Perform updates asynchronously. Intended to be used by Vue Test Utils
       * This will significantly reduce performance if set to false.
       */
      async: true,
  
      /**
       * Exposed for legacy reasons
       */
      _lifecycleHooks: LIFECYCLE_HOOKS
    });
  
    /*  */
  
    /**
     * unicode letters used for parsing html tags, component names and property paths.
     * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
     * skipping \u10000-\uEFFFF due to it freezing up PhantomJS
     */
    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/;
  
    /**
     * Check if a string starts with $ or _
     * 判断字符串是否以_开头
     */
    function isReserved (str) {
      var c = (str + '').charCodeAt(0);
      return c === 0x24 || c === 0x5F
    }
  
    /**
     * Define a property.
     * 在一个对象上定义一个属性的构造函数
     */
    function def (obj, key, val, enumerable) {
      Object.defineProperty(obj, key, {
        value: val,
        enumerable: !!enumerable, // 强制转换成布尔值
        writable: true,
        configurable: true
      });
    }
  
    /**
     * Parse simple path.
     * unicodeRegExp.source  去掉了开头和结尾的/
     * "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"
     * 不太清除这个方法是干什么的
     * 先匹配一个地址  如果匹配到bailRE的地址 return掉
     * 没匹配到  通过.分隔 使用闭包将数组保存在内存中 返回一个方法
     * 方法入参为一个对象  循环内存中的数组  如果入参中 对象为空直接return
     *                  如果入参不为空  将obj中的 数组中值对应key的值赋值给obj
     *                  其实不太明白具体是干什么用的
     */
    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
      }
    }
  
    /*  */
  
    // can we use __proto__?
    var hasProto = '__proto__' in {};
  
    // Browser environment sniffing
    // 浏览器环境判断
    var inBrowser = typeof window !== 'undefined';
    // 判断微信环境 !! 强制转为布尔值
    var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform;
    var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase();
    var UA = inBrowser && window.navigator.userAgent.toLowerCase();
    var isIE = UA && /msie|trident/.test(UA);
    var isIE9 = UA && UA.indexOf('msie 9.0') > 0;
    var isEdge = UA && UA.indexOf('edge/') > 0;
    var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android');
    var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios');
    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...
    // 火狐浏览器中的对象原型上有一个watch方法
    var nativeWatch = ({}).watch;
  
    var supportsPassive = false;
    
  // 这个代码块是用来检查浏览器是否支持passive属性的。
  // passive能优化页面的滚动性能
  // 在使用addEventListener添加事件时,
  // 如下做,支持passive属性,则使用这一属性,
  // 否则addEventListener的第三个参数按照旧版的DOM 标准,第三个参数为布尔值,默认为falseif (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) {}
    }
  
    // this needs to be lazy-evaled because vue may be required before
    // vue-server-renderer can set VUE_ENV
    // 判断当前执行环境是不是服务器端  即node环境下执行的
    var _isServer;
    var isServerRendering = function () {
      if (_isServer === undefined) {
        /* istanbul ignore if */
        if (!inBrowser && !inWeex && typeof global !== 'undefined') {
          // detect presence of vue-server-renderer and avoid
          // Webpack shimming the process
          _isServer = global['process'] && global['process'].env.VUE_ENV === 'server';
        } else {
          _isServer = false;
        }
      }
      return _isServer
    };
  
    // detect devtools
    // vue的工具方法
    var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__;
  
    /* istanbul ignore next */
    // 判断一个方法是否是js内置方法的  方法
    function isNative (Ctor) {
      return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
    }
    // 判断 es6 Symbol 和 Reflect 属性是否支持
    var hasSymbol =
      typeof Symbol !== 'undefined' && isNative(Symbol) &&
      typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys);
  
    var _Set;
    /* istanbul ignore if */ // $flow-disable-line
    // 判断 es6  Set 是否支持
    if (typeof Set !== 'undefined' && isNative(Set)) {
      // use native Set when available.
      _Set = Set;
    } else {
      // 不支持就手动实现一个set
      // a non-standard Set polyfill that only works with primitive keys.

      /* 
        手动实现set 的方法
        使用自执行函数
      */
      _Set = /*@__PURE__*/(function () {
        function Set () {
          this.set = Object.create(null);
        }
        Set.prototype.has = function has (key) {
          return this.set[key] === true
        };
        Set.prototype.add = function add (key) {
          this.set[key] = true;
        };
        Set.prototype.clear = function clear () {
          this.set = Object.create(null);
        };
  
        return Set;
      }());
    }
  
    /*  */
    // 给warn一个空方法
    var warn = noop;
    var tip = noop;
    var generateComponentTrace = (noop); // work around flow check
    var formatComponentName = (noop);
  
    {
      var hasConsole = typeof console !== 'undefined';
      // 这个正则就是把连接符转换成的大驼峰写法, 并且第一个字符大写  ^|[-_]&emsp;的意思是 字符串的开头, 或者 -_ 后面的一个字符
      var classifyRE = /(?:^|[-_])(\w)/g;
      var classify = function (str) { return str
        .replace(classifyRE, function (c) { return c.toUpperCase(); })
        .replace(/[-_]/g, ''); };
      /**
       * 警告日志输出
      */
      warn = function (msg, vm) {
        var trace = vm ? generateComponentTrace(vm) : '';
  
        if (config.warnHandler) {
          config.warnHandler.call(null, msg, vm, trace);
        } else if (hasConsole && (!config.silent)) {
          console.error(("[Vue warn]: " + msg + trace));
        }
      };
      /**
       * 提示日志输出
      */
      tip = function (msg, vm) {
        if (hasConsole && (!config.silent)) {
          console.warn("[Vue tip]: " + msg + (
            vm ? generateComponentTrace(vm) : ''
          ));
        }
      };
      // 格式化组件名
      formatComponentName = function (vm, includeFile) {
        // 判断是否是根组件
        if (vm.$root === vm) {
          return '<Root>'
        }
        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) {
          var match = file.match(/([^/\\]+)\.vue$/);
          name = match && match[1];
        }
  
        return (
          (name ? ("<" + (classify(name)) + ">") : "<Anonymous>") +
          (file && includeFile !== false ? (" at " + file) : '')
        )
      };
      // n个字符串
      var repeat = function (str, n) {
        var res = '';
        while (n) {
          if (n % 2 === 1) { res += str; }
          if (n > 1) { str += str; }
          n >>= 1;
        }
        return res
      };
      /* 
        组件树规则
        没太看明白
      */
      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] = [3, 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;
  
    /**
     * A dep is an observable that can have multiple
     * directives subscribing to it.
     */
    /**
     * 注册订阅 订阅构造函数
     * 订阅者数据收集
     * this.subs 订阅者数组
    */
    var Dep = function Dep () {
      this.id = uid++;
      this.subs = [];
    };
    // 添加订阅者
    Dep.prototype.addSub = function addSub (sub) {
      this.subs.push(sub);
    };
    // 删除一个订阅者
    Dep.prototype.removeSub = function removeSub (sub) {
      remove(this.subs, sub);
    };
    // 为Watcher 添加 为Watcher.newDeps.push(dep); 一个dep对象
    /* 
      设置某个Watcher的依赖
      理解成改变watcher执行的作用域
    */
    Dep.prototype.depend = function depend () {
      //添加一个dep    target 是Watcher dep就是dep对象
      if (Dep.target) {
        Dep.target.addDep(this);
      }
    };
    // 触发订阅 也就是发布
    Dep.prototype.notify = function notify () {
      // stabilize the subscriber list first
      var subs = this.subs.slice();
      if (!config.async) {
        // subs aren't sorted in scheduler if not running async
        // we need to sort them now to make sure they fire in correct
        // order
        // 根据id升序排序
        subs.sort(function (a, b) { return a.id - b.id; });
      }
      // 循环执行订阅者中的update方法,进行订阅状态更新通知
      for (var i = 0, l = subs.length; i < l; i++) {
        subs[i].update();
      }
    };
  
    // The current target watcher being evaluated.
    // This is globally unique because only one watcher
    // can be evaluated at a time.
    /**
     * 全局唯一的观察者
    */
    Dep.target = null;
    var targetStack = [];
    /**
     * dep
     */
    function pushTarget (target) {
      targetStack.push(target);
      Dep.target = target;
    }
  
    function popTarget () {
      targetStack.pop();
      Dep.target = targetStack[targetStack.length - 1];
    }
  
    /* 
      虚拟dom 
    */
    var VNode = function VNode (
      tag,
      data,
      children,
      text,
      elm,
      context,
      componentOptions,
      asyncFactory
    ) {
      this.tag = tag;
      this.data = data;
      this.children = children;
      this.text = text;
      this.elm = elm;
      this.ns = undefined;
      this.context = context;
      this.fnContext = undefined;
      this.fnOptions = undefined;
      this.fnScopeId = undefined;
      this.key = data && data.key;
      this.componentOptions = componentOptions;
      this.componentInstance = undefined;
      this.parent = undefined;
      this.raw = false;
      this.isStatic = false;
      this.isRootInsert = true;
      this.isComment = false;
      this.isCloned = false;
      this.isOnce = false;
      this.asyncFactory = asyncFactory;
      this.asyncMeta = undefined;
      this.isAsyncPlaceholder = false;
    };
  
    var prototypeAccessors = { child: { configurable: true } };
  
    // DEPRECATED: alias for componentInstance for backwards compat.
    /* istanbul ignore next */
    prototypeAccessors.child.get = function () {
      return this.componentInstance
    };
  
    Object.defineProperties( VNode.prototype, prototypeAccessors );
    
    // 创建空的虚拟dom
    var createEmptyVNode = function (text) {
      if ( text === void 0 ) text = '';
  
      var node = new VNode();
      node.text = text;
      node.isComment = true;
      return node
    };
    // 创建文本dom  即一个字符串
    function createTextVNode (val) {
      return new VNode(undefined, undefined, undefined, String(val))
    }
  
    // optimized shallow clone
    // used for static nodes and slot nodes because they may be reused across
    // multiple renders, cloning them avoids errors when DOM manipulations rely
    // on their elm reference.
    // 优化浅拷贝
    // 用于静态节点和插槽节点,因为它们可以在多个渲染中重用,
    // 当DOM操作依赖于它们的elm引用时,克隆它们可以避免错误
    function cloneVNode (vnode) {
      var cloned = new VNode(
        vnode.tag,
        vnode.data,
        // #7975
        // clone children array to avoid mutating original in case of cloning
        // a child.
        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
    }
  
    /*
     * not type checking this file because flow doesn't play well with
     * dynamically accessing methods on Array prototype
     */
    // 对数组的原型方法进行重写
    /* 
    tips:
      数组和对象不同的是  数组不能通过  getter/setter 来监听变化
      因此需要通过拦截器来实现监听变化
    */
    //  拦截器 arrayProto 
    //  每当使用数组上的原型方法时 
    //  实际执行的是拦截器上面的方法  也就是使用拦截器上的 数组的原型方法
    var arrayProto = Array.prototype;
    var arrayMethods = Object.create(arrayProto);
  
    var methodsToPatch = [
      'push',
      'pop',
      'shift',
      'unshift',
      'splice',
      'sort',
      'reverse'
    ];
  
    /**
     * Intercept mutating methods and emit events
     */
    methodsToPatch.forEach(function (method) {
      // cache original method
      // 缓存原始方法  即 Array 上的方法
      var original = arrayProto[method];

      /* 
      tips:
        下面代码等同于于
        Object.defineProperty(arrayMethods, method, {
          enumerable: false,
          configurable: true,
          writable: true,
          value: function mutator () {
            ....
          }
      })
      */
      def(arrayMethods, method, function mutator () {
        // 将类数组对象 arguments 转换成真数组中
        var args = [], len = arguments.length;
        while ( len-- ) args[ len ] = arguments[ len ];
        // 调用Array原型上的方法 拿到执行结果
        var result = original.apply(this, args);
        // 数组新插入的元素需要重新进行observe才能响应式
        var ob = this.__ob__;
        var inserted;
        switch (method) {
          case 'push':
          case 'unshift':
            inserted = args;
            break
          case 'splice':
            inserted = args.slice(2);
            break
        }
        // 没看懂这一步是用来干什么的
        if (inserted) { ob.observeArray(inserted); }
        // notify change
      // 通知所有注册的观察者进行响应式处理
        ob.dep.notify();
        return result
      });
    });
  
    /* 
     Object.getOwnPropertyNames 
     一个对象,其自身的可枚举和不可枚举属性的名称被返回。
      Object.getOwnPropertyNames(arrayMethods); 返回 []

      MDN上的例子
      var arr = ["a", "b", "c"];
      console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]
      // 类数组对象
      var obj = { 0: "a", 1: "b", 2: "c"};
      console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

    */
  
    var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
  
    /**
     * In some cases we may want to disable observation inside a component's
     * update computation.
     * 在某些情况下,我们可能希望禁用组件更新中的观察
     */
    var shouldObserve = true;
  
    function toggleObserving (value) {
      shouldObserve = value;
    }
  
    /**
     * Observer class that is attached to each observed
     * object. Once attached, the observer converts the target
     * object's property keys into getter/setters that
     * collect dependencies and dispatch updates.
     */
    var Observer = function Observer (value) {
      this.value = value;
      this.dep = new Dep();
      this.vmCount = 0;
      def(value, '__ob__', this);
      if (Array.isArray(value)) {
        if (hasProto) {
          protoAugment(value, arrayMethods);
        } else {
          copyAugment(value, arrayMethods, arrayKeys);
        }
        this.observeArray(value);
      } else {
        this.walk(value);
      }
    };

更多原生js的个人学习总结欢迎查看 star

设计模式个人学习总结,点开有惊喜哦