重学JavaScript系列——(五)基本引用类型

重学JavaScript系列——(五)基本引用类型

博主以扎实JavaScript基础为目的,以《JavaScript高级程序设计(第四版)》为核心参考资料,以一个“复习者”的角度有针对性地来创作这期专栏。文章加入了博主的很多思考和开发经验,关注初学JavaScript时容易忽略的地方,着重总结了ECMAScript新标准知识点的特性和应用场景。最终,本专栏将覆盖完整的JavaScript知识体系,以辅佐各路豪杰在开发路上的稳步前进。

专栏传送门:https://blog.csdn.net/huoyihengyuan/category_10586561.html



引用值(或者对象)是某个特定 引用类型的实例。在ECMAScript中,引用类型是把数据和功能组织到一起的结构, 经常被人错误地称作“类”。虽然从技术上讲,JavaScript是一门面向对象语言,但ECMAScript缺少传统面向对象编程语言的基本结构,包括类和接口。引用类型有时候被称为 对象定义,因为它们描述了自己的对象应有的属性和方法。

引用类型虽然有点像类,但是跟类不是一个概念。为了避免混淆,后买呢不会使用术语类。

对象被认为是某个特定引用类型的实例。新对象通过new操作符后跟一个构造函数(constructor)来创建。

5.1 Date

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

在使用Date对象时,我们记不住那么多表示时间的API,但可以记住几个常用的API。

Date.parse()和Date.UTC()都能接受一个表示日期的参数,然后返回1970.01.01之后的毫秒数。Date.now()返回方法执行时的毫秒数,这个方法方便我们测试代码运行时间。

5.1.1 继承的方法

与其他类型一样,Date类型重写了toLocaleString()、toString()、valueOf()方法。对前两个方法来说,它们返回日期和时间,现代浏览器已经在这两个方法的输出上趋于一致,但是由于旧浏览器上是不同的。这些差异意味着toLocaleString()和toString()可能只对调试有用,不能用于显示。

Date的valueOf()返回日期的毫秒表示,可以直接利用操作符(如大于号和小于号)来直接使用它返回的值。

let date1 = new Date(2019,0,1)
let date2 = new Date(2019,1,1)

console.log(date2>date1)//true
console.log(date2<date1)//false

5.1.2 其他Date的API方法

Date的API提供了很多格式化方法或取得日期值的特定部分,有兴趣或有需要的朋友可以自行查阅更详细的表格,这个就不再过多阐述,毕竟我们也不可能记住所有的日期API。

Date的API最令我头大的是,它们某些特定值有的是从0开始的,有的是从1开始计数的,容易混淆。除了手动查阅API外,社区中也出现了不错的Date相关的工具库,但在工作时要小心变成工具库的搬运工(手动滑稽)。

5.2 RegExp

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

let expression = /pattern/flag

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

  • g:全局模式,表示查找字符串的全部内容。
  • i:不区分大小写,表示在查找匹配时忽略pattern和字符串的大小写。
  • m:多行模式,表示查找到一行文本末尾时会继续查找。
  • y:粘附模式,表示只查找从lastIndex开始及之后的字符串。
  • u:Unicode模式,启用Unicode匹配。
  • s:doAll模式,表示元字符.可以匹配任何字符(包括\n和\r)。

与其他语言中的正则表达式类似,所有元字符在模式中也必须转意:

{ [ ( \ ^ $ | ) ] } ? * + .

创建正则表达式除了使用简介语法之外,也能直接使用RegExp构造函数来创建。由于构造函数需要传入字符串,某些情况下需要二次转义,这个需要特别注意不要混淆。

字面量模式对应的字符串
/\[bc\]at/"\\[bc\\]at"
\.at"\\.at"
\w\\hello\\123"\\w\\\\hellp\\\\123"

5.2.1 RegExp实例属性

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

  • global:布尔值,表示是否设置了g标记。
  • ignoreCase:布尔值,表示是否设置了i标记。
  • unicode:布尔值,表示是否设置了u标记。
  • sticky:布尔值,表示是否设置了y标记。
  • lastIndex:整数,表示在源字符串中下一次搜索的开始位置,始终从0开始。
  • multiline:布尔值,表示是否设置了m标记。
  • doAll:布尔值,表示是否设置了s标记。
  • source:正则表达式的字面量字符串(不是构造函数的字符串),没有开头和结尾的斜杠。
  • flags:正则表达式的标记字符串。

