ES6 面向对象 — 类

对象的创建方式

// 字面量
let obj = {}

// Object实例
obj = new Object()
复制代码
let o = {}
console.log(o) // Object{}

// 构造函数 --- 目的是为了区分不同的实例对象
function Person(name, age) {
  this.name = name
  this.age = age
}

// new 是一种特殊的函数调用,不看return, 内部this为生成的那个实例对象,并会将实例对象返回
const per = new Person('Klaus', 24) 
console.log(per) // Person{}
复制代码

面向对象的三大特性

封装 + 继承 + 多态

封装

  1. 将零散的数据,封装起来包含在一个类型中,统一管理

  2. 使用者只能看见类中定义的公共变量和公共方法,

    而看不见方法的具体实现细节,也不能对类中非公共的数据进行操作。

    这样可以防止外部的干扰和误用。

  3. 当封装的数据和逻辑发生改变的时候,只要不修改输入和输出的内容,

    封装体的修改并不会封装体外部的代码产生任何的影响,内聚度高

继承

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

// 将方法定义在原型上,避免实例共有的方法定义到实例对象上
// 而是定义到统一的空间(原型)中,以便于节省存储空间,避免重复定义
Person.prototype.eat = function() {
  console.log('eating')
}

// 组合继承 = 原型链继承 + 构造函数继承
function Student(name, age, sno) {
  // 构造函数继承 --- 继承属性
  // 使用Student的实例对象来调用父类的构造函数
  // 所以此时的name和age本质上是加在Student的实例对象上的
  Person.call(this, name, age)
  this.sno = sno // 定义子类特有的属性
}

// 原型链继承 --- 继承方法 --- 子类原型是父类的实例对象
// 即原本的原型是Object的实例对象,现修改为父类的实例对象
// 这样子类就可以顺着原型链去获取到定义在父类中的方法
Student.prototype = new Person()
// new Person() => 虽然改变了原型,但是这依旧是构造函数的调用
// 这必然会导致Student的实例上有实际使用的name属性和age属性
// Student原型,也就是Person的实例上也有name和age属性
// 其值都是undefined, 因为原型链,所以会先获取到Student实例上的name和age属性
// 而永远无法获取到Student实例对象原型上的name和age属性,所以这个问题可以暂时忽略

// 手动添加子类的原型中的构造方法是子类的构造方法
// 这里就是修改stu.constuctor对应的值
// 设置 --- => Student构造函数
// 不设置 ---- => Person构造函数  --- 不合理
Student.prototype.constructor = Student // ☆☆☆☆☆

const stu = new Student('Klaus', 24, 1800160)
复制代码
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.eat = function() {
  console.log('eating')
}

function Student(name, age, sno) {
  Person.call(this, name, age)
  this.sno = sno
}

// Object.setPrototypeOf和new Person()都可以实现原型链继承

// Object.setPrototypeOf(子类, 父类) => 为子类设置原型
// 1. 这种设置,Student的原型依旧是Object实例对象
// 2. 子类的原型对象上不会存在值为undefined的name属性和age属性
// Object.setPrototypeOf(Student, Person)


// 1. 下面这种设置的时候,Student的原型是Person的实例对象
// 2. 子类的原型对象上会存在值为undefined的name属性和age属性
Student.prototype = new Person()
Student.prototype.constructor = Student

const per = new Person('Alex', 33)
const stu = new Student('Klaus', 24, 1800160)

// 推荐使用Object.setPrototypeOf(子类, 父类)的方式,实现原型链继承

// 获取stu的原型对象
// console.log(Object.getPrototypeOf(stu))
复制代码
function Person(name, age) {
  this.name = name
  this.age = age
}

Person.prototype.eat = function() {
  console.log('eating')
}

function Student(name, age, sno) {
  Person.call(this, name, age)
  this.sno = sno
}

// 不可以设置子类的原型对象为父类的原型对象
Student.prototype = Person.prototype

const per = new Person('Alex', 33)
const stu = new Student('Klaus', 24, 1800160)

stu.eat() // => 虽然这么做子类也可以继承父类的方法

console.log(stu.constructor) // => Person()
console.log(per.constructor) // => Person()

// 子类的原型和父类的原型是一个对象
// 所以此时无论怎么修改,stu.constructor === per.constructor
// 这是不合理的
Student.prototype.constructor = Student
console.log(stu.constructor) // => Student()
console.log(per.constructor) // => Student()
复制代码

多态

同一个方法,根据调用者的不同,产生不同的结果

js中多态有2种表现:方法重写 + 方法重载

重写(Override)

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

Person.prototype.eat = function() {
  console.log('eating')
}

function Student(name, age, sno) {
  Person.call(this, name, age)
  this.sno = sno
}

Student.prototype = new Person()

// 子类定义和父类同名的方法,以便于覆盖父类的方法
// 这就是方法的重写
Student.prototype.eat = function()  {
  console.log('student eating')
}

const per = new Person('Klaus', 33)
const stu = new Student('Klaus', 24, 1800160)

// 同一个方法根据不同的调用者产生不同的结果
per.eat() // => eating
stu.eat() // student eating
复制代码

重载(Overload)

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

