JavaScript 高级

JavaScript 高级

面向对象编程

什么是面向对象?

面向对象编程 —— Object Oriented Programming,简称 OOP ,是一种编程开发思想。

它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工,可以完成接受信息、处理数据、发出信息等任务。

因此,面向对象编程具有灵活、代码可复用、高度模块化等特点,容易维护和开发,比起由一系列函数或指令组成的传统的过程式编程(procedural programming),更适合多人合作的大型软件项目。

面向对象与面向过程:

  • 面向过程就是亲历亲为,事无巨细,面面俱到,步步紧跟,有条不紊,面向过程是解决问题的一种思维方式,关注点在于解决问题的过程。
  • 面向对象就是找一个对象,指挥得结果,解决问题的思维方式,关注点在解决问题的对象上。
  • 面向对象将执行者转变成指挥者
  • 面向对象不是面向过程的替代,而是面向过程的封装

面向对象编程三大特性

  1. 封装性:
    • 将功能的具体实现,全部封装到对象的内部,外界使用对象时,只需要关注对象提供的方法如何使用,而不需要关心对象对象的内部具体实现,这就是封装。
  2. 继承性:
    • 在js中,继承的概念很简单,一个对象没有的一些属性和方法,另外一个对象有,拿过来用,就实现了继承。
    • 注意:在其他语言里面,继承是类与类之间的关系,在js中,是对象与对象之间的关系。
  3. 多态性:
    • 多态是在强类型的语言中才有的。js是弱类型语言,所以JS不支持多态。

prototype 原型

什么叫原型:

  • 任何一个函数,都有一个属性,prototype 值是一个对象
  • 把函数的 prototype 指向的这个对象就叫做原型。
  • 原型内所有的属性和方法都可以被这个构造函数new出来的实例访问。

原型的作用: 存储一些所有实例需要共享的内容,比如方法。

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

原型(prototype)的概念

JavaScript中任何一个函数都有一个属性,prototype指向一个对象,这个函数构造的所有实例都可以继承这个对象的所有属性和方法;

原型(prototype)的作用

原型(prototype)的作用是存储所有实例需要共享和继承的属性和方法;

构造函数,实例,原型之间的关系

构造函数:构造函数就是一个函数,配合new可以新建对象。

实例:通过构造函数实例化出来的对象我们把它叫做构造函数的实例。一个构造函数可以有很多实例。

原型:每一个构造函数都有一个属性prototype,这个属性就叫做原型属性。通过构造函数创建出来的实例能够直接使用原型上的属性和方法。

构造函数:其实就一个函数,通常来说,构造函数的首字母要大写。构造函数要通过new来使用。构造函数的作用:实例化对象。

实例(对象): 任何一个对象都是由构造函数new出来。

1
2
var obj = {};  // var obj = new Object();
var arr = []; // var arr = new Array();

原型:任何一个构造函数,都会有一个prototype属性,原型可以理解为构造函数的配偶,或者是实例的父亲。原型就是一个对象。 默认的原型对象只有一个属性:constructor的属性,指向了构造函数。

1
Person.prototype == 实例.__proto__

__proto__ 属性

通过构造函数创建的对象,自带一个 __proro__ 属性,这个属性指向了构造函数的prototype属性,也就是原型对象。

获取原型对象:

  • 通过 构造函数.prototype 可以获取
  • 通过 实例.__proto__ 可以获取(隐式原型)
  • 它们指向了同一个 对象构造函数.prototype === 实例.__proto__

注意:__proto__ 是浏览器的一个隐藏(私有)属性,早期的IE浏览器不支持,不要去修改它,如果要修改原型中的内容,使用 构造函数.prototype 去修改

constructor属性

默认情况下,原型对象中值包含了一个属性:constructor,constructor属性指向了当前的构造函数。

原型链

任何一个对象,都有原型对象,原型对象本身又是一个对象,所以原型对象也有自己的原型对象,这样一环扣一环就形成了一个链式结构,我们把这个链式结构称为:原型链。

总结:Object.prototype 是原型链的尽头,Object.prototype 的原型是 null

