JavaScript 高级

77 阅读18分钟

构造函数

  • 构造函数语法
  • 分析构造函数
  • 构造函数和实例对象的关系
    • 实例的 constructor 属性
    • instanceof 操作符
  • 普通函数调用和构造函数调用的区别
  • 构造函数的返回值
  • 构造函数的问题

更优雅的工厂函数:构造函数

一种更优雅的工厂函数就是下面这样,构造函数:

function Person (name, age) {
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
}

let p1 = new Person('Jack', 18)
p1.sayName() // => Jack

let p2 = new Person('Mike', 23)
p2.sayName() // => Mike

解析构造函数代码的执行

在上面的示例中,Person() 函数取代了 createPerson() 函数,但是实现效果是一样的。 这是为什么呢?

我们注意到,Person() 中的代码与 createPerson() 有以下几点不同之处:

  • 没有显示的创建对象
  • 直接将属性和方法赋给了 this 对象
  • 没有 return 语句
  • 函数名使用的是大写的 Person

而要创建 Person 实例,则必须使用 new 操作符。 以这种方式调用构造函数会经历以下 4 个步骤:

  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象)
  3. 执行构造函数中的代码
  4. 返回新对象

下面是具体的伪代码:

function Person (name, age) {
  // 当使用 new 操作符调用 Person() 的时候,实际上这里会先创建一个对象
  // let instance = {}
  // 然后让内部的 this 指向 instance 对象
  // this = instance
  // 接下来所有针对 this 的操作实际上操作的就是 instance

  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }

  // 在函数的结尾处会将 this 返回,也就是 instance
  // return this
}

构造函数和实例对象的关系

使用构造函数的好处不仅仅在于代码的简洁性,更重要的是我们可以识别对象的具体类型了。 在每一个实例对象中同时有一个 constructor 属性,该属性指向创建该实例的构造函数:

console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true

对象的 constructor 属性最初是用来标识对象类型的, 但是,如果要检测对象的类型,还是使用 instanceof 操作符更可靠一些:

console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true

总结:

  • 构造函数是根据具体的事物抽象出来的抽象模板
  • 实例对象是根据抽象的构造函数模板得到的具体实例对象
  • 每一个实例对象都具有一个 constructor 属性,指向创建该实例的构造函数
    • 注意: constructor 是实例的属性的说法不严谨,具体后面的原型会讲到
  • 可以通过实例的 constructor 属性判断实例和构造函数之间的关系
    • 注意:这种方式不严谨,推荐使用 instanceof 操作符

构造函数的问题

使用构造函数带来的最大的好处就是创建对象更方便了,但是其本身也存在一个浪费内存的问题:

function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = function () {
    console.log('hello ' + this.name)
  }
}

let p1 = new Person('Tom', 18)
let p2 = new Person('Jack', 16)

在该示例中,从表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。 那就是对于每一个实例对象,typesayHello 都是一模一样的内容, 每一次生成一个实例,都必须为重复的内容,多占用一些内存,如果实例对象很多,会造成极大的内存浪费。

console.log(p1.sayHello === p2.sayHello) // => false

对于这种问题我们可以把需要共享的函数定义到构造函数外部:

function sayHello = function () {
  console.log('hello ' + this.name)
}

function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = sayHello
}

let p1 = new Person('Top', 18)
let p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true

这样确实可以了,但是如果有多个需要共享的函数的话就会造成全局命名空间冲突的问题。

你肯定想到了可以把多个函数放到一个对象中用来避免全局命名空间冲突的问题:

let fns = {
  sayHello: function () {
    console.log('hello ' + this.name)
  },
  sayAge: function () {
    console.log(this.age)
  }
}

function Person (name, age) {
  this.name = name
  this.age = age
  this.type = 'human'
  this.sayHello = fns.sayHello
  this.sayAge = fns.sayAge
}

let p1 = new Person('lpz', 18)
let p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true

原型

  • 使用 prototype 原型对象解决构造函数的问题
  • 分析 构造函数、prototype 原型对象、实例对象 三者之间的关系
  • 属性成员搜索原则:原型链
  • 实例对象读写原型对象中的成员
  • 原型对象的简写形式
  • 原生对象的原型
    • Object
    • Array
    • String
    • ...
  • 原型对象的问题
  • 构造的函数和原型对象使用建议

更好的解决方案: prototype

JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。 这个对象的所有属性和方法,都会被构造函数的所拥有。

