Vue.extend 看完这篇,你就学废了。

11,295

Vue.extend( options ) : 基础Vue构造器

参数是一个包含组件选项的对象。

data选项是特例,在Vue.extend()中它必须是函数,为了保证引用数据不乱来。

<div id='app'>
  <span>MyApp</span>
</div>
<script>
 var Profile = Vue.extend({
   template: '<p>目标:绑定到id为app的元素下面  打印data中的数据{{p1}},{{p2}}</p>'data:function(){
     return {
       p1:'打印记录1',
       p2:'打印记录2'
     }
   }
 })
 //进行绑定
 new Profile().$mount('#mount-point');
</script>

为什么要使用extend ?

常规办法,我们的所有页面都是用router进行管理,组件也是通过import进行局部注册,也会存在不足的地方。

例如:假设我们需要从接口动态渲染组件的情况?如何实现一个类似window.alert()类似的提示组件,要求调用js一样使用?

如何使用extend构造一个自定义弹窗

1.编写弹窗组件

// 测试的话 建议直接复制粘贴

<template>
    <div v-if="show" ref="modal" class="ek-modal_wrap">
      <div class="ek-modal-content">
        <div class="modal-title-wrap">
          <div class="modal-title">{{ title }}</div>
          <slot name="description"></slot>
        </div>
        <div class="modal-button">
          <a v-if="confirmVisible" class="contral-btn confirm-btn" href="javascript:;" @click="confirm">{{
            confirmText
          }}</a>
          <a v-if="cancleVisible" class="contral-btn cancle-btn" href="javascript:;" @click="cancle">{{ cancleText }}</a>
        </div>
      </div>
    </div>
  </template>

  <script>
  export default {
    data() {
      return {
        show: true,
        title: '', // 标题
        confirmText: '确定', // 确认文字
        confirmVisible: true, // 是否展示确认按钮
        onConfirm: () => { // 确认执行函数
          this.$emit('confirm')
        }, 
        cancleText: '取消', // 取消文字
        cancleVisible: true, // 是否展示取消按钮
        onCancle: () => { // 取消执行函数
          this.$emit('cancle')
        } 
      }
    },
    methods: {
      confirm() {
        this.onConfirm()
        this.close()
      },
      cancle() {
        this.onCancle()
        this.close()
      },
      close() {
        this.show= false
        if (this.$refs.modal) {
          this.$refs.modal.remove() // 关闭时候直接移除当前元素
        }
      }
    }
  }
  </script>

  <style lang="scss" scoped>
  .ek-modal_wrap {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 999;
    width: 100%;
    height: 100%;
    font-size: 28px;
    background: rgba(0, 0, 0, 0.7);
    .ek-modal-content {
      position: fixed;
      top: 50%;
      left: 50%;
      min-width: 7.2rem;
      overflow-x: hidden;
      overflow-y: hidden;
      text-align: center;
      background-color: white;
      border-top-left-radius: 0.266667rem;
      border-top-right-radius: 0.266667rem;
      border-bottom-right-radius: 0.266667rem;
      border-bottom-left-radius: 0.266667rem;
      transform: translate(-50%, -50%);
      .modal-title-wrap {
        display: flex;
        flex-direction: column;
        flex-grow: 1;
        justify-content: center;
        min-height: 55px;
        padding: 0 20px;
        color: #333;
      }
      .modal-title {
        display: flex;
        flex-direction: column;
        flex-grow: 1;
        justify-content: center;
        min-height: 100px;
        margin-top: 30px;
        margin-bottom: 30px;
        font-weight: 600px;
        line-height: 50px;
        color: #333;
      }
      .modal-button {
        display: flex;
        line-height: 1;
        color: #333;
        border-top-color: #e7e7e7;
        border-top-style: solid;
        border-top-width: 1px;
        & > a {
          color: #333;
        }
        .contral-btn {
          flex-basis: 0%;
          flex-grow: 1;
          flex-shrink: 1;
          font-weight: 600px;
          line-height: 3;
          text-align: center;
          &.cancle-btn {
            border-left-color: #e7e7e7;
            border-left-style: solid;
            border-left-width: 1px;
          }
        }
      }
    }
  }
  </style>

第二步:在vue的原型上绑定方法。