通过这些属性可以全面了解正则表达式的信息,但是实际开发中用得不多,因为模式声明中包含这些信息。

举个例子:

let pattern1 = /\[bc\]at/i
let pattern2 = new RegExp("\\[bc\\]at","i")
console.log(pattern1.source)//"\[bc\]at"
console.log(pattern2.source)//"\[bc\]at"

两个模式分别通过字面量和构造函数创建,但是source属性是相通的,返回的是规范化之后的字面量表示。

5.2.2 RegExp实例方法

RegExp的主要方法是exec(),传入要匹配的字符串,在调用后返回第一个匹配信息,并包含index属性(当前匹配位置的下标)和input属性(传入的字符串)等。

如果设置全局标记g,可以通过多次调用exec来依次匹配符合模式的子串,每次调用exec都会更新lastIndex到下一次准备匹配到位置。

如果设置粘附模式y,则覆盖g标记,含义是只从lastIndex到位置上查找,如果找不到就重置为0。

不过在JavaScript中,我们更常用字符串的match或matchAll方法来匹配字符串,分别返回数组或迭代器,方便我们拿到指定模式的值。

正则表达式的valueOf()方法返回正则表达式本身。

5.2.3 RegExp的构造函数属性

构造函数本身也有几个属性,并且都有几个简写:

全名简写说明
input$_最后搜索的字符串
lastMatch$&最后搜索的字符串
lastParen$+最后搜索的最后匹配的捕获组
leftContext$`input字符串中出现在lastMatch前面的文本
rightContext$’input字符串中出现在lastMatch后面的文本

使用简写模式时,要加上中括号,因为大多数简写模式都不是合法的ECMAScript标识符:

RegExp.$_
RegExp.["$`"]
RegExp.["$'"]
RegExp.["$&"]
RegExp.["$+"]
RegExp.["$*"]

RegExp构造函数的所有属性都没有任何的Web标准出处,因此不要在生产环境中使用它们。(总结:暂时性风险过高,不推荐使用)

5.2.4 模式局限

虽然ECMAScript现在对正则表达式支持已经有了长足进步,但还是有一些高级特性没有被支持:

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

虽然还有一些局限,但ECMAScript正则表达式的日常开发的已经可以覆盖大多数应用场景。

5.3 原始值包装类型/基本包装类型

为了方便操作原始值,ECMAScript提供了三种特殊的引用类型:String、Number和Boolean。

当对一个原始类型的值调用函数时,后台会自动创建对应的包装类型进行操作,紧接着销毁该包装类型。

5.3.1 Boolean

Boolean我们一般不必显式地调用,而是直接使用原始值。

5.3.2 Number

num.toString(10)代表转化为10进制
num.toString(2)代表转化为2进制
...

num.toFixed(2)//保留两位小数
num.toExponential(1)//科学计数法,小数保留1位

//根据数值和精度来自动确定应该调用toFixed/toExponential
num.toPrecision()

Number.isInteger()用于辨别一个数值是否是整数。(1.00也算作整数)

IEEE754有一个特殊的数值范围,从Number.MIN_SAFE_INTEGER(-253+1)到Number.MAX_SAFE_INTEGER(253-1)。可以用Number.isSafeInteger()判断是否在这个范围内。

5.3.3 String

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

可以通过String.fromCodePoint()传入数字(不只十进制表达)得到对应ASCII的字符,如果传入一个数字列表,则会得到一个对应字符拼接的字符串。

相反,如果期望得到字符串对应的ASCII的十进制表示,可以用codePointAt()。

String.fromCharCode()和charCodeAt()是另一组达到相同目的的函数,但是他们每次只能识别一个码元,某些字符(如笑脸符号😊)是由代理对字符(两个码元组成的字符),这样一来,String.fromCharCode()和charCodeAt()就不能准确处理代理对字符。而String.fromCodePoint()和codePointAt()却可以正确处理,所以应用场景更广阔一些。

(2)normalize()方法

某些Unicode字符可以有多种编码方式,有时候一些特殊字符外观相同,但通过“===”比较却不相等,这是因为它们使用的规范化形式不同,分别是NFD、NFC、NFKD、NFKC。此时就可以通过normalize()进行规范化。

规范化形式的具体细节超出了JavaScript的范围,在工作场景中也不常见,感兴趣的朋友可自行google。

(3)字符串操作方法

stringValue.concat()和字符串使用“+”拼接的效果一样,顺便说一下,前者可以传入多个参数。

