JS面向对象补充(ES6中类的使用和多态)

196 阅读6分钟

学习了coderwhy的JavaScript高级语法视频课的笔记

如有错误或者不合适的地方,敬请见谅,欢迎指出和拓展,谢谢各位了

一、面向对象的继承

我们先回顾一下之前讲的几种面向对象继承的方法,之后就会学习ES6中类的使用类实现面向对象继承的方法。

1、原型链继承

function Person() {
  this.name = 'Person'
  this.arr = []
}

Person.prototype.eat = function () {
  console.log(this.name + '吃饭了')
}

function Student() {
  this.sno = 123456789
}

Student.prototype = new Person()
//因为Student.prototype指向的是new Person()实例对象,所以没有constructor,指向也就不会是Student构造函数
//到时候就会根据原型链找Person.prototype的constructor,指向的是Person构造函数
//这里就是解决constructor这个问题
Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false, //不可枚举
  configurable: true, //可删除/重新定义
  writable: true, //可赋值
  value: Student
})

Person.prototype.study = function () {
  console.log(this.name + '学习了')
}

var stu = new Student()
stu.eat() //Person吃饭了
console.log(stu.__proto__.constructor) //[Function: Student]
console.log(stu) //Student { sno: 123456789 }

console.log('------------------')

var stu2 = new Student()

stu.arr.push('引用类型,被多个对象共享')

console.log(stu.arr) //[ '引用类型,被多个对象共享' ]
console.log(stu2.arr) //[ '引用类型,被多个对象共享' ]

//弊端:
//1、继承的属性在子类中不可枚举,也就是打印出来看不到
//2、父类中,引用类型,被多个对象共享
//3、不能给Person传递参数

2、构造函数继承

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

Person.prototype.eat = function () {
  console.log(this.name + '吃饭了')
}

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

Student.prototype.study = function () {
  console.log(this.name + '学习了')
}

var stu = new Student('xxx')

console.log(stu) //Student { name: 'Person', sno: 123456789 }
console.log(stu.__proto__) //{ study: [Function (anonymous)] }
stu.study() //xxx学习了

stu.eat() //报错:stu.eat is not a function

//弊端:1、不能继承父类Person的原型属性和方法

3、组合式继承

上面两种方式的组合

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

Person.prototype.eat = function () {
  console.log(this.name + '吃饭了')
}

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

Student.prototype = new Person()
//因为Student.prototype指向的是new Person()实例对象,所以没有constructor,指向也就不会是Student构造函数
//到时候就会根据原型链找Person.prototype的constructor,指向的是Person构造函数
//这里就是解决constructor这个问题
Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false, //不可枚举
  configurable: true, //可删除/重新定义
  writable: true, //可赋值
  value: Student
})

Person.prototype.study = function () {
  console.log(this.name + '学习了')
}

var stu = new Student('xxx', 123456789)

console.log(stu) //Student { name: 'xxx', sno: 123456789 }
console.log(stu.__proto__.constructor) //[Function: Student]
stu.eat() //xxx吃饭了

//弊端:1、Person至少被调用了两次
//2、在new Person()中,就在实例中添加了name:undefined,这是不必要的属性

4、原型式继承—对象

var obj = {
  eat: function () {
    console.log('该干饭了')
  }
}

//第一种方式
function inheritProrotype(o) {
  var newObj = {}
  //   newObj.__proto__ = o
  Object.setPrototypeOf(newObj, o)
  return newObj
}

var info = inheritProrotype(obj)

//第二种方式
function inheritProrotype2(o) {
  var newObj = function Foo() {}
  newObj.prototype = o
  return new newObj()
}

var info2 = inheritProrotype2(obj)

// 第三种方式—ES6
var info3 = Object.create(obj)

info.eat() //该干饭了
info2.eat() //该干饭了
info3.eat() //该干饭了

5、寄生式继承

var obj = {
  name: 'obj对象',
  eat: function () {
    console.log('该干饭就干饭')
  }
}