任何一个函数,都会有 prototype 属性。 任何一个对象,都会有(原型)__proto__,这个原型又是一个对象,所以原型也会原型(__proto__),一环扣一环,就形成了一个链式的结构,我们把这个链式结构就叫做原型链。

  1. 所有的函数的都是 new Fucntion 创建出来的,函数.__proto__ === Function.prototype
  2. 所有的原型都是 new Object 创建出来的,原型.__proto__ === Object.prototype
1
2
3
4
5
Function.__proto__ === Function.prototype
Object.__proto__ === Function.prototype
Function.prototype.__proto__ === Object.prototype
Array.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

Math是一个对象。

原型链属性查找原则

如果是获取操作

  1. 会先在自身上查找,如果没有
  2. 则根据 __proto__ 对应的原型去找,如果没有
  3. 一直找到 Object.prototyp,如果没有,那就找不到了,返回 undefined。(万物皆对象)
  4. 当我们访问某个对象的属性的时候, 先找自己的属性,如果有,直接返回;如果没有,沿着原型链一直找到 Object.prototype,如果还没有,就会返回 undefined

如果是修改操作

  • 只会修改对象自身的属性,如果自身没有这个属性,那么就会添加这个属性,并不会修改原型中的属性。
  • 如果对象自己有,就覆盖原来的值。如果对象没有,就增加一个值。

Object.prototype成员介绍

hasOwnProperty()

hasOwnProperty() 方法会返回一个布尔值,判断某个属性是否是对象自己的属性。

  • 如果是自己的属性,会返回true。
  • 如果不是自己的属性或者这个属性不存在,都会返回false。

hasOwnProperty与in的区别

in操作符:如果属性不是自己提供的,是从原型上继承来的,也会返回true;

hasOwnProperty: 该属性必须是自己提供,才返回true,否则返回false。

propertyIsEnumerable()

propertyIsEnumerable() 方法返回一个布尔值,表明指定的属性名是否是当前对象可枚举的自身属性。

  • 该属性必须是可枚举,即可以被for..in遍历
  • 该属性必须是自身属性。

Object.defineProperty() 了解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//第一个参数:需要给哪个对象添加属性
//第二个参数:给对象添加的属性的名字
//第三个参数:给属性添加的修饰,是一个对象
var obj = {
name: "age",
age: 18
}
Object.defineProperty(obj, 'gender', {
//value: "女", //该属性对应的值,默认是undefined
configurable: true, //默认为false,为true时,才可以修改值和删除
enumerable: false, //默认为false,为true时才可以被for..in遍历
//writable: false, //默认为false, 为true才可以被赋值运算符改变
//当获取属性值的时候,get方法会被调用
get: function () {
console.log("get执行了")
return this.value;
},
//该方法将接受唯一参数,并将该参数的新值分配给该属性。
set: function (val) {
this.value = val;
console.log("set执行了");
}
});

obj.gender = "男";
console.log(obj.gender);

valueOf()/toString()/toLocalString()

  • valueOf() 会返回对象的原始值,我们不需要手动的调用这个方法,当我们需要使用到原始值的时候,JavaScript会自动的调用他。
  • toString() 方法返回一个表示该对象的字符串。当对象需要转换为一个字符串的时候,toString方法会被调用
  • toLocaleString() 方法返回一个该对象的字符串表示。该方法主要用于被本地化相关对象覆盖。

参考资料:

  • http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8
  • http://lzw.me/pages/ecmascript/#100‘
  • 当一个对象需要转换成字符串类型的时候,会调用 tostring() 方法,如果需要转换成原始值,会调用 valueOf() 方法。
    1. 如果是Date类型,会默认先调用 toString() 方法,如果 toString() 类型没有获取到原始值,就会调用 valueOf() 方法
    2. 如果是其他类型,会默认先调用 valueOf() 方法,如果 valueOf() 没有获取到原始值,会调用 toString() 方法
    3. 如果 toString()valueOf() 方法都没有转换成原始值,就会报错。

isPrototypeOf()

isPrototypeOf() 用来判断对象

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。

A.isPrototypeOf(B) 判断A是否是B的原型或者是原型的原型。说白了就是判断A是否是B的祖先

A.isPrototypeOf(B) 判断A是否在B的原型链上;A 是一个原型对象