import Vue from 'vue';
import dialog from './components/Dialog.vue';
function showDialog(options) {
    const Dialog = Vue.extend(dialog); // 返回一个vue子类
     //创建实例并且挂载
    const app = new Dialog().$mount(document.createElement('div'));
    //初始化参数
    for (let key in options) {
        app[key] = options[key];
    }
    //将元素插入body中
    document.body.appendChild(app.$el);
}
Vue.prototype.$showDialog = showDialog; //将方法放在原型上。

第三步:在vue页面中调用方法既可

....某vue页面  

  mounted() {
        console.log(this.$showDialog);
        this.$showDialog({
            title: '测试弹窗',
            confirmText: '希望弹出窗口正确调用',
            cancelVisible: false,
        });
    },

... 欢迎各位尝试体验

Vue.extend原理解析:

作用:Vue.extend返回的是一个扩展实例构造器,也就是预设了部分选项的Vue实例构造器,但未曾实例化,可以理解为创建一个子类,然后让它继承Vue身上的一些功能。

创建一个Vue的子类。

let cid = 1// 用以进行特殊标记
function extend(extendOptions){
    extendOptions = extendOptions || {}
    // this->vue
    const Super = this
    const SuperId = Super.cid
    // 使用父类的id作为缓存的key
    // 只要父类id相同,每次调用会返回相同的子类

    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    //如果存在缓存,可直接返回。 为了性能考虑,增加缓存策略
    if (cachedCtors[SuperId]) {
      return cachedCtors[SuperId]
    }   
    // 对name命名方式进行校验
    // /^[a-zA-Z][\w-]*/
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      validateComponentName(name)
    }

    const Sub = function VueComponent (options) {
      // vue._init 此函数的解析会在下面有解析
      this._init(options)
    }
    // 继承父类构造函数
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    // 合并父类的options
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 初始化props
    // 将props挂载在原型对象的_props属性下
    if (Sub.options.props) {
      initProps(Sub)
    }
    // 初始化computed
    // 将初始化computed挂载在原型对象下
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    // 复制父类的静态方法
    // 子类继承Vue的能力
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)
    // cache constructor
    cachedCtors[SuperId] = Sub
    return Sub
  }

1、为了性能考虑,在Vue.extend方法内增加了缓存策略。反复调用Vue.extend其实应该返回同一个结果。

2、只要返回结果是固定的,就可以将计算结果缓存,再次调用extend方法时,只需要从缓存中取出结果即可。

3、使用父类的id作为缓存的key,将子类缓存在cachedCtors中。

4、对name的校验,如果发现name选项不合格,会在开发环境下发出警告。

5、最后,创建子类并将它返回,这一步并没有继承的逻辑,此时子类是不能用的,它还不具备Vue的能力。

创建vue子类中 _init()函数解析

在上述代码中出现了一个vue的_init函数,附上其解析。简化版本

let uid = 0;
export function initMixin(Vue: Class<Component>) {
  Vue.prototype._init = function(options?: Object) {
    const vm: Component = this;

    vm._uid = uid++; // 当前实例的 _uid 加 1

	//  a flag to avoid this being observed
    // 用 _isVue 来标识当前实例是 Vue 实例, 这样做是为了后续被 observed
    vm._isVue = true;

    // merge options 合并options 
    if (options && options._isComponent) { // _isComponent 标识当前为 内部Component
      // 内部Component 的 options 初始化
      initInternalComponent(vm, options); 
    }
    else { // 非内部Component的 options 初始化
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      );
    }

    // 在render中将this指向vm._renderProxy
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm);
    }
    else {
      vm._renderProxy = vm;
    }
    // expose real self
    vm._self = vm;
    initLifecycle(vm); // 初始化生命周期
    initEvents(vm); // 初始化事件
    initRender(vm); // 初始化渲染函数
    callHook(vm, 'beforeCreate'); // 回调 beforeCreate 钩子函数
    initInjections(vm); // resolve injections before data/props
    // 初始化 vm 的状态
    initState(vm);
    initProvide(vm); // resolve provide after data/props
    callHook(vm, 'created'); // vm 已经创建好 回调 created 钩子函数

    if (vm.$options.el) { // 挂载实例
      vm.$mount(vm.$options.el);
    }
  }