function createStudent(sno) {
  // 这里用的是原型式继承中第三中方法—ES6
  var newObj = Object.create(obj)
  newObj.sno = sno
  return newObj
}

var stu = createStudent(132456789)
console.log(stu) //{ sno: 132456789 }
stu.eat() //该干饭就干饭

6、寄生组合继承

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

Person.prototype.eat = function () {
  console.log('去吃饭吧')
}

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

//立即执行函数
;(function (Student, Person) {
  Student.prototype = Object.create(Person.prototype)
  //因为Student.prototype指向的是new Person()实例对象,所以没有constructor,指向也就不会是Student构造函数
  //到时候就会根据原型链找Person.prototype的constructor,指向的是Person构造函数
  //这里就是解决constructor这个问题
  Object.defineProperty(Student.prototype, 'constructor', {
    enumerable: false, //不可枚举
    configurable: true, //可删除/重新定义
    writable: true, //可赋值
    value: Student
  })
})(Student, Person)

var stu = new Student('xxx', 123456789)
console.log(stu) //Student { name: 'xxx', sno: 123456789 }
stu.eat() //去吃饭吧

二、ES6中类的使用

1. class定义类的方式

//类的声明式
class Person {}

// 类的表达式
// var Person = class {}

console.log(Person.prototype) //{}

var p = new Person()
console.log(p.__proto__ === Person.prototype) // true

console.log(Person.prototype.__proto__) //[Object: null prototype] {}
console.log(Person.prototype.constructor) //[class Person]
console.log(typeof Person) // function

2. class的构造方法

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

var stu = new Person('xxx')
var stu2 = new Person('zzz')
console.log(stu) //Person { name: 'xxx' }
console.log(stu2) //Person { name: 'zzz' }

3. class中方法的定义

// 1、
class Person {
  constructor(name) {
    this.name = name
  }
  // 普通的实例方法,eat添加到了Person.prototype原型对象上
  eat() {
    console.log(this.name + '该吃饭了喂')
  }
}

var stu = new Person('xxx')
stu.eat() //xxx该吃饭了喂

//  用来获取一个对象的所有自身属性的描述符。
console.log(Object.getOwnPropertyDescriptors(Person.prototype))
// {
//     constructor: {
//       value: [class Person],
//       writable: true,
//       enumerable: false,
//       configurable: true
//     },
//     eat: {
//       value: [Function: eat],
//       writable: true,
//       enumerable: false,
//       configurable: true
//     }
//   }


// 2、
class Person {
  constructor(name, sno) {
    this.name = name
    this._sno = sno
  }

  // 类的访问器方法,sno添加到了Person.prototype原型对象上
  get sno() {
    return this._sno
  }
  set sno(newVal) {
    this._sno = newVal
  }
}

var stu = new Person('xxx', 123456789)
console.log(stu) //Person { name: 'xxx', _sno: 123456789 }
console.log(stu.sno) //123456789
stu.sno = 888888888
console.log(stu.sno) //888888888

// 用来获取一个对象的所有自身属性的描述符。
console.log(Object.getOwnPropertyDescriptors(Person.prototype))
// {
//     constructor: {
//       value: [class Person],
//       writable: true,
//       enumerable: false,
//       configurable: true
//     },
//     sno: {
//       get: [Function: get sno],
//       set: [Function: set sno],
//       enumerable: false,
//       configurable: true
//     }
//   }


//3、
var arr = ['a', 'b', 'c']

class Person {
  constructor(name, sno) {
    this.name = name
  }

  //类的静态方法(类方法),huoqu添加到了Person上
  static huoqu(num) {
    var val = arr[num]
    return val
  }
}

console.log(Person.huoqu(1)) //b

