ES5、ES6自学笔记

类和对象

对象

  • 在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等。
  • 对象是由属性和方法组成的:
    • 属性:事物的特征,在对象中用属性来表示(常用名词)
    • 方法:事物的行为,在对象中用方法来表示(常用动词)

  • 在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象。
  • 类抽象了对象的公共部分,它泛指某一大类(class)
  • 对象特指某一个,通过类实例化一个具体的对象

创建类

  • 语法:

    class name {
      // class body
    }    
    
  • 创建一个实例:

    new name();
    
  • constructor() 方法是类的构造函数(默认方法),用于传递参数,返回实例对象,通过 new 命令生成对象实例时,自动调用该方法。如果没有显示定义, 类内部会自动给我们创建一个constructor()

  • 添加共有的方法:直接将方法放在类里面就可以了,但是注意类里面的函数都不需要写function,并且多个函数方法之间不需要添加逗号分隔

    // 1.创建类
    // 创建类 class 创建一个用户类
    class Star {
        // 使用constructor构造函数
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
        }
    }
    // 利用类创建对象 new
    var demo1 = new Star("demo1", 19);
    console.log(demo1.uname + demo1.age);
    //(1) 通过class 关键字创建类, 类名我们还是习惯性定义首字母大写
    //(2) 类里面有个constructor 函数,可以接受传递过来的参数,同时返回实例对象
    //(3) constructor 函数 只要 new 生成实例时,就会自动调用这个函数, 如果我们不写这个函数,类也会自动生成这个函数
    //(4) 生成实例 new 不能省略
    //(5) 最后注意语法规范, 创建类 类名后面不要加小括号,生成实例 类名后面加小括号, 构造函数不需要加function
    
    // 2.创建共有的方法
    class People {
        // 使用constructor构造函数
        constructor(uname, age) {
            this.uname = uname;
            this.age = age;
        }
        // 创建共有的方法 show
        show(){
            console.log("调用show方法");
        }
        run(){
            console.log("调用run方法");
        }
    }
    var demo2 = new People("demo2", 19);
    // 调用方法
    demo2.show();
    demo2.run();
    // (1) 我们类里面所有的函数不需要写function 
    // (2) 多个函数方法之间不需要添加逗号分隔
    
  • 在ES6之前,我们都是通过构造函数+原型实现面向对象编程。

  • 在ES6之后,我们通过类来实现面向对象编程

  • 类的本质其实还是一个函数,我们也可以简单的认为,类就是构造函数的另一种写法

  • 我们知道构造函数有以下特点:

    • 构造函数具有原型对象prototype
    • 构造函数原型对象prototype里面有constructor指向构造函数本身
    • 构造函数可以通过原型对象添加方法
    • 构造函数创建的实例对象有__proto__对象原型指向构造函数的原型对象
    class Person {
    }
    // 1. 构造函数具有原型对象prototype
    console.log(Person.prototype);
    // 2. 构造函数原型对象prototype里面有constructor指向构造函数本身
    console.log(Person.prototype.constructor);
    // 3. 构造函数可以通过原型对象添加方法
    Person.prototype.eat = function () {
        console.log('hello');
    }
    // 4. 构造函数创建的实例对象有__proto__对象原型指向构造函数的原型对象
    var xy = new Person;
    xy.eat();// console输出hello
    console.log(xy.__proto__);
    

extends和super关键字

类的继承通过 extends 关键字实现,子类通过 super 方法获得父类属性。
语法:
class Father{ // 父类}
class Son extends Father { // 子类继承父类}
super(x, y) 直接使用父类的构造函数
super.方法名() 在子类中调用父类的方法

// 类的继承
// 类的继承通过 extends 关键字实现,子类通过 super 方法获得父类属性。
class Father{
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
    money(){
        console.log(100);
    }
}
class Son extends Father{
    constructor(name, age, sex){
        // 使用super关键字获取父类的构造方法
        super(name, age);
        this.sex = sex;
    }
    // 在子类中直接声明一个与父类同名函数即可进行重写
    money(){
        console.log(10);
    }
    // 创建子类独有的方法
    game(){
        console.log("我在打游戏");
    }
}
var son = new Son("demo", 19, "男");
// 使用子类独有的方法
son.game();
// 因为是继承,所以Son可以直接调用Father的money方法,如果后面我们不重写父类的方法,会打印100;但是后面我们又将方法重写了,所以打印的是10
son.money();

注意:

  1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象.
  2. 类里面的共有属性和方法一定要加this使用.
  3. 类里面的this指向问题.
  4. constructor 里面的this指向实例对象, 方法里面的this 指向这个方法的调用者
   class Father1 {
        constructor(x, y) {
            this.x = x;
            this.y = y;
        }
        sum() {
            console.log(this.x + this.y);
        }
    }
    class Son1 extends Father1{
        constructor(x, y) {
            // 利用super 调用父类的构造函数,并且给自己的x, y赋值
            // super 必须在子类this之前调用
            super(x, y);
            this.x = x;
            this.y = y;
        }
        sub(){
            console.log(this.x - this.y);
        }
    }
    var son1 = new Son1(10, 1);
    son1.sum();// 调用父类的sum
    son1.sub();// 使用自己的sub

类里的this指向问题

  • 在类的方法里面,谁调用了方法谁就是this。如:按钮调用了类里面的方法,this就指向的是按钮
    1. 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
    2. 类里面的共有的属性和方法一定要加this使用.
    3. 类里面不使用this直接访问的则是全局变量
    4. constructor里面的this指向实例对象,方法里面的this指向的是这个方法的调用者
 	<button>点击</button>
    <script>
        var name = "ljy";
        var value = "lv";
        var that, _that;
        class Star {
            constructor(value) {
                // 将实例赋值给that,以方便获取name,并且赋值要放在方法的前面
                _that = this;
                // constructor 里面的this 指向的是 创建的实例对象
                this.name = value;
                // 不使用this则是直接指向全局变量
                console.log(value);// 形参也叫value,所以输出形参
                console.log(name);// ljy
                console.log(this.name);// 刘德华
                // 想让函数一构造就使用sing方法
                this.sing();

                // 给按钮绑定事件
                this.btn = document.querySelector("button");

                this.btn.onclick = this.sing;
            }
            sing() {
                // 由于这里的this指向的方法的调用者,所以给按钮绑定这个方法则指向按钮,但是按钮又没有name值,所以我们可以在构建对象的时候将this赋值给其他的变量(_that),然后再从_that里面获取name的值
                // console.log(this.name);
                console.log(_that.name);
            }
            dance() {
                // 这里的this指向的是调用的对象
                that = this;
            }
        }
        var ldh = new Star("刘德华");
        // 先调用dance方法,将this赋值给that,然后进行对比
        ldh.dance();
        // 这里的that绝对等于ldh,因为是ldh调用了dance()
        console.log(that === ldh);
    </script>

构造函数和原型

构造函数

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

  • ES6, 全称 ECMAScript 6.0 ,2015.06 发版。但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

  • 在 ES6之前 ,对象不是基于类创建的,而是用一种称为构建函数的特殊函数来定义对象和它们的特征。

  • 创建对象的三种方法:

