js3之高级面向对象

1 面向对象

1.1 概述

1.1.1 面向过程 POP

面向过程编程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候再一个一个的依次调用就可以了

就是按照我们分析好了的步骤,一步一步去解决问题

1.1.2 面向对象 OOP

面向对象编程是把事务分解成为一个个对象,然后由对象之间分工与合作

面向对象是以对象功能来划分问题,而不是步骤

面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目

特性:封装性、继承性、多态性

1.1.3 区别

面向过程:

  • 优点:性能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程
  • 缺点:没有面向对象易维护、易复用、易扩展

面向对象:

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
  • 缺点:性能比面向过程低

2 ES6中的类和对象

2.1 对象

在 JavaScript 中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串、数值、数组、函数等

对象是由属性和方法组成的:

  • 属性:事物的特征,在对象中用属性来表示(常用名词)
  • 方法:事物的行为,在对象中用方法来表示(常用动词)

2.2 类

在 ES6 中新增加了类的概念,可以使用 class 关键字声明一个类,之后以这个类来实例化对象

类抽象了对象的公共部分,它泛指某一大类(class)

对象特指某一个,通过类实例化一个具体的对象

2.2.1 面向对象的思维特点:

1、 抽取(抽象)对象共用的属性和行为组织(封装)成一个(模板)

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

2.2.2 创建类

 // 1. 创建类 class 
class Person {
    // 类的共有属性放到 constructor 里面
    constructor(uname, age) {
        this.uname = uname;
        this.age = age
    }
     // 类中的方法:注意不要加 functon
    sayHi() {
        console.log('你好' + this.uname);
    }
}
// 2. 利用类创建对象 new
var p = new Person('Tom', 18);
p.sayHi(); // 返回:你好Tom
console.log(p.uname,p.age); // 返回:Tom  18

1、 通过 class 关键字创建类,类名一般是首字母大写

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

3、 constructor 函数: 只要 new 生成实例时,就会自动调用这个函数,如果我们不写这个函数,类也会自动生成这个函数

4、 类生成实例必须使用 new

5、 语法规范:

​ 创建类,类名后面不加小括号;

​ 生成实例,类名后面加小括号;

​ 构造函数不需要加function

6、 注意事项:

  • 在 ES6 中类没有变量提升,所以必须先定义类,才能通过类实例化对象
  • 类里面的共有属性和方法一定要加this使用

constructor 里面的this:指向实例对象

方法里面的this :指向这个方法的调用者

2.2.3 继承类

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

    // 1. 创建类 class 
    class Person {
      constructor(uname, age, x, y) {
        this.uname = uname;
        this.age = age;
        this.x = x;
        this.y = y;
      }
      // 类中的方法:注意不要加 functon
      sayHi() {
        console.log('你好' + this.uname);
      }
      sum() {
        console.log(this.x + this.y);
      }
    }
    // 类Member 继承类 Person
    class Member extends Person { }

    // 2. 利用类创建对象 new
    var m = new Member('Tom', 18, 1, 2);
    m.sayHi();  // 返回:你好Tom
    m.sum();  // 返回: 3
    console.log(m.uname, m.age); // 返回:Tom  18

2.2.4 super 关键字

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

// 父类
class Person {   
      constructor(surname){
         this.surname = surname;
     }
} 
// 子类继承父类
class  Student extends Person {       
     constructor(surname,firstname){
          // 调用父类的constructor(surname)
          super(surname);     
          // 定义子类独有的属性
	      this.firstname = firstname; 
     }
}       

