理解 JS 的 class「类」

400 阅读5分钟

我正在参加「掘金·启航计划」

前言

请先理解 「原型」 和 「new 操作符」在看此篇文章

概念

推理

// 当我们声明一个对象的时候
let obj = {
  name: 'hone',
  age: 18
}
// 再次声明一个的时候
let obj2 = {
  name: 'dl',
  age: 19
}
// 以上两个对象是不是很像,很像的对象把它们归为一类
// obj 和 obj2 叫做同一类对象
let obj3 = { // 显然它们和以上两个不是同一类
  xxx: 1,
  yyy: 2
}

// 如果同一类对象我们表示一类,我们怎么能用代码表示它呢?

// 写一篇文档
// 同志们 如果要创建一个人类对象请一定要这样写
let xxx = { // 你写的类必须要有这两个 key 属性 name age
  name: xxx, 
  age: 18
}
// 你提供一个函数来创建这一类的对象
function createPerson(name, age) {
  let obj = {}
  obj.name = name || ''  // 有就给没有就是 ‘’
  obj.age = age || 18
  return obj
}
createPerson()  //  {name:'', age: 18}
createPerson('hone') // {name: 'hone', age: 18}
// 用 createPerson 创建的对象都具有相同的特征
// 这个 createPerson 只能创建一类对象,它是拥有 name 和 age 的对象
// 拥有 name 和 age 就叫做这个类的特征

创建拥有共同特征的对象就是创建这个类,那么类就是拥有共同特征的对象

类是一个抽象的东西,它并不存在,没有一个实体叫做人类, 人类是所有人的分类,所有人都是人类,但是不存在一个具体的人类

人和人之间有共同的属性,这个共有属性就叫做原型

// 假设人 必须有 4 个属性 name age walk species
// 我们发现每个人都有自己的自有name 自己的名字
// 每个人都有自己的 自有age
// 人类走路的动作 共有walk
// 所有的人都是人类 共有species
// 如何通过代码来区分 自有的 和 公有的
// 把所有共有属性放到 原型里面
// 把所有的非共有属性 放到 createPerson 里面

let 人类共有属性 = {
  walk(){ console.log('走两步') },
  species: '人类'
}

function createPerson(name, age) {
  let obj = {}
  obj.name = name || ''
  obj.age = age || 18
  obj.__proto__ = 人类共有属性  // 需要给一个这个对象的原型
  return obj
}

// 在 JS 里的类,没有这么严格, 只要拥有相同属性的就叫做一类

结论

  • 类: 拥有相同属性的对象
  • 构造函数:用来创建某个类的对象的函数
  • 自有属性:对象自身的属性
  • 共有属性:对象原型 (链) 里的属性

简单语法

class、new、extends、super()

构造函数的由来:我想生成一类的东西,我不想每次都写一遍。

所以需要一个函数来帮助我们创建符合某种特征的对象,符合某种特征就叫做类,用函数来创建某一类的对象,那这个函数就叫做构造函数。

class Person {
  constructor(name) {
    this.name = name
    this.age = 18
  }
}

let p1 = new Person('hone')

每一个 class 都有一个构造函数 constructor ,它用来构造自有属性

class Person {
  // constructor 里面写所有的自有属性
  constructor(name) {
    this.name = name
    this.age = 18
  }
  // 所有的共有属性 写成 xxx(){}
  // walk 在原型上,也就是存在共有属性上
  walk(){
  	console.log('走两步')
  }
}
let p1 = new Person('hone')
let p2 = new Person('h')
p1.walk === p2.walk

当你要声明对象的时候就用 new ****, 继承就要用 extends

class Animal {
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
  }
  move(){
    console.log('我能动')
  }
}

// Person 继承了 Animal
class Person extends Animal { // extends 后面的叫做 基类/超类
  constructor(name){
    super() //  相当于把这句话 this.body = '身体' 给弄过来
    this.name = name
    this.age = 18
  }
  // 人类自己的共有属性
  walk(){
    console.log('走')
  }
}

let p1 = new Person('hone') 

打印的是 p2 为啥显示的是大写的 Person, 这个东西是没有任何意义的不要去管它

extends 的意思是如果一个类 Person 继承另外一个类 Animal, 那么 Person 的对象就拥有 Animal 的属性

super() 的意思是 执行你继承的那个类的 constructor

