数据类型 | 原始值包装类型

红宝书第四版P113:为了方便操作原始值,ECMAScript 提供了3 种特殊的引用类型:Boolean、Number 和String

装箱和拆箱

  • 装箱:将基本数据类型转换为对应的引用数据类型(分为隐式装箱和显式装箱)
    • 隐式装箱:当读取一个基本类型值时,后台会创建一个该基本类型所对应的基本包装类型对象
    • 显式装箱:通过 new 包装类型的方式进行转换
  • 拆箱:将引用数据类型转换为基本数据类型

看下面这个例子:

1
2
var s1 = 'someText';    // 后台偷偷操作了,隐式!
var s2 = s1.substring(2);

分析:

s1是一个保存字符串的变量,是一个原始值,在第二行调用了调用了substring()方法,并把结果保存在s2 中。原始值本身不是一个对象,照理说不应该有方法,但是事实确实调用方法成功了。实际上,当第二行访问s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下3 步:

  • 创建一个String 类型的实例;
  • 调用实例上的特定方法
  • 销毁实例

以上3步可抽象为下列代码:

1
2
3
var s1 = new String('someText');    // new 了, 显式!
var s2 = s1.substring(2);
s1 = null; // 这一步不可忽略,实例必须销毁

从上面的例子我们不难看出:

引用类型与原始值包装类型的主要区别在于对象的生命周期

具体来说:在通过new 实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码执行期间,这意味着不能在运行时给原始值添加属性和方法

1
2
3
let s1 = "some text";
s1.color = "red";
console.log(s1.color); // undefined

创建原始值包装类型【装箱】的方式有:

  • 显式地使用Boolean、Number 和String 构造函数创建原始值包装对象
  • Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例
1
2
let obj = new Object("some text");
console.log(obj instanceof String); // true

需要注意的是:使用new 调用原始值包装类型的构造函数,与调用同名的转型函数并不一样(注意区分转型函数和构造函数

1
2
3
4
5
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number); // "number" 转型函数的类型检测为原始值类型
let obj = new Number(value); // 构造函数
console.log(typeof obj); // "object" 原始值包装类型检测为object

Boolean

Boolean 是对应布尔值的引用类型

创建Boolean对象

1
let booleanObject = new Boolean(true);

Boolean实例的变化

Boolean实例重写valueOf()方法,返回一个原始值true或者falsetoString()方法被调用时也会被覆盖,返回字符串''true''或者''false''

看下面这个例子:

1
2
3
4
5
6
7
8
9
let falseObject = new Boolean(false);     // 包装了一个值为false的Boolean对象
let res = falseObject && true;
console.log(res); // true

/*
对于res的打印结果,可能会有点懵
回顾下上面的内容,我们要知道typeof new Boolean(false) === 'object',在布尔表达式中,所有的对象都会转化为true
这里注意区分,原始布尔表达式中 false && true 等于 false,但本例子中 实际是 true && true 自然为 true
*/

Boolean对象与原始布尔值的区别

  • typeof返回不一样
1
2
3
4
5
let falseObject = new Boolean(false); 
let falseValue = false;

typeof falseObject; // object
typeof falseValue; // boolean
  • instanceof检测不一样
1
2
3
4
5
6
7
8
9
10
11
12
let falseObject = new Boolean(false); 
let falseValue = false;

typeof falseObject; // object
typeof falseValue; // boolean

falseObject instanceof Boolean // true
falseValue instanceof Boolean // false

/*
因为Boolean对象是Boolean类型的实例
*/

Number

Number 是对应数值的引用类型

创建Number对象

1
let numberObject = new Number(10);

Number实例的变化

Number实例重写valueOf()方法,toString()方法和toLocaleString()方法

valueOf()方法返回Number 对象表示的原始数值,另外两个方法返回数值字符串

toString()方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串

1
2
3
4
5
6
let num = 10;
console.log(num.toString()); // "10"
console.log(num.toString(2)); // "1010"
console.log(num.toString(8)); // "12"
console.log(num.toString(10)); // "10"
console.log(num.toString(16)); // "a"

Number类型新方法

数值格式化为字符串

toFix()

toFixed()方法返回包含指定小数点位数的数值字符串,接受一个参数表示小数位数

1
2
let num = 10;
console.log(num.toFixed(2)); // "10.00"
toExponential()

toExponential(),返回以科学记数法(也称为指数记数法)表示的数值字符串,接受一个参数表示小数位数

toPrecision()

toPrecision()方法会根据情况返回最合理的输出结果,可能是固定长度,也可能是科学记数法形式

