一:前言在正式开始写之前,先说说我们部门前端代码的大致架构。我们有一个框架叫L,基于backbone又提供了许多定制化的组件,大致这么一个框架。我们都知道backbone是一个经典的mvc框架,虽然存在的时间有点久老不过依旧经典。backbone只实现了M:model层和V:view层。基于这样的现实,所以我们在写公司业务代码的时候主要的逻辑都放在了view层。这导致我们的view非常的臃肿。翻开代码随便找几个页面,都能发现view层的代码都在1500+。最疯狂的是一个同事负责的美食频道,里面的一个页面view层代码居然有4000+。一个页面的view层有4000+的js代码这是一个很恐怖的数量,也能从某种层面反映业务的复杂性。后来我就在思考造成我们view层很庞大的原因,是我们的业务太复杂了还是我们写的代码有冗余。通过review自己负责的几个频道发现每个页面的代码基本都在1500+,里面有很多冗余代码,就比如页面跳转可能a,b,c页面都有跳入详情页的需求,这边的代码是在a,b,c页面都有一个跳转详情页的函数,这就造成了冗余代码。如果我们有一个父类将跳转详情页的函数写在父类父类,子类继承父类后就实现跳入详情页的需求。基于这样的思路我花了一段时间把某一个频道的高频跳转页面的跳转函数都放入父类,发现代码冗余有很明显的减少。
二:对原型链的一些了解。
基于原型链继承这部分思路不是我的原创,是我在看backbone源码时候学习过来的。里面实现了一个extend方法来扩展class。看完后感觉还不错想和大家一起分享一下。我假设大家都对原型链有过一定的了解,能够知道以下几点对原型和原型链的认识
var Person=function(){
console.log('Hi,我是用来创建的实例的函数,大家都叫我构造函数,其实我和java的构造函数没有半毛钱关系');
}
var p1=new Person();
console.log(p1.__proto__==Person.prototype);//true
console.log(Person.prototype.constructor==Person);//true
console.log(Person.prototype.__proto__==Object.prototype);//true
console.log(Person.prototype.__proto__.__proto__==null);
1:构造函数是生成实例模板的函数(Person)
2:每个实例(p1)都有一个__proto__指针用于指向构造函数的原型(Person.prototype)//p1.__proto__==Person.prototype
3:每个构造函数的原型(Person.prototype)都有着一个指针constructor用于指向构造函数本身(Person)//Person.prototype.constructor==Person
4:构造函数的原型本身也是一个特许的实例,所以他也有一个__proto__用于指向Object.prototype,而Object.prototype.__proto指向null,这也体现了万物皆空的道理。//Person.prototype.__proto__==Object.prototype,Person.prototype.__proto__.__proto__==null
三:基于原型链继承的实现
介绍完上面后下面我们来说说基于原型链继承的实现,在说继承前我们要明确一个思想,通过原型链的继承其实就是做了两件事。一、保持子类的构造函数不被改变。二、合并子类和父类原型属性和类自身属性。
//父类的构造函数
var View = function(options) {
console.log('Hi,我是用来创建的实例的函数,大家都叫我构造函数,其实我和java的构造函数没有半毛钱关系');
};
//我们这里把undersc的extend拷贝过来用于下面的对象合并操作
var extend = function(obj) {
if (!(typeof obj=== 'function'|| typeof obj === 'object' && !!obj)) return obj;
var source, prop;
for (var i = 1, length = arguments.length; i < length; i++) {
source = arguments[i];
for (prop in source) {
if (hasOwnProperty.call(source, prop)) {
obj[prop] = source[prop];
}
}
}
return obj;
};
View.Inherit = function(
protoProps, //为子类的原型prototype提供属性
staticProps//staticProps,为子类自身提供属性
) {
//父类,这里就是View()构造函数,这里顺带说一下js里面的this,只有在调用的时候才知道,你被谁调用this就代表谁。
var parent = this;
//申明一个子类
var child;
//要扩展的子类是一个构造函数直接赋值给child
if (protoProps && hasOwnProperty.call(protoProps, 'constructor')) {
child = protoProps.constructor;
} else {//要扩展的子类是一个普通对象,创建一个新的构造函数,而且这个构造函数就是父类的构造函数
child = function(){ return parent.apply(this, arguments); };
}
//合并子类,父类,和要子类要扩展的自身属性
extend(child, parent, staticProps);
//穿件一个新的构造函数,用于保存父类的构造函数,这里可以把Surrogate看成a和b想要换值需要一个c来作为临时变量来存储a的值一样。
//这里的Surrogate就是那个c,这样一说是不是感觉好很多。
//我们都知道每个构造函数的原型都有一个constrocutor,用于指向这个原型的构造函数。
//也就是Person.prototype.constrocutor=Person。
//js在找constructor这指针的时候会首先从构造函数自身属性开始找起,要是没有发现再去构造函数原型中去找。
//所以当我们在构造函数中自己定义一个constructor时候,js找到就会停止。
//Surrogate所有实例constrocutor都是指向child的不会被改变
var Surrogate = function(){ this.constructor = child; };
//把父类的原型赋值给Surrogate的原型,注意如果我们没有在构造函数(Surrogate)中重定向constrocutor指针指向(
//this.construcotr=child;)js会去找原型中constructor,这样在将父类的原型赋值给子类的时候也会修改子类构造函数,
//即是子类的构造函数会被赋值为父类的。
//现在我们的Surrogate拥有了子类child的构造函数,和父类的原型
Surrogate.prototype = parent.prototype;
//重新将子类的构造函数赋值为一个扩展后的对象
child.prototype = new Surrogate;
//合并子类要扩展的原型属性
if (protoProps) extend(child.prototype, protoProps);
//定义一个指针用于指向父类
child.__super__ = parent.prototype;
return child;
};
到这我们就实现了一个基于原型链的继承了。
四:使用
//下面我们来使用一些定义的Inherit
//首先我们给父类的原型扩展一个方法
View.prototype={
sayHi:function(){
console.log('hi, i am parent fun');
}
}
//我们定义一个子类 t来继承父类
var T=View.Inherit({
});
//我们来测试一些T是否能使用父类的方法
new T().sayHi();
整个基于原因链继承就这样了,整个实现追寻原理简单到,a,b想要换数值需要借用到c来作为临时存取变量。