从实战上彻底搞懂JS对象第二篇

举报
龙哥手记 发表于 2022/03/12 09:42:35 2022/03/12
【摘要】 系列

一 Boolean对象

1、概述

Boolean对象是 JavaScript 的三个包装对象之一。作为构造函数,它主要用于生成布尔值的包装对象实例。

			var b = new Boolean(true)
			typeof b // object
			b.valueOf() // true
			

上面代码的变量b是一个Boolean对象的实例,它的类型是对象,值为布尔值true

注意,false对应的包装对象实例,布尔运算结果也是true

			if (new Boolean(false)) { // 返回的是一个对象,所有对象对应的布尔值都是true
					  console.log('true');
} // true

				if (new Boolean(false).valueOf()) { // 它的值为false
					  console.log('true');
} // 无输出

上面代码的第一个例子之所以得到true,是因为false对应的包装对象实例是一个对象,进行逻辑运算时,被自动转化成布尔值true(因为所有对象对应的布尔值都是true)。而实例的valueOf方法,则返回实例对应的原始值,本例为false

2、Boolean 函数的类型转换作用

Boolean对象除了可以作为构造函数,还可以单独使用,将任意值转为布尔值。这时Boolean就是一个单纯的工具方法

				
					Boolean(undefined) // false
					Boolean(null) // false
					Boolean(0) // false
					Boolean('') // false
					Boolean(NaN) // false

					Boolean(1) // true
					Boolean('false') // true
					Boolean([]) // true
					Boolean({}) // true
					Boolean(function () {}) // true
					Boolean(/foo/) // true
					

上面代码中几种得到true的情况,都值得认真记住

直接使用上面方法括号内的参数进行if逻辑运行相当于调用了Boolean方法并传入参数

顺便提一下,使用双重的否运算符(!)也可以将任意值转为对应的布尔值。

						!!undefined // false
						!!null // false
						!!0 // false
						!!'' // false
						!!NaN // false

						!!1 // true
						!!'false' // true
						!![] // true
						!!{} // true
						!!function(){} // true
						!!/foo/ // true
						

最后,对于一些特殊值,Boolean对象前面加不加new,会得到完全相反的结果,必须小心。

						if (Boolean(false)) {
									  console.log('true');
} // 无输出

						if (new Boolean(false)) { // 加new 转为对象,对象为true
									  console.log('true');
} // true

						if (Boolean(null)) {
									  console.log('true');
} // 无输出

						if (new Boolean(null)) { // 加new 转为对象,对象为true
									  console.log('true');
} // true

一、RegExp 对象

RegExp对象提供正则表达式的功能。

1、概述

正则表达式(regular expression)是一种表达文本模式(即字符串结构)的方法,有点像字符串的模板,常常用来按照“给定模式”匹配文本。比如,正则表达式给出一个 Email 地址的模式,然后用它来确定一个字符串是否为 Email 地址。JavaScript 的正则表达式体系是参照 Perl 5 建立的。

新建正则表达式有两种方法:

一种是使用字面量,以斜杠表示开始和结束。

		var regex = /xyz/; // 效率较高,且直观,推荐。

另一种是使用RegExp构造函数。

		var regex = new RegExp('xyz');
		

上面两种写法是等价的,都新建了一个内容为xyz的正则表达式对象。它们的主要区别是,第一种方法在引擎编译代码时,就会新建正则表达式,第二种方法在运行时新建正则表达式,所以前者的效率较高。而且,前者比较便利和直观,所以实际应用中,基本上都采用字面量定义正则表达式。

RegExp构造函数还可以接受第二个参数,表示修饰符(详细解释见下文)。

			var regex = new RegExp('xyz', 'i'); // i为修饰符
			// 等价于
			var regex = /xyz/i;
			

上面代码中,正则表达式/xyz/有一个修饰符i

2、实例属性

正则对象的实例属性分成两类。

一类是修饰符相关,用于了解设置了什么修饰符。

  • RegExp.prototype.ignoreCase:返回一个布尔值,表示是否设置了i修饰符。
  • RegExp.prototype.global:返回一个布尔值,表示是否设置了g修饰符。
  • RegExp.prototype.multiline:返回一个布尔值,表示是否设置了m修饰符。
  • RegExp.prototype.flags:返回一个字符串,包含了已经设置的所有修饰符,按字母排序。

