Vue源码分析拓展 - Vue 模板编译渲染函数原理分析

目录

Vue 模板编译渲染函数

编译

Vue 模板编译渲染函数原理分析.html

compiletoFunctions.html

compileToFunctions.js

vue.2.5.1.源码学习.js

一张AI生成图~


Vue 模板编译渲染函数

 

new Vue():初始化

$mount:挂载

compile():编译

        parse:解析

        optimize:静态节点优化

        generate:针对不同目标平台生成的代码

render function —touch【依赖收集】—>getter

Watcher:观察数据

patch():更新数据,新老旧DOM对比,

DOM :映射到DOM

编译

compile编译可用分成parse、optimizegenerate三个阶段,最终需要得到render function.

√parse

parse会用正则等方式解析template模板中的指令、class、style等数据,形成AST。【json数据】

√optimize

optimize的主要作用是标记static静态节点。【不会改变的一些结构】

√generate

generate是将AST转化成render function字符串的过程,得到结果是render的字符串以及staticRenderFns字符串。

再经历过parse、optimizegenerate这三个阶段以后,组件中就会存在渲染VNode【虚拟DOM】所需的render function【组件映射成真实DOM中间起桥接作用】了

Vue 模板编译渲染函数原理分析.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 模板编译渲染函数原理分析</title>
</head>
<body>
  <div id="app">
  </div>
  <script src="vue.2.5.1.生命周期.js"></script>
  <script>
    var vm = new Vue({
      el: "#app",
      template: "<div><a>{{age}}</a></div>", // render function
      data: {
        age: 30
      }
    })
    // compiler【编译】 模板  1: template: "<div><a></a></div>" 2. #app outHTML
  </script>
</body>
</html>
compiletoFunctions.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue 模板编译渲染函数原理分析</title>
</head>
<body>
  <div id="app">
  </div>
  <script src="compileToFunctions.js"></script>
  <script>
    var ref = compileToFunctions("<div><a>{{age}}</a></div>", {
      // 自定义配置
    }, this)
    ref.render()
  </script>
