使用动态类型、对象构造而非继承
今天读了OOP The Good Parts: Message Passing, Duck Typing, Object Composition, and not Inheritance,作者提倡使用动态类型(duck typing)、对象构造来代替继承。
关于动态类型的示例代码:
if ( ! (foo instanceof Bar) ) {
throw new TypeError("This is an annoying restriction!");
}
return foo.baz(foo.quux(10));
}
function good (foo) {
if ( !foo.baz || !foo.quux ) {
throw new TypeError("We need foo to have baz and quux methods.");
}
return foo.baz(foo.quux(10));
}
下面是作者给出使用动态类型和对象构造的一个操作DOM的示例:
var k, ctor;
for ( k in definition ) {
ctor = definition[k];
if ( ctor === Number ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object Number]" || isNaN(obj[k]) ) {
return false;
}
} else if ( ctor === String ) {
if ( Object.prototype.toString.call(obj[k]) !== "[object String]" ) {
return false;
}
} else if ( ! (obj[k] instanceof ctor) ) {
return false;
}
}
return true;
};
Object.combine = function () {
var newObj = {},
i = 0,
args = Array.prototype.slice.call(arguments),
len = args.length;
function copyProperty(k) {
newObj[k] = args[i][k];
}
for ( ; i < len; i++ ) {
Object.getOwnPropertyNames(args[i]).forEach(copyProperty);
}
return newObj;
};
window.$ = (function () {
var undef, listen, customAPI;
// Private function to turn 'thing' in to an array,
// optionally slicing the first 'n' elems off.
function toArray(thing, n) {
return Array.prototype.slice.call(thing, n || 0);
}
// Private function to partially apply the first n arguments
// to 'fn'.
function curry (fn) {
var args = toArray(arguments, 1);
return function () {
return fn.apply(this, args.concat(toArray(arguments)));
};
}
// Private function to set the attribute 'attr' of a single
// DOM node 'node' to 'val'.
function nodeAttributeSetter (attr, val, node) {
node.setAttribute(attr, val);
}
// Private function to get the value of the attribute 'attr'
// on the DOM node 'node'.
function nodeAttributeGetter (attr, node) {
return node.getAttribute(attr);
}
// Do the work to determine the correct method for attaching
// event listeners only once by using 'quacksLike' and an
// immediately invoked function.
listen = (function () {
var w3c = {
addEventListener: Function
},
ie = {
attachEvent: Function
};
if ( quacksLike(document.body, w3c) ) {
return function (event, fn, node) {
node.addEventListener(event, fn, false);
};
} else if ( quacksLike(document.body, ie) ) {
return function (event, fn, node) {
node.attachEvent("on" + event, fn);
};
} else {
throw new TypeError("Do not know how to attach event listeners.");
}
}());
// This object is a mixin to be composed with the NodeList
// results of querying the DOM by CSS selector. It provides
// handy methods for getting and setting attributes,
// attaching event listeners, and performing sub-queries by
// CSS on these elements.
customAPI = {
attr: function (attr, val) {
if ( val !== undef ) {
this.forEach(curry(nodeAttributeSetter, attr, val));
return this;
} else {
return this.length > 0 ? nodeAttributeGetter(attr, this[0]) : "";
}
},
on: function (event, fn) {
this.forEach(curry(listen, event, fn));
return this;
},
find: function (selector) {
var res = [];
this.forEach(function (node) {
res.push.apply(res, toArray(node.querySelectorAll(selector)));
});
return Object.combine(res, customAPI);
}
};
return function (selector, context) {
context = context || document;
return Object.combine(Array.prototype, context.querySelectorAll(selector), customAPI);
};
}());
//The mini DOM library might be used like this:
// The following two forms are equivalent.
// They select the same elements.
$("div").find("p");
$("div p");
// Disable all links.
$("a").on("click", function (event) {
event.preventDefault();
alert("Don't leave me all alone!");
});
// Get the id of the first paragraph.
$("p").attr("id");
// Disable all input elementss.
$(input").attr("disabled", "disabled");
// Do stuff with each matched element.
$("div.foo").forEach(function (el) {
doStuffWith(el);
});