这一节争取搞完!

  

  回头来看看那个render代码,为了便于分析,做了更细致的注释;

    (function() {
// 这里this指向vue对象 下面的所有方法默认调用Vue$3.prototype上的方法
with(this){
return _c/*方法调用 => has拦截器过滤*/
('div',{attrs:{"id":"app"}},
_l/*方法调用 => has拦截器过滤*/(
(items/*_data属性访问 => 自定义proxy过滤*/),
function(item){
return _c/*方法调用 => has拦截器过滤*/
('a',{attrs:{"href":"#"}},
[_v/*方法调用 => has拦截器过滤*/
(_s/*方法调用 => has拦截器过滤*/(item))])
}))
}
})

  所有的has拦截器之前分析过了,跳过,但是这里又多了一个特殊的访问,即items,但是Vue$3上并没有这个属性,属性在Vue$3._data上,如图:,那这是如何访问到的呢?

  Vue在initState的时候自己又封装了一个proxy,所有对属性的访问会自动跳转到_data上,代码如下:

    Vue.prototype._init = function(options) {
// code... // 这里处理是ES6的Proxy
{
initProxy(vm);
} // beforeCreate initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created'); // code...
}; function initState(vm) {
// if...
if (opts.data) {
initData(vm);
} else {
// 没有data参数
observe(vm._data = {}, true /* asRootData */ );
}
// if...
} function initData(vm) {
// code... while (i--) {
if (props && hasOwn(props, keys[i])) {
// warning
} else if (!isReserved(keys[i])) {
proxy(vm, "_data", keys[i]);
}
}
// observe data...
} // target => vm
// sourceKey => _data 这个还有可能是props 不过暂时不管了
// key => data参数中所有的对象、数组
function proxy(target, sourceKey, key) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}

  可以看到,最后一个函数中,通过defineProperty方法,所有对vm属性的直接访问会被跳转到Vue$3[sourceKey]上,这里指就是_data属性。

  而这个属性的读写,同样被特殊处理过,即数据劫持,跑源码的时候也讲过,直接贴核心代码:

    function defineReactive$$1(obj, key, val, customSetter) {
// var... Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
}
if (Array.isArray(value)) {
dependArray(value);
}
}
return value
},
set: function reactiveSetter(newVal) {
// set...
}
});
}

  简单来讲,所有对_data上的属性的读写都会被拦截并调用自定义的get、set方法,这里也不例外,数据会被添加到依赖接受监听,详细过程太细腻就不贴了,有兴趣可以自己去跑跑。

  访问items后,数组中的元素会被watch,有变化会通知DOM进行更新,这里接下来会执行_l方法:

    Vue.prototype._l = renderList;

    // val => items
// render => function(item){...}
function renderList(val, render) {
var ret, i, l, keys, key;
// 数组 => 遍历进行值渲染
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
}
// 纯数字 => 处理类似于item in 5这种无数据源的模板渲染
else if (typeof val === 'number') {
ret = new Array(val);
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
}
// 对象 => 取对应的值进行渲染
else if (isObject(val)) {
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);
}
}
return ret
}

  代码还是清晰的,三种情况:数组、纯数字、对象。

  用过应该都明白是如何处理三种情况的,这里将对应的值取出来调用render方法,这个方法来源于第二个参数:

    // item => 1,2,3,4,5
(function(item) {
return _c('a', {attrs: {"href": "#"}}, [_v(_s(item))])
})

  方法很抽象,慢慢解析。

  因为与tag相关,所以再次调用了_c函数,但是执行顺序还是从内到外,因此会对_v、_s做过滤并首先调用_s函数:

    Vue.prototype._s = toString;

    // val => item => 1,2,3,4,5
function toString(val) {
return val == null ?
'' :
typeof val === 'object' ?
JSON.stringify(val, null, 2) :
String(val)
}

  这个方法一句话概括就是字符串化传进来的参数。

  这里先传了一个数字1,返回字符串1并将其作为参数传入_v函数:

    Vue.prototype._v = createTextVNode;

    // val => 1
function createTextVNode(val) {
return new VNode(undefined, undefined, undefined, String(val))
}

  这个函数从命名也能看出来,创建一个文本的vnode,值为传进来的参数。

  可以看一眼这个虚拟DOM的结构:,因为是文本节点,所以只有text是有值的。

  形参都处理完毕,下一步进入_c函数,看下代码:

    vm._c = function(a, b, c, d) {
return createElement(vm, a, b, c, d, false);
}; var SIMPLE_NORMALIZE = 1;
var ALWAYS_NORMALIZE = 2; function createElement(context, tag, data, children, normalizationType, alwaysNormalize) {
// 参数修正
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)
} // context => vm
// tag => 'a'
// data => {attr:{'href':'#'}}
// children => [vnode...]
// normalizationType => undefined
// alwaysNormalize => false
function _createElement(context, tag, data, children, normalizationType) {
if (isDef(data) && isDef((data).__ob__)) {
// warning...
return createEmptyVNode()
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// 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;
// 判断标签是否为math、SVG
// math是HTML5新出的标签 用来写数学公式
// SVG就不用解释了吧……
ns = config.getTagNamespace(tag);
// 判断标签是否为内置标签
if (config.isReservedTag(tag)) {
// 生成vnode
// config.parsePlatformTagName返回传入的值 是一个傻逼函数
vnode = new VNode(
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
);
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag);
} else {
// 未知标签
vnode = new VNode(
tag, data, children,
undefined, undefined, context
);
}
} else {
// direct component options / constructor
vnode = createComponent(tag, data, context, children);
}
if (isDef(vnode)) {
// 特殊标签处理
if (ns) {
applyNS(vnode, ns);
}
return vnode
} else {
return createEmptyVNode()
}
}

  其实吧,这函数看起来那么长,其实也只能根据传进去的参数生成一个vnode,具体过程看注释,看看结果:

  可以看出,属性还是那样子,没怎么变,children是之前生成的那个文本虚拟DOM。

  

  在renderList函数中,循环调用render,分别传进去items数组的1、2、3、4、5,所以依次生成了5个vnode,作为数组ret的元素,最后返回一个数组:

  接下来进入外部的_c函数,这一次是对div标签进行转化,过程与上面类似,最后生成一个完整的虚拟DOM,如下所示:

  这里也就将整个挂载的DOM转化成了虚拟DOM,其实吧,一点也不难,是吧!

  要不先这样,下一节再patch……

