任何编程都提出代码复用,否则话每次开发一个新程序或者写一个新功能都要全新编写的话,那就歇菜了。但是代码复用也是有好要坏,今天这篇文章我们将针对代码复用来进行讨论,里面一种介绍了10种模式,其中前面4种模式是推荐使用的,是推荐大家使用的模式,一般不会有什么问题。后面6种模式是要尽量避免使用的,因为后面6种模式或多或少会带来一些问题。模式1:原型继承
原型继承是让父对象作为子对象的原型,从而达到继承的目的:function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
var parent = { name: "Papa"};
var child = object(parent);
console.log(child.name);
function Person() {
this.name = "Adam";
}
Person.prototype.getName = function () {
return this.name;
};
var papa = new Person();
var kid = object(papa);
console.log(kid.getName());
function Person() {
this.name = "Adam";
}
Person.prototype.getName = function () {
return this.name;
};
var kid = object(Person.prototype);
console.log(typeof kid.getName);
console.log(typeof kid.name);
同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:
var child = Object.create(parent);
var child = Object.create(parent, {
age: { value: 2}
});
console.log(child.hasOwnProperty("age"));
var man = Object.create(null);
var config = {
writable: true,
enumerable: true,
configurable: true
};
var defineProp = function (obj, key, value)
{
config.value = value;
Object.defineProperty(obj, key, config);
}
defineProp(man, 'car', 'Delorean');
defineProp(man, 'dob', '1981');
defineProp(man, 'beard', false);
var driver = Object.create( man );
defineProp(driver, 'topSpeed', '100mph');
driver.topSpeed;
但是有个地方需要注意,就是Object.create(null)创建的对象的原型为undefined,也就是没有toString和valueOf方法,所以alert(man);的时候会出错,但alert(man.car);是没问题的。模式2:复制所有属性进行继承
这种方式的继承就是将父对象里所有的属性都复制到子对象上,一般子对象可以使用父对象的数据。
function extend(parent, child) {
var i;
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
return child;
}
var dad = { name: "Adam" };
var kid = extend(dad);
console.log(kid.name);
var dad = {
counts: [1, 2, 3],
reads: { paper: true }
};
var kid = extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString());
console.log(dad.reads === kid.reads);
代码的最后一行,你可以发现dad和kid的reads是一样的,也就是他们使用的是同一个引用,这也就是浅拷贝带来的问题。
function extendDeep(parent, child) {
var i,
toStr = Object.prototype.toString,
astr = "[object Array]";
child = child || {};
for (i in parent) {
if (parent.hasOwnProperty(i)) {
if (typeof parent[i] === 'object') {
child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
}
var dad = {
counts: [1, 2, 3],
reads: { paper: true }
};
var kid = extendDeep(dad);
kid.counts.push(4);
console.log(kid.counts.toString());
console.log(dad.counts.toString());
console.log(dad.reads === kid.reads);
kid.reads.paper = false;
模式3:混合(mix-in)
混入就是将一个对象的一个或多个(或全部)属性(或方法)复制到另外一个对象,我们举一个例子:function mix() {
var arg, prop, child = {};
for (arg = 0; arg < arguments.length; arg += 1) {
for (prop in arguments[arg]) {
if (arguments[arg].hasOwnProperty(prop)) {
child[prop] = arguments[arg][prop];
}
}
}
return child;
}
var cake = mix(
{ eggs: 2, large: true },
{ butter: 1, salted: true },
{ flour: '3 cups' },
{ sugar: 'sure!' }
);
console.dir(cake);
mix函数将所传入的所有参数的子属性都复制到child对象里,以便产生一个新对象。那如何我们只想混入部分属性呢?该个如何做?其实我们可以使用多余的参数来定义需要混入的属性,例如mix(child,parent,method1,method2)这样就可以只将parent里的method1和method2混入到child里。上代码:
var Car = function (settings) {
this.model = settings.model || 'no model provided';
this.colour = settings.colour || 'no colour provided';
};
var Mixin = function () { };
Mixin.prototype = {
driveForward: function () {
console.log('drive forward');
},
driveBackward: function () {
console.log('drive backward');
}
};
function augment(receivingObj, givingObj) {
if (arguments[2]) {
for (var i = 2, len = arguments.length; i < len; i++) {
receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]];
}
}
else {
for (var methodName in givingObj.prototype) {
if (!receivingObj.prototype[methodName]) {
receivingObj.prototype[methodName] = givingObj.prototype[methodName];
}
}
}
}
augment(Car, Mixin, 'driveForward', 'driveBackward');
var vehicle = new Car({ model: 'Ford Escort', colour: 'blue' });
vehicle.driveForward();
vehicle.driveBackward();
模式4:借用方法
一个对象借用另外一个对象的一个或两个方法,而这两个对象之间不会有什么直接联系。不用多解释,直接用代码解释吧:var one = {
name: 'object',
say: function (greet) {
return greet + ', ' + this.name;
}
};
console.log(one.say('hi'));
var two = {
name: 'another object'
};
console.log(one.say.apply(two, ['hello']));
var say = one.say;
console.log(say('hoho'));
var yetanother = {
name: 'Yet another object',
method: function (callback) {
return callback('Hola');
}
};
console.log(yetanother.method(one.say));
function bind(o, m) {
return function () {
return m.apply(o, [].slice.call(arguments));
};
}
var twosay = bind(two, one.say);
console.log(twosay('yo'));
if (typeof Function.prototype.bind === 'undefined') {
Function.prototype.bind = function (thisArg) {
var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
return function () {
return fn.apply(thisArg, args.concat(slice.call(arguments)));
};
};
}
var twosay2 = one.say.bind(two);
console.log(twosay2('Bonjour'));
var twosay3 = one.say.bind(two, 'Enchanté');
console.log(twosay3());
模式5:默认模式
代码复用大家常用的默认模式,往往是有问题的,该模式使用Parent()的构造函数创建一个对象,并且将该对象赋值给Child()的原型。我们看一下代码:function inherit(C, P) {
C.prototype = new P();
}
function Parent(name) {
this.name = name || 'Adam';
}
Parent.prototype.say = function () {
return this.name;
};
function Child(name) {
}
inherit(Child, Parent);
var kid = new Child();
console.log(kid.say());
var kiddo = new Child();
kiddo.name = "Patrick";
console.log(kiddo.say());
var s = new Child('Seth');
console.log(s.say());
这种模式的缺点是Child不能传进参数,基本上也就废了。模式6:借用构造函数
该模式是Child借用Parent的构造函数进行apply,然后将child的this和参数传递给apply方法:
function Parent(name) {
this.name = name || 'Adam';
}
Parent.prototype.say = function () {
return this.name;
};
function Child(name) {
Parent.apply(this, arguments);
}
var kid = new Child("Patrick");
console.log(kid.name);
console.log(typeof kid.say);
缺点也很明显,say方法不可用,因为没有继承过来。模式7:借用构造函数并设置原型
上述两个模式都有自己的缺点,那如何把两者的缺点去除呢,我们来尝试一下:
function Parent(name) {
this.name = name || 'Adam';
}
Parent.prototype.say = function () {
return this.name;
};
function Child(name) {
Parent.apply(this, arguments);
}
Child.prototype = new Parent();
var kid = new Child("Patrick");
console.log(kid.name);
console.log(typeof kid.say);
console.log(kid.say());
console.dir(kid);
delete kid.name;
console.log(kid.say());
运行起来,一切正常,但是有没有发现,Parent构造函数执行了两次,所以说,虽然程序可用,但是效率很低。模式8:共享原型
共享原型是指Child和Parent使用同样的原型,代码如下:function inherit(C, P) {
C.prototype = P.prototype;
}
function Parent(name) {
this.name = name || 'Adam';
}
Parent.prototype.say = function () {
return this.name;
};
function Child(name) {
}
inherit(Child, Parent);
var kid = new Child('Patrick');
console.log(kid.name);
console.log(typeof kid.say);
kid.name = 'Patrick';
console.log(kid.say());
console.dir(kid);
模式9:临时构造函数
首先借用构造函数,然后将Child的原型设置为该借用构造函数的实例,最后恢复Child原型的构造函数。代码如下:
var inherit = (function () {
var F = function () {
};
return function (C, P) {
F.prototype = P.prototype;
C.prototype = new F();
C.uber = P.prototype;
C.prototype.constructor = C;
}
} ());
function Parent(name) {
this.name = name || 'Adam';
}
Parent.prototype.say = function () {
return this.name;
};
function Child(name) {
}
inherit(Child, Parent);
var kid = new Child();
console.log(kid.name);
console.log(typeof kid.say);
kid.name = 'Patrick';
console.log(kid.say());
var kid2 = new Child("Tom");
console.log(kid.say());
console.log(kid.constructor.name);
console.log(kid.constructor === Parent);
模式10:klass
var klass = function (Parent, props) {
var Child, F, i;
Child = function () {
if (Child.uber && Child.uber.hasOwnProperty("__construct")) {
Child.uber.__construct.apply(this, arguments);
}
if (Child.prototype.hasOwnProperty("__construct")) {
Child.prototype.__construct.apply(this, arguments);
}
};
Parent = Parent || Object;
F = function () {
};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.uber = Parent.prototype;
Child.prototype.constructor = Child;
for (i in props) {
if (props.hasOwnProperty(i)) {
Child.prototype[i] = props[i];
}
}
return Child;
};
var Man = klass(null, {
__construct: function (what) {
console.log("Man's constructor");
this.name = what;
},
getName: function () {
return this.name;
}
});
var first = new Man('Adam');
first.getName();
var SuperMan = klass(Man, {
__construct: function (what) {
console.log("SuperMan's constructor");
},
getName: function () {
var name = SuperMan.uber.getName.call(this);
return "I am " + name;
}
});
var clark = new SuperMan('Clark Kent');
clark.getName();
console.log(clark instanceof Man);
console.log(clark instanceof SuperMan);
怎么样?看着是不是有点晕,说好点,该模式的语法和规范拧得和别的语言一样,你愿意用么?咳。。。总结
以上就是我要给大家分享的10个代码复用模式,前面4种模式推荐使用。后面6种模式尽量避免使用。虽然后面6种模式,在某种特殊情况下实现了某些功能,但是都存在各自的缺点,所以一般情况,请大家要避免使用。参考:http://shichuan.github.com/javascript-patterns/#code-reuse-patterns
推荐阅读
9种日常JavaScript编程中经常使用的对象创建模式