这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。

function Person (name, age) {
  this.name = name
  this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayName = function () {
  console.log(this.name)
}

let p1 = new Person(...)
let p2 = new Person(...)

console.log(p1.sayName === p2.sayName) // => true

这时所有实例的 type 属性和 sayName() 方法, 其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。

构造函数、实例、原型三者之间的关系

Image.png

属性查找规则:

  • 读取属性:先查找对象本身属性,如果没有找到,会去原型链上查找。

  • 设置属性:不会检索原型链,而是直接给对象新增属性。

内置对象中的prototype不可以修改,只能增加

任何函数都具有一个 prototype 属性,该属性是一个对象。

function F () {}
console.log(F.prototype) // => object

F.prototype.sayHi = function () {
  console.log('hi!')
}

构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数。

console.log(F.prototype.constructor === F) // => true

通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__

let instance = new F()
console.log(instance.__proto__ === F.prototype) // => true

`__proto__` 是非标准属性。

实例对象可以直接访问原型对象成员。

instance.sayHi() // => hi!

总结:

  • 任何函数都具有一个 prototype 属性,该属性是一个对象
  • 构造函数的 prototype 对象默认都有一个 constructor 属性,指向 prototype 对象所在函数
  • 通过构造函数得到的实例对象内部会包含一个指向构造函数的 prototype 对象的指针 __proto__
  • 所有实例都直接或间接继承了原型对象的成员

自调用函数

自调用函数 -- 开启一个新的作用域,避免命名冲突;每一个自调用函数前面加分号';'( 原因是第一个自调用函数执行完之后会返回一个undefine)

  • 自调用函数传入window的目的,将来代码压缩的时候,可以把 function (window) 压缩成 function (w)。

  • 自调用函数传入undefined的目的,是在有的老版本的浏览器中 undefined可以被重新赋值,防止undefined 被重新赋值。

  • 通过自调用函数,已经防止了变量命名污染的问题

  • 问题1

// 如果存在多个自调用函数要用分号分割,否则语法错误
// 下面代码会报错
(function () {
}())

(function () {
}())
// 所以代码规范中会建议在自调用函数之前加上分号
// 下面代码没有问题
;(function () {
}())

;(function () {
}())
  • 问题2
// 当自调用函数 前面有函数声明时,会把自调用函数作为参数
// 所以建议自调用函数前,加上;
let a = function () {
  alert('11');
}
    
(function () {
  alert('22');
}())

继承

构造函数的属性继承:借用构造函数

function Person (name, age) {
  this.type = 'human';
  this.name = name;
  this.age = age;
}

function Student (name, age) {
  // 借用构造函数继承属性成员 
  Person.call(this, name, age);
}

let s1 = Student('张三', 18);
console.log(s1.type, s1.name, s1.age); // => human 张三 18

构造函数的原型方法继承:拷贝继承(for-in)

function Person (name, age) {
  this.type = 'human';
  this.name = name;
  this.age = age;
}

Person.prototype.sayName = function () {
  console.log('hello ' + this.name);
}

function Student (name, age) {
  Person.call(this, name, age);
}

// 原型对象拷贝继承原型对象成员
for(let key in Person.prototype) {
  Student.prototype[key] = Person.prototype[key];
}

let s1 = Student('张三', 18);

s1.sayName(); // => hello 张三

另一种继承方式:原型继承

function Person (name, age) {
  this.type = 'human';
  this.name = name;
  this.age = age;
}

Person.prototype.sayName = function () {
  console.log('hello ' + this.name);
}

function Student (name, age) {
  Person.call(this, name, age);
}

// 利用原型的特性实现继承
Student.prototype = new Person();
Student.prototype.constructor = Student;

let s1 = Student('张三', 18);

console.log(s1.type); // => human

s1.sayName(); // => hello 张三

Image.png

函数

函数的定义方式

  • 函数声明
  • 函数表达式
  • new Function

函数声明

function foo () {
}

函数表达式

var foo = function () {

}

函数声明与函数表达式的区别

  • 函数声明必须有名字
  • 函数声明会函数提升,在预解析阶段就已创建,声明前后都可以调用
  • 函数表达式类似于变量赋值
  • 函数表达式可以没有名字,例如匿名函数
  • 函数表达式没有变量提升,在执行阶段创建,必须在表达式执行之后才可以调用

函数的调用方式

  • 普通函数
  • 构造函数
  • 对象方法

函数内 this 指向的不同场景

函数的调用方式决定了 this 指向的不同:

调用方式非严格模式备注
普通函数调用window严格模式下是 undefined
构造函数调用实例对象原型方法中 this 也是实例对象
对象方法调用该方法所属对象紧挨着的对象
事件绑定方法绑定事件对象
定时器函数window

函数也是对象

  • 所有函数都是 Function 的实例

call、apply、bind

call

call() 方法调用一个函数, 其具有一个指定的 this 值和分别地提供的参数(参数的列表)。

注意:该方法的作用和 `apply()` 方法类似,只有一个区别,就是 `call()` 方法接受的是若干个参数的列表,而 `apply()` 方法接受的是一个包含多个参数的数组。

语法:

fun.call(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 在 fun 函数运行时指定的 this 值
    • 如果指定了 null 或者 undefined 则内部 this 指向 window
  • arg1, arg2, ...

    • 指定的参数列表

apply

apply() 方法调用一个函数, 其具有一个指定的 this 值,以及作为一个数组(或类似数组的对象)提供的参数。

注意:该方法的作用和 `call()` 方法类似,只有一个区别,就是 `call()` 方法接受的是若干个参数的列表,而 `apply()` 方法接受的是一个包含多个参数的数组。

语法:

fun.apply(thisArg, [argsArray])

参数:

  • thisArg
  • argsArray

apply()call() 非常相似,不同之处在于提供参数的方式。 apply() 使用参数数组而不是一组参数列表。例如:

fun.apply(this, ['eat', 'bananas'])

bind

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的call属性)。 当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。 一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

语法:

fun.bind(thisArg[, arg1[, arg2[, ...]]])

参数:

  • thisArg

    • 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
  • arg1, arg2, ...

    • 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

返回值:

返回由指定的this值和初始化参数改造的原函数拷贝。

示例1:

this.x = 9; 
let module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

let retrieveX = module.getX;
retrieveX(); // 返回 9, 在这种情况下,"this"指向全局作用域

// 创建一个新函数,将"this"绑定到module对象
let boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

示例2:

function LateBloomer() {
  this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
  window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
  console.log('I am a beautiful flower with ' +
    this.petalCount + ' petals!');
};

let flower = new LateBloomer();
flower.bloom();  // 一秒钟后, 调用'declare'方法

小结

  • call 和 apply 特性一样

    • 都是用来调用函数,而且是立即调用
    • 但是可以在调用函数的同时,通过第一个参数指定函数内部 this 的指向
    • call 调用的时候,参数必须以参数列表的形式进行传递,也就是以逗号分隔的方式依次传递即可
    • apply 调用的时候,参数必须是一个数组,然后在执行的时候,会将数组内部的元素一个一个拿出来,与形参一一对应进行传递
    • 如果第一个参数指定了 null 或者 undefined 则内部 this 指向 window
  • bind

    • 可以用来指定内部 this 的指向,然后生成一个改变了 this 指向的新的函数
    • 它和 call、apply 最大的区别是:bind 不会调用
    • bind 支持传递参数,它的传参方式比较特殊,一共有两个位置可以传递
        1. 在 bind 的同时,以参数列表的形式进行传递
        1. 在调用的时候,以参数列表的形式进行传递
      • 那到底以谁 bind 的时候传递的参数为准呢还是以调用的时候传递的参数为准
      • 两者合并:bind 的时候传递的参数和调用的时候传递的参数会合并到一起,传递到函数内部

函数的其它成员

  • arguments
    • 实参集合
  • caller
    • 函数的调用者
  • length
    • 形参的个数
  • name
    • 函数的名称
function fn(x, y, z) {
  console.log(fn.length) // => 形参的个数
  console.log(arguments) // 伪数组实参参数集合
  console.log(arguments.callee === fn) // 函数本身
  console.log(fn.caller) // 函数的调用者
  console.log(fn.name) // => 函数的名字
}

function f() {
  fn(10, 20, 30)
}

f()

高阶函数

  • 函数可以作为参数
  • 函数可以作为返回值

作为参数

function eat (callback) {
  setTimeout(function () {
    console.log('吃完了')
    callback()
  }, 1000)
}

eat(function () {
  console.log('去唱歌')
})

作为返回值

function genFun (type) {
  return function (obj) {
    return Object.prototype.toString.call(obj) === type
  }
}

let isArray = genFun('[object Array]')
let isObject = genFun('[object Object]')

console.log(isArray([])) // => true
console.log(isArray({})) // => true

函数闭包

function fn () {
  let count = 0
  return {
    getCount: function () {
      console.log(count)
    },
    setCount: function () {
      count++
    }
  }
}

let fns = fn()

fns.getCount() // => 0
fns.setCount()
fns.getCount() // => 1

作用域、作用域链、预解析

  • 全局作用域
  • 函数作用域
  • 没有块级作用域
{
  let foo = 'bar';
}

console.log(foo);

if (true) {
  let a = 123;
}
console.log(a)

作用域链示例代码:

let a = 10;

function fn () {
  let b = 20;

  function fn1 () {
    let c = 30;
    console.log(a + b + c);
  }

  function fn2 () {
    let d = 40;
    console.log(c + d);
  }

  fn1()
  fn2()
}
  • 内层作用域可以访问外层作用域,反之不行

什么是闭包

闭包就是能够读取其他函数内部变量的函数, 由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量, 因此可以把闭包简单理解成 “定义在一个函数内部的函数”。 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途:

作用1: 隐藏变量,避免全局污染

作用2: 可以读取函数内部的变量

同时闭包使用不当,优点就变成了缺点:

缺点1: 导致变量不会被垃圾回收机制回收,造成内存消耗

缺点2: 不恰当的使用闭包可能会造成内存泄漏的问题

一些关于闭包的例子

示例1:

let arr = [10, 20, 30];
for(let i = 0; i < arr.length; i++) {
  arr[i] = function () {
    console.log(i);
  }
}

示例2:

console.log(111)

for(let i = 0; i < 3; i++) {
  setTimeout(function () {
    console.log(i)
  }, 0)
}
console.log(222)

函数递归

递归执行模型

function fn1 () {
  console.log(111);
  fn2();
  console.log('fn1');
}

function fn2 () {
  console.log(222);
  fn3();
  console.log('fn2');
}

function fn3 () {
  console.log(333);
  fn4();
  console.log('fn3');
}

function fn4 () {
  console.log(444);
  console.log('fn4');
}

fn1();

举个例子:计算阶乘的递归函数

function factorial (num) {
  if (num <= 1) {
    return 1;
  } else {
    return num * factorial(num - 1);
  }
}

递归和拷贝

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。

13253432-4eae0bbcf9d34ae3.webp

浅拷贝实现方式

1. Object.assign()

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); // wade

