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

news2024/11/28 1:58:07

目录

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生成图~

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/754495.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

身份证读卡器安卓SDK在安卓12版本targetSdkVersion=32报错解决办法

之前的东信智能的EST-100身份证读卡器安卓SDK版本V1.0.40在安卓12版本&#xff0c;targetSdkVersion32的时候会出现以下错误&#xff1a; Targeting S (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingInten…

Nginx 解决漏洞扫描 弱CORS策略

主要在nginx配置允许通过的地址 如&#xff1a; if ($http_host !~* 192.168.0.1|127.0.0.1|localhost) { # 允许的ipreturn 403 ; }add_header Access-Control-Allow-Origin $http_origin; #跨域请求

高压放大器到底有什么作用

高压放大器是一种重要的电子元器件&#xff0c;其作用是将信号放大到更高的电压水平&#xff0c;以便供给需要高电压的负载使用。高压放大器被广泛应用于通讯设备、医疗仪器、仿真模拟、气体激光、光学器件等领域。下面安泰电子将详细介绍高压放大器的作用以及其在各领域中的应…

Win11 设置FTP服务详细教程

起因&#xff1a; 因测试需要&#xff0c;本机建立FTP服务测试使用&#xff0c;此文章用于记录使用&#xff01; 操作步骤&#xff1a; 1、配置FTP功能 ①、"winR" > 在运行窗口输入"control" 回车&#xff1b; ②、打开"控制面板" > 点击…

图文讲解Redis延时双删原因及必要性

目录 一、前言 二、常见更新策略 2.1 先删缓存&#xff0c;再更新数据库 2.2 先更新数据库&#xff0c;再删除缓存 2.3 普通双删 2.4 延迟双删 三、建议 一、前言 我们在实际项目中经常会使用到Redis缓存用来缓解数据库压力&#xff0c;但是当更新数据库时&#xff0c;…

今天实习第三天,vue(cli部分)

01.创建第一个vue-cli。这里用的是node.js。早上的时候&#xff0c;就需要把node.js安装上去 02.node.js安装 第一步.去官网下载node.js https://nodejs.org/en 第二步.运行官网下载的node.js的msi文件&#xff08;记住所有的node.js文件的安装包都是msi文件的形式&#xff0…

算法笔记\python 笔记: 相似性度量

1 欧氏距离 1.1 python实现&#xff1a; from scipy.spatial import distance distance.euclidean([1,2],[2,1]) #1.4142135623730951 1.2 标准化欧氏距离 先将数据标准化 &#xff08;减去的均值两两抵消&#xff09; 2 曼哈顿距离 又称为城市街区距离 2.1 python 实现 f…

C++图形开发(16):绘制一个圆环和一根针

文章目录 绘制一个圆环和一根针1.1 绘制1.2 line()函数1.3 circle()函数1.4 setlinestyle()函数1.5 setlinecolor()函数 接下来&#xff0c;我会继续制作一些小游戏&#xff0c;但因为整个难度的上升&#xff08;毕竟我也是初学者&#xff09;&#xff0c;可能文章不会再像之前…

吐血整理,性能测试-Jmeter分布式压测实战(超细详解)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Jmeter的集群模式…

InsCode Stable Diffusion使用教程【InsCode Stable Diffusion美图活动一期】

记录一下如何使用 InsCode Stable Diffusion 进行 AI 绘图以及使用感受。 一、背景介绍 目前市面上比较权威&#xff0c;并能用于工作中的 AI 绘画软件其实就两款。一个叫 Midjourney&#xff08;简称 MJ&#xff09;&#xff0c;另一个叫 Stable Diffusion&#xff08;简称 …

Unity游戏源码分享-Unity经营类美食小摊小游戏

Unity经营类美食小摊小游戏 挺有意思的小游戏 关卡页面 游戏主页面 有顾客上门 需要给顾客搭配他们想要的美食 会不断地有顾客过来&#xff0c;这个时候就考验手速的时候了&#xff0c;真实模拟经营 服务到位立马有钱 项目地址&#xff1a; https://download.csdn.net/downl…

VisualStudio2022将printf信息打印到控制台

点击“解决方案管理器”&#xff0c;选中项目名称&#xff0c;点击鼠标右键---->属性---->生成事件---->生成后事件&#xff0c;在命令行的右侧输入框里填写如下内容&#xff1a; editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\$(ProjectName).exe接下来在编译运行时&#x…

数据库--->MySQL(2)【事务、SQL优化】

文章目录 事务什么是事务&#xff1f;隔离性中的不同隔离级别事务实现的原理隔离级别的实现原理&#xff08;MVCC&#xff09;MySQL中的锁机制 SQL优化 事务 什么是事务&#xff1f; 事务就是逻辑上的一组操作&#xff0c;在同一个事务中&#xff0c;如果有多条sql语句执行&am…

一文详解新一代高效前端构建工具VITE-达观数据

Vite 是一个快速、简单且高效的前端构建工具&#xff0c;它的出现为前端开发者带来了新的构建体验。在本文中&#xff0c;我们将探讨 Vite 的技术原理、优点和使用方法。 Vite 的技术原理 Vite 的核心技术是基于 ES Modules 和浏览器原生模块系统的构建工具。Vite 的构建过程是…

多态的基本使用

这部分的内容主要是记住使用方法&#xff0c;原理在之后会讲。 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 比如说买票&#xff0c;普通人买票就是正常买&…

WTM框架页面被其他网站引用免登录

用ASP.NET CORE开发通常都会有这样一个需求&#xff0c;自己框架开发的页面&#xff0c;要被其他网站嵌套引用&#xff0c;但其他网站通过链接到自己的开发页面的时候&#xff0c;通常会有一个登录页面&#xff0c;有的时候网站无缝集成的时候&#xff0c;这就会要求跳过这个WT…

前端实现 DIV 高度只有100px,宽度只有100px ,我要在这个DIV放一个宽度200的DIV,左右拉动滚动条显示

<!DOCTYPE html> <html> <head><title>点击监听两组span标签</title><style>.outer-div {width: 100px;height: 100px;overflow-x: scroll;background-color: #abc1ee;}.inner-div {width: 200px;}/* 自定义滚动条样式 */.outer-div::-web…

Java 的集合

一、Collection 1、ArrayList 底层采用数组实现&#xff0c;操作大多基于对数组的操作。 在添加和删除时需要做 System.arraycopy(native层方法) 拷贝工作。 添加元素时可能会扩容&#xff0c;这要大量的拷贝工作&#xff0c;删除元素时&#xff0c;会把后面的元素向前拷贝。…

剑指oferr68-II.二叉树的最近公共祖先

为什么这道题的难度是easy&#xff0c;我感觉挺难的啊&#xff0c;我想了挺久没有一点思路就直接看题解了。题解有两种解法&#xff0c;先看第一种存储父节点 class Solution {Map<Integer,TreeNode> parent new HashMap<Integer,TreeNode>();Set<Integer>…

ffmpeg2段视频合成一段

查看分辨率 帧率和编码器 ffprobe -v error -select_streams v:0 -show_entries streamcodec_name,width,height,avg_frame_rate -of defaultnoprint_wrappers1 rs2.mp4得到&#xff0c;编码器&#xff0c;分辨率&#xff0c;还有帧率 codec_nameh264 width1920 height1080 avg…