JavaScript高级

本教程里的资料来源于网友的资料,自己整理以供学习。视频学习: 黑马程序员

两大编程思想

(面向过程,面向对象)

面向过程pop

process-oriented programming: 分析出问题所需的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个调用(蛋炒饭)

  • 优点: 性能高,适合跟硬件紧密联系

  • 缺点: 没有面向对象易维护、易复用、易扩展

面向对象oop

object-oriented programming: 把事务分解成一个一个对象,然后对象之间分工与合作(盖浇饭)适用于复杂的项目

  • 优点: 易维护、易复用、易扩展,可以设计出低耦合的系统。使系统更加灵活、更加易于维护

  • 缺点: 性能比面向过程低

面向对象的特性

  1. 封装性: 即封装后无需知道原理,运行即可

  2. 继承性: 某个接口的功能继承于其父亲

  3. 多态性: 多个工作状态

面向对象的思维特点

  1. 抽取(抽象)对象公用的属性和行为阻止(封装)成一个类(模板)

  2. 对类进行实例化,获取类的对象

类抽象了对象的公共部分,泛指一大类

类和对象的区别: 对象是具体的,而类是所有同类具体对象的共同属性方法的集合。可以通过类实例化一个具体的对象

类(ES6)

类的创建

class name { // class body}

1
2
3
4
5
6
class Star { 		
constructor(uname,age) {
this.uname = uname;
this.age = age;
}
}

类constructor构造函数: 用于传递参数,返回实例,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义,类内部会自动创建一个 constructor()。即将参数传递给实例对应的属性或方法。

创建实例

必须用new创建实例

var xx = new name(参数);

1
var ldh = new Star('刘德华',20);

注意:

  1. 通过 class 创建类,类名首字母大写

  2. 类里面有 constructor 函数,可以传递实参,同时返回实例, constructor 记得加小括号

  3. 只要 new 生成实例时,就会自动调用这个函数

  4. 生成实例 new 不能省略

  5. 创建类时类名后没有小括号,生成实例时类名后一定有小括号用于传递实参,构造函数不需要加 function()

类中添加方法

实质为添加在原型对象中

class name { 函数名(){}}

1
2
3
4
5
6
7
8
9
10
class Star { 
constructor(uname,age) {
this.uname = uname;
this.age = age;
}
sing(song) {
console.log('我在唱歌');
console.log(this.uname + song);
}
}

注意:

  1. 直接添加在 constructor 之后

  2. 可传递参数

  3. 可添加多个方法

  4. 可在方法中调用该对象属性

  • 添加类方法

    在函数前面加 static 保留字即可

    1
    2
    3
    4
    5
    6
    class Big { 
    static big() {
    console.log("i'm private!");
    }
    }
    Big.big();

类的继承

子类可以继承父类的一些属性和方法

继承语法

class Son extends Father {}

1
2
3
4
5
6
7
8
9
10
11
12
class Father {
constructor() {
}
money() {
console.log(100);
}
}
class Son extends Father {

}
var son = new Son();
son.money();
super 关键字

用于访问和调用对象父类上的函数。可以调用父类的构造函数,也可以调用父类的普通函数

  1. 利用 super 调用构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Father { 
    constructor(x,y) {
    this.x = x;
    this.y = y;
    } sum() {
    console.log(this.x + this.y);
    }
    }
    class Son extends Father {
    constructor(z,v) {
    super(z,v);
    }
    }
    var son = new Son(1,2);
    son.sum();

    即继承的本质是通过子类到父类中去执行函数,如果没有用 super 调用父类的函数,那么子类的形参无法传给父类,也就得不出结果。只有在子类的构造函数 constructor 中使用 super 调用父类的构造函数,才能将形参传给父类。

  2. 利用 super 调用普通函数

    super.父类中的函数名()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Father { 
    say() {
    return 'woshibaba';
    }
    }
    class Son extends Father {
    say() {
    console.log(super.say() + 'deerzi');
    }
    }
    var son = new Son();
    son.say(); // 就近原则调用
    // 继承中属性或者方法查找原则: 就近原则

    调用原则: 就近原则。如果子类调用了一个函数,则先查看子类中有无此函数,如果没有,就去父类中寻找,super 可视为等于f ather

子类继承父类方法同时扩展自己的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Father {
constructor(x,y) {
this.x = x;
this.y =y;
}
sum() {
console.log(this.x + this.y);
}
}
class Son extends Father {
constructor(x,y) {
super(x,y);
this.x = x;
this.y = y;
}
substract() {
console.log(this.x -this.y);
}
}
var son = new Son(5,3);
son.substract();
son.sum();

重点: 构造函数里有 this 和 super 的时候,super 一定要写在this之前,父亲为大!

使用类注意事项

  1. ES6 中没有变量提升,所以必须先定义类,才能通过类实例化对象

  2. 类里面的共有的属性和方法必须一定要加 this 使用

  3. 可以通过在构造函数里this.函数名直接调用方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var that;
    class Star {
    constructor(uname,age) {
    that = this;
    //constructor里面的this指向创建的实例对象
    this.uname = uname;
    this.age =age;
    this.sing();
    }
    sing() {
    // this 指向实例
    console.log(this);
    }
    }
  4. constructor 里面的 this 指向创建的实例对象,方法中的 this 指向调用者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    var that;
    class Star {
    constructor(uname,age) {
    that = this;
    //constructor里面的this指向创建的实例对象
    this.uname = uname;
    this.age =age;
    this.sing();
    this.btn = document.querySelector('button');
    this.btn.onclick = this.dance;
    }
    sing() {
    // this 指向实例
    console.log(this);
    }
    dance() {
    //指向btn的uname
    console.log(this.uname);
    }
    }

ES6中的类的本质

  1. class 本质还是 function.
  2. 类的所有方法都定义在类的 prototype 属性上
  3. 类创建的实例里面也有__proto__ 指向类的 prototype 原型对象
  4. 所以 ES6 的类它的绝大部分功能, ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
  5. 所以 ES6 的类其实就是语法糖
  6. 语法糖语法糖就是一种便捷写法简单理解,有两种方法可以实现同样的功能但是一种写法更加清晰、方便
    那么这个方法就是语法糖

扩展方法:

  • xx.select(): 实现 xx 元素的选中

  • xx.blur(): 实现 xx 元素的失焦

构造函数和原型(ES5)

概述

  • 在典型的 OOP 的语言中(如 Java ) , 都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6 之前,
    JS中并没用引入类的概念。

  • ES6 前 JS 没有类,通过构造函数来定义

  • ES6 ,全称 ECMAScript6.0 , 2015.06发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏
    览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
    在 ES6 之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征。

创建对象三种方式

  1. 通过new Object创建对象

    1
    var obj1 = new Object();
  2. 通过字面量创建对象

    1
    var obj2 = {};
  3. 通过构造函数创建对象

    1
    2
    3
    4
    5
    6
    7
    8
    function Star(uname,age) { 
    this.uname = uname;
    this.age = age;
    this.sing = function() {
    console.log('我会唱歌');
    }
    }
    var ldh = new Star('刘德华',18);
  4. 对象的增强写法

    1
    2
    3
    4
    5
    6
    7
    var name = "zykj";
    var age = 18;

    var obj3 = {
    name,
    age
    }

静态成员和实例成员

实例成员: 包括了实例方法和实例属性。通过构造函数内部 this 创建的成员,比如上面的 uname ,age , sing()

实例成员只能通过实例访问

1
2
var ldh = new Star('刘德华',20); 
console.log(ldh.uname);

不能通过构造函数访问

1
console.log(Star.uname); // 错误

静态成员: 即类属性和类方法(如Number.MAX_VALUE)。构造函数本身添加的成员,如

1
Star.sex = 'man';

静态成员只能通过构造函数访问

1
console.log(Star.sex);

不能通过实例访问

1
console.log(ldh.sex); // 错误

原型

重要内容、忘记了记得看原型

JS规定每一个构造函数都有一个 prototype 属性,指向一个原型对象。当函数被定义的时候,prototype 属性会自动创建和初始化。这个对象拥有的属性和方法都会被构造函数所拥有。即通过 prototype 定义的属性和方法都会自动成为该其构造函数创建实例所共有的属性和方法(prototype只有构造函数有,对象实例没有)

作用: 共享作用,可以把那些不变的方法,直接定义在 Prototype 对象上,这样所有对象的实例就可以共享这些方法

优点:

  1. 使用原型对象可以显著减少每个对象所需的内存数量,因为对象可以继承原型的很多属性。

  2. 即便是在对象创建以后才添加到原型中的属性,对象也可以继承它

:

1
2
3
4
5
Star.prototype.sing = function() { 
console.log('我会唱歌');
}
var ldh = new Star('刘德华',18);
ldh.sing();

注意: 继承的属性依旧可以用 for/in 来枚举,in 运算符依旧起效。只能用 Object.hasOwnProperty() 来区分继承的属性和常规的属性

对象原型 __proto__

每个实例对象(包括原型对象)都有一个属性__proto__(是两个杠),指向构造这个对象的构造函数的原型对象,对象通过__proto__就可以调用其中的属性和原型(即__proto__prototype是等价的)

开发中不可以使用__proto__这个属性,不可对其赋值

1
ldh.sing(); // 等价于 ldh.__proto__.sing()

即构造函数只能通过prototype属性获取原型对象,而实例对象必须通过proto获取原型对象

原型对象中的 constructor 属性

原型对象初始化后只有一个属性 constructor

构造函数.prototype.constructor对象实例. proto.constructor ( proto 可以省略) 可以返回这个对象所引用的构造函数(只会返回其构造函数,没有对象实例)

1
2
console.log(ldh.__proto__.constructor) 
console.log(Star.prototype.constructor)

如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Star(uname,age) { 
this.uname = uname;
this.age = age;
}

Star.prototype = {
// 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
constructor: Star,
sing :function() {
console.log("我爱唱歌");
},
movie : function() {
console.log("我爱演电影");
}
}

var ldh = new Star('刘德华',18);

构造函数、实例、原型对象的三者关系

  • 构造函数通过new生成对象实例

  • 构造函数通过prototype属性获取原型对象

  • 对象实例通过__proto__获取原型对象

  • 原型对象通过constructor获取构造函数

类和原型

原型对象是类的唯一标识: 当且仅当两个对象继承自同一个原型对象时,他们才是属于同一个类的实例。即类的识别是通过原型来完成而不是构造函数的名字。构造函数是类的外在表现,通常构造函数的名字用作类名

定义类的步骤

  1. 定义构造函数,设置初始化新对象的实例属性

  2. 给构造函数的 prototype 对象定义实例的方法