Vue源码后记-vFor列表渲染(2)的更多相关文章

  1. Vue源码后记-vFor列表渲染(1)

    钩子函数比较简单,没有什么意思,这一节搞点大事情 => 源码中v-for的渲染过程. vue的内置指令包含了v-html.v-if.v-once.v-bind.v-on.v-show等,先从一个 ...

  2. Vue源码后记-vFor列表渲染(3)

    这一节肯定能完! 经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了. render函数跑完应该是在这里: function mountComponent( ...

  3. Vue源码后记-其余内置指令(3)

    其实吧,写这些后记我才真正了解到vue源码的精髓,之前的跑源码跟闹着玩一样. go! 之前将AST转换成了render函数,跳出来后,由于仍是字符串,所以调用了makeFunction将其转换成了真正 ...

  4. Vue源码后记-钩子函数

    vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过 ...

  5. Vue源码后记-其余内置指令(2)

    -- 指令这个讲起来还有点复杂,先把html弄上来: <body> <div id='app'> <div v-if="vIfIter" v-bind ...

  6. Vue源码后记-其余内置指令(1)

    把其余的内置指令也搞完吧,来一个全家桶. 案例如下: <body> <div id='app'> <div v-if="vIfIter" v-bind ...

  7. Vue源码后记-更多options参数(1)

    我是这样计划的,写完这个还写一篇数据变动时,VNode是如何更新的,顺便初探一下diff算法. 至于vue-router.vuex等插件源码,容我缓一波好吧,vue看的有点伤. 其实在之前讲其余内置指 ...

  8. vue项目开发之v-for列表渲染的坑

    不知道大家在用vue开发的过程中有没有遇到过在使用v-for的时候会出现大片的黄色警告,比如下图: 其实这是因为没有写key的原因 :key是为vue的响应式渲染提供方法,在列表中单条数据改变的情况下 ...

  9. vue源码解析阅读列表

    https://zhuanlan.zhihu.com/p/24435564 开发vue(或类似的MVVM框架)的过程中,需要面对的主要问题有哪些? 剖析vue实现原理,自己动手实现mvvm 官网介绍

随机推荐

  1. Mybatis源码解析-DynamicSqlSource和RawSqlSource的区别

    XMLLanguageDriver是ibatis的默认解析sql节点帮助类,其中的方法其会调用生成DynamicSqlSource和RawSqlSource这两个帮助类,本文将对此作下简单的简析 应用 ...

  2. 自定义Django的中间件

    分析Django的生命周期,我们知道所有的http请求都要经过Django的中间件. 假如现在有一个需求,所有到达服务端的url请求都在系统中记录一条日志,该怎么做呢? Django的中间件的简介 D ...

  3. JVM 菜鸟进阶高手之路九(解惑)

    转载请注明原创出处,谢谢! 在第八系列最后有些疑惑的地方,后来还是在我坚持不懈不断打扰笨神,阿飞,ak大神等,终于解决了该问题.第八系列地址:http://www.cnblogs.com/lirenz ...

  4. BZOJ2748_音量调节_KEY

    [HAOI2012]音量调节 Time Limit: 3 Sec Memory Limit: 128 MB Description 一个吉他手准备参加一场演出.他不喜欢在演出时始终使用同一个音量,所以 ...

  5. Codeforces Round #436 (Div. 2) E. Fire

    http://codeforces.com/contest/864/problem/E 题意: 有一堆物品,每个物品有3个属性,需要的时间,失效的时间(一开始)和价值.只能一件一件的选择物品(即在选择 ...

  6. DOM中元素对象的属性方法

    在 HTML DOM (文档对象模型)中,每个部分都是节点. 节点是DOM结构中最基本的组成单元,每一个HTML标签都是DOM结构的节点. 文档是一个    文档节点 . 所有的HTML元素都是    ...

  7. snsapi_base和snsapi_userinfo

    1.以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的.用户感知的就是直接进入了回调页(往往是业务页面) 2.以snsap ...

  8. 第4章 同步控制 Synchronization ----同步机制的摘要

    同步机制摘要Critical Section Critical section(临界区)用来实现"排他性占有".适用范围是单一进程的各线程之间.它是:  一个局部性对象,不是一个核 ...

  9. 【NOIP】OpenJudge - 15:银行利息

    #include<stdio.h>//银行利息 int main() { float a,b; int i,c,d; scanf("%f%f%d",&a,&am ...

  10. 基于FPGA的肤色识别算法实现

    大家好,给大家介绍一下,这是基于FPGA的肤色识别算法实现. 我们今天这篇文章有两个内容一是实现基于FPGA的彩色图片转灰度实现,然后在这个基础上实现基于FPGA的肤色检测算法实现. 将彩色图像转化为 ...