`
hhr_michael
  • 浏览: 72708 次
  • 性别: Icon_minigender_1
  • 来自: 惠州
社区版块
存档分类
最新评论

JavaScript高级程序设计 (读书笔记7)

阅读更多
定义类或对象

1 工厂方式

对象的属性可在对象创建后动态定义

问题是可能需要创建多个car实例。

所有这些问题引发了开发者定义的构造函数的出现。

2 构造函数方式
在构造函数内部无创建对象,而是使用this关键字。

现在,用new运算符和类名car创建对象,就更像创建ECMAScript中一般对象了。你也许会问,这种方式在管理函数方面是否存在与前一种方式相同的问题呢?是的。

就像工厂函数,构造函数会重复生成函数,为每个对象都创建独立的函数版本。


3 原型方式

该方式利用了对象的prototype属性,可把它看成创建新对象所依赖的原型。
这里,用空构造函数来设置类名。
然后所有的属性和方法都被直接赋予prototype属性。


首先,这个构造函数没有参数。使用原型方式时,不能通过给构造函数传递参数初始化属性的值,
因为car1和car2的color属性都等于"red",doors属性都等于4,mpg属性都等于23。
这意味必须在对象创建后才能改变属性的默认值,这点很令人讨厌,但还不至于是世界末日。

真正的问题出现在属性指向的是对象,而不是函数时。

函数共享不会造成任何问题,但对象却很少被多个实例共享的。

由于创建对象时有这么多问题,你一定会想,是否有种合理的创建对象的方法呢?答案是联合使用构造函数和原型方式。


4 混合的构造函数/原型方式

联合使用构造函数和原型方式,就可像用其他程序设计语言一样创建对象。

这种概念非常简单,即

用构造函数定义对象的所有非函数属性,

用原型方式定义对象的函数属性(方法)。


function Car (sColor, iDoors, iMpg){
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.drivers = new Array("Mike","Sue");
}

Car.prototype.showColor = function(){
        alert(this.color);
};

var oCar1 = new Car("red", 4, 23);
var oCar2 = new Car("black", 5, 24);
oCar1.drivers.push("Matt");

alert(oCar1.drivers);//outputs Mike Sue Matt
alert(oCar2.drivers);//outputs Mike Sue


这种方式是ECMAScript主要采用的方式,它具有其他方式的特性,却没有它们的副作用。不过,有些开发者仍觉得这种方法不够完美。


5 动态原型方法

动态原型方法的基本想法与混合的构造函数/原型方式相同,即在构造函数内定义非函数属性,而函数属性则利用原型属性定义。唯一的区别是赋予对象方法的位置。

function Car (sColor, iDoors, iMpg){
    this.color = sColor;
    this.doors = iDoors;
    this.mpg = iMpg;
    this.drivers = new Array("Mike","Sue");
    if(typeof Car._initialized == "undefined"){
        Car.prototype.showColor = function(){
            alert(this.color);
        };
    }
}


直到检查typeof Car._initialized是否等于"undefined"之前,这个构造函数都未发生变化。
这行代码是动态原型方法中最重要的部分。

如果这个值未定义,构造函数将用原型方式继续定义对象的方法,然后把Car._initialized设置为true。

如果这个值定义了(它的值为true时,typeof的值为Boolean),那么就不再创建该方法。

简而言之,该方法使用标志(_initialized)来判断是否已给原型赋予了任何方法。

该方法只创建并赋值一次,为取悦传统的OOP开发者,这段代码看起来更像其他语言中的类定义了。


6 混合工厂方式

这种方式通常是在不能应用前一种方式时的变通方法。它的目的是创建假构造函数,只返回另一种对象的新实例。

这种方式在对象方法的内部管理方面与经典方式有着相同的问题。

强烈建议:除非万不得已(请参阅第15章),还是避免使用这种方式。


7 采用哪种方式

如前所述,目前使用最广泛的是混合的构造函数/原型方式。

此外,动态原型方法也很流行,在功能上与构造函数/原型方式等价。

可以采用这两种方式中的任何一种。

不过不要单独使用经典的构造函数或原型方式,因为这样会给代码引入问题。


8 实例

对象令人感兴趣的一点是用它们解决问题的方式。ECMAScript中最常见的一个问题是字符串连接的性能。

与其他语言类似,ECMAScript的字符串是不可变的,即它们的值不能改变。考虑下面的代码:
var str = "hello";
str += "world";

实际上,这段代码在幕后执行的步骤如下:

(1) 创建存储"hello"的字符串。

(2) 创建存储"world"的字符串。

(3) 创建存储连接结果的字符串。

(4) 把str的当前内容复制到结果中。

(5) 把"world"复制到结果中。

(6) 更新str,使它指向结果。

每次完成字符串连接都会执行步骤2到6,使得这种操作非常消耗资源。如果重复这一过程几百次,甚至几千次,就会造成性能问题。

解决方法是用Array对象存储字符串,然后用join()方法(参数是空字符串)创建最后的字符串。想像用下面的代码代替前面的代码:

var arr = new Array();
arr[0] = "hello";
arr[1] = "world";
var str = arr.join("");

这样,无论在数组中引入多少字符串都不成问题,
因为只在调用join()方法时才会发生连接操作。

此时,执行的步骤如下:

(1) 创建存储结果的字符串。
(2) 把每个字符串复制到结果中的合适位置。

虽然这种解决方法很好,但还有更好的方法。

问题是这段代码不能确切反映出它的意图。要使它更容易理解,可以用StringBuffer类打包该功能:

function StringBuffer(){
    this._strings_ = new Array;
}
StringBuffer.prototype.append = function(str){
    this._strings_.push(str);
};
StringBuffer.prototype.toString = function(){
    return this._strings_.join("");
};


第一点要注意的是,这段代码是strings的属性,本意是私有属性。
它只有两个方法,即append()和toString()方法。
append()方法有一个参数,它把该参数附加到字符串数组中,
toString()方法调用数组的join()方法,返回真正连接成的字符串。

要用StringBuffer对象连接一组字符串,可以用下面的代码:
var buffer = new StringBuffer();
buffer.append("hello");
buffer.append("world");
alert(buffer.toString());
可用下面代码测试StringBuffer对象和传统的字符串连接方法的性能:

这段代码对字符串连接进行两个测试,第一个使用加号,第二个使用StringBuffer类。每个操作都连接10000个字符串。日期值d1和d2用于判断完成操作需要的时间。记住,创建新Date对象时,如果没有参数,赋予对象的是当前的日期与时间。要计算连接操作历经多少时间,把日期的毫秒表示(getTime()方法的返回值)相减即可。这是衡量JavaScript性能的常用方法。该测试的结果应该说明使用StringBuffer类比使用加号节省了100%~200%的时间。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics