Loading...
墨滴

因为我是李金铭啊

2021/03/29  阅读:39  主题:嫩青

JavaScript高级程序设计(第四版)阅读笔记第五章

JavaScript高级程序设计(第四版)阅读笔记

第五章_基本引用类型

引用值(或者对象)是某个特定引用类型的实例。引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。引用类型虽然有点像类,但跟类并不是一个概念。 对象被认为是某个特定引用类型的实例。新对象通过使用 new 操作符后跟一个构造函数(constructor) 来创建。构造函数就是用来创建新对象的函数,比如下面这行代码:

let now = new Date(); 

这行代码创建了引用类型 Date 的一个新实例,并将它保存在变量 now 中。Date()在这里就是构 造函数,它负责创建一个只有默认属性和方法的简单对象。

本文重点

引用值(或者对象)是某个特定引用类型的实例。引用类型有时候也被称为对象定义,因为它们描述了自己的对象应有的属性和方法。在ECMAScript 中提供了几种内置的引用类型来方便开发人员使用:

  • Date类型用于提供日期和时间的信息,它自身含有多个辅助方法如:Date.parse()和 Date.UTC()等。
  • RegExp类型用于正则表达式,正则表达式可以用于很多地方,但是我总是看一遍忘一遍,希望读者可以用心的学习一下,并且多写几个例子加深一下印象。
  • Boolean、Number 和 String这三种相应原始包装类型的对象。
  • 内置对象:Global和Math。这两个对象代码在开始执行时全局上下文中就会存在。

这些都是一些方便开发人员开发的引用类型,个人觉得更像是一些便捷的对象,其中包含的方法可以更快的实现我们的需求。

5.1 Date

Date 类型参考了 Java早期版本中的 java.util.Date。为此,Date 类型将日期保存为自协调世界时(UTC,Universal Time Coordinated)时间 1970年 1月 1日午夜(零时)至今所经过的毫秒数。使用这种存储格式,Date 类型可以精确表示 1970年 1月 1日之前及之后 285 616年的日期。

在不给 Date 构造函数传参数的情况下,创建的对象将保存当前日期和时间。要基于其他日期和时 间创建日期对象,必须传入其毫秒表示(UNIX纪元 1970年 1月 1日午夜之后的毫秒数)。ECMAScript 为此提供了两个辅助方法:Date.parse()和 Date.UTC()。

Date.parse()方法接收一个表示日期的字符串参数,尝试将这个字符串转换为表示该日期的毫秒数。ECMA-262第 5版定义了 Date.parse()应该支持的日期格式,填充了第 3版遗留的空白。所有实现都必须支持下列日期格式:

  • “月/日/年”,如"5/23/2019";
  • “月名 日, 年”,如"May 23, 2019";
  • “周几 月名 日 年 时:分:秒 时区”,如"Tue May 23 2019 00:00:00 GMT-0700";
  • ISO 8601扩展格式“YYYY-MM-DDTHH:mm:ss.sssZ”,如 2019-05-23T00:00:00(只适用于 兼容 ES5的实现)。

比如,要创建一个表示“2019年 5月 23日”的日期对象,可以使用以下代码:

let someDate = new Date(Date.parse("May 23, 2019")); 

如果传给 Date.parse()的字符串并不表示日期,则该方法会返回 NaN。如果直接把表示日期的字 符串传给 Date 构造函数,那么 Date 会在后台调用 Date.parse()。换句话说,下面这行代码跟前面 那行代码是等价的:

let someDate = new Date("May 23, 2019"); 

Date.UTC()方法也返回日期的毫秒表示,但使用的是跟 Date.parse()不同的信息来生成这个值。 传给 Date.UTC()的参数是年、零起点月数(1月是 0,2月是 1,以此类推)、日(1~31)、时(0~23)、 分、秒和毫秒。这些参数中,只有前两个(年和月)是必需的。如果不提供日,那么默认为 1日。其他 参数的默认值都是 0。下面是使用 Date.UTC()的两个例子:

// GMT 时间 2000 年 1 月 1 日零点 
let y2k = new Date(Date.UTC(20000)); 
 
// GMT 时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒 
let allFives = new Date(Date.UTC(200545175555)); 

与 Date.parse()一样,Date.UTC()也会被 Date 构造函数隐式调用,但有一个区别:这种情况下创建的是本地日期,不是 GMT日期。不过 Date 构造函数跟 Date.UTC()接收的参数是一样的。因此,如果第一个参数是数值,则构造函数假设它是日期中的年,第二个参数就是月,以此类推。

