JavaScript对象与继承教程之内置对象(上)

JavaScript对象及继承教程

一、类与对象

在JavaScript世界里,关于面向对象第一个要澄清的概念就是类。对象都是有类来定义的,通过类来创建对象就是我们所熟悉的实例化。然而,在JavaScript中别没有真正的类,对象的定义就是对象自身。而ECMA-262干脆把这种妥协的方式称作为对象的调和剂。为了方便理解,我通常把这个发挥类的作用的调和剂称为类。

 

二、内置对象


1、Array类

数组在js中是非常常用的一种数据结构,由于其灵活性和易用性,合理的使用数组可以帮助我们更好的实现相应的功能。
让我们先看Array对象的创建吧
第一种:

var arr = new Array(10);

该方法在实际的使用当中并不那么的实用,与很多编译型语言不同,js数组的长度是可变的,不但增强了灵活性,还给我们有了更多好的选择。

第二种:

var arr = newArray("one","two","three");

使用new方式创建数组的方法一般多为这两者,当然也可以使用new Array()创建一个空的数组对象。通常情况下,我推荐如下的方法
第三种:

var arr =["one","two","three"];

使用数组的字面量方式创建一个数组对象,不但简洁易读,而且几乎完全等价于使用new方式创建数组对象的效果。数组对象有很多好用的方法,接下来我们就一起看看这个数组对象的强大功能吧。

首先要介绍的是push方法,学过数据结构的朋友都知道push意味着什么,没错,他的出现让数组能够实现栈的数据结构(同时需要配合pop方法)。push方法帮助我们紧凑的添加数组元素。前面提到js中的数组是长度是可变的,则我们可以添加元素。既然可以通过arr[length] = newValue;来给arr添加一个新元素并放置于数组尾。更好的办法就是使用push方法。arr.push(newValue);怎么样,使用他比你还要通过数组长度来赋新值方便多了吧。在这里有一点需要注意。请看下面的代码:

var arr = [];
arr[4] = 5;
alert(arr.length == 5);
alert(arr); //alert :,,,,5


当我们需要给指定数组位置赋予指定的值的时候,这种赋值就显得十分有用了,比如在用于装箱排序的时候。
pop方法则是实现与push相反的作用,返回数组的最后一个元素,并出栈。

var arr = [1,2,3,4,5];
var ele = arr.pop();
alert(ele == 5);
alert(arr.length == 4);

数组对象的toString和valueOf方法比较人性化的重写了,它的实现是把每一项都调用toString方法,然后用半角逗号(,)连接每一项。那么:

var arr = [1,2,3,4,5];
alert(arr);//output:1,2,3,4,5

toLocaleString方法在这里不做详细说明了,他的效果与toString方法类似,只是每项调用toLocateString方法。

如果你想使用个性化的分隔符来显示数组元素,那么join方法可能会更加的适合。比如:

var city = ["上海","北京","天津","重庆","深圳"];
alert(city.join("|"));//output:上海|北京|天津|重庆|深圳

由此可见join是把数组元素转换为一个字符串。在介绍字符串的时候我们将再次看到join方法的使用。

concat方法和slice方法是又一对好用的方法,这两个方法的特殊之处在于String对象也拥有他们。当我们希望给一个数组添加多个数组元素的时候,使用push可能就显得有些冗余和复杂了,而且也会让coding变得不那么有意思了。好在我们有concat方法,该方法将其参数们按序加入数组元素中。如:

var arr = [1,2,3,4,5];
arr =arr.concat(6,7,8,9);
alert(arr.length == 9);

注意,concat并不修改数组对象本身,而是将数组元素与concat方法的数组元素合并后返回。所以需要给数组元素进行赋值运算才行。

slice方法则是从数组对象中返回一个子数组。该子数组是从slice方法的第一个参数所指位置至第二个参数所指的位置。这是一个半开半闭区间[a,b)。如:

var arr = [1,2,3,4,5];
var arr1 =arr.slice(1,3);
alert(arr1);//output:2,3
alert(arr);//output:1,2,3,4,5

好了,slice和concat方法一样不是修改数组对象本身。同时参数1,3表示从位置1到位置3的半开半闭区间的子数组。

刚才讨论了后进先出的栈操作,现在我们来看看先进先出的队列操作吧。进列使用push方法没有问题,那么出列呢。是shift,他删除数组对象的第一个元素并返回:

var arr = [1,2,3,4,5];
var ele = arr.shift();
alert(ele); //output:1
alert(arr.length);//output:4


