JS高级编程(一)-基础

摩森特沃 2021年03月07日 478次浏览

数据类型

基本数据类型

  • 表示空:Undefined,Null
  • 基础三大件:Number,String,Boolean
  • 两个新类型:Symbol,BigInt(用来表示大于 253 - 1 的整数)

常见引用类型

  • 普通对象 Object
  • 数组对象 Array
  • 正则对象 RegExp
  • 函数 Function

Js包装类型

什么是包装类型

在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:

const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"

包装类型与基本类型的类型转换

基本类型转包装类型

  • 使用Object函数显示转换
var a = 'abc'
Object(a) // String {"abc"}

包装类型转基本类型

  • 使用valueOf方法将包装类型倒转成基本类型
var a = 'abc'
var b = Object(a)
var c = b.valueOf() // 'abc'

类型转换

JavaScript 的类型转换的结果总是得到string、number和boolean类型的一种

显式类型转换

string 转 number

  • Number(string)
  • parseInt,parseFloat
const a = 18
// 注意不需要加new 否则含义变成了创建实例
const b = String(18) // '18'
const c = Number(b) // 18

const a = '18'
const b = +a //18


const a = '18px'
const b = parseInt(a) // 18
// Number不允许传入非数字字符
const c = Number(a) // NaN

// 对于+操作符,还有一个特殊的作用,可以将日期对象转换为number
var date = new Date( "Mon, 1 Mar 2020 08:53:06" )
+date // 1583013186000

number 转 string

  • String(number)
  • toString()
const a = 18
const b = String(18) // '18'

const a = 18
const b = a.toString() // '18'

任意值转换成boolean

  • Boolean(any)
  • !!
const a = '123'
const b = undefined
const c = 0
Boolean(a) // true
Boolean(b) // false
Boolean(c) // false

!!a // true
!!b // false
!!c // false

隐式类型转换

ToPrimitive

ToPrimitive是 JavaScript 中每个值隐含的自带的方法,用来将值 (无论是基本类型值还是对象)转换为基本类型值。如果值为基本类型,则直接返回值本身;如果值为对象

/**
* @obj 需要转换的对象
* @type 期望的结果类型,type的值为number或者string
*/
ToPrimitive(obj,type)
  • 当type为number时规则如下:
  • 调用obj的valueOf方法,如果为原始值,则返回,否则下一步;
  • 调用obj的toString方法,后续同上;
  • 抛出TypeError 异常。
  • 当type为string时规则如下:
  • 调用obj的toString方法,如果为原始值,则返回,否则下一步;
  • 调用obj的valueOf方法,后续同上;
  • 抛出TypeError 异常

可以看出两者的主要区别在于调用toString和valueOf的先后顺序。默认情况下:

  • 如果对象为 Date 对象,则type默认为string;
  • 其他情况下,type默认为number。

总结上面的规则,对于 Date 以外的对象,转换为基本类型的大概规则可以概括为一个函数:

  • var objToNumber = value => Number(value.valueOf().toString())
  • objToNumber([]) === 0
  • objToNumber({}) === NaN

使用操作符的转换

严格上来讲,使用+、-、*、/以及==、>、<其实是一种隐式转换,因为这些操作符只能操作基本数据类型,所以在进行这些运算前的第一步就是将两边的值用ToPrimitive转换成基本类型,再进行操作,以下以对象为例进行说明

  • 案例一
var a = {}
a > 2 // false

其对比过程如下

a.valueOf() // {}, 上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]",现在是一个字符串了
Number(a.toString()) // NaN,根据上面 < 和 > 操作符的规则,要转换成数字
NaN > 2 //false,得出比较结果
  • 案例二
var a = {name:'Jack'}
var b = {age: 18}
a + b // "[object Object][object Object]"

运算过程如下

a.valueOf() // {},上面提到过,ToPrimitive默认type为number,所以先valueOf,结果还是个对象,下一步
a.toString() // "[object Object]"

b.valueOf() // 同理
b.toString() // "[object Object]"

a + b // "[object Object][object Object]"

判断一个数据的类型

typeof

  • typeof 可以检测出 number、string、boolean、undefined 和 function 的类型,但是当被检测的值为数组、对象或者 null 类型时,结果均为 object,无法准确判断它们的类型
  • 使用typeof判断null的方法