  3. 给构造函数定义类字段和类属性

原型链

1
2
3
4
5
// 1. 只要是对象就 __proto__ 原型,指向原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype); // true
// 2. 我们 Star 原型对象里面的 __proto__ 原型指向的 Object.prototype
console.log(Object.prototype.__proto__); // null

Object

是最通用的类。构造函数和实例的原型对象的原型对象指向Object的原型对象(Object.prototype),Object的原型对象的原型对象为空。(所有通过 new Object 创建的对象都具有同一个原型对象,即Object.prototype.Object.prototype是没有原型的对象)

也就是对象实例可以调用 Object 的 prototype 中的属性方法

实例对象属性方法的查找规则

  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  • 如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)。
  • 如果还没有就查找原型对象的原型(Object的原型对象).
  • 依此类推一直找到Object为止 (null)。
  • __ proto__ 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
1
2
3
4
5
6
7
8
9
10
function Star(uname,age) { 
this.uname = uname;
this.age = age;
}
Star.prototype.sex = 'woman';
// Object.prototype.sex = 'nomannowoman';
var ldh = new Star('刘德华',18);
// Star.sex = 'man';
// ldh.sex = 'man';
console.log(ldh.sex);

先查找实例对象中有没有查找的值,没有则到其原型对象中查找,再没有就到 Object 的原型对象中查找,如果再没有则返回 undefined

当写入一个属性的值时,js不会使用原型对象,因为这样会影响其他同个构造函数创建的对象

如果 o 对象的原型中有了 p 属性,给 o 对象设置 p 属性时只是直接在 o 对象中创建p属性,而不再继承原型中的 p属性,也就是 o 的 p 遮盖了原型对象中的 p。也就是说,JS 中只有查询属性时才体会到继承的存在,而设置属性和继承无关,属性的设置永远都是设置在目标对象中。

原型对象中 this 的指向

原型对象中的 this指向调用者

1
2
3
4
5
6
7
8
9
10
11
12
var that; 
function Star(uname,age) {
this.uname = uname;
this.age = age;
}
Star.prototype.sing = function() {
console.log('我会唱歌');
that = this;
}
var ldh = new Star('刘德华',18);
ldh.sing();
console.log(that === ldh); // true

扩展内置对象方法(不建议这么做!)

可以通过给原型对象追加方法,实现内置对象方法的扩展。如 Array 数组对象追加求和的方法

1
2
3
4
5
6
7
Array.prototype.sum = function() { 
var sum = 0;
for(var i =0;i < this.length;i++) {
sum += this[i];
}
return sum;
}

不可以使用下面形式(因为会覆盖内置的方法)

1
2
3
4
5
6
7
8
9
Array.prototype = { 
sum: function() {
var sum = 0;
for(var i =0;i < this.length;i++) {
sum += this[i];
}
return sum;
}
}

ES5的继承

call函数
    fun.call(thisArg, arg1, arg2, ...)
  • thisArg : 当前调用函数this的指向对象
  • arg1 , arg2 : 传递的其他参数
1
2
3
4
5
6
7
8
9
10
function fn(){
console.log("哈哈哈")
console.log(this);
}

var o = {
name : "zykj"
}

fn.call(o);
属性的继承利用call函数

ES6 前的继承就是在子构造函数中利用 call 引用父构造函数,并改变 this 指向为子构造函数的实例

调用这个函数并且修改函数运行时的this指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.父构造函数
function Father(uname,age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2.子构造函数
function Son(uname,age,score) {
// this 指向子构造函数的对象实例
Father.call(this,uname,age);
this.score = score;
}
var son = new Son('刘德华',18,100);
console.log(son);

注意: 可以不断嵌套

1
2
3
function Grandson(uname,age,score) { 		
Son.call(this,uname,age,score);
}
方法的继承

子类.prototype = new 父类()

确保子类的原型对象是父类的一个实例即可

1
2
3
Son.prototype = new Father(); 
//Son 的原型对象的 constructor 指回原来的(构造函数) Son
Son.prototype.constructor = Son; // 即 Son.prototype.__proto__.constructor = Son;

切记: Son 的原型对象的 constructor 指向要改回为 Son

类的判别方法
  1. instanceof

    基于原型链检测,而不是构造函数的名字

    缺点: 无法通过对象获得类名,只能检测对象是否属于指定的类名。在多窗口和多框架子页面的Web应用中兼容性不佳,如一个页面中的数组不是另一个页面中数组的实例

  2. constructor属性

    缺点与 instanceof 中一样,在多个执行上下文的场景中无法正常工作

  3. 利用构造函数的名称

    利用Object.toString方法获取然后裁剪出来

  4. 鸭式辨型

    如果该对象出现与某个同名的方法,那么就认为该对象属于这个类

ES5新增对象方法

  1. Object.keys() 用于获取对象自身所有的属性

    1
    Object.keys(obj)
    • 效果类似for...in
    • 返回一个由属性名组成的数组
  2. Object.defineProperty() 定义对象中新属性或修改原有的属性。

    1
    object.defineProperty(obj, prop, descriptor)
    • obj: 必需。目标对象

    • prop: 必需。需定义或修改的属性的名字

    • descriptor: 必需。目标属性所拥有的特性

      Object.defineProperty()第三个参数 descriptor 说明: 以对象形式{}写

    • value: 设置属性的值默认为 undefined

    • writable: 值是否可以重写。true | false 默认为 false

    • enumerable: 目标属性是否可以被枚举(遍历)。true | false 默认为 false

    • configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false

      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
       var obj = {
      id:1,
      name:'zykj'
      }

      Object.defineProperty(obj,'num',{
      value: 100
      });

      Object.defineProperty(obj,'name',{
      value: 'skx'
      });

      Object.defineProperty(obj,'id',{
      //设置为false代表不能修改
      writable: false
      });

      Object.defineProperty(obj,'price',{
      value: 15,
      // 默认为 false 代表不能被遍历
      enumerable: false,
      // 默认为 false 代表不能删除这个属性和修改这个参数的特性
      configurable: false
      });

      //delete obj.price // 错误

      console.log(obj);

函数进阶

函数定义方式
  1. function 函数名(){}

  2. var 函数名 = function(){}

  3. var 函数名 = new Function('参数1','参数2','函数体');(了解即可,这种方法创建的函数并不适用词法作用域)

    1
    var f = new Function('a','b','console.log(a + b)');
  4. 函数的增强写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //原本
    const fun2 = {
    getName: function(){

    }
    }

    //增强写法
    const fun2 = {
    getName(){

    }
    }

注意:

  1. Function里面必须是字符串格式

  2. 效率较低,较少使用

  3. 所有函数都是Function的实例对象

img

函数调用方式
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
// 1.普通函数 
function fn() {
}
fn();
fn.call();

// 2.对象的方法
var o = {
sayHi: function() {
console.log('Hi');
}
}
o.sayHi();

// 3.构造函数
function Star() {
}
new Star();

// 4.绑定事件函数
btn.onclick = function() { }

// 5.定时器函数
setInterval(function() {},2000);

// 6.立即执行函数(立即调用) (function() {})()
函数内 this 指向
调用方式this 指向
普通函数调用window
构造函数调用实例对象 函数原型对象里面的方法也指向实例对象
对象调用方法该方法所属对象
事件绑定方法绑定事件对象
定时器函数window
立即执行函数window
函数内改变 this 指向方法
  1. call 方法

    • 函数.call(this指向的对象,参数1,参数2,。。。)

    • 作用: 调用该函数并且将其this指向修改

    1
    2
    3
    4
    5
    6
    7
    function fn(x,y) { 
    console.log('123');
    console.log(this);
    console.log(x + y);
    }
    var o = { name: 'andy' } ;
    fn.call(o,1,3); // 将fn函数的this指向改为o
  2. apply 方法

    函数名.apply(this指向的对象,[参数1,参数2,。。。])

    同样会调用函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function fn(a,b) { 
    console.log(this);
    console.log(a + b);
    }
    fn.apply(o,[1,2]);

    var arry = [1,66,3,99,4];
    var max = Math.max.apply(Math,arry);
    console.log(max);

    注意:

    • 参数必须是数组形式,返回值会自动改为需要的形式
  3. bind 方法

    函数名.bind(this指向的对象,参数1,参数2...)

    不会调用函数,而是返回一个改造后的原函数的拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var o = { 
    name: 1
    }
    function fn(a,b) {
    console.log(this);
    console.log(a + b);
    }
    var f = fn.bind(o);
    f(1,2);

    注意: 因为不会调用原函数,所以要新建变量来接收改造后的函数

    使用例子

    1
    2
    3
    4
    5
    6
    7
    var btn = document.querySelector('button');
    btn.onclick = function() {
    btn.disabled = true;
    setTimeout(function() {
    this.disabled = false;
    }.bind(this),3000)
    }
总结

相同点:

  1. 都可以改变函数内部的 this 指向.

区别点:

  1. call 和 apply 会调用函数并且改变函数内部 this 指向,
  2. call 和 apply 传递的参数不一样, call 传递参数arg1, arg2…形式 apply 必须数组形式 [arg]
  3. bind 不会调用函数,可以改变函数内部 this 指向

主要应用场景:

  1. call 经常做继承
  2. apply 经常跟数组有关系,比如借助于数学对象实现数组最大值最小值
  3. bind 不调用函数但是还想改变this指向,比如改变定时器内部的 this 指向.
高阶函数

对其他函数进操作的函数,接收函数作为参数或将函数作为返回值输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
function fn(callback){
callback && callback();
}

fn(function(){
alert('hi')
})
</script>

<script>
function fn(){
return function(){}
}
fn();
</script>

函数作为一种数据类型,也可以作为参数传递给另一个函数使用,最典型的就是作为回调函数

  • filter

    数组.filter(function(currentValue,index){})

    实现数组中满足某条件的数值的筛选,返回一个新数组

    个人理解:通过返回值的真假来判断是否添加到一个新的数组中

    1
    2
    3
    4
    5
    6
    let arr = [12,66,4,88,3,7]; 
    let arr1 = arr.filter(function(value,index) {
    // return value >= 20;
    return value % 2 === 0; // 实现筛选偶数
    });
    console.log(arr1); // [12, 66, 4, 88]
  • reduce

    reduce(function(total, currentValue, currentIndex, arr), initialValue)

    参数描述
    total必需。初始值, 或者计算结束后的返回值
    currentValue必需。当前元素
    currentIndex可选。当前元素的索引
    arr可选。当前元素所属的数组对象。
    initialValue可选。传递给函数的初始值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    let arr4 = [1,2,3,4,5,6] 
    const sum = arr4.reduce((prev,item) => {
    console.log(item);
    // console.log(prev);
    return item + prev;
    },0) // 2 3 4 5 6
    console.log(sum); // 21

    /**
    解释
    第一次 prev: 0 item: 1
    第二次 prev: 1 item: 2
    第三次 prev: 3 item: 3
    第四次 prev: 6 item: 4
    第五次 prev: 10 item: 5
    第五次 prev: 15 item: 6
    最后结果为 21
    **/
  • map

    数组.map(function(currentValue,index)

    改方法按照原始数组元素顺序依次处理元素、返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值

    1
    2
    3
    4
    5
    let arr = [12,66,4,88,3,7]; 
    let arr2 = arr.map(function(value,index) {
    return value *2 ; // 实现数据*2
    });
    console.log(arr2); // [24, 132, 8, 176, 6, 14]

严格模式(IE10以上版本支持)

内容

  1. 消除了 Javascript 语法的一些不合理不严谨之处,减少了一些怪异行为。
  2. 消除代码运行的一些不安全之处,保证代码运行的安全。
  3. 提高编译器效率,增加运行速度。
  4. 禁用了在 ECMAScript 的未来版本中可能会定义的一-些语法,为未来新版本的 Javascript 做好铺垫。比
    如一些保留字如: class, enum, export, extends, import, super不能做变量名

两种形式及其开启方式

为脚本开启严格模式
  1. 在脚本开头添加'use strict'

    1
    2
    3
    4
    <script type="text/javascript"> 	
    // 开启脚本严格模式 下面的js代码会按照严格模式执行代码
    'use strict';
    </script>
  2. 在立即执行函数的函数体开头添加'use strict'

    1
    2
    3
    4
    5
    <script type="text/javascript"> 
    (function() {
    'use strict';
    })();
    </script>

    注: 此时所有的代码按照严格模式执行

为函数开启严格模式

在函数的函数体开头添加'use strict'

1
2
3
4
5
6
7
8
9
<script type="text/javascript"> 	
// 只为fn开启严格模式
function fn() {
'use strict';
// 下面代码按照严格模式执行
}
function fun() {
}
</script>

注: 此时只对 fn 执行严格模式,其中的函数体按照严格模式执行

严格模式中的变化

变量规定
  • 变量必须先声明再使用

    1
    2
    3
    'use strict'
    num = 10; //错误
    var num = 10;
  • 严禁删除已经声明的变量

    1
    2
    'use strict'
    delete num; //不能删除已经声明好的变量
this 指向问题
  • 全局作用域中 this 的指向为 undefined

    1
    2
    3
    4
    'use strict'
    function x() {
    console.log(this); // undefined
    }
  • 定时器的 this 依旧为 window

  • 严格模式构造函数不加 new 调用,this 会指向 undefined

函数的变化
  • 函数的形参不能有重名
  • 不允许在非函数(如 if , for) 代码块中定义函数
对象的变化
  • 对象中不能有同名的属性

闭包

指一个嵌套函数被导出到他所定义的作用域外访问其外部函数的参数和变量,即某个作用域可以访问另一个函数内部的局部变量。也可理解为一种某个函数可以调用其他函数中变量的现象

原理

词法作用域: 函数是通过词法来划分作用域的,而不是动态的划分作用域。他们在定义的位置运行而不是调用的位置运行

一个定义在函数 f 中的函数 g。当f被调用的时候,作用域链包含了对 f 的这一调用的调用对象,后边是全局对象。g 定义在f 中,因为 f 调用时才会定义 g,所以这个作用域链接保存为 g 的定义的一部分。g 被调用时,作用域链包括3个对象: g 自己的调用对象,f 的调用对象和全局对象

当嵌套函数的引用放到全局作用域中时,这种情况下有一个对嵌套的函数的外部引用,并且嵌套的函数将它的引用保留给外围函数的调用对象,结果就是外围函数的一次特调用的调用对象依然存在,外围函数的参数和局部变量的名字和值在这个调用对象中得以维持,而他又是嵌套函数定义的一部分,所以嵌套函数的调用可以访问这些值

如:

1
2
3
4
5
6
7
8
function fn() { 
var num = 20;
function fun() {
console.log(num);
}
fun();
}
fn();

调用的这个变量所在的函数被称为闭包函数,如上面的 fn 函数

注意: 闭包是为了调用一个变量,而不是他的值!!!

闭包的主要作用

作用:延伸了变量的作用范围

最常用的闭包还是函数中的函数调用其父函数的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function fn() {
var num = 10;
function f() {
console.log(num);
}
return f;
// 相当于
//return function f() {
//console.log(num);
//}
}

var a = fn();
a();

经典案例

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
<div class="nav">
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
</div>

<script>
var lis = document.querySelector('.nav').querySelectorAll('li');

// 1.传统方式获取li索引
for(var i = 0;i < lis.length;i++){
lis[i].index = i;
lis[i].onclick = function (){
console.log(this.index);
}
}

// 2.闭包方式式获取li索引
for(var i = 0;i < lis.length;i++){
// 立即执行函数称为小闭包
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i)
}

// 3. 三秒后打印里的内容
for(var i = 0;i < lis.length;i++){
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML)
},2000)
})(i)
}

