在JS这种弱类型的语言中,类型不匹配错误很难跟踪。可以使用接口来进行类型检查。如果强制使用,又会弱化语言的灵活性。因为有额外的代码调用,所以又会降低性能。解决方法就是在开发时进行类型检查,在开始完成后删除此部分代码。
但JS中的接口实现方式是模拟的,它以文档和方法检查相结合。缺点在于它只检查方法的名称,并不检查参数的名称,类型,数量以及方法的返回值。
var Interface = function(name, methods) {
if(arguments.length != 2) {
throw new Error("Interface constructor expected exactly 2.");
}
this.name = name;//接口名称
this.methods = [];//接口的方法名称数组
for(var i = 0, len = methods.length; i < len; i++) {
if(typeof methods[i] !== 'string') {
throw new Error("Interface constructor expects method names is String.");
}
this.methods.push(methods[i]);
}
};
Interface.ensureImplements = function(object) {
if(arguments.length < 2) {
throw new Error("Interface.ensureImplements exactly arguments at least 2.");
}
for(var i = 1, len = arguments.length; i < len; i++) {
var interface = arguments[i];
if(interface.constructor !== Interface) {
throw new Error(interface + "exactly type is Interface.");
}
for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
var method = interface.methods[j];
if(!object[method] || typeof object[method] !== 'function') {
throw new Error("Function Interface.ensureImplements: object "
+ "does not implement the " + interface.name
+ " interface. Method " + method + " was not found.");
}
}
}
};
//模拟使用
//定义了接口Person0和Person1,并在每个接口中定义三个方法
var Interface0 = new Interface('Person0', ['add', 'del', 'getItem']);
var Interface1 = new Interface('Person1', ['add', 'del', 'getItem']);
function addForm(myClassInstance){
//用来检查myClassInstance是否实现了接口Interface0和Interface1
Interface.ensureImplements(myClassInstance, Interface0, Interface1)
}
JS的灵活性是其最大的特点,所以在强制类型检查时需要有选择的使用。
创建私有成员是OOP中最基本和有用的特性之一。信息隐藏是目的,封装则是达到这个目的的技术。JS实现封装的方式多种多样,比如公用、私用、特权。下面有两个简单的例子。
Book.getInt8 = function(){
//static Method
}
Book.prototype = {
_checkIsbn: function (isbn) {
}//用「-」人为约定私有方法和属性,并不在程序中提供保护
}
//用闭包的方式来实现私有方法和变量
var Book = function(newIsbn, newTitle, newAuthor) {
// Private attributes.
var isbn, title, author;
// Private method.
function checkIsbn(isbn) {
}
}
var Class = (function() {
// Private static attributes.
var constants = {
UPPER_BOUND: 100,
LOWER_BOUND: -100
}
//Constructor
var ctor = function(constructorArgument){};
// Privileged static method.
ctor.getConstant = function(name){
return constants[name];
}
// Return the constructor.
return ctor;
})();
/* 调用,如果在上面的括号中加入参数也可以实现常量的动态实例化 */
var t = Class.getConstant('UPPER_BOUND');
在实现JS继承时需要注意一个类依赖另一个类的内部实现这样的耦合关系。如果设计的是一个供众人使用的API,最好使用类继承方式。原型的方式更简洁也更节省内存。掺元适合即要共享类的方法又不需要结对的情况。
//superClass
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() {return this.name;}
//subClass
function Author(name, books) {
Person.call(this, name); //在子类中调用父类的构造.
this.books = books; // Add an attribute to Author.
}
Author.prototype = new Person(); // Set up the prototype chain.
Author.prototype.constructor = Author; // Set the constructor
Author.prototype.getBooks = function() { // Add method to Author.
return this.books;
};
function Person(name){
this.name = name;
}
Person.prototype.getName = function() {
return this.name;
};
function Author(name, books) {
Author.superclass.constructor.call(this, name);
this.books = books;
}
extend(Author, Person);
Author.prototype.getName = function() {
var name = Author.superclass.getName.call(this);
return name + ', Author of ' + this.getBooks().join(', ');
};
/* Extend function, improved.这处可以用JQUERY的EXTEND方法,比这个更好 */
function extend(subClass, superClass) {
var F = function() {};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype;
if(superClass.prototype.constructor == Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}
采用这种方式时,并不需要用类定义对象的结构,只需直接创建一个对象即可。这个对象随后可以被新的对象重用。这种方式可以看出,不需要定义子类的结构。代码更简洁,但也有优缺点,需要了解原型的实现方式。
function clone(object) {
function F() {}
F.prototype = object;
return new F;
}
var Person = {
name: 'default name',
getName: function() {
return this.name;
}
};
var reader = clone(Person);
如果原型中还有自己的子对象时,需要修改子对象的默认值时,最好采用工厂的创建方法。
var CompoundObject = {};
CompoundObject.string1 = 'default value',
CompoundObject.createChildObject = function() {
return {
bool: true,
num: 10
}
};
CompoundObject.childObject = CompoundObject.createChildObject();
var compoundObjectClone = clone(CompoundObject);
compoundObjectClone.childObject = CompoundObject.createChildObject();
compoundObjectClone.childObject.num = 5;
/*使receivingClass类具有givingClass类的所有方法*/
function augment(receivingClass, givingClass) {
for(methodName in givingClass.prototype) {
if(!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
}
}
/* 使receivingClass类具有givingClass类的特定方法. */
function augment(receivingClass, givingClass) {
if(arguments[2]) { // Only give certain methods.
for(var i = 2, len = arguments.length; i < len; i++) {
receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
}
}
else { // Give all methods.
for(methodName in givingClass.prototype) {
if(!receivingClass.prototype[methodName]) {
receivingClass.prototype[methodName] = givingClass.prototype[methodName];
}
}
}
}
//使类Author具有类BaseClass的SeriMethod方法
augment(Author, BaseClass, 'SeriMethod')
var Mixin = function() {};
Mixin.prototype = {
serialize: function() {
}
};
augment(Author, Mixin);//Author的代码省略了
augment(Author, Mixin, 'serialize');
var author = new Author('Ross Harmes', ['JavaScript Design Patterns']);
var serializedString = author.serialize();
定义一个构造函数,把元素作为数组保存在一个实例属性中,并让所有定义在构造函数的prototype属性所指对象中的方法都返回用以调用方法的那个实例的引用,那么就具有了进行链式调用的能力。
(function() {
function _$(els) {
}
_$.prototype = {
each: function(fn) {
for ( var i = 0, len = this.elements.length; i < len; ++i ) {
fn.call(this, this.elements[i]);
}
return this;
},
setStyle: function(prop, val) {
this.each(function(el) {
el.style[prop] = val;
});
return this;
}
};
window.$ = function() {
return new _$(arguments);
};
})();
$(window).addEvent('load', function() {
$('test-1', 'test-2').setStyle('color', 'red')
.addEvent('click', function(e) {
$(this).setStyle('color', 'green');
});
});
Function.prototype.method = function(name, fn) {
this.prototype[name] = fn;
return this;//指向_$
};
(function() {
function _$(els) {
}
_$.prototype = {
each: function(fn) {
for ( var i = 0, len = this.elements.length; i < len; ++i ) {
fn.call(this, this.elements[i]);
}
return this;
},
setStyle: function(prop, val) {
this.each(function(el) {
el.style[prop] = val;
});
return this;
}
};
_$.method('show', function(){//扩展方法,这样就不必全写在原型中了
return this;
});
window.installHelper = function(scope, interface) {
scope[interface] = function() {
return new _$(arguments);
}
};
})();
//定义此方法是防止$被占用
installHelper(window, '$');
var t = $('example').setStyle('color', 'green').show();//链式调用
链式调用很适合赋值操作,有时对于取值器方法,并不希望返回this,而是需要返回需要的数据,但如果不返回this就失去了链式调用的方便,所以这时可以使用callback Function。的方式。
window.API2 = window.API2 ||function(){
var name='ld';
this.setName = function(nn){
name = nn;
return this;
};
this.getName = function(cl){
cl.call(this, name);//此处相当于一个return
return this;
}
};
var o2 = new API2;
o2.getName(a).setName('Meow').getName(a);
function a(a){
console.log(a);
}