vue2.x(虚拟dom&vnode)

如何理解vue中的vnode虚拟dom?

return _c(
    'div',
    { attrs: { id: 'app' } },
    [
      _c('span', { on: { click: onClick } }, [_v('我是data数据' + _s(message))]),
      _v(' '), // 没整懂 为啥每个中间都夹杂个空VNode(在编译阶段时 遇到同级兄弟节点 会默认生成一个空本文节点)
      _l(dsArr, function (item, index) {
        return _c('p', { key: index }, [_v('我是v-for' + _s(index))]);
      }),
      _v(' '), // 没整懂 为啥每个中间都夹杂个空VNode(在编译阶段时 遇到同级兄弟节点 会默认生成一个空本文节点)
      !!message ? _c('span', [_v('我是v-if')]) : _e(),
      _v(' '), // 没整懂 为啥每个中间都夹杂个空VNode(在编译阶段时 遇到同级兄弟节点 会默认生成一个空文节点)
      _c('base-show'), //  with(this){return _c('h3',[_v(_s(myMessage))])}
    ],
    2
  );
}
// 1.1 定义_c 源码 3485行
function initRender(vm) {
  vm._vnode = null; // the root of the child tree
  vm._staticTrees = null; // v-once cached trees
  var options = vm.$options;
  var parentVnode = (vm.$vnode = options._parentVnode); // the placeholder node in parent tree
  var renderContext = parentVnode && parentVnode.context;
  vm.$slots = resolveSlots(options._renderChildren, renderContext);
  vm.$scopedSlots = emptyObject;
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // internal version is used by render functions compiled from templates

  // TODO:这里在实例上面挂载_c和$createElement两个方法 均访问的是createElement函数
  vm._c = function (a, b, c, d) {
    // 但是有点不同的地方是_c这里第6个参数'alwaysNormalize'给的false
    return createElement(vm, a, b, c, d, false);
  };
  // normalization is always applied for the public version, used in
  // user-written render functions.
  vm.$createElement = function (a, b, c, d) {
    // 但是有点不同的地方是_c这里第6个参数'alwaysNormalize'给的true
    return createElement(vm, a, b, c, d, true);
  };

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

  /* istanbul ignore else */
  // TODO: 在之前分析data响应式时老是有两个属性先设置 就是在这个地方挂载的
  // $attrs和$listeners均只能读取 不能写入
  {
    defineReactive$$1(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      function () {
        !isUpdatingChildComponent && warn('$attrs is readonly.', vm);
      },
      true
    );
    defineReactive$$1(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      function () {
        !isUpdatingChildComponent && warn('$listeners is readonly.', vm);
      },
      true
    );
  }
}

// 开始下面前 先执行renderMixin(Vue) -> installRenderHelpers(Vue.prototype)
// 给Vue原型上添加各种方法 所以上面能访问_c\_s等等操作 是在这里绑定的
// 源码3519行
function renderMixin(Vue) {
  installRenderHelpers(Vue.prototype);
  // .....
}
// 源码 2940行
function installRenderHelpers(target) {
  target._o = markOnce;
  target._n = toNumber;
  target._s = toString;
  target._l = renderList;
  target._t = renderSlot;
  target._q = looseEqual;
  target._i = looseIndexOf;
  target._m = renderStatic;
  target._f = resolveFilter;
  target._k = checkKeyCodes;
  target._b = bindObjectProps;
  target._v = createTextVNode;
  target._e = createEmptyVNode;
  target._u = resolveScopedSlots;
  target._g = bindObjectListeners;
  target._d = bindDynamicKeys;
  target._p = prependModifier;
}

// 1.1.1(源码 96行) 上面函数首先执行的是内层(基本操作)即:
// _s(message)
// 这个地方还涉及到一个问题就是 当执行_s这个函数时message->this.message(with作用)->触发getter(Object.definedProperty)
// 拿到真实的值
function toString(val) {
  // TODO: 这里值得关注的是JSON.stringify用法
  // JSON.stringify({obj:1,age:2}) => "{"obj":1,"age":2}"
  // JSON.stringify({obj:1,age:2},null,2) => "{
  //                                           "obj": 1,
  //                                           "age": 2
  //                                         }"
  // 表示解析所有属性且解析出来属性前面空格2格
  return val == null ? '' : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) ? JSON.stringify(val, null, 2) : String(val);
}

