文章目录
  1. 1. javascript 原型编程详解

javascript 原型编程详解

当你定义javascript方法的时候,会产生一些预定义的属性,其中一个比较让人迷惑的属性就是prototype。在本文中,我们将详细介绍什么是Prototype,并且为什么使用prototype。


什么是prototype?


prototype属性初始时是一个空的对象,可以添加对象
,你可以添加任何对象到它里面去。

\

var myObject = function(name){

    this.name = name;

    return this;

};

console.log(typeof myObject.prototype);
// object

myObject.prototype.getName =
function(){

    return this.name;

};

在以上这段代码中,我们创建了一个方法,但是如果我们调用myObject(),将会返回window对象,因为它被定义在全局范围中。
this将会返回全局对象,因为没有被实例化。


console.log(myObject() === window); //
true

秘密的连接


每一个javascript中的对象都有一个秘密属性。 

在我们继续之前,我想讨论一下决定prototype工作方式的“秘密”连接。


每一个javascript对象在定义或者实例化的时候都会添加一个秘密的属性,叫__proto__,这决定了prototype链如何被访问。然而,在你的应用中访问这个__proto__属性绝对不是一个好主意,因为不是所有浏览器都可访问。


__prototype__属性在一个对象的prototype中不应该被弄混了,
因为它有两个分开的属性;意味着他们都是手拉手来使用的。对于弄清楚这个很重要。因为最开始的时候肯定比较令人迷惑。
那究竟什么意思呢? 这里我们解析一下。
当我们创建myObject方法时,我们定义了一个Function类型的对象。


console.log(typeof myObject); //
function

如果你不知道的话,
Function是一个javascript预定义的对象,这样的话,拥有自己的属性(例如,length和arguments)和方法(例如,call和apply)。这意味着,在javascript的引擎中,这里有类似如下代码的部分:


Function.prototype = {

    arguments: null,

    length: 0,

    call: function(){

        // secret code

    },

    apply: function(){

        // secret code

    }

    …

当然可能没有这么简单;不过这里只是演示prototype的链式如何工作的。

因此当我们定义myObject为一个方法并且提供一个参数name;但是并不设置其它属性和方法,例如length,和call,那么如下代码为什么可以工作?

console.log(myObject.length); // 1 (being
the amount of available arguments)

这是因为我们定义了myObject,它创建了__proto__属性并且设置数值为Function.prototype。因此,当我们访问myObject.lengh,会寻找myObject的属性,调用lenght,但是找不到。于是它将会沿链式向上,通过__proto__
link来找到属性并且返回。

 

你可能会问,为什么会返回1而不是0或者其它数值,因为myObject其实是Function的一个实例。

console.log(myObject instanceof
Function); // true console.log(myObject === Function); // false

当一个对象的实例创建后,__proto__属性将被更新然后指向构建器(constructor)的prototype,这里是Funciton。


console.log(myObject.__proto__ ===
Function.prototype) // true

而且当你创建一个新的Function对象,Function构建器的本地代码将会计算参数的个数并且更新this.length。这样你得到了1。


然而,如果我们使用new来创建一个新的myObject实例,__proto__将会指向myObject.prototype作为我们新的实例的构建器。


var myInstance = new
myObject(“foo”);

console.log(myInstance.__proto__ ===
myObject.prototype); // true

除了能够访问Function的Prototype中的本地方法call和apply,我们也可以访问myObject的方法getName。


console.log(myInstance.getName()); //
foo

var mySecondInstance = new
myObject(“bar”);

console.log(mySecondInstance.getName());
// bar

console.log(myInstance.getName()); //
foo

正如你猜到的,这个非常的实用,可以用来生成一个对象的基本设计图,创建尽可能多的对象。下一个主题中我们将介绍。


为什么使用prototype更好? 


比方说,我们要开发一个画布游戏需要几个对象。每一个对象都拥有自己的属性,例如,x和y参数,宽和高,其它等等。