// 经典案例
var car = (function() {
// 起步价
var start = 5;
// 总价
var total = 0;
return {
price : function(n) {
if(n<=3){
total = start;
}else{
total = start + (n - 3) * 5;
}
},
yd : function(flag) {
return flag ? total + 10 : total;
}
}
})();

car.price(5);
car.yd(true);
</script>

递归函数

如果一个函数在内部可以调用其本身,就称为递归函数

  • 递归函数的作用和循环效果一样

  • 由于递归很容易发生栈溢出错误(即不停地新建栈区域),所以必须要添加退出条件 return

  • 当遍历多层次的数据时可以用递归函数来遍历

剩余参数(用来代表剩余形参的参数 …x)

当使用箭头函数时,无法使用 arguments 代替数量不定的参数,必须用剩余参数 …args(一定要加3点,可以任意命名)

1
2
3
4
5
6
7
8
const sum = (...args) => { 
var sum = 0;
args.forEach(function(value) {
sum += value;
})
return sum;
}
console.log(sum(10,20,30)); // 60

用法与 arguments 基本相同,都是数组形式


剩余参数与数组解构的搭配使用(即可以用任意命名剩余参数代替参数中的剩余部分)

1
2
3
4
5
let arr1 = [1,2,3,4,5,6]; 
let [s1,s2,...s3] = arr1;
console.log(s1); // 1
console.log(s2); // 2
console.log(s3); // 3456

正则表达式

正则表达式(regular expression)。用于匹配字符串中字符组合的模式,在 js 中也是对象

作用

用来检索、替换那些符合某个模式(规则)的文本。亦可用于过滤页面内容的一些敏感词,或从字符串中提取我们想要的特定部分

正则表达式在js中的使用

创建方式
  1. 利用 RegExp 构造函数创建( ES5 中用这个方法创建两个字面量相同的对象不是同个对象)

    new RegExp(/xxxx/) 一定要加 //

    1
    var regexp = new RegExp(/123/);
  2. 利用字面量创建

    var x = /xxx/;

    1
    var a = /zykj/;
测试正则表达式 test

正则表达式.test(被测字符串): 正则对象的方法,用于检测字符串是否符合该规则,返回布尔值

1
2
var rg = /123/; 
console.log(rg.test(123)); // 返回true
正则表达式构成:

正则表达式可以由简单的字符组成,如 /abc/ ,也可以是简单和特殊的字符组成,比如 /ab*c/ ,其中特殊字符被称为元字符,在正则表达式中是具有特殊意义的专用符号

锚字符(用来提示字符所处位置)

边界符说明
^表示匹配行首的文本(以谁开始)
$表示匹配行尾的文本(以谁结束)
说明
\b匹配一个单词的边界
\B匹配非单词边界的位置
(?=p)零宽正向先行断言,要求接下来的字符都与 p 匹配,但不能包括匹配 p 的字符
(?!p)零宽负向先行断言,要求接下来的字符不与 p 匹配

^用法:

1
2
3
4
var reg = /^abc/;  	
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false

$用法:

1
2
var reg2 = /ab$/; 	
console.log(reg2.test('ssab'));

/^xxxx$/: 表示精确匹配,只能是xxxx出现不能有其他字符

1
2
3
4
5
var  reg1 = /^abc$/; // 如果是^xx$ 则为精确匹配,要求必须是abc字符串才符合规范 
console.log(reg1.test('abc')); // true
console.log(reg1.test('abcd')); // false
console.log(reg1.test('aabcd')); // false
console.log(reg1.test('abcabc')); // false

\b: 匹配词语的边界,可以代替 \s 实现匹配字符串前后的空格,而不用是字符串一定得有空格才能匹配

1
2
3
var txt = 'language java'; 
var reg = /\bjava\b/;
console.log(reg.test(txt)); // true

\B: 匹配非词语边界的位置

/\B[Ss]cript/"JavaScript""postscript" 匹配,但不与 "script""Scripting" 匹配

(?=p): 正向前声明,要求接下来的字符都与模式 p 匹配,但是不包括匹配中的那些字符

1
2
3
var txt = 'JavaScript:the definitive guide'; 
var reg = /[Jj]ava([Ss]cript)?(?=\:)/;
console.log(reg.test(txt));

(?!p): 反向前声明,要求接下来的字符不与模式p匹配

字符类

将单独的直接量字符放进方括号内就可以组成字符串类

[]用法:

[abc]: 表示只要包含有abc其中一个就返回 true

1
2
3
console.log(rg.test('andy')); // true 
console.log(rg.test('color')); // true
console.log(rg.test('google')); // false

/^[abc]$/: 三选一 只包含 a 或 b 或 c 单个字符的才返回 true

1
2
3
4
5
console.log(rg1.test('aac')); // false 
console.log(rg1.test('a')); // true
console.log(rg1.test('b')); // true
console.log(rg1.test('c')); // true
console.log(rg1.test('abc')); // false

/^[a-z]$/: 加了 - 号,表示 26 个英文字母返回任何一个 true

1
2
3
4
5
console.log(rg2.test('a')); // true 
console.log(rg2.test('z')); // true
console.log(rg2.test(1)); // false
console.log(rg2.test('A')); // false
console.log(rg2.test('as')); // false

/^[a-zA-Z0-9_-]$/: 26 个英文字母(大写和小写都可以)任何数字以及两个符号返回任何一个 true

1
2
3
4
console.log(rg3.test('adada'));// false 
console.log(rg3.test('_')); // true
console.log(rg3.test('A')); // true
console.log(rg3.test(1)); //true

/^[^a-zA-Z0-9_-]$/: 中括号里的 ^ 为取反的意思,与边界符不同

1
2
3
4
console.log(rg4.test('adada')); // false 
console.log(rg4.test('_')); // false
console.log(rg4.test('A')); // false
console.log(rg4.test(1)); // false
量词符(设定某个模式出现的次数)
量词量词
*重复零次或更多次
+重复一次或更多次
?零次或一次
{n}重复 n 次
{n,}重复 n 次或更多次
{n,m}重复 n 到 m 次

都是加在规定字符后面

*用法: 相当于a>=0 可以出现0次或者很多次

1
2
3
4
5
var reg = /^a*$/; // 只允许出现a,a的次数可以是0或者多次 
console.log(reg.test('')); // true
console.log(reg.test('a')); // true
console.log(reg.test('aaaaa')); // true
console.log(reg.test('aaaabbbb')); // false

+用法: 相当于>=1 可以出现1次或者很多次

1
2
3
4
var reg1 = /^a+$/; //只允许出现a,a的次数可以是1次或者多次 
console.log(reg1.test(''));// false
console.log(reg1.test('ab'));// false
console.log(reg1.test('aaaaaaaaaaaa')); // true