var a = null;

(!a && typeof a === "object"); // true

instanceof

  • instanceof 可用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,返回boolean类型的值,instanceof有以下两个限制
    • 不能检测字面声明的基本变量的类型,如:instanceof Number === false
    • 由于 instanceof 会遍历原型链,所以只要是在原型链上的构造函数都会返回 true

isArray

  • isArray 用于检测检测是否是数组:Array.isArray

Object.prototype.toString.call

  • 在任何值上调用Object.prototype.toString方法,都会返回一个 [object NativeConstructorName]格式的字符串。每个类在内部都有一个 [[Class]] 属性,这个属性中就指定了上述字符串中的构造函数名
  • 使用Object.prototype.toString.call,目前这是最为可靠的检测类型的方法
// JavaScript 中并没有Null()和Undefined构造器,此处为其内部的特殊处理
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
// 注意对基本类型而言,是没有构造函数的,此处之所以能进行判断,是因为发生了隐式类型转换,经基本数据类型转换为了包装类型
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"

// 函数类型
function fn(){}
Object.prototype.toString.call(fn); // "[object Function]"
// 数组类型
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"

检测对象是否拥有属性

  • '属性名' in 对象
console.log('name' in cat); // false or true

数据遍历

  • 对象属性的遍历:for-in
  • for-in只遍历可枚举属性,包括原型链上的可枚举属性
  • for-in会以任意顺序遍历对象的可枚举属性
  • for-in用于遍历数组时,得到的是数组的下标,如果数组中存在键值对,则得到的是键值对的键
for (var p in cat) {
    console.log(p); // 输出属性的名称
    console.log(cat[p]); // 访问对应的属性的值
} 
// 遍历数组
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for(let o in iterable){
    console.log(o, iterable[o]);
}
// 输出结果为:
0, 3
1, 5
2, 7
foo, hello
  • 数组value的遍历:for-of
  • for-of会在可迭代对象上(包括:Array,Map,Set,String,TypedArray,arguments对象等等)上创建一个迭代循环,调用自定义的迭代钩子([Symbol.iterator])
  • for-of不可以遍历普通对象
  • for-of遍历可迭代对象定义要迭代的数据,其准守的是可迭代协议,此类型的对象都具有一个[Symbol.iterat]属性,其值为一个函数
let iterable = [3, 5, 7];
iterable.foo = 'hello';
for (let i of iterable) {
  console.log(i); // 3, 5, 7
}

json字符串与javascript对象的转换

JSON.parse()

  • 用于将一个 JSON 字符串转换为 JavaScript 对象
  • 参数
  • text:必需,一个有效的 JSON 字符串
  • reviver: 可选,一个转换结果的函数,将为对象的每个成员调用此函数
  • 使用示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>使用可选参数,回调函数</h2>
<p id="demo"></p>
<script>
var obj1 = JSON.parse('{"p": 5}', function(k, v) {
	if (k === '') { return v; } 
	return v * 2;               
});  
console.log(obj1);
// 运行结果:
// 控制台输出:{p: 10}                        
JSON.parse('{"1": 1, "2": 2, "3": {"4": 4, "5": {"6": 6}}}', function(k, v) {
  document.write( k );// 输出当前属性,最后一个为 ""
  document.write("<br>");
  return v;       // 返回修改的值
});
// 运行结果:
// 页面展示:1 2 4 6 5 3
// 控制台输出:{1: 1, 2: 2, 3: {4: 4, 5: {6: 6}}}
</script>

</body>
</html>

JSON.stringify()

  • 用于将 JavaScript 值(通常为对象或数组)转换为 JSON 字符串
  • 参数
  • value:必需,要转换的 JavaScript 值(通常为对象或数组)
  • replacer:可选,用于转换结果的函数或数组
    • 如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。最终转换结果使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:""
    • 如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样
  • space:可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t
  • 使用示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p id="demo"></p>
<script>
var str = {"name":"菜鸟教程", "site":"http://www.runoob.com"}
var str_pretty2 = JSON.stringify(str, null, 4) //使用四个空格缩进
document.write( "使用参数情况:" );
document.write( "<br>" );
document.write("<pre>" + str_pretty2 + "</pre>" ); // pre 用于格式化输出
// 运行结果:
// 使用参数情况:
// {
//     "name": "菜鸟教程",
//     "site": "http://www.runoob.com"
// }
</script>
</body>
</html>

