# 悟透 JavaScript - 原型扩展

**URL:** <https://www.isharkfly.com/t/javascript/8702>\
**Category:** 前端\
**Created:** [October 29, 2012, 4:51am UTC](https://www.isharkfly.com/t/javascript/8702 "2012-10-29T04:51:00Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![honeymoose](https://www.isharkfly.com/user_avatar/www.isharkfly.com/honeymoose/32/20796_2.png) [@honeymoose](https://www.isharkfly.com/u/honeymoose)\
**Post date:** [October 29, 2012, 4:51am UTC](https://www.isharkfly.com/t/javascript/8702/1 "2012-10-29T04:51:00Z")

</div>

想必君的悟性极高，可能你会这样想：如果在JavaScript内置的那些如Object和Function等函数的prototype上添加些新的方法和属性，是不是就能扩展JavaScript的功能呢？

那么，恭喜你，你得到了！

在AJAX技术迅猛发展的今天，许多成功的AJAX项目的JavaScript运行库都大量扩展了内置函数的prototype功能。[比如微软的ASP.NET](http://xn--ASP-eo8em6yd3sj9qbs9b.NET) AJAX，就给这些内置函数及其prototype添加了大量的新特性，从而增强了JavaScript的功能。

我们来看一段摘自MicrosoftAjax.debug.js中的代码：`String.prototype.trim = function String$trim() {
    if (arguments.length !== 0) throw Error.parameterCount();
    return this.replace(/^\s+|\s+$/g, '');
}`这段代码就是给内置String函数的prototype扩展了一个trim方法，于是所有的String类对象都有了trim方法了。有了这个扩展，今后要去除字符串两段的空白，就不用再分别处理了，因为任何字符串都有了这个扩展功能，只要调用即可，真的很方便。

当然，几乎很少有人去给Object的prototype添加方法，因为那会影响到所有的对象，除非在你的架构中这种方法的确是所有对象都需要的。

前两年，微软在设计AJAX类库的初期，用了一种被称为“闭包”(closure)的技术来模拟“类”。其大致模型如下：[code]function Person(firstName, lastName, age)  
{  
//私有变量：  
var \_firstName = firstName;  
var \_lastName = lastName;

```
//公共变量:
this.age = age;

//方法：
this.getName = function()
{
    return(firstName + " " + lastName);
};
this.SayHello = function()
{
    alert("Hello, I'm " + firstName + " " + lastName);
};

```

};

var BillGates = new Person(“Bill”, “Gates”, 53);  
var SteveJobs = new Person(“Steve”, “Jobs”, 53);

BillGates.SayHello();  
SteveJobs.SayHello();  
alert(BillGates.getName() + " " + BillGates.age);  
alert(BillGates.firstName); //这里不能访问到私有变量[/code]很显然，这种模型的类描述特别象C#语言的描述形式，在一个构造函数里依次定义了私有成员、公共属性和可用的方法，显得非常优雅嘛。特别是“闭包”机制可以模拟对私有成员的保护机制，做得非常漂亮。

所谓的“闭包”，就是在构造函数体内定义另外的函数作为目标对象的方法函数，而这个对象的方法函数反过来引用外层外层函数体中的临时变量。这使得只要目标对象在生存期内始终能保持其方法，就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束，临时变量的名称也都消失了，但在目标对象的方法内却始终能引用到该变量的值，而且该值只能通这种方法来访问。即使再次调用相同的构造函数，但只会生成新对象和方法，新的临时变量只是对应新的值，和上次那次调用的是各自独立的。的确很巧妙！

但是前面我们说过，给每一个对象设置一份方法是一种很大的浪费。还有，“闭包”这种间接保持变量值的机制，往往会给JavaSript的垃圾回收器制造难题。特别是遇到对象间复杂的循环引用时，垃圾回收的判断逻辑非常复杂。无独有偶，IE浏览器早期版本确实存在JavaSript垃圾回收方面的内存泄漏问题。再加上“闭包”模型在性能测试方面的表现不佳，微软最终放弃了“闭包”模型，而改用“原型”模型。正所谓“有得必有失”嘛。

原型模型需要一个构造函数来定义对象的成员，而方法却依附在该构造函数的原型上。大致写法如下：[code]//定义构造函数  
function Person(name)  
{  
this.name = name; //在构造函数中定义成员  
};

//方法定义到构造函数的prototype上  
Person.prototype.SayHello = function()  
{  
alert("Hello, I’m " + this.name);  
};

//子类构造函数  
function Employee(name, salary)  
{  
Person.call(this, name); //调用上层构造函数  
this.salary = salary; //扩展的成员  
};

//子类构造函数首先需要用上层构造函数来建立prototype对象，实现继承的概念  
Employee.prototype = new Person() //只需要其prototype的方法，此对象的成员没有任何意义！

//子类方法也定义到构造函数之上  
Employee.prototype.ShowMeTheMoney = function()  
{  
alert(this.name + " $" + this.salary);  
};

var BillGates = new Person(“Bill Gates”);  
BillGates.SayHello();

var SteveJobs = new Employee(“Steve Jobs”, 1234);  
SteveJobs.SayHello();  
SteveJobs.ShowMeTheMoney();[/code]原型类模型虽然不能模拟真正的私有变量，而且也要分两部分来定义类，显得不怎么“优雅”。不过，对象间的方法是共享的，不会遇到垃圾回收问题，而且性能优于“闭包”模型。正所谓“有失必有得”嘛。

在原型模型中，为了实现类继承，必须首先将子类构造函数的prototype设置为一个父类的对象实例。创建这个父类对象实例的目的就是为了构成原型链，以起到共享上层原型方法作用。但创建这个实例对象时，上层构造函数也会给它设置对象成员，这些对象成员对于继承来说是没有意义的。虽然，我们也没有给构造函数传递参数，但确实创建了若干没有用的成员，尽管其值是undefined，这也是一种浪费啊。

唉！世界上没有完美的事情啊！
