之前写的一篇文章,发出来!
前提:在阅读本篇文章的内容之前,请先阅读Klesh Wong 的 理解Javascript中类的定义 这篇文章。
关于如何在Javascript中定义类,网上可以找到不少的文章。在开始讲之前,还是先来看看定义类的两种基本方式吧: (1) 利用函数构造类型。 function Foo(text, url) { this.text = text; this.url = url;
this.render = function() { document.write('<a href="' + this.url + '">' + this.text + '</a>'); } } (2) 利用原型prototype。 function Bar(text, url) { this.text = text; this.url = url; } Bar.prototype = { render : function() { document.write('<a href="' + this.url + '">' + this.text + '</a>'); } }
代码并不复杂,相信大多数人看完之后都会晓得怎么使用了。但是当我第一次看到这种类的定义方式的时候觉得很奇怪,很长一段时间里都是知其然而不知其所以然。如果你有和我一样的困惑,也许可以分享一下我的一点经验。 先来看看怎么使用定义好的类型, var a = new Foo('Link A', 'http://www.cnblogs.com'); var b = new Bar('Link B', 'http://www.youkuaiyun.com'); a.render(); b.render();
跟很多面向对象语言(如C#)的使用方法一样,都是使用new关键字实例化类,而且当你用instanceof来测试它们是不是相应类型时,都可以得到预望的答案。两种不同方式定义出来的类型Foo和Bar很多时候使用起来并没有什么不同。 但它们在原理上是完全不同,方式1,是在构造函数中动态中创建类的成员,这就意味着每个Foo的实例它们的render方法其实是两个完全独立的函数;而方式2,则是通过原型prototype的方式将类的共享成员与类的实例绑定在一起,所以每个Bar实例的render方法都是指向同一个方法,它是静态的。请看以下代码: document.write(Object.prototype==String.prototype); var c = new Foo('Link C', 'http://www.asp.net'); document.write(c.render == a.render); // false var d = new Bar('Link D', 'http://www.klesh.cn); document.write(d.render == b.render); // true
Foo在实例化后就是一个独立的个体,它的任何改动不会影响到原来的类型定义,也不会影响到其他的实例;而Bar则不同,它的所有实例都依然受prototype的影响,通过对Bar.prototype的修改或扩展,可以影响到所有包括已经实例化的实例。回过头来看Foo,由于它的成员,特别是成员函数是动态创建的,利用闭包,则可以模拟面向对象中的“私有成员”,这一点内容会很长,稍后有时间再和大家分享。 两种方式各有特点。但是最后,严格来说,无论什么方式,都只能说是“模拟自定义类型”,因为在基于对象的Javascript中,它事实上是没有原生的“自定义类型”的概念的(这一点也很长,暂不细说)。 最后推荐大家一般使用方式2来在Javascript模拟自定义类,一来,速度上会比较快,而且还可以通过prototype对类型进行修改,除非你有需要到“私有成员”(好像也有人叫“闭包“)……除非你有需要用到闭包来模拟“私有成员”。 删除线和斜体部分根据木野狐同志的意见修正。 |
这篇文章已经将得比较清楚,但是选择的例子可能不太能够体现两种方式的区别,下面就我实际应用中遇到的问题列给大家:
目的:自定义的String操作Concat
先来看用javascript 自身的concat方法实现的字串的拼接代码:
function testBody()
{
var clientHeight = document.body.clientHeight;
var offsetHeight = document.body.offsetHeight;
var screenHeight = window.scrollHeight;
var scrollHeight = document.body.scrollHeight;
var str1 = new String();
str1 = str1.concat("clientHeight:").concat(clientHeight).concat(",");
str1 = str1.concat("offsetHeight:").concat(offsetHeight).concat(",");
str1 = str1.concat("screenHeight:").concat(screenHeight).concat(",");
str1 = str1.concat("scrollHeight:").concat(scrollHeight).concat(",");
alert(str1);
}
这里面有个问题,每次我们操作concat方法的时,都要把结果重新赋给一个新的String 对象:str1,
这样我们才能通过str1得到最终的结果。如果改成这样:
var str1 = new String();
str1.concat("clientHeight:").concat(clientHeight).concat(",");
str1.concat("offsetHeight:").concat(offsetHeight).concat(",");
str1.concat("screenHeight:").concat(screenHeight).concat(",");
str1.concat("scrollHeight:").concat(scrollHeight).concat(",");
alert(str1);
结果是空值。
于是我们可以用前面所讲到的两种方法来实现新的String类的定义,我们先用prototype类型:
RichString = function(value)
{
this._value = value;
}
RichString.prototype.Concat = function(value)
{
this._value = this._value.concat(value);
return new RichString(this._value);//必须返回一个新的RichString对象,才能实现后续的Concat操作
}
RichString.prototype.ToString = function()
{
return this._value;
}
我们再将上面的代码改为:
var str = new RichString("");
str.Concat("clientHeight:").Concat(clientHeight).Concat(",");
str.Concat("offsetHeight:").Concat(offsetHeight).Concat(",");
str.Concat("screenHeight:").Concat(screenHeight).Concat(",");
str.Concat("scrollHeight:").Concat(scrollHeight).Concat(",");
alert(str.ToString());
输出的结果确是:clientHeight:offsetHeight:screenHeight:scrollHeight:,后面的值包括逗号都没有输出。
我们再修改一下代码:
var str = new RichString("");
str.Concat("clientHeight:");str.Concat(clientHeight);str.Concat(",");
str.Concat("offsetHeight:");str.Concat(offsetHeight);str.Concat(",");
str.Concat("screenHeight:");str.Concat(screenHeight);str.Concat(",");
str.Concat("scrollHeight:");str.Concat(scrollHeight);str.Concat(",");
可以看到,这次的结果是正确的。原因就是每调用一次Concat方法返回的都是一个新的对象,如果连写:
str.Concat("clientHeight:").Concat(clientHeight).Concat(","); 返回的永远都是第一次创建的对象;分开写:
str.Concat("clientHeight:");str.Concat(clientHeight);str.Concat(","); 每次都将新对象赋给当前对象,所以值是连续的,如果这样的话,岂不是跟javascript本身的写法毫无区别?
我们来看采用第一种(利用函数构造类型)的写法:
RichString = function(value)
{
this._value = value;
this.Concat =function(value)
{
this._value = this._value.concat(value);
return this;
};
this.ToString =function()
{
return this._value;
};
}
var str = new RichString("");
str.Concat("clientHeight:").Concat(clientHeight).Concat(",");
str.Concat("offsetHeight:").Concat(offsetHeight).Concat(",");
str.Concat("screenHeight:").Concat(screenHeight).Concat(",");
str.Concat("scrollHeight:").Concat(scrollHeight).Concat(",");
alert(str.ToString());
输出结果是正确的,所以第一种:利用函数构造类型,每次返回的都是对象本身,操作的结果也是对其自身的改变;而第二种:利用prototype构造类型,其实相当于静态函数,每个对象都可以调用,是公共的。两种情况好比C#语言里面的new string(),跟String区别(实体类型跟静态类型)。