// 本地时间 2000 年 1 月 1 日零点 
let y2k = new Date(20000); 
 
// 本地时间 2005 年 5 月 5 日下午 5 点 55 分 55 秒 
let allFives = new Date(200545175555); 

ECMAScript 还提供了Date.now()方法,返回表示方法执行时日期和时间的毫秒数。这个方法可 以方便地用在代码分析中:

// 起始时间 
let start = Date.now(); 
 
// 调用函数
doSomething(); 
 
// 结束时间 
let stop = Date.now(),
result = stop - start;

5.1.1 继承的方法

Date 类型重写了 toLocaleString()、toString()和 valueOf()方法。但与其他类型不同,重写后这些方法的返回值不一样。Date 类型的 toLocaleString()方法返回与浏览器运行的本地环境一致的日期和时间。这通常意味着格式中包含针对时间的 AM(上午)或 PM(下午), 但不包含时区信息(具体格式可能因浏览器而不同)。toString()方法通常返回带时区信息的日期和时间,而时间也是以 24小时制(0~23)表示的。下面给出了 toLocaleString()和 toString()返回的 2019年 2月 1日零点的示例(地区为"en-US"的 PST,即 Pacific Standard Time,太平洋标准时间):

toLocaleString() - 2/1/2019 12:00:00 AM 
 
toString() - Thu Feb 1 2019 00:00:00 GMT-0800 (Pacific Standard Time)

Date 类型的 valueOf()方法根本就不返回字符串,这个方法被重写后返回的是日期的毫秒表示。 因此,操作符(如小于号和大于号)可以直接使用它返回的值。

let date1 = new Date(201901);    // 2019 年 1 月 1 日 
let date2 = new Date(201911);    // 2019 年 2 月 1 日 
 
console.log(date1 < date2); // true c
onsole.log(date1 > date2); // false 

5.1.2 日期格式化方法

Date 类型有几个专门用于格式化日期的方法,它们都会返回字符串:

  • toDateString()显示日期中的周几、月、日、年(格式特定于实现);
  • toTimeString()显示日期中的时、分、秒和时区(格式特定于实现);
  • toLocaleDateString()显示日期中的周几、月、日、年(格式特定于实现和地区);
  • toLocaleTimeString()显示日期中的时、分、秒(格式特定于实现和地区);
  • toUTCString()显示完整的 UTC日期(格式特定于实现)。

这些方法的输出与 toLocaleString()和 toString()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。

5.1.3 日期/时间组件方法

Date 类型剩下的方法(见下表)直接涉及取得或设置日期值的特定部分。注意表中“UTC日期” , 指的是没有时区偏移(将日期转换为 GMT)时的日期。

5.2 RegExp

ECMAScript通过 RegExp 类型支持正则表达式。正则表达式使用类似 Perl的简洁语法来创建:

let expression = /pattern/flags; 

这个正则表达式的 pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、 分组、向前查找和反向引用。每个正则表达式可以带零个或多个 flags(标记),用于控制正则表达式的行为。下面给出了表示匹配模式的标记。

  • g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
  • i:不区分大小写,表示在查找匹配时忽略 pattern 和字符串的大小写。
  • m:多行模式,表示查找到一行文本末尾时会继续查找。
  • y:粘附模式,表示只查找从 lastIndex 开始及之后的字符串。
  • u:Unicode模式,启用 Unicode匹配。
  • s:dotAll 模式,表示元字符.匹配任何字符(包括\n 或\r)。
// 匹配字符串中的所有"at" 
let pattern1 = /at/g
 
// 匹配第一个"bat"或"cat",忽略大小写
let pattern2 = /[bc]at/i
 
// 匹配所有以"at"结尾的三字符组合,忽略大小写 
let pattern3 = /.at/gi

元字符在正则表达式中都有一种或多种特殊功能,所以要匹配上面这些字符本身,就必须使用反斜杠来转义。 ( [ { \ ^ $ | ) ] } ? * +

// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i
 
// 匹配第一个"[bc]at",忽略大小写
let pattern2 = /\[bc\]at/i
 
// 匹配所有以"at"结尾的三字符组合,忽略大小写 
let pattern3 = /.at/gi
 
// 匹配所有".at",忽略大小写 
let pattern4 = /\.at/gi

正则表达式也可以使用 RegExp 构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串。任何使用字面量定义的正则表达式也可以通过构造函数来创建