子类在构造函数中使用super, 必须放到 this 前面 (必须先调用父类的构造方法,再使用子类构造方法)

    // 1. 创建类 class 
    class Person {
      constructor(uname, age) {
        this.uname = uname;
        this.age = age;
      }
      sayHi() {
        return '你好' + this.uname;
      }
    }
    // 类Member 继承类 Person
    class Member extends Person {
      constructor(uname, age, sex) {
        // 1.  使用 super 调用父类的 构造函数
        super(uname, age);
        this.sex = sex;
      }
      sayHi() {
        // 2.  使用 super 调用父类的 普通函数
        console.log(super.sayHi() + ',我今年' + this.age + '岁了,性别:' + this.sex);
      }
    }

    // 2. 利用类创建对象 new
    var m = new Member('Tom', 18, '男');
    m.sayHi();  // 返回:你好Tom,我今年18岁了,性别:男

3 构造函数和原型

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

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

创建对象可以通过以下三种方式:

  • 对象字面量 var obj={}
  • new Object()
  • 自定义构造函数

3.1 构造函数

构造函数是一种特殊的函数,主要用来初始化对象,即为对象成员变量赋初始值,它总与 new 一起使用。我们可以把对象中一些公共的属性和方法抽取出来,然后封装到这个函数里面

new 在执行时会做四件事情:

  • 1、 在内存中创建一个新的空对象
  • 2、 让 this 指向这个新的对象
  • 3、 执行构造函数里面的代码,给这个新对象添加属性和方法
  • 4、 返回这个新对象(所以构造函数里面不需要 return )
    // 1. 创建 构造函数
    function Person(uname, age) {
      // 创建 属性  
      this.uname = uname;
      this.age = age;
      
      // 创建 方法  
      this.sayHi = function () {
        console.log('你好' + this.uname);
      }
    }

    // 2. 实例化 对象
    var p = new Person('小明', 20);
    p.sayHi(); // 返回:你好小明
    console.log(p.uname, p.age); // 返回:小明 20

3.2 静态成员和实例成员

JavaScript 的构造函数中可以添加一些成员,可以在构造函数本身上添加,也可以在构造函数内部的 this 上添加。通过这两种方式添加的成员,就分别称为静态成员和实例成员

静态成员

构造函数本身上面添加的成员称为静态成员只能由构造函数本身来访问

实例成员

构造函数内部,通过this创建的对象成员称为实例成员,只能由实例化的对象来访问

// 1. 创建 构造函数
// 构造函数中的 属性和方法 我们称为成员, 成员可以添加:通过构造函数this添加和通过构造函数本身添加
function Person(uname, age) {
    // 1.实例成员就是构造函数内部通过this添加的成员 uname age sing 就是实例成员
    // 实例成员只能通过实例化的对象来访问:就是如果想要访问 uname age sing ,只能通过实例化对象 p 来访问
    this.uname = uname;
    this.age = age;
    this.sayHi = function () {
        console.log('你好' + this.uname);
    }
}

// 2. 实例化对象
var p = new Person('小明', 20);
// 访问 实例成员
p.sayHi(); // 返回:你好小明
console.log(p.uname, p.age); // 返回:小明 20


// 3. 静态成员 在构造函数本身上添加的成员  sex 就是静态成员
Person.sex = '男';
// 静态成员只能通过构造函数来访问,不能通过实例对象访问
console.log(Person.sex); // 返回:男
console.log(p.sex); // 不能通过实例对象访问 返回:undefined

3.3 构造函数的原型对象 prototype

构造函数通过原型分配的函数是所有对象所共享的,可以利用这一特性,解决构造函数浪费内存的问题

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

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

3.3.1 原型对象的this指向

1、 构造函数中的this 指向 的是 实例对象

2、 构造函数里面的原型对象,这个方法里面的this 指向的是 这个方法的调用者, 也就是这个实例对象

function Person(uname, age) {
    this.uname = uname;
    this.age = age;
    // 1.  构造函数中的this 指向实例对象
    console.log(this); // 指向:Person
}

Person.prototype.sing = function () {
    console.log('我会唱歌');
    // 2.  原型对象里面的 this 指向函数的调用者 p ,也是这个实例对象
    console.log(this); // 指向:Person
}

var p = new Person('小明', 20);
p.sing();

3.3.2 原型对象扩展内置对象

我们可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求偶数和的功能