上面四个属性都是只读的。

		var r = /abc/igm;

			r.ignoreCase // true 是否设置i修饰符
			r.global // true 是否设置g修饰符
			r.multiline // true 是否设置m修饰符
			r.flags // 'gim' 字符串,包含设置的所有修饰符,按字母排序
			

另一类是与修饰符无关的属性,主要是下面两个。

  • RegExp.prototype.lastIndex:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义,详细介绍请看后文。
  • RegExp.prototype.source:返回正则表达式的字符串形式(不包括反斜杠),该属性只读。
			var r = /abc/igm;

				r.lastIndex // 0
				r.source // "abc" 正则表达式的字符串形式
				

3、实例方法

3.1 RegExp.prototype.test() 当前模式是否匹配参数字符串,返回布尔值

正则实例对象的test方法返回一个布尔值,表示当前模式是否能匹配参数字符串

			/cat/.test('cats and dogs') // true 验证字符串是否包含cat

上面代码验证参数字符串之中是否包含cat,结果返回true

如果正则表达式带有g修饰符,则每一次test方法都从上一次结束的位置开始向后匹配。

			var r = /x/g; // 带有g全局匹配修饰符
			var s = '_x_x';

			r.lastIndex // 0  下一次开始搜索的位置
			r.test(s) // true 内部原理:从0位置开始匹配,匹配到'x'在1位置,返回true,并把lastIndex属性设置为2,

			r.lastIndex // 2 下一次开始搜索的位置
			r.test(s) // true  内部原理:从2位置开始匹配,匹配到'x'在3位置,返回true,并把lastIndex属性设置为4

			r.lastIndex // 4 下一次开始搜索的位置
			r.test(s) // false 内部原理:从4位置开始匹配,匹配不到’x‘,返回false
			

上面代码的正则表达式使用了g修饰符,表示是全局搜索,会有多个结果。接着,三次使用test方法,每一次开始搜索的位置都是上一次匹配的后一个位置。

带有g修饰符时,可以通过正则对象的lastIndex属性指定开始搜索的位置。

			var r = /x/g;
			var s = '_x_x';

			r.lastIndex = 4; // lastIndex属性可读写,改写后,test方法将从该位置开始匹配
			r.test(s) // false

			r.lastIndex // 0  重置为0
			r.test(s) // true
			

上面代码指定从字符串的第五个位置开始搜索,这个位置为空,所以返回false。同时,lastIndex属性重置为0,所以第二次执行r.test(s)会返回true

注意带有g修饰符时,正则表达式内部会记住上一次的lastIndex属性,这时不应该更换所要匹配的字符串,否则会有一些难以察觉的错误。

		var r = /bb/g;
			r.test('bb') // true  由于带有g修饰符,匹配后会把lastIndex属性修改为了2
			r.test('-bb-') // false 从2位置开始匹配'bb',并没有匹配到
			

上面代码中,由于正则表达式r是从上一次的lastIndex位置开始匹配,导致第二次执行test方法时出现预期以外的结果。

lastIndex属性只对同一个正则表达式有效,所以下面这样写是错误的。

		var count = 0;
			while (/a/g.test('babaa')) count++;
			

上面代码会导致无限循环,因为while循环的每次匹配条件都是一个新的正则表达式,导致lastIndex属性总是等于0。

如果正则模式是一个空字符串,则匹配所有字符串

			new RegExp('').test('abc')
			// true
			

3.2 RegExp.prototype.exec() 返回匹配结果,如匹配则返回一个数组,成员是匹配到的子字符串,否则返回null

正则实例对象的exec方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null

			var s = '_x_x';
			var r1 = /x/;
			var r2 = /y/;

				r1.exec(s) // ["x"] 返回一个成员
				r2.exec(s) // null
				

上面代码中,正则对象r1匹配成功,返回一个数组,成员是匹配结果;正则对象r2匹配失败,返回null

如果正则表示式包含圆括号(即含有“组匹配”),则返回的数组会包括多个成员。第一个成员是整个匹配成功的结果,后面的成员就是圆括号对应的匹配成功的组。也就是说,第二个成员对应第一个括号,第三个成员对应第二个括号,以此类推。整个数组的length属性等于组匹配的数量再加1。

			var s = '_x_x';
			var r = /_(x)/; // 含有圆括号,组匹配

			r.exec(s) // ["_x", "x"]  返回多个成员,第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果
			