?用法: 相当于 1 | 0 只能出现1次或0次

1
2
3
4
var reg2 = /^a?$/; // a出现1次或0次为true 其他为false 
console.log(reg2.test('')); // true
console.log(reg2.test('a'));// true
console.log(reg2.test('aaaa')); // false

利用 ? 和其他重复字符可以组成非贪婪的重复

例如/a+/与字符串"aaa"的三个都匹配,而/a+?/只与第一个 a 匹配

{x}用法: 相当于重复 3 次为 true

1
2
3
4
5
var reg3 = /^a{3}$/; // a重复3次为true 
console.log(reg3.test('')); // false
console.log(reg3.test('a'));// false
console.log(reg3.test('aaaa'));// false
console.log(reg3.test('aaa'));// true

{x,}用法: 相当于重复大于等于 x 次为 true

1
2
3
4
5
6
var reg3 = /^a{3,}$/; // a出现大于3次为true 
console.log(reg3.test('')); // false
console.log(reg3.test('a')); // false
console.log(reg3.test('aaaa')); // true
console.log(reg3.test('aaaaa'));// true
console.log(reg3.test('aaa'));// true

{x,y}用法: 重复大于等于 3 次小于等于 y 次为 true ,输入时中间不能有空格

1
2
3
4
5
var reg4 = /^a{3,6}$/; 
console.log(reg4.test('')); // false
console.log(reg4.test('a')); // false
console.log(reg4.test('aaaa')); // true
console.log(reg4.test('aaaaaaa')); // false
选择分组引用
说明
| 符号选择,匹配的是该符号左边的子表达式或者右边的子表达式
(...)组合,将几个项组合成为一个单元,可通过 * + ? \ | 等符号加以修饰,可以被引用
(?:...)只组合,把项组合到一个单元,但不记忆与该组相匹配的字符,不可被引用
\n和第 n 个分组第一次匹配的字符串相匹配,组是圆括号里的子表达式,从左向右算起。不包括(?: )的分组

选择: | 符号

1
2
3
4
var reg = /ab|cd|ef/;
console.log(reg.test("ab")); // true
console.log(reg.test("cd")); // true
console.log(reg.test("ef")); // true

注意: 选择项的匹配是从左到右,直到发现了匹配项。如果左边的匹配则忽略右边,即使它产生更好的匹配

小括号的作用:

  1. 把单独的项组合成子表达式,以便可以像处理一个独立的单元那样用 | 、* 、+ 或者 ?等来对单元内的项进行处理。例如 /Java(script)?/ 可以匹配字符串 “java”,其后可以有 “script” 也可以没有。

    1
    2
    3
    var reg = /Java(script)?/; 
    console.log(reg.test('Java'));
    console.log(reg.test('Javascript'));
  2. 第二个作用是引用。即允许我们在同一正则表达式后部引用前面的子表达式。通过\后面加数字实现,数字指定了带括号的子表达式在正则表达式中的位置。这个引用不是引用模式,而是引用与那个模式匹配的文本

    例:

    1
    2
    3
    var txt = '232'; 
    var reg = /([12])[3]*\1/;
    console.log(reg.test(txt));
    1
    2
    3
    4
    var reg = /^(abc){3}$/; 
    console.log(reg.test('abc')); // false
    console.log(reg.test('abcabcabc')); // true
    console.log(reg.test('abccc')); // false

    注: 不能在字符类中使用这种引用。

括号总结

  1. 大括号量词符里面表示重复次数
  2. 中括号字符集合。匹配方括号中的任意字符
  3. 小括号表示优先级

可以在线测试

预定义类(某些常见模式的预定义类)
预定类说明
\d匹配 0-9 之间的任一数字,相当于[0-9]
\D匹配所有 0-9 以外的字符,相当于[^0-9]
\w匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]
\W除所有字母、数字和下划线以外的字符,相当于[^A-Za-20-9_]
\s匹配空格(包括换行符、制表符、空格符等),相等于[\t\r\n\v\f]
\S匹配非空格的字符,相当于[^ \t\r\n\v\f]
1
var reg = /^\d{3}-\d{8} | \d{4}-\d{7}$/; // 实现座机号码验证

注意: 正则表达式里可以用或符号,但是符号为|而不是||

敏感字的替换

replace(regexp/substr,replacement)

replace 除了可以替换字符串也可以替换正则表达式

目标字符串.replace(/xxx/,'yy') 将字符串中的 xxx 替换为 yy

参数1

replace(/表达式/[switch],替换的内容)

正则表达式的参数(写在/后面):

  • g: 全局匹配,可以匹配多个

  • i: 忽略大小写

  • gi: 全局匹配+忽略大小写

1
div.innerHTML = text.value.replace(/激情|gay/g,'**'); // 实现将文本中所有关键字替换

注意: 不能用精确匹配模式!!!!!

参数2

$1, $2, $3, …, $n: 依次匹配子表达式

1
2
3
var sStr='讨论一下正则表达式中的replace的用法';
sStr.replace(/(正则)(.+?)(式)/,"《$1》$2<$3>");
// 得到:"讨论一下《正则》表达<式>中的replace的用法"

函数

先看arguments的用法:

1
2
3
4
5
var sStr='讨论一下正则表达式中的replace的用法';
sStr.replace(/(正则).+?(式)/,function() {
console.log(arguments);
});
// ["正则表达式", "正则", "式", 4, "讨论一下正则表达式中的replace的用法"]

参数分别为:

  • 匹配到的字符串(此例为”正则表达式”)
  • 如果正则使用了分组匹配就为多个否则无此参数。(此例的参数就分别为 “正则”, “式”)
  • 匹配字符串的对应索引位置(也就是”正则表达式”的索引位置,此例为4)
  • 原始字符串

for … in

for...in是ES5标准,用来遍历对象和数组, 返回键名key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 遍历对象
let obj = {
a: 1,
b: 2,
c: 3
}
for (let key in obj) {
console.log(key)
} // a b c

// 遍历数组
let arr = [1, 2, 3]
for (let key in arr) {
console.log(key)
} // 0 1 2

ES6

let 和 const

let

let: 用于声明变量

特点
  1. 只在块级作用域(即{})中起作用,而 var 不具备这个特点

    1
    2
    3
    4
    5
    6
    7
    if(true) { 	
    var b = 20;
    if(true) {
    let c =30;
    }
    console.log(c); // 报错
    }
  1. 没有变量提升,即必须先声明再使用

    1
    2
    console.log(a); 
    let a = 10; // 报错
  2. 具有暂时性死区特性

    在块级区域用 let 声明的变量会与该区域绑定,与区域外同名变量的使用互不影响

    ES6中,在代码块内,在用 let 和 const 声明变量之前,该变量都是不可用的,不可用的区域就叫做暂时性死区(temporal dead zone,TDZ)

    1
    2
    3
    4
    5
    6
    7
    8
    if(true) {    
    //TDZ开始
    k = 5;
    console.log(k); // 皆报错
    let k = 4; // TDZ结束
    console.log(k); // 4
    }
    console.log(k); // 10

    暂时性死区的本质: 再进入一个代码块的时候,变量就已经存在,但是无法引用,只有 let 或者 const 声明时,才能使用

  3. 不允许重复声明

    不允许在同一个作用域中重复声明同一个变量

let经典面试题

img

输出 2 2

img

输出 0 1

区别在于 for 循环中的i如果是 var 声明,则为全局的。如果是 let ,则有了局部作用域,每一次循环都是一个新的 i 值

注意: for 循环设置变量的部分是一个父作用域,而循环体内部是一个单独的子作用域

证明:

1
2
3
4
for(let i = 0; i < 3 ;i ++) { 
let i = 'abc';
console.log(i);
}// abc abc abc

输出的是 3 次 abc ,说明花括号内部的作用域和循环设置内部的作用域不是同一个,如果是同一个,会因为 let 不能重复声明而报错

const

const: 用于声明常量

特性
  1. 具有块级作用域,即只在其所在中括号内生效

  2. const 声明的常量必须赋予初始值

  3. 不存在变量提升

  4. const 赋值后,值/引用不能修改(即地址不能修改),不可重复声明

注意: 对于简单数据类型 num ,string 等,不能修改其中的值

1
2
const p = 3.14;
p = 20; // 报错

而对于复杂数据类型,数组等,其中的值可以修改,但不能修改这个常量的整体,因为前者不会改变地址,后者则会改变地址

1
2
3
4
const arr = [100,200]; 
arr[0] = 50; // 正确
arr = [1,2];
console.log(arr);// 报错

const 本质: const 不是让变量的值不能改动,而是让变量指向的地址不能改动。对于值类型的数据,其值就保存在变量指向的地址中,所以无法改变,是一个常量。而对于引用数据类型,其变量指向的地址保存的不是值而是一个指针,这个指针指向堆中存放的具体值,所以复杂数据类型的具体值可以改变

总结

let , const , var 的区别

  1. 使用 var 声明的变量,其作用域为该语句所在的明数内,存在变量提升现象。
  2. 使用 let 声明的变量,其作用城为该语句所在的代码块内,不存在变量提升,
  3. 使用 const 声明的是常量,在后面出现的代明中不能再修改该常量的值。
varletconst
函数级作用域块级作用域块级作用域
变量提升不存在变量提升不存在变量提升
值可更改值可更改值不可更改

一般常量用 const ,效率比较高

加上 es6 , js 一共有六种声明变量的方法: var function let const import class

解构赋值

ES6 允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构,结构什么用就用对应的括号

  • 数组解构(用中括号)

    1
    2
    3
    4
    5
    let ary = [1,2,3]; 
    let [a,b,c] = ary;
    console.log(a);
    console.log(b);
    console.log(c);
  • 只要右边数据类型具有 Iterator 接口,就可以用数组解构

    1
    2
    const [a,b,c] = 'wwe'; 
    console.log(a,b,c);// w w e
  • 允许给左边指定默认值

    1
    2
    3
    let [a,b=true] = [1]; 
    console.log(a);// 1
    console.log(b);// true
  • 默认值生效的条件是右边对应值严格等于(===) undefined,否则不生效

    1
    2
    3
    let [a,b=true] = [1,null]; 
    console.log(a); // 1
    console.log(b);// null 因为null不等于undefined
  • 允许把默认值设为函数,但是函数执行的条件是右边对应位置没有赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function fn() { 
    return 'aaa';
    }
    let [k=fn()] = [,2];
    console.log(k); // aaa
    function fn() {
    return 'aaa';
    }
    let [k=fn()] = [1];
    console.log(k); // 1
  • 默认值也可以引用解构赋值中的其他变量,但是前提是该变量已经声明

    1
    2
    3
    4
    5
    6
    7
    8
    let [a=1,b=a] = [1];
    console.log(a,b); // 1 1
    let [x = 1,y = x] = [];
    console.log(x,y);// 1 1
    let [a = 1,b = a] = [1,2];
    console.log(a,b) // 1 2
    let [m = n,n = 1] = [];
    console.log(m,n);// n is not defined

    总之,只要记住数组解构的顺序是从左到右,先默认,再赋值