数组和字符串内置对象不能采用直接覆盖的方式 Array.prototype = {},只能是 Array.prototype.xxx = function(){} 这样的方式

// 原型对象的应用: 扩展内置对象中的方法
// 给Array数组添加个求和的方法
Array.prototype.sum = function () {
    var sum = 0;
    for (var i = 0; i < this.length; i++) {
        sum += this[i];
    }
    return sum;
};
var arr = [1, 2, 3];
console.log(arr.sum()); // 返回:6

// 此时 Array的原型对象上面就有了 sum 这个方法
console.log(Array.prototype);

3.4 对象原型 __proto__

对象都会有一个属性 __proto__ ,指向构造函数的 prototype 原型对象:所以我们就可以使用构造函数 prototype原型对象的属性和方法

// 1. 创建 构造函数
function Person(uname, age) {
    this.uname = uname;
    this.age = age;
    this.sayHi = function () {
        console.log('你好' + this.uname);
    }
}
// 在构造函数的原型对象上添加 sing 方法
Person.prototype.sing = function () {
    console.log('我会唱歌');
}
// 实例化对象
var p = new Person('小明', 20);

// 对象可以直接访问构造函数的原型对象上的方法原因是:对象都会有一个属性  __proto__ (对象原型),它指向构造函数的 prototype 原型对象
p.sing(); // 返回:我会唱歌 
console.log(p.__proto__ === Person.prototype); // 返回:true

// 方法的查找规则: 首先先看 p实例对象身上是否有 sing 方法,如果有就执行这个对象上的sing
// 如果没有sing 这个方法,因为有__proto__ 的存在,就会去构造函数原型对象prototype身上去查找sing这个方法

对象原型 __proto__ 和原型对象 prototype 是等价的

__proto__ 对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype

image

3.5 constructor 构造函数

对象原型 __proto__和构造函数prototype原型对象里面都有一个 constructor 属性 ,constructor 我们称为构造函数,因为它指回构造函数本身

constructor 主要用于指明了当前对象引用的是哪个构造函数,它可以让原型对象重新指向原来的构造函数。

一般情况下,对象的方法都在构造函数的原型对象中设置。如果有多个对象的方法,我们可以给原型对象采取对象形式赋值,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象 constructor 就不再指向当前构造函数了。此时,我们可以在修改后的原型对象中,添加一个 constructor 指向原来的构造函数

// 1. 创建 构造函数
function Person(uname, age) {
    this.uname = uname;
    this.age = age;
}

// 下面在原型对象上添加方法的方式比较繁琐
/*

     // 在原型对象上添加 sing 方法
    Person.prototype.sing = function () {
      console.log('我会唱歌');
    }
    // 在原型对象上添加 dance 方法
    Person.prototype.dance = function () {
      console.log('我会跳舞');
    }

    */


// 如果想要在原型对象上面添加多个方法,可以采用对象的方式
Person.prototype = {
    // 注意:如果我们修改了原来的原型对象,给原型对象赋值的是一个对象,则必须手动的利用constructor指回原来的构造函数
    constructor: Person,
    sing: function () { console.log('我会唱歌1'); },
    dance: function () { console.log('我会跳舞2'); },
}

// 创建一个实例对象 
var p = new Person('小明', 20);
p.sing(); // 返回:我会唱歌 
p.dance(); // 返回:我会跳舞 

// 1.1  原型对象和对象原型 都有一个 constructor 属性
console.log(Person.prototype);
console.log(p.__proto__);

// 1.2  而且这个 constructor 属性都指向了构造函数本身 Person
console.log(Person.prototype.constructor);
console.log(p.__proto__.constructor);

3.6 原型链

image

function Star(uname, age) {
    this.uname = uname;
    this.age = age;
}
Star.prototype.sing = function() {
    console.log('我会唱歌');
}
var ldh = new Star('刘德华', 18);
// 1. 只要是对象就有__proto__ 原型, 指向原型对象
console.log(Star.prototype);
console.log(Star.prototype.__proto__ === Object.prototype);
// 2.我们Star原型对象里面的__proto__原型指向的是 Object.prototype
console.log(Object.prototype.__proto__);
// 3. 我们Object.prototype原型对象里面的__proto__原型  指向为 null

3.7 对象成员查找规则

1、 当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性或方法

2、 如果没有,就查找它的原型(也就是 __proto__指向的 prototype 原型对象)

3、 如果还没有,就查找原型对象的原型(Object的原型对象)

4、 依此类推一直找到 Object 为止(null)。

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线

4 继承

4.1 call 函数

主要作用:调用函数,并且修改函数运行时 this的 指向

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

thisArg :当前调用函数 this 的指向对象
arg1,arg2:传递的其他参数

function fun() {
    console.log('你好');
    console.log(this); // this指向的是window对象
}
fun();
function fun(a, b) {
    console.log('你好');
    console.log(this); // this指向的是 obj对象
}
var obj = {
    name: 'Tom'
}
// call() 可以改变这个函数的this指向: 此时这个函数的this 就指向了obj这个对象
fun.call(obj);

4.2 继承

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

4.2.1 借用 构造函数 继承 父类型属性

核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性

// 1. 父级 构造函数
function Father(uname, age) {
    // this 指向父级构造函数的对象实例
    this.uname = uname;
    this.age = age;
}

// 2. 子级 构造函数
function Son(uname, age, sex) {
    // this 指向子级构造函数的对象实例

    // 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性
    Father.call(this, uname, age);

    // 定义自己的 属性
    this.sex = sex;
}

var son = new Son('Tom', 18, '男');
console.log(son);

4.2.2 借用 原型对象 继承 父类型方法

一般情况下,对象的方法都在构造函数的原型对象中设置的,所以通过构造函数是无法继承父类方法

借用 原型对象 继承 父类型方法 核心原理:

1、 将父类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类()

2、 将子类的 constructor 重新指向子类的构造函数

3、 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象

// 父级 构造函数
function Father(uname, age) {
    // this 指向父级构造函数的对象实例
    this.uname = uname;
    this.age = age;
}
// 创建父类 原型对象 方法
Father.prototype.sing = function () {
    console.log('我会唱歌--Father的方法')
}

// 子级 构造函数
function Son(uname, age, sex) {
    // this 指向子级构造函数的对象实例

    // 1.  继承父类的 属性
    // 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性
    Father.call(this, uname, age);

    // 定义自己的 属性
    this.sex = sex;
}

// 2.  继承父类的方法
Son.prototype = new Father();
// 将子类的 constructor 重新指向子类的构造函数
Son.prototype.constructor = Son;

// 创建 子类 自己的方法
Son.prototype.dance = function () {
    console.log('我会跳舞--Son自己的方法')
}

var son = new Son('Tom', 18, '男');
console.log(son);
son.sing(); // 我会唱歌--Father的方法
son.dance();  // 我会跳舞--Son自己的方法

4.3 ES6 类的本质

1、 class类的本质还是function,类就是 构造函数的另外一种写法

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

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

构造函数的特性:

  • 构造函数有原型对象prototype
  • 构造函数原型对象prototype 里面有constructor ,指向构造函数本身
  • 构造函数可以通过原型对象添加方法
  • 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象

所以,构造函数的特性 类也同样具有

class Person {
}
// (1) 类有原型对象prototype 
console.log(Person.prototype);

// (2) 类原型对象prototype 里面有constructor 指向类本身
console.log(Person.prototype.constructor);

// (3)类可以通过  原型对象  添加方法
Person.prototype.sing = function () {
    console.log('我会唱歌');
}
var ldh = new Person();
console.dir(ldh);

// (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
console.log(ldh.__proto__ === Person.prototype); // 返回 true

5 函数的进阶

5.1 函数的定义和调用

5.1.1 函数的3种定义方式

// 函数定义的3种方式

// 1. 普通定义:自定义函数(命名函数) 
function fun() {
    console.log('你好');
}

// 2. 函数表达式 (匿名函数)
var fun = function () {
    console.log('你好');
}

// 3. 利用 new Function('参数1','参数2', '函数体'); -- 不推荐
// Function 里面参数都必须是字符串格式
var fun = new Function('a', 'b', 'console.log(a + b)');
fun(1, 2); // 返回3

所有函数都是 Function 的实例(对象)

函数也属于对象

5.1.2 函数的调用方式

1、 普通函数

function fun() {
    console.log('你好');
}
// 直接调用
fun();
// 或者  fun.call()  fun.apply()

2、 对象的方法

var obj = {
    sing: function () {
        console.log('我会唱歌');
    }
}
// 对象.方法名
obj.sing();

3、 构造函数

function Person() {
}
// 创建实例对象
new Person()

4、 绑定事件函数

// 点击了按钮就可以调用这个函数
btn.onclick = function () { };

5、 定时器函数

// 定时器自动1秒钟调用一次
setInterval(function () { }, 1000);

6、 立即执行函数

// 立即调用
(function () {
    console.log('你好呀');
})();

5.2 函数内的 this 指向

函数内的 this 指向,一般是当我们调用这个函数的时候,才确定的。 调用方式的不同决定了this 的指向不同

一般是指向函数的调用者

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


// 2. 对象的方法 this指向的是 当前对象 obj
var obj = {
    sing: function () {
        // this 指向调用这个方法sing 的对象 obj
        console.log('对象方法的this:' + this);
    }
}
// 对象.方法名
obj.sing();


// 3. 构造函数 this 指向 p 这个实例对象
//    原型对象里面的this 指向的也是 p 这个实例对象
function Person() {
}
Person.prototype.sing = function () {
    console.log(this)
}
// 创建实例对象
var p = new Person()
p.sing();


// 4. 绑定事件函数 this 指向的是函数的调用者, btn这个按钮对象
var btn = document.querySelector('button');
btn.onclick = function () {
    // this指向btn 这个按钮对象
    console.log(this);
};


// 5. 定时器函数 this 指向的是window
setInterval(function () {
    console.log(this);
}, 1000);


// 6. 立即执行函数 this 指向的是window
(function () {
    console.log(this);
})();

特殊的this指向

var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        console.log(this);  // this 指向 object
        return function () {
            console.log(this); // this 指向 widdow
            return this.name; // 返回:The Window
        };
    }
};
console.log(object.getNameFunc()())


var name = "The Window";
var object = {
    name: "My Object",
    getNameFunc: function () {
        var that = this;
        return function () {
            return that.name; // 返回:My Object
        };
    }
};
console.log(object.getNameFunc()())

5.3 改变函数内部this指向

JavaScript 为我们专门提供了一些函数方法来帮我们更优雅的处理函数内部 this 的指向问题,常用的有 bind()、call()、apply() 三种方法

5.3.1 call()

call()作用:立即调用函数,并改变函数运行时的this指向

利用这个特性,call()可以实现继承

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

thisArg:在 fun 函数运行时,指定的 this 值
arg1,arg2:传递的其他参数

// call()的主要作用: 调用函数;改变函数内的this 指向
// 利用上面的特性,还可以实现继承
var obj = {
    name: '小米'
}
function fun(a, b) {
    console.log(this); // 指向 obj 对象
    console.log(a + b); // 输出:3
};
fun.call(o, 1, 2);

// 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);
}
var son = new Son('小明', 18, '男');
console.log(son);

5.3.2 apply()

call()作用:立即调用函数,并改变函数运行时的this指向

传递的参数必须是数组

fun.apply(thisArg, [argsArray])
 
 thisArg:在fun函数运行时指定的 this 值
 argsArray:传递的值,必须包含在数组里面

因此 apply 主要跟数组有关系,比如使用 Math.max() 求数组的最大值

var obj = {
    name: 'Tom'
};
function fn(arr1, arr2, arr3) {
    console.log(this); // 指向对象 obj

    console.log(arr1); // 返回:小明
    console.log(arr2); // 返回:小米
    console.log(arr3); // 返回:小兰
};
fn.apply(obj, ['小明', '小米', '小兰']);

// 立即调用函数, 可以改变函数内部的this指向
// 但是他的参数必须是数组(伪数组)
// apply 的主要应用 : 比如说我们可以利用 apply 借助于数学内置对象求数组最大值
var arr = [1, 66, 3, 99, 4];
// null 也可以写 对象Math
var max = Math.max.apply(null, arr);
console.log(max); // 返回99

5.3.3 bind()

bind()作用:不会立即调用函数,也会改变函数运行时的this指向

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

thisArg:在 fun 函数运行时指定的 this 值
arg1,arg2:传递的其他参数

注意:有返回值,返回的是由指定的 this 值和初始化参数改造的原函数拷贝

因此当我们只是想改变 this 指向,并且不想调用这个函数的时候,可以使用 bind

var o = {
    name: 'andy'
};
function fn(a, b) {
    console.log(this); // 指向 对象o 
    console.log(a + b);
};
// 返回一个 由指定的 this 值和初始化参数改造的原函数拷贝
var f = fn.bind(o, 1, 2);
f();

主要应用:改变定时器内部this的指向

<button>点击</button>
<button>点击</button>
<button>点击</button>
<script>
    // 业务场景: 点击按钮后立即禁用当前按钮,3秒后重新开始按钮的点击作用
    var btns = document.querySelectorAll('button');
    for (var i = 0; i < btns.length; i++) {
        btns[i].onclick = function () {
            // 这个this 指向的是 当前 btns[i] 这个按钮
            this.disabled = true;
            setTimeout(function () {
                // 此处的this指向的不是window对象,而是 每一个 btns[i]对象
                this.disabled = false;
            }.bind(this), 2000);
        }
    }
</script>

5.3.4 异同

相同点

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

不同点

1、 call 和 apply 会调用函数,并且改变函数内部this指向

2、 call 和 apply 传递的参数不一样:call 传递参数 aru1, aru2..形式,apply 必须数组形式[arg]

3、 bind 不会调用函数,也可以改变函数内部this指向,传递参数的方式是 arg1,arg2,....

主要应用场景

1、 call 主要用做继承.

2、 apply 经常跟数组有关系。比如借助于数学对象实现数组最大值最小值

3、 bind 不调用函数,但是还想改变this指向: 比如改变定时器内部的this指向

5.4 严格模式

5.4.1 什么是严格模式

JavaScript 除了提供正常模式外,还提供了严格模式(strict mode)。ES5 的严格模式是采用具有限制性 JavaScript 变体的一种方式,即在严格的条件下运行 JS 代码。

严格模式在 IE10 以上版本的浏览器中才会被支持,旧版本浏览器中会被忽略。

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

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

5.4.2 开启严格模式

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

为脚本开启严格模式

为整个脚本文件开启严格模式,需要在所有语句之前放一个特定语句 “use strict”;(或‘use strict’;)

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

为函数开启严格模式

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

function fn(){
  "use strict";
  return "这是严格模式。";
}

5.4.3 严格模式下的变化

变量规定

1、 在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,变量都必须先用var 命令声明,然后再使用。

2、 严禁删除已经声明变量。例如,delete x; 语法是错误的

严格模式下 this指向问题

1、以前在全局作用域函数中的 this 指向 window 对象。严格模式下全局作用域中函数中的 this 是 undefined。

2、 以前构造函数时不加 new也可以 调用,当普通函数,this 指向全局对象;严格模式下,如果 构造函数不加new调用, this 指向的是undefined 如果给他赋值则 会报错

3、new 实例化的构造函数指向创建的对象实例。

4、定时器 this 还是指向 window 。

5、事件、对象还是指向调用者

函数变化

1、 函数不能有重名的参数。