这个方法接收一个参数,表示结果中数字的总位数(不包含指数)

isInteger()

ES6 新增了Number.isInteger()方法,用于辨别一个数值是否保存为整数

.isSafeInteger()

IEEE 754 数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从Number.MIN_SAFE_INTEGER(-2^53 + 1)到Number.MAX_SAFE_INTEGER(2^53 - 1)。对超出这个范围的数值,即使尝试保存为整数,IEEE 754 编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用Number.isSafeInteger()方法

Number对象与原始数值的区别

  • typeof返回不一样
1
2
3
4
5
let numberObject = new Number(10); 
let numberValue = 10;

typeof numberObject; // object
typeof numberValue; // number
  • instanceof检测不一样
1
2
3
4
5
6
7
8
9
10
11
12
let numberObject = new Number(10); 
let numberValue = 10;

typeof numberObject; // object
typeof numberValue; // number

numberObject instanceof Number // true
numberValue instanceof Number // false

/*
因为Number对象是Number类型的实例
*/

String

String 是对应字符串的引用类型

创建String对象

1
let stringObject = new String('hello world');

String实例

  • String 对象的方法可以在所有字符串原始值上调用
  • 3 个继承的方法valueOf()toLocaleString()toString()都返回对象的原始字符串值
  • 每个String对象都有一个length属性,表示字符串中字符的数

String类型方法

JavaScript字符

  • length:表示字符串中字符的数量
  • charAt():返回给定索引位置的字符,由传给方法的整数参数指定
1
2
let message = "abcde";
console.log(message.charAt(2)); // "c"
  • charCodeAt(): 可以查看指定码元的字符编码,这个方法返回指定索引位置的码元值,索引以整数指定
1
2
3
4
5
let message = "abcde";
// Unicode "Latin small letter C"的编码是U+0063
console.log(message.charCodeAt(2)); // 99
// 十进制99 等于十六进制63
console.log(99 === 0x63); // true
  • fromCharCode(): 用于根据给定的UTF-16 码元创建字符串中的字符。这个方法可以接受任意多个数值,并返回将所有数值对应的字符拼接起来的字符串
1
2
3
4
5
6
7
8
9
10
11
12
// Unicode "Latin small letter A"的编码是U+0061
// Unicode "Latin small letter B"的编码是U+0062
// Unicode "Latin small letter C"的编码是U+0063
// Unicode "Latin small letter D"的编码是U+0064
// Unicode "Latin small letter E"的编码是U+0065
console.log(String.fromCharCode(0x61, 0x62, 0x63, 0x64, 0x65)); // "abcde"
// 0x0061 === 97
// 0x0062 === 98
// 0x0063 === 99
// 0x0064 === 100
// 0x0065 === 101
console.log(String.fromCharCode(97, 98, 99, 100, 101)); // "abcde"
  • codePointAt(): codePointAt()接收16 位码元的索引并返回该索引位置上的码点(code point)。码点是Unicode 中一个字符的完整标识
1
2
3
4
5
let message = "ab☺de";
console.log(message.codePointAt(1)); // 98
console.log(message.codePointAt(2)); // 128522
console.log(message.codePointAt(3)); // 56842
console.log(message.codePointAt(4)); // 100
  • fromCodePoint(): 方法接收任意数量的码点,返回对应字符拼接起来的字符串
1
2
console.log(String.fromCharCode(97, 98, 55357, 56842, 100, 101)); // ab☺de
console.log(String.fromCodePoint(97, 98, 128522, 100, 101)); // ab☺de

字符串规范化方法

  • normalize(): 通过比较字符串与其调用normalize()的返回值,就可以知道该字符串是否已经规范化

字符串拼接方法

  • concat(): 用于将一个或多个字符串拼接成一个新字符串,不改变原字符串
1
2
3
4
let stringValue = "hello ";
let result = stringValue.concat("world", "!");
console.log(result); // "hello world!"
console.log(stringValue); // "hello"
  • 加号操作符(+): 当加号操作符有一方为字符串时,则执行字符串拼接

字符串提取方法

  • slice()
  • substr()
  • substring()

字符串slice substr substring的区别

字符串位置方法

  • indexOf()
  • lastIndexOf()

这两个方法从字符串中搜索传入的字符串,并返回位置,如果没找到,则返回-1

两者的区别在于,indexOf()方法从字符串开头开始查找子字符串,而lastIndexOf()方法从字符串末尾开始查找子字符串

1
2
3
let stringValue = "hello world";
console.log(stringValue.indexOf("o")); // 4
console.log(stringValue.lastIndexOf("o")); // 7