对象解构(用大括号)

第一种方式:

1
2
3
4
5
let person = {name:'list',age:30,sex:'nan'}; 
let {name,age,sex} = person;
console.log(name); // list
console.log(age); // 30
console.log(sex); //nan

注意: let 的属性名必须和对象中的属性名一样,本质是下面的简写

1
let {name:name,age:age} = {name:'bruce',age:20};

第二种方式:

1
2
3
4
let {name:myName,age:myAge,sex:mySex} = person;
console.log(myName);
console.log(myAge);
console.log(mySex);

注意: let 中属性名后的值可以任意命名,myName 可以改为其他

本质: 对象解构本质上只有第二种方式,第一种只是简写了。其内部机制是先找到同名属性,再赋值给属性中的变量。前者只是个名字,用于匹配。后者才是接收值的变量。所以对象解构赋值中属性名顺序不一定要与右边对象属性名顺序一致

也可以嵌解构赋值

1
2
3
4
5
6
7
8
9
let obj = { 
p: [
'hello',
{y:'world'}
],
}

let {p:[x,{y}]} = obj;
console.log(x + y);

对象解构也可以赋予默认值,生效条件与数组的一致

1
2
3
4
let {a = 1} = {a:undefined};
console.log(a); // 1
let {b = 2} = {b:null}
console.log(b); // null

使用对象解构,如果右边是基本数据类型,则将其转为包装类型,可以用同名属性获取其中内置的方法属性

1
2
3
4
5
6
let {k} = 1;  
console.log(k); // undefined
let {length} = 'heihei';
console.log(length); // 6
let {__proto__} = [1,2,3];
console.log(__proto__); // {}

结构赋值右侧的数组所包含的元素不必和左侧的变量一一对应。左侧多余的变量的赋值为 undefined ,而右侧多于的值则会忽略。左侧的变量列表可以包含连续的逗号用以跳过右侧对应的值.此外解构赋值也适用于数组嵌套的情况,但是格式应当相同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var [a,b,c] = [1,2]; // 左边对于右边
console.log(a); // 1
console.log(b); // 2
console.log(c);// undefined
var [a,b,c] = [1,2,3,4];//右边多于左边
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
var [a,,b,,c] = [1,3,5,7,9];// 逗号跳过
console.log(a); // 1
console.log(b); // 5
console.log(c); // 9
var [a,[b,c],d] = [1,[3,4],5]; // 数组嵌套
console.log(a); // 1
console.log(b); // 3
console.log(c); // 4
console.log(d); // 5

函数参数的解构赋值

1
2
3
4
5
6
7
8
function get([a,b,c,d]) { 
console.log(a,b,c,d);
}
get([1,2,3,4]);
function Obj({name,age}) {
console.log(name,age);
}
Obj({name:'bruce',age:18});

扩展运算符(…变量)

可以将数组、Map 、Set、元素集合、arguments、字符串等具有 Iterator 接口的可遍历数据结构转换成逗号分隔的形式,可应用于形参或数组合并中

也可以将数组转变为非数组形式

  1. 应用于函数中

    1
    2
    3
    4
    5
    6
    let ary = [1,2,3]; 
    console.log(...ary);
    function add(a,b,c) {
    console.log(a + b + c);
    }
    add(...ary);
  2. 数组合并

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //方法1:  
    let ary1 = [1,2,3];
    let ary2 = [2,3,4];
    let ary3 = [...ary1,...ary2];
    console.log(ary3);

    //方法2:
    ary1.push(...ary2);
    console.log(ary1);
  3. 将伪数组转换成真正的数组

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div>1</div>
    <div>2</div>
    <div>3</div>
    <div>4</div>
    <script>
    var divs = document.querySelectorAll('div');
    console.log(divs instanceof Array); // false
    divs = [...divs];
    console.log(divs instanceof Array); // true
    </script>
  4. 将数组转为非数组

    1
    2
    let arr1 = [1,2,3]; 
    console.log(...arr1);//1 2 3

ES6新增方法

数组方法

ES5新增数组方法
  • 数组.forEach(function(currentValue,index,arr){})

    value: 为数组的值

    index: 为索引值

    arr: 为数组本身

    实现数组、伪数组、set 数据的遍历

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var arr1 = [1,2,3];
    var sum = 0;
    arr1.forEach(function(value,index,arr) {
    console.log(value);
    console.log(index);
    console.log(arr1);
    sum += value;
    })
    console.log(sum);
  • 数组.filter(function(currentValue,index,arr){})

    实现数组中满足某条件的数值的筛选,返回一个新数组

    参数值同 forEach

    1
    2
    3
    4
    5
    6
    var arr = [12,66,4,88,3,7]; 
    var arr2 = arr.filter(function(value,index) {
    // return value >= 20;
    return value % 2 === 0; // 实现筛选偶数
    });
    console.log(arr2);
  • 数组.some(function(currentValue,index,arr){})

    用于查找数组是否有满足条件的数值

    返回的是布尔值,如果有满足条件的值就返回 true ,否则为 false

    当查找到第一个满足条件的数值时就停止查找,退出函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var arr  = [10,30,4]; 
    var flag = arr.some(function(value){
    // return value > 20;
    return value < 3;
    })
    console.log(flag); // false
    var arr1 = ['red','pink','blue'];
    var flag1 = arr1.some(function(value) {
    return value == 'pink';
    })
    console.log(flag1); // true

    some 和其他两个区别: 利用 return true 可以使 some 停止迭代,forEach和filter不可以

  • 数组.every(function(currentValue,index,arr), thisValue)

    用于检测数组所有元素是否都符合指定条件(通过函数提供)、在判断是否全选中好用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 检查ages数组中的所有值是否为18或更高:
    var ages = [32, 33, 16, 40];

    function checkAdult(age) {
    return age >= 18;
    }

    function myFunction() {
    document.getElementById("demo").innerHTML = ages.every(checkAdult); // false
    }
ES6新增数组方法
  • Array.of(): 与Array()构造数组,区别在于对于单个整数参数,前者返回一个第一个值为参数值的数组,后者返回一个长度等于参数值的空数组

    1
    2
    3
    const arr1 = Array.of(7); 
    const arr2 = Array(7);
    console.log(arr1,arr2); // [7] [,,,,,,]
  • Array.from方法(Array.from 方法从一个类似数组(既具有length属性)或可迭代对象(对象、字符串)中创建一个新的,浅拷贝的数组实例)

    Array.from(目标对象,函数)

    转换对象时,对象里面的属性名应有双引号且必须是整数,一定要有length属性

    1
    2
    3
    4
    5
    6
    7
    8
    var arrayLike = { 
    "0":"zs",
    "1":"ls",
    "2":"ww",
    "length":3 // 一定要有length属性
    }
    var arr = Array.from(arrayLike);
    console.log(arr); // ['zs','ls','ww']

    转换伪数组

    1
    2
    3
    var weishuzu = document.querySelectorAll('div'); 
    var arr1 = Array.from(weishuzu);
    console.log(arr1 instanceof Array);

    转换字符串

    1
    2
    3
    var str = '123'; 
    var str1 = Array.from(str);
    console.log(str1);// [1,2,3]
  • Array.from: 中的函数用于对每个值进行操作

    方法还可以接受第二个参数, 作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。

    1
    2
    3
    4
    5
    6
    7
    let arrayLike2 = { 
    "0":1,
    "1":2,
    "length":2
    }
    let newary = Array.from(arrayLike2,item => item * 2);
    console.log(newary);

    所以将数据转为对象有两种方法:

    1. 使用扩展运算符

    2. 使用 Array.from 区别在于前者无法转换对象,后者可以

  • 数组.find

    array.find(function(item,index) {}): 返回符合函数中条件的第一个数值, item 是数组的每个值,index为其对应的索引值, 没有返回 undefined

    1
    2
    3
    let ary = [{ id:1, name:'zhangsan' }, { id:2, name:'ls' }]; 
    let target = ary.find((item,index) => item.id == 2);
    console.log(target);
  • 数组.findIndex

    array.findIndex(function(value,index){} ): find 返回第一个符合条件的数值的索引值, value 为值,index 为其索引值

    1
    2
    3
    let ary = [1,10,2,18]; 
    let ary1 = ary.findIndex((value,index) => value > 9);
    console.log(ary1);
  • 数组.includes

    array.includes(目标值): 返回的是布尔值

    1
    2
    console.log([1,2,3].includes(2)); // true 
    console.log([1,'a',3].includes('e')); // false
  • 数组.copyWithin 复制数组制定成员内容到数组内其他位置

    array.copyWithin(替换的目标的起始位置,替换内容的起始位置,替换内容的结束位置(不包括该位置))

    1
    2
    3
    let arr3 = [1,2,3,4,5,6,7,8]; 
    arr3.copyWithin(4,2,5);
    console.log(arr3); // [1,2,3,4,3,4,5,8]
  • 数组.fill 用给定内容替换数组内容

    array.fill(替换内容,替换开始位置,替换结束位置(不包括)): 默认全部替换

    1
    2
    3
    4
    5
    let arr2 = [1,2,3,4]; 
    arr2.fill('a');
    console.log(arr2); // [a,a,a,a]
    console.log(new Array(3).fill('a')); // [a,a,a]
    console.log(arr3.fill('1',3,5)); // [1,2,3,1,1,4,5,8]
  • reduce 迭代 直接从第二项开始迭代

    参数1:上一次迭代return的值

    参数2: 当前值

    1
    2
    3
    4
    5
    6
    7
    let arr4 = [1,2,3,4,5,6] 
    const sum = arr4.reduce((prev,item) => {
    console.log(item);
    // console.log(prev);
    return item + prev;
    }) // 2 3 4 5 6
    console.log(sum); // 21
  • reduceRight 跟 reduce一样 但是是从右边开始

  • keys 用于 for...of... 遍历数组每一项的索引值

    1
    2
    3
    4
    let arr6 = [1,2,3,4,5,6]; 
    for(let k of arr6.keys()) {
    console.log(k); // 0 1 2 3 4 5
    }
  • values 用于 for...of... 遍历数组每一项的值

    1
    2
    3
    for(let k of arr.values()) { 
    console.log(k); // 1 2 3 4 5 6
    }
  • entries 用于 for...of... 遍历数组每一项键值对 返回数值形式

    1
    2
    3
    for(let k of arr6.entries()) { 
    console.log(k); // [0,1] [1,2] [2,3] [3,4] [4,5] [5,6]
    }
  • 注意: 所有数组方法的结束位置参数都是不包括在内 即顾前不顾后

字符串扩展

模板字符串

利用反引号``定义字符串

1
let name = `woshizykj`;