上面代码的exec方法,返回一个数组。第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果。

exec方法的返回数组还包含以下两个属性:

  • input:整个原字符串。
  • index:整个模式匹配成功的开始位置(从0开始计数)。
			var r = /a(b+)a/;
			var arr = r.exec('_abbba_aba_');

			arr // ["abbba", "bbb"] // 第一个成员是整个匹配的结果,第二个成员是圆括号匹配的结果

			arr.index // 1  表示是在1位置开始匹配成功的
			arr.input // "_abbba_aba_"
			

上面代码中的index属性等于1,是因为从原字符串的第二个位置开始匹配成功。

如果正则表达式加上g修饰符,则可以使用多次exec方法,下一次搜索的位置从上一次匹配成功结束的位置开始。

			var reg = /a/g; // 带'g'修饰符
			var str = 'abc_abc_abc'

			var r1 = reg.exec(str);
				r1 // ["a"]
				r1.index // 0 在0位置匹配成功
				reg.lastIndex // 1  下一次匹配开始位置为1

			var r2 = reg.exec(str);
				r2 // ["a"]
				r2.index // 4 在4位置匹配成功
				reg.lastIndex // 5 下一次匹配开始位置为5

				var r3 = reg.exec(str);
				r3 // ["a"]
				r3.index // 8 在8位置匹配成功
				reg.lastIndex // 9 下一次匹配开始位置为9

				var r4 = reg.exec(str);
				r4 // null 没有匹配到返回null
				reg.lastIndex // 0 下一次匹配位置为0
				

上面代码连续用了四次exec方法,前三次都是从上一次匹配结束的位置向后匹配。当第三次匹配结束以后,整个字符串已经到达尾部,匹配结果返回null,正则实例对象的lastIndex属性也重置为0,意味着第四次匹配将从头开始。

利用g修饰符允许多次匹配的特点,可以用一个循环完成全部匹配。

				var reg = /a/g;
				var str = 'abc_abc_abc'

				while(true) {
					  var match = reg.exec(str);
					  if (!match) break;
					  console.log('#' + match.index + ':' + match[0]);
}
			// #0:a
			// #4:a
			// #8:a
			

上面代码中,只要exec方法不返回null,就会一直循环下去,每次输出匹配的位置和匹配的文本。

正则实例对象的lastIndex属性不仅可读,还可写。设置了g修饰符的时候,只要手动设置了lastIndex的值,就会从指定位置开始匹配。

4、字符串的实例方法

字符串的实例方法之中,有4种与正则表达式有关。

  • String.prototype.match():返回一个数组,成员是所有匹配的子字符串。
  • String.prototype.search():按照给定的正则表达式进行搜索,返回一个整数,表示匹配开始的位置。
  • String.prototype.replace():按照给定的正则表达式进行替换,返回替换后的字符串。
  • String.prototype.split():按照给定规则进行字符串分割,返回一个数组,包含分割后的各个成员。

4.1 String.prototype.match() 匹配,返回匹配结果数组或null

字符串实例对象的match方法对字符串进行正则匹配,返回匹配结果

			
		var s = '_x_x';
		var r1 = /x/;
		var r2 = /y/;

		s.match(r1) // ["x"] // 未带g修饰符,匹配到一个即返回结果
		s.match(r2) // null
		

从上面代码可以看到,字符串的match方法与正则对象的exec方法非常类似:匹配成功返回一个数组,匹配失败返回null

如果正则表达式带有g修饰符,则该方法与正则对象的exec方法行为不同,会一次性返回所有匹配成功的结果。

			var s = 'abba';
			var r = /a/g;

			s.match(r) // ["a", "a"]  带g修饰符,会返回全部结果到数组成员
			r.exec(s) // ["a"] 正则实例的exec方法只返回一个
			

设置正则表达式的lastIndex属性,对match方法无效,匹配总是从字符串的第一个字符开始。

			
			var r = /a|b/g;
			r.lastIndex = 7;
			'xaxb'.match(r) // ['a', 'b']
			r.lastIndex // 0
			

上面代码表示,设置正则对象的lastIndex属性是无效的。

4.2 String.prototype.search() 返回第一个满足匹配条件的位置,不满足则返-1

字符串对象的search方法,返回第一个满足条件的匹配结果在整个字符串中的位置如果没有任何匹配,则返回-1

		'_x_x'.search(/x/)
		// 1
		