Js事件监听

监听事件的两种方式

  • 注册监听器

语法:element.addEventListener(event, function, useCapture)
参数

  • event:事件名字符串,不要使用 "on" 前缀
  • function:事件触发时执行的函数
  • useCapture:事件是否在捕获或冒泡阶段执行,可选值:true(事件句柄在捕获阶段执行)/false(默认,事件句柄在冒泡阶段执行)
  • 绑定事件

语法:element.onXXX = function()

  • 二者区别

使用addEventListener可以在一个事件上注册多个监听器,且不会发生覆盖,而使用onXXX的方式定义在后面的事件回调方法会覆盖前面的回调方法,最终只有一个会被执行

  • 注意

在ie浏览器中,需要使用attachEvent代替addEventListener

补充内容(事件响应顺序)

js操作dom自定义属性

获取自定义属性值

  • var value = domObj.dataset.attrName;
  • var value = domObj.getAttribute("data-attr-name");
  • 注意
    • 可直接使用domObj.dataset获取dataset数据集,dataset拥有数组的部分特性,但不是数组,不能用forEach方式遍历
var dom = document.getElementById("myDiv");
var dataset = dom.dataset;
var str = "";
for (var item in dataset) {
    str += item + " ";
}
var name = dom.dataset.name;
var name1 = dom.getAttribute("data-name");