特点:

  1. 可以解析变量

    1
    2
    3
    4
    let name = `zykj`; 
    console.log(name instanceof String);
    let sayHello = `hello my name is ${name}`;
    console.log(sayHello);
  2. 可以空行

    1
    2
    3
    4
    5
    6
    7
    let result = { name:'zd', age:18 }; 
    let html = `
    <div>
    <span>${result.name}</span>
    <span>${result.age}</span>
    </div>
    `;
  3. 可以调用函数

    1
    2
    3
    const fn = () => { return '我是fn函数'; } 
    let html1 = `我是模板字符串 ${fn()}`; // 我是模板字符串 我是fn函数
    console.log(html1);

注意: 调用外部变量和函数必须用 ${} , {} 中可以放入任意 js 表达式

String.raw 用来将模板字符串转义(包括变量)

1
2
let str = 'world'; 
console.log(String.raw`hello${str}`); // helloworld

三种确定字符串是否包含另外一个字符串方法

ES6提供了 三种新的用来确定一个字符串是否包含了另外一个字符串的方法(原先只有 indexOf) : startsWith、endsWith、includes

判断字符串开头结尾

字符串.startsWith('xxx'); : 判断 str 字符串是否以 xxx 开头

字符串.endsWith('xxx'); : 判断 str 字符串是否以 xxx 结尾

1
2
3
4
5
let str = 'hello myworld'; 
// startsWith 判断字符串是否以某字符串开头 返回布尔值
console.log(str.startsWith('he')); // true
// endsWith 判断字符串是否以某字符串结尾 返回布尔值
console.log(str.endsWith('rld')); // true

string.includes(查找字符,开始查找的位置): 判断字符串是否有某个字符

1
2
const str1 = 'hello world';
console.log(str1.includes('hello',2)); // false

以上三种方法都接受第二个参数,表示开始搜索的位置

重复某个字符串

返回新的字符串

字符串.repeat(n): 将字符串 str 重复 n 次,返回新的,不改变旧的

1
2
3
4
let str = 'x'.repeat(5); 
console.log(str);
str1 = str.repeat(2);
console.log(str1);

字符串补全

padStart(补全后长度,补全的字符串)

1
2
let str  = 'xxx'.padStart(5,'ab'); 
console.log(str);// abxxx

padEnd(补全后长度,补全的字符串)

1
2
let str1 = 'xxx'.padEnd(6,'abb'); 
console.log(str1);// xxxabb

若第一个参数小于原字符串长度,则返回原字符串

1
2
let str1 = 'xxx'.padEnd(1,'abbaaaaa'); 
console.log(str1);// xxx

若补全的字符串长度加上原字符串长度大于第一个参数,则会截除补全后的字符串多余的部分

1
2
let str1 = 'xxx'.padEnd(6,'abbaaaaa');
console.log(str1)// xxxabb

数值的扩展

ES6提供了二进制和八进制的新写法

  • 二进制: 0b(0B)开头

  • 八进制: 0o(0O)开头

1
2
console.log(0o767 === 503)// true 
console.log(0b011 === 3)// true

如果要将改写法的二进制和八进制转为十进制,则要调用Number方法

1
console.log(Number(0o767))// 503

Number.isFinite()、Number.isNaN()

Number.isFinite: 判断一个数值是否是有限的

Number.isNaN: 判断一个值是否是 NaN

这两个方法与传统的全局方法在于: 传统方法会先将非数值转为数值在进行判断。而新方法只对数值有效,对于非数值一律返回 false

ES6 还将parseIntparseFloat移植到了 Number 对象上面,行为保持不变

Number.isInteger(): 判断一个数值是否是整数。

1
2
3
4
5
console.log(Number.isInteger(1)); // true 
console.log(Number.isInteger('1')); // false
console.log(Number.isInteger(1.0)); // true
console.log(Number.isInteger(3.1)); // false
console.log(Number.isInteger('nu')); // false

注意: js中 1 和 1.0 都是同样的存储方式,所以被视为同一个值

函数的扩展

函数参数设置默认值

function xx(arg1=xx,arg2=yy,...){}

1
2
3
4
5
6
function Star(x=0,y=0) { 
this.x = x;
this.y = y;
}
let star = new Star();
console.log(star.x,star.y);

注意: 参数默认值是不传值的,每次调用都会重新计算默认值。一旦设置了默认值,函数进行声明初始化时,参数列表会形成一个作用域。


可以解构赋值默认值配合使用

第一种: 只有赋值结构有默认值

1
2
3
4
5
6
7
function foo({x,y=5}) { 
console.log(x,y);
}
foo(); // 报错
foo({}); // undefined 5
foo({x:5}); // 5 5
foo({x:5,y:6}); //5 6

第二种: 参数和赋值结构都有默认值

1
2
3
4
5
6
7
function fetch({x,y=5} = {}) { 
console.log(x,y);
}
fetch(); // undefined 5
fetch({}); // undefined 5
fetch({x:3}); // 3 5
fetch({x:3,y:10}); // 3 10

函数的length

函数参数如果指定了默认值,那么函数的参数只会计算第一个指定了默认值的参数前面的参数个数,第一个指定了默认值的参数及其后面的参数(不管有没有指定默认值)都不计入 length 属性中

1
2
let foo = function(x,y=2,z,k=1) {}
console.log(foo.length);// 1

rest参数

用于获取函数多余的参数,形式为…变量名,变量名代表的是一个数组变量

1
2
3
4
function foo(x,...args) { 
console.log(...args);
}
foo(1,2,3,4); // [2,3,4]
注意

rest参数必须是最后一个参数。函数的length不包括rest参数

name 属性

函数的 name 属性返回函数的名字

1
2
3
4
function Big() { 
return 'Big';
}
console.log(Big.name);// Big

箭头函数

(参数1,参数2...) => {函数体}

特性
  1. 如果函数体只有一句代码,且代码的执行结果就是函数的返回值,函数体大括号可以省略

    1
    2
    var fn = (a,b) => a + b; 
    console.log(fn(1,2));
  2. 箭头函数中,如果形参只有一个,形参外侧的小括号可以省略

    1
    2
    var fun  = v => v; 
    console.log(fun(1));
  3. 箭头函数不绑定 this 关键字,箭头函数中的 this 指向的是函数定义位置所在的对象中的(即箭头函数定义时所在花括号的外层的 this 的指向)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const obj = {name: 11}; 
    function fn () {
    console.log(this);
    return () => {
    console.log(this);
    }
    }
    var fun = fn(); //返回window fun();
    // 返回window

    解析: 因为fun() 的函数体是在 fn() 中定义,所以其中的 this 即为 fn 中的 this,也就是通过 call 设定的 obj,所以输出值不是 fun 而是 obj

  4. 箭头函数里没有 arguments 对象 用 rest 参数代替

    1
    2
    3
    fn = (x,y) => {   
    console.log(arguments);
    }
  5. 箭头函数不能做构造函数 不能 new 调用

    1
    2
    3
    4
    5
    fn = (x,y) => { 
    this.name = x;
    this.age = y;
    }
    const obj = new fn('bruce',1);

    箭头函数主要用于具名函数的声明和简化回调函数

箭头函数面试题
1
2
3
4
5
6
7
8
var age = 100; 
var obj = {
age: 20,
say: () => {
alert(this.age);
}
}
obj.say(); // 100

解析: 因为 say 函数定义在 obj 中,而 obj 是个对象,不形成作用域,所以 say 的指向为 window

对象扩展

对象的简写赋值

可以直接将函数声明、变量写进对象中,属性名即为变量名,属性值即为该变量的值

1
2
3
4
5
6
7
let name = 'bruce'; 
let age = 20; // 简写赋值
let school = {name,age};
let obj = {
fn(){}
}
console.log(school.name,school.age) // 'bruce' 20

对象里用变量表示属性名

属性名使用 [] 里面就可以使用变量

1
2
3
let str = 'name'; 
let obj2 = { [str]:name };
console.log(obj2.name) // bruce

Object.assign

将源对象所有可枚举属性复制到目标对象(浅拷贝)

Object.assign(target,sources...)

1
2
3
4
5
let obj3 = { 
age:20,
}
let obj4 = Object.assign(obj2,obj3);
console.log(obj4);

可以利用该方法实现浅拷贝

1
2
3
let obj5 = {} 
let obj6 = Object.assign(obj5,obj4);
console.log(obj6)

深拷贝: 拷贝多层,每一级别的数据都会重新开辟地址拷贝

拷贝方法: 利用递归函数

利用 for in 和 递归函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function deepcopy(newobj,oldobj) { 
for(var k in oldobj) {
var item = oldobj[k]; //判断是否是数组
if(item instanceof Array) {
newobj[k] = [];
deepcopy(newobj[k],item);
} else if (item instanceof Object) {
newobj[k] = {};
deepcopy(newobj[k],item);
} else {
newobj[k] = item;
}
}
}

注意: 因为数组也是对象,所以要放在对象之前

对象也可以使用扩展运算符

1
2
3
4
let obj11 = {name:'clark'}; 
let obj22 = {age:20}
let obj33 = {...obj11,...obj22};
console.log(obj33); // {name:'clark',age:20}

方法

  • Object.defineProperty(目标对象,'修改的属性',descriptor)

    其中 descriptor 以对象形式 {} 说明,包括了

    value: 设置属性的值

    writable: 值是否可以重写 true | false 默认为 false

    enumerable: 目标属性是否可以被遍历 true | false 默认为 false

    configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为 false

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var obj = { 
    id : 1,
    pname : 'xiaomi',
    price : 1999
    } // 修改添加对象的属性
    Object.defineProperty(obj,'num',{
    value: 1000
    })
    Object.defineProperty(obj,'price',{
    value: 9.9
    })
    Object.defineProperty(obj,'id',{
    writable:false
    })

    注意: 一开始就设置的好的属性如果没有利用该方法就不会被影响。只能修改已有属性或者新建自有属性,但不能修改继承属性

  • Object.getOwnPropertyDescriptor(对象名,对象属性名): 返回某个对象特定属性的属性描述符)

    1
    2
    3
    4
    5
    6
    var i = { 		
    num : {
    num1 : 2
    }
    }
    console.log(Object.getOwnPropertyDescriptor({x:1},"x"));//{value: 1, writable: true, enumerable: true, configurable: true}
  • Object.keys(对象名)

    获取对象的所有可枚举的自有属性属性名**,**以数组形式返回

    1
    2
    3
    4
    5
    var arr = Object.keys(obj);
    // console.log(arr);
    arr.forEach(function(value) {
    console.log(value);
    })

    与 for(var k in obj){} 的区别在于后者返回字符串 前者返回数组

  • Object.values 返回对象的属性值 数组形式

    1
    2
    let obj = { name:'bruce', } ;
    console.log(Object.values(obj)); // ['bruce']
  • Object.entries 返回对象的属性和值 二维数组形式

    1
    2
    let obj = { name:'bruce', } 
    console.log(Object.entries(obj)); // [['name','bruce']]

对象的解构赋值

1
2
let {x,y,...z} = {x:1,y:2,a:3,b:4}; 
console.log(z); // {a:3,b:4}

对象的扩展运算符

1
2
3
let obj = {x:1,y:2}; 
let obj1 = {...obj};
console.log(obj1); // {x:1,y:2}

Set数据结构