上面代码中,第一个匹配结果出现在字符串的1号位置。

4.3 String.prototype.replace() 替换匹配的值,参数一是子串或正则,参数二是替换内容

字符串对象的replace方法可以替换匹配的值。它接受两个参数,第一个是正则表达式,表示搜索模式,第二个是替换的内容。

		
		str.replace(search, replacement)
		

正则表达式如果不加g修饰符,就替换第一个匹配成功的值,否则替换所有匹配成功的值。

		'aaa'.replace('a', 'b') // "baa"
		'aaa'.replace(/a/, 'b') // "baa" 不带g修饰符,只匹配并替换第一个
		'aaa'.replace(/a/g, 'b') // "bbb" 带g修饰符,匹配全局,并替换全部匹配到的值
		

上面代码中,最后一个正则表达式使用了g修饰符,导致所有的b都被替换掉了。

replace方法的一个应用,就是消除字符串首尾两端的空格。

		
		var str = '  #id div.class  ';

			str.replace(/^\s+|\s+$/g, '')
			// "#id div.class"
			

replace方法的第二个参数可以使用美元符号$,用来指代所替换的内容

  • $&:匹配的子字符串。
  • $`:匹配结果前面的文本。
  • $':匹配结果后面的文本。
  • $n:匹配成功的第n组内容,n是从1开始的自然数。
  • $$:指代美元符号$
		'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
			// "world hello"
			/*说明:
				$n 用于组内容,$2为匹配到的第二组,即world; $1为匹配到的第一组,即hello
			*/

		'abc'.replace('b', '[$`-$&-$\'-$$]')  
			// "a[a-b-c-$]c"   
			/*说明:
			$`为匹配结果 b 前面的文本,即 a
			$&为匹配的子字符串,即 b
			$’为匹配结果 b 后面的文本,即 c
			$$代表$符号本身
			*/
			

上面代码中,第一个例子是将匹配的组互换位置,第二个例子是改写匹配的值。

replace方法的第二个参数还可以是一个函数,将每一个匹配内容替换为函数返回值

	
	'3 and 5'.replace(/[0-9]+/g, function (match) {
		  return 2 * match; // match参数是匹配到的内容
})
		// "6 and 10"

		var a = 'The quick brown fox jumped over the lazy dog.';
		var pattern = /quick|brown|lazy/ig;

		a.replace(pattern, function replacer(match) {
		  return match.toUpperCase();
});
		// The QUICK BROWN fox jumped over the LAZY dog.
		

作为replace方法第二个参数的替换函数,可以接受多个参数。其中,第一个参数是捕捉到的内容,第二个参数是捕捉到的组匹配(有多少个组匹配,就有多少个对应的参数)。此外,最后还可以添加两个参数,倒数第二个参数是捕捉到的内容在整个字符串中的位置(比如从第五个位置开始),最后一个参数是原字符串。下面是一个网页模板替换的例子。

	var prices = {
			  'p1': '$1.99',
 			 'p2': '$9.99',
  			'p3': '$5.00'
};

	var template = '<span id="p1"></span>'
		  + '<span id="p2"></span>'
		  + '<span id="p3"></span>';

	template.replace(
		  /(<span id=")(.*?)(">)(<\/span>)/g,
		  function(match, $1, $2, $3, $4, index, str){
  		  return $1 + $2 + $3 + prices[$2] + $4;
  }
);
		// "<span id="p1">$1.99</span><span id="p2">$9.99</span><span id="p3">$5.00</span>"

		// 说明:
		// match 整个匹配到的内容:<span id="p*"></span>

		// 以下参数分别是匹配到的组内容,有多少组就对应多少个参数
		// $1 <span id="
		// $2  p1~p3
		// $3  ">
		// $4  </span>

		// 后面还可以接受两个参数:
		// 倒数第二个是:捕捉到的内容(即match的内容)在整个字符串中的位置
		// 最后一个是:原字符串

上面代码的捕捉模式中,有四个括号,所以会产生四个组匹配,在匹配函数中用$1$4表示。匹配函数的作用是将价格插入模板中。

4.4 String.prototype.split() 按给定规则分割字符串,返回数组

字符串对象的split方法按照正则规则分割字符串,返回一个由分割后的各个部分组成的数组。

		str.split(separator, [limit])
		

