面向对象复习

170 阅读3分钟

类的声明

  • es5中
function Class1(){
  this.name = 'class1'
  }
  • es6中
class Class2(){
  constructor(){
    this.name = 'class2'
  }
}

类的继承

借助构造函数继承

function Teacher(){
  this.title = '老师'
}
Teacher.prototype.beat = '打学生'

function Student(){
  Teacher.call(this)     <-
  this.type = '学生'
}
    
var s1 = new Student()
console.log(s1)
//Student {title: "老师的", type: "学生"}
console.log(s1.beat)       <-
//undefined

原理:通过改变父类构造函数this的指向,使其指向子类的实例 缺点:无法继承父类原型上的方法和属性,只能继承父类构造函数上的方法属性

借助原型链实现继承

function Teacher2(){
  this.title = '老师'
  this.array1 = [1,2,3]
}
Teacher2.prototype.beat = '打学生'

function Student2(){
  this.type = '学生'
  this.array2 = [7,8,9]
}
Student2.prototype = new Teacher2()    <-
Teacher2.prototype.say() = function(){
  console.log('666')
}
var s2 = new Student2()
console.log(s2)
//Student2 {type: "学生"}
//  -type: "学生"
//  -__proto__: Teacher2
//    -title: "老师"
//    -__proto__:
//      -beat: "打学生"
//      -constructor: ƒ Teacher2()
//      -__proto__: Object
console.log(s2.beat)
// 打学生
s2.say()
// 666

原理:子类的原型引用了父类的实例,此时JS会顺着原型链在 子类实例->子类原型 === 父类实例->父类原型中依次寻找方法属性

能通过instanceof

给原型添加方法的语句一定要放在原型指向了父类实例之后

var s3 = new Student2()
var s4 = new Student2()

s3.title = '老师76'    
console.log(s3.title) //老师76
console.log(s4.title) //老师

s3.__proto__.title = '老师76'     
console.log(s3.title) // 老师76      <-
console.log(s4.title) // 老师76      <-

s3.type = '毕业了的学生'
console.log(s3.type)  //毕业了的学生
console.log(s4.type)  //学生

s3.array1.push('abc')
console.log(s3.array1) // [1, 2, 3, "abc"]    <-
console.log(s4.array1) // [1, 2, 3, "abc"]    <-

s3.array2.push('def')
console.log(s3.array2) // [7, 8, 9, "def"]
console.log(s4.array2) // [7, 8, 9,]

//罪魁祸首:
s3.__proto__ === s4.__proto__ // true

记住JS顺着原型链往上寻找方法和属性,先在子类实例中(由子类构造函数获得的属性方法)找,再去子类的原型(已经指向了父类实例)中找......同时父类实例又有自己的__proto__...真是绕.....

缺点:子类的实例共享同一个原型,修改一个子类的属性另一个也会改变

组合继承

function Teacher3(){
  this.title = '老师'
  this.array3 = [1,2,3]
}

function Student3(){
  Teacher3.call(this)   <-
  this.type = '学生'
}
Student3.prototype = new Teacher3()    <-

var s5 = new Student3()
var s6 = new Student3()
s5.array3.push(4)
console.log(s5.array3)  // [1,2,3,4]
console.log(s6.array3)  // [1,2,3]

缺点,实例化了三次

优化版本1

function Teacher4 () {
  this.title = '老师'
  this.array4 = [1,2,3]
}

function Student4 () {
  Teacher4.call(this)  //    <-
  this.type = '学生'
}
Student4.prototype = Teacher4.prototype //     <-
var s7 = new Student4()
var s8 = new Student4()
s7.array3.push(4)
console.log(s7.array3)  // [1,2,3,4]
console.log(s8.array3)  // [1,2,3]

只在实例化的时候才实例类

__上述两种共同的缺点:最终产生的实例的构造函数都会指向父类构造函数

原因:Student4.prototype = Teacher4.prototype或者Student3.prototype = new Teacher3()__

s5.constructor = function Teacher3(){...}
s7.constructor = function Teacher4(){...}

优化版本2

function Teacher5 () {
  this.title = '老师'
  this.array5 = [1,2,3]
}

function Student5 () {
  Teacher5.call(this)        //  <-
  this.type = '学生'
}
Student5.prototype = Object.create(Teacher5.prototype) //  <-
// Object.creat() 创造的中间对象的原型对象就是传入的参数
// 此时中间对象的原型也就是父类的原型
// 中间对象通过类似在原型链上“加一段”的方式把父子类的构造器隔开
var s9 = new Student5()
console.log(s9.constructor)   //依然为父类构造器

最终版本

function Teacher6 () {
  this.title = '老师'
  this.array5 = [1,2,3]
}

function Student6 () {
  Teacher6.call(this)        //  <-
  this.type = '学生'
}
Student6.prototype = Object.create(Teacher6.prototype) //  <-

Student6.prototype.constructor = Student6  //  <-!!!!!
var s10 = new Student6()
console.log(s10 instanceof Teacher6,s10 instanceof Student6)
// true true
console.log(s10.constructor)    // Student5 () {..}

KILL IT!