zoukankan      html  css  js  c++  java
  • 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……

  • 相关阅读:
    mongodb
    python中读取文件的read、readline、readlines方法区别
    uva 129 Krypton Factor
    hdu 4734
    hdu 5182 PM2.5
    hdu 5179 beautiful number
    hdu 5178 pairs
    hdu 5176 The Experience of Love
    hdu 5175 Misaki's Kiss again
    hdu 5174 Ferries Wheel
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/7280489.html
Copyright © 2011-2022 走看看