B instanceof A 判断A的prototype是否在B的原型链上;A 是一个构造函数

instanceof

instanceof 判断的是构造函数(实际执行的是构造函数.prototype

instanceof 运算符作用和isPrototypeOf类似,用于判断构造函数的prototype属性是否在对象的原型链上。如果是,就返回true,如果不在,就返回false。

语法: 实例对象 instanceof 构造函数

返回值:检测构造函数的prototype属性是否在实例对象的原型链上。

A.isPrototypeOf(B) 判断A是否在B的原型链上;A 是一个原型对象
B instanceof A 判断A的prototype是否在B的原型链上;A 是一个构造函数

函数的四种调用模式(this)

函数内部可以使用this,这个this是动态的,函数的调用模式不同,this指向的对象也不同;

函数和方法的区别

对象内部的函数叫方法;

不在一个对象内的函数,叫函数

函数调用模式

this指向window 函数名();

方法调用模式

this指向当前调用函数的对象 对象名.方法名();

构造函数调用模式

this指向 new 构造函数名() 的实例对象;

上下文调用模式(方法借调模式)

call()

call函数调用

fn.call() 所有的函数都可以使用call进行调用;

参数1:指定函数的this,如果不传,则this指向window

其余参数:和函数的参数列表一模一样。

说白了,call方法也可以和()一样,进行函数调用,call方法的第一个参数可以指定函数内部的this指向。

fn.call(thisArg, arg1, arg2, arg2);

thisArg:this指向对象,如果不传,则this指向window

arg1, arg2, arg2:形参

方法借调

obj2.方法名.call(obj1) obj2借用obj2的方法

伪数组也叫类数组

  1. 伪数组其实就是一个对象,但是跟数组一样,伪数组也会有length属性,也有0,1,2,3等属性。
  2. 伪数组并没有数组的方法,不能使用push/pop等方法
  3. 伪数组可以跟数组一样进行遍历,通过下标操作。
  4. 常见的伪数组:argumentsdocument.getElementsByTagName的返回值、jQuery对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arrayLike = {
0:"张三",
1:"李四",
2:"王五",
length:3
}
//伪数组可以和数组一样进行遍历

//伪数组借用数组的方法
Array.prototype.push.call(arrLike, "赵六");
Array.prototype.join.call(arrLike, "-");

//将伪数组转换成真数组
var arr = Array.prototype.slice.call(arrLike);

借调的时候,调用了这个函数;this指向传入的参数;

apply()

apply() 方法

apply() 方法的作用和 call() 方法类似,只有一个区别,就是 apply() 方法接受的是一个包含多个参数的数组。

而call()方法接受的是若干个参数的列表

apply(thisArg, Array) 不传递参数默认指向window;参数格式固定,可以不写,

thisArg:this指向的对象

Array:所有实参组成的数组

call和apply的使用场景:

  • 如果参数比较少,使用call会更加简洁
  • 如果参数存放在数组中,此时需要使用apply
1
2
3
4
5
var arr = [1, 4, 7, 2, 5, 8, 3, 6, 9, 951, 357, 654, 852];
var num1 = Math.max.apply(null, arr);
var num2 = Math.min.call(null, 1, 4, 7, 2, 5, 8, 3, 6, 9, 951, 357, 654, 852);
console.log(num1);
console.log(num2);

沙箱模式

沙箱,沙盒,沙盘:这是一个独立的环境,这里面的任何变量都是这个环境中的变量,与外部无关;

函数自调用就是一个沙箱模式,自调用函数内部不会影响到外部;

如果想让一个变量可以被外部调用,在自调用函数结尾的地方使用 window.变量名 = 变量

1
2
3
4
5
6
7
(function(window){
var fn = function () {}
//如果想让这个函数变成外部的全局变量,赋值给window
window.fn = fn;
})(window);
//形参:window,可以被压缩
//实参:window

继承

一个对象可以访问构造函数的原型中的属性和方法,那么如果想要让一个对象增加某些属性和方法,

只需要把这些属性和方法放到原型对象中即可。这样就实现了继承, 称之为原型链继承;

混入式继承(mixin)

把一个对象中的属性和方法拷贝到另一个对象中;

原型链继承

直接给原型添加属性和方法

替换原型,在替换的对象中加入constructor:构造函数名;

混入式继承+原型替换

Object.create

最初是由道格拉斯丶克罗克福德发布的一篇文章提出的,ECMAScript5新增了 Object.create() 方法来规范化了这种继承。

ES5中新增了一个方法 Object.create(),方法会使用指定的原型对象及其属性去创建一个新的对象。

1
2
3
4
5
6
7
8
//参数:proto 一个对象
//返回值:obj 新对象,新对象的原型就是 proto
var obj = Object.create(proto);
console.log(obj);

var o = Object.create(lw);
console.log(o);
console.log(o.__proto__ == lw);//true

函数进阶

函数声明的三种方式

函数声明 function fn() {}

函数表达式 var fn = function () {}

构造函数 var fn = new Function()

1
2
3
4
5
6
7
8
9
10
11
12
//函数也是对象,可以使用Function构造函数new出来
//相当于var fn = function(){}
var fn = new Function();

//语法:new Function(arg1,arg2,arg3..,body);
// 1. 所有的参数都是字符串类型。
// 2. 前面可以定义任意多个形参,最后一个参数是代码体。
var fn = new Function("alert(1111)");
fn();

var fn1 = new Function("a1", "a2", "alert(a1+a2)");
fn1(1,2);

所见即所得函数

new Function可以让一段字符串当成代码来执行。

1
2
3
var str = "var n1 = 1; var n2 = 2; console.log(n1 + n2)";
var fn = new Function(str);
fn();

eval函数

eval是一函数,作用可以执行一段js程序;

eval的可以和new Function一样,执行字符串代码

注意:eval函数的功能非常的强大,但是实际使用的情况并不多。

eval形式的代码难以阅读;eval形式的代码无法打断点,因为本质还是还是一个字符串;在浏览器端执行任意的 JavaScript 会带来潜在的安全风险,恶意的JavaScript代码可能会破坏应用;

立即执行函数/自调用函数

自调用函数前面用分号开头,防止上一行代码没有用分号结束,而被浏览器默认连接在一起;

;()()

() [] // ` :`之前的代码必须使用分号结束

函数的原型链

函数也是一个对象:函数是由new Function创建出来的,因此函数也是一个对象,所有的函数都是new Function的实例。

函数的完整版原型链

  1. 所有的函数的都是 new Fucntion 创建出来的,函数.__proto__ === Function.prototype
  2. 所有的原型都是 new Object 创建出来的,原型.__proto__ === Object.prototype
1
2
3
4
5
Function.__proto__ === Function.prototype;
Object.__proto__ === Function.prototype;
Function.prototype.__proto__ === Object.prototype;
Array.prototype.__proto__ === Object.prototype;
Object.prototype.__proto__ === null;

ECMAScript 族谱

1
2
3
4
5
6
7
console.log(Object instanceof Function);
console.log(Function instanceof Function);
console.log(Object instanceof Object);
console.log(Function instanceof Object);
console.log(Array instanceof Function);
console.log(Array instanceof Object);
console.log([] instanceof Array);

预解析与作用域

预解析详解

预解析:预先解析

js执行代码分为两个过程:

  • 预解析过程(变量与函数提升)
  • 代码一行一行执行

预解析过程:JavaScript解析器在执行代码前,会把所有变量的声明和函数的声明提升到当前作用域的顶部。例如var a = 11;其实会分为var a;a = 11两部分,其中var a;会被提升。

预解析规则:

  1. 函数优先,先提升function,后提升var
  2. 遇到重名的var会被忽略。
  3. 遇到重名的function会被覆盖。

推荐:不要在一个作用域内重复的声明相同的变量和函数

作用域详解

作用域:变量起作用的区域,作用域决定了一个变量被定义在哪里,以及该如何被查找。

全局变量:在函数外定义的变量就叫全局变量,全局变量在任何地方都能访问到。

局部变量:在函数内定义的变量就叫局部变量,局部变量只有在当前函数内才能访问到。

1
2
3
4
5
6
var num = 11;//全局变量
function fn(){
var num1 = 22;//局部变量
console.log(num1);
}
console.log(num);

词法作用域

编程语言中,作用域规则分为两种:

  • 词法作用域(静态作用域)
  • 动态作用域

JavaScript采用的是词法作用域规则,词法作用域也叫静态作用域,变量在函数声明的时候,它的作用域就定下来了,与函数的调用无关。

JavaScript使用的是词法作用域(静态作用域);

函数在声明的时候,作用域就已经确定了;

函数内部的变量查找,与函数在哪里调用无关,只与函数声明的作用域有关;this指向另说;

作用域链

作用域链:只要是函数,就会形成一个作用域,如果这个函数被嵌套在其他函数中,那么外部函数也有自己的作用域,这个一直往上到全局环境,就形成了一个条作用域链。

变量的搜索原则:

  1. 从当前作用域开始搜索变量,如果存在,那么就直接返回这个变量的值。
  2. 如果不存在,就会往上一层作用域查询,如果存在,就返回。
  3. 如果不存在,一直查询到全局作用域,如果存在,就返回。如果不存在说明该变量是不存在的。
  4. 如果一个变量不存在
    • 获取这个变量的值会报错xxx is not defined;,
    • 给这个变量设置值,那么设置变量就是隐式全局变量。

全局作用域只要页面不卸载,就一直存在,不释放。

函数每次在调用时,都会形成一个作用域,当函数调用结束时,这个作用域就释放了。

函数闭包

闭包的概念

闭包的基本概念

If you can’t explain it to a six-year-old, you really don’t understand it yourself.

闭包(closure)是JavaScript语言的一个难点,也是JavaScript的一个特色,很多高级的应用都要依靠闭包来实现。

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数

在JavaScript中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。

闭包中包含了内部函数的代码,以及所需外部函数中的变量的引用

产生闭包的条件:当内部函数访问了外部函数的变量的时候,就会形成闭包。

当内部函数访问了外部函数的变量的时候,就会形成闭包。当一个函数访问另一个函数内的局部变量,只有在内外函数的情况。

闭包的应用

计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 需求:统计一个函数的调用次数
var count = 0;
function fn(){
count++;
console.log("我被调用了,调用次数是"+count);
}
fn();
fn();
fn();

/*
缺点:count是全局变量,不安全。
使用闭包解决这个问题!!!!
*/
function outer(){
var count = 0;
function add(){
count++;
console.log("当前count"+count);
}
return add;
}

var result = outer();
result();

私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 使用闭包实现私有变量的读取和设置
function outer(){
var num = 10;
function set_num(n){
num = n;
}
function get_num(){
return num;
}
return {
set_num:set_num,
get_num:get_num
}
}

var obj = outer();
obj.set_num(2000);
console.log(obj.get_num());

实现缓存

缓存(cache):数据的缓冲区,当要读取数据时,先从缓冲中获取数据,如果找到了,直接获取,如果找不到,重新去请求数据。

计算斐波那契数列,会有很大的性能问题,因为重复的计算了很多次,因此我们可以使用缓存来解决这个性能问题。

初级优化:

使用缓存的基本步骤:

  • 如果要获取数据,先查询缓存,如果有就直接使用
  • 如果没有,就进行计算,并且将计算后的结果放到缓存中,方便下次使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var arr = [];
var fbi = function (n) {
count++;
if (n == 1 || n == 2) {
return 1;
}
if (arr[n]) {
return arr[n];
} else {
var temp = fbi(n - 1) + fbi(n - 2);
arr[n] = temp;//存入缓存
return temp;
}
}

缺点:既然使用缓存,就需要保证缓存的数据的安全,不能被别人修改,因此,需要使用闭包来实现缓存的私有化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function outer() {
//缓存
var arr = [];
var fbi = function (n) {
if (n == 1 || n == 2) {
return 1;
}
if (arr[n]) {
return arr[n];
} else {
var temp = fbi(n - 1) + fbi(n - 2);
arr[n] = temp;//存入缓存
return temp;
}
}
return fbi;
}
var fbi = outer();
console.log(fbi(40));

闭包存在的问题

闭包占用的内存是不会被释放的,因此,如果滥用闭包,会造成内存泄漏的问题。闭包很强大,但是只有在必须使用闭包的时候才使用。

js的垃圾回收机制

引用计数法清除

1
2
3
4
5
6
7
8
var o = {
name:"zs"
}
//对象被o变量引用 ,引用记数1
var obj = o; //变量被o和obj引用,引用记数2

o = 1; //o不在引用对象了, 引用记数1
obj = null; //obj不在引用对象了,引用记数0,可以被垃圾回收了。

标记清除法清除

使用引用计数法进行垃圾回收的时候,会出现循环引用导致内存泄漏的问题。因此现代的浏览器都采用标记清除法来进行垃圾回收。

这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象Window)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。

