本文介紹的四種代碼復(fù)用模式都是最佳實(shí)踐,推薦大家在編程的過(guò)程中使用。
原型繼承是讓父對(duì)象作為子對(duì)象的原型,從而達(dá)到繼承的目的:
function object(o) {
function F() {
}
F.prototype = o;
return new F();
}
// 要繼承的父對(duì)象
var parent = {
name: "Papa"
};
// 新對(duì)象
var child = object(parent);
// 測(cè)試
console.log(child.name); // "Papa"
// 父構(gòu)造函數(shù)
function Person() {
// an "own" property
this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
return this.name;
};
// 創(chuàng)建新person
var papa = new Person();
// 繼承
var kid = object(papa);
console.log(kid.getName()); // "Adam"
// 父構(gòu)造函數(shù)
function Person() {
// an "own" property
this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
return this.name;
};
// 繼承
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function",因?yàn)槭窃谠屠锒x的
console.log(typeof kid.name); // "undefined", 因?yàn)橹焕^承了原型
同時(shí),ECMAScript5也提供了類似的一個(gè)方法叫做Object.create用于繼承對(duì)象,用法如下:
/* 使用新版的ECMAScript 5提供的功能 */
var child = Object.create(parent);
var child = Object.create(parent, {
age: { value: 2} // ECMA5 descriptor
});
console.log(child.hasOwnProperty("age")); // true
而且,也可以更細(xì)粒度地在第二個(gè)參數(shù)上定義屬性:
// 首先,定義一個(gè)新對(duì)象man
var man = Object.create(null);
// 接著,創(chuàng)建包含屬性的配置設(shè)置
// 屬性設(shè)置為可寫,可枚舉,可配置
var config = {
writable: true,
enumerable: true,
configurable: true
};
// 通常使用Object.defineProperty()來(lái)添加新屬性(ECMAScript5支持)
// 現(xiàn)在,為了方便,我們自定義一個(gè)封裝函數(shù)
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 // 100mph
但是有個(gè)地方需要注意,就是Object.create(null)創(chuàng)建的對(duì)象的原型為undefined,也就是沒(méi)有toString和valueOf方法,所以alert(man);的時(shí)候會(huì)出錯(cuò),但alert(man.car);是沒(méi)問(wèn)題的。
這種方式的繼承就是將父對(duì)象里所有的屬性都復(fù)制到子對(duì)象上,一般子對(duì)象可以使用父對(duì)象的數(shù)據(jù)。
先來(lái)看一個(gè)淺拷貝的例子:
/* 淺拷貝 */
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); // "Adam"
var dad = {
counts: [1, 2, 3],
reads: { paper: true }
};
var kid = extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString()); // "1,2,3,4"
console.log(dad.reads === kid.reads); // true
代碼的最后一行,你可以發(fā)現(xiàn)dad和kid的reads是一樣的,也就是他們使用的是同一個(gè)引用,這也就是淺拷貝帶來(lái)的問(wèn)題。
我們?cè)賮?lái)看一下深拷貝:
/* 深拷貝 */
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()); // "1,2,3,4"
console.log(dad.counts.toString()); // "1,2,3"
console.log(dad.reads === kid.reads); // false
kid.reads.paper = false;
深拷貝以后,兩個(gè)值就不相等了,bingo!
混入就是將一個(gè)對(duì)象的一個(gè)或多個(gè)(或全部)屬性(或方法)復(fù)制到另外一個(gè)對(duì)象,我們舉一個(gè)例子:
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函數(shù)將所傳入的所有參數(shù)的子屬性都復(fù)制到child對(duì)象里,以便產(chǎn)生一個(gè)新對(duì)象。
那如何我們只想混入部分屬性呢?該個(gè)如何做?其實(shí)我們可以使用多余的參數(shù)來(lái)定義需要混入的屬性,例如mix(child,parent,method1,method2)這樣就可以只將parent里的method1和method2混入到child里。上代碼:
// Car
var Car = function (settings) {
this.model = settings.model || 'no model provided';
this.colour = settings.colour || 'no colour provided';
};
// Mixin
var Mixin = function () { };
Mixin.prototype = {
driveForward: function () {
console.log('drive forward');
},
driveBackward: function () {
console.log('drive backward');
}
};
// 定義的2個(gè)參數(shù)分別是被混入的對(duì)象(reciving)和從哪里混入的對(duì)象(giving)
function augment(receivingObj, givingObj) {
// 如果提供了指定的方法名稱的話,也就是參數(shù)多余3個(gè)
if (arguments[2]) {
for (var i = 2, len = arguments.length; i < len; i++) {
receivingObj.prototype[arguments[i]] = givingObj.prototype[arguments[i]];
}
}
// 如果不指定第3個(gè)參數(shù),或者更多參數(shù),就混入所有的方法
else {
for (var methodName in givingObj.prototype) {
// 檢查receiving對(duì)象內(nèi)部不包含要混入的名字,如何包含就不混入了
if (!receivingObj.prototype[methodName]) {
receivingObj.prototype[methodName] = givingObj.prototype[methodName];
}
}
}
}
// 給Car混入屬性,但是值混入'driveForward' 和 'driveBackward'*/
augment(Car, Mixin, 'driveForward', 'driveBackward');
// 創(chuàng)建新對(duì)象Car
var vehicle = new Car({ model: 'Ford Escort', colour: 'blue' });
// 測(cè)試是否成功得到混入的方法
vehicle.driveForward();
vehicle.driveBackward();
該方法使用起來(lái)就比較靈活了。
一個(gè)對(duì)象借用另外一個(gè)對(duì)象的一個(gè)或兩個(gè)方法,而這兩個(gè)對(duì)象之間不會(huì)有什么直接聯(lián)系。不用多解釋,直接用代碼解釋吧:
var one = {
name: 'object',
say: function (greet) {
return greet + ', ' + this.name;
}
};
// 測(cè)試
console.log(one.say('hi')); // "hi, object"
var two = {
name: 'another object'
};
console.log(one.say.apply(two, ['hello'])); // "hello, another object"
// 將say賦值給一個(gè)變量,this將指向到全局變量
var say = one.say;
console.log(say('hoho')); // "hoho, undefined"
// 傳入一個(gè)回調(diào)函數(shù)callback
var yetanother = {
name: 'Yet another object',
method: function (callback) {
return callback('Hola');
}
};
console.log(yetanother.method(one.say)); // "Holla, undefined"
function bind(o, m) {
return function () {
return m.apply(o, [].slice.call(arguments));
};
}
var twosay = bind(two, one.say);
console.log(twosay('yo')); // "yo, another object"
// ECMAScript 5給Function.prototype添加了一個(gè)bind()方法,以便很容易使用apply()和call()。
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')); // "Bonjour, another object"
var twosay3 = one.say.bind(two, 'Enchanté');
console.log(twosay3()); // "Enchanté, another object"
就不用總結(jié)了吧。
更多建議: