ES6(六)JS中的类

164 阅读4分钟

在ES6之前,class是一个保留字,我们不能使用/创建class,在这之前JS使用的是原型去模拟类的使用。为何要使用原型?原型和类在写法上有哪些区别?请接着往下看。

何为原型

原型:用JavaScript中最复杂的数据类型——Object去创建的对象,每个对象都存在 __proto__属性,该属性的属性值是一个地址,与其构造函数的prototype属性存放的地址一致,这两个地址指向的那一块内容,就是原型对象。

继承:当我们创建了一个空对象,并没有创建它的toString()方法,但是依然可以调用该方法,并不会报错,这是因为obj对象是由Object构造函数创建的,根据公式:obj.__proto__===Object.prototype,在Object.prototype对象里面有所有对象的共有属性,里面就有toString()方法,就可以说obj对象继承了Object这个对象的toString()方法。

原型链:当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。如果没有则去原型的原型中寻找,直到找到Object对象的原型,Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined。这样一条条指向原型的链条,就是原型链,如下所示:

  • obj->Object
  • person->Person->Object
  • arr->Array->Object

何为类

以下代码可以明显看出,obj和obj2对象是同一类的对象,obj3和它们不是同一类的。

let obj={
  name:'lucy',
  age:18
}
let obj2={
  name:'Jack',
  age:19
}
let obj3={
  xxx:1,
  yyy:2
}

如何把这种区分用代码表示出来?可以写一个说明文档:

如果要创建一个“人类对象”,请一定要这样做:
let xxx={
  name:'____',
  age:'____'
}

但是肯定会有人不遵守这种方式,更保险一点的方法,我们可以设定规则,提供一个函数,来创建这一类的对象,这就是构造函数

function createPerson(name,age){
  let obj={}
  obj.name= name || ''
  obj.age=age || 18
  return obj
}
createPerson()           //{name:'',age:18}
createPerson('lucy')     //{name:'lucy',age:18}
createPerson('Jack',19)  //{name:'Jack',age:19}

函数createPerson()就是创建了拥有name和age这两种属性的对象的函数,属性之间也有区分,例如人和人之间有一些自有属性:name,age,还有一些公有属性:例如走路。这些该如何区分呢?

可以把所有自有属性放到构造函数createPerson()里,把公有属性放到原型里,这就形成了类的雏形。

let 人类共有属性={
  waik(){ console.log('走两步') },
  species:'人类'
}
function createPerson(name,age){
  let obj={}
  obj.name= name || ''
  obj.age= age || 18
  obj.__proto__=人类共有属性
  return obj
}
createPerson('Jack',19)  

image.png

总结

  • 什么是类:类就是拥有相同属性的对象。
  • 如何创建类:构造函数,用来创建某个类的对象的函数。上例中 createPerson就创建了四种属性(共有/自有)
  • 共有属性:在原型里的属性,species,waik
  • 自有属性:对象自身的属性,name,age

用JS实现类

方法一:使用原型(prototype)

将自身属性写到构造函数里,共有属性写到原型上,不造成内存的浪费。

function Dog(name){ 
  this.name = name               //自身属性
  this.legsNumber = 4 
} 
Dog.prototype.kind = '狗'         //共有属性
Dog.prototype.say = function(){   
  console.log(`我是${this.name},我有${this.legsNumber}条腿。`) 
} 
Dog.prototype.run = function(){   
  console.log(`${this.legsNumber}条腿跑起来。`) 
} 
const d1 = new Dog('贵宾犬') 

image-20220318095006245.png

方法二:使用 ES6 推出的 class

在类的参数传递中我们用constructor( )进行传参。传递参数后可以直接使用this.xxx进行调用。

自身属性写到constructor里面,共有方法写在constructor外面:

class Dog { 
  kind = '狗'     // 等价于在 constructor 里写 this.kind = '狗',没有办法写在原型上
  constructor(name) {   //自身属性
    this.name = name 
    this.legsNumber = 4 
  } 
  say(){                //共有方法,挂在原型上
    console.log(`我是${this.name},我有${this.legsNumber}条腿。`) 
  } 
  run(){ 
    console.log(`${this.legsNumber}条腿跑起来。`) 
  } 
} 
const d1 = new Dog('贵宾犬') 

image-20220318095202242.png

用JS实现继承

基于 原型 的继承

首先定义一个名为Animal的构造函数,然后用Dog函数继承它的属性和方法:

/* 父类  */
function Animal(legsNumber){   
  this.legsNumber = legsNumber  //自身属性:腿
} 
Animal.prototype.kind = '动物'   //共有属性:动物

/*  子类  */
function Dog(name){ 
  this.name = name              //自身属性
  Animal.call(this, 4)          //关键代码1  继承父类自有属性  
} 
function f(){ }                 //关键代码2
f.prototype = Animal.prototype 
Dog.prototype = new f()

Dog.prototype.kind = '狗'        //覆盖父类原型上的属性kind为自己的共有属性
Dog.prototype.say = function(){  //创建子类的共有属性say
  console.log(`我是 ${this.name},我有${this.legsNumber}条腿。`) 
} 
const d1 = new Dog('贵宾犬') 
console.dir(d1)

image-20220318095850787.png

基于 类 的继承

首先定义一个名为Animal的父类,然后创建一个名为Dog的类继承它的属性和方法。

class Animal{ 
  constructor(legsNumber){ 
    this.legsNumber = legsNumber   //自身属性
  } 
  run(){                   //共有属性
    console.log('我要跑')
  }   
} 
class Dog extends Animal{  //关键代码1:extends自动继承原型上的属性
  constructor(name) { 
    super(4)               //关键代码2:继承父类constructor里面的自身属性
    this.name = name       //创建子类的自有属性
  } 
  say(){                   //创建子类共有属性
    console.log(`我是${this.name},我有 ${this.legsNumber}条腿。`) 
  } 
}

image-20220318100841504.png

使用class实现继承的写法更优雅,更容易让人理解。