从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。

正则表达式

正则表达式:用于匹配规律规则的表达式,正则表达式最初是科学家对人类神经系统的工作原理的早期研究,现在在编程语言中有广泛的应用,经常用于表单校验,高级搜索等。

创建正则表达式

  • 构造函数的方式 var regExp = new RegExp(/\d/);
  • 正则字面量 var regExp = /\d/;
  • 正则的使用 /\d/.test("aaa1");

普通元字符

  • /abc/
  • /123/
  • /:,汉字等/
  • 点要用转义字符 \.

元字符

正则表达式由一些普通字符和元字符组成,普通字符包括大小写字母、数字等,而元字符则具有特殊的含义。

  • \d [0-9] 数字字符
  • \D [^0-9] 非数字字符
  • \s [\f\r\n\t\v] 不可见字符
  • \S [^\f\r\n\t\v] 可见字符
  • \w [a-zA-Z0-9_] word字符
  • \W [^a-zA-Z0-9_] 非word字符
  • . [^\n\r] 除了换行和回车之外的所有字符

| 表示或的意思

() 优先级最高,表示分组

字符类元字符

[a-z] [1-9] 表示范围

[] 在正则表达式中表示一个字符的位置,[] 里面写这个位置可以出现的字符。

[^] 在中括号中^表示非的意思;表示该位置不可以出现的字符;

