红宝书笔记(二):基础引用类型

第五章 基本引用类型

重点掌握:

  1. 理解对象;
  2. 基本JS数据类型;
  3. 原始值与原始值包装类型

引用值(或者对象)是某个特定引用类型的实例。在 ECMAScript 中,引用类型是把数据和功能组织到一起的结构,经常被人错误地称作“类”。

函数也是一种引用类型。

5.1 Date

使用new操作符调用Date构造函数创建日期对象;

let now = new Date();

返回毫秒值的两个方法:

1
2
Date.parse()
Date.UTC()

5.1.1 继承的方法

Date类型重写了 toLocaleString()、 toString()和 valueOf()方法。

toLocaleString()返回与浏览器运行本地环境一致的日期和时间;

toString()带时区信息的日期和时间;

valueOf()返回日期的毫秒;

5.1.2 日期格式化方法

5.1.3 日期/时间组件方法


5.2 RegExp

5.2.1 RegExp 实例属性

5.2.2 RegExp 实例方法

1. exec()

用于配合捕获组使用,只接受一个参数,即要应用模式的字符串。若找到,返回第一个匹配项的数组,否则返回null。

注意非全局与全局的区别,前者只匹配第一个,后者每执行一次,会依次返回匹配项。

2. test()

接收一个字符串参数,匹配返回true,否则返回false。

5.2.3 RegExp 构造函数属性