var GameObject1 = {

    x: Math.floor((Math.random() *
myCanvasWidth) + 1),

    y: Math.floor((Math.random() *
myCanvasHeight) + 1),

    width: 10,

    height: 10,

    draw: function(){

        myCanvasContext.fillRect(this.x,
this.y, this.width, this.height);

    }

   …

};

 

var GameObject2 = {

    x: Math.floor((Math.random() *
myCanvasWidth) + 1),

    y: Math.floor((Math.random() *
myCanvasHeight) + 1),

    width: 10,

    height: 10,

    draw: function(){

        myCanvasContext.fillRect(this.x,
this.y, this.width, this.height);

    }

    …

};

创建这种对象98次… …


这样做会在内存中创建所有的对象 -
所有都使用分开的方法定义
,例如draw,还有其它的方法。这肯定不够理想,因为这个游戏会让javascript占用浏览器大量内存,运行会变得非常缓慢,甚至崩溃。


当然这种情况不会在创建100个对象而出现,
但是仍旧会对性能有不小的伤害。因为它会访问100个不同的对象,而不是一个prototype对象。


如何使用Prototype?


为了让程序运行的更快(或者使用最佳实践),我们可以重新定义GameObject的prototype属性;每一个实例都会参考GameObject.prototype,正如使用自己方法一样。


// define the GameObject constructor
function

var GameObject = function(width, height)
{

    this.x = Math.floor((Math.random() *
myCanvasWidth) + 1);

    this.y = Math.floor((Math.random() *
myCanvasHeight) + 1);

    this.width = width;

    this.height = height;

    return this;

};

 

// (re)define the GameObject prototype
object

GameObject.prototype = {

    x: 0,

    y: 0,

    width: 5,

    width: 5,

    draw: function() {

        myCanvasContext.fillRect(this.x,
this.y, this.width, this.height);

    }

};

我们可以实例GameObject 100次。


var x = 100,

arrayOfGameObjects = [];

 

do {

    arrayOfGameObjects.push(new
GameObject(10, 10));

} while(x—);

现在我们拥有了一个100个对象的数组,所有对象都共享同样的prototype和draw方法定义。


当我们调用draw方法。会参考同样一个方法:


var GameLoop = function() {

    for(gameObject in arrayOfGameObjects)
{

        gameObject.draw();

    }

};

Prototype是一个活动的对象


一个对象的prototype是一个活动的对象。简单来说,在我们创建了我们的Gameobject实例后,如果我们不想绘制矩形,我们可以更新我们的GameObject.prototype.draw方法来绘制一个圆形。


GameObject.prototype.draw = function()
{

    myCanvasContext.arc(this.x, this.y,
this.width, 0, Math.PI*2, true);

}

现在如果再调用draw方法将会都绘制圆形了。


更新本地对象prototype


没错,这也有可能。你可能比较熟悉某些javascript类库,例如,Prototypes
,这个类库就利用了这个方法。


我们看一个简答的例子:


String.prototype.trim = function()
{

    return this.replace(/^\s+|\s+$/g,
‘’);

};

我们可以使用字符串来调用这个方法:


“foo bar “.trim(); // “foo bar”

这里有一个负面的影响。例如,你可以在你的web应用中使用这个方法,但是1,2年后,一个浏览器可能也会在String的prototype中实现类似的本地方法trim。这意味着你的trim定义会覆盖本地版本。为了解决这个问题,我们可以添加一个简单的检查,如下:


if(!String.prototype.trim) {

    String.prototype.trim = function()
{

        return
this.replace(/^\s+|\s+$/g, ‘’);

    };

}

现在如果存在本地版本,将会使用本地版本的trim方法。


一般的规律来说,我们要避免扩展本地对象。但是,必要情况下,规矩是可以被打破的。

文章目录
  1. 1. javascript 原型编程详解