字符串包含方法

  • startsWith()
  • endsWith()
  • includes()

这些方法都会从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值

区别在于,startsWith()检查开始于索引0 的匹配项,endsWith()检查开始于索引(string.length - substring.length)的匹配项,而includes()检查整个字符串

startsWith()和includes()方法接收可选的第二个参数,表示开始搜索的位置,如果传入第二个参数,则意味着这两个方法会从指定位置向着字符串末尾搜索,忽略该位置之前的所有字符

1
2
3
4
5
6
7
let message = "foobarbaz";
console.log(message.startsWith("foo")); // true
console.log(message.startsWith("bar")); // false
console.log(message.endsWith("baz")); // true
console.log(message.endsWith("bar")); // false
console.log(message.includes("bar")); // true
console.log(message.includes("qux")); // false

字符串删除空格

  • trim(): 这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果,不影响原字符串
1
2
3
4
let stringValue = " hello world ";
let trimmedStringValue = stringValue.trim();
console.log(stringValue); // " hello world "
console.log(trimmedStringValue); // "hello world"
  • trimLeft()
  • trimRight()

trimLeft()和trimRight()方法分别用于从字符串开始和末尾清理空格符

字符串重复方法

  • repeat(): 这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果
1
2
3
let stringValue = "na ";
console.log(stringValue.repeat(16) + "batman");
// na na na na na na na na na na na na na na na na batman

字符串填充方法

  • padStart()
  • padEnd()

这两个方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件

这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格

1
2
3
4
5
let stringValue = "foo";
console.log(stringValue.padStart(6)); // " foo"
console.log(stringValue.padStart(9, ".")); // "......foo"
console.log(stringValue.padEnd(6)); // "foo "
console.log(stringValue.padEnd(9, ".")); // "foo......"

可选的第二个参数并不限于一个字符,如果提供了多个字符的字符串,则会将其拼接并截断以匹配指定长度

此外,如果长度小于或等于字符串长度,则会返回原始字符串

1
2
3
4
5
let stringValue = "foo";
console.log(stringValue.padStart(8, "bar")); // "barbafoo"
console.log(stringValue.padStart(2)); // "foo"
console.log(stringValue.padEnd(8, "bar")); // "foobarba"
console.log(stringValue.padEnd(2)); // "foo"

字符串迭代与解构

  • @@iterator: 表示可以迭代字符串的每个字符
  • for-of: 在for-of 循环中可以通过这个迭代器按序访问每个字符
  • 解构操作符: 字符串可以通过解构操作符来解构

字符串大小转换

  • toUpperCase()
  • toLocaleLowerCase()
  • toLowerCase()
  • toLocaleUpperCase()

字符串模式匹配方法

  • match(): 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象
1
2
3
4
5
6
7
let text = "cat, bat, sat, fat";
let pattern = /.at/;
// 等价于pattern.exec(text)
let matches = text.match(pattern);
console.log(matches.index); // 0
console.log(matches[0]); // "cat"
console.log(pattern.lastIndex); // 0
  • search(): 接收一个参数,可以是一个正则表达式字符串,也可以是一个RegExp 对象,返回模式第一个匹配的位置索引,如果没找到则返回1
1
2
3
let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos); // 1
  • replace(): 方法接收两个参数,第一个参数可以是一个RegExp 对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记
1
2
3
4
5
let text = "cat, bat, sat, fat";
let result = text.replace("at", "ond");
console.log(result); // "cond, bat, sat, fat"
result = text.replace(/at/g, "ond");
console.log(result); // "cond, bond, sond, fond"
  • htmlEscape(): 将一段HTML 中的4 个字符替换成对应的实体:小于号、大于号、和号,还有双引号(都必须经过转义)
  • split(): 会根据传入的分隔符将字符串拆分成数组
1
2
3
4
let colorText = "red,blue,green,yellow";
let colors1 = colorText.split(","); // ["red", "blue", "green", "yellow"]
let colors2 = colorText.split(",", 2); // ["red", "blue"]
let colors3 = colorText.split(/[^,]+/); // ["", ",", ",", ",", ""]

字符串比较方法

  • localeCompare(): 比较两个字符串,返回如下3 个值中的一个
    • 如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值,一般为-1
    • 如果字符串与字符串参数相等,则返回0
    • 如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值,一般为1
1
2
3
4
let stringValue = "yellow";
console.log(stringValue.localeCompare("brick")); // 1
console.log(stringValue.localeCompare("yellow")); // 0
console.log(stringValue.localeCompare("zoo")); // -1