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. 常见的伪数组:arguments、document.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) () 这里是一个赋值运算表达式,给一个方法赋值了一个函数,但是括号内的这个运算表达式也产生了一个结果,就是右边的方法函数,所以会是函数调用;

,逗号运算符

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