目前为止看到的继承模式的问题是:无法保护隐私,对象的所有属性都是可见的。有一些无知的程序员会使用伪装私有的模式,即给一个需要私有的属性起一个古怪的名字,并希望其他使用代码的程序员假装看不到它们!
其实有更好的方法:应用模块模式。
我们先构造一个生成对象的函数,它有这些步骤:
①. 创建新对象。这有四种方式:
【1】构造一个对象字面量。
【2】调用一个构造器函数。
【3】构造一个已存在对象的新实例。
【4】调用任意一个会返回对象的函数。
②. 定义私有实例变量与方法。
③. 为这个新对象扩充方法,这些方法拥有特权去访问这些参数。
④. 返回这个新对象。
函数化构造器的伪代码如下:
var constructor = function (spec, my){ var that, 其他私有变量; my = my || {}; //把共享的变量和函数添加到 my 中 that = 一个新对象 //添加给 that 的特权方法 return that; };
spec 对象包含了需要构造一个新实例的所有信息,它可以被用到到私有变量或者其他函数中。
my 对象为在一个继承链中的构造器提供了共享的容器,如果没有传入,那么会创建一个 my 对象。
创建特权方法的方式是:把函数定义为私有方法,然后再把它们分配给 that:
var methodical = function (){ ... }; that.methodical = methodical;
这样分两步定义的好处是:私有的 methodical 不受这个实例被改变的影响。
现在,我们把这个模式应用到 mammal 示例中:
var mammal = function (spec) { var that = {}; that.get_name = function () { return spec.name; }; that.says = function () { return spec.saying || ''; }; return that; }; var myMammal = mammal({name: 'Herb'}); console.log(myMammal.get_name());//Herb var cat = function (spec) { spec.saying = spec.saying || 'meow'; var that = mammal(spec); that.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; that.get_name = function () { return that.says() + ' ' + spec.name + ' ' + that.says(); }; return that; }; var myCat = cat({name: 'Henrietta'}); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow
函数化模式还能调用父类的方法。这里我们构造一个 superior 方法,它会返回调用某个方法名的函数:
//返回调用某个方法名的函数 Object.method('superior', function (name) { var that = this, method = that[name]; return function () { return method.apply(that, arguments); }; });
现在创建一个 coolcat,它拥有一个可以调用父类方法的 get_name:
var coolcat = function (spec) { var that = cat(spec), super_get_name = that.superior('get_name'); that.get_name = function (n) { return 'like ' + super_get_name() + ' baby'; }; return that; }; var myCoolCat = coolcat({name: 'Bix'}); console.log(myCoolCat.get_name());//like meow Bix meow baby
函数化模式有很大的灵活性,而且可以更好地实现封装、信息隐藏以及访问父类方法的能力。
如果对象所有的状态都是私有的,那么就称为防伪对象。这个对象的属性可以被替换或删除,但这个对象的状态不受影响。如果用函数化模式来创建对象,并且这个对象的所有方法都不使用 this 或 that,那么这个对象就是持久性的,它不会被入侵。除非存在特权方法,否则不能访问这个持久性对象的内部状态。
5 事件处理函数可以构造一个能够给任何对象添加简单事件处理特性的函数。这里,我们给这个对象添加一个 on 方法,fire 方法和私有的事件注册对象:
var eventuality = function (that) { var registry = {}; /** * 触发事件 * * 使用 'on' 方法注册的事件处理程序将被调用 * @param 可以是包含事件名称的字符串,或者是一个拥有 type 属性(值为事件名称)的对象。 */ that.fire = function (event) { var array, func, handler, i, type = typeof event === 'string' ? event : event.type; //如果这个事件已被注册,则遍历并依序执行 if (registry.hasOwnProperty(type)) { array = registry[type]; for (i = 0; i < array.length; i += 1) { handler = array[i];//处理程序包含一个方法和一组可选的参数 func = handler.method; if (typeof func === 'string') {//如果方法是字符串形式的名称,则寻找它 func = this[func]; } //调用它。如果处理程序包含参数,则传递过去,否则就传递事件对象 func.apply(this, handler.parameters || [event]); } } return this; }; /** * 注册一个事件 * @param type * @param method * @param parameters */ that.on = function (type, method, parameters) { var handler = { method: method, parameters: parameters }; if (registry.hasOwnProperty(type)) {//如果已存在,就新增数组项 registry[type].push(handler); } else {//新增 registry[type] = [handler]; } return this; }; return that; };
可以在任何单独对象上调用 eventuality,授予它事件处理方法。也可以在 that 被返回前,在构造函数中调用它:
eventuality(that);
JavaScript 弱类型的特性在此是一个巨大的优势,因为我们无须处理对象继承关系中的类型O(∩_∩)O~