2、 函数必须声明在顶层.新版本的 JavaScript 会引入“块级作用域”( ES6 中已引入)。为了与新版本接轨,不允许在非函数的代码块内声明函数

5.5 高阶函数

高阶函数是对其他函数进行操作的函数,它接收函数作为参数将函数作为返回值输出,都称为高阶函数

// 1. 将函数作为参数传递
function fn(callback) {
    callback && callback();
}
fn(function () {
    alert('hi')
}


function fn() {
    // 2. 返回一个函数
    return function () { }
}
fn();

5.6 闭包

5.6.1 变量作用域

变量根据作用域的不同分为两种:全局变量和局部变量。

1、 函数内部可以使用全局变量

2、 函数外部不可以使用局部变量

3、 当函数执行完毕,本作用域内的局部变量会销毁

5.6.2 什么是闭包

闭包(closure)是指有权访问另一个函数作用域中变量的函数

简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量

闭包是一个函数 (一个作用域可以访问另外一个函数的局部变量)

// 闭包(closure)指有权访问另一个函数作用域中变量的函数。
// 闭包: 我们fun 这个函数作用域 访问了另外一个函数 fn 里面的局部变量 num
function fn() {
    var num = 10;

    function fun() {
        console.log(num);
    }
    fun();
}
fn();

5.6.3 闭包的作用

主要作用:延伸变量的作用范围

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

一些案例

点击li一次输出对应的索引号

<ul class="nav">
    <li>榴莲</li>
    <li>臭豆腐</li>
    <li>鲱鱼罐头</li>
    <li>大猪蹄子</li>
</ul>
<script>
    // 闭包应用-点击li输出当前li的索引号
    // 1. 我们可以利用动态添加属性的方式
    var lis = document.querySelector('.nav').querySelectorAll('li');
    for (var i = 0; i < lis.length; i++) {
        lis[i].index = i;
        lis[i].onclick = function() {
            // console.log(i);  // 
            console.log(this.index);

        }
    }
    // 2. 利用闭包的方式得到当前小li 的索引号
    for (var i = 0; i < lis.length; i++) {
        // 利用for循环创建了4个立即执行函数
        // 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
        (function(i) {
            // console.log(i);
            lis[i].onclick = function() {
                console.log(i);

            }
        })(i);
    }
</script>

定时器中的闭包

<ul class="nav">
    <li>篮球</li>
<li>足球</li>
<li>乒乓器</li>
</ul>
<script>
    var lis = document.querySelector('.nav').querySelectorAll('li');

// 3秒钟之后,打印所有li元素的内容
for (var i = 0; i < lis.length; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(lis[i].innerHTML);
        }, 3000)
    })(i);
}

</script>

5.7 递归

如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。就是函数内部自己调用自己,这个函数就是递归函数

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

由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件 return

5.8 深浅拷贝

浅拷贝只是拷贝一层, 更深层次对象级别的只拷贝引用

深拷贝拷贝多层,每一级别的数据都会拷贝

浅拷贝

var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            }
        };
        var o = {};
        // for (var k in obj) {
        //     // k 是属性名   obj[k] 属性值
        //     o[k] = obj[k];
        // }
        // console.log(o);
        // o.msg.age = 20;
        // console.log(obj);

        console.log('--------------');
        Object.assign(o, obj);
        console.log(o);
        o.msg.age = 20;
        console.log(obj);

深拷贝

 // 深拷贝拷贝多层, 每一级别的数据都会拷贝.
        var obj = {
            id: 1,
            name: 'andy',
            msg: {
                age: 18
            },
            color: ['pink', 'red']
        };
        var o = {};
        // 封装函数 
        function deepCopy(newobj, oldobj) {
            for (var 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);
        console.log(o);

        var arr = [];
        console.log(arr instanceof Object);
        o.msg.age = 20;
        console.log(obj);
posted @ 2023-10-10 09:53  songxia777  阅读(8)  评论(0编辑  收藏  举报