    • 对象字面量
    • new Object()
    • 自定义构造函数
    // 1. 利用 new Object() 创建对象
    var obj1 = new Object();
    // 2. 利用 对象字面量创建对象
    var obj2 = {};
    // 3. 利用构造函数创建对象
    function Star(uname, age) {
        this.uname = uname;
        this.age = age;
        this.sing = function() {
            console.log('我会唱歌');
        }
    }
    var ldh = new Star('刘德华', 18);
    var zxy = new Star('张学友', 19);
    console.log(ldh);
    ldh.sing();
    zxy.sing();
    
  • 在 JS 中,使用构造函数时要注意以下两点:

    • 构造函数用于创建某一类对象,其首字母要大写
    • 构造函数要和 new 一起使用才有意义
  • new 在执行时会做四件事情:

    • 在内存中创建一个新的空对象。
    • 让 this 指向这个新的对象。
    • 执行构造函数里面的代码,给这个新对象添加属性和方法。
    • 返回这个新对象(所以构造函数里面不需要 return )。

实例成员和静态成员

  • JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员。
    • 静态成员:在构造函数本上添加的成员称为静态成员,只能由构造函数本身来访问
    • 实例成员:在构造函数内部创建的对象成员称为实例成员,只能由实例化的对象来访问
// 构造函数中的属性和方法我们称为成员,成员可以添加
function Star(uname, age) {
    this.uname = uname;
    this.age = age;
    this.sing = function () {
        console.log("能唱歌");
    }
}
var ldh = new Star("刘德华", 40);
// 1. 实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
// 实例成员只能通过实例化的对象来访问
console.log(ldh.uname); // 刘德华
ldh.sing(); // 能唱歌
console.log(Star.uname);// undefined,不可以通过构造函数来访问实例成员

// 2. 静态成员:在构造函数本身上添加的成员 sex 就是静态成员
Star.sex = "男";
// 静态成员只能通过构造函数来访问
console.log(Star.sex);// 男
console.log(ldh.sex);// undefined,不能通过对象来访问

原型

构造函数原型prototype

  • 构造函数通过原型分配的函数是所有对象所共享的。
  • JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象。注意这个 prototype 就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。
  • 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。
        // 1. 构造函数的问题. 
        function Star(uname, age) {
            this.uname = uname;
            this.age = age;
            // this.sing = function() {
            //     console.log('我会唱歌');

            // }
        }
        // 在prototype中定义共享的sing方法,一般情况下,我们的公共属性定义到构造函数里面, 公共的方法我们放到原型对象身上
        Star.prototype.sing = function () {
            console.log(this.uname + '我会唱歌');
        }
        var ldh = new Star('刘德华', 18);
        var zxy = new Star('张学友', 19);
        console.log(ldh.sing === zxy.sing);
        // console.dir(Star);
        ldh.sing();//刘德华我会唱歌
        zxy.sing();//张学友我会唱歌

对象原型__proto__