// 匹配第一个"bat"或"cat",忽略大小写
let pattern1 = /[bc]at/i
 
// 跟 pattern1 一样,只不过是用构造函数创建的 
let pattern2 = new RegExp("[bc]at""i"); 

5.2.1 RegExp 实例属性

每个 RegExp 实例都有下列属性,提供有关模式的各方面信息。 

  • global:布尔值,表示是否设置了 g 标记。 
  • ignoreCase:布尔值,表示是否设置了 i 标记。
  • unicode:布尔值,表示是否设置了 u 标记。
  • sticky:布尔值,表示是否设置了 y 标记。
  • lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从 0开始。
  • multiline:布尔值,表示是否设置了 m 标记。
  • dotAll:布尔值,表示是否设置了 s 标记。
  • source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
  • flags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没有前后斜杠)。
let pattern1 = /\[bc\]at/i
 
console.log(pattern1.global);      // false
console.log(pattern1.ignoreCase);  // true 
console.log(pattern1.multiline);   // false 
console.log(pattern1.lastIndex);   // 0 
console.log(pattern1.source);      // "\[bc\]at"
console.log(pattern1.flags);       // "i" 
 
let pattern2 = new RegExp("\\[bc\\]at""i"); 
 
console.log(pattern2.global);      // false
console.log(pattern2.ignoreCase);  // true
console.log(pattern2.multiline);   // false 
console.log(pattern2.lastIndex);   // 0
console.log(pattern2.source);      // "\[bc\]at"
console.log(pattern2.flags);       // "i" 

5.2.2 RegExp 实例方法

RegExp 实例的主要方法是 exec(),主要用于配合捕获组使用。这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一个匹配信息的数组;如果没找到匹配项,则返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性:index 和 input。index 是字符串中匹配模式的起始位置,input 是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串, 其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。

let text = "mom and dad and baby"
let pattern = /mom( and dad( and baby)?)?/gi
 
let matches = pattern.exec(text); 
console.log(matches.index);   // 0 
console.log(matches.input);   // "mom and dad and baby" 
console.log(matches[0]);      // "mom and dad and baby" 
console.log(matches[1]);      // " and dad and baby" 
console.log(matches[2]);      // " and baby" 

5.2.3 RegExp 构造函数属性

RegExp 构造函数本身也有几个属性。(在其他语言中,这种属性被称为静态属性。)

通过这些属性可以提取出与 exec()和 test()执行的操作相关的信息。来看下面的例子:

let text = "this has been a short summer";
let pattern = /(.)hort/g;  
 
if (pattern.test(text)) {   
  console.log(RegExp.input);        // this has been a short summer   
  console.log(RegExp.leftContext);  // this has been a   
  console.log(RegExp.rightContext); // summer  
  console.log(RegExp.lastMatch);    // short  
  console.log(RegExp.lastParen);    // s 
}

以上代码创建了一个模式,用于搜索任何后跟"hort"的字符,并把第一个字符放在了捕获组中。 不同属性包含的内容如下。

  • input 属性中包含原始的字符串。
  • leftConext 属性包含原始字符串中"short"之前的内容,rightContext 属性包含"short" 之后的内容。
  • lastMatch 属性包含匹配整个正则表达式的上一个字符串,即"short"。
  • lastParen 属性包含捕获组的上一次匹配,即"s"。

5.2.4 模式局限

虽然 ECMAScript对正则表达式的支持有了长足的进步,但仍然缺少 Perl语言中的一些高级特性。 下列特性目前还没有得到 ECMAScript 的支持(想要了解更多信息,可以参考 Regular-Expressions.info 网站):

  • \A 和\Z 锚(分别匹配字符串的开始和末尾)
  • 联合及交叉类
  • 原子组
  • x(忽略空格)匹配模式
  • 条件式匹配
  • 正则表达式注释

5.3 原始值包装类型

为了方便操作原始值,ECMAScript提供了 3种特殊的引用类型:Boolean、Number 和 String。 这些类型具有本章介绍的其他引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。

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

在这里,s1是一个包含字符串的变量,它是一个原始值。第二行紧接着在s1上调用了substring() 方法,并把结果保存在 s2 中。我们知道,原始值本身不是对象,因此逻辑上不应该有方法。而实际上这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操作。具体来说,当第二行访问 s1 时,是以读模式访问的,也就是要从内存中读取变量保存的值。在以读模式访问字符串值的任何时候,后台都会执行以下 3步
(1) 创建一个 String 类型的实例;
(2) 调用实例上的特定方法;
(3) 销毁实例。
可以把这 3步想象成执行了如下 3行 ECMAScript代码:

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

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

可以显式地使用 Boolean、Number 和 String 构造函数创建原始值包装对象。不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。在原始值包装类型的实例上调用 typeof 会返回"object",所有原始值包装对象都会转换为布尔值 true。

5.3.1 Boolean

Boolean 是对应布尔值的引用类型。要创建一个 Boolean 对象,就使用 Boolean 构造函数并传入 true 或 false,如下例所示:

let booleanObject = new Boolean(true); 

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

一个容易出错的概念:

let falseObject = new Boolean(false);
let result = falseObject && true;
console.log(result); // true 
//所有对象在布 尔表达式中都会自动转换为 true,因此 falseObject 在这个表达式里实际上表示一个 true 值。
 
let falseValue = false
result = falseValue && true;
console.log(result); // false 

原始值和引用值(Boolean 对象)还有几个区别。首先,typeof 操作符对原始值返回 "boolean",但对引用值返回"object"。同样,Boolean 对象是 Boolean 类型的实例,在使用 instaceof 操作符时返回 true,但对原始值则返回 false

console.log(typeof falseObject);             // object 
console.log(typeof falseValue);              // boolean 
console.log(falseObject instanceof Boolean); // true
console.log(falseValue instanceof Boolean);  // false 

5.3.2 Number

Number 是对应数值的引用类型。要创建一个 Number 对象,就使用 Number 构造函数并传入一个 数值,如下例所示:

let numberObject = new Number(10); 

与 Boolean 类型一样,Number 类型重写了 valueOf()、toLocaleString()和 toString()方 法。valueOf()方法返回 Number 对象表示的原始数值,另外两个方法返回数值字符串。toString() 方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串,如下所示:

let num = 10console.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 类型还提供了几个用于将数值格式化为字符串的方法。

toFixed()方法返回包含指定小数点位数的数值字符串,如:

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

toExponential()返回以科学记数法(也称为指数记数法)表示的数值字符串。与 toFixed()一样,toExponential()也接收一个参数,表示结果中小数的位数,如:

let num = 10console.log(num.toExponential(1));  // "1.0e+1" 

toPrecision()方法会根据情况返回合理的输出结果,可能是固定长度,也可能是科学记数法形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)。来看几个例子:

let num = 99;
console.log(num.toPrecision(1)); // "1e+2" 
console.log(num.toPrecision(2)); // "99" 
console.log(num.toPrecision(3)); // "99.0" 

本质上,toPrecision()方法会根据数值和精度来决定调用 toFixed()还是 toExponential()。为了以正确的小数位精确表示数值,这 3个方法都会向上或向下舍入。

在处理原始数值和引用数值时,typeof 和 instacnceof 操作符会返回不同的结果:

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 

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

console.log(Number.isInteger(1));    // true 
console.log(Number.isInteger(1.00)); // true 
console.log(Number.isInteger(1.01)); // false 

5.3.3 String

String 是对应字符串的引用类型。要创建一个 String 对象,使用 String 构造函数并传入一个 数值,如下例所示:

let stringObject = new String("hello world"); 

String 对象的方法可以在所有字符串原始值上调用。 3个继承的方法 valueOf()、toLocaleString() 和 toString()都返回对象的原始字符串值。

每个 String 对象都有一个 length 属性,表示字符串中字符的数量。str.length

1. JavaScript字符

JavaScript字符串由 16位码元(code unit)组成。对多数字符来说,每 16位码元对应一个字符。换 句话说,字符串的 length 属性表示字符串包含多少 16位码元:

let message = "abcde"
 
console.log(message.length); // 5 

charAt()方法返回给定索引位置的字符,由传给方法的整数参数指定:

let message = "abcde"
 
console.log(message.charAt(2)); // "c" 

使用 charCodeAt()方法可以查看指定码元的字符编码。这个方法返回指定索引位置的码元值,索 引以整数指定。

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 码元创建字符串中的字符。这个方法可以接受任意 多个数值,并返回将所有数值对应的字符拼接起来的字符串:

// 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(0x610x620x630x640x65));  // "abcde" 
 
// 0x0061 === 97 
// 0x0062 === 98 
// 0x0063 === 99 
// 0x0064 === 100 
// 0x0065 === 101 
 
console.log(String.fromCharCode(979899100101));          // "abcde" 

2. normalize()方法