// 1.1.2(源码 821行)再执行 即:
// _v('我是data数据test')
function createTextVNode(val) {
  return new VNode(undefined, undefined, undefined, String(val));
}
// VNode终于出来了 (源码 767行)
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;
}

// 1.1.3 createElement函数 源码 3344行
/**
 *
 * @param {*} context 在initRender里面这个参数就是传的vm实例
 * @param {*} tag 标签 比如div span
 * @param {*} data 这个在ast->element时对应每个标签作了处理 比如上面的就是{on:{click:onClick}}
 * @param {*} children 当前标签下的child
 * @param {*} normalizationType 0:不需要规则化 1:需要简单规划 2:完全需要规划
 * @param {*} alwaysNormalize true&false
 */
function createElement(context, tag, data, children, normalizationType, alwaysNormalize) {
  // TODO: 这里判断data是否为数组结构或者原始数据 如string\number\symbol\boolean
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children;
    children = data;
    data = undefined;
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE;
  }
  return _createElement(context, tag, data, children, normalizationType);
}

// 1.1.3.1
function _createElement(context, tag, data, children, normalizationType) {
  if (isDef(data) && isDef(data.__ob__)) {
    warn('Avoid using observed data object as vnode data: ' + JSON.stringify(data) + '\n' + 'Always create fresh vnode data objects in each render!', context);
    return createEmptyVNode();
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is;
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode();
  }
  // warn against non-primitive key
  if (isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {
    {
      warn('Avoid using non-primitive value as key, ' + 'use string/number value instead.', context);
    }
  }
  // support single function children as default scoped slot
  if (Array.isArray(children) && typeof children[0] === 'function') {
    data = data || {};
    data.scopedSlots = { default: children[0] };
    children.length = 0;
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children);
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children);
  }
  var vnode, ns;
  if (typeof tag === 'string') {
    var Ctor;
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      if (isDef(data) && isDef(data.nativeOn)) {
        warn('The .native modifier for v-on is only valid on components but it was used on <' + tag + '>.', context);
      }
      vnode = new VNode(config.parsePlatformTagName(tag), data, children, undefined, undefined, context);
    } else if ((!data || !data.pre) && isDef((Ctor = resolveAsset(context.$options, 'components', tag)))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag);
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(tag, data, children, undefined, undefined, context);
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children);
  }
  if (Array.isArray(vnode)) {
    return vnode;
  } else if (isDef(vnode)) {
    if (isDef(ns)) {
      applyNS(vnode, ns);
    }
    if (isDef(data)) {
      registerDeepBindings(data);
    }
    return vnode;
  } else {
    return createEmptyVNode();
  }
}

// 1.1.3.1 type为2时 拍平树状为一维数组 比如里面嵌套有v-for时需要将v-for生成的Vnode集合拍平
// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
function normalizeChildren(children) {
  return isPrimitive(children) ? [createTextVNode(children)] : Array.isArray(children) ? normalizeArrayChildren(children) : undefined;
}

// 1.1.3.2 type为1时 简易拍平树状为一维数组
// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.
function simpleNormalizeChildren(children) {
  for (var i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children);
    }
  }
  return children;
}

// 1.1.3.3
function normalizeArrayChildren(children, nestedIndex) {
  var res = [];
  var i, c, lastIndex, last;
  for (i = 0; i < children.length; i++) {
    c = children[i];
    // TODO: 1. 判断当前vnode是否存在或者是一个布尔值 即不再执行
    if (isUndef(c) || typeof c === 'boolean') {
      continue;
    }
    lastIndex = res.length - 1;
    last = res[lastIndex];
    //  nested
    // TODO: 2. 判断当前元素是否是一个数组 比如v-for生成的数据
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, (nestedIndex || '') + '_' + i);
        // merge adjacent text nodes
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + c[0].text);
          c.shift();
        }
        // TODO: 2.1 这里就将比如v-for生成的数组的节点vnode 压入了一维数组后面 将二维数组变成一维数组
        res.push.apply(res, c);
      }
    } 

    // TODO:3. 判断当前元素是否一个原始值
    else if (isPrimitive(c)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c);
      } else if (c !== '') {
        // convert primitive to vnode
        res.push(createTextVNode(c));
      }
    } 
    
    // TODO:4. 排除前面的情况
    else {
      // TODO: 4.1 是否当前节点是一个文本节点并且res的最后一个也是文本节点
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text);
      } 
      // TODO: 4.2 则为正常节点 直接push进res
      else {
        // default key for nested array children (likely generated by v-for)
        if (isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {
          // 这个地方 我觉得是兼容了当我们写v-for的时候 没有绑定key值 这里默认生成了一个key
          c.key = '__vlist' + nestedIndex + '_' + i + '__';
        }
        res.push(c);
      }
    }
  }
  return res;
}

