包装类的概念其实在前面介绍过,这里做一个回顾,JS 中的原始类型一共有这几种,数字,字符串,布尔,undefined,null。其中,undefined 和 null 表示的其实都是空,没有什么特别意义。但是数字,字符串以及布尔,都是有意义的值,但是由于他们是原始类型,不像对象那样有很多属性和方法可以用,但是 JS 又打算提供一些方法和属性增强一下这几个属性,所以就提供了 Number,String,Boolean 三个构造函数,分别对应这三种原始类型。
当我们将这三种原始类型当做对象用的时候,JS 会自动调用构造函数,创造出一个和它等价的对象,然后使用该对象。这里其实主要介绍 JS 为这三提供了哪些属性和方法来强化这三个原始类型。
Number
我们可以使用 new 关键字创造一个对象类型的数字,如果直接调用,返回的则是原始类型,不管是哪种调用方式,如果传入的不是数字,都会发生类型转换,所以我们可以使用这个函数来实现类似强制类型转换的效果。
js
console.log(Number(1));//原始类型的1
console.log(new Number(1));//对象类型的1
console.log(new Number("2"));//会自动发生类型转换,对象类型的2
console.log(Number("2"));//会自动发生类型转换,数字类型的2
console.log(new Number("a"));//无法转换的会返回NaNNumber.isNaN():判断一个东西是不是 NaNNumber.isFinite():判断一个东西是否不是 InfinityNumber.isInteger():判断一个数是否是整数Number.parseInt():强制转换为整数Number.parseFloat():强制转换为小数
以上为 Number 类的常见静态方法,其中前三个不需要过多介绍,后两个需要单独介绍,因为后两个的转换规则不同于前面学到的类型转换。
Number.parseInt() 的转换规则为向 0 取整,或者说是直接将小数部分截断。
实际上,Number.parseInt 要求接受的参数是一个字符串,如果我们传入一个其他东西,会强制转换为字符串,然后再进行转换。该函数的转换规则很特别,不同于以前提到的字符串转数字规则,它是从字符串最开始位置进行查找,找到第一个有效数字进行转换,如果没有找到就返回 NaN。
举几个例子:
Number.parseInt("123");: 返回 123Number.parseInt("123abc223");: 返回 123Number.parseInt(" 123abc223");: 返回 123 //可见,会忽略左右空格Number.parseInt("abc223");: 返回 NaN //从开始位置寻找,第一个位置为 a,无法转换Number.parseInt("12 35abc223");: 返回 12 //中间的空格不会忽略Number.parseFloat("123abc223");: 返回 123Number.parseFloat("12.3abc223");: 返回 12.3 //可见,和 ParseInt 差不多Number.parseFloat("12.3.3abc223");: 返回 12.3
说简单点,其实就是从左往右看,找第一个有效数字,找不到就是 NaN,很简单的规则,这个比
Number()更好用
Number.parseInt函数还有一个功能,就是进制转换,可以把其他进制的数字转换为十进制,即Number.parseInt("101",2),表示将101转换为数字,第二个参数表示这个101是二进制下,这样就可以将一个二进制转换为十进制,最后的结果就是 5。Number.parseInt("123",2)的结果为 1,因为二进制里有效数字只有 0 和 1 ,所以 2 和 3 都会被忽略,最后只读取到一个 1。
那读者接下来可能会想到几个问题,这里一个一个解答,Number.parseInt可以将任意进制的整数转换为十进制整数,那小数呢?很可惜,Number.parseFloat并不支持第二个参数,如果我们要将任意进制的小数转为十进制,我们可能不得不自己去写这个函数。
还有一个问题是,既然可以将任何进制的整数转换为十进制,那是否有一个函数支持我们将十进制转换为任意进制数字的呢?当然有,就是Number.prototype.toString方法,Number对象重写覆盖了这个toString方法,此时这个toString方法可以支持一个参数,即进制,可以将十进制转换为任意进制,并且支持小数。var a = 123.2;a.toString(2),例如上面的代码,就可以将123.2转换为 2 进制,以字符串的格式返回。
接下来就是实例方法了
有时候我们并不需要那么长的数字,比如我需要保留两位小数,保留三位小数这种,就可以使用 Number.prototype.toFixed()
js
var a = 1.55;
a.toFixed(1);//1.6
a = 1.54;
a.toFixed(1);//1.5注:toFixed 并不是四舍五入,而是四舍六入五成双,在绝大多数情况下二者相同,但是四舍六入五成双有一个特殊情况,假设保留 x 位小数,如果该数小数后面一共 x+1 位,并且 x+1 位的数据为 5,此时就是一种特殊情况,在这种情况下的舍入规则为,保证第 x 位为偶数,也就是说如果 x 位原来就是偶数,那么就直接舍去 x+1 位,如果 x 位原来就是奇数,就进一凑成偶数。
和这个类似的是 Number.prototype.toPrecision(),这个函数以指定精度返回一个字符串,例如 2.5 有两个有效数字,精度就是 2,12.5631 有 6 个有效数字,精度就是 6。var a = 12.123;a.toPrecision(3),相当于将 a 以 3 位有效数字的精度返回,得到的结果就是 12.1
Boolean
和 Number 类似,Boolean 直接调用的话也可以用于将其他类型强制转换为布尔类型,如果使用 new 关键字调用,也可以生成一个对象类型的布尔。
由于这个布尔属性只有两个值,你用来用去也只有俩,所以实际上这里面根本没有什么方法可以给我们用。
String
String 这个构造函数的用法和前面俩一样,也是两种调用方式,不在多介绍。
整个 String 类,只有一个属性可供我们使用,即 String.prototype.length,这个属性可以返回字符串的长度,如下所示:
js
var str = "this is a string";
console.log(str.length);//16字符串和数组
这个 length 属性是不是在哪见过?是不是数组也有一个 length 属性?是的,字符串就是一个字符类型的伪数组。我们可以通过 str[0] 取到字符串的第一个字符。实际上,在 C 语言中,字符串完完全全就是一个数组。所以我们完全可以用数组的方式来循环一个字符串。
js
var str = "lsgjaklsfklajsklfjsklg";
[].forEach.call(str, function (item) {
console.log(item);
});这里顺便回顾一下 call,以及 forEach。读者也可以用普通 for 循环自行实现
但字符串和数组毕竟功能完全不同,应用的领域也完全不同,所以肯定有很多不一样的地方,字符串的每一位我们都可以认为是只读的,我们可以通过 str[0] 获取到第一位,但是赋值语句是完全无效的,同样,对于 str.length,也是完全无效的。
其实这里也涉及到包装类的只是,在使用 str[0] 的时候,相当于访问 str 属性名为 0 的属性,这个其实是在把 str 当做对象调用,所以会生成一个和他等价的 String 类的对象,此时这个 String 类的对象其实就是一个类数组。同样,访问 length 属性的时候,也是临时生成了一个 String 的对象,但是当我们访问完 length 属性之后,这个对象就直接销毁了,所以我们对其 length 属性的修改是无效的。
String 类强大的地方在于它提供了非常多的方法给我们使用,接下来我们介绍方法。
首先是静态成员
我们知道,字符串是由很多个字符组成的,每一个字符实际上都是一个编码,其中一种编码是 unicode 编码,那如果我需要使用一串 unicode 编码来创建一个字符串,应该怎么搞呢?我们可以使用 String.fromCharCode() 方法,这个函数可以传入多个参数,每一个参数就是一个 unicode 编码,该函数就会返回这些编码对应的字符串。
js
console.log(String.fromCharCode(189, 43, 190, 61));//"½+¾="这个函数有时候很好用,例如我需要一个 A 到 Z 的字符串,我们就可以使用循环实现
js
var str = "";
for (var i = 0; i < 26; i++) {
str += String.fromCharCode(i + 65);
}
console.log(str);这个是通过 unicode 码来生成字符串,但是有一个问题,这个函数没有进行有效性检查,因为并不是任何一个数字都存在 unicode 编码,如果我们要进行有效性检查,可以使用 String.fromCodePoint()。对于一个不存在 unicode 编码的数字,如果使用 fromCharCode 转换,会发生截断,但如果使用 fromCodePoint 则会进行有效性检查,如果不对会直接报错。
如果我们要得到字符串中,某一个位置的 unicode 编码,应该怎么做呢?在前面我们提到了如何将 unicode 编码转换为字符串,但这里反过来了,我给你一个字符串,让你得到某一个字符的 unicode 编码。这个需求可以使用 String.prototype.charCodeAt() 来实现。
js
var str = "abc";
console.log(str.charCodeAt(1));//得到b的unicode编码,即98前面我们提到过,要获取字符串中的某一位,可以使用 str[1] 直接获取,JS 中其实提供了专门用来做这个事的方法,即 charAt 方法,str.charAt(1) 和 str[1] 的效果是相同的,这两种方法各有优缺点,例如 charAt 的可读性很强,但 str 支持负索引,即 str[-1] 可以取出字符串的最后一位,当然 charAt() 配合 length 属性也可以做到,但是没有 str[-1] 来的简洁。具体采用哪种方式,根据需求。如果追求可读性,可以采用 charAt,但我更喜欢直接通过索引来取。
实际上,一些常用操作 JS 中都提供了相应的方法来实现,比如上面的 charAt 可以取代直接通过索引来取。包括字符串拼接,我们可以直接使用加号来进行字符串拼接,但是也可以使用 String.prototype.concat() 方法来实现字符串的拼接,即 str.concat(str1,str2,str3) 就可以将 str1,str2,str3 拼接在 str 后面进行返回。这个方法是不是很像数组的 concat。其实还有很多和数组类似的方法,例如 String.prototype.slice, String.prototype.includes, String.prototype.indexOf, String.prototype.lastIndexOf,实际上都是仿造数组写的。
在字符串中,还有一个 String.prototype.substring 方法这个方法和 String.prototype.slice 几乎一模一样,只不过会调整参数顺序,例如 "abc".substring(3,1) 可以输出正确的结果,这个等价于 "abc".substring(1,3),他会自动调整,但是 "abc".slice(3,1) 则返回空串。除此之外,substring 不接受负参数,这一点不如 slice。
其实还有一个 substr 方法,这个方法已经被弃用,推荐使用前面两个方法来实现字符串的截取,具体可参考:String.prototype.substr() - JavaScript | MDN