该方法接受两个参数,第一个参数是正则表达式,表示分隔规则,第二个参数是返回数组的最大成员数(数组长度)。

		// 非正则分隔
			'a,  b,c, d'.split(',')
			// [ 'a', '  b', 'c', ' d' ]

			// 正则分隔,去除多余的空格
			'a,  b,c, d'.split(/, */)
			// [ 'a', 'b', 'c', 'd' ]

			// 指定返回数组的最大成员(数组长度)
			'a,  b,c, d'.split(/, */, 2)
			[ 'a', 'b' ]
			

上面代码使用正则表达式,去除了子字符串的逗号后面的空格。

		// 例一
			'aaa*a*'.split(/a*/)
			// [ '', '*', '*' ]  分隔符,即被过滤的符号,

		// 例二
			'aaa**a*'.split(/a*/)
			// ["", "*", "*", "*"]
			

上面代码的分割规则是0次或多次的a,由于正则默认是贪婪匹配,所以例一的第一个分隔符是aaa,第二个分割符是a,将字符串分成三个部分,包含开始处的空字符串。例二的第一个分隔符是aaa,第二个分隔符是0个a(即空字符),第三个分隔符是a,所以将字符串分成四个部分。

如果正则表达式带有括号,则括号匹配的部分也会作为数组成员返回。

		'aaa*a*'.split(/(a*)/)
		// [ '', 'aaa', '*', 'a', '*' ]
		

上面代码的正则表达式使用了括号,第一个组匹配是aaa,第二个组匹配是a,它们都作为数组成员返回。

Object.preventExtensions() 防止新增

	var obj = new Object()
				Object.preventExtensions(obj) // prevent Extensions 中文: 防止扩展
				Object.defineProperty(obj,'p',{
 		   value: 'hello'
}) // TypeError:cannot define property:p, object is not extensible

			obj.p = 1
			obj.p // undefined
			

上面代码中,obj对象经过Object.preventExtensions以后,就无法添加新属性了。

Object.isExtensible() 是可新增的?

Object.isExtensible方法用于检查一个对象是否使用了Object.preventExtensions方法。也就是说,检查是否可以为一个对象添加属性。

	var obj = new Object()
			Object.isExtensible(obj) // true    is Extensible 中文: 是可扩展的?
			Object.preventExtensions(obj)
			Object.isExtensible(obj) // false
			

上面代码中,对obj对象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已经不能添加新属性了。

Object.seal() 密封

Object.seal方法使得一个对象既无法添加新属性,也无法删除旧属性。

		var obj = {p: 'hello'}
			Object.seal(obj) // seal 中文: 密封

		delete obj.p // 删除属性无效
			obj.p // hello

		obj.p = 'hi' // 可修改值
			obj.p // hi

		obj.x = 'world' // 新增属性无效
			obj.x // undefined
			

上面代码中,obj对象执行Object.seal方法以后,就无法添加新属性和删除旧属性了。

Object.seal实质是把属性描述对象的configurable属性设为false,因此属性描述对象不再能改变了。

		var obj = {p: 'a'}

			Object.getOwnPropertyDescriptor(obj, 'p')
						// Object {
						//   value: "a",
						//   writable: true,
						//   enumerable: true,
						//   configurable: true
// }

			Object.seal(obj)

			Object.getOwnPropertyDescriptor(obj, 'p')
						// Object {
						//   value: "a",
						//   writable: true,
						//   enumerable: true,
						//   configurable: false
// }

			Object.defineProperty(obj,'p',{
  					  enumerable:
})
			// TypeError: Cannot redefine property: p
			

上面代码中,使用Object.seal方法之后,属性描述对象的configurable属性就变成了false,然后改变enumerable属性就会报错。

Object.seal只是禁止新增或删除属性,并不影响修改某个属性的值。

		var obj = { p: 'a' };
			Object.seal(obj);
		obj.p = 'b';
			obj.p // 'b'
			

上面代码中,Object.seal方法对p属性的value无效,是因为此时p属性的可写性由writable决定。

最后求关注

更多干货请公众号:龙哥手记 点击「加群进阶」, 进龙哥唯一的读者群。
如果有错误或者不严谨的地方,请务必得出指正,非常感谢。如果喜欢或者 有所启发,欢迎star, 对作者也是一种鼓励。
跟着龙哥一起掉亿点点秀发吧~, `别忘给我点赞 大帅比大漂亮们再走啊!!!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。