class Person extends Animal { // extends 后面的叫做 基类/超类
  constructor(name){
    super() // 在使用 this 之前必须先调用 super()
    this.name = name
    this.age = 18
    this.body = this.body + '手脚'
  }
  // 人类自己的共有属性
  walk(){
    console.log('走')
  }
}

get、set

如果共有属性是一个数字呢?

// 1
class Animal {
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
  }
  move(){
    console.log('我能动')
  }
  race(){
    return '动物'
  }
}


class Person extends Animal {
  constructor(name){
    super()
    this.name = name
    this.age = 18
  }
  walk(){
    console.log('走')
  }
}

let p1 = new Person('hone') 

p1.race() // '动物'  

// 可以不写 () ?   使用 get 就可以

// 2
// ES6
class Animal {
  // #race = '动物'
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
  }
  move(){
    console.log('我能动')
  }
  get race(){  // 在前面加一个 get
    return '动物'
  }
}

p1.race  // 这样就可以调用

p1.race = 'animal' 
p1.race // '动物'  改不动,为什么? 因为它始终在调用 race() 这个函数, 这个函数永远会返回 ‘动物’ 

// 3
// 如果你想赋值的话 需要用到 set
class Animal {
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
    // 隐藏的属性 this._race 去保留真正的值
    this._race = '动物'  // 在这里开始存
  }
  move(){
    console.log('我能动')
  }
  
  // 用两个函数去操作这个隐藏的属性
  get race(){  // 在前面加一个 get
    return this._race
  }
  set race(value){ // 这个 set 接收用户赋的值
    this._race = value
  }
  // 属性封装
}

p1.race // '动物'  在运行这个的时候调用了 get 方法
p1._race // '动物'  奇怪吧, 后续会有修复语法
p1.race = 'animal'
p1.race // 'animal'

// 还可以用 # 
class Animal {
  #race = '动物' // 于是所有人不可以通过 this._race 来得到这个 _race
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
  }
}

以上代码, 用一个隐藏的属性 this._race 去保留真正的值,然后用两个函数去操作这个隐藏的属性,这个叫做属性封装

// 设置人类不能改自己的年龄,如何设置不允许更改年龄
class Animal {
  constructor(){
    this.body = '身体'  // 假设 Animal 有一个自有属性 body
  }
  move(){
    console.log('我能动')
  }
  race(){
    return '动物'
  }
}


class Person extends Animal {
  constructor(name){
    super()
    this.name = name
    this._age = 18 // 在这里给她隐藏起来
    this.body = this.body + '手脚'
  }
  walk(){
    console.log('走')
  }
  // 不设置 set 就无法更改了
  get age(){
    return this._age
  }
}

let p1 = new Person('hone') 


// 对 name 进行封装
class Person extends Animal {
  constructor(name){
    super()
    this._name = name
    this._age = 18 // 在这里给她隐藏起来
    this.body = this.body + '手脚'
  }
  walk(){
    console.log('走')
  }
  // 不设置 set 就无法更改了
  get age(){
    return this._age
  }
  get name(){
    return this._name
  }
  set name(v){
    if(v.length > 4 || v.length < 2) {
      console.log('请重新输入 name ')
    }else{
      this._name = v
    }
  }
}

get 是用来控制读的, set 是用来控制写的,这就是面向对象发明 get 和 set 的用意

static

static 静态方法: 你可以直接通过类名访问到的方法(可以通过 class 名字直接 点 出来的方法)

class Animal {
  constructor(){
    this.body = '身体'
  }
  move(){
    console.log('我能动')
  }
  race(){
    return '动物'
  }
}

class Person extends Animal {
  constructor(name){
    super()
    this._name = name
    this._age = 18
    this.body = this.body + '手脚'
  }

  // static 让 p1 访问不了
  // 人类灭亡不可以通过 p1.die 因为这只表示是p1死亡,不代表人类
  // 需要 Person.die  这个类 灭亡才是人类灭亡
  static die(){  
    console.log('地球炸了')
  }
  walk(){
    console.log('走')
  }
  get age(){
    return this._age
  }
  get name(){
    return this._name
  }
  set name(v){
    if(v.length > 4 || v.length < 2) {
      console.log('请重新输入 name ')
    }else{
      this._name = v
    }
  }
}

let p1 = new Person('hone') 
// 以上都可以通过 p1.age、p1.name、p1.walk 来访问
// 我们可以写一个东西让 p1 访问不了
p1.die  // 函数 没有返回值 undefined
Preson.die() //  地球炸了