学习vue源码(2) 手写Vue.extend方法

1,506 阅读3分钟

Vue.extend( optons );

(1)参数

{ object } options

(2)用法

使用基础Vue构造器创建一个“子类”,其参数是一个包含“组件选项”的对象。data选项是特例,在Vue.extend()中,它必须是函数。

<div id="mount-point"></div>
<!-- 创建构造器 -->
var Profile = Vue.extend({
 template:'<p>{{firstName}} {{lastName}} aka{{alias}}</p>',
 data:function(){
  return{
   firstName:'Walter',
   lastName:'White',
   alias:'Heisenberg'
  }
 }
})
<!-- 创建Profile实例,并挂载到一个元素上 -->
new Profile().$mount('#mount-point');

(3)全局API和示例方法不同,后者是在Vue的原型上挂载方法,也就是在Vue.prototype上挂载方法,而前者是直接在Vue上挂载方法。

Vue.extend = function(extendOptions){
 <!-- 做点什么 -->
}

(4)作用

Vue.extend的作用是创建一个子类,所以可以创建一个子类,然后让它继承Vue身上的一些功能。

二、实现 (1)创建一个子类

Vue.cid = 0
let cid = 1;
 
Vue.extend = function(extendOptions){
 extendOptions = extendOptions || {};
 const Super = this; // 指向 Vue这个构造函数
 const SuperId = Super.cid; 指向 Vue这个构造函数的id
 const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
 if(cachedCtors[SuperId]){
  return cachedCtors[SuperId];
 }
 const name = extendOptions.name || Super.options.name;
 if(process.env.NODE_ENV !== 'production'){
  if(!/^[a-zA-Z][\w-]*$/.test(name)){
   warn(
    'invalid component name:"'+name+'".Component names'+
    'can only contain alphanumeric characters and the hyphen,'+
    'and must start with a letter.'
   )
  }
 }
 const Sub = function VueComponent(options){
  this._init(options);
 }
 <!-- 缓存构造函数 -->
 cachedCtors[SuperId] = Sub;
 return sub;
}

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

 if(cachedCtors[SuperId]){
  return cachedCtors[SuperId];
 }

那它是怎么判断是不是相同的呢?

我们举个例子。


const obj= {
 template: '<p></p>',
 data: function() {
      return {}
 }
}  
var Profile = Vue.extend(obj)
var Profile2 = Vue.extend(obj)
console.log(Profile == Profile2) //true

如例子所示,只要obj相同,那么创建的子类就是同一个。

那怎么判断obj即源码中的extendOptions是否是同一个对象呢?

这就是这段代码的作用了

 const Super = this; // 指向 Vue这个构造函数
 const SuperId = Super.cid; 指向 Vue这个构造函数的id,值为0
 const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});

我们给第一次作为参数的extendOptions添加一个属性_Ctor={}。因此下次我们再用同一个extendOptions来创建子类的时候,这个extendOptions身上就会有_Ctor这个属性了。 然后再赋值

 cachedCtors[SuperId] = Sub;

注意cachedCtors是引用extendOptions._Ctor的,也就是说他们是指向同一个对象。这时

extendOptions._Ctor = {
  0:Sub
}

因此它下次再用同一个extendOptions就会判断 _Ctor是否已经有缓存了没有。

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

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

(2)子类继承Vue的能力

首先,将父类的原型继承到子类中

Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;

1、为子类添加了cid,它表示每个类的唯一标识。

将父类的options选项继承到子类中
Sub.options = mergeOptions(
 Super.options,
 extendOptions
)
Sub['super'] = Super;

1、合并了父类选项与子类选项的逻辑,并将父类保存到子类的super属性中。而mergeOptions方法会将两个选项合并为一个新对象。

如果选项中存在props属性,则初始化它

if(Sub.options.props){
 initProps(Sub);
}

1、初始化props的作用是将key代理到_props中。例如,vm.name实际上可以访问到的是Sub.prototype._props.name。

function initProps(Comp){
 const props = Comp.options.props;
 for(const key in props){
  proxy(Comp.prototype,'_props',key)
 }
}
function proxy(target,sourceKey,key){
 sharedPropertyDefinition.get = function proxyGetter(){
  return this[sourceKey][key];
 }
 sharedPropertyDefinition.set = function proxySetter(val){
  this[sourceKey][key] = val;
 }
 Object.definedProperty(target,key,sharedPropertyDefinition);
}

如果选项中存在computed,则对它进行初始化

if(Sub.options.computed){
 initComputed(Sub);
}
function initComputed (Comp){
 const computed = Comp.options.computed;
 for(const key in computed){
  defineComputed(Comp.prototype,key,computed[key]);
 }
}

将父类中存在的属性依次复制到子类中

Sub.extend = Super.extend;
Sub.mixin = Super.mixin
Sub.use = Super.use;
// ASSET_TYPES = ['component','directive','filter']
ASSET_TYPES.forEach(function(type){
 Sub[type] = Super[type];
})
if(name){
 Sub.options.components[name] = Sub;
}
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({},Sub.options);

1、复制到子类中的方法包括extend、mixin、use、component、directive和filter

2、在子类上新增了superOptions、extendOptions和sealedOptions属性

总结:

创建了一个Sub函数并继承了父级。如果直接使用Vue.extend,则Sub继承于Vue构造函数。

三、完整代码

Vue.cid = 0
let cid = 1;
 
Vue.extend = function(extendOptions){
 extendOptions = extendOptions || {};
 const Super = this;
 const SuperId = Super.cid;
 const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
 if(cachedCtors[SuperId]){
  return cachedCtors[SuperId];
 }
 const name = extendOptions.name || Super.options.name;
 if(process.env.NODE_ENV !== 'production'){
  if(!/^[a-zA-Z][\w-]*$/.test(name)){
   warn(
    'invalid component name:"'+name+'".Component names'+
    'can only contain alphanumeric characters and the hyphen,'+
    'and must start with a letter.'
   )
  }
 }
 const Sub = function VueComponent(options){
  this._init(options);
 }
 <!-- 将父类原型继承到子类中 -->
 <!-- cid每个类的唯一标识 -->
 Sub.prototype = Object.create(Super.prototype);
 Sub.prototype.constructor = Sub;
 Sub.cid = cid++;
 <!-- 将父类的options选项继承到子类中 -->
 Sub.options = mergeOptions(
  Super.options,
  extendOptions
 )
 Sub['super'] = Super;
 <!-- 如果选项中存在props属性,则初始化它 -->
 if(Sub.options.props){
  initProps(Sub);
 }
 <!-- 如果选项中存在computed属性,则对它进行初始化 -->
 if(Sub.options.computed){
  initComputed(Sub);
 }
 <!-- 将父类中存在的属性依次复制到子类中 -->
 Sub.extend = Super.extend;
 Sub.mixin = Super.mixin'
 Sub.use = Super.use;
 
 <!-- ASSET_TYPES = ['component','directive','filter'] -->
 ASSET_TYPES.forEach(function(type){
  Sub[type] = Super[type];
 })
 
 if(name){
  Sub.options.components[name] = Sub;
 }
 
 Sub.superOptions = Super.options;
 Sub.extendOptions = extendOptions;
 Sub.sealedOptions = extend({},Sub.options);
 <!-- 缓存构造函数 -->
 cachedCtors[SuperId] = Sub;
 return sub;
}

本文使用 mdnice 排版