设置自定义属性值

  • domObj.dataset.attrName = value;
  • dom.setAttribute("data-attrName", value");
  • 注意
    • 使用domObj.dataset设置自定义属性时,驼峰命名的属性将会被转换为以'-'分割的属性名
    • 基于以上原因,如果使用domObj.dataset设置自定义属性,但使用domObj.getAttribute()获取自定义属性值时,需要将自定义属性名转换为"-"形式的属性名来获取,而直接使用domObj.dataset获取时不需要进行转换
    • 使用domObj.setAttribute设置自定义属性时,data后面的部分会被全部转换为小写字符,在使用domObj.getAttribute获取值时,使用大写和小写的形式均可获取到值
var dom = document.getElementById("myDiv");
dom.dataset.age = 13;
dom.setAttribute("data-name", "tom");

jquery获取自定义属性值

  • var value = $dom.data("attr-name");
  • var value = $dom.attr("data-attr-name");

jquery设置自定义属性值

  • $dom.data("attr-name", value);
  • $dom.attr("attr-name", value);
  • 注意
    • 在使用$.data()方法设置dom结点中本身不具有的属性时,设置后并不会直接在dom上体现,而是jquery直接将属性和值进行了缓存
    • 在使用$.data()方法设置和获取自定义属性的值时,使用驼峰命名和使用"-"命名都可以混用,但大小写敏感
    • 在jquery1.6后引入了$.prop()的相关方法,该方法主要用于设置checked,selected等值为true/false相关的特性

判断dom节点的包含关系

  • node.contains(otherNode)
  • 用于判断是否包含otherNode节点
  • 判断otherNode是否是node的后代节点
  • 如果otherNode是node的后代节点或者是node节点本身,则返回true,否则返回false

setTimeout

参数和返回值

  • 参数
  • 必需。要调用一个代码串,也可以是一个函数
  • 可选。执行或调用 code/function 需要等待的时间,以毫秒计。默认为 0
  • 可选。 传给执行函数的其他参数(IE9 及其更早版本不支持该参数)
  • 返回值
  • 返回一个 ID(数字),可以将这个ID传递给 clearTimeout() 来取消执行
setTimeout(code, milliseconds, param1, param2, ...)
setTimeout(function, milliseconds, param1, param2, ...)

错误用法

function showName() {
    console.log("my name is 猪小明")
}
// 使用以下写法时,showName方法将会立即调用
setTimeout(showName(), 1000)
// 第一个参数必须是一个代码串或者是一个函数,因此可以使用以下三种写法
// 传入一个函数,以下两种写法效果相同
setTimeout(showName, 1000)
setTimeout(function () {
    showName()
}, 1000)
// 传入一个代码串
setTimeout('showName()', 1000)

设置延迟执行时间为0

  • 问题引出
var fuc = [1,2,3];
for(var i in fuc){
  setTimeout(function(){console.log('定时器输出结果', fuc[i])},0);
  console.log(fuc[i]);
}
// 代码输出结果
// 1
// 2
// 3
// 定时器输出结果 3
// 定时器输出结果 3
// 定时器输出结果 3
  • 原因分析

这是事件循环机制,因为js是单线程的,是基于事件循环的。而setTimeout函数是异步的,异步的事件会加入一个队列,会等到当前同步的任务执行完毕后,再执行setTimeout队列的任务。所以,通过设置任务在延迟0毫秒后执行,事件本身并没有延迟,但是能改变任务执行的先后顺序,改变它所调用的函数的优先级,使之异步执行

  • 可能会遇到的问题及处理方法
<input type="text" name="" onkeydown="changeFunc(this)">
<div id="showBox"></div>
<script type="text/javascript">
    function changeFunc (event) {
        document.getElementById("showBox").innerHTML = event.value
    }
</script>
// 在以上方法中,input中的最后一个字符无法在div中显示,可使用以下方式修订
// 方式一:将写入div标签的操作放在定时任务中进行
setTimeout(function() {document.getElementById("showBox").innerHTML = event.value})
// 方式二:将keydown改为keyup,这种改法与setTimeout内容无法,此处不再介绍

js关键词

delete

  • 只能删除属性,不能删除方法和原型链上的属性,也不能删除变量
function fun() {
    this.name = "zhangsan";
    this.say = function() {
	alert(this.name);
    }
}
var obj = new fun();
alert(obj.name); // 张三
delete obj.name;
alert(obj.name); // undefined

this

  • 参考文档
  • javascript中this关键字指向的是它所属的对象
  • this所属的对象由其使用的位置决定
    • 在方法中,this指向的是它所有者对象
    • 单独使用的情况下,this指向的是全局对象
    • 在函数中,非严格模式下,this指向的是全局对象,严格模式下是undefined
    • 在事件中,this指向的是接收事件的元素
    • 构造函数中,this直接指向new之后返回的对象
    • window.setTimeout()和window.setInterval()默认的是this是window对象
  • 使用call,apply,bind方法可以将this引用到任何对象

注意:上述的方法和函数的区别是,方法是指某一特定对象的函数,示例代码如下:

// 在函数内部定义属性
function test() {
    this.x = 1;
    alert(this.x);
}
// 此方法直接调用和使用new 调用时this的效果是不一样的

// 直接调用时,this.x相当于全局变量,即this代表window,其效果与在函
// 数外通过var声明一个名为x的变量,然后再函数内部改变x的值效果一样
test(); 
var t = new test();
// 此时this指向当前调用的对象,即t
alert(t.x); 

new

// 使用new关键字调用函数时内部的执行步骤
// 1,创建一个空对象
// 2,把构造函数的prototype属性,作为空对象的原型
// 3,this赋值为这个空对象
// 4,执行函数
// 5,如果函数没有返回值,则返回this(返回之前的那个空对象)

super

// 1,作为父类构造函数调用
// 2,作为对象的方式调用
// 2.1,非静态方法中访问super->父类原型
// 2.2,静态方法中,访问super -> 父类
// 注意:
// 1,使用super调用父类时,父类中的的this,始终是子类的this

Object.defineProperty()

描述

  • Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象,该方法允许精确地添加或修改对象的属性
Object.defineProperty(obj, prop, descriptor)

// obj 要定义属性的对象
// prop 要定义或修改的属性的名称或 Symbol
// descriptor 要定义或修改的属性描述符
// 返回值 被传递给函数的对象

属性描述符

  • 数据描述符,是一个具有值的属性,该值可以是可写的,也可以是不可写的
  • 存取描述符,是由 getter 函数和 setter 函数所描述的属性
  • 一个描述符只能是这两者其中之一;不能同时是两者
  • 如果一个描述符不具有 value、writable、get 和 set 中的任意一个键,那么它将被认为是一个数据描述符
  • 如果一个描述符同时拥有 value 或 writable 和 get 或 set 键,则会产生一个异常

属性描述符键值

  • 共享描述符
    • configurable,默认值为false,当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变(包括被重新定义),同时该属性也能从对应的对象上被删除
    • enumerable,默认值为false,当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中(属性会被for...in 或 Object.keys 方法枚举到)
  • 数据描述符特有键值
    • value,默认为 undefined,表示该属性对应的值,可以是任何有效的 JavaScript 值(数值,对象,函数等)
    • writable,默认为 false,当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变
  • 存取描述符特有属性
    • get,默认为 undefined,属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象),该函数的返回值会被用作属性的值
    • set,默认为 undefined,属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象
