ES5中类、对象和原型

ES5没有真正定义类的关键字,它通过函数首字母大写的方式来告诉别人这是一个类,看起来是比较诡异的。比如下面代码:

function People(name, age) {
    // 实例属性
    this.name = name
    this.age = age
}

// 类实例化对象
let xiaoming = new People('xiaoming', 22)

接下来举例子都是用People这个类来举例子,具体实例化对象会是某一个person对象。如同上面代码所示,我们通过首字母大写的方式定义一个类(或者说告诉大家,这不是一个普通函数,这是一个类),如果写成函数的话,要首字母小写,也就是写成people的形式。但这样的话,就很容易混淆了,前面用function定义的话,这到底是函数还是类呢?判断是类还是函数的关键在于new这个关键字。如果通过new赋值的话,就是一个类,并实例化对象。如果没有的话,就算是一个函数。至于是不是首字母大写,不是重点。我们来看下面一个例子。

function People() {
  console.log('我的名字是小明')
}

People()

这样的语句就会直接执行函数,这就是类和函数之间的区别,在于关键字new而不是是否首字母大写,我们一般约定,首字母大写的是类。

ES5中也没有构造函数的关键字。构造函数可以理解为,类在实例化对象时所传的最初的参数,让每一个对象具有各自不同的特点。比如我们最初的这个代码:

function People(name, age) {
    // 实例属性
    this.name = name
    this.age = age
}

// 类实例化对象
let xiaoming = new People('xiaoming', 22)

我们可以传递xiaoming也可以传递xiaoli,这样就会声明两个不同的实例化对象。我们通过的是this关键字来实现构造函数的功能。this表示对于当前对象的引用。我们没有构造函数关键字的话,People既算是一个类,同时也担负起了类中构造函数的责任

我们演示完在类中如何添加属性之后,下面看看如何在类中添加方法方法和函数也是容易混淆的概念,具体实现的话功能基本上是一样的。就是定义的地方不同而已。如果不在类中去定义,我们称之为函数,如果定义在类里面称之为类的方法。实例化对象之后,对象还可以调用方法。

在类中定义方式,使用的是原型。原型是一个称之为prototype的属性,这个属性是一个对象。在它上面定义的属性和方法,可以被实例化的对象所共享,我们可以将方法定义在类的原型上,这样实例化对象时,实例化的对象也可以调用对应的方法了。我们一起来看一下代码:

function People(name, age) {
  this.name = name
  this.age = age
}

// 实例方法
People.prototype.showName = function () {
    console.log('我的名字是' + this.name)
}

let xiaoming = new People('xiaoming', 22)
xiaoming.showName()

从图中可以看出__proto__下面挂载了showName()方法。

ES5中静态属性和静态方法

我解释完这些之后,你可能还有一个疑惑,你说类可以实例化对象,而且类要大写。那为什么我见到有代码可以写成如Math.random()这样呢?按照这个之前讲解的规则,它好像没有实例化对象吧。是这样的,这个称之为静态方法,与之对应的还有一个静态属性。不需要将定义的类实例化对象就可以调用他们。

// 静态属性
People.count = 0

// 静态方法
People.getCount = function(){
    console.log(this.age) // undefined
    console.log('当前共有' + People.count + '个人')
}

这样就可以像Math.random()一样,直接调用静态属性和静态方法了。

ES5中的继承
// 父类
function Animal(name) {
    this.name = name
}

// 子类
function Dog(name, color) {
    // call的作用是用于改变this指向,传入得第一个参数用于表示指向谁
    // 这里用于指向Dog(传入得是指向Dog的this)
    // 第二个参数是Anmial中所对应的参数
    Animal.call(this, name) // 继承属性

    // 自己个性化的属性
    this.color = color
}

let d1 = new Dog('wangcai', 'white')

但是需要注意的是,写成这样的形式只能继承父类的属性,并不能继承父类的方法。如果想要继承父类的方法,需要写成这样的形式:

// 父类
function Animal(name) {
  this.name = name
}
Animal.prototype.showName = function () {
  console.log('名字是:' + this.name)
}

// 子类
function Dog(name, color) {
  // call的作用是用于改变this指向,传入得第一个参数用于表示指向谁
  // 这里用于指向Dog(传入得是指向Dog的this)
  // 第二个参数是Anmial中所对应的参数
  Animal.call(this, name) // 继承属性

  // 自己个性化的属性
  this.color = color
}
// 原型继承
Dog.prototype = new Animal()
Dog.prototype.constuctor = Dog

let d1 = new Dog('wangcai', 'white')
d1.showName()

 

这种继承称之为组合继承,结合了原型继承的和构造函数继承。不过可以看到,这样写起来真的比较难以理解。在ES6中,我们引入了很多新的关键字,代码写起来要更优雅,下面我们一起来看一下在ES6中如何实现这一点。
 

ES6中类、对象和继承

class People {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  showName() {
    console.log('我的名字是' + this.name)
  }

  // 静态方法
  static getCount() {
    console.log('当前共有' + People.count + '个人')
  } 
}

People.count = 1
let xiaoming = new People('xiaoming', 22)
xiaoming.showName()

我们看到有新的关键字class用于声明这是一个类,新的关键字constructor用于声明构造函数,新的关键字static用于声明静态方法。静态属性的话,同样是不能写在类里面,要写在外面,而且同样静态属性和静态方法不能被实例化对象所调用,只能被类所调用。

虽然ES6中的类实现是一种基于原型的语法糖形式,但看起来要比ES5的写法容易理解多了。下面我们再来看一下类的继承。

class Animal {
  constructor(name) {
    this.name = name
  }

  showName() {
    console.log('名字是:' + this.name)
  }
}

class Dog extends Animal{
  constructor(name, color) {
    super(name)
    this.color = color
  }

  showColor() {
    console.log('颜色是' + this.color)
  }
}

let xiaohua = new Dog('xiaohua', 'white')
xiaohua.showName()
xiaohua.showColor()

在ES6中引入了两个新的关键字来实现继承,分别是extends用来继承父类的方法,和super用来让子类继承父类中构造函数的属性。