类似于数组,但是会自动消除重复的数据,适用于搜索引擎。即成员的值都是唯一的,没有key 没有索引 无法通过索引获取

Set数据结构的创建

new Set();

参数必须是数组或者是类数组或者是有 iterable 接口

有 iterable 接口: 数组 arguments DOM 元素集合 set Map 字符串

1
2
3
4
5
const a  = new Set(); 
console.log(a.size); // 0
const b = new Set([1,2,3,4,3,1,2,4,'a','a']);
console.log(b);
console.log(b.size); // 5

可以利用 Set 数据结构对数组去重

1
2
const s3 = new Set([1,2,3,2,1]); 
console.log(...s3);

Set数据结构方法

  • set.size: 返回实例的值个数

  • set.add(value): 添加某个值 返回Set解构本身

    1
    2
    3
    const s1 = new Set();
    s1.add('pink').add('red');
    console.log(s1);
  • set.delete(value): 删除某个值 返回布尔值显示删除是否成功

    1
    2
    3
    const r1 = s1.delete('red');
    console.log(s1);
    console.log(r1); // true
  • set.has(): 判断是否存在某个值 返回布尔值

    1
    2
    const r2 = s1.has('pink'); 
    console.log(r2);//true
  • set.clear(): 删除全部数据

    1
    2
    s1.clear();
    console.log(s1);

遍历Set数据结构

利用forEach函数

1
2
const s2 = new Set([1,2,3,4]); 
s2.forEach(value => console.log(value));

Set 实例也有 keys、values、entries 方法,由于 Set 数据类型没有索引,所以 keys 和 values 返回的是同样的值

WeakSet

与 Set 数据类型相同,也是不重复的值的集合。但是与 Set 有两个区别:

  1. WeakSet 的成员必须是对象类型的值

    1
    const ws = new WeakSet([1,2,3]);//报错
  2. WeakSet 的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,而是会继续回收该对象

    垃圾回收机制依赖引用计数,当一个数值的引用次数不为 0,就无法被回收。结束该值的使用后,有时会忘记取消引用,导致该对象之前所占内存无法释放,进而可能引发内存泄漏。而 WeakSet 的引用都不计入垃圾回收机制,所以就不存在这个问题。由于这些特点,WeakSet 无法遍历也没有 size 属性,因为其成员随时可能消失。

Symbol数据类型

是一个基本数据类型 表示独一无二的值

通过Symbol()方法直接创造 Symbol 类型值

1
2
let sym1 = Symbol(); 
let sym2 = Symbol();

注意: 不能用 new 调用,因为 Symbol 是一个基本数据类型而不是函数


Symbol 函数可接受一个参数 用以描述当前 Symbol 值(相当于一个键值)

1
2
3
let sym3 = Symbol('one'); 
let sym4 = Symbol('one');
console.log(sym3 === sym4); // false

Symbol 不可以进行数值运算,但是可以转为字符串和布尔值

1
2
// console.log(Symbol(3) +1); 错误
// console.log(Symbol('1') + 'bruce') 错误

Symbol.for(参数): 根据描述寻找对应值 如果之前有相同参数的 Symbol 值,则返回这个值,否则则创建一个新的 Symbol

1
2
3
let zf1 = Symbol.for('zhufeng'); 
let zf2 = Symbol.for('zhufeng');
console.log(zf1 === zf2); // true

Symbol.keyFor(Symbol值): 返回 Symbol.for 找到或创造的 Symbol 值

1
console.log(Symbol.keyFor(zf2));

用途: Symbol 数据类型主要用于属性名,防止对象内属性名的冲突

1
2
3
4
5
let obj = { [sym1]:'bruce', [sym2]:'clark' } ;
console.log(obj[sym1]);
console.log(obj.sym2);
obj[sym3] = 'diana';
console.log(obj);

注意: Symbol 做为属性名时,不会出现在for in for of Object.keys Object.getOwnPropertyNames

Map数据类型

一个构造函数,代表了键值对的集合,键的范围包括各种类型的值而不局限于字符串

创建实例

new Map([[key1,value1],[key2,value2],...]): 参数是一个二维数组,每一项内嵌数组里有key和对应的映射值value

1
let map1 =  new Map([[1,'zhufeng'],[false,{name:'bruce'}]]);

注意:

  1. 属性名不能重复,因为是映射关系,一个值只能映射一个值

方法

属性.size: 映射对的个数

1
console.log(map2.size); // 4

map.get(key): 获取key键的映射值,key值不用转为字符串

1
2
3
4
const age = map2.get(1); 
console.log(age); // sz
const bol = map2.get(true);
console.log(bol);

map.set(key,value): 给键 key 设置值 value

1
2
map2.set(1,'hz');
console.log(map2.get(1)); // hz

map.has(key): 判断 key 键有没有对应的 value 值

1
console.log(map2.has(false)); // true

map.delete(key): 删除 key 键 返回布尔值

1
2
map2.delete(false); 
console.log(map2.has(false)); // false

map2.forEach((value,index,input) => {}): 遍历 map 数据

1
2
3
map2.forEach((value,index) => { 
console.log([index,value]);
})

map.clear(): 清空所有映射

map.keys(): 获取键值 用于遍历

1
2
3
for(let k of map2.keys()) { 
console.log(k);
}

map.values(): 获取映射值 用于遍历

1
2
3
for(let k of map2.values()) { 
console.log(k);
}

map.entries(): 获取映射对

1
2
3
for(let k of map2.entries()) { 
console.log(k);
}

WeakMap

与 Map 基本相同,但有两个区别:

  1. 键名只能用对象

  2. 键名所指向的对象不计入垃圾回收机制

由于这些特性,WeakMap 数据同样没有 size 属性也无法遍历

注意: WeakMap 弱引用的只是键名而不是键值。

Proxy

用于代理对象,拦截/改写对对象的操作

创建实例

new Proxy(target,handler):

第一个参数: 代理的对象也可以是函数

第二个参数: 配置对象,对于每一个拦截的操作,都需要提供一个对应的处理函数,该函数将拦截对应的操作

1
2
3
4
5
6
7
8
9
let proxy1 = new Proxy(obj,{ 
get(target,key,proxy) {
console.log('get')
return 'haha';
},
set() {
console.log('set');
}
})

注意: 必须是对 Proxy 实例操作其代理的对象才能执行拦截的函数

Proxy拦截对象操作

get: 用于拦截对对象属性值的读取,三个参数

参数:

target: 原对象

propKey : 读取的属性名

receiver : 调用的 proxy 对象

利用 get 拦截操作实现数组读取负数索引

1
2
3
4
5
6
7
8
9
10
11
let arr = [1,2,3,4]; 
let proxy = new Proxy(arr,{
get:function(target,prop,receiver) {
if(Number(prop) < 0) {
return target[target.length+Number(prop)];
} else {
return target[prop];
}
}
})
console.log(proxy[-4]);

set: 用于拦截对对象属性值的值的设置,四个参数

参数:

target: 原对象

propKey: 读取的属性名

value: 设置的值

receiver: 调用的 proxy 对象

set 实现属性设置的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let obj = { 
name:'bruce',
age:20
}
let proxy = new Proxy(obj,{
set:function(target,propKey,value,receiver) {
if(propKey === 'age') {
if(value >= 100) {
return new Error('exceed the limit!')
} else {
target[propKey] = value;
}
}
}
})
proxy.age = 90;
console.log(obj.age); // 20

has: 拦截in运算符

参数:

target: 原对象

propKey : 读取的属性名

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
let obj = {name:'zhufeng'}; 
let proxy1 = new Proxy(obj,{
get(target,propKey,receiver) {
// target:原对象
// Propkey:获取的属性名
// receiver 当前Proxy实例
return target[propKey]
}, set(target,propKey,value,receiver) {
// target:原对象
// Propkey:获取的属性名
// value:设置的值
// receiver 当前Proxy实例
console.log('set');
target[propKey] = value;
},
// has 拦截in运算符
has(target,propKey) {
// console.log('yes');
return propKey in target;
},
})
console.log(proxy1.name); // zhufeng
proxy1.name = 'haha';
console.log(obj['name']); // haha
console.log('name' in proxy1); // true

Proxy拦截函数操作

apply: 用于拦截函数的执行,三个参数

参数:

target : 原函数

object : 函数中 this 指向

args : 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let proxy2 = new Proxy(getObj,{
// 拦截函数执行
apply(target,object,args) {
// target:原函数
// args:传入的参数 数组形式
// obejct: 修改函数this指向
console.log('function');
console.log(args);
console.log(object);
if(object) {
object.fn = target;
object.fn(...args);
delete object.fn;
} else {
target(...args);
}
},
})
proxy2();// function [] undefined NaN
proxy2(1,2);// function [1,2] undefiend 3
proxy2.call({name:'bruce'},1,2);// function [1,2] {name:'bruce'} 3

Promise

用于处理异步函数的执行顺序、主要用于网络请求

基础

Promise 是一个容器,一个对象,里面存放着异步事件,里面的异步事件有三种状态:

  • pending (进行中)
  • fulfilled (已成功)
  • rejected (已失败)

Promise 容器可以获取根据异步事件的结果将其转为完成状态或失败状态,并将事件结果传送出去

Promise 对象的特点:

  • 对象的状态不受外界影响,只要异步操作的结果可以决定是哪一种状态

  • 一旦状态改变就不会再变,会一直保持这个状态,成为 Resolved

步骤:

  1. 创建 promise 容器 : 即创建一个承诺变量 里面封装了异步任务 会自动调用

    promise 本身不是异步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var p1 = new Promise((resolve,reject) => { 
    fs.readFile('./data/a.txt','utf8',(err,data) => {
    if(err) {
    // 如果失败
    // console.log(err);
    // 调用reject 将容器的状态由pending改为Rejected、然后执行catch
    reject(err);
    } else {
    // 如果成功
    // console.log(data);
    // 调用resolve 将容器的状态由pending改为Resolved、然后执行then
    resolve(data);
    }
    })
    }).then((data)=>{
    // resolve(data); 会执行到这里
    console.log(data)
    }).catch((error)=>{
    // reject(err); 会执行到这里
    console.log("error")
    })

    设置承诺了之后调用 then 方法

    设置承诺里面回调函数的 resolve/reject 事件:

    • 参数 1 就是上面的函数 resolved
    • 参数 2 就是函数 rejected 这里是在设置 resolvereject 的具体函数

    resolve 里面最后要 return 接下去要执行的异步事件所在的 Promise 对象实例,形成链式编程,实现顺序执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    p1.then((data) => { 
    console.log(data);
    // return 一个Promise对象进行设置 链式编程
    return p2;
    },(err) => {
    console.log('failed');
    }).then((data) => {
    // 这是定义p2的resolved
    console.log(data + 'haha');
    return p3;
    },(err) => {
    // 这是定义p2的reject
    console.log(err);
    })