var o = {};

o.a = 1;
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: true,
  configurable: true,
  enumerable: true
});


// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于:
Object.defineProperty(o, "a", {
  value: 1,
  writable: false,
  configurable: false,
  enumerable: false
});
function Archiver() {
  var temperature = null;
  var archive = [];

  Object.defineProperty(this, 'temperature', {
    get: function() {
      console.log('get!');
      return temperature;
    },
    set: function(value) {
      temperature = value;
      archive.push({ val: temperature });
    }
  });

  this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]

注意:在 get 和 set 方法内部,this 指向被访问和修改属性的对象

function myclass() {
}

Object.defineProperty(myclass.prototype, "x", {
  get() {
    return this.stored_x;
  },
  set(x) {
    this.stored_x = x;
  }
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined

扩展延申

  • 相关联的方法
  • Object.seal(obj):封闭一个对象,返回值为与传入的参数相同的对象,阻止添加新属性并将所有现有属性标记为不可配置,当前属性的值只要原来是可写的就可以改变
  • Object.freeze(obj):冻结一个对象,返回值为与传入的参数相同的对象,一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改
  • Object.seal(obj)与Object.freeze(obj)都是对对象的浅操作,当对象的属性也是一个对象时,则属性对应的对象不受封闭和冻结的影响
  • Object.seal(obj)与Object.freeze(obj)区别:使用Object.freeze()冻结的对象中的现有属性值是不可变的。用Object.seal()密封的对象可以改变其现有属性值

正则表达式

常用元数据

  • \b 代表边界
  • \B 代表非边界
  • \d 代表整数
  • \D 代表非整数
  • \w 代表单词字符,等价于[A-Za-z0-9_]
  • \W 代表非单词字符
  • \s 代表空白字符
  • \S 代表非空白字符
  • ^n 代表任何开头为 n 的字符串
  • n$ 代表任何结尾为 n 的字符串

常用限定符(常用量词)

  • *:出现零次或多次
  • ?:出现一次或零次
  • +:出现一次或多次
  • :出现n次
  • {n,}:至少出现n次
  • {n,m}:出现n次至m次

正则匹配模式

  • 贪心模式:正则表达式匹配时,默认采用贪心模式,即尽量多地匹配字符串
  • 非贪心模式:在量词后面加上一个?字符即表示非贪心模式,也就是说?跟在字符后面就是量词,跟在量词后面就是开启非贪心模式

相同字符的不同含义

  • ?
    • 在字符后面表示量词
    • 在量词后面表示非贪心模式
  • ^
    • 在正则表达式开头时,表示匹配字符串开始的位置,如:/^23/
    • 当其出现在字符组开头时,表示非 (排除),如:/[^123]/

常用方法

RegExp.prototype.test()

test是JavaScript中正则表达式对象的一个方法,用来检测正则表达式对象与传入的字符串是否匹配,若匹配返回true,若不匹配返回false,由于test方法的返回值只是一个boolean值,所以括号在test方法中唯一的作用就是分组

/123{2}/.test('123123') // false
/(123){2}/.test('123123') // true

String.prototype.match()

match方法是字符串的方法,传入参数为正则表达式,返回字符串匹配正则表达式的结果。括号在match方法中有两个作用

  • 分组
  • 捕获,捕获的意思是将用户指定的匹配到的子字符串暂存并返回给用户

当传入的正则表达式没有使用g标志时,其返回一个数组,数组第一个值为第一个完整匹配,后续的值分别为括号捕获的所有值,且此数组含有以下三个属性

  • groups,命名捕获组
  • index,匹配结果的开始下标
  • input,传入的原始字符串
const result1 = '123123'.match(/123{2}/) // null
const result2 = '123123'.match(/(123){2}/) // ["123123", "123", index: 0, input: "123123", groups: undefined]
console.log(result2.index) // 0
console.log(result2.input) // 123123
console.log(result2.groups) // undefined

对比这两个匹配返回的结果,可以证明括号起到了分组的作用。再看第二句,返回数组的第一个值为正则匹配到的第一个子字符串,第二个值为括号捕获的值。

虽然第二句用括号引用了捕获组,但是result2.groups的值依然为undefined,这是因为第二句中的捕获组为匿名捕获组,而result2.groups只返回命名捕获组的值,命名捕获组的用法是:(?<name>...)

const result = 'a123a123'.match(/a(?<first>\d)(?<second>\d)/) // ["a12", "1", "2", index: 0, input: "a123a123", groups: {…}]
console.log(result.index) // 0
console.log(result.input) // a123a123
console.log(result.groups) // {first: "1", second: "2"}

在match方法中,当传入的正则表达式有 g 标志时,将返回所有与正则表达式匹配的结果,忽略捕获,如:

const result = 'a123a123'.match(/a(?<first>\d)(?<second>\d)/g) // ["a12", "a12"]

RegExp.prototype.exec

exec方法是正则表达式的方法,传入参数为字符串,返回字符串匹配正则表达式的结果。当正则表达式没有g标志时,其返回值与String.prototype.match()没有g标志时返回的结果一样:

'123123'.match(/(123){2}/) // ["123123", "123", index: 0, input: "123123", groups: undefined]
/(123){2}/.exec('123123')  // ["123123", "123", index: 0, input: "123123", groups: undefined]

当正则表达式有 g 标志时,可以多次执行 exec 方法来查找同一个字符串中的成功匹配。如:

var html = "123123";
var tag = /(12)3/g
var match, arr = []
do {
  if (match) arr.push(match)
  match = tag.exec(html)
} while (match)

console.log(arr[0]) // ["123", "12", index: 0, input: "123123", groups: undefined]
console.log(arr[1]) // ["123", "12", index: 3, input: "123123", groups: undefined]
console.log(arr[2]) // undefined

使用场景总结

  • 当只是想检测字符串和正则表达式是否匹配时,使用test方法
  • 当不仅想检测两者是否匹配,还想知道是哪些子字符串匹配,或者想要在非global模式下捕获子字符串时,用match方法
  • 当既想在global模式下匹配字符串,又想捕获子字符串时,就是用exec方法

经典案例

正则表达式中的引用

使用正则表达式支持以下三种格式的日期

2016-06-12
2016/06/12
2016.06.12

答案

/\d{4}(-|\/|\.)\d{2}\1\d{2}/
// 注意答案中使用到了反向引用,第一个分隔符用括号括住来进行捕获,然后答案中d{2}和d{2}之间的\1,代表之前匹配到的第一个字符,这样就实现了两个字符统一的效果

注意:/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/是错误答案,因为/\d{4}(-|\/|\.)\d{2}(-|\/|\.)\d{2}/.test("2016-06/12")结果也会返回true

写一个函数,将驼峰字符串转换成-分割字符串

function conversion(str) { 
  return str.replace(/([A-Z])/g, '-$1').toLowerCase() 
}
// $1类似于前面提到的\1,代表了前面捕获到的值

给数字加上千分位分隔符

  • 使用toLocaleString方法
const num = 1234567
num.toLocaleString() // 1,234,567
  • 使用正则表达式
‘1234567’.replace(/(?=(\B\d{3})+$)/g, ‘,’) // 1,234,567
// ?= 表示断言,匹配任何其后紧接指定字符串 n 的字符串
// \B 表示排除字符串的开头

escape,encodeURI和encodeURIComponent

escape

  • escape方法已经被废弃,其作用是对字符串(string)进行编码

encodeURI

  • encodeURI用来处理整个 URI,它不会对下列字符编码ASCII字母 数字 ~!@#$&*()=:/,;?+'(完整URI必备字符)
encodeURI("http://www.cnblogs.com/season-huang/some other thing");
// http://www.cnblogs.com/season-huang/some%20other%20thing

encodeURIComponent

  • encodeURIComponent通常只用它来转义URI的参数,它不会对下列字符编码ASCII字母 数字 ~!*()'
encodeURIComponent("http://www.cnblogs.com/season-huang/some other thing");
// http%3A%2F%2Fwww.cnblogs.com%2Fseason-huang%2Fsome%20other%20thing

引用参考