另外一个还有一个方法,叫unshift,他将新元素插入数组对象的第一项,究其功能与shift是相反的操作。
sort方法很灵活,使用好了,他可以给数组元素以任意你想要的排序方式来进行排序。因为sort方法接收一个匿名函数(其实,它同样可以接收一个非匿名的函数,但是通常不推荐为此而创建一个这样的命名函数,除非该函数可重用)作为自己的排序的条件。比如:

Object.prototype.toString= function(){
    var str = [];
    for (var i in this){
        str.push(i+':'+this[i]);
    }
    return str.join(',');
};
var arr =[{key:3,value:"three"},{key:1,value:"one"},{key:2,value:"two"}];
arr.sort(function(a,b){
    return a.key - b.key;
});
alert(arr);//output:key:1,value:one,key:2,value:two,key:3,value:three

我们先不去纠结Object.prototype.toString方法,他的左右就是将对象遍历使之可以输出为键值对格式字符串,在介绍原型链的时候会再次提到。我们可以看到sort方法通过这个匿名方法让我们可以根据key属性来进行排序。那就让我们来看看这个匿名方法吧。

function(a,b) {
    return a.key - b.key;
};

可以看到,这个方法接收2个参数,然后对参数的自身或某个属性进行比较,然后返回比较结果,他们的比较结果与排序对应关系如下:

如果paramA - paramB > 0,return 正数,则b排在 a的前面
如果paramA - paramB < 0,return 负数,则b排在a的后面
如果paramA - paramB = 0,return 0,则顺序不变。
上面的实现是顺序排序,那么倒序呢?对,return paramB - paramA;
reverse方法可以将数组对象反转。他和sort方法一样是修改数组对象内部元素顺序的。
最后我们看看splice方法,他是替换和删除数组对象元素的方法。根据参数的改变而拥有不同的实现结果。splice(pos,count[,insertParams]);pos参数是删除元素的第一个项的位置,count参数是删除元素的个数,当为0时则不删除(不删除还要这个方法干嘛,别着急,往下看),insertParams则是参数列表,这些参数是即将插入的元素集合。插入的位置为pos。那么就出现了以下几种情况了。
1、insertParams忽略时,该方法就是删除数组元素
2、当count参数为0时,该方法将只是将insertParams插入到pos位置
3、当count参数不为0且insertParams不忽略时,该方法就是删除pos位置开始的count个元素,并替换insertParams参数集合。
2、Math类
我们花了很大的篇幅来介绍数组类,我要再次强调一点,这个类只是为了介绍方便强加于它的一个名字,实际上他们也只是对象。而非真正的类。
Math类的使用范围相对狭窄,因为他作为一个数学计算的类,而非一个数据结构类,但是我们也看到了Math.random以及各种取整等常用方法。因此我们不妨花些时间来看看他们,但是如果你对此兴趣不大,那么看完random方法之后就可以跳到下一节去,以后用到的时候再翻手册就可以了。
Math通常是一个“静态”类,因为没有人会实例化一个Math对象,而是直接使用其“静态”方法,有些资料直接称它为Math对象,在这里我们不妨称它为“静态”类吧。
首先我必须介绍random方法,因为他常用且太有用了。在制造随机事件的时候他总是不可或缺,同样在防止缓存上他也显得很有用处。Math.random方法返回的是一个0到1之间的开区间浮点数,即(0,1),他的使用非常简单,唯一需要注意的是,当我们取整的时候对floor和ceil方法的筛选时需要谨慎,前者使得random间接转换为前闭后开区间,而后者则是前开后闭区间。假如我们现在需要一个取1-100的随机数,那么有如下的两种常用解决方案


方法一:

Math.ceil(Math.random*100);

方法二:

Math.floor(Math.random*100)+1;


ceil方法和floor方法都是用来取整的数学方法,根据单词含义我们可以理解,前者是向上取整,而后者则是向下取整。
当我们从一个连续的数组对象中随机选择一个数组元素时,我们可以借助random轻松的来帮我们挑选:

["ipad","iphone","ipodtouch","ipodnano","macbook"][Math.ceil(Math.random()*4)];

这个例子中,我直接使用的是数组字面量,一开始你可能会觉得费解或者不易理解,当你深入以后你会发现原来JavaScript是如此的方便、简洁、灵活。