并不是所有和数组相关的操作 String 都提供了支持,比如
Array.prototype.splice方法,该方法在数组中可以用来在指定位置插入,删除。但是我们基本不会有这种需求,所以就并没有实现这个内置功能。
字符串虽然和数组很类似,但是毕竟也有很多不同的地方,所以也提供了很多独属于字符串的方法。
比如说,一个很常见的需求是,我要判断字符串是否以指定字符串开头,我就可以用 String.prototype.startsWith() 方法,判断字符串是否以指定字符串结尾,就可以用 String.prototype.endsWith() 方法。
js
var str = "This is a string";
console.log(str.startsWith("This"));//true
console.log(str.endsWith("ring"));//true
console.log(str.endsWith("ong"));//false
//如果这两个函数传入的是空字符串,那么恒为true实际上,以上两个方法,甚至包括
String.prototype.includs()方法,其实都可以用正则表达式来替代,关于字符串中和正则表达式相关的内容,内容非常多,会在后面的地方单独详细介绍。
接下来考虑一个问题,我有时候需要一个指定长度的字符串,但是用户输入的长度不够长,我是不是就可以考虑在他前面或者后面填充一些指定字符,以达到指定的长度。举个例子,比如我需要一个长度为 10 的数字字符串,用户输入的长度不一定是 10 位的,我需要在前面给他填充字符串 0,让他变成 10 位的字符串,为了实现这个需求,给出以下代码:
js
var str = "1234";//我需要在前面填充0,达到10位
var need = 10 - str.length;//计算出需要填充多少个0
for(var i = 0;i<need;i++){//循环need次,每次在前面填一个0
str = "0"+str;
}其实这个场景非常常见,所以 JS 内置了实现这个功能的方法,即 String.prototype.padStart() 以及 String.prototype.padEnd(),分别用于实现在前面填充和在后面填充,即当字符串长度不够的时候,自动在前或者后填充指定字符,以补足长度。
js
var str = "1234";
str.padStart(10,"0");//可以实现和上面等价的效果
str.padEnd(5,"0");//"12340"
//这里给一个实际的例子
var hour = 8;
var minute = 15;
var second = 5;
//这里给出时分秒,我要按格式输出
console.log(`当前是:${hour}:${minute}:${second}`);//当前是:8:15:5,显然不够规整
console.log(`当前是:${hour.toString().padStart(2,"0")}:${minute.toString().padStart(2,"0")}:${second.toString().padStart(2,"0")}`);//当前是:08:15:05,格式更加友好。接下来再看一个需求,如果我要将字符串重复 n 次,形成一个新字符串返回,这个需求相信读者都会,JS 中提供了这个方法来实现这个效果,即 String.prototype.repeat()
js
var str = "123";
str.repeat(3);//"123123123"下面一个需求是,将字符串进行大小写转化,比如我要让字符串中的字母全部转换为大写,可以使用 String.prototype.toUpperCase(),如果要全部转换为小写,使用 String.prototype.toLowerCase()。这两个函数都没有参数。
接下来是,我们经常要让一个字符串去掉首尾空白字符,这个时候就需要使用 String.prototype.trim(),如果我们只需要去掉左侧的所有空白字符,使用 String.prototype.trimStart(),如果我们只需要去掉右侧的所有空白字符,使用 String.prototype.trimEnd()。这个函数主要用来处理用户的输入,有时候用户可能不小心或者习惯性地给账号前面或者后面加上了一些空格,就可以这样处理。读者可以去尝试一下,大多数网站在登陆的时候,哪怕在收尾输入一些空格依旧可以登录,就是这样处理的。
String.prototype.trimStart()的一个别名是String.prototype.trimLeft()String.prototype.trimEnd()的一个别名是String.prototype.trimRight()
最后介绍一个函数,在 JS 中,我们要把一个数组转换为字符串,我们是不是提到过一个 Array.prototype.join() 方法,我们回顾一下:
js
var arr = [1,2,3,4];
console.log(arr.join(","));//"1,2,3,4"接下来我们反过来,将字符串按照一个指定字符,分割成一个一个的数组,这个需求可以通过 String.prototype.split() 实现
js
var str = "1,2,3,4,5";
console.log(str.split(","));//[1,2,3,4,5]
str = "1.2.3.4.5";
console.log(str.split("."));//[1,2,3,4,5]可以看到,String.prototype.split 和 Array.prototype.join 是完全互逆的操作。
以下是一些小有难度的练习
js
//将字符串中的空格去掉,然后把每个单词的首字母转为大写,例如:"hello world" -> "HelloWorld"
/**
* 将字符串中的每个单词的首字母大写,并且移除单词间的空格。
* @param {string} str - 原始字符串
* @return {string} - 处理后的字符串
*/
var str = "hello world";
// 将字符串按空格分割成数组
var arr = str.split(" ");
// 对数组中的每个元素进行处理:首字母大写,移除其他字母的大小写,再将字母数组转换回字符串
arr = arr.map(function (item) {
item = item.split("");
item[0] = item[0].toUpperCase();
return item.join("");
});
// 将处理后的单词数组重新连接为一个字符串,移除之前的空格
str = arr.join("");
console.log(str);
//找到一个字符串中出现次数最多的字符,打印字符出现的次数。
// 初始化一个字符串变量str,包含一串随机字符
var str = "kfklasjglkajsiopthweiothoiwghjoasdjfioahjsgoihjasofihjwioegh";
var obj = {}; // 初始化一个空对象,用于存储字符及其出现次数
// 将字符串分割为字符数组,并遍历每个字符,统计每个字符出现的次数
str.split("").forEach(function (item) {
if (!obj[item]) {
obj[item] = 1; // 如果字符尚未出现过,则初始化其出现次数为1
} else {
obj[item]++; // 如果字符已经出现过,则增加其出现次数
}
});
var arr = []; // 初始化一个数组,用于存储出现次数最多的字符及其次数
for (var i in obj) {
// 如果数组arr为空,或者当前字符的出现次数大于目前记录的最大次数,则更新记录
if (!arr[0]) {
arr[0] = i; // 记录字符
arr[1] = obj[i]; // 记录次数
}
if (obj[i] > arr[1]) {
arr[0] = i; // 更新字符
arr[1] = obj[i]; // 更新次数
}
}
// 输出最多出现的字符及其出现次数
console.log(`最多出现的字符是${arr[0]},出现了${arr[1]}次`);
//封装一个函数,生成指定长度的随机字符
/**
* 生成一个随机字符串
* @param {number} n - 需要生成的随机字符串的长度
* @returns {string} 由小写字母、大写字母和数字组成的随机字符串
*/
function randomString(n) {
// 初始化一个空字符串
var str = "";
// 循环n次,每次生成一个随机字符并添加到str中
for (var i = 0; i < n; i++) {
// 生成一个0到61之间的随机数,代表62个可选字符(10数字、26小写、26大写)中的一个
var randomCharCode = Math.random() * 62;
randomCharCode = Math.floor(randomCharCode); // 将随机数转换为整数
// 根据随机整数的范围,将其转换为对应的数字、小写或大写字母的ASCII码
if (randomCharCode < 10) randomCharCode += 48; // 数字字符的ASCII码范围:48~57
else if (randomCharCode < 36) randomCharCode += 55; // 小写字母的ASCII码范围:97~122
else randomCharCode += 61; // 大写字母的ASCII码范围:65~90
// 将ASCII码转换为字符,并添加到字符串str中
str += String.fromCharCode(randomCharCode);
}
return str; // 返回生成的随机字符串
}
//字符串按照字符编码排序
/**
* 对输入的字符串进行排序,按照字符的ASCII码值降序排列
* @param {string} str - 需要排序的字符串
* @return {string} - 排序后的字符串
*/
function sortStr(str) {
// 将字符串转换为字符数组,并按照自定义的排序规则进行排序,最后将排序后的字符数组转换回字符串
return str
.split("") // 将输入字符串分割成字符数组
.sort(function (first, second) {
// 自定义排序规则,按照字符的ASCII码值升序排列
return first.charCodeAt(0) - second.charCodeAt(0);
})
.join(""); // 将排序后的字符数组转换为字符串并返回
}