ECMAScript中slice()、subStr()、substring()都能提取字串,它们的使用细节略有不同,但应用场景几乎一致,所以熟记一种即可。

(4)字符串位置方法

indexOf()和lastIndexOf()分别从初始和末尾定位子串的位置下标,并且都允许传入第二个数值参数,代表从指定下标开始查找。

利用第二个参数,可以遍历所有子串,实现类似正则表达式的功能。

(5)字符串包含方法

ECMAScript6添加3个字符串包含的方法并返回布尔值,startsWith()和endsWith()分别检查是否包含在头和尾,includes()则可检查到任意位置。

(6)trim()方法

trim()用于消除字符串的头尾空格,另外trimLeft()和trimRight()可以分别消除头和尾空格。

trimStart()和trimEnd()可以实现相同效果。

(7)repeat()方法

stringValue.repeat(5)代表字符串重复多少遍,并返回一个副本。

(8)padStart()和padEnd()方法。

这两个方法会复制字符串,如果小于指定长度,会在相应的一边填充复制的字符串,直至满足长度。默认字符串为空格。

这个应用场景尤其适合日期时间中需要把“1,2,3”转为“01,02,03”的场景。

(9)字符串迭代与解构

字符串原型上暴露了一个@@iterator方法,所以它可以被扩展运算符和for-of循环使用,除此之外,我们也可以手动的使用迭代器。

关于@@itetator在之前的文章(重学JavaScript系列——(三)语言基础/Symbol部分)中已经介绍过,这里不再过多阐述。

(10)字符串大小写转换

用toLocaleLowerCase()和toLocaleUpperCase()来替代toLowerCase()和toUpperCase(),对于少数语言(如土耳其语)会有更好的兼容性,可以放心使用。

(11)字符串模式匹配方法

字符串和正则表达式的结合可谓是如虎添翼,match()、matchAll()、search()、replace()方法是最常用的模式匹配方法,不再介绍。

(12)localeCompare()方法

按照字母表顺序比较两个字符串。这个方法的独特之处是实现了所在的地区(国家和语言)决定了这个方法如何比较字符串。在美国,英语是ECMAScript的标准语言,它会区分大小写,大写字母排在小写字母前面,但其他地区未必是这种情况。

(13)HTML方法

stringValue.blod()会生成<b>string</b>的标签,类似这种生成标签字符串的HTML方法还有很多,但现在基本上已经没有人使用了,了解即可,不必深究。

5.4 单例内置对象

5.4.1 Global

ECMA-262规定Global是一种兜底对象。事实上,不存在全局对象和全局函数这种东西,在全局作用域中的变量和函数都会变成Global的对象属性。

(1)URL编码方法

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

相应地,decodeURI()和decodeURIComponent()用于解码。

(2)eval()方法

eval()是一个完整的ECMAScript解释器,它传入要执行的JavaScript代码的字符串。通过eval()执行的代码属于该调用所在上下文,和上下文具拥有相同作用域链。这就意味着包含在上下文中的变量可以在eval()的调用内部引用。

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

在严格模式下,eval()内部创建的变量和函数无法被外部访问到。

为了减少XSS隐患,请谨慎使用eval()。

(3)Global对象属性

Global对象有很多属性,像之前提到的undefined、NaN、Infinity等特殊值,此外,所有原生引用类型构造函数,比如Object和Function等等皆是Global的属性。

(4)window对象

ECMA-262没有规定直接访问Global对象的方式,但浏览器将window对象实现为Globel对象的代理

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

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

5.4.2 Math

Math经常被用于处理数学逻辑的场景,我们应该再熟悉不过了。

Math对象本身有一些属性作为特殊值(如Math.PI等),也提供了一些常用的方法min()、max()、ceil()、floor()、round()。

Math.random()方法方便我们创建“随机逻辑”。

注意,如果为了加密而需要生成随机数(给生成器的输入需要较高的不确定性),那么建议使用window.crypto.getRandomValues()。

Math还有很多其他方法,需要的时候拿来即用即可。

小结

JavaScript中的对象成为引用值,几种内置的引用类型用于创建特定类型的对象,它和传统面向对象编程语音的类类似,但实现不同。

Date用于日期相关、RegExp用于正则相关、包装类型方便我们操作基本类型。

当代码开始执行时,全局上下文中会存在两个内置的对象Global和Math。

基本引用类型的存在,无疑使JavaScript变得更灵活,它使数据和功能结合在一起。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值