1
2
3
4
5
6
console.log(/[abc]/);//匹配a,b,c;
console.log(/[^abc]/);//匹配除了a,b,c以外的其他字符
console.log(/[a-z]/.test("d"));//小写字母
console.log(/[A-Z]/.test("d"));//大写字母
console.log(/[0-9]/.test("8"));//数字
console.log(/[a-zA-Z0-9]/);//所有的小写字母和大写字母以及数字

边界类元字符

^ 表示以 ^ 之后的字符开始,精确匹配

$ 表示以 $ 之前的字符结尾,精确匹配

1
2
3
4
5
6
console.log(/^chuan/.test("dachuan"));//必须以chuan开头
console.log(/chuan$/.test("chuang"));//必须以chuan结尾
console.log(/^chuan$/.test("chuan"));//精确匹配chuan

//精确匹配chuan,表示必须是这个
console.log(/^chuan$/.test("chuanchuan"));//fasle

量词类元字符

量词用来控制出现的次数,一般来说量词和边界会一起使用

  • * 表示出现0次或者多次,>=0
  • + 表示最少出现一次或者多次,>=1
  • ? 表示只能出现0次或者1次,=0或者=1
  • {n} 表示出现n次,=n
  • {n,} 表示最少出现n次,>=n
  • {n,m} 表示可以出现n到m次,n<=x<=m