前面我们介绍了ceil和floor方法的取整,那么当我们想要接近舍入时呢,我们可以使用Math.round方法,他在取整时根据数值进行靠近取整。比如Math.round(5.4)返回的是5。那么如果Math.round(5.5)呢,答案是6而不是5。关于这点需要有所了解。好吧我承认我较真了,但是知道了他有什么坏处呢。
当我们想从2个数中取得较小或者较大的数的时候怎么做呢?

if(a>b) {
    return a;
} else {
    return b;
}


我们多虑了。Math提供了Math.max和Math.min方法来帮助我们解决这个问题。
Math还有一大堆的“静态”方法和属性。在这里我就不一一列举了,当我们要进行数学计算的时候不妨去查查手册。
3、String类
字符串对象的使用频率恐怕比数组对象有过之而无不及,为什么我要放到后面来说呢,其实是因为对于字符串,我们要说的更多,而可扩展的方法和工具函数也更加丰富。我们一起先来看看String类本身吧。
创建一个字符串对象有以下几种方法:
方法一:

var str = newString("Hello World");

方法二:

var str =String("Hello World");


方法三:

var str = "HelloWorld";

和数组对象一样,我推荐大家使用最后一种方法,及字符串字面量。关于是否有new的区别,周爱民老师的博客中有过很详细的解释,同时,如果你一直读下去,在介绍自定义对象的时候也会提到new运算符。

String对象有且只有一个属性length,他返回字符串的长度,在这里我们必须要弄明白JavaScript是unicode编码,那么汉字和英文都当作一个字符长度来处理,之所以提到这个是因为曾经遇到不止一位朋友在论坛提问这个问题,呵呵,动手的必要性啊。那么如果我们非要把汉字当作2个字符长度来计算呢?这就带来了我们第一个自定义方法。

String.prototype.getLength= function(isSplit){
    if(isSplit) {
        returnthis.replace(/[^/u0000-/u00FF]/g,"tt").length;
    }
    else {
        return this.length;
    }
};

该方法通过传递一个Boolean类型的参数,如果为真则将非半角字符、数字、英文字母分割为2个长度来求长度,不用担心这个分割,他并不会修改字符串对象本身。如果为假,则直接返回length属性值。由于介绍方法不是根据方法的字符排列顺序而来,如果作为字典,我想还是w3school更合适,因为我是根据作用的不同和关联性来进行介绍。ok,介绍完了length属性,我们看看字符串查找吧。

indexOf和lastIndexOf方法。
这两个方法从是从字符串中查找一个字符或字符子串,区别在于查找方向,前者是从位置0处开始查找,并返回第一个查找到的位置,后者从位置length-1处开始查找,并返回第一个查找到的位置。如果查找不到呢,返回-1。例如

var str = "了解面向对象编程和基于对象编程是一个基础理论";
alert(str.indexOf("对象")); //output:4
alert(str.lastIndexOf("对象"));//output:11
alert(str.indexOf("过程"));//output:-1

从输出的结果我可以得到以下结论:

1、字符位置是从0开始索引
2、即使是从后往前查找,返回位置时也还是位置0开始计算
3、当在字符串中索引不到该子串时,返回-1值。
charAt和charCodeAt方法根据一个位置索引来返回字符,其中前者是返回字符本身,后者返回字符编码。我们简单的看个例子后结束他们:

var str = "了解面向对象编程和基于对象编程是一个基础理论";
alert(str.charAt(5));//output:象
alert(str.charCodeAt(5));//output:35937

接下来轮到slice,substr和substring方法,说实话很多熟悉JavaScript的程序员也经常会混淆两者的用法,并非是我夸张,而是substring和很多后台语言的substring方法区别很大的哦。先看看slice方法。