注意:当object只有一层的时候,是深拷贝

let obj = {
   username: 'kobe'
};
let obj2 = Object.assign({},obj);
obj2.username = 'wade';
console.log(obj);//{username: "kobe"}

2. Array.prototype.concat()

let arr = [1, 3, {
   username: 'kobe'
}];
let arr2=arr.concat();    
arr2[2].username = 'wade';
console.log(arr); // [ 1, 3, { username: 'wade' } ]

3. Array.prototype.slice()

let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。

原数组的元素会按照下述规则拷贝:

  1. 如果该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。
  2. 对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
let arr = [1, 3, {
   username: ' kobe'
}];
let arr3 = arr.slice();
arr3[1] = 2
console.log(arr,arr3); // [ 1, 3, { username: ' kobe' } ] [ 1, 2, { username: ' kobe' } ]

深拷贝实现方式

1. JSON.parse() 和 JSON.stringify()

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。因为 JSON.stringify() 方法是将一个JavaScript值(对象或者数组)转换为一个 JSON字符串,不能接受函数。

let arr = [1, 3, {
   username: ' kobe'
}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4) // [ 1, 3, { username: ' kobe' } ] [ 1, 3, { username: 'duncan' } ]
let arr = [1, 3, {
   username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4); // (4) [1, 3, {…}, ƒ] (4) [1, 3, {…}, null]

2. 手写递归方法

原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝。

// 定义检测数据类型的功能函数
function checkedType(target) {
	return Object.prototype.toString.call(target).slice(8, -1);
}
// 实现深度克隆---对象/数组
function clone(target) {
	// 判断拷贝的数据类型
	// 初始化变量result 成为最终克隆的数据
	let result,
		targetType = checkedType(target);
	if (targetType === "object") {
		result = {};
	} else if (targetType === "Array") {
		result = [];
	} else {
		return target;
	}
	// 遍历目标数据
	for (let i in target) {
		// 获取遍历数据结构的每一项值。
		let value = target[i];
		// 判断目标结构里的每一值是否存在对象/数组
		if (checkedType(value) === "Object" || checkedType(value) === "Array") {
			//对象/数组里嵌套了对象/数组
			// 继续遍历获取到value值
			result[i] = clone(value);
		} else {
			// 获取到value值是基本的数据类型或者是函数。
			result[i] = value;
		}
	}
	return result;
}

3. 函数库lodash

函数库也有提供 _.cloneDeep 用来做 Deep Copy。

let _ = require('lodash');
let obj1 = {
   a: 1,
   b: { f: { g: 1 } },
   c: [1, 2, 3]
};
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false

正则表达式

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。

正则表达式的作用

  1. 给定的字符串是否符合正则表达式的过滤逻辑(匹配)
  2. 可以通过正则表达式,从字符串中获取我们想要的特定部分(提取)
  3. 强大的字符串替换能力(替换)

正则表达式的组成

  • 普通字符abc 123
  • 特殊字符(元字符):正则表达式中有特殊意义的字符\d \w

常用元字符串

元字符说明
\d匹配数字
\D匹配任意非数字的字符
\w匹配字母或数字或下划线
\W匹配任意不是字母,数字,下划线
\s匹配任意的空白符
\S匹配任意不是空白符的字符
.匹配除换行符以外的任意单个字符
表示匹配行首的文本(以谁开始)
$表示匹配行尾的文本(以谁结束)

限定符

限定符说明
*重复零次或更多次
+重复一次或更多次
?重复零次或一次
{n}重复n次
{n,}重复n次或更多次
{n,m}重复n到m次

其它

[] 字符串用中括号括起来,表示匹配其中的任一字符,相当于或的意思
[^]  匹配除中括号以内的内容
\ 转义符
| 或者,选择两者中的一个。注意|将左右两边分为两部分,而不管左右两边有多长多乱
()
[\u4e00-\u9fa5]  匹配汉字

案例

验证手机号:

^\d{11}$

验证邮编:

^\d{6}$

验证日期 2012-5-01

^\d{4}-\d{1,2}-\d{1,2}$

验证邮箱 xxx@itcast.cn

^\w+@\w+\.\w+$

验证IP地址 192.168.1.10

^\d{1,3}\(.\d{1,3}){3}$

JavaScript 中使用正则表达式

  1. RegExp对象-test()匹配,exec()提取

let regularExpression = new RegExp();

  1. String对象-match()匹配,replace()替换,split()切割,search()查找

创建正则对象

方式1:

let reg = new RegExp('\d', 'i');
let reg = new RegExp('\d', 'gi');

方式2:

let reg = /\d/i;
let reg = /\d/gi;

参数

标志说明
i忽略大小写
g全局匹配
gi全局匹配+忽略大小写

正则匹配

// 匹配日期
let dateStr = '2015-10-10';
let reg = /^\d{4}-\d{1,2}-\d{1,2}$/
console.log(reg.test(dateStr));

正则提取

// 1. 提取工资
let str = "张三:1000,李四:5000,王五:8000。";
let array = str.match(/\d+/g);
console.log(array);

// 2. 提取email地址
let str = "123123@xx.com,fangfang@valuedopinions.cn 286669312@qq.com 2、emailenglish@emailenglish.englishtown.com 286669312@qq.com...";
let array = str.match(/\w+@\w+\.\w+(\.\w+)?/g);
console.log(array);

// 3. 分组提取  
// 3. 提取日期中的年部分  2015-5-10
let dateStr = '2016-1-5';
// 正则表达式中的()作为分组来使用,获取分组匹配到的结果用Regex.$1 $2 $3....来获取
let reg = /(\d{4})-\d{1,2}-\d{1,2}/;
if (reg.test(dateStr)) {
  console.log(RegExp.$1);
}

// 4. 提取邮件中的每一部分
let reg = /(\w+)@(\w+)\.(\w+)(\.\w+)?/;
let str = "123123@xx.com";
if (reg.test(str)) {
  console.log(RegExp.$1);
  console.log(RegExp.$2);
  console.log(RegExp.$3);
}

正则替换

// 1. 替换所有空白
let str = "   123AD  asadf   asadfasf  adf ";
str = str.replace(/\s/g,"");
console.log(str);

// 2. 替换所有,或,
let str = "abc,efg,123,abc,123,a";
str = str.replace(/,|,/g, ".");
console.log(str);