正则表达式修饰符

g global 全局查找
i ignore 忽视大小写

正则表达式的使用

正则的使用:

  1. 字符串替换: replace 正则的替换
  2. 字符串匹配:match 匹配某个字符串中所有符合规律的字符串。
  3. 正则的测试:test 表单校验,判断某个字符串是否符合正则的规律
  4. 正则的提取: 提取匹配的字符串的每一个部分。() 进行分组
  • 正则测试 .test(String); 测试字符串,是否满足正则表达式,返回值是布尔类型
  • 正则提取 .exec(String); 提取满足正则表达式的字符串,返回值是一个数组
  • 正则匹配 String.match(正则表达式g); 把所有满足的正则表达式的字符串返回,返回值是一个数组
  • 正则替换 String.replace(正则表达式g,替换成xx); 把所有满足正则表达式的字符替换成xx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
正则测试
1. 验证座机
- 比如010-12345678 0797-1234567
- 开头是3-4位,首位必须是0
- -后面是7-8
var phoneReg = /^0\d{2,3}-\d{7,8}$/;
2. 验证姓名
- 只能是汉字
- 长度2-6位之间
- 汉字范围[\u4e00-\u9fa5]
var nameReg = /^[\u4e00-\u9fa5]{2,6}$/;
3. 验证QQ
- 只能是数字
- 开头不能是0
- 长度为5-11
var qqReg = /^[1-9]\d{4,10}$/;
4. 验证手机
- 11位数字组成
- 号段13[0-9] 147 15[0-9] 177[0178] 18[0-9]
var mobileReg = /^(13[0-9]|147|15[0-9]|17[0178]|18[0-9])\d{8}$/;
5. 验证邮箱
- 前面是字母或者数字
- 必须有@
- @后面是字母或者数字
- 必须有.
- .后面是字母或者数字
var emailReg = /^\w+@\w+(\.\w+)+$/;