slice(start[,end])方法需要提供至少一个整数参数,作用是返回从start的位置开始到end位置的字符子串。接下来几句话请仔细看清楚了,以防造成曲解,当参数start为负数的时候他将从字符串尾部开始计算,当end没有指定时,end即为字符串的结尾。如果为负数呢,他也要从字符串尾部开始计算。所以当我们需要一个字符串的之后3个字符时只需slice(-3);由此可见,合理的使用负数让我们的程序变得简单。但是在此之前,请确保自己了解了他的作用。
据我所知的编程语言中,有很大一部分的substring方法设计为substring(beginposition,length),而在JavaScript中正好也有这么一个方法,可惜真正与之对应的是substr方法。substr(pos[,length])方法中,如果pos为负数,则与slice的负数解释相同,length省略时与slice的end省略也相同。
到了substring方法,substring(from[,to]);从定义上就可以看到,后一个参数是一个位置,而非长度,因此他更像slice,但是与之有一点重要的区别,那就是substring方法不包含to位置。即是一个半开半闭区间。另一个区别是substring不支持负向位置,如果第一个参数为负数,那么就是从位置0开始。后一个位置如果是负数,则返回空串,如果第二个参数小于第一个参数,那么同样返回空串,但是如果相等呢,还是空串,因为这是一个半开半闭区间[from,to)。
另外几个查找的方法:match、search将在后面介绍正则表达式和RegExp类的时候详细介绍。替换方法replace也将在介绍正则表达式时介绍。另外一对有用的方法是toLowerCase和toUpperCase。他们就是将字符串进行大小写转换的。
字符串操作的另一个领悟的连接字符串。字符串对象是长度不可变的,仔细回顾下之前所有的方法中没有一个是修改对象本身的方法。关于这点将于稍后一个思考题单独会展开来介绍。现在你要做的就是知道这点。字符串对象的连接方法常见的三种,第一种是使用concat方法,在介绍Array类的时候我们也见过这个方法,他们其实是一样的东西,连接字符串为一个新串,另外字符串对象重载了+号运算符,用来简化连接操作,因此"abc"+"de" =="abcde";还有一个方法是借助Array对象中的push和join方法连接字符串。

var arr = [];
for(var i = 0; i <10; i++) {
    arr.push("<imgalt=/"test/" src=/"pic" + i + ".jpg/"/>");
}
arr =arr.join("");
var arr = [];
for(var i = 0; i <10; i++) {
    arr.push("<imgalt=/"test/" src=/"pic" + i + ".jpg/"/>");
}
arr =arr.join("");

这种方法很类似与C#中的StringBuilder的append和ToString方法,而好处也很类似。不要忘了在join的方法中加上参数""哦,否则的话,多出的逗号可能就有点事与愿违了。为了方便操作,我们通常还会扩展一个方法来模拟printf(in c)和format(in c#):

functionformatString()  {
    var args = arguments;
    if(args.length > 1) {
        return args[0].replace(newRegExp("/{([0-" + (args.length-2).toString() +"])/}","g"),function(s,m){
            return args[+m+1];
        });
    }
}

首先我要说明,这个方法与原来的方法相比丑陋了许多,但是更容易理解,其次,这个方法有自身的bug存在,即当参数过多(>10)时,正则表达式将不能正确运转。因此我们换个解决方案来解决这个bug(感谢zswang的仔细,下面的解决方案也是仿照zswang的。)

functionformatString(str,params) {
         return str.replace(//{(/d+)/}/g,function(s,m){
                 return params[+m];
         });
}
alert(formatString("{0}is {1} old",['JeeChang',25]));


JavaScript中没有一个trim方法,让我们很是苦恼,没有例外,我们自己写一个trim方法吧

String.prototype.trim =function(which){
         var pattern = {
                 "left":"^//s*",
                 "right":"//s*$",
                 "both":"^//s*|//s*$"
         }
         return this.replace(newRegExp(pattern[which],'g'),"");
};

字符串的操作一直都不仅限于此,比如字符串截取往往还有这样的需求,即我们在浏览器显示的时候往往需要根据全半角来截断字符串,但是最后一个字符是全角的话则需要全部截断。看上去一定很复杂吧。没关系我们来添加这个方法。

String.prototype.splitCount= function(count){
         var str = this;
         var signs =['~','!','|','`',':','$','#','&','*','@','.','?'];
         var sign = '';
         for(var i = 0; i < signs.length; i++) {
                 if(str.indexOf(signs[i]) < 0) {
                          sign = signs[i];
                          break;
                 }
         }
         str = str.replace(/[^/u0000-/u00FF]/g,sign + sign);
         var ig = count;
         for(var i = 0; i < count; i++) {
                 if(str[i] == sign)
                          ig-=0.5;
         }
         return this.substr(0,Math.floor(ig));
};

这个方法很可惜,替换字符不够全面,如果恰巧这些字符都存在于其中,那么结果就很可悲了。所以这个方法不是那么的可靠。其实可以通过初步判断总字符长度来截取掉后面的字符串然后再查找是否有替换字符。这样概率就相对小些。

介绍完了数组对象和字符串对象之后,我们看下面一个例子:

var a = "abc";
var b = "abc";
alert(a.valueOf() ===b.valueOf);
alert(a.toString() ===b.toString());
alert(a.valueOf() ==b.valueOf());
var arr1 =['a','b','c'];
var arr2 =['a','b','c'];
alert(arr1.toString()=== arr2.toString());
alert(arr1.valueOf() ===arr2.valueOf());

