继承

151 阅读4分钟
  1. 对象冒充 无法继承原型链的方法 原理是通过 Person.call(this) 把Person的指向改变为Yu这个构造函数
function Person() {
    this.name='哈哈'
    this.age=12
    this.run=function(){
    console.log('我是方法')
  }
}
Person.prototype.sex="男"
Person.prototype.work=function() {//在原型上添加属性和方法
  console.log(this.name+'在工作')
}
let p = new Person()
console.log(p.name, '???name')
function Yu() {
  Person.call(this)
}
let y = new Yu()
console.log(y.name, 'y.name') // 哈哈
console.log(y.sex, 'y.sex') // undefined
  1. 原型链继承 既可以继承函数上的属性和方法也可以继承原型链上的属性和方法 其实就是通过Web.prototype=new Person()改变的Web函数实例化出来的对象指向的构造函数
function Web() {}
console.log(Web.prototype.constructor, '???111') // Web()
Web.prototype=new Person()//但是实例化的时候没办法给父类传参
console.log(Web.prototype.constructor, '???222') // Person()
let web=new Web()
console.log(web.sex, '???websex')
  1. 原型链+对象冒充组合继承模式既可以继承函数上的属性和方法也可以继承原型链上的属性和方法
function Web (name,age) {
  Person.call(this,name,age)
 }
Web.prototype = new Person()
console.log(Web.prototype.constructor, '???222')
// Web.prototype = Person.prototype  另外一种写法

let web=new Web('传入的name',15)//实例的时候还可以给父类传参
console.log(web.age)
web.work()

4.拷贝继承

  // 没有办法继承到Animal自身的属性
  // Animal 对象
 function Animal(){}
  Animal.prototype.species = "动物";
  // Cat对象
  function Cat(name,color){  
    this.name = name;
    this.color = color;
  }

// 拷贝方法
function extend2(Child, Parent) {
    var p = Parent.prototype;
    var c = Child.prototype;
    for (var i in p) {
      c[i] = p[i];
    }
  }

// 调用
 extend2(Cat, Animal);
  var cat1 = new Cat("大毛","黄色");
  console.log(cat1.species); // 动物

#一. 封装 ###1. 构造函数模式

// 首先创建一个构造函数
 function Cat (name, color) {
    this.name = name;
    this.color = color;
  }
// 实例化
const cat1 = new Cat('名字', '颜色')
console.log(cat1.name, '???name') // 名字
console.log(cat1.color, '???color') // 颜色

// 可以用instanceof的方法来判断实例化的方法是否属于构造杉树(注意没有点)
console.log(cat1 instanceof Cat, '???') // true

但是构造函数会存在一个浪费内存的问题

// 给这个构造函数添加一个eat方法
 function Cat (name, color) {
   this.name = name;
   this.color = color;
     this.eat = function () {
          console.log('我的名字')
      }
}
// 每次实例化之后相当于创建了一个新对象 对象里面的属性都是重复的内容,会多占用内存(相当于每次实例化出来的对象里面的属性会再一次被实例化出来)
var cat1 = new Cat("一","黄色");
var cat2 = new Cat ("二","黄色");
console.log(cat1.color); // 黄色
console.log(cat2.color); // 黄色
  cat1.eat(); // 我的名字
  cat2.eat(); // 我的名字
console.log(cat2.color == cat1.color) // true
console.log(cat1.eat == cat2.eat) // false

###2. prototype模式 js规定,每一个构造函数上都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承,所以可以把那些公共的方法或者属性直接定义在prototype上面。

 function Cat(name,color){
    this.name = name;
    this.color = color;
  }
Cat.prototype.type = "类型";
Cat.prototype.eat = function(){console.log("我的名字")};

var cat1 = new Cat("一","黄色");
var cat2 = new Cat ("二","黄色");
console.log(cat1.type); // 黄色
console.log(cat2.type); // 黄色
console.log(cat1.eat === cat2.eat); // true
// 它们都指向的同一个内存地址
// js提供了一些方法 方便配合prototype
// 1. isPrototypeOf() 判断构造函数里面有没有这个实例 (注意:要用构造函数的prototype去调用这个方法)
console.log(Cat.prototype.isPrototypeOf(cat1)); //true
console.log(Cat.prototype.isPrototypeOf(cat2)); //true

// 2.  hasOwnProperty() 判断自身有没有这个属性 有的话返回true 否则返回false (注意:挂载到prototype上面的属性不属于自身属性,所以会返回false)
console.log(cat1.hasOwnProperty("color")); // true
console.log(cat1.hasOwnProperty("type")); // false

// in运算符 判断实例化的对象有没有这个属性 无论他是自身的还是prototype的
console.log("color" in cat1); // true
console.log("type" in cat1); // true

#二. 继承

function Animal(){
  this.species = "动物";
}

###1.构造函数绑定/对象冒充 (通过apply或者call改变Animal的指向)

 function Cat(name,color){
    Animal.apply(this, arguments);
    this.name = name;
    this.color = color;
  }
  var cat1 = new Cat("大毛","黄色");
  console.log(cat1.species); // 动物

###2.prototype继承(*比较难理解)

/**
  1. 首先需要知道的是构造函数的prototype.construct默认指向它本身这个构造函数(例如:Cat.prototype.constructor === Cat)
  2. 其次实例化出来的对象的constructor默认指向构造函数的prototype.constructor(例如: cat1.constructor === Cat.prototype.constructor)
  3. 所以实例化出来的对象的constructor默认指向它的构造函数 (例如:cat1.constructor === Cat)
*/
console.log(Cat.prototype.constructor) // Cat() 
 // 正常情况下Cat.prototype.constructor默认应该指向Cat的这个构造函数是没问题的
Cat.prototype = new Animal();
// 但是,在这里Cat.prototype把Cat这个构造函数的prototype对象修改成了Animal这个构造函数的实例化。
console.log(Cat.prototype.constructor) // Animal()
console.log(cat1.constructor) // Animal()
// 所以现在的Cat.prototype.constructor和他的实例化的指向为Animal这个构造函数

// 但是这段代码是什么意思呢
Cat.prototype.constructor = Cat;
// 因为在上述的案例中 通过修改Cat.prototype 使Cat.prototype.constructor指向了Animal这个构造函数,
// 但是cat1这个实例化出来的对象明明是指向Cat这个构造函数的。
// 所以我们要把Cat.prototype.constructor手动让其重新指向Cat这个构造函数

/**
  所以切记!!! 即如果替换了prototype对象,
  那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
**/
console.log(Cat.prototype.constructor ) // Cat
// 最后既可以继承到Animal的这个属性并实例化的指向也没有变
var cat1 = new Cat("大毛","黄色");
console.log(cat1.species); // 动物
console.log(cat1.constructor === Cat.prototype.constructor) // true

###3.直接继承prototype

// 声明一个构造函数 在prototype添加一个动物的属性
function Animal(){ }
Animal.prototype.species = "动物";

// Cat 不变
function Cat(name,color){
    this.name = name;
    this.color = color;
  }

    // 因为空函数几乎不占内存
   var F = function(){};
  F.prototype = Animal.prototype; // 同理改变F.prototype 的指向 F.prototype.constructor === Animal
  Cat.prototype = new F(); 
   // 同理改变Cat.prototype 的指向 Cat.prototype.constructor === F 
   // 因为上面 F.prototype.constructor === Animal 
   // 所以 Cat.prototype.constructor === Animal

  Cat.prototype.constructor = Cat; // 改变了Cat.prototype.constructor的指向之后要记得把它改回来
  console.log(Animal.prototype.constructor); // Animal