最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 你真的理解Vue的混入吗

    正文概述 掘金(沉默术士)   2021-05-23   650

    混入Mixin是Vue中非常常用的一个功能,也非常灵活,可以大大减少Vue组件中的重复功能,提高可维护性。
    先看下使用方法:

    // 定义一个混入对象
    var myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin!')
        }
      }
    }
    
    // 定义一个使用混入对象的组件
    var Component = Vue.extend({
      mixins: [myMixin]
    })
    
    var component = new Component() // => "hello from mixin!"
    

    Mixin的优势在于灵活的配置项合并,可以根据业务需求以恰当的方式进行合并,如组件选项合并,全局混入,及自定义合并策略等等。
    注意官方教程上有这么几句话:

    别小看上面上面几点提示,可能关键时候会直接影响到程序性能。分别看看这几句话在源码上如何实现的。

    先看下全局混入如何实现的:

    function initGlobalAPI(Vue){
        //...此前代码省略
        initMixin$1(Vue);
        //...此后代码省略
    }
    

    initGlobalAPI是全局函数,也是全局API的入口,其中就包含了全局混入。

    function initMixin$1(Vue) {
        //全局混入
        Vue.mixin = function (mixin) {
          this.options = mergeOptions(this.options, mixin);
          return this;
        };
    }
    

    由此可见核心方法在于mergeOptions,那mergeOptions干了些什么:

      /**
      这里主要干了两件事:
      1、检查和序列化选项;
      2、合并选项
      */
     function mergeOptions(parent, child, vm) {
        //检查是否是合法的组件
        {
          checkComponents(child);
        }
    
        if (typeof child === "function") {
          child = child.options;
        }
    
        normalizeProps(child, vm);
        normalizeInject(child, vm);
        normalizeDirectives(child);
        
        //这里的_base是Vue对象
        if (!child._base) {
          if (child.extends) {
            parent = mergeOptions(parent, child.extends, vm);
          }
          //mixins是数组,遍历多个混入
          if (child.mixins) {
            for (var i = 0, l = child.mixins.length; i < l; i++) {
              parent = mergeOptions(parent, child.mixins[i], vm);
            }
          }
        }
        //返回一个新的配置项
        var options = {};
        var key;
        //优先遍历父组件选项
        for (key in parent) {
          mergeField(key);
        }
        //再遍历子组件选项
        for (key in child) {
          if (!hasOwn(parent, key)) {
            mergeField(key);
          }
        }
        //注意这里的strats对象
        function mergeField(key) {
          var strat = strats[key] || defaultStrat;//合并策略
          options[key] = strat(parent[key], child[key], vm, key);
        }
        return options;
      }
    

    上面代码看起来挺简单,不就是合并对象吗,我们看下上面要注意的strats对象是什么就知道了。

    var strats = config.optionMergeStrategies;
    

    strats是一个全局变量,用来重写父选项和子选项的合并规则。,它的属性包括组件的所有配置项。

    //返回合并后的data
      strats.data = function (parentVal, childVal, vm) {
        if (!vm) {
          if (childVal && typeof childVal !== "function") {
            warn('The "data" option should be a function ' + "that returns a per-instance value in component " + "definitions.", vm);
    
            return parentVal;
          }
          return mergeDataOrFn(parentVal, childVal);
        }
    
        return mergeDataOrFn(parentVal, childVal, vm);
      };
    
    
    function mergeDataOrFn(parentVal, childVal, vm) {
        if (!vm) {
          // Vue.extend合并,都必须是函数
          if (!childVal) {
            return parentVal;
          }
          if (!parentVal) {
            return childVal;
          }
          
          return function mergedDataFn() {
            return mergeData(typeof childVal === "function" ? childVal.call(this, this) : childVal, typeof parentVal === "function" ? parentVal.call(this, this) : parentVal);
          };
        } else {
          return function mergedInstanceDataFn() {
            // instance merge
            var instanceData = typeof childVal === "function" ? childVal.call(vm, vm) : childVal;
            var defaultData = typeof parentVal === "function" ? parentVal.call(vm, vm) : parentVal;
            if (instanceData) {
              return mergeData(instanceData, defaultData);
            } else {
              return defaultData;
            }
          };
        }
      }
    
    //这里就干了一件事,递归合并data
    function mergeData(to, from) {
        if (!from) {
          return to;
        }
        var key, toVal, fromVal;
    
        var keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from);
    
        for (var i = 0; i < keys.length; i++) {
          key = keys[i];
          // in case the object is already observed...
          if (key === "__ob__") {
            continue;
          }
          toVal = to[key];
          fromVal = from[key];
          if (!hasOwn(to, key)) {
            set(to, key, fromVal);
          } else if (toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) {
            mergeData(toVal, fromVal);
          }
        }
        return to;
      }
    
    //合并props,methods,inject,computed
    strats.props = strats.methods = strats.inject = strats.computed = function (parentVal, childVal, vm, key) {
        if (childVal && "development" !== "production") {
          assertObjectType(key, childVal, vm);
        }
        if (!parentVal) {
          return childVal;
        }
        var ret = Object.create(null);
        extend(ret, parentVal);//注意这里是创建了新对象,然后将父值合并到新对象
        if (childVal) {
          extend(ret, childVal);//所以是子组件覆盖或拓展父组件属性或方法
        }
        return ret;
      };
    
    //合并生命周期钩子函数
     LIFECYCLE_HOOKS.forEach(function (hook) {
        strats[hook] = mergeHook;
      });
    
    function mergeHook(parentVal, childVal) {
        var res = childVal ? (parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal]) : parentVal;
        return res ? dedupeHooks(res) : res;
      }
    
    //合并watch,注意不是覆盖,而是作为数组进行合并
    strats.watch = function (parentVal, childVal, vm, key) {
        // work around Firefox's Object.prototype.watch...
        if (parentVal === nativeWatch) {
          parentVal = undefined;
        }
        if (childVal === nativeWatch) {
          childVal = undefined;
        }
        /* istanbul ignore if */
        if (!childVal) {
          return Object.create(parentVal || null);
        }
        {
          assertObjectType(key, childVal, vm);
        }
        if (!parentVal) {
          return childVal;
        }
        var ret = {};
        extend(ret, parentVal);
        for (var key$1 in childVal) {
          var parent = ret[key$1];
          var child = childVal[key$1];
          if (parent && !Array.isArray(parent)) {
            parent = [parent];
          }
          ret[key$1] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child];
        }
        return ret;
      };
    

    看到这我们应该明白了上面所说的第2、3点,钩子函数实际是存储在一个数组队列中,这里是父组件钩子函数concat子组件钩子函数,所以同名钩子函数都会执行,先执行父组件钩子函数,再执行子组件钩子函数。

    所以在实际业务中用到全局mixin时候,我们会经常发现有些方法被执行了多次,先后顺序是不一样的。因此也就不难理解前文提到的第4点,谨慎使用全局mixin,因为它会在所有的组件中都会执行,一旦组件非常多就会导致严重的性能问题。举个例子,通常我们会把一个页面分成若干组件,如果一个列表有100个item组件,那么全局混入中的方法至少执行100次以上,如果其中有比较耗性能的方法,那么就会扩大100倍以上。所以当遇到有严重性能问题的时候,不妨看看是不是全局混入导致的。

    小结一下


    起源地下载网 » 你真的理解Vue的混入吗

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元