// 1.1.4 (源码 2629行)再执行 即: v-for原理
// _l(dsArr, function (item, index) {return _c('p', { key: index }, [_v('我是v-for' + _s(index))]);})
function renderList(val, render) {
  var ret, i, l, keys, key;
  // TODO:1. 若in&of 后面跟的是数组或者字符串 则对应创建数组或字符串个数的对应个数的空数组
  if (Array.isArray(val) || typeof val === 'string') {
    ret = new Array(val.length);
    for (i = 0, l = val.length; i < l; i++) {
      // 这里将数组每一项传入匿名函数 所以我们经常使用item.xxx访问每一项的值
      ret[i] = render(val[i], i);
    }
  } else if (typeof val === 'number') {
    // TODO:2. 若in&of 后面跟的是数字 则创建对应数字的对应个数的空数组
    ret = new Array(val);
    for (i = 0; i < val; i++) {
      ret[i] = render(i + 1, i);
    }
  } else if (isObject(val)) {
    // TODO:3. 若in&of 后面跟的是对象 这种模式我们一般使用的很少

    // TODO: 3.1判断是否数据结构是symbol格式
    if (hasSymbol && val[Symbol.iterator]) {
      ret = [];
      var iterator = val[Symbol.iterator]();
      var result = iterator.next();
      while (!result.done) {
        ret.push(render(result.value, ret.length));
        result = iterator.next();
      }
    } else {
      // TODO:3.2若一般的格式 就获取key及key对应的value
      keys = Object.keys(val);
      ret = new Array(keys.length);
      for (i = 0, l = keys.length; i < l; i++) {
        key = keys[i];
        ret[i] = render(val[key], key, i);
      }
    }
  }
  // 若前面都不是 则作一层容错处理 置ret为空数组
  if (!isDef(ret)) {
    ret = [];
  }
  // 标识当前数组最后一项属性为_isVList为true
  ret._isVList = true;
  return ret;
}

// 1.1.5 (源码 812行)这里先执行this.message -> getter拿到真实值 -> 执行三目 -> 执行_c
// 这里分析下_e 当false时执行_e()
// !!message ? _c('span', [_v('我是v-if')]) : _e(),
function createEmptyVNode(text) {
  // TODO:等于说就创建个空的VNode
  if (text === void 0) text = '';
  var node = new VNode();
  node.text = text; // text等于空字符串
  node.isComment = true; // 并且这里改成了true
  return node;
}

// 1.1.6 (源码 3176行)再执行 即:
// _c('base-show') 这里遇到需要创建组件 组件名字为base-show
function createComponent(Ctor, data, context, children, tag) {
  if (isUndef(Ctor)) {
    return;
  }

  var baseCtor = context.$options._base;

  // plain options object: turn it into a constructor
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor);
  }

  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    {
      warn('Invalid Component definition: ' + String(Ctor), context);
    }
    return;
  }

  // TODO: 1. 处理异步组件
  // async component
  var asyncFactory;
  if (isUndef(Ctor.cid)) {
    asyncFactory = Ctor;
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor);
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      return createAsyncPlaceholder(asyncFactory, data, context, children, tag);
    }
  }

  data = data || {};

  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor);

  // TODO:2. 转化在组件上得v-model绑定
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data);
  }

  // TODO:3. 处理组件上的props绑定
  // extract props
  var propsData = extractPropsFromVNodeData(data, Ctor, tag);

  // TODO: 4. 处理函数式组件
  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children);
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  var listeners = data.on;
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn;

  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    var slot = data.slot;
    data = {};
    if (slot) {
      data.slot = slot;
    }
  }

  // install component management hooks onto the placeholder node
  installComponentHooks(data);

  // return a placeholder vnode
  var name = Ctor.options.name || tag;
  var vnode = new VNode(
    'vue-component-' + Ctor.cid + (name ? '-' + name : ''),
    data,
    undefined,
    undefined,
    undefined,
    context,
    { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
    asyncFactory
  );

  return vnode;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值