- 概述
Javascript 是一门弱类型语言,对象的起源是无关紧要的。对于一个对象来说重要的是它能做什么,而不是它从哪里来。Javascript 提供了一套丰富的代码重用模式。它可以模拟那些基于类的模式,同时它也可以支持其他更具表现力的模式. 在JS中可能的继承模式有很多。在这里,我们将研究几种最为直接的模式.
在基于类的语言中,对象是类的实例,并且类可以从另一个类继承。而Javascript是一门基于原型的语言,这意味着对象直接从其他对象继承。
- 伪类模式
Javascript 的原型存在着诸多矛盾。它不让对象直接从其他对象继承,反而插入了一个多余的间接层. 它使用构造器函数产生对象。这种构造器函数就是一个伪类.
当一个函数对象被创建时,Function 构造器产生的函数对象会运行类似下面的代码:
this.prototype = {constructor : this};
新函数对象被赋予一个 prototype 对象,该对象又包含一个 constructor 属性。而这个constructor属性的值即为该新函数自身. 因为Javascript 语言并没有提供一种方法去确定哪个函数作为构造器。所以每个函数都会得到一个 prototype 对象。 大多数情况下,constructor 属性没什么用,重要的是prototype 对象.
这里假设new操作为一个方法,那么当使用new去构造对象时,可能的执行代码如下:
Function.method("new", function(){
//创建一个继承自身原型的对象
var that = Object.beget(this.prototype);
//调用构造器函数,绑定 this 到新对象上
var other = this.apply(that,arguments);
//如果它的返回值不是一个对象,则返回新对象
return (typeof other === 'object' && other) || that;
}
由上可知,我们可以定义一个构造器函数,并对其原型进行扩展.
var Mammal = function(name){ // 由约定可知,构造器函数使用大写开头
this.name = name;
};
Mammal.prototype.get_name = function(){
return this.name;
};
Mammal.prototype.says = function(){
return this.saying || "";
};
var myMammal = new Mammal("Herb the Mammal");
var name = myMammal.get_name();
document.writeln(name);
var Cat = function(name){
this.name = name;
this.saying = "meow";
};
// 替换Cat.prototype,使其"继承" Mannal
Cat.prototype = new Mammal();
Cat.prototype.purr = function(n){
var i,s = "";
for(i = 0 ; i < n ; i ++){
if(s){
s+="-";
}
s += "r";
}
return s;
};
// 覆盖原型链中的get_name()
Cat.prototype.get_name = function(){
return this.says() + " " + this.name + " " + this.says();
};
var myCat = new Cat("Henrietta");
document.writeln(myCat.says()); // meow
document.writeln(myCat.purr(5)); // r-r-r-r-r
document.writeln(myCat.get_name()); // meow Henrietta meow
伪类模式的本意是模拟OO语言中的继承,向面向对象靠拢,但它看起来显得格格不入。下面可以通过一些辅助方法来隐藏一些丑陋的细节.
Function.method("inherits",function(Parent){
this.prototype = new Parent();
return this;
});
var Dog = function(name){
this.name = name;
this.saying = "wanwan";
}.inherits(Mammal).method("purr",function(n){
var i,s = "";
for(i = 0 ; i < n ; i ++){
if(s){
s+="-";
}
s += "w";
}
return s;
}).method("get_name",function(){
return this.says() + " " + this.name +" " + this.says();
});
var myDog = new Dog("Kiten");
document.writeln(myDog.says()); // wanwan
document.writeln(myDog.purr(5)); // w-w-w-w-w
document.writeln(myDog.get_name()); // wanwan Kiten wanwan
虽然通过隐藏一些繁琐的针对prototype的操作细节,使用伪类继承看起来没那么怪异了.但我们是否真的有所改善呢? 我们现在有了行为像 “类” 的构造器函数,但仔细去看,他们却存在令人惊讶的行为: 没有私有环境,所有属性都是公开的。无法访问父类的方法(super). 更糟糕的是,使用构造器函数存在一个严重的危害。如果你在调用构造器函数时忘了在前面加上new操作符,那么this将会被绑定到全局对象上,这样你既没有扩充新对象,反而破坏了全局变量。而且此时既没有编译时警告,也没有运行时警告。这是一个严重的语言设计错误。为了降低产生这个问题的风险,所有构造器函数都约定为使用首字母大写命名。
“伪类” 形式可以给不熟悉Javascript 的程序员提供便利,但它也已隐藏了该语言的真实本质。借鉴类的表示法可能误导程序员去编写过于深入与复杂的层次结构。而Javascript中却有更好的选择。
- 函数化模式
在一个纯粹的原型模式中,我们会摒弃类,转而专注于对象。基于原型的继承相比基于类的继承在概念上更为简单:一个新对象可以继承一个旧对象的属性。通过构造一个有用的基础对象,接着可以构造更多和那个对象类似的对象。可以完全避免把一个应用拆解成一些列嵌套抽象类的分类过程。
// 我们先使用字面变量去构造一个有用的对象:
var myMammal = {
name : "Herb the Mammal",
get_name : function(){
return this.name;
},
says : function(){
return this.saying || "";
}
};
// 接下来我们可以使用 beget 方法构造出更多的实例,之后对实例进行定制。
var myCat = Object.beget(myMammal);
myCat.name = "Henrietta";
myCat.saying = "meow";
myCat.purr = function(n){
var i,s="";
for(i = 0 ; i < n ; i ++){
if(s){
s+="-";
}
s+="s";
}
return s;
};
myCat.get_name = function(){
return this.says + " " + this.name + " " + this.says;
};
这是一种 “差异化继承”. 通过定制该新对象,我们指明了它与其基类对象的区别.
- 隐私的保护
迄今为止,我们所论述的继承模式都存在一个弱点:我们没法保护隐私. 对象的所有属性都是可见的。我们设置保护私有变量和私有函数。其中一个解决方法是使用模块模式。下面是一个使用模块模式的伪代码模板:
// var constructor = function(spec,my){
// var that, 其他私有实例变量;
// my = my || {};
// 把共享变量和函数添加到my中;
// that = 一个新对象;
// 添加that 中的方法(特权方法);
// return that;
// }
使用示例:
var mammal = function(spec){
var that = {};
that.get_name = function(){
return spec.name;
};
that.says = function(){
return spec.saying || "";
};
return that;
};
var myProtectedMammal = mammal({name:"Herb"});
- 高级的函数化模式
在伪类模式中,构造器函数Cat 不得不重复其基类构造器Mammal 已经完成的工作(初始化自身属性). 而且函数化模式中却不再需要这么做,因为构造器cat将会调用构造器mammal, 让mammal 去完成初始化工作。所以Cat只需关注自身的差异即可。
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 ++){
if(s){
s+="-";
}
s+="r";
}
return s;
};
that.get_name = function(){
return that.says() + " " + spec.name + " " + that.says();
};
return that;
};
var myProtectedCat = cat({name:"Henrietta"});
函数化模式还给我们提供了一个处理父类方法的机会. 我们将构造一个 superior 方法,它取得一个方法名并返回调用哪个方法的函数.该函数将调用原来的方法。(装饰/代理模式)
Object.method("superior",function(name){
var that = this,method = that[name];
return function(){
return method.apply(that,arguments);
};
});
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"});
document.writeln(myCoolCat.get_name());// like meow Bix meow baby!
函数化模式有很大的灵活性。它不仅不像伪类模式那样需要花费很多功夫,还让我们得到更好的封装和信息隐藏,以及访问父类方法的能力。我们可以从一套部件中组合出对象。例如,我们可以构造一个能添加简单事件处理特性到任何对象上的函数.他会给对象添加一个on方法,一个fire方法和一个私有的事件注册表对象。
var eventuality = function(that){
var registry = {};
that.fire = function(event){
// 触发通过 'on' 方法注册的事件处理程序。该事件可以是一个包含事件名称的字符串,
// 或是一个拥有type属性的对象.
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++){
handler = array[i];
func = handler.method;
// 每个处理程序包含一个方法和一组可选的参数.
// 如果该方法是一个字符串形式的名字,那么就查找该函数.
if(typeof func === 'string'){
func = this[func];
}
// 调用处理程序。如果该条目包含参数,那么传递它们过去.否则,传递该事件对象。
func.apply(this, handler.parameters || [event]);
}
}
return this;
};
// 注册一个事件
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;
};
用这种方式,一个构造器函数可以从一套部件(函数)中组装出对象。Javascript的弱类型在 此处是一个巨大的优势,因为我们无须花费精力去关注整个类型系统。相反,我们可以专注于它们的个性化内容。
相关推荐
JavaScript: The Good Parts 是 Douglas Crockford 著名的一本书,它深入探讨了JavaScript语言的精华部分,帮助开发者避开语言中的陷阱并充分利用其优势。这篇读书笔记将聚焦于书中的核心概念和重要知识点。 首先,...
《JavaScript的精华部分笔记》是基于道格拉斯·克罗克福德(Douglas Crockford)的著作《JavaScript: The Good Parts》的一份详细注解。这份开源资源深入解析了JavaScript语言中最为核心、最有价值的部分,帮助...
"LearningJS: 通过 Crockford 和其他来源的 Good Parts 书学习 JS" 提到的学习资源,很可能是指 Douglas Crockford 的《JavaScript: The Good Parts》一书和其他相关材料。这本书是 JS 开发者的经典之作,Crockford ...
《JavaScript The Good Parts》是Douglas Crockford所著的一本深入探讨JavaScript语言核心特性的书籍。在学习JavaScript的过程中,理解假值(falsy values)以及全等运算符(===)是非常重要的。假值是指在...
关于道格拉斯·克罗克福德的《 JavaScript的精髓》 :rocket: 这些都是书上的笔记Javascript the Good Parts ,其于2008年出版,ES6之前。 在撰写本文时,该书尚未发布任何修订版,有关此方面的持续讨论,请参见 。 ...
在JavaScript编程中,作用域和闭包是两个非常重要的概念,它们对于理解代码的执行流程以及如何有效地管理变量至关重要。本文将深入探讨这两个主题,并举例说明如何使用它们来避免全局空间污染。 首先,我们来看看...
在探讨JavaScript编程语言时,作用域、闭包和避免全局变量污染是几个非常重要的概念。作用域决定了代码块中变量和其他资源的可见性和生命周期,闭包是JavaScript中一个强大的特性,它允许函数访问并操作函数外部的...
附件中的《OReilly.JavaScript.The.Good.Parts.May.2008.pdf》可能是这本书的电子版,详细解释了JavaScript语言的各个方面,对于理解webOS的JavaScript开发非常有帮助。而“Kristen Stewart.jpg”可能是无关文件,与...
前端面试笔记 可能在前端职位面试中出现的有用笔记和算法的集合。 仅供学术使用。 Challenges.js 包含一些在 JS 中...crockford.js 包含来自“Javascript the Good Parts”的注释 dom.js 包含有用的 DOM 方法和属性