注意:

  1. 即先创造保证,再设置异步事件完成后具体做什么

  2. 实现同步输出的原理就是: new Promise 是同步的,里面的函数依旧是异步的,所以继续往下进行 resolved 和 reject 函数的定义,第一个定义的 promise 的异步函数执行完后,会调用 resolved 或者 reject 函数,所以会找到定义之处进行执行,由于另外一个 promise 的 resolved 和 reject 是在第一个定义之后 return 出来再定义的,所以这个 promise 只能在第一个 promise 执行之后再定义,所以其 reject 和 resolve 函数也只能在其之后调用,依次递归形成同步

  3. then 方法只有在当前脚本所有同步任务执行完毕后才会执行

  4. resolve 函数只能有一个参数


  • catch方法: 捕获 promise 实例和 then 中 resolve 的错误 一般会在最后使用 catch

    1
    2
    3
    4
    5
    6
    pro1.then((mes) => { 
    console.log(mes);
    let a1 = 2;
    }).catch((e) => {
    console.log(e);
    })
  • Promise.all(): 用于将多个 promise 实例整合成一个 promise 实例 参数为数组形式

    1
    let pro2 = Promise.all([p1,p2,p3])
  • 整合的实例的状态有两种情况:

    1. p1 , p2 , p3 的状态都为 fulfilled,p 才为 fulfilled。此时 p1 , p2 , p3 的结果组成一个数组,传递给 p 的回调函数
    2. 只要 p1 , p2 , p3 中有一个进入 rejected 状态,p 就会进入 rejected 状态。此时第一个进入 rejected 的子实例的结果就会传给 p 的回调函数
  • 执行错误则停止执行并通过 catch 方法输出

    1
    2
    3
    4
    5
    pro2.then((res) => { 
    console.log(res);
    }).catch((e) => {
    console.log(e);
    }) // ['OK1','OK2','OK3']
  • Promsie.race(): 与 all 方法类似,但只会输出第一个执行成功的回调函数的 resolve 的结果

    1
    2
    3
    4
    5
    Promise.race([p1,p2,p3]).then((res) => { 
    console.log(res);
    }).catch((e) => {
    console.log(e);
    }) // 'OK1'

mongoose 中使用 promise

mongoose 中已经包装了 promise ,数据库操作事件后面可以直接调用 then 方法

1
2
3
4
5
6
7
8
9
10
11
User.findOne({author:'clark'}).then(function(data) { 
if(data) {
console.log('用户已存在');
} else {
return new User({
author:'clark',
title:'wonder woman',
body:'prinecss'
}).save();
}
})

async函数和await

Generator 函数的语法糖,加在函数声明前方,async 函数返回一个 Promise 对象

async 函数可以看做由多个 promise 包装成的 Promise 对象, await 则是内部 promise 的 then 命令的语法糖

async 即把其后面的函数包装成一个 promise 对象,可以使用 then 方法添加回调函数。async 函数内 return 的值会作为 then 方法回调函数的参数

1
2
3
4
5
6
async function getName() { 
return 'i am a async function!';
}
getName().then((res) => {
console.log(res); // 'i am a async function!'
})

当 async 遇到 await 时,会等待其函数执行完毕后再继续执行后面的函数体.

await 后面一般是跟 Promise 对象

await promise 实例

1
2
3
4
5
6
7
8
9
10
11
12
async function whatName(ms) { 
await new Promise((res) => {
setTimeout(res,ms);
})
return 'bruce';
}

whatName(5000).then((res) => {
console.log(res); // 5秒后输出'bruce'
}).catch((e) => {
console.log(e);
})

async 函数返回的 Promise 状态由函数内 await 后面的 promise 实例的状态决定。如果 async 内部发生错误或者某一个 await 发生错误,那么该错误就会被 async 函数的 Pomise 实例的 catch 方法捕获。并且发生错误的await后面的函数体不再执行。只有 async 函数内部所有异步操作执行完,才会执行 then 方法指定的回调函数

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
async function fn() { 
throw new Error('Error Happend!');
await new Promise((res) => {
res();
})
}
fn().then((res) => {
console.log(res);
}).catch((e) => {
console.log(e); // 'Error Happend!'
})
async function fn() {
await new Promise((res) => {
throw new Error('Error Happend!');
})
await new Promise((res) => {
res();
})
}
fn().then((res) => {
console.log(res);
}).catch((e) => {
console.log(e);// 'Error Happend!'
})
async function fn() {
await new Promise((res) => {
res();
})
await new Promise((res) => {
res();
})
return 'successed!';
}
fn().then((res) => {
console.log(res); // 'successed!'
}).catch((e) => {
console.log(e);
})

Iterator

Iterator(遍历器): 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构,只要部署了Iterator 接口,就可以完成遍历操作

Iterator作用:

为各种数据结构提供统一的访问接口

  1. 使得数据结构的成员能够按照某种次序排列
  2. 供 for…of 消费

Iterator的遍历过程:

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用指针对象的 next 方法,将指针指向数据结构第一个成员
  3. 第二次调用指针对象的 next 方法,将指针指向数据结构第二个成员
  4. 不断调用 next 方法,直到遍历结束

next方法会返回数据结构的当前成员的信息。即一个包含 value 和 done 属性的对象。value 是当前成员的值, done 属性是一个布尔值,表示遍历是否结束

默认 Iterator 接口

数据结构只要部署了 Iterator 接口,就是可遍历的

许多数据结构都默认部署了 iterator 接口,包括:

  1. Array

  2. Map

  3. Set

  4. NodeList对象

  5. String

  6. arguments

  7. TyperArray

默认的 Iterator 接口都部署在数据结构的 Symbol.iterator 属性中。

Symbol.iterator 是一个表达式,返回 Symbol 对象的 iterator 属性。该属性是个函数,会返回一个指针对象

1
2
3
4
5
6
7
8
9
10
11
12
const obj = { 
[Symbol.iterator] : function() {
return {
next: function() {
return {
value:1,
done:true,
}
}
}
}
}

for…of

for...of是ES6标准,用来遍历value值,遍历数组,不能遍历普通对象

1
2
3
4
5
// 遍历数组
let arr = [1, 2, 3]
for (let value of arr) {
console.log(value)
} // 1 2 3
  • for...of 不能遍历普通对象的原因

    原因是:普通对象没有Symbol.iterator属性,如果一个对象拥有Symbol.iterator属性,那么就可以使用for…of遍历

ES6的module

ES6的模块化的基本规则或特点:

  1. 每一个模块只加载一次, 每一个JS只执行一次, 如果下次再去加载同目录下同文件,直接从内存中读取。 一个模块就是一个单例,或者说就是一个对象;

  2. 每一个模块内声明的变量都是局部变量, 不会污染全局作用域;

  3. 模块内部的变量或者函数可以通过export导出;

  4. 一个模块可以导入别的模块

export 导出 import 导入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export var c = 18;

var a = "zykj";
var b = 18;
//export中可以使用as关键字重命名对外输出名称
export {
name as a,
age as b
}


// import * as 变量名 用以获取目标文件中所有export的变量,对象形式
import * as obj from './config.js';

// 解构赋值形式用以获取目标文件中同名变量
import {c,d} from './config.js';

import {name,age} from './config.js'

// import 变量 用以获取目标文件中export default后的值
import fn from './config.js';

ES6的模块化实现

如何实现模块化,在html中需要使用type='module'属性。

1
2
3
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>

此时表示aaa.js是一个单独的模块,此模块是有作用域的。如果要使用aaa.js内的变量,需要在aaa.js中先导出变量,再在需要使用的地方导出变量。

直接导出

1
export let name = '小明'

使用

1
2
import {name} from './aaa.js'
console.log(name)

./aaa.js表示aaa.js和mmm.js在同级目录。

统一导出

1
2
3
4
5
6
7
8
9
10
11
12
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
//2.最后统一导出
export {
flag,sum,age
}

使用import {name,flag,sum} from './aaa.js'导入多个变量

1
2
3
4
5
6
7
8
9
import {name,flag,sum} from './aaa.js'

console.log(name)

if(flag){
console.log("小明是天才");
}

console.log(sum(20,30));

使用{}将需要的变量放置进去

导出函数/类

在aaa.js中添加

1
2
3
4
5
6
7
8
9
//3.导出函数/类
export function say(value) {
console.log(value);
}
export class Person{
run(){
console.log("奔跑");
}
}

在mmm.js中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
import {name,flag,sum,say,Person} from './aaa.js'

console.log(name)

if(flag){
console.log("小明是天才");
}

console.log(sum(20,30));

say('hello')
const p = new Person();
p.run();

导入 export default

导出

1
2
3
export default {
flag,sum,age
}

导入

1
2
3
//4.默认导入 export default
import aaa from './aaa.js'
console.log(aaa.sum(10,110));

注意:使用默认导出会将所有需要导出的变量打包成一个对象,此时导出一个对象,此时我在mmm.js中导入变量时候命名为aaa,如果要调用变量需要使用aaa.变量。

统一全部导入

使用import * as aaa from './aaa.js'统一全部导入

1
2
3
4
// 5.统一全部导入
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.name);

前端模块化

随着前端项目越来越大,团队人数越来越多,多人协调开发一个项目成为常态。

例如现在小明和小张共同开发一个项目,小明定义一个aaa.js,小张定义了一个bbb.js。

1
2
3
4
5
6
7
8
9
10
11
//小明开发
var name = '小明'
var age = 22

function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20));
}

此时小明的sum是没有问题的。

1
2
3
//小红
var name = "小红"
var flag = false

此时小明和小红各自用各自的flag你变量没问题。

1
2
3
4
//小明
if(flag){
console.log("flag是true")
}

在index.html页面导入这些js文件

1
2
3
<script src="aaa.js" ></script>
<script src="bbb.js" ></script>
<script src="ccc.js" ></script>

此时小明知道自己在aaa.js中定义的flagtrue,认为打印没有问题,但是不知道小红的bbb.js中也定义了flagtrue,所以mmm.js文件并没有打印出“flag是true”。

这就是全局变量同名问题。

使用导出全局变量模块解决全局变量同名问题

aaa.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//模块对象
var moduleA = (function (param) {
//导出对象
var obj = {}
var name = '小明'
var age = 22

function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
obj.flag=false
return obj
})()

mmm.js

1
2
3
4
5
//小明
//使用全局变量moduleA
if(moduleA.flag){
console.log("flag是true")
}

这样直接使用aaa.js导出的moduleA变量获取小明自己定义的flag

CommonJS的模块化实现

CommonJS需要nodeJS的依支持。

aaa.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//CommonJS需要nodeJS支持
var name = '小明'
var age = 22

function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}

// module.exports = {
// flag : flag,
// sum : sum
// }
//导出对象
module.exports = {
flag,
sum
}

使用module.exports = {}导出需要的对象。

mmm.js

1
2
3
4
5
6
7
8
//导入对象,nodejs语法,需要node支持,从aaa.js取出对象
var {flag,sum} = require("./aaa")

console.log(sum(10,20));

if(flag){
console.log("flag is true");
}

使用 var {flag,sum} = require("./aaa")获取已经导出的对象中自己所需要的对象。