某些 Unicode 字符可以有多种编码方式。有的字符既可以通过一个 BMP 字符表示,也可以通过一 个代理对表示。比如:

// U+00C5:上面带圆圈的大写拉丁字母 A 
console.log(String.fromCharCode(0x00C5));          // Å 
 
// U+212B:长度单位“埃” 
console.log(String.fromCharCode(0x212B));          // Å 
 
// U+004:大写拉丁字母 A 
// U+030A:上面加个圆圈 
console.log(String.fromCharCode(0x00410x030A));  // Å 

比较操作符不在乎字符看起来是什么样的,因此这 3个字符互不相等。 为解决这个问题,Unicode提供了4种规范化形式,可以将类似上面的字符规范化为一致的格式,无论底层字符的代码是什么。这 4种规范化形式是:NFD(Normalization Form D)、NFC(Normalization Form C)、 NFKD(Normalization Form KD)和 NFKC(Normalization Form KC)。可以使用 normalize()方法对字 符串应用上述规范化形式,使用时需要传入表示哪种形式的字符串:"NFD"、"NFC"、"NFKD"或"NFKC"。

let a1 = String.fromCharCode(0x00C5),   
    a2 = String.fromCharCode(0x212B),     
    a3 = String.fromCharCode(0x00410x030A); 
 
// U+00C5 是对 0+212B 进行 NFC/NFKC 规范化之后的结果 
console.log(a1 === a1.normalize("NFD"));  // false 
console.log(a1 === a1.normalize("NFC"));  // true 
console.log(a1 === a1.normalize("NFKD")); // false 
console.log(a1 === a1.normalize("NFKC")); // true 
 
// U+212B 是未规范化的 
console.log(a2 === a2.normalize("NFD"));  // false 
console.log(a2 === a2.normalize("NFC"));  // false 
console.log(a2 === a2.normalize("NFKD")); // false 
console.log(a2 === a2.normalize("NFKC")); // false 
 
// U+0041/U+030A 是对 0+212B 进行 NFD/NFKD 规范化之后的结果 
console.log(a3 === a3.normalize("NFD"));  // true 
console.log(a3 === a3.normalize("NFC"));  // false 
console.log(a3 === a3.normalize("NFKD")); // true 
console.log(a3 === a3.normalize("NFKC")); // false 

//选择同一种规范化形式可以让比较操作符返回正确的结果: 
console.log(a1.normalize("NFD") === a2.normalize("NFD"));    // true 
console.log(a2.normalize("NFKC") === a3.normalize("NFKC"));  // true
console.log(a1.normalize("NFC") === a3.normalize("NFC"));    // true 

3. 字符串操作方法

concat(),用于将一个或多个字符串拼接成一个新字符串。concat()方法可以接收任意多个参数,因此可以一次性拼接多个字符串:

let stringValue = "hello "
let result = stringValue.concat("world"); 
 
console.log(result);      // "hello world" 
console.log(stringValue); // "hello" 
//多个拼接:
let resultA = stringValue.concat("world""!"); 
console.log(result);      // "hello world!" 
console.log(stringValue); // "hello"  
//原字符串不变

虽然 concat()方法可以拼接 字符串,但更常用的方式是使用加号操作符(+)。而且多数情况下,对于拼接多个字符串来说,使用加号更方便。

3个从字符串中提取子字符串的方法:slice()、substr()和 substring()(我就总是记混这三个)。这 3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。第一个参数表示子字符串开 始的位置,第二个参数表示子字符串结束的位置。对 slice()和substring()而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。对 substr()而言,第二个参数表示返回的子字符串数量。 任何情况下,省略第二个参数都意味着提取到字符串末尾。与 concat()方法一样,slice()、substr() 和substring()也不会修改调用它们的字符串,而只会返回提取到的原始新字符串值。

let stringValue = "hello world"
console.log(stringValue.slice(3));       // "lo world" 
console.log(stringValue.substring(3));   // "lo world" 
console.log(stringValue.substr(3));      // "lo world"
console.log(stringValue.slice(37));    // "lo w" 
console.log(stringValue.substring(3,7)); // "lo w"
console.log(stringValue.substr(37));   // "lo worl" 

当某个参数是负值时,这3个方法的行为又有不同。比如,slice()方法将所有负值参数都当成字符串长度加上负参数值。 而 substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为 0。 substring()方法会将所有负参数值都转换为 0。

