ES6—类

1. 构造函数和原型

1.1 概述

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

ES6,全称ECMAScript 6.0,2015.06发版。在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义它们的特征。

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

  1. 对象字面量
  2. new Object()
  3. 自定义构造函数
   // 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);
    console.log(ldh); // Star
    ldh.sing(); // 我会唱歌

1.2 构造函数

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

在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('刘德华', 18);
var zxy = new Star('张学友', 19);
// 实例成员就是构造函数内部通过this添加的成员 uname age sing 是实例成员
// 实例成员只能通过实例化的对象来访问

console.log(ldh.uname); // 刘德华
ldh.sing(); // 我会唱歌
zxy.sing(); // 我会唱歌
console.log(ldh.sing===zxy.sing) // false
console.log(Star.uname); // undefined, 不可以通过构造函数来访问实例成员

// 在构造函数本身上添加的成员称为静态成员
Star.sex = '男'
console.log(Star.sex); // 男
console.log(ldh.sex); // undefined

1.3 构造函数的问题

构造函数很好用,但是存在浪费内存的问题

因为里面的方法是复杂数据类型需要开辟新的内存空间,而且,每实例化一个对象就要开辟一个空间,就造成了内存空间的浪费,于是就有了构造函数原型

1.4 构造函数原型 prototype

构造函数通过原型分配的函数是所有对象所共享的

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

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

  1. 原型是什么?

    一个对象,也称prototype为原型对象,每一个构造函数都有一个prototype

  2. 原型的作用是什么?

    共享方法

     // 1. 利用构造函数创建对象
        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);
      
        console.log(Star.prototype);
        ldh.sing(); // 我会唱歌
        zxy.sing(); // 我会唱歌
        console.log(ldh.sing===zxy.sing) // true
    

1.5 对象原型_proto_

对象都会有一个属性 __proto__ 指向构造函数的prototype原型对象,之所以对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有 _proto_ 原型的存在。

  • _proto_ 对象原型和原型对象prototype是等价的

方法的查找规则:

首先查找该对象是否有该方法,有则执行,无则查找构造函数原型对象prototype上是否有该方法

1.6 constructor 构造函数

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

很多情况下,需要手动的利用constructor 这个属性指回 原来的构造函数

// 当需要添加多个方法时,可以如下简写
Star.prototype = {
    sing: function() {},
    movie: function() {}
    // 但是这样写,就改变了原来的原型,所以需要利用constructor指回原来的构造函数
    constructor:Star
}

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

ES6—类_第1张图片

1.8 原型链

只要是一个对象,它就有一个原型_proto_

ES6—类_第2张图片

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

  • 当访问一个对象的属性(包括方法)时,首先查找这个对象自身没有该属性。

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

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

  • 以此类推一直找到Object为止(null)

1.10 原型对象this指向

  • 在构造函数中,this指向的是对象实例
  • 原型对象函数里面的 this指向实例对象

1.11 扩展内置对象

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

// 原型对象的应用 扩展内置对象方法
console.log(Array.prototype);
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
    

2. 继承

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

2.1 call()

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

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

  • thisArg:当前调用函数this的指向对象
  • arg1,arg2:传递的其它参数
    function fn(x,y) {
      console.log('我想喝手磨咖啡');
      console.log(this); 
      console.log(x+y);
    }
    var o = {
      name:"andy"
    }
    // 1. call()可以调用函数
    fn.call(); // 我想喝手磨咖啡  Window  NaN
    // 2. call() 可以改变this指向。此时指向o对象
    fn.call(o,1,2); //  我想喝手磨咖啡  Object  3

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

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

    // 1. 父构造函数
    function Father(uname,age) {
      // this 指向父构造函数的对象实例
      this.uname = uname;
      this.age = age;
    }
    // 2. 子构造函数
    function Son(uname,age) {
      // this 指向子构造函数的对象实例
      Father.call(this, uname, age);
    }
    var son = new Son('ldh', 18);
    console.log(son);

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

 // 1. 父构造函数
    function Father() {
      ...
    }
    Father.prototype.money = function() {
      console.log(1000);
    }
    // 2. 子构造函数
    function Son() {
      ...
    }
    //Son.prototype = Father.prototype; // 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也一起被修改
    Son.prototype = new Father();
        
    // 如果利用对象的形式修改了原型对象,别忘了利用constructor指回原来的构造函数
    Son.prototype.constructor = Son;
        
    // 这个是子构造函数专门的方法
    Son.prototype.exam = function() {
      console.log('孩子要考试');
    }
    console.log(son);
    console.log(Father.prototype);
    console.log(Son.prototype.constructor);

3. 类的本质

  1. class本质还是function, 可以简单的认为类就是构造函数的另外一种写法

ES6 之前通过构造函数+原型实现面向对象编程

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

ES6 通过类实现面向对象编程

class Star {
    
}
// 1. 类有原型对象prototype
console.log(Star.prototype);
// 2. 类原型对象prototype里面有constructor 指向类本身
console.log(Star.prototype.constructor);
// 3. 类可以通过原型对象添加方法
Star.prototype.sing = function() {
    console.log('冰雨')
}
// 4. 类创建的实例对象有__proto__原型指向类的原型对象
var ldh = new Star();
console.dir(ldh); // 
console.log(ldh.__proto__ === Star.prototype); // true

总结:类具有构造函数的特点,即类是构造函数的另一种写法。所以ES6的类的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

你可能感兴趣的:(ES6—类)