</body>
</html>
compileToFunctions.js
// vue2.0编译器方法
// 编译器中默认的配置项
var baseOptions = {
  expectHTML: true,
  modules: {},
  directives: {},
  isPreTag: {},
  isUnaryTag: {},
  mustUseProp: {},
  canBeLeftOpenTag: {},
  isReservedTag: {},
  getTagNamespace: {},
  staticKeys: {}
};
var noop = function () { }
//用于分析标记和属性的正则表达式
// var attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// var dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/;
// var ncname = "[a-zA-Z_][\\-\\.0-9_a-zA-Z" + (unicodeRegExp.source) + "]*";
// var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")";
// var startTagOpen = new RegExp(("^<" + qnameCapture));
// var startTagClose = /^\s*(\/?)>/;
// var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>"));
// var doctype = /^<!DOCTYPE [^>]+>/i;
// // #7298: escape - to avoid being passed as HTML comment when inlined in page
// var comment = /^<!\--/;
// var conditionalComment = /^<!\[/;
// Special Elements (can contain anything)
// var isPlainTextElement = makeMap('script,style,textarea', true);
var reCache = {};
var decodingMap = {
  '&lt;': '<',
  '&gt;': '>',
  '&quot;': '"',
  '&amp;': '&',
  '&#10;': '\n',
  '&#9;': '\t',
  '&#39;': "'"
};
var encodedAttr = /&(?:lt|gt|quot|amp|#39);/g;
var encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g;
/**
 *将属性混合到目标对象中。
  */
function extend(to, _from) {
  for (var key in _from) {
    to[key] = _from[key];
  }
  return to
}
// 渲染函数的生成
function createFunction(code, errors) { // code:code.render
  try {
    return new Function(code) // 最终渲染函数的长相
  } catch (err) {
    errors.push({ err: err, code: code });
    return noop
  }
}
function createCompileToFunctionFn(compile) {
  // 创建缓存对象
  var cache = Object.create(null);
  return function compileToFunctions(template, options, vm) {
    // 扩展一下
    options = extend({}, options);
    /* istanbul ignore if */
    {
      // detect possible CSP restriction
      try { // 当前支不支持new Function
        new Function('return 1');
      } catch (e) {
        // string.match(regexp)
        // match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
        if (e.toString().match(/unsafe-eval|CSP/)) {
          console.error(
            '您似乎正在使用Vue.js的独立版本' +
            '具有禁止不安全评估的内容安全策略的环境。' +
            '模板编译器无法在此环境中工作。考虑' +
            '放宽政策以允许不安全评估或预编译您的应用程序' +
            '将模板转换为渲染函数。'
          );
        }
      }
    }
    // check cache 缓存优化
    var key = options.delimiters // 自定义文本编译符
      ? String(options.delimiters) + template
      : template;
    if (cache[key]) {
      return cache[key]
    }
    // compile
    var compiled = compile(template, options); // ast render staticRenderFns
    // turn code into functions
    var res = {};
    var fnGenErrors = []; // 编译错误信息   渲染函数所需要的字符串
    res.render = createFunction(compiled.render, fnGenErrors);
    return (cache[key] = res) // 缓存优化
  }
}
function createCompilerCreator(baseCompile) {
  return function createCompiler(baseOptions) {
    // 编译的核心方法
    function compile(template, options) {
      var finalOptions = Object.create(baseOptions); // {}.__proto__【原型】baseOptions【编译器内置配置项】
      var errors = []; // 编译错误
      var tips = [];    // 编译提示
      finalOptions.warn = function (msg, tip) { // msg 信息;tip 提示信息;否则错误信息
        (tip ? tips : errors).push(msg);
      };
      // 自定义编译器的选项    内置的编译器选项
      if (options) {
        // 合并finalOptions
        // ...
      }
      var compiled = baseCompile(template, finalOptions);
      // ...
      compiled.errors = errors;
      compiled.tips = tips
      return compiled
    }
    return {
      compile: compile, // compiled
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
//`createCompilerCreator`允许创建使用替代方法的编译器
//解析器/优化器/代码生成器,例如SSR优化编译器。
//这里我们只是使用默认部分导出默认编译器。
// createCompilerCreator 编译器的构造者
var createCompiler = createCompilerCreator(function baseCompile(template, options) {
  // 模板  编译成AST   词法分析   句法分析   代码生成   (token)
  // 编译器:将源代码转化为目标代码的工具
  // 将对于人编写,阅读维护的高级计算机语言所写的源代码程序编译为计算机能够解读运行的低阶机器语言的程序
  // var ast = parse(template.trim(), options); // 核心方法 parseHTML=> ast【抽象语法树】
  // if (options.optimize !== false) {
  //   optimize(ast, options); // 静态节点的标记
  // }
  // var code = generate(ast, options); // 生成web端所需要的代码;生成其他端代码
  return {
    ast: {},//ast,
    render: 'alert("hello 编译器")',//code.render, // 渲染函数所需要的字符串=>生成渲染函数
    staticRenderFns: "staticRenderFns"//code.staticRenderFns
  }
});
function parseHTML(html, options) {
  var stack = [];
  var expectHTML = options.expectHTML;
  var isUnaryTag$$1 = options.isUnaryTag || no;
  var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
  var index = 0;
  var last, lastTag;
  while (html) { // 字符串  切割单元(token) 【都切成 token 令牌 词】一直到切没了 ""  正则检测标签,属性
    last = html;
    // Make sure we're not in a plaintext content element like script/style
    if (!lastTag || !isPlainTextElement(lastTag)) {
      var textEnd = html.indexOf('<');
      if (textEnd === 0) {
        // Comment:
        if (comment.test(html)) {
          var commentEnd = html.indexOf('-->');
          if (commentEnd >= 0) {
            if (options.shouldKeepComment) {
              options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3);
            }
            advance(commentEnd + 3);
            continue
          }
        }
        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          var conditionalEnd = html.indexOf(']>');
          if (conditionalEnd >= 0) {
            advance(conditionalEnd + 2);
            continue
          }
        }
        // Doctype:
        var doctypeMatch = html.match(doctype);
        if (doctypeMatch) {
          advance(doctypeMatch[0].length);
          continue
        }
        // End tag:
        var endTagMatch = html.match(endTag);
        if (endTagMatch) {
          var curIndex = index;
          advance(endTagMatch[0].length);
          parseEndTag(endTagMatch[1], curIndex, index);
          continue
        }
        // Start tag:
        var startTagMatch = parseStartTag();
        if (startTagMatch) {
          handleStartTag(startTagMatch);
          if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
            advance(1);
          }
          continue
        }
      }
      var text = (void 0), rest = (void 0), next = (void 0);
      if (textEnd >= 0) {
        rest = html.slice(textEnd);
        while (
          !endTag.test(rest) &&
          !startTagOpen.test(rest) &&
          !comment.test(rest) &&
          !conditionalComment.test(rest)
        ) {
          // < in plain text, be forgiving and treat it as text
          next = rest.indexOf('<', 1);
          if (next < 0) { break }
          textEnd += next;
          rest = html.slice(textEnd);
        }
        text = html.substring(0, textEnd);
      }
      if (textEnd < 0) {
        text = html;
      }
      if (text) {
        advance(text.length);
      }
      if (options.chars && text) {
        options.chars(text, index - text.length, index);
      }
    } else {
      var endTagLength = 0;
      var stackedTag = lastTag.toLowerCase();
      var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
      var rest$1 = html.replace(reStackedTag, function (all, text, endTag) {
        endTagLength = endTag.length;
        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
          text = text
            .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298
            .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
        }
        if (shouldIgnoreFirstNewline(stackedTag, text)) {
          text = text.slice(1);
        }
        if (options.chars) {
          options.chars(text);
        }
        return ''
      });
      index += html.length - rest$1.length;
      html = rest$1;
      parseEndTag(stackedTag, index - endTagLength, index);
    }
    if (html === last) {
      options.chars && options.chars(html);
      if (!stack.length && options.warn) {
        options.warn(("Mal-formatted tag at end of template: \"" + html + "\""), { start: index + html.length });
      }
      break
    }
  }
  // Clean up any remaining tags
  parseEndTag();
  function advance(n) {
    index += n;
    html = html.substring(n);
  }
  function parseStartTag() {
    var start = html.match(startTagOpen);
    if (start) {
      var match = {
        tagName: start[1],
        attrs: [],
        start: index
      };
      advance(start[0].length);
      var end, attr;
      while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
        attr.start = index;
        advance(attr[0].length);
        attr.end = index;
        match.attrs.push(attr);
      }
      if (end) {
        match.unarySlash = end[1];
        advance(end[0].length);
        match.end = index;
        return match
      }
    }
  }
  function handleStartTag(match) {
    var tagName = match.tagName;
    var unarySlash = match.unarySlash;
    if (expectHTML) {
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag);
      }
      if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
        parseEndTag(tagName);
      }
    }
    var unary = isUnaryTag$$1(tagName) || !!unarySlash;
    var l = match.attrs.length;
    var attrs = new Array(l);
    for (var i = 0; i < l; i++) {
      var args = match.attrs[i];
      var value = args[3] || args[4] || args[5] || '';
      var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
        ? options.shouldDecodeNewlinesForHref
        : options.shouldDecodeNewlines;
      attrs[i] = {
        name: args[1],
        value: decodeAttr(value, shouldDecodeNewlines)
      };
      if (options.outputSourceRange) {
        attrs[i].start = args.start + args[0].match(/^\s*/).length;
        attrs[i].end = args.end;
      }
    }
    if (!unary) {
      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end });
      lastTag = tagName;
    }
    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end);
    }
  }
  function parseEndTag(tagName, start, end) {
    var pos, lowerCasedTagName;
    if (start == null) { start = index; }
    if (end == null) { end = index; }
    // Find the closest opened tag of the same type
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase();
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break
        }
      }
    } else {
      // If no tag name is provided, clean shop
      pos = 0;
    }
    if (pos >= 0) {
      // Close all the open elements, up the stack
      for (var i = stack.length - 1; i >= pos; i--) {
        if (i > pos || !tagName &&
          options.warn
        ) {
          options.warn(
            ("tag <" + (stack[i].tag) + "> has no matching end tag."),
            { start: stack[i].start, end: stack[i].end }
          );
        }
        if (options.end) {
          options.end(stack[i].tag, start, end);
        }
      }
      // Remove the open elements from the stack
      stack.length = pos;
      lastTag = pos && stack[pos - 1].tag;
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end);
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end);
      }
      if (options.end) {
        options.end(tagName, start, end);
      }
    }
  }
}
var ref$1 = createCompiler(baseOptions);
var compile = ref$1.compile;
var compileToFunctions = ref$1.compileToFunctions;
vue.2.5.1.源码学习.js
// 整体结构是立即执行函数。
// 带两个参数,一个global用作全局变量存放,一个factory工厂函数制造一些初始化方法。
// typeof判断变量类型,以此校验执行环境
// 需要理解逻辑运算符之间的优先级,
// 其中用到了几个不理解的变量,属性和方法,分别是 module、module.exports、define、define.amd
(function (global, factory) {// global = window, factory
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
  // 暴露出factory工厂函数
}(this, function () {
  // ASSET_TYPES 常量复用
  // Vue.options.components
  // Vue.component..   Vue.directive..
  var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
  ];
  // 生命周期钩子常量组
  var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    'serverPrefetch'
  ];
  // 浏览器是否有window对象
  var inBrowser = typeof window !== 'undefined';
  var noop = function () { }
  // Vue 全局配置对象
  // 最终config会暴露为Vue.config
  var config = {
    /**
     * 选项合并策略(用于core/util/options)
     */
    //$flow-disable-line 禁用行
    optionMergeStrategies: Object.create(null),
  }
  /**
     * 检查对象是否具有该属性
     */
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key)
  }
  /**
   * 创建纯函数的缓存版本。
   */
  function cached(fn) {
    var cache = Object.create(null);
    return (function cachedFn(str) {
      var hit = cache[str];
      return hit || (cache[str] = fn(str))
    })
  }
  /**
   *获取元素的outHTML,小心
   *IE中SVG元素的使用。
   */
  function getOuterHTML(el) { // el === DOM元素
    if (el.outerHTML) {
      return el.outerHTML
    } else { // IE9-11  svg 不支持outerHTML 兼容性处理
      var container = document.createElement('div');
      container.appendChild(el.cloneNode(true));
      return container.innerHTML
    }
  }
  // 类型检测 对象Object
  function isPlainObject(obj) {
    return toString.call(obj) === '[object Object]'
  }
  /**
 * 检查字符串是否以$or开头_
 */
  function isReserved(str) {
    var c = (str + '').charCodeAt(0); // 获取Unicode编码 0~65535
    return c === 0x24 || c === 0x5F // 十六进制的Unicode 编码 $=== 0x24  _=== 0x5F
  }
  /** 自定义策略
   * 选项覆盖策略是处理
   * 如何合并父选项值和子选项
   * 将值转换为最终值。
   */
  // 自定义处理用在了很多地方,如el,props,watch....
  var strats = config.optionMergeStrategies;
  // 自定义策略处理
  strats.data = function (parentVal, childVal, vm) {
    // 组件的基本原理
    // 聚焦到vm,判别是根实例,还是组件
    if (!vm) { // 组件,不是根实例
      if (childVal && typeof childVal !== "function") {
        console.error("data选项应该为函数 返回组件中每个实例的值");
        return parentVal
      }
      // 处理子组件data的选项
      return mergeDataOrFn(parentVal, childVal)
    }
    // 处理根实例data的选项
    return mergeDataOrFn(parentVal, childVal, vm)
  };
  // 处理根data的选项
  function mergeDataOrFn(
    parentVal,
    childVal,
    vm
  ) {
    if (!vm) { // 子组件data选项
      // 在Vue.extend合并中,这两个函数都应该是函数
      // if (!childVal) {
      //   return parentVal
      // }
      // if (!parentVal) {
      //   return childVal
      // }
      //当parentVal和childVal都存在时,
      //我们需要返回一个函数,该函数返回
      //两个函数的合并结果。。。没必要
      //此处检查parentVal是否为函数,因为
      //它必须是传递先前合并的函数。
      // return function mergedDataFn() {
      //   return mergeData(
      //     typeof childVal === 'function' ? childVal.call(this, this) : childVal,
      //     typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      //   )
      // }
    } else { // 根实例data选项
      return function mergedInstanceDataFn() {
        // 实例合并
        return typeof childVal === 'function'
          ? childVal.call(vm, vm)
          : childVal;
      }
    }
  }
  // 所有钩子函数的自定义策略   parentVal === undefined    childVal === function(){}
  function mergeHook(parentVal, childVal) {
    return childVal
      ? parentVal // 数组
        ? parentVal.concat(childVal) // 合并parentVal、childVal
        : Array.isArray(childVal) // 是不是数组
          ? childVal
          : [childVal] // [function(){}]
      : parentVal;
  }
  LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook;
  })
  /**
   * "所有"选项默认策略
   */
  var defaultStrat = function (parentVal, childVal) {
    return childVal === undefined
      ? parentVal
      : childVal
  };
  function mergeOptions(parent, child, vm) {
    /* 选项规范检测 Components Props Inject Directives */
    var options = {};
    var key;
    for (key in parent) {
      //parent-> 'components', 'directives', 'filters'
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) { // 父组件没有这个key,才会扩展key
        mergeField(key);
      }
    }
    // 选项的策略处理;要考虑el,data,生命周期的钩子函数...
    // 自定义策略挂载在(strats对象)  默认策略
    function mergeField(key) { // 组件也会调用,组件包含vue的除根实例特有的el等外的实例
      // console.log(key);
      // 属性值 || 默认策略
      var strat = strats[key] || defaultStrat;
      // 给options扩展key属性
      options[key] = strat(parent[key], child[key], vm, key);
    }
    return options;
  }
  // 执行钩子函数方法
  function callHook(vm, hook) {
    var handlers = vm.$options[hook];
    if (handlers) {
      for (var i = 0, j = handlers.length; i < j; i++) {
        handlers[i].call(vm)
      }
    }
  }
  var sharedPropertyDefinition = {
    enumerable: true, // 可枚举的
    configurable: true, // 可配置的
    get: noop,
    set: noop
  };
  // 代理数据
  // target === vm, sourceKey === "_data", key === key 属性名称
  function proxy(target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter() {
      return this[sourceKey][key] // this === target === vm;vm._data.root
    };
    sharedPropertyDefinition.set = function proxySetter(val) {
      this[sourceKey][key] = val; // vm._data.root = val
    };
    // vm._data 数据对象
    // vm root    监听 vm.root === vm._data.root; sharedPropertyDefinition 加的钩子
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }
  // 初始化状态值
  function initState(vm) {
    var opts = vm.$options;
    if (opts.data) { // data == mergedInstanceDataFn
      initData(vm);
    } else {
      // observe(vm._data = {}, true /* asRootData */);
    }
  }
  // 初始化data
  function initData(vm) {
    // 校验数据对象data是否是一个纯对象
    var data = vm.$options.data;// data = mergedInstanceDataFn
    // 因为beforeCreate钩子可以修改data,所以要再判断一次data当前值是否是函数
    data = vm._data = typeof data === 'function'
      ? data(vm, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      console.error(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // 校验data是否与props、methods冲突了【key】
    // 实例上的代理数据
    var keys = Object.keys(data); // 所有key的属性
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          console.error(
            // methods 对象上的 key 属性,已经被定义为 data 数据对象属性
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        console.error(
          // data的数据属性 key 因为成为props 的prop;prop是该属性的默认值。
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if (!isReserved(key)) {
        // 数据代理的时候是否有不合理的属性
        // vm._data === 以获取数据对象的引用
        proxy(vm, "_data", key);
      }
    }
    // 观察数据,开启响应式系统之路
    // observe(data, true /* asRootData */);
  }
  function initMixin(Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      // 选项合并,给$options插入方法,参数
      vm.$options = mergeOptions(
        // 返回vue选项的引用
        Vue.options, // Vue.options
        options || {}, // options
        vm // Vue
      );
      // 执行钩子函数beforeCreate
      callHook(vm, 'beforeCreate') // 这里可以修改data
      initState(vm); // 数据初始化 data
      console.log(vm.$options)
      if (vm.$options.el) {
        vm.$mount(vm.$options.el);
      }
    };
  }
  // config ,Vue全局配置对象
  function initGlobalAPI(Vue) {
    var configDef = {};
    configDef.get = function () { return config; };
    {
      configDef.set = function (newVal) {
        console.error('不要尝试修改Vue.config的引用',
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // 让Vue可访问config
    // 不直接写Vue.config而是监听,就是防止Vue.config引用被篡改
    Object.defineProperty(Vue, 'config', configDef);// 监听你对Vue.config属性的访问,暴漏出自定义策略接口
  }
  function initExtend(Vue) {
    /**
      *每个实例构造函数(包括Vue)都有一个唯一的
      *cid。这使我们能够创建包装的“子对象”
      *“构造函数”用于原型继承并缓存它们。
      */
    //  用于原型继承   缓存构造函数
    Vue.cid = 0;
    var cid = 1;
    /**
     * 类继承
     */
    Vue.extend = function (extendOptions) {
      extendOptions = extendOptions || {};
      var Super = this; // Super === Vue
      var SuperId = Super.cid;
      // 缓存检测 cachedCtors
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      // 缓存处理cachedCtors[0] = 子类的引用
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
      var name = extendOptions.name || Super.options.name;
      if (name) {
        // validateComponentName(name); // 组件名称规范检查
      }
      // 子类 构造函数
      var Sub = function VueComponent(options) {
        this._init(options);
      };
      // 把Vue的原型引用赋给Sub
      // {}.__proto__ = Super.prototype = Vue.prototype
      Sub.prototype = Object.create(Super.prototype);
      Sub.prototype.constructor = Sub;
      Sub.cid = cid++;
      // 组件在初始化 mergeOptions 选项的合并 => 规范的检测 => 策略的处理
      Sub.options = mergeOptions(
        Super.options, // Vue.options
        extendOptions // 组件的选项对象
      );
      Sub['super'] = Super;
      //对于props和computed属性,我们在
      //在扩展原型上扩展时的Vue实例。这
      //避免为创建的每个实例调用Object.defineProperty。
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }
      // 允许进一步扩展/混合/插件使用 extension/mixin/plugin
      // Sub.extend = Super.extend;
      // Sub.mixin = Super.mixin;
      // Sub.use = Super.use;
      //创建资产寄存器,以便扩展类
      //也可以拥有他们的私人资产。
      // Super == Vue Vue.component【注册全局组件的一种方法】 不等于  Vue.options.components【挂载内置抽象组件】
      ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type];
      });
      //启用递归自查找
      // if (name) {
      //   Sub.options.components[name] = Sub;
      // }
      //在扩展时保留对超级选项的引用。
      //稍后在实例化时,我们可以检查Super的选项是否有
      //已更新。
      // Sub.superOptions = Super.options;
      // Sub.extendOptions = extendOptions;
      // Sub.sealedOptions = extend({}, Sub.options);
      // 缓存构造函数
      cachedCtors[SuperId] = Sub;
      return Sub
    };
  }
  // 拿到空对象的引用
  Vue.options = Object.create(null);
  // 添加内置组件'components',指令'directives',过滤器'filters',他们都加一个空对象的引用
  ASSET_TYPES.forEach(function (type) {
    // ASSET_TYPES 常量复用
    Vue.options[type + 's'] = Object.create(null);// Vue.options.components
  });
  function Vue(options) {
    if (!(this instanceof Vue)) { // 得new一个Vue,不然就报错
      console.error('Vue is a constructor and should be called with the `new` keyword');
    }
    // 初始化
    this._init(options);
  }
  /**
 * 如果元素选择器不是元素,则查询它
 */
  function query(el) { // query(el)   el === 字符串 || DOM对象
    if (typeof el === 'string') {
      var selected = document.querySelector(el);
      if (!selected) {
        console.error(
          'Cannot find element: ' + el
        );
        return document.createElement('div')
      }
      return selected
    } else {
      return el
    }
  }
  // 挂载组件 mountComponent
  function mountComponent() {
    // 核心方法 
    // vm._render()   生成的虚拟节点:会调用vm.$options.render  返回:生成的虚拟节点【vnode】
    // vm._update   把vm._render()生成的虚拟节点 渲染成真正的DOM
    // new Watcher(vm,updateComponent) 实例   渲染函数观察者 updateComponent——>vm.$options.render  ——>get拦截器   重新渲染,依赖收集
  }
  // 公共装载法  runtime时的代码
  // 1.定义$mount:运行时的mount
  Vue.prototype.$mount = function (el, hydrating) {
    // 渲染函数  组件的挂载
    el = el && inBrowser ? query(el) : undefined;
    return mountComponent(this, el, hydrating)
  };
  var idToTemplate = cached(function (id) { // 获取对应id内的DOM
    var el = query(id);
    return el && el.innerHTML
  });
  // 2.缓存mount
  var mount = Vue.prototype.$mount
  // 3.重构$mount;完整版本 = 运行时 + compiler【单独打包,减少体积量】【编译器 ——> render function】
  // cli——>构建时编译
  Vue.prototype.$mount = function (el, hydrating) {
    el = el && query(el);
    /* istanbul ignore if */
    if (el === document.body || el === document.documentElement) {
      console.error(
        "不要将Vue装载到<html>或<body>-而是装载到普通元素。挂载点是会被组件元素替换的,html 和 body 是页面基本元素"
      );
      return this
    }
    var options = this.$options;
    // 解析 模板/el 并转换为渲染函数
    if (!options.render) {
      var template = options.template;
      if (template) {
        if (typeof template === 'string') {
          if (template.charAt(0) === '#') { // 字符串第0索引下的字符串是#
            template = idToTemplate(template); // 获取id下的innerHTML
            /* istanbul ignore if */
            if (!template) {
              console.error(
                ("Template element not found or is empty: " + (options.template)),
                this
              );
            }
          }
        } else if (template.nodeType) { // Dom 的节点找到
          template = template.innerHTML;
        } else {
          {
            console.error('无效的模板选项:' + template, this);
          }
          return this
        }
      } else if (el) {
        template = getOuterHTML(el);
      }
      // 获取dom完毕
      if (template) {
        // template  模板=> render function => AST
        // {} 开发者拥有定制编译器的能力
        /* istanbul ignore if 编译器性能统计 */
        // if (config.performance && mark) { // 编译器性能统计
        //   mark('compile');
        // }
        // 开始模板编译 compileToFunctions
        var ref = compileToFunctions(template, {
          outputSourceRange: "development" !== 'production',
          shouldDecodeNewlines: shouldDecodeNewlines, // 兼容
          shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, // 兼容
          delimiters: options.delimiters, // {{}} => ${}  改变文本插入符
          comments: options.comments // true 会保留备注;保留渲染模板中的注释
        }, 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');
        // }
      }
    }
    console.log(template)
    // 内置一个编译器
    return mount.call(this, el, hydrating)
  }
  initMixin(Vue);
  initGlobalAPI(Vue);
  initExtend(Vue)
  return Vue;
}));

一张AI生成图~

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值