正则提取
reg.test(字符串) 正则测试某个字符串是否符合规则,,,一般用在表达校验。
console.log(/1[3-9]\d{9}/.test("18511223344"));
console.log(/^1[3-9]\d{9}$/.test("1851122334411111111"));

//今天是2018-05-11, 要求;得到年月日;
var str = "今天是2018-05-11, 要求;得到年月日"

//提取,分组的概念,,,,如果不分组,提取只能拿到符合规律的整个值

var reg = /(\d{4})-(\d{2})-(\d{2})/;

//正则的方法:test exec
var arr = reg.exec(str);
console.log(arr);

// 今年是2018年12月25日
var str = "今年是2018年12月25日";

var reg = /(\d{4})年(\d{2})月(\d{2})日/;

var arr = reg.exec(str);
console.log(arr);

正则匹配
//需求:
var str = "我的手机号是:18511241111, 我的女朋友的手机号是:13211111111,我的前女友的手机号是:18522223333,我的前前女友的手机号是:18511112293";
//需求:把字符串中所有的手机号找出来。

//会返回满足正则条件的字符串,返回一个数组
console.log(str.match(/1[3-9]\d{9}/g));

正则替换
// 字符串.replace方法
var str = "aa bb cc dd ee bb";
str = str.replace(/bb/g, "gg");// g: global: 找到所有bb
console.log(str);

var str = " 123AD asadf asadfasf adf aD Ad ";
//1 替换掉字符串中的所有空白
//2. 将所有的ad替换成xx
//3. 将所有的ad/AD替换成xx
str = str.replace(/\s/g, "");
str = str.replace(/ad/g, "xx");
str = str.replace(/ad/gi, "xx");// g:global:全局查找 i:ignore:忽视大小写
console.log(str);

//var str = abc,efg,123,abc,123,a
//4. 所有的逗号替换成句号

var jsonStr = '[{"name":"张三",score:80},{"name":"张三",score:9},{"name":"张三",score:81}]';
// //5. 把所有成绩都修改成100分
jsonStr = jsonStr.replace(/\d{1,2}/g, "100");
console.log(jsonStr);


var str = "a-b-c-d";//a,b,c,d

Event loop

JavaScript是单线程

JavaScript是单线程的,在执行JavaScript代码时遇见定时器,事件等不是立即执行的代码,会交给浏览器(浏览器是多线程的),浏览器会开启一个队列,把JavaScript交给浏览器的代码,排列在队列中,在JavaScript执行完代码后,会将队列中的符合执行条件的代码执行;

运算符相关

=赋值运算符

赋值运算符是一个表达式,这个表达式会有一个结果,是=右边的结果;相当于函数返回了一个结果;

赋值运算符会把一个值赋值给一个变量,这个表达式也会有一个结果;可以用 console.log 来打印这个结果测试;

(foo.bar = foo.bar) () 这里是一个赋值运算表达式,给一个方法赋值了一个函数,但是括号内的这个运算表达式也产生了一个结果,就是右边的方法函数,所以会是函数调用;

,逗号运算符

逗号运算符和赋值运算符一样;也产生了一个结果;