请问输出结果是什么呢?

4、Date类
日期类恐怕是我见过最无奈的JavaScript内置类(对象)。因为他的浏览器不同表现,时间日期的操作处理都显得那么的不友好。幸好,JavaScript固有的机制可以让我们自己来解决这些问题。不过在此之前我们还是先大致了解下他的一些方法和属性。
创建一个日期对象可以有这些方法
方法一:

var d = newDate(ms);//ms代表从1970.1.1凌晨0点的毫秒数


方法二:

var d = newDate(year,month[,day,hour,minute,second,millisecond]);

方法三:

var d = new Date("localDateString");//这里不是那么的通用。2011/5/5格式相对通用

如果我们需要创建一个当前时间的日期对象。直接new Date()用无参数的构造函数即可。当然我们不能忽略这个new,前面提到String和Array可以省略,然而这里千万不能这样做。因为Date()的结果是浏览器实现的一个日期对象的toString返回的表示日期的字符串。故此,这里两者不能混用。

日期对象的方法大致分为获取和设置日期时间的某一(几)个部分。获取方法相对好用一些,然后设置方法则显得不那么够用。这里有几个需要拿出来先说说。
getDate/setDate。该方法操作的是天数,而非日期值。这个还是有点不大直观的。
getDay/setDay。该方法操作的是周数,序数从0开始,即周日的值是0。
getMonth/setMonth。该方法操作的是月数没有疑问,但是月数是从0开始。
getFullYear/setFullYear。我通常建议用这组方法来代替直观的getYear/setYear。
toDateString/toTimeString==输出日期的方法。但是并不是那么的好用。
接下来,让我们一步一步来打造更适合自己的一套方法。
首先,我们来格式化输出

var conf = {
    syslang : 'cn'
};
Date.prototype.toFormatString= function(format) {
    var weeks = {};
    weeks['cn'] = ['星期日', '星期一','星期二', '星期三', '星期四', '星期五', '星期六'];
    weeks['en'] = ['Sunday', 'Monday','Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
    var self = this;
    var fix = {
        'yyyy' : self.getFullYear(),
        'MM' : self.getMonth() + 1,
        'dd' : self.getDate(),
        'wk' :weeks[conf.syslang][self.getDay()],
        'hh' : self.getHours(),
        'min' : self.getMinutes(),
        'ss' : self.getSeconds()
    };
    return format.replace(/[a-zA-Z]+/g,function(m) {
        return fix[m];
    });
};

嗯,这个方法多了个全局的配置对象,但是这个不是必须的,只是在这里提示大家如果实现个性化定制,但是如果是自己的项目使用,我更建议减少代码(把en或cn去掉)来打造适合自己项目的精简代码。该方法的使用一目了然,在这里也不多解释了。

接下来是日期的操作,第一组是日期的加减。熟悉.net的朋友都知道AddXXX的一组方法,因此我们也可以打造一组这样的代码,在此我只列举一个,有需要的可以自己实现其他的。其实这套方法可以使用伪泛型的方式将Add方法组并到一个方法。但是我更愿意用一目了然的方法名来提供。

Date.prototype.add =function(tps, n) {
         var par = {
                 dd : 'Date',
                 wk : 'Day',
                 MM : 'Month',
                 yyyy : 'FullYear',
                 hh : 'Hours',
                 min : 'Minutes',
                 ss : 'Seconds'
         };
         var tmp = this['get' + par[tps]]();
         this['set' + par[tps]](tmp + n);
};

怎么样,很简单吧。不用担心溢出(13月)或者负月份(-1月)会造成什么不良后果,日期对象会自己为了通过调整年数来得到合适的结果。

接下来是日期比较。纯粹的日期比较不是问题,因为getTime获取毫秒数之后进行加减操作即可。然而如果要是比较相差的天数怎么办呢。其实也简单。那就是相差的毫秒数换算到天数即可

Date.prototype.compareTime= function(time) {
  var ticks = time.getTime() - this.getTime();
  return Math.floor(ticks/(1000*60*60*24));
}

至于比较周数同理,但是月数和年数呢?对不起,考虑到闰年和大小月等问题,这个方法比较复杂,在这里就不贴出来了,如果您有兴趣不妨尝试着自己写写看。

第一部分就到这里,下一篇将是正则表达式类和全局对象方法。

评论 66
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值