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

这一节争取搞完!

  

  回头来看看那个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……

时间: 2024-08-06 11:55:27

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

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

这一节肯定能完! 经过DOM字符串的AST转化,再通过render变成vnode,最后就剩下patch到页面上了. render函数跑完应该是在这里: function mountComponent(vm, el, hydrating) { vm.$el = el; if (!vm.$options.render) { vm.$options.render = createEmptyVNode; { // warning } } // beforeMount var updateComponen

Vue源码后记-钩子函数

vue源码的马拉松跑完了,可以放松一下写点小东西,其实源码讲20节都讲不完,跳了好多地方. 本人技术有限,无法跟大神一样,模拟vue手把手搭建一个MVVM框架,然后再分析原理,只能以门外汉的姿态简单过一下~ 想到什么写什么了,这节就简单说说钩子函数吧! vue中的钩子函数主要包含初始化的beforeCreated/created,Virtual Dom更新期间的beforeUpdate/updated,页面渲染期间的beforeMount/mounted,组件销毁期间的beforeDestroy

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

-- 指令这个讲起来还有点复杂,先把html弄上来: <body> <div id='app'> <div v-if="vIfIter" v-bind:style="styleObject"> <input v-show="vShowIter" v-model='vModel' /> <span v-once>{{msg}}</span> <div v-html=&qu

Vue源码探究-虚拟DOM的渲染

Vue源码探究-虚拟DOM的渲染 在虚拟节点的实现一篇中,除了知道了 VNode 类的实现之外,还简要地整理了一下DOM渲染的路径.在这一篇中,主要来分析一下两条路径的具体实现代码. 按照创建 Vue 实例后的一般执行流程,首先来看看实例初始化时对渲染模块的初始处理.这也是开始 mount 路径的前一步.初始包括两部分,一是向 Vue 类原型对象上挂载渲染相关的方法,而是初始化渲染相关的属性. 渲染的初始化 下面代码位于vue/src/core/instance/render.js 相关属性初始

[Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串

本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsunlei/javascri

vue源码之响应式数据

分析vue是如何实现数据响应的. 前记 现在回顾一下看数据响应的原因. 之前看了vuex和vue-i18n的源码, 他们都有自己内部的vm, 也就是vue实例. 使用的都是vue的响应式数据特性及$watchapi. 所以决定看一下vue的源码, 了解vue是如何实现响应式数据. 本文叙事方式为树藤摸瓜, 顺着看源码的逻辑走一遍, 查看的vue的版本为2.5.2. 目的 明确调查方向才能直至目标, 先说一下目标行为: vue中的数据改变, 视图层面就能获得到通知并进行渲染. $watchapi监

[Vue源码]一起来学Vue双向绑定原理-数据劫持和发布订阅

有一段时间没有更新技术博文了,因为这段时间埋下头来看Vue源码了.本文我们一起通过学习双向绑定原理来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsun

[Vue源码]一起来学Vue模板编译原理(一)-Template生成AST

本文我们一起通过学习Vue模板编译原理(一)-Template生成AST来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsunlei/javascrip

vue源码解读(一)Observer/Dep/Watcher是如何实现数据绑定的

欢迎star我的github仓库,共同学习~目前vue源码学习系列已经更新了5篇啦~ https://github.com/yisha0307/... 快速跳转: Vue的双向绑定原理(已完成) 说说vue中的Virtual DOM(已完成) React diff和Vue diff实现差别 Vue中的异步更新策略(已完成) Vuex的实现理解 Typescript学习笔记(持续更新ing) Vue源码中闭包的使用(已完成) 介绍 最近在学习vue和vuex的源码,记录自己的一些学习心得.主要借鉴