let stringValue = "hello world";
console.log(stringValue.slice(-3));         // "rld"
console.log(stringValue.substring(-3));     // "hello world" 
console.log(stringValue.substr(-3));        // "rld" 
console.log(stringValue.slice(3-4));      // "lo w" 
console.log(stringValue.substring(3-4));  // "hel"
console.log(stringValue.substr(3-4));     // "" (empty string) 

4. 字符串位置方法

有两个方法用于在字符串中定位子字符串:indexOf()和 lastIndexOf()。这两个方法从字符 串中搜索传入的字符串,并返回位置(如果没找到,则返回-1)。两者的区别在于,indexOf()方法 从字符串开头开始查找子字符串,而 lastIndexOf()方法从字符串末尾开始查找子字符串。

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

这两个方法都可以接收可选的第二个参数,表示开始搜索的位置。这意味着,indexOf()会从这个参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符;lastIndexOf()则会从这个参数指 定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符。

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

5. 字符串包含方法

ECMAScript 6 增加了 3 个用于判断字符串中是否包含另一个字符串的方法:startsWith()、 endsWith()和 includes()。这些方法都会从字符串中搜索传入的字符串,并返回一个表示是否包含的布尔值。它们的区别在于,startsWith()检查开始于索引 0 的匹配项,endsWith()检查开始于索 引(string.length - substring.length)的匹配项,而 includes()检查整个字符串:

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 

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

let message = "foobarbaz"
 
console.log(message.startsWith("foo"));     // true 
console.log(message.startsWith("foo"1));  // false 
 
console.log(message.includes("bar"));       // true 
console.log(message.includes("bar"4));    // false 

endsWith()方法接收可选的第二个参数,表示应该当作字符串末尾的位置。如果不提供这个参数, 那么默认就是字符串长度。如果提供这个参数,那么就好像字符串只有那么多字符一样:

let message = "foobarbaz"
 
console.log(message.endsWith("bar"));     // false 

6. trim()方法

trim()方法。这个方法会创建字符串的一个副本,删除前、 后所有空格符,再返回结果。比如

let stringValue = "  hello world  "
let trimmedStringValue = stringValue.trim();
console.log(stringValue);         // "  hello world " 
console.log(trimmedStringValue);  // "hello world" 

由于 trim()返回的是字符串的副本,因此原始字符串不受影响,即原本的前、后空格符都会保留。 另外,trimLeft()和 trimRight()方法分别用于从字符串开始和末尾清理空格符。

7. repeat()方法

ECMAScript 在所有字符串上都提供了 repeat()方法。这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。

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 

8. padStart()和 padEnd()方法

padStart()和 padEnd()方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格 (U+0020)

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......" 

可选的第二个参数并不限于一个字符。如果提供了多个字符的字符串,则会将其拼接并截断以匹配指定长度。此外,如果长度小于或等于字符串长度,则会返回原始字符串。

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" 

9. 字符串迭代与解构

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

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} 

10. 字符串大小写转换

下一组方法涉及大小写转换,包括 4 个方法:toLowerCase()、toLocaleLowerCase()、toUpperCase()和toLocaleUpperCase()。toLowerCase()和toUpperCase()方法是原来就有的方法, 与 java.lang.String 中的方法同名。toLocaleLowerCase()和 toLocaleUpperCase()方法旨在基于 特定地区实现。在很多地区,地区特定的方法与通用的方法是一样的。但在少数语言中(如土耳其语) , Unicode大小写转换需应用特殊规则,要使用地区特定的方法才能实现正确转换。

let stringValue = "hello world";
console.log(stringValue.toLocaleUpperCase());  // "HELLO WORLD" 
console.log(stringValue.toUpperCase());        // "HELLO WORLD" 
console.log(stringValue.toLocaleLowerCase());  // "hello world" 
console.log(stringValue.toLowerCase());        // "hello world" 

11. 字符串模式匹配方法

String 类型专门为在字符串中实现模式匹配设计了几个方法。第一个就是 match()方法,这个方法本质上跟 RegExp 对象的 exec()方法相同。match()方法接收一个参数,可以是一个正则表达式字符串,也可以是一个 RegExp 对象。

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 

match()方法返回的数组与 RegExp 对象的 exec()方法返回的数组是一样的:第一个元素是与整个模式匹配的字符串,其余元素则是与表达式中的捕获组匹配的字符串(如果有的话)。

另一个查找模式的字符串方法是 search()。这个方法唯一的参数与 match()方法一样:正则表达 式字符串或 RegExp 对象。这个方法返回模式第一个匹配的位置索引,如果没找到则返回-1。search() 始终从字符串开头向后匹配模式。看下面的例子:

let text = "cat, bat, sat, fat";
let pos = text.search(/at/);
console.log(pos);  // 1

为简化子字符串替换操作,ECMAScript提供了 replace()方法。这个方法接收两个参数,第一个参数可以是一个 RegExp 对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数可以是一个字符串或一个函数。如果第一个参数是字符串,那么只会替换第一个子字符串。要想替换所有子字符串,第一个参数必须为正则表达式并且带全局标记,如下面的例子所示:

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" 

第二个参数是字符串的情况下,有几个特殊的字符序列,可以用来插入正则表达式操作的值。 ECMA-262中规定了下表中的值。

使用这些特殊的序列,可以在替换文本中使用之前匹配的内容,如下面的例子所示:

let text = "cat, bat, sat, fat"
result = text.replace(/(.at)/g"word ($1)"); 
console.log(result);  // word (cat), word (bat), word (sat), word (fat) 

replace()的第二个参数可以是一个函数。在只有一个匹配项时,这个函数会收到 3个参数:与整 个模式匹配的字符串、匹配项在字符串中的开始位置,以及整个字符串。在有多个捕获组的情况下,每 个匹配捕获组的字符串也会作为参数传给这个函数,但后两个参数还是与整个模式匹配的开始位置和 原始字符串。这个函数应该返回一个字符串,表示应该把匹配项替换成什么。使用函数作为第二个参数 可以更细致地控制替换过程,如下所示:

function htmlEscape(text{  
  return text.replace(/[<>"&]/gfunction(match, pos, originalText{   
    switch(match) {     
    case "<":         
      return "&lt;";      
    case ">":      
      return "&gt;";      
    case "&":       
      return "&amp;";     
    case "\"":       
      return "&quot;";    
    }  
  });

 
console.log(htmlEscape("<p class=\"greeting\">Hello world!</p>")); 
// "&lt;p class=&quot;greeting&quot;&gt;Hello world!</p>" 

后一个与模式匹配相关的字符串方法是 split()。这个方法会根据传入的分隔符将字符串拆分成数组。作为分隔符的参数可以是字符串,也可以是 RegExp 对象。(字符串分隔符不会被这个方法当成 正则表达式。)还可以传入第二个参数,即数组大小,确保返回的数组不会超过指定大小。来看下面的 例子:

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(/[^,]+/);   // ["", ",", ",", ",", ""] 

12. localeCompare()方法

最后一个方法是 localeCompare(),这个方法比较两个字符串,返回如下 3个值中的一个。

  • 如果按照字母表顺序,字符串应该排在字符串参数前头,则返回负值。(通常是-1,具体还要看与实际值相关的实现。)
  • 如果字符串与字符串参数相等,则返回 0。
  • 如果按照字母表顺序,字符串应该排在字符串参数后头,则返回正值。(通常是 1,具体还要看与实际值相关的实现。)

下面是一个例子:

let stringValue = "yellow";
console.log(stringValue.localeCompare("brick"));  // 1
console.log(stringValue.localeCompare("yellow")); // 0 
console.log(stringValue.localeCompare("zoo"));    // -1 

13. HTML方法

早期的浏览器开发商认为使用 JavaScript动态生成 HTML标签是一个需求。因此,早期浏览器扩展了规范,增加了辅助生成 HTML标签的方法。下表总结了这些 HTML方法。不过,这些方法基本上已经没有人使用了,因为结果通常不是语义化的标记。

5.4 单例内置对象

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

5.4.1 Global

Global 对象是ECMAScript中特别的对象,因为代码不会显式地访问它。ECMA-262规定 Global 对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成 Global 对象的属性 。本书前面介绍的函数, 包括 isNaN()、isFinite()、parseInt()和 parseFloat(),实际上都是 Global 对象的方法。除了这些,Global 对象上还有另外一些方法。

1. URL编码方法

encodeURI()和 encodeURIComponent()方法用于编码统一资源标识符(URI),以便传给浏览器。 有效的 URI不能包含某些字符,比如空格。使用 URI编码方法来编码 URI可以让浏览器能够理解它们, 同时又以特殊的 UTF-8编码替换掉所有无效字符。 ecnodeURI()方法用于对整个 URI 进行编码,比如"www.wrox.com/illegal value.js"。而 encodeURIComponent()方法用于编码URI中单独的组件,比如前面URL中的"illegal value.js"。这两个方法的主要区别是,encodeURI()不会编码属于 URL组件的特殊字符,比如冒号、斜杠、问号、 井号,而 encodeURIComponent()会编码它发现的所有非标准字符。

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

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

2. eval()方法

这个方法就是一个完 整的 ECMAScript 解释器,它接收一个参数,即一个要执行的 ECMAScript(JavaScript)字符串。

eval("console.log('hi')"); 
//上面这行代码的功能与下一行等价: 
console.log("hi"); 
eval("function sayHi() { console.log('hi'); }");
sayHi(); 

当解释器发现 eval()调用时,会将参数解释为实际的 ECMAScript语句,然后将其插入到该位置。 通过 eval()执行的代码属于该调用所在上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在 eval()调用内部被引用。

在严格模式下,在 eval()内部创建的变量和函数无法被外部访问。同样,在严格模式下,赋值给 eval 也会导致错误。

3. Global 对象属性

4. window 对象

虽然 ECMA-262没有规定直接访问 Global 对象的方式,但浏览器将 window 对象实现为 Global 对象的代理。因此,所有全局作用域中声明的变量和函数都变成了 window 的属性。

var color = "red"
 
function sayColor({   console.log(window.color); } 
 
window.sayColor(); // "red" 

另一种获取 Global 对象的方式是使用如下的代码:

let global = function({   return this;  }(); 

这段代码创建一个立即调用的函数表达式,返回了 this 的值。如前所述,当一个函数在没有明确 (通过成为某个对象的方法,或者通过 call()/apply())指定 this 值的情况下执行时,this 值等于 Global 对象。因此,调用一个简单返回 this 的函数是在任何执行上下文中获取 Global 对象的通用方式。

5.4.2 Math

ECMAScript提供了 Math 对象作为保存数学公式、信息和计算的地方。Math 对象提供了一些辅助 计算的属性和方法。

1. Math 对象属性

2. min()和 max()方法

min()和 max()方法用于确定一组数值中的小值和大值。这两个方法都接收任意多个参数

let max = Math.max(3543216); 
console.log(max);  // 54 
 
let min = Math.min(3543216);
console.log(min);  // 3 

要知道数组中的大值和小值,可以像下面这样使用扩展操作符:

let values = [12345678]; 
let max = Math.max(...val); 

3. 舍入方法

用于把小数值舍入为整数的 4个方法:Math.ceil()、Math.floor()、Math.round() 和 Math.fround()

  • Math.ceil()方法始终向上舍入为接近的整数。
  • Math.floor()方法始终向下舍入为接近的整数。
  • Math.round()方法执行四舍五入。
  • Math.fround()方法返回数值接近的单精度(32位)浮点值表示。
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 

4. random()方法

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

number = Math.floor(Math.random() * total_number_of_choices + first_possible_value) 
//1-10范围随机数
let num = Math.floor(Math.random() * 10 + 1); 

5. 其他方法

5.5 小结

JavaScript中的对象称为引用值,几种内置的引用类型可用于创建特定类型的对象。

  • 引用值与传统面向对象编程语言中的类相似,但实现不同。
  • Date 类型提供关于日期和时间的信息,包括当前日期、时间及相关计算。
  • RegExp 类型是 ECMAScript支持正则表达式的接口,提供了大多数基础的和部分高级的正则表达式功能。

JavaScript 比较独特的一点是,函数实际上是 Function 类型的实例,也就是说函数也是对象。因为函数也是对象,所以函数也有方法,可以用于增强其能力。

由于原始值包装类型的存在,JavaScript 中的原始值可以被当成对象来使用。有 3 种原始值包装类 型:Boolean、Number 和 String。它们都具备如下特点。

  • 每种包装类型都映射到同名的原始类型。
  • 以读模式访问原始值时,后台会实例化一个原始值包装类型的对象,借助这个对象可以操作相应的数据。
  • 涉及原始值的语句执行完毕后,包装对象就会被销毁。

当代码开始执行时,全局上下文中会存在两个内置对象:Global 和 Math。其中,Global 对象在大多数 ECMAScript实现中无法直接访问。不过,浏览器将其实现为 window 对象。所有全局变量和函数都是 Global 对象的属性。Math 对象包含辅助完成复杂计算的属性和方法。

参考文献:

《JavaScript高级程序设计(第4版)》

因为我是李金铭啊

2021/03/29  阅读:39  主题:嫩青

作者介绍

因为我是李金铭啊