全名 说明 说明
input $_ 最后搜索的字符串(非标准特性)
last Match $& 最后匹配的文本
lastParen $+ 最后匹配的捕获组(非标准特性)
leftContext $` input 字符串中出现在 lastMatch 前面的文本
rightContext $’ input 字符串中出现在 lastMatch 后面的文本

5.3 原始值包装类型

ECMAScript 提供了 3 种特殊的引用类型: Boolean、 Number 和 String。这些类型具有引用类型的特点,但也具备各自的特殊行为。每当用到原始值的方法或属性时,后台会创建一个相应原始类型的对象,暴露方法。例如:

1
2
let s1 = "some text";
let s2 = s1.substring(2);

s1是原始值,但拥有对象的方法substring。这是因为后台进行了很多处理,从而实现了上述操作。具体来说,当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下 3 步:
(1) 创建一个 String 类型的实例;
(2) 调用实例上的特定方法;
(3) 销毁实例。
可以把这 3 步想象成执行了如下 3 行 ECMAScript 代码:

1
2
3
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;

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

引用类型:通过new实例化之后,得到的实例会在离开作用域时被销毁;

原始包装类型:只存在于它访问的那行代码执行期间。即不能在运行时给原始值添加属性和方法。例如:

1
2
3
let s1 = "some text";
s1.color = "red"; // 临时创建一个String对象
console.log(s1.color); // undefined【第三行代码执行时,对象已经销毁。实际上,第三行代码在这里创建了自己的 String 对象,但这个对象没有 color 属性。】

另外, Object 构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:

1
2
let obj = new Object("some text");
console.log(obj instanceof String); // true

如果传给 Object 的是字符串,则会创建一个 String 的实例。如果是数值,则会创建 Number 的实例。布尔值则会得到 Boolean 的实例。

注意,使用 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"

在这个例子中,变量 number 中保存的是一个值为 25 的原始数值,而变量 obj 中保存的是一个Number 的实例。

虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的。每个原始值包装类型都有相应的一套方法来方便数据操作。

5.3.1 Boolean

5.3.2 Number

不建议直接实例化 Number 对象。在处理原始数值和引用数值时, typeof 和 instacnceof操作符会返回不同的结果,如下所示:

1
2
3
4
5
6
let numberObject = new Number(10);
let numberValue = 10;
console.log(typeof numberObject); // "object"
console.log(typeof numberValue); // "number"
console.log(numberObject instanceof Number); // true
console.log(numberValue instanceof Number); // false

原始数值在调用 typeof 时始终返回”number”,而 Number 对象则返回”object”。类似地, Number对象是 Number 类型的实例,而原始数值不是。

isInteger()方法与安全整数

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

Number.isSafeInteger():鉴别整数是否在Number.MIN_SAFE_INTEGER( -2^53^ + 1)到 Number.MAX_SAFE_INTEGER( 2^53^ +1)范围内。

5.3.3 String

每个 String 对象都有一个 length 属性,表示字符串中字符的数量。来看下面的例子:

1
2
let stringValue = "hello world";
console.log(stringValue.length); // "11"

1. 操作方法

1.1concat(), 拼接字符串,返回新值;

1.2 提取子字符串,返回新值;

  1. slice(startIndex, endIndex),遇负值:参数都为字符串长度+负参数值;
  2. substring(startIndex, endIndex),遇负值:负值转为0;
  3. substr(startIndex, length),遇负值:第一个参数为字符串长度+负参数值,第二个负参数值转为0;

1.3 字符串位置方法,返回位置值:

  1. indexOf();
  2. lastIndexOf();

1.4 字符串包含方法,返回true/false:

  1. startWith():检查开始于索引 0 的匹配项;
  2. endsWith():检查开始于索引(string.length - substring.length)的匹配项;
  3. Includes():检查整个字符串;

1.5 trim()方法:删除前、后所有空格符,返回新值。

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

1.6 repeat(number)方法:字符串拼接几次,返回新值;

1.7 padStart(length, str)、padEnd(length, str))方法:复制字符串;

1.8 字符串迭代与解构

字符串的原型上暴露了一个@@iterator 方法,表示可以迭代字符串的每个字符。可以像下面这样手动使用迭代器:

1
2
3
4
5
6
let message = "abc";
let stringIterator = message[Symbol.iterator]();
console.log(stringIterator.next()); // {value: "a", done: false}
console.log(stringIterator.next()); // {value: "b", done: false}
console.log(stringIterator.next()); // {value: "c", done: false}
console.log(stringIterator.next()); // {value: undefined, done: true}

故可使用for-of循环迭代;

故可使用解构操作符:[…str];

1
2
let message = "abcde";
console.log([...message]); // ["a", "b", "c", "d", "e"]

1.9 字符串大小写转换

包括 4 个方法: toLowerCase()、 toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。

1.10 字符串模式匹配方法

  1. match():接收正则表达式;与Regexp对象的exec()方法类似;
  2. search():接收正则表达式;返回第一个匹配的位置索引,否则返回-1;
  3. replace():用法丰富;
  4. split();拆分成数组;

1.11 localeCompare()方法

比较两个字符串,按照字母表顺序,排在参数前面,返回-1;相同0;排参数后面,返回1;


5.4 单例内置对象

内置对象定义:任何由 ECMAScript 实现提供、与宿主环境无关,并在 ECMAScript程序开始执行时就存在的对象。

内置对象包括之前介绍的3种Object、 Array 和 String。本节介绍 ECMA-262定义的另外两个单例内置对象: Global 和 Math。

5.4.1 Global

事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成 Global 对象的属性 。

1. URL编码方法

encodeURI()和 encodeURIComponent()方法用于编码统一资源标识符( URI),以便传给浏览器。有效的 URI 不能包含某些字符,比如空格。

ecnodeURI()方法用于对整个 URI 进行编码,比如”www.wrox.com/illegal value.js”。

encodeURIComponent()方法用于编码 URI 中单独的组件,比如前面 URL 中的”illegal value.js”。

两者主要区别:encodeURI()不会编码属于 URL 组件的特殊字符,比如冒号、斜杠、问号、井号,而 encodeURIComponent()会编码它发现的所有非标准字符。

1
2
3
4
5
let uri = "http://www.wrox.com/illegal value.js#start";
// "http://www.wrox.com/illegal%20value.js#start"
console.log(encodeURI(uri));
// "http%3A%2F%2Fwww.wrox.com%2Fillegal%20value.js%23start"
console.log(encodeURIComponent(uri));

这就是使用 encodeURI()编码整个URI,但只使用 encodeURIComponent()编码那些会追加到已有 URI 后面的字符串的原因。

注意 :一般来说,使用 encodeURIComponent()应该比使用 encodeURI()的频率更高,这是因为编码查询字符串参数比编码基准 URI 的次数更多。

decodeURI()只对使用 encodeURI()编码过的字符解码。例如, %20 会被替换为空格,但%23 不会被替换为井号( #),因为井号不是由 encodeURI()替换的。类似地, decodeURIComponent()解码所有
被 encodeURIComponent()编码的字符,基本上就是解码所有特殊值。

2. eval()方法

接收一个参数,可执行的js字符串。

通过 eval()定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()执行的时候才会被创建。

注意: 解释代码字符串的能力是非常强大的,但也非常危险。在使用 eval()的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对 XSS 利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。

3. Global对象属性

4. window对象

浏览器将 window 对象实现为 Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。

但window对象远不止这些功能。

5.4.2 Math

Math.ceil()方法始终向上舍入为最接近的整数;

Math.floor()方法始终向下舍入为最接近的整数。

Math.round()方法执行四舍五入。

Math.fround()方法返回数值最接近的单精度( 32 位)浮点值表示。

1
2
3
4
5
6
7
8
9
10
11
12
console.log(Math.ceil(25.9)); // 26
console.log(Math.ceil(25.5)); // 26
console.log(Math.ceil(25.1)); // 26
console.log(Math.round(25.9)); // 26
console.log(Math.round(25.5)); // 26
console.log(Math.round(25.1)); // 25
console.log(Math.fround(0.4)); // 0.4000000059604645
console.log(Math.fround(0.5)); // 0.5
console.log(Math.fround(25.9)); // 25.899999618530273
console.log(Math.floor(25.9)); // 25
console.log(Math.floor(25.5)); // 25
console.log(Math.floor(25.1)); // 25

重点方法Math.random();

Math.random()方法返回一个 0~1 范围内的随机数,其中包含 0 但不包含 1。

从一组整数中随机选择一个数的公式:【向下取整】

1
number = Math.floor(Math.random() * total_number_of_choices + first_possible_value)

因此,如果想从 1~10 范围内随机选择一个数,代码就是这样的:
let num = Math.floor(Math.random() * 10 + 1); 最小值为1

如果想选择一个 2~10 范围内的值,则代码就要写成这样:
let num = Math.floor(Math.random() * 9 + 2); 最小值为2

调用 selectFrom(2,10)就可以从 2~10(包含)范围内选择一个值了。

1
2
3
4
5
6
function selectFrom(lowerValue, upperValue) {
let choices = upperValue - lowerValue + 1;
return Math.floor(Math.random() * choices + lowerValue);
}
let num = selectFrom(2,10);
console.log(num); // 2~10 范围内的值,其中包含 2 和 10
1
2
3
4
// 使用这个函数,从一个数组中随机选择一个元素就很容易,比如:
let colors = ["red", "green", "blue", "yellow", "black", "purple", "brown"];
let color = colors[selectFrom(0, colors.length-1)];
// 在这个例子中,传给 selecFrom()的第二个参数是数组长度减 1,即数组最大的索引值。

5.5 小结