第五章:基本引用类型
引用值(或者对象)是某个特定引用类型的实例。新对象通过使用new操作符后跟一个构造函数(constructor)来创建。
5.1 Date
这里不对Date进行详细深入,仅基于书本列出一些常用方法。更多方法和用法请参考:https://www.runoob.com/jsref/jsref-obj-date.html。
1)基于其他其他日期和时间创建日期对象:
Date.parse
和 Date.UTC
Date.now
返回表示方法执行日期和时间的毫秒数。
2)继承的方法:
Date类型重写了 toLocaleString()
、toString()
、valueOf()
方法。
-
toLocaleString()
方法返回与浏览器运行的本地环境一致的日期和时间。 -
toString()
方法通常返回带时区信息的日期和时间。 -
valueOf
被重写后返回的是日期的毫秒表示。
3)日期格式化方法:
Date类型有几个专门用于格式化日期的方法,它们都会返回字符串:
-
toDateString()
显示日期中的周几、月、日、年(格式特定于实现); -
toTimeString()
显示日期中的时、分、秒和时区(格式特定于实现); -
toLocaleDateString()
显示日期中的周几、月、日、年(格式特定于实现和地区);tolocaleTimeString()显示日期中的时、分、秒(格式特定于实现); -
toUTCString()
显示完整的UTC日期(格式特定于实现)。
这些方法的输出与tolocaleString()和 tostring()一样,会因浏览器而异。因此不能用于在用户界面上一致地显示日期。
注意 还有一个方法叫
toGMTString()
,这个方法跟toUTCString()
是一样的,目的
是为了向后兼容。不过,规范建议新代码使用toUTCString()
。
4)日期/时间组件方法。
5.2 RegExp `
ECMAScript通过RegExp类型支持正则表达式。正则表达式使用类似Perl的简洁语法来创建:
let expression = /pattern/flags;
这个正则表达式的pattern(模式)可以是任何简单或复杂的正则表达式,包括字符类、限定符、分组、向前查找和反向引用。每个正则表达式可以带零个或多个 flags(标记),用于控制正则表达式的行为。下面给出了表示匹配模式的标记。
-
g:全局模式,表示查找字符串的全部内容,而不是找到第一个匹配的内容就结束。
-
i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写。
-
m:多行模式,表示查找到一行文本末尾时会继续查找。
-
y:粘附模式,表示只查找从1astIndex开始及之后的字符串。
-
u:Unicode模式,启用Unicode匹配。
-
s:dotA11模式,表示元字符,匹配任何字符(包括\n或\r)。
5.2.1 RegExp实例属性
每个RegExp实例都有下列属性,提供有关模式的各方面信息。
+ global:布尔值,表示是否设置了g标记。
+ ignoreCase:布尔值,表示是否设置了1标记。
+ unicode:布尔值,表示是否设置了u标记。
+ sticky:布尔值,表示是否设置了y标记
+ astIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从0开始。
+ multiline:布尔值,表示是否设置了m标记。
+ dotA11:布尔值,表示是否设置了。标记。
+ source:正则表达式的字面量字符串(不是传给构造函数的模式字符串),没有开头和结尾的斜杠。
+ f1ags:正则表达式的标记字符串。始终以字面量而非传入构造函数的字符串模式形式返回(没5有前后斜杠)。
通过这些属性可以全面了解正则表达式的信息,不过实际开发中用得并不多,因为模式声明中包含这些信息。
5.2.2 实例方法
1)exex()
Regexp实例的主要方法是 exec()
,主要用于配合捕获组使用。
这个方法只接收一个参数,即要应用模式的字符串。如果找到了匹配项,则返回包含第一不匹配信息的数组;如果没找到匹配项,则返回null。
返回的数组虽然是Array的实例,但包含两个额外的属性:index和input。index是字符串中匹配模式的起始位置,input是要查找的字符串。这个数组的第一个元素是匹配整个模式的字符串,其他元素是与表达式中的捕获组匹配的字符串。如果模式中没有捕获组,则数组只包含一个元素。
这里不再继续讨论
exec()
方法的使用,可以自行百度详细用法。或者这里我搜了两篇说的还不错的文章:https://blog.csdn.net/qq_35087256/article/details/79966865 | https://segmentfault.com/a/1190000018864720
2)test()
正则表达式的另一个方法是 test()
,接收一个字符串参数。如果输入的文本与模式匹配,则参数返回true,否则返回false。
这个方法适用于只想测试模式是否匹配,而不需要实际匹配内容的情况。test()
经常用在if语句中:
let text ="000-00-0000°;
let pattern/\d{3}-\d{2}-\d{4}/;
if (pattern.test(text))
console.log ("The pattern was matched.");
在这个例子中,正达式用于测试特定的数值序列。如果输入的文本与模式匹配,则显示匹配成功的消息。这个用法常用于验证用户输入,此时我们只在乎输入是否有效,不关心为什么无效。
无论正则表达式是怎么创建的,继承的方法 toLocaleString()
和 toString()
都返回正则表达式的字面量表示。
注意:正则表达式的
valueOf()
方法返回正则表达式本身。
5.2.3 RegExp构造函数属性
这里不进行讨论。
注意:RegExp构造函数的所有属性都没有任何Web标准出处,因此不要在生成环境中使用它们。
5.3 原始值包装类型
为了方便操作原始值,ECMAScript提供了3种特殊的引用类型:Boolean、Number和String。
这些类型具有本章介绍的其他引用类型一样的特点,但也具有与各自原始类型对应的特殊行为。每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,从而暴露出操作原始值的各种方法。来看下面的例子:
let sl = "some text";
let s2 = sl.substring(2);
在这里,s1是一个包含字符串的变量,它是一个原始值。第二行紧接着在s1上调用了substring()方法,并把结果保存在s2中。我们知道,原始值本身不是对象,因此逻辑上不应该有方法。而实际上这个例子又确实按照预期运行了。这是因为后台进行了很多处理,从而实现了上述操作。具体来说,当第二行访问s1时,是以读模式访问的,也就是要从内存中读取变量保存的值。
在以读模式访问字符串值的任何时候,后台都会执行以下3步:
-
创建一个string类型的实例;
-
调用实例上的特定方法;
-
销毁实例。
可以把这3步想象成执行了如下3行ECMAScript代码:
let s1 = new String("some text");
let s2 = s1.substring(2);
s1 = null;
这种行为可以让原始值拥有有对象的行为,对布尔值和数值而言,以上3步也会在后台发生,只不过使用的是Boolean和Number包装类型而已。
引用类型与原始值包装类型的主要区别在于对象的生命周期,在通过new实例化引用类型后,得到的实例会在离开作用域时被销毁,而自动创建的原始值包装对象则只存在于访同它的那行代码执行期间。
这意味着不能在运行时给原始值添加属性和方法。比如下面的倒子:
可以显式地使用Boolean、Number和string构造函数创建原始值包装对象。不过应该在确实必要时再这么做,否则容易让开发者疑惑,分不清它们到底是原始值还是引用值。在原始值包装类型的实例上调用 typeof
会返回object,所有原始值包装对象都会转换为布尔值true。
另外,Object 构透函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例。比如:
let obj = new Object ("sometext");
console.log(obj intanceof string); //-> true
//如果传给Object的是字符串,则会创建一个String的实例。如果是数值,则会创建Number的实例。布尔值则会得到Boolean的实例。
注意,使用new调用原始值包装类型的构造函数,与调用同名的转型函数并不一样。例如:
let value = "25";
let number = Number(value); // 转型函数
console.log(typeof number), //-> "number”
let obj = new Number(value); // 构造函數
console.log(typeof obj); //-> "object"
虽然不推荐显式创建原始值包装类型的实例,但它们对于操作原始值的功能是很重要的。每个原值包装类型都有相应的一套方法来方便数据操作。
5.3.1 Boolean
Boolean是对应布尔值的引用类型。要创建一个Boolean对象,就使用Boolean构造函数并传true或false,如下例所示:
let booleanobject = new Boolean(true);
Boolean 的实例会重写 valueOf()
方法,返回一个原始值true 或 false。toString()
方法被调用时也会被覆盖,返回字符串"true"或"false"。不过,Boolean对象在ECMAScript中用得很少。不仅如此,它们还容易引起误会,尤其是在布尔表达式中使用Boolean对象时。
除此之外,原始值和引用值(Boolean对象)还有几个区别。首先,typeof
操作符对原始值返回“boolean”,但对引用值返回“object”。同样,Boolean对象是 Boolean 类型的实例,在使用 instanceof
操作符时返回true,但对原始值则返回false,如下所示:
console.log(typeof falseObject); //-> object
console.log(typeof falseValue); //-> boolean
console.log(falseObject instanceof Boolean); //-> true
console.log(falseValue instanceof Boolean): //-> false
理解原始布尔值和Boolean对象之间的区别非常重要,强烈建议永远不要使用后者。
5.3.2 Number `
Number是对应数值的引用类型。要创建一个Number对象,就使用Number构造函数并传入一个数值,如下例所示:
let NumberObject = new Number(10);
1)继承的方法:
与Boolean 类型一样,Number 类型重写了 valueOf()
、totocalestring()
和toString()
方法。
valueOf()
方法返回Number对象表示的原始数值,另外两个方法返回数值字符串。
toString()
方法可选地接收一个表示基数的参数,并返回相应基数形式的数值字符串。
2)将数值格式化为字符串的方法:
toFixed()
方法返回包含指定小数点位数的数值字符串,如:
let num = 10;
console.log(num.toFixed(2)); //-> "10.00"
如果数值本身的小数位超过了参数指定的位数,则四舍五入到最接近的小数位。这个特点可以用于处理货币。但是要注意,多个浮点数值的数学计算不一定得到精确的结果。
注意:
toFixed()
方法可以表示有0-20个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。
toExponential()
,返回以科学记数法(也称为指数记数法)表示的数值字符串。与toFixed()
一样,该方法也接收一个参数,表示结果中小数的位。
toPrecision()
方法会给根据情况返回最合理的验出结果。可能是固定长度,也可能是科学记数形式。这个方法接收一个参数,表示结果中数字的总位数(不包含指数)。来看几个例子:
let num = 99;
console.log(num.toprecision(1)); //-> "1e+2"
console.log(num.toPrecision(2)); //-> "99"
console.log(num.toPrecision(3)); //-> "99.0"
在这个例子中,首先要用1位数字表示数值99,得到“1e+2”,也就是100。因为99不能只用1数字来精确表示,所以这个方法就将它舍入为100,这样就可以只用1位数字(及其科学记数法形式)来表示了。
用2位数字表示99得到“99”,用3位数字则是“99.0”。本质上,toPrecision()
方法根据数值和精度来决定调用toFixed()
还是toExponentia1()
。为了以正确的小数位精确表示数值,这3个方法都会向上或向下s舍入。
注意
toprecisionl()
方法可以表示带1-21个小数位的数值。某些浏览器可能支持更大的范围,但这是通常被支持的范围。
3)isInteger()
方法与安全整数 *
ES6新增了 Number.isInteger()
方法,用于辨别一个数值是否保存为整数。
IEEE 754数值格式有一个特殊的数值范围,在这个范围内二进制值可以表示一个整数值。这个数值范围从Number.MIN_SAFE_INTEGER
(-2^53+1) 到 Number.MAX_SAFE_INTEGER
(2^53-1)。对超出这个范围的数值,即使尝试保存为整数,IEEE754 编码格式也意味着二进制值可能会表示一个完全不同的数值。为了鉴别整数是否在这个范围内,可以使用 Number.isSafeInteger()
方法:
console.log(Number.isSafeInteger(-1 * (2 ** 53))); //-> false
console.log(Number.isSafeInteger(-1 * (2 ** 53) + 1)); //-> true
console.log(Number.isSafeInteger(2 ** 53)); //-> false
console.log(Number.isSafeInteger((2 ** 53) - 1)); //-> true
5.3.3 String `
String 是对应字符串的引用类型。要创建一个String对象,使用String构造函数并传入一个数值,如下例所示:
let stringobject = new String(hello wor1d");
String对象的方法可以在所有字符串原始值上调用。3个继承的方法 valueOf()
、toLocaleString()
和toString()
都返回对象的原始字符串值。每个String对象都有一个1ength属性,表示字符串中字符的数量。
1. JavaScript字符
1)16位码元字符
JavaScript字符串由16位码元(code unit)组成。对多数字符来说,每16位码元对应一个字符。换句话说,字符串的 1ength
属性表示字符串包含多少16位码元;
let message ="abcde";
console.log(message.length); //-> 5
charAt ()
方法返回给定索引位置的字符,由传给方法的整数参数指定。具体来说,这个方法查找指定索引位置的16位码元,并返回该码元对应的字符。
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
console.log(String.fromCharCode(0x61,0x62,0x63,0x64)); //-> abcd
//0x0061 === 97
//0x0062 === 98
//0x0063 === 99
//0x0064 === 100
console.log (String.fromCharCode(97,98,99,100); //-> abcd
JavaScript字符串使用了两种Unicode编码混合的策略:UCS-2和UTF-16。对于可以采用16位编码的字符(U+0000 ~ U+FFFF),这两种编码实际上是一样的。
2)代理对
对于U+0000-U+FFFF范围内的字符,length
、 charAt()
、charCodeAt()
和 fromCharCode()
返回的结果都跟预期是一样的。这是因为在这个范围内,每个字符都是用16位表示的,而这几个方法也都基于16位码元完成操作。只要字符编码大小与码元大小一一对应,这些方法就能如期工作。
这个对应关系在扩展到 Unicode增补字符平面时就不成立了。问题很简单,即16位只能唯一表示65536个字符。这对于大多数语言字符集是足够了,在 Unicode中称为基本多语言平面(BMP)。为了表示更多的字符,Unicode采用了一个策略,即每个字符使用另外16位去选择一个增补平面。
这种每字符使用两个16位码元的策略称为代理对。
在涉及增补平面的字符时,前面讨论的字符串方法就会出问题。比如,下面的例子中使用了一个笑脸表情符号,也就是一个使用代理对编码的字符:
//"smiling face with smiling eyes"表情符号的編码是 U+1F60A
//0x1F60A === 128522
let message="ab☻de";
console.log(message.length); //-> 6
console.log(message.charAt(1)); //-> b
console.log(message.charAt(2)); //-> <?>
console.log(message.charAt(3)); //-> <?>
console.log(message.charAt(4)); //-> d
console.log(message.charCodeAt(1)); //-> 98
console.log(message.charCodeAt(2)); //-> 55357
console.log(message.charCodeAt(3)); //-> 56842
console.log(message.charCodeAt(4)); //-> 100
console.log(String.fromCodePoint(0x1F60A)); //-> ☻
console.log(String.fromCharCode(97,98,55357,56842,100,101)); //-> ab☻de
这些方法仍然将16位码元当作一个字符,事实上索引2和索引3对应的码元应该被看成一个代理对,只对应一个字符。fromCharCode()
方法仍然返回正确的结果,因为它实际上是基于提供的二进制表示直接组合成字符串。浏览器可以正确解析代理对(由两个码元构成),并正确地将其识别为一个Unicode笑脸字符。
为正确解析既包含单码元字符又包含代理对字符的字符串,可以使用 codePointAt()
来代替 charCodeAt()
。跟使用charCodeAt()
时类似,codePointAt()
接收16位码元的索引并返回该索引位置上的码点(code point)。码点是Unicode中一个字符的完整标识。比如,“ c ”的码点是0x0063,而“ ☻ ”的码点是0x1F60A。码点可能是16位,也可能是32位,而 codePointAt()
方法可以从指定码元位置识别完整的码点。
let message = "ab☻de";
console.log(message.codePointAt(1)); //-> 98
console.log(message.codePointAt(2)); //-> 128522
console.log(measage.codePointAt(3)); //-> 56842
console.log(message.codePointAt(4)); //-> 100
注意,如果传入的码元索引并非代理对的开头,就会返回错误的码点。这种错误只有检测单个字的时候才会出现,可以通过从左到右按正确的码元数遍历字符串来规避。迭代字符串可以智能地识别理对的码点:
console.log([..."ab☻de"]); //-> ["a","b","☻","d","e"]
fromCharCode()
也有一个对应的 fromcodePoint()
。 这个方法接收任意数量的码点,返回对应字符拼接起来的字符串:
console.log(String.fromCharCode(97,98,55357,56842,100,101)); //-> ab☻de
console.log(String.fromCodePoint(97,98,128522,100,101)); //-> ab☻de
2. normalize()方法
某些 Unicode字符可以有多种编码方式。有的字符既可以通过一个BMP字符表示,也可以通过代理对表示。
该方法主要是为字符串应用四种规范化形式:NFD、NFC,NFKD和NFKC。至于这四种形式具体含义和该方法的使用,因为应用情况的罕见这里不作总结。可以自行百度了解。
3. 字符串操作方法
concat()
用于将一个或多个字符串拼接成一个新字符串。
slice()
、substr()
、subString()
用于从字符串中提取子字符串。
4. 字符串位置方法
indexOf()
和 lastIndexOf()
用于在字符串中定位子字符串。
5. 字符串包含方法 *
ES6新增了3个用于判断字符串中是否包含另一个字符串的方法:startsWith()
、 endWith()
和 includes()
。
6. trim()方法
ECMAScript在所有字符串上都提供了 trim()
方法。这个方法会创建字符串的一个副本,删除前、后所有空格符,再返回结果。原始字符串不会受影响。
trimLeft()
和 trimRight()
方法分别用于从字符串开始和结尾清理空格符。
7. repeat()方法
ECMAScript在所有字符串上都提供了 repeat()
方法。这个方法接收一个整数参数,表示要将字符串复制多少次,然后返回拼接所有副本后的结果。
8. padStart() 和 padEnd() 方法
padStart()
和 padEnd()
方法会复制字符串,如果小于指定长度,则在相应一边填充字符,直至满足长度条件。这两个方法的第一个参数是长度,第二个参数是可选的填充字符串,默认为空格(U+0020)。
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)
在for-of循环中可以通过这个迭代器按序访问每个字符:
for (const c of "abcde"){
console.log(c);
}
//-> a
//-> b
//-> c
//-> d
//-> e
有了这个迭代器之后,字符串就可以通过解构操作符来解构了。比如,可以更方便地把字符串分割为字符数组:
let message = "abcde";
console.log([...message]); //-> ["a","b","c","d","e"]
10.字符串大小写转换
下一组方法涉及大小写转换,包括4个方法:toLowerCase()
、tolocaleLowerCase()
、toUpperCase()
和 toLocaleCase()
。
11. 字符串模式匹配方法
匹配字符串:match()
。本质上与RegExp对象的 exec()
方法相同。
查找字符串:search()
。
替换字符串:replace()
。
拆分字符串:split()
。
12. localeCompare() 方法
5.4 单例内置对象
ECMA-262对内置对象的定义是“任何由ECMAScript实现提供、与宿主环境无关,并在ECMAScript程序开始执行时就存在的对象”。这就意味着,开发者不用显式地实例化内置对象,因为它们已经实例化好了。前面我们已经接触了大部分内置对象,包括Object、Array和String。本节介绍 ECMA-262定义的另外两个单例内置对象:G1obal和Math。
5.4.1 Global
G1obal对象是ECMAScript中最特别的对象,因为代码不会显式地访问它。ECMA-262规定G1obal对象为一种兜底对象,它所针对的是不属于任何对象的属性和方法。
事实上,不存在全局变量或全局函数这种东西。在全局作用域中定义的变量和函数都会变成G1obal对象的属性。
本书前面介绍的函数,包括 isNaN()
、isFinite()
、parseInt()
和 parseFloat()
,实际上都是G1obal对象的方法。除了这些,G1obal对象上还有另外一些方法。
1. URL编码方法
encodeURI()
和 encodeURIComponent()
方法用于编码统一资源标识符(URI),以便传给浏览器。有效的URI不能包含某些字符,比如空格。使用URI编码方法来编码URI可以让浏览器能够理解它们,同时又以特殊的UTF-8编码替换掉所有无效字符。
ecnodeURI()
方法用于对整个URI进行编码,比如 “www.wrox.com/illegal value.js”。而encodeURIComponent()
方法用于编码URI中单独的组件,比如前面URL中的 “i11egal value.js”。
2. eval() 方法
这个方法可能是整个ECMAScript语言中最强大的了。这个方法就是一个完整的ECMAScript解释器,它接收一个参数,即一个要执行的ECMAScript字符串:
eval("alert('hello')"");
//等价于下面的语句
alert("hello");
当解释器发现 eval()
调用时,会将参数解释为实际的ES语句,然后将其插入到该位置。通过 eval()
执行的代码属于该调用所在的上下文,被执行的代码与该上下文拥有相同的作用域链。这意味着定义在包含上下文中的变量可以在 eval()
调用内部被引用,比如下面这个例子:
let msg = "hello world";
eval("console.log(msg)"); //-> "hello world"
通过 eval()
定义的任何变量和函数都不会被提升,这是因为在解析代码的时候,它们是被包含在一个字符串中的。它们只是在 eval()
执行的时候才会被创建。
在严格模式下,在 eval()
内部创建的变量和函数无法被外部访问。
注意:解释代码字符串的能力是非常强大的,但也非常危险。在使用
eval()
的时候必须极为慎重,特别是在解释用户输入的内容时。因为这个方法会对XSS利用暴露出很大的攻击面。恶意用户可能插入会导致你网站或应用崩溃的代码。
3. Global对象属性
这里不再列出。
4. window对象
虽然ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Global对象的代理。因此,所有全局作用域中声明的变量和函数都变成了window的属性。
window对象会在第12章更加详细的介绍。
5.4.2 Math
这里不再介绍。