  • 对象都会有一个属性 proto 指向构造函数的 prototype 原型对象,之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 原型的存在。
    • __proto__对象原型和原型对象 prototype 是等价的
    • __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype
      在这里插入图片描述
function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function () {
    console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
ldh.sing();
console.log(ldh); // 对象身上系统自己添加一个 __proto__ 指向我们构造函数的原型对象 prototype
console.log(ldh.__proto__ === Star.prototype);
// 方法的查找规则: 首先先看ldh 对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果没有sing 这个方法,因为有__proto__ 的存在,就去构造函数原型对象prototype身上去查找sing这个方法

constructor 构造函数

  • 对象原型( proto)和构造函数(prototype)原型对象里面都有一个属性 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身。
  • constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。
  • 一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数。
function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
var ldh = new Star('刘德华', 18);
var zxy = new Star('张学友', 19);
console.log(Star.prototype === ldh.__proto__);// true
console.log(Star.prototype.constructor);
// ƒ Star(uname, age) {
//     this.uname = uname;
//     this.age = age;
// }
console.log(Star.prototype.constructor === ldh.__proto__.constructor);// true
// 使用原型增加方法
// Star.prototype.sing = function () {
//     console.log("唱歌");
// }
// Star.prototype.movie = function () {
//     console.log("电影");
// }
 // 直接对原型赋值
Star.prototype = {
    // 如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
    constructor: Star,
    sing: function () {
        console.log("唱歌");
    },
    movie: function () {
        console.log("电影");
    }
}
console.log(Star.prototype.constructor === ldh.__proto__.constructor);// true,不手动利用constructor指回原来的构造函数则为false

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

在这里插入图片描述

原型对象this指向

  • 构造函数中的this 指向我们实例对象.
  • 原型对象里面放的是方法, 这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象.

JavaScript 的成员查找机制(规则)

  1. 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
  2. 如果没有就查找它的原型(也就是 __proto__指向的 prototype 原型对象)。
  3. 如果还没有就查找原型对象的原型(Object的原型对象)。
  4. 依此类推一直找到 Object 为止(null)。
  5. __proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

扩展内置对象

  • 可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能。
  • 注意:数组和字符串内置对象不能给原型对象覆盖操作 Array.prototype = {} ,只能是 Array.prototype.xxx = function(){} 的方式。
Array.prototype.sum = function () {
    var sum = 0;
    for (let i = 0; i < this.length; i++) {
        const element = this[i];
        sum += element;
    }
    return sum;
}
var arr = [1, 2, 3];
console.log(arr.sum());

继承

  • ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。

call()

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

  • fun.call(thisArg, arg1, arg2, …)
    1. thisArg :当前调用函数 this 的指向对象
    2. arg1,arg2:传递的其他参数
// call 方法
function fn(x, y) {
    console.log('我想喝手磨咖啡');
    console.log(this);
    console.log(x + y);
}
var o = {
    name: 'andy'
};
// fn();
// 1. call() 可以调用函数
fn.call();
// 我想喝手磨咖啡
//  Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// NaN
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
// 我想喝手磨咖啡
// {name: "andy"}name: "andy"__proto__: Object
// 3

借用构造函数继承父类型属性、方法

  • 核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
  • 继承属性:
// 1.父构造函数
function Father(uname, age) {
    // this指向父构造函数的对象实例
    this.uname = uname;
    this.age = age;
}
// 2.子构造函数
function Son(uname, age, sex) {
    // this指向子构造函数的对象实例
    Father.call(this, uname, age);
    this.sex = sex;
}
var son = new Son("lv", 19, "男");
console.dir(son);
  • 继承方法:
// 给父的原型上添加方法
Father.prototype.money = function () {
    console.log(10000);
}
// Son.prototype = Father.prototype;  这样直接赋值会有问题,如果修改了子原型对象(__proto__),父原型对象也会跟着一起变化,因为父构造函数的原型中的prototype指向的是父的原型对象
Son.prototype = new Father(); // 让子的构造函数原型指向父的构造函数原型,这样子的构造函数原型就可以使用父的构造函数原型,但是父类的原型对象是要使用__proto__才可以指向
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 子的原型独有的方法
Son.prototype.exam = function () {
    console.log("考试");
}

类的本质

  1. class本质还是function.

  2. 类的所有方法都定义在类的prototype属性上

  3. 类创建的实例,里面也有__proto__ 指向类的prototype原型对象

  4. 所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

  5. 所以ES6的类其实就是语法糖.

  6. 语法糖:语法糖就是一种便捷写法. 简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖

    实例对象.proto === 实例对象.prototype.prototype === 类构造函数.prototype === 类的原型

ES5新增方法

数组方法

  • array.forEach(function(currentValue, index, arr))
  1. currentValue:数组当前项的值
  2. index:数组当前项的索引
  3. arr:数组对象本身
  • array.filter(function(currentValue, index, arr))
  1. filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组
  2. 注意它直接返回一个新数组
  3. currentValue: 数组当前项的值
  4. index:数组当前项的索引
  5. arr:数组对象本身
  • array.some(function(currentValue, index, arr))
  1. some() 方法用于检测数组中的元素是否满足指定条件. 通俗点 查找数组中是否有满足条件的元素
  2. 注意它返回值是布尔值, 如果查找到这个元素, 就返回true , 如果查找不到就返回false.
  3. 如果找到第一个满足条件的元素,则终止循环. 不在继续查找.
  4. currentValue: 数组当前项的值
  5. index:数组当前项的索引
  6. arr:数组对象本身
  • filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来
  • some 也是查找满足条件的元素是否存在 返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环
// forEach 迭代(遍历) 数组
var arr1 = [1, 2, 3];
var sum = 0;
arr1.forEach(function (value, index, array) {
    console.log('每个数组元素' + value);
    console.log('每个数组元素的索引号' + index);
    console.log('数组本身' + array);
    sum += value;
})

// filter 筛选数组
var arr2 = [12, 66, 4, 88, 3, 7];
console.log(sum);
var newArr = arr2.filter(function(value, index) {
    // return value >= 20;
    return value % 2 === 0;
});
console.log(newArr);
// some 查找数组中是否有满足条件的元素 
var arr1 = ['red', 'pink', 'blue'];
var flag1 = arr1.some(function(value) {
    return value == 'pink';
});
console.log(flag1);
// 1. filter 也是查找满足条件的元素 返回的是一个数组 而且是把所有满足条件的元素返回回来(在filter中就算return也不会停止迭代)
// 2. some 也是查找满足条件的元素是否存在  返回的是一个布尔值 如果查找到第一个满足条件的元素就终止循环(如果返回true就停止迭代,false就接着迭代)

字符串方法

  • trim() 方法会从一个字符串的两端删除空白字符。
    • str.trim()
    • trim() 方法并不影响原字符串本身,它返回的是一个新的字符串。
<input type="text"> <button>点击</button>
<div></div>
<script>
    var str = "  111   ";
    console.log(str); //   111   
    var str2 = str.trim();
    console.log(str); //   111   
    console.log(str2);//111

    var input = document.querySelector('input');
    var btn = document.querySelector('button');
    var div = document.querySelector('div');
    btn.onclick = function() {
        var str = input.value.trim();
        if (str === '') {
            alert('请输入内容');
        } else {
            console.log(str);
            console.log(str.length);
            div.innerHTML = str;
        }
    }
</script>

对象方法

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

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

    1. 使用方法:Object.defineProperty(obj, prop, descriptor)
    2. obj:必需。目标对象
    3. prop:必需。需定义或修改的属性的名字
    4. descriptor:必需。目标属性所拥有的特性
    • 第三个参数 descriptor 说明: 以对象形式 { } 书写
    1. value: 设置属性的值 默认为undefined
    2. writable: 值是否可以重写。true | false 默认为false
    3. enumerable: 目标属性是否可以被枚举(遍历)。true | false 默认为 false
    4. configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false 默认为false
<script>
    var obj = {
        id: 1, 
        name: '小米',
        price: 1999
    }
    // 给对象新增属性
    obj.num = 1000;
    // 修改属性
    obj.price = 99;
    console.log(obj);
    // 将对象属性名封装成一个数组返回
    var keys = Object.keys(obj);
    console.log(keys);// [id, name, price, num]
    // 添加和修改属性的方法
    Object.defineProperty(obj, 'num', {
        value: 1000
    })
    Object.defineProperty(obj, 'id', {
     writable: false // 此属性不能被修改
    })
    // 添加一个属性
    Object.defineProperty(obj, 'address', {
        value: '中国山东蓝翔技校xx单元',
        // 如果只为false 不允许修改这个属性值 默认值也是false
        writable: false,
        // enumerable 如果值为false 则不允许遍历, 默认的值是 false
        enumerable: false,
        // configurable 如果为false 则不允许删除这个属性 不允许在修改第三个参数里面的特性 默认为false
        configurable: false
    });
</script>

函数进阶

函数的定义和调用

  1. 函数声明方式 function 关键字 (命名函数)
  2. 函数表达式 (匿名函数)
  3. new Function()
    • var fn = new Function(‘参数1’,‘参数2’…, ‘函数体’)
    • Function 里面参数都必须是字符串格式
    • 第三种方式执行效率低,也不方便书写,因此较少使用
    • 所有函数都是 Function 的实例(对象)
    • 函数也属于对象
      在这里插入图片描述

函数的定义:

        //  函数的定义方式
        // 1. 自定义函数(命名函数) 
        function fn() {};

        // 2. 函数表达式 (匿名函数)
        var fun = function() {};

        // 3. 利用 new Function('参数1','参数2', '函数体');
        var f = new Function('a', 'b', 'console.log(a + b)');
        f(1, 2);

        // 4. 所有函数都是 Function 的实例(对象)
        console.dir(f);

        // 5. 函数也属于对象
        console.log(f instanceof Object);
函数的调用:
        // 1. 普通函数
        function fun() {
            console.log("普通函数调用");
        }
        fun();
        fun.call();
        // 2. 对象的方法
        var obj = {
            show: function () {
                console.log("对象的方法调用");
            }
        }
        obj.show();
        // 3. 构造函数
        function Star() {};
        new Star();
        // 4. 绑定事件函数
        btn.onclick = function () {
        }// 点击这个按钮就可以调用函数
        // 5. 定时器函数
        setInterval(function () {
        }, 1000)// 每隔一秒钟调用此函数,也可以使用变量命来代替函数体
        // 6. 立即执行函数
        (function () {
            console.log('立即执行函数执行');
        })();// 只要加载到此处就自动调用

this

  • 这些 this 的指向,是当我们调用函数的时候确定的。 调用方式的不同决定了this 的指向不同一般指向我们的调用者.
    在这里插入图片描述

call

  • call() 方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。
    • fun.call(thisArg, arg1, arg2, …)
    • thisArg:在 fun 函数运行时指定的 this 值
    • arg1,arg2:传递的其他参数
    • 返回值就是函数的返回值,因为它就是调用函数
    • 因此当我们想改变 this 指向,同时想调用这个函数的时候,可以使用 call,比如继承
// 1. call()
        var o = {
            name: 'andy'
        }
        function fn(a, b) {
            console.log(this);
            console.log(a + b);
        };
        fn.call(o, 1, 2);
        // call 第一个可以调用函数 第二个可以改变函数内的this 指向
        // call 的主要作用可以实现继承
        function Father(uname, age, sex) {
            this.uname = uname;
            this.age = age;
            this.sex = sex;
        }

        function Son(uname, age, sex) {
            Father.call(this, uname, age, sex);// 执行Father,但是Father中的this指向的是son
        }
        var son = new Son('刘德华', 18, '男');
        console.log(son);

apply

  • apply() 方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的 this 指向。(和call一样,只是apply将call后面的参数放在一个数组)
    • fun.apply(thisArg, [argsArray])
    • thisArg:在fun函数运行时指定的 this 值
    • argsArray:传递的值,必须包含在数组里面
    • 返回值就是函数的返回值,因为它就是调用函数
    • 因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值
// 2. apply()
var obj = {
    name: 'ljy'
};
function fn() {
    console.log(this);
}
fn.apply(obj); // 打印obj的指向
// 1. 也是调用函数 第二个可以改变函数内部的this指向
// 2. 但是他的参数必须是数组(伪数组)
// 3. apply 的主要应用 比如说我们可以利用 apply 借助于数学内置对象求数组最大值 
// Math.max();
var arr = [1, 66, 3, 99, 4];
var arr1 = ['red', 'pink'];
// var max = Math.max.apply(null, arr);
// Math.max方法不能传递一个数组过去,只能传递一个一个参数,所以我们可以使用apply的特性:第二个参数必须是数组。从而可以将arr以一个数组的形式传递过去
var max = Math.max.apply(Math, arr);
var min = Math.min.apply(Math, arr);
console.log(max, min);

bind

bind() 方法不会调用函数。但是能改变函数内部this 指向

  • fun.bind(thisArg, arg1, arg2, …)
  • thisArg:在 fun 函数运行时指定的 this 值
  • arg1,arg2:传递的其他参数
  • 返回由指定的 this 值和初始化参数改造的原函数拷贝
  • 因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind
        // 3. bind()  绑定 捆绑的意思
        var o = {
            name: 'andy'
        };
        function fn(a, b) {
            console.log(this);
            console.log(a + b);
        };
        var f = fn.bind(o, 1, 2);
        f();
        // 1. 不会调用原来的函数   可以改变原来函数内部的this 指向
        // 2. 返回的是原函数改变this之后产生的新函数
        // 3. 如果有的函数我们不需要立即调用,但是又想改变这个函数内部的this指向此时用bind
        // 4. 我们有一个按钮,当我们点击了之后,就禁用这个按钮,3秒钟之后开启这个按钮
        var btn1 = document.querySelector('button');
        btn1.onclick = function() {
            this.disabled = true; // 这个this 指向的是 btn 这个按钮
            // var that = this; // 原先的写法,使用一个变量接受this
            setTimeout(function() {//定时器是window调用的
                // that.disabled = false; // 定时器函数里面的this 指向的是window
                this.disabled = false; // 此时定时器函数里面的this 指向的是btn
            }.bind(this), 3000); // 这个this 指向的是btn 这个对象
        }
        // 为所有的btn绑定事件
        var btns = document.querySelectorAll('button');
        for (var i = 0; i < btns.length; i++) {
            btns[i].onclick = function () {
                this.disabled = true;
                setTimeout(function () {
                    this.disabled = false;
                }.bind(this), 2000);
            }
        }

三者之间的区别

  • 相同点:
    都可以改变函数内部的this指向.
  • 区别点:
  1. call 和 apply 会调用函数, 并且改变函数内部this指向.
  2. call 和 apply 传递的参数不一样, call 传递参数 aru1, aru2…形式 apply 必须数组形式[arg]
  3. bind 不会调用函数, 可以改变函数内部this指向.
  • 主要应用场景:
  1. call 经常做继承.
  2. apply 经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
  3. bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向.

严格模式

  • JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

  • 严格模式对正常的 JavaScript 语义做了一些更改:

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

开启严格模式

严格模式可以应用到整个脚本或个别函数中。因此在使用时,我们可以将严格模式分为为脚本开启严格模式和为函数开启严格模式两种情况。

为脚本开启严格模式:

<script>
  "use strict";
  console.log("这是严格模式。");
</script>

因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。

<script>
    "use strict";// 因为"use strict"加了引号,所以老版本的浏览器会把它当作一行普通字符串而忽略。
    console.log("这是严格模式");
</script>

有的 script 基本是严格模式,有的 script 脚本是正常模式,这样不利于文件合并,所以可以将整个脚本文件放在一个立即执行的匿名函数之中。这样独立创建一个作用域而不影响其他 script 脚本文件。(在函数的第一个语句写‘use strict’就是为函数开启严格模式)

<script>
    // 将整个脚本文件放在一个立即执行函数里,然后再开启严格模式,独立开启一个作用域,从而不影响其他非严格模式的script文件
    (function () {
        'use strict';
    })();
</script>

为函数开启严格模式:
要给某个函数开启严格模式,需要把“use strict”; (或 ‘use strict’; ) 声明放在函数体所有语句之前。

<script>
    // 此时只是给fn函数开启严格模式
    function fn() {
        'use strict';
        // 下面的代码按照严格模式执行
    }

    function fun() {
        // 里面的还是按照普通模式执行
    }
</script>

严格模式的变化

变量规定
① 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。
② 严禁删除已经声明变量。例如,delete x; 语法是错误的。

    <!-- 变量规定 -->
    <!-- <script>
        // 以前不是用var创建变量也不会报错
        num = 10;
        console.log(num);// 不会报错
        delete num; // 不会报错
    </script> -->
    <script>
        (function () {
            "use strict";
            // num = 10;// 不使用var创建变量会报错
            var num = 10;
            console.log(num);// 报错 Uncaught ReferenceError: num is not defined
            delete num;// 报错 Uncaught SyntaxError: Delete of an unqualified identifier in strict mode.
        })();
    </script>

this指向
① 以前在全局作用域函数中的 this 指向 window 对象。
② 严格模式下全局作用域中函数中的 this 是 undefined。
③ 以前构造函数是不加new也可以调用,当普通函数this 指向全局对象
④ 严格模式下,如果构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错
⑤ new 实例化的构造函数指向创建的对象实例。
⑥ 定时器 this 还是指向 window 。
⑦ 事件、对象还是指向调用者。

    <!-- this指向 -->
    <script>
        (function () {
            console.log(this);// 函数里的this指向window
        })();
        (function () {
            "use strict";
            console.log(this);// 严格模式下的this指向undefined
        })();
    </script>

函数变化
① 函数不能有重名的参数。
② 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数。 非函数代码块:代码块就是if、for循环里面的执行体,也就是说只有再function里面可以再次定义function

"use strict";
if (true) {
  function f() { } // !!! 语法错误
  f();
}
for (var i = 0; i < 5; i++) {
  function f2() { } // !!! 语法错误
  f2();
}
function baz() { // 合法
  function eit() { } // 同样合法
}

更多严格模式要求参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Strict_mode

高阶函数

  • 高阶函数是对其他函数进行操作的函数,它接收函数作为参数或将函数作为返回值输出。
<script>
    // 将函数作为参数
    function fun1(callback) {
        callback&&callback();// "fun1"
    }
    fun1(function () {
        console.log("fun1");
    })

    // 将函数作为返回值
    function fun2() {
        return function fun3() {
            console.log("fun3");
        }
    }
    fun2();
</script>

闭包

  • 闭包(closure)指有权访问另一个函数作用域中变量的函数。 ----- JavaScript 高级程序设计
  • 简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
    闭包的作用:延伸了变量的作用范围
function fn1() { // fn1就是闭包函数
    var num = 10;
    function fn2() {
        console.log(num); // 10
    }
    fn2();
}
fn1();

在函数外部使用函数内的变量:

<script>
    // 我们fn 外面的作用域可以访问fn 内部的局部变量
    // 闭包的主要作用: 延伸了变量的作用范围
    function fn() {
        var num = 10;
        // function fun() {
        //     console.log(num);

        // }
        // return fun;
        return function () {
            console.log(num);
        }
    }
    var f = fn();
    f();
    // 类似于
    // var f = function() {
    //         console.log(num);
    //     }
    // var f =  function fun() {
    //         console.log(num);

    //     }
</script>

递归

  • 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。
  • 简单理解:函数内部自己调用自己, 这个函数就是递归函数
  • 递归函数的作用和循环效果一样
  • 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return。
// 递归函数:函数内部自己调用自己,这个函数就是递归函数
var num = 1;
function fn() {
    console.log('我要打印6句话');
    if (num == 6) {
        return; // 递归里面必须加退出条件
    }
    num++;
    fn();
}
fn();

浅拷贝和深拷贝

  1. 浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用.
  2. 深拷贝拷贝多层, 每一级别的数据都会拷贝.
  3. Object.assign(target, …sources) es6 新增方法可以浅拷贝

浅拷贝

var obj = {
    id: 1,
    name: 'andy',
    msg: {
        age: 18
    }
};
var o = {};
// 使用原生js进行浅拷贝
for (const k in obj) {
    // k 是属性名 obj[k] 是属性值
    o[k] = obj[k];
}
// 因为obj.msg是对象,所以使用浅拷贝拷贝出来的是地址,改变o.msg也会导致obj.msg改变
o.msg.age = 20;
console.log(obj.msg.age);// 20
console.log(o.msg.age);  // 20

// es6中的语法糖,使用Object.assign(拷贝, 被拷贝)方法进行浅拷贝
Object.assign(o, obj);

深拷贝

    var obj = {
        id: 1,
        name: 'andy',
        msg: {
            age: 18
        },
        color: ['pink', 'red']
    };
    var o = {};
    // 使用递归深拷贝
    // 封装函数
    function deepCopy(newobj, oldobj) {
        for (const k in oldobj) {
            // 判断我们的属性值属于那种数据类型
            // 1.获取属性值 oldobj[k]
            var item = oldobj[k];
            // 2.判断这个值是否是数组(因为数组也是对象,所以要把数组放在上面)
            if (item instanceof Array) {
                newobj[k] = [];
                deepCopy(newobj[k], item);
            } else if (item instanceof Object) {
                // 3.判断这个值是否是对象
                newobj[k] = {};
                deepCopy(newobj[k], item);
            } else {
                // 4.属于简单数据类型
                newobj[k] = item;
            }
        }
    }
    deepCopy(o, obj);

正则表达式

特点:

  1. 灵活性、逻辑性和功能性非常的强。
  2. 可以迅速地用极简单的方式达到字符串的复杂控制。
  3. 对于刚接触的人来说,比较晦涩难懂。比如: ^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)*$
  4. 实际开发,一般都是直接复制写好的正则表达式. 但是要求会使用正则表达式并且根据实际情况修改正则表达式. 比如用户名: /1{3,16}$/

创建正则表达式

在 JavaScript 中,可以通过两种方式创建一个正则表达式。

创建正则表达式时不管是数字型还是字符串型都不需要加引号

  1. 通过调用 RegExp 对象的构造函数创建
    var 变量名 = new RegExp(/表达式/);

  2. 通过字面量创建
    var 变量名 = /表达式/;

// 一种是通过对象创建
var regexp1 = new RegExp(/123/);
console.log(regexp1);

// 一种是通过字面量创建
var regexp2 = /123/;
console.log(regexp2);

测试正则表达式

test() 正则对象方法,用于检测字符串是否符合该规则,该对象会返回 true 或 false,其参数是测试字符串。

  • regexObj.test(str)
  • regexObj 是写的正则表达式
    str 我们要测试的文本
    就是检测str文本是否符合我们写的正则表达式规范.
// 使用test()方法验证
var yz = regexp1.test(123);// true
console.log(yz);

特殊字符

一个正则表达式可以由简单的字符构成,比如 /abc/,也可以是简单和特殊字符的组合,比如 /ab*c/ 。其中特殊字符也被称为元字符,在正则表达式中是具有特殊意义的专用符号,如 ^ 、$ 、+ 等。
特殊字符非常多,可以参考:

  • MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
  • jQuery 手册:正则表达式部分
  • 正则测试工具: http://tool.oschina.net/regex

边界符

正则表达式中的边界符(位置符)用来提示字符所处的位置,主要有两个字符。
在这里插入图片描述

如果 ^ 和 $ 在一起,表示必须是精确匹配。

// 边界符 ^ $ 
var rg = /abc/; // 正则表达式里面不需要加引号 不管是数字型还是字符串型
// /abc/ 只要包含有abc这个字符串返回的都是true
console.log(rg.test('abc'));
console.log(rg.test('abcd'));
console.log(rg.test('aabcd'));
console.log('---------------------------');
var reg = /^abc/;
console.log(reg.test('abc')); // true
console.log(reg.test('abcd')); // true
console.log(reg.test('aabcd')); // false
console.log('---------------------------');
var reg1 = /^abc$/; // 精确匹配 要求必须是 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

字符类

字符类表示有一系列字符可供选择,只要匹配其中一个就可以了。所有可供选择的字符都放在方括号内。

  • 方括号[]
    /[abc]/.test('andy') // true
    后面的字符串只要包含 abc 中任意一个字符,都返回 true 。

  • 方括号内部 范围符- :[-]
    /^[a-z]$/.test(c') // true
    方括号内部加上 - 表示范围,这里表示 a 到 z 26个英文字母都可以。

  • 方括号内部 取反符^ :[^]
    /[^abc]/.test('andy') // false
    方括号内部加上 ^ 表示取反,只要包含方括号内的字符,都返回 false 。
    注意和边界符 ^ 区别,边界符写到方括号外面

  • 字符组合
    /[a-z1-9]/.test('andy') // true
    方括号内部可以使用字符组合,这里表示包含 a 到 z 的26个英文字母和 1 到 9 的数字都可以。

  • 量词符
    量词符用来设定某个模式出现的次数。
    在这里插入图片描述

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

  • 预定义类
    预定义类指的是某些常见模式的简写方式。
    在这里插入图片描述

工具网站:https://c.runoob.com/

替换

replace() 方法可以实现替换字符串操作,用来替换的参数可以是一个字符串或是一个正则表达式。

stringObject.replace(regexp/substr,replacement)

  1. 第一个参数: 被替换的字符串 或者 正则表达式
  2. 第二个参数: 替换为的字符串
  3. 返回值是一个替换完毕的新字符串
    表达式参数:/表达式/[switch]
    switch(也称为修饰符) 按照什么样的模式来匹配. 有三种值:
  • g:全局匹配
  • i:忽略大小写
  • gi:全局匹配 + 忽略大小写
var text = document.querySelector('textarea');
var btn = document.querySelector('button');
var div = document.querySelector('div');
btn.onclick = function() {
    div.innerHTML = text.value.replace(/激情|gay/g, '**');
}

变量

ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。有三个关键字可以声明变量:var、let、const。其中var在ECMAScript的所有版本中都可以使用,而let和const只能在ECMAScript6中使用。

  • let:声明块级作用域变量

    • 防止循环变量变成全局变量
    • 使用let关键字声明的变量没有变量提升
    • 使用let关键字声明的变量具有暂时性死区特性
  • const: 声明常量

    • 具有块级作用域
    • 声明常量时必须赋值
    • 常量赋值后,值不能修改

var

var message

声明名为message的变量,可以用于保存任何类型的值(不初始化的情况下,变量会保存一个特殊的值undefind)

var message="hi";
message=100;//合法但不推荐

虽然不推荐改变保存变量保存值的类型,但这在ECMAScript中是完全有效的。

var作用域

var操作符定义的变量会成为包含它的函数的局部变量

 function test() {
 var message="hi";
}
console.log(message);//出错

这里message变量是函数内部使用var定义,意味该变量将在函数退出时被销毁,所以打印message会报错

var声明提升

使用var时下面的代码不会报错。因为使用var声明的变量会自动提升到函数作用域顶部:

function foo () {
console.log(age);
var age=20;
}
foo();
//类似于
function foo () {
var age;
console.log(age);
var age=20;
}
foo();//undefined

这就是所谓的"提升",也就是把所有的变量声明拉到函数作用域的顶部。此外,反复使用多次var也没问题。

function foo() {
	var age = 16;
	var age = 26;
	var age = 36;
}

let

let 跟 var作用差不多,但有着非常重要的区别。最明显的区别是
let:声明范围是块

// let是块级作用域,并且变量不会得到提升
if (true) {
    var a = 10;
}
console.log(a); // 10
if(true){
    let b = 20;
}
console.log(b); // 报错

在这里,age变量之所以不能在if块外部被引用,是因为它的作用域仅限于该块内部。
1)块作用域是函数作用域的子集,因此适用于var的作用域限制同样也适用于let
2)let也不允许同一块作用域中出现冗余声明,会导致报错

var name;
var name;// 不会报错

let age;
let age;//SyntaxError:标识符age已经声明过

暂时性死区

let 与 var的另一个重要的区别就是let声明的变量不会在作用域中被提升

//name会被提升
console.log(name);//undefined
var name ="Matt";

//age不会被提升
console.log(age);//ReferenceError: age 没有定义
let age=26;

在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为"暂时性死区",在此阶段引用任何后面才声明的变量都会抛出ReferenceError。

var num = 10
if (true) {
    console.log(num);// 报错,因为此块状区域类有使用let来声明了num,所以这里的num就不会往外面找,只会在此区域找,又因为let在console.log后面,所以产生错误
    let num = 20;
}

全局声明

与var关键字不同,使用let在全局作用域中声明的变量不会成为window对象的属性(var 声明的变量则会)

var name ="xiao";
cosole.log(window.name);//"xiao"

let age= 20;
console.log(window.age);//undefined

不过,let声明仍然是在全局作用域中发生的,相应变量会在页面的生命周期内存续。因此,为了避免SyntaxError,必须确保页面不会重复声明同一个变量。

条件说明

在使用var声明变量时,由于声明会被提升,JavaScript引擎会自动将多余的声明在作用域顶部合并为一个声明。因为let的作用域是块,所以不可能检查前面是否已经使用let声明过同名变量,同时也就不可能在没有声明的情况下声明它。

<script>
var name= "Continue"
let age=20
</script>

<script>
//假设脚本不确定页面中是否已经声明了同名名量
//那它可以假设还没有声明过

var name ="Continue"
//这里没问题,因为可以被作为一个提升声明来处理
//不需要检查之前是否声明过同名变量

let age=20;
//如果age之前声明过,这里会报错
</script>

for循环中的let声明

在let出现之前,for循环定义的迭代变量会渗透到循环体外部:

for(var i=0;i<5;++i){
}
console.log(i);//5

改成使用let之后,这个问题就消失了,因为迭代变量的作用域仅限于for循环块内部:

for(let i=0;i<5;++i){
}
console.log(i);//ReferrenceError:i 没有定义

在使用var的时候,最常见的问题就是对迭代变量的奇特声明和修改:

for(var i = 0; i< 5; ++i){
  setTimeout(()=>console.log(i), 0)
}
//实际上输出5、5、5、5、5

之所以会这样,是因为在退出循环时,迭代变量保存的是导致循环退出的值:5。在之后执行超时逻辑时,所有i都是同一个变量,因而输出的都是同一个最终值。
而在使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout引用的都是不同的变量实例。

for(let i = 0; i< 5; ++i){
  setTimeout(()=>console.log(i), 0)
}
//实际上输出0、1、2、3、4

const

const的行为与let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行错误

const age=20;
age=22;//TypeError:给常量赋值

//const也不允许重复声明
const name="Continue";
const name="xiao"//SyntaxError

//const声明的作用域也是块
const name="Continue"
if(true){
const name="xiao"
}
console.log(name);//Continue

const声明的限制只适用于它指向的变量的引用。换句话说,如果const变量引用的是一个对象,那么修改这个对象的内部属性并不违反const的限制。

const person(){};
person.name="Continue";

即使JavaScript引擎会为for循环中的let声明分别创建独立的变量实例,而且const变量跟let变量很相似,也不能用const来声明迭代变量(因为迭代变量会自增):

for(const i = 0; i< 5; ++i){
}//TypeError:给常量赋值

声明风格及最佳实践

  1. 不使用var
    有了let和const,大多数开发者会发现不需要var了。限制自己只使用let和const有助于提升代码质量,因为变量有了明确的作用域、声明位置、以及不变的值。

  2. const优先,let次之
    使用const声明可以让浏览器运行时强制保持质量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,在使用let。这样可以让开发者更有信心的判断某些变量的值永远不会变,同时也能迅速的发现因意外赋值导致的非预期行为。

解构赋值

ES6中允许从数组中提取值,按照对应位置,对变量赋值。对象也可以实现解构。(算是一种定义变量的语法糖)

数组解构

        // 不使用数组解构赋值
        // let a = 1;
        // let b = 2;
        // let c = 3;
        // 数组解构
        let [a, b, c] = [1, 2, 3];
        console.log(a);// 1
        console.log(b);// 2
        console.log(c);// 3
        // 扩展
        let arr = [4, 5, 6];
        let [d, e, f] = arr;
        console.log(d); //4
        console.log(e); //5
        console.log(f); //6

如果变量名和值的数量不对应, 则值为underfined

对象解构

let person = {
    name : "zhangsan",
    age : 27
}
let {name, age, like} = person;
console.log(name);// zhangsan
console.log(age); // 27
console.log(like);// undefined

let {name : myname } = person;// 这里的myname属于别名;
console.log(myname); // zhangsan

箭头函数

ES6中新增的定义函数的方式。

定义

  1. 普通语法:

    () => {} 
    const fn = () => {}
    
  2. 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号和return关键字

    	function sum(num1, num2) { 
    	     return num1 + num2; 
    	}
    	const sum = (num1, num2) => num1 + num2; 
    
  3. 如果形参只有一个,可以省略小括号

function fn (v) {
     return v;
} 
const fn = v => v;

实际应用

        // 定义一个普通的箭头函数
        const fn1 = () =>{
            console.log("这是一个普通的箭头函数");
        }
        // 调用
        fn1();

        // 函数体中只有一句代码,且代码的执行结果就是返回值,可以省略大括号和return关键字
        const fn2 = (num1, num2) => num1 + num2;
        console.log(fn2(2, 3));

        // 如果形参只有一个,可以省略小括号
        const fn3 = num => {
            console.log(num);
        }
        fn3(20);

this关键字

箭头函数不绑定this,箭头函数没有自己的this关键字,如果在箭头函数中使用this,this关键字将指向箭头函数定义位置中的this

// 箭头函数不绑定this 箭头函数没有自己的this关键字 如果在箭头函数中使用this this关键字将指向箭头函数定义位置中的this
function fn4(){
    console.log(this);
    return () =>{
        console.log(this); // 这里的this会指向fn4里面的this
    }
}
const obj = {
    name : "zhangsan",
    age : 24
}
const fn5 = fn4(); // Window
const fn6 = fn4.call(obj); // obj
fn5(); // Window
fn6(); // obj

注意:对象是不能产生作用域的,所以在对象中创建箭头函数,这时候箭头函数的this指向的是对象外面的this。(这里对象外面的this指向window)

var age = 100;
var obj = {
    age: 20,
    say: () => {
        alert(this.age)
    }
}
obj.say(); 打印100

剩余参数

剩余参数语法允许我们将一个不定数量的参数表示为一个数组。

function sum (first, ...args) {
     console.log(first); // 10
     console.log(args); // [20, 30] 
 }
 sum(10, 20, 30)
		...args就代表剩余参数,但是打印不需要添加三个点
        // 定义一个可以随意添加参数的sum方法
        let sum = (...agrs) => {
            let total = 0;
            // 只有一个形参,所以item两边的括号省略
            agrs.forEach(item => {
                total += item;
            })
            return total;
        }
        sum(10, 20, 30);

剩余参数和解构配合使用

 // 剩余参数和解构配合使用
    let ary1 = ['张三' , '李四', '王五'];
    let [s1, ...s2] = ary1;
    console.log(s1);// 张三
    console.log(s2);// [李四,王五]

内置对象扩展

扩展运算符

扩展运算符(…)可以将数组或者对象转为用逗号分隔的参数序列。

        // 扩展运算符可以将数组或者对象转为用逗号分隔的参数序列。
        let arr1 = [1, 2, 3];
        console.log(...arr1);// 1, 2, 3

        // 合并数组
        let arr2 = [2, 3, 4];
        let arr3 = [3, 4, 5];
        // 方法1:
        let arr4 = [...arr1, ...arr2, ...arr3];
        console.log(arr4);// [1, 2, 3, 2, 3, 4, 3, 4, 5]
        // 方法2:
        arr2.push(...arr3);
        console.log(arr2);// [2, 3, 4, 3, 4, 5]

        // 利用扩展运算符将伪数组转换为真正的数组
        var oDivs = document.getElementsByTagName('div');
        console.log(oDivs)
        var ary = [...oDivs];
        ary.push('a');
        console.log(ary);

Array扩展方法

Array.from()

Array.from()方法可以将伪数组转换成真正的数组

// 定义一个伪数组,伪数组的序号也要从0开始
let wsz = {
    "0" : "2",
    "1" : "4",
    "2" : "6",
    "length" : 3
}
// 调用方法将伪数组转换成真正的数组
let sz = Array.from(wsz);
console.log(sz);

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

let sz2 = Array.from(wsz, item => item * 2);
console.log(sz2);

Array.find()

用于找出第一个符合条件的数组成员,如果没有找到返回undefined

let arr = [
    {
        id : 1,
        name : "zhangsan"
    },{
        id : 2,
        name : "lisi"
    }
]
let res1 = arr.find(item => item.id == 2);
let res2 = arr.find(item => item.id == 3);
console.log(res1);// id: 2 name: "lisi"
console.log(res2);// undefined

Array.findIndex()

用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
// 用于找出第一个符合条件的数组成员的位置,如果没有找到返回-1
let arr = [1, 3, 4, 5, 7, 10];
let index = arr.findIndex(item => item > 6);
console.log(index);// 4

Array.includes()

表示某个数组是否包含给定的值,返回布尔值。
// 表示某个数组是否包含给定的值,返回布尔值。
let arr = [0, 1, 3];
console.log(arr.includes(2));// false
console.log(arr.includes(1));// true

String扩展方法

  • startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
  • endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
  • repeat():表示将原字符串重复n次,返回一个新字符串。
let str1 = "Hello world !";
// startsWith():表示参数字符串是否在原字符串的头部,返回布尔值
console.log(str1.startsWith("Hello"));// true
// endsWith():表示参数字符串是否在原字符串的尾部,返回布尔值
console.log(str1.endsWith("!"));// true
// repeat():表示将原字符串重复n次,返回一个新字符串。
let str2 = str1.repeat(2);
console.log(str2);// Hello world !Hello world !

模板字符串

ES6新增的创建字符串的方式,使用反引号定义。

let name = `zhangsan`;

模板字符串中可以解析变量。

let name = `张三`;
let sayHello = `Hello, 我的名字叫${name}`;
console.log(sayHello);
模板字符串中可以换行
let result = {
    name: "zhangsan",
    age: 20
};
let html = `
    <div>
        <span>${result.name}</span>
        <span>${result.age}</span>
    </div>
`;
console.log(html);
在模板字符串中可以调用函数。
const fn = () => {
    return '我是fn函数'
}

let html = `我是模板字符串 ${fn()}`;
console.log(html)

Set数据结构

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

创建:
Set本身是一个构造函数,用来生成 Set 数据结构。
let set1 = new Set();
Set函数可以接受一个数组作为参数,用来初始化。
let set2 = new Set([1, 2, 3, 4]);

  • 利用Set实现数组去重
// 利用set的数据解构实现数组去重
    let arr1 = [1, 2, 3, 4, 5, 5, 6, 4, 2, 1];
    let set3 = new Set([...arr1]);
    console.log(set3);
    // 将set数据解构转换成数组
    let arr2 = [...set3];
    console.log(arr2);

常用方法:

  • size 返回集合的元素个数
  • add(value):添加某个值,返回 Set 结构本身
  • delete(value):删除某个值,返回一个布尔值,表示删除是否成功
  • has(value):返回一个布尔值,表示该值是否为 Set 的成员
  • clear():清除所有成员,没有返回值
const set4 = new Set();
// 向set结构中添加值 使用add方法
set4.add('a').add('b');
console.log(set4.size)
// 从set结构中删除值 用到的方法是delete
const r1 = set4.delete('c');
console.log(set4.size)
console.log(r1);
// 判断某一个值是否是set数据结构中的成员 使用has
const r2 = set4.has('d');
console.log(r2)
// 清空set数据结构中的值 使用clear方法
set4.clear();
console.log(set4.size);

遍历:
可以使用foreach来遍历set数组

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

Map 数据结构

ES6提供了 Map 数据结构,它类似于对象,也是键值对的集合。但是”键“的范围不限于字符串,各种类型的值(包括对象)都可以当作键,Map也实现了Iterator接口,所以可以使用 扩展运算符 和 for of 进行遍历。

创建:通过new创建
let map1 = new Map();
常用方法:

  • size 返回Map的元素个数
  • set 增加一个新元素,返回当前Map
  • get 返回对象的键值
  • has 检测Map中是否包含某个元素,返回boolean值
  • delte 删除键值对
  • claear 清空Map
        let m = new Map();
        // 1. size 返回Map的元素个数
        console.log(m.size);
        // 2. set 增加一个新元素,返回当前Map
        m.set('name', 'xy');
        m.set('age', 20);
        m.set('eat', function () {
            console.log('吃饭');
        });
        console.log(m);
        // 3. get 返回对象的键值
        console.log(m.get('name'));
        // 4. has 检测Map中是否包含某个元素,返回boolean值
        console.log(m.has('name'));
        // 5. delte 删除键值对
        m.delete('name');
        console.log(m);
        // 6. claear 清空Map
        m.clear();
        console.log(m);

前端模块化

模块化是一种思想,是一种解决问题的思路。

随着业务的发展我们的系统可能越来越复杂,那我们如何保证在复杂系统中代码可以方便维护、功能可以复用呢?模块化思想可以解决这个问题呀,可以将我们的复杂系统分解为可管理的模块、每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能

在前端想要将一个js文件归为一个模块,则需要使用<script>标签,并给他设置type类型为type="module"

<script src="./aaa.js" type="module">

挂载

在模块中使用export关键字定义(挂载)导出的变量、对象、函数

var name = '小明'
var age = 18
var flag = true

function sum(num1, num2) {
  return num1 + num2
}

if (flag) {
  console.log(sum(20, 30));
}

// 1.导出方式一:
export {
  flag, sum
}

// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88


// 3.导出函数/类
export function mul(num1, num2) {
  return num1 * num2
}

export class Person {
  run() {
    console.log('在奔跑');
  }
}

// 5.export default
// const address = '北京市'
// export {
//   address
// }
// export const address = '北京市'
// const address = '北京市'
//
// export default address

// 默认导出一个fun
export default function (argument) {
  console.log(argument);
}

导入

使用关键字importfrom来确定在什么模块中导入什么

// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";

if (flag) {
  console.log('小明是天才, 哈哈哈');
  console.log(sum(20, 30));
}

// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";

console.log(num1);
console.log(height);

// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";

console.log(mul(30, 50));

const p = new Person();
p.run()

// 4.导入 export default中的内容
import addr from "./aaa.js";

addr('你好啊');

// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
// * 代表通配符,as aaa表示导出对象取名为aaa
import * as aaa from './aaa.js'
// 使用变量
aaa.flag

Promise

callback hell(回调地狱):一个异步请求依赖于另外一个异步请求的结果
文件的读取无法判断执行顺序(文件的执行顺序是依据文件的大小来决定的)(异步api无法保证文件的执行顺序)

// 无法保证读取顺序,a->b->c、a->c->b、c->b->a都有可能
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})
fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})
fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
})

通过回调嵌套的方式来保证顺序:

// 通过回调嵌套保证读取顺序,这样读取就一定是a->b->c
// 这也就是callback hell,回调地狱
fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
    if (err) {
        // 抛出异常
        throw err
    }
    console.log(data)
    fs.readFile('./data/b.txt', 'utf-8', (err, data) => {
        if (err) {
            // 抛出异常
            throw err
        }
        console.log(data)
        fs.readFile('./data/c.txt', 'utf-8', (err, data) => {
            if (err) {
                // 抛出异常
                throw err
            }
            console.log(data)
        })
    })
})

为了解决以上编码方式带来的问题(回调地狱嵌套),所以在EcmaScript6新增了一个API:Promise

  • Promise:承诺,保证
  • Promise本身不是异步的,但往往都是内部封装一个异步任务
  • Promise有三种状态
    • pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
    • fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
    • reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()

基本语法:

new Promise()需要往里面传递一个函数,这个函数就是异步任务,但是这个函数接受两个参数(resolve,reject这两个参数本身就是函数)

异步请求有返回值:使用resolve将返回值传递出来,传递多少个返回值,then函数的参数就有多少个形参

console.log(1)
// 创建Promise容器,容器当中一般存放一个异步任务,并且返回一个实例,Promise容器一旦创建,就会开始执行里面的方法
// 容器接受两个参数,resolve:解决,就是成功、reject:驳回,就是失败
const p1 = new Promise((resolve, reject) => {
    // 异步任务
    fs.readFile('./data/a.txt', 'utf-8', (err, data) => {
        if (err) {
            // 失败了,Promise容器中的任务失败了
            // console.log(err)
            // 将容器的Pending状态改为reject
            reject(err)
        } else {
            console.log(3)
            // Promise容器中的任务成功了
            // console.log(data)
            // 将容器的Pending状态改为resolve
            resolve(data)
        }
    })
})
console.log(4)
// 打印顺序为1243,因为Promise本身不是异步的,但是里面会有异步的任务

// 使用p1.then()方法来做指定的操作,方法两个回调函数,一个是resolve情况下的处理方法,一个是reject情况下的处理方法
p1
    .then((data) => {
        console.log('文件读取成功:' + data)
    }, (err) => {
        console.log('文件读取失败:' + err)
    })

异步请求没有返回值:使用resolve进行占位

// 将一个定时器回调多次
new Promise((resolve, reject) => {
    setTimeout(() => {
  		// 使用resolve()进行占位,后面使用then()再将函数体进行替换      
        resolve()
    },1000)
}).then(() => {
    console.log('hello world')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        },1000)
    })
}).then(() => {
    console.log('hello vue')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
        },1000)
    })
}).then(() => {
    console.log('hello node')
})

链式循环:

封装Promise的readFile

const fs = require('fs')

let pReadFile = (filePath) => {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, 'utf-8', (err, data) => {
            if (err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}

pReadFile('./data/a.txt')
    .then((data) => {
        console.log(data)
        // 在返回一个pReadFile方法并且设置读取b.txt文件,这样就从pReadFile('./data/a.txt')方法变成了pReadFile('./data/b.txt')
        return pReadFile('./data/b.txt')
    })
    .then((data) => {
        console.log(data)
        // 在返回一个pReadFile方法并且设置读取c.txt文件
        return pReadFile('./data/c.txt')
    })
    .then((data) => {
        console.log(data)
    })

jQuery的ajax方法也支持Promise

var data = {}
// path是请求路径
$.get(path1)
    .then((userData) => {
        data.userData = userData
        return $.get(path2)
    })
    .then((jobData) => {
        // 在这里是不能拿到userData的数据的,只能在外面再声明一个变量
        data.jobData = jobData
    })

mongoose所有的API都支持Promise:

// 查询所有
User.find()
	.then(function(data){
        console.log(data)
    })

注册:

User.findOne({username:'admin'},function(user){
    if(user){
        console.log('用户已存在')
    } else {
        new User({
             username:'aaa',
             password:'123',
             email:'fffff'
        }).save(function(){
            console.log('注册成功');
        })
    }
})
User.findOne({
    username:'admin'
})
    .then(function(user){
        if(user){
            // 用户已经存在不能注册
            console.log('用户已存在');
        }
        else{
            // 用户不存在可以注册
            return new User({
                username:'aaa',
                password:'123',
                email:'fffff'
            }).save();
        }
    })
    .then(funciton(ret){
		console.log('注册成功');
    })

  1. a-z0-9_- ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值