// 用来获取一个对象的所有自身属性的描述符。
console.log(Object.getOwnPropertyDescriptors(Person))
// {
//     length: { value: 2, writable: false, enumerable: false, configurable: true },
//     prototype: {
//       value: {},
//       writable: false,
//       enumerable: false,
//       configurable: false
//     },
//     huoqu: {
//       value: [Function: huoqu],
//       writable: true,
//       enumerable: false,
//       configurable: true
//     },
//     name: {
//       value: 'Person',
//       writable: false,
//       enumerable: false,
//       configurable: true
//     }
//   }

4. class中实现继承的extends

  • 面向对象的继承前面复习就讲到了好几种方法,现在又新添一种面向对象继承
  • super的使用位置有三个:子类的构造函数、实例方法、静态方法
  • 注意:在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
class Person {
  constructor(name) {
    this.name = name
  }

  eat() {
    console.log(this.name + '在Person的干饭')
  }

  eat2() {
    console.log(this.name + '在Person的干饭2')
  }

  run() {
    console.log('Person的跑路1')
    console.log('Person的跑路2')
  }

  static staticFoo() {
    console.log('Person的staticFoo1')
    console.log('Person的staticFoo2')
  }
}

class Student extends Person {
  constructor(name, sno) {
    super(name)
    this.sno = sno
  }

  // 方法重写
  eat2() {
    console.log(this.name + '在Student的干饭2')
  }

  // 方法的复用
  run() {
    super.run()
    console.log('Student的跑路3')
    console.log('Student的跑路4')
  }

  // 重写静态方法
  static staticFoo() {
    // 静态方法的复用
    super.staticFoo()
    console.log('Student的staticFoo3')
    console.log('Student的staticFoo4')
  }
}

var stu = new Student('xxx', 123456789)

stu.eat() //xxx在Person的干饭
stu.eat2() //xxx在Student的干饭2

stu.run()
// Person的跑路1
// Person的跑路2
// Student的跑路3
// Student的跑路4

Person.staticFoo()
// Person的staticFoo1
// Person的staticFoo2

Student.staticFoo()
// Person的staticFoo1
// Person的staticFoo2
// Student的staticFoo3
// Student的staticFoo4

5. 转成ES5代码

babel中文网站的试一试功能跳转链接

image.png

6. 继承内置类

class hyArray extends Array {
  getStart() {
    return this[0]
  }

  getEnd() {
    return this[this.length - 1]
  }
}

var arr = new hyArray(1, 2, 3, 4, 5)
console.log(arr.getStart()) //1
console.log(arr.getEnd()) //5
// 除了自定义的方法外,还继承了Array
arr.push(666)
console.log(arr.getEnd()) //666

7. 类的混入mixin

class Person {
  person() {
    console.log('Person类')
  }
}

// 在JS中类只能有一个父类: 单继承
class Student extends Person {
  student() {
    console.log('student类')
  }
}

function mixinStudent2(cl) {
  return class Student2 extends cl {
    student2() {
      console.log('student2类')
    }
  }
}

function mixinStudent3(cl) {
  return class Student3 extends cl {
    student3() {
      console.log('student3类')
    }
  }
}

// 返回一个class类赋给stu4
var stu4 = mixinStudent3(mixinStudent2(Student))
var s = new stu4()
s.person() //Person类
s.student() //student类
s.student2() //student2类
s.student3() //student3类

三、面向对象的多态

  • 当对不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样, 那么就是多态的体现。
// 传统的面向对象多态是有三个前提:
// 1> 必须有继承(是多态的前提)
// 2> 必须有重写(子类重写父类的方法)
// 3> 必须有父类引用指向子类对象

// 当对不同的数据类型执行同一个操作时, 如果表现出来的行为(形态)不一样, 那么就是多态的体现。

// 1、
function foo(val) {}

var obj = {
  bar: function () {
    return 100
  }
}

class person {
  bar() {
    return 200
  }
}
var p = new person()

foo(obj)
foo(p)

//2、
function sum(a, b) {
  return a + b
}

sum(2, 3)
sum('a', 'b')