Person.prototype.eat = function() {
  console.log('eating')
}

function Student(name, age, sno) {
  Person.call(this, name, age)
  this.sno = sno
}

Student.prototype = new Person()

// 所谓方法重载 -- 就是依靠传入的参数的个数不同或类型不同
// 来实现不同的调用返回不同的结果
Student.prototype.eat = function(food)  {
  console.log(`eat ${food}`)
}

const per = new Person('Klaus', 33)
const stu = new Student('Klaus', 24, 1800160)

per.eat()  // => eating
复制代码

ES6中的类就是对之前ES5的面向对象写法的语法糖

class Person {
  // 如果没有指定constructor,会存在一个默认的迭代器
  // 如果指定了,那么就会使用自定义的constructor,默认的就会失效

  /*
    默认的构造器:
   	constructor() {

  	}
  */
}

// 创建类的实例对象
const per = new Person()
复制代码
class Person {  constructor(name, age) {    // this -> Person的实例对象    this.name = name    this.age = age  }  // 这里定义的方法会被定义到实例的原型上  eat() {    console.log('eat')  }}// 创建类的实例对象,并进行初始化操作const per = new Person('Klaus', 22)
复制代码
class Person {  // 在这里写的属性是所有子类共有的公共属性  // 但是不推荐,因为公共的属性和方法应该被添加到Person实例对象的原型上  country = 'China'constructor(name, age) {  this.name = name  this.age = age}eat() {  console.log('eat')}}
复制代码
class Person {  // 属性和方法之前添加static  // 那么这个属性和方法就是类的属性和方法,需要通过Person.xxx去调用  // 实例对象是无法调用静态属性或方法的 per.xxx -> error  static country = 'China'  constructor(name, age) {    this.name = name    this.age = age  }  static eat() {    console.log('eat')  }}
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }  eat() {    console.log('eat')  }}// 使用extends关键字来表示Student继承自Person类class Student extends Person {  constructor(name, age, sno) {    // super方法必须在子类构造器第一次使用this关键字之前被调用    // 实际上name和age属性是加载在Student的实例对象上的    // 只是借用了父类构造器的代码    // 即等价于 Student.prototype.costructor.call(this, ...)    super(name, age)    this.sno = sno // 定义子类特有的属性  }}const stu = new Student('Klaus', 22, 1810166)// 此时stu的原型上也不会像ES5一样存在值为undefined的name和age属性console.log(stu)
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }}class Student extends Person {  // 以下的构造方法是实现继承后,子类默认的构造方法  /*        constructor(...args) {          super(...args)        }      */}const per = new Person()const stu = new Student('Klaus', 18)console.log(stu.name, stu.age) // Klaus 18
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }  eat() {    console.log('eat')  }}class Student extends Person {  constructor(name, age, sno) {    super(name, age)    this.sno = sno  }  // 方法重写  eat() {    console.log('student eating')  }}
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }  // 这个属性在每一个Person实例上都会存在  // 在继承的时候这个属性也会被继承  // 且是存放在子类的实例对象上,而不是子类实例的原型对象上  count = 23eat() {  console.log('eat')}}class Student extends Person {  constructor(name, age, sno) {    super(name, age)    this.sno = sno  }  eat() {    console.log('student eating')  }}const stu = new Student('Klaus', 23, 1800160)console.log(stu) // { name: 'Klaus', age: 23, sno: 1800160, count: 23 }
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }  eat() {    console.log('eat')  }}// foo定义在Person的实例的原型对象上,所以子类可以使用super关键字获取foo对应的值Person.prototype.foo = 'foo'class Student extends Person {  constructor(name, age, sno) {    super(name, age)    this.sno = sno  }  // 子类有一个关键字super,表示的是父类的实例的原型对象  // 所以可以使用super关键字来获取父类的方法和定义在父类实例的原型上的公共属性  eat() {    // console.log(super) // => error 无法输出super对象    console.log(super.foo) // foo    console.log('student eating')    super.eat() // => eat  }}
复制代码
class Person {  constructor(name, age) {    this.name = name    this.age = age  }}class Student extends Person {  /*      当一个普通的构造函数运行时,它会创建一个空对象作为 this,然后继续运行。      但是当派生类(子类)的构造函数运行时,在js编译的时候,子类的构造函数会被打上标记 [[ConstructorKind]]:“derived”      此时js认为创建实例时候创建对象并赋值给this的操作,也是所有类在初始化的时候共有的特征      所以也应该被抽取到父类中,由父类实现this的创建和指派      所有子类中必须在使用this之前优先调用super方法,以借用父类的构造函数来创建this    */  constructor(name, age, sno) {    // console.log(this) -> error 无法在super函数前调用this关键字    super(name, age) // 借用父类构造函数初始化this    this.sno = sno // this可以被正常使用  }}
复制代码
class Person {  static count = 30constructor(name, age) {  this.name = name  this.age = age}static eat() {  console.log('eat')}}class Student extends Person {  constructor(name, age, sno) {    super(name, age)    this.sno = sno  }}Person.eat() // => eatconsole.log(Person.count) // => 30// 父类的静态方法和属性可以被子类继承Student.eat() // => eatconsole.log(Person.count) // => 30
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享