JS 继承的几种方式-(对象、原型、继承关系)

1.继承的方案

能不能直接让子类的原型对象=父类的原型对象

  • 不要这么做,因为这么做就意味着以后修改了子类型的原型对象的某个引用类型的时候,父类原型对象的引用类型也会被修改
// * 父类 :放公共的属性和方法
function Person(name,age,friend){
  this.name=name;
  this.age=age;
  this.friend=friend;
}
Person.prototype.eating=function(){
  console.log(this.name+"在吃东西");
}

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

Student.prototype=Person.prototype;
Student.prototype.study=function(){
  console.log(this.name+"正在学习。");
}
var stu=new Student("wjy",20,['hyz'],111)
console.log(stu);
stu.eating()
stu.study()
var p=new Person("hyz",20,['zmj','lt']);
p.study();//* 由于Student是在原型上添加,study应该是Student类特有的,但是Person也会跟着有了 

1.1原型式继承函数

原型链继承的渊源

  • 这种模式要从道格拉斯 克罗克福德(Douglas Crockford,著名的前端大师,JSON的创立者)在2006年些的一篇文章说起:Prototypal Inheritance in JavaScript
  • 在这篇文章中,它介绍了一种继承方法,而且这种继承方法不是通过构造函数类实现的
  • 为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法

真实开发中,不建议以下这种方式:

  var newObj={};
  newObj.__proto__=o;
var obj={
  name:"wjy",
  age:18
}
//*  原型式继承函数
function createObject1(o){
  var newObj={};
  Object.setPrototypeOf(newObj,o);//设置 newObj的原型为o
  return newObj;
}
// * 道格拉斯
function createObject2(o){
  function Fn(){};
  Fn.prototype=o
  var newObj= new Fn()
  return newObj;
}
var info=createObject1(obj)
console.log(info);
console.log(info.__proto__);
var info2=createObject2(obj);
console.log(info2);
console.log(info2.__proto__);

但是后来Object.create实现的功能跟createObject1和createObject2是一样的。

// * Object.create实现的功能跟createObject1和createObject2是一样的
var info3=Object.create(obj);
console.log(info3);
console.log(info3.__proto__);

但是以上都是基于对象与对象之间的继承,最后我们是实现函数与函数之间的继承。

1.2寄生式工厂继承函数

寄生式(Parasitic)继承

  • 寄生式继承是与原型式继承紧密相关的一种思想,并且同样由道格拉斯 克罗克福德提出和推广的
  • 寄生式继承的思路是结合原型式继承工厂模式的一种方式
  • 即创建一个封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再将则这个对象返回

未使用寄生式继承之前

var personObj={
  running:function(){
    console.log("running");
  }
}

var stuObj=Object.create(personObj)
stuObj.name="wjy";
stuObj.study=function(){
  console.log(this.name+"在学习");
}

stuObj2.name="wjy2";
stuObj2.study=function(){
  console.log(this.name+"在学习");
}

stuObj3.name="wjy3";
stuObj3.study=function(){
  console.log(this.name+"在学习");
}

使用寄生式函数之后:

var personObj={
  running:function(){
    console.log("running");
  }
}
function createStudent(name){
  var stu=Object.create(personObj);
  stu.name=name
  stu.study=function(){
    console.log(this.name+"在学习");
  }
}

 

var stuObj=createStudent("wjy")
var stuObj2=createStudent("hyz")
var stuObj3=createStudent("zmj")
40.png
1.4 缺点
  • 不能明确对象的具体类型
  • study函数在每个对象中都会有一份重复,

1.43寄生组合式继承

现在我们来回顾一下之前提出的比较理想的组合继承。

  • 组合继承是比较理想的继承方式。但是存在两个问题
  • 问题1:构造函数会被调用两次:一次在创建子类原型对象的时候,一次是创建子类实例的时候
  • 问题2:父类型中的属性会有两份:一份在原型对象中,一份在实例中

事实上,我们现在可以利用寄生式继承将这两个问题给解决掉

  • 你需要先明确一点:当我们在子类型的构造函数中调用父类型.call(this,参数)这个函数的时候,就会将父类型中属性复制一份到子类中,所以父类型本身里面的内容,我们不再需要
  • 这个时候,我们还需要获取到一份父类型的原型对象中的属性和方法
 function Person(name,age,friend){
   this.name=name;
   this.age=age;
   this.friend=friend
 }

 Person.prototype.running=function(){
   console.log("running");
 }

 Person.prototype.eating=function(){
  console.log("eating");
}

function Student(name,age,friend,sno,score){ //* 这个只是暂时实现了属性的继承
  Person.call(this,name,age,friend);
  this.sno=sno;
  this.score=score;

}

Student.prototype=Object.create(Person.prototype);//* 实现了方法的继承
Object.defineProperty(Student.prototype,"constructor",{ //* 使用Object.defineProperty精确某个对象的属性
  value:Student, 
  configurable:true,
  enumerable:false,
  writable:true
})
// Student.prototype.constructor=Student

Student.prototype.studying=function(){
  console.log("studying");
}

var stu=new Student("wjy",20,["hyz"],1,90)

//* 打印的时候,会去打印stu.constructor.name
console.log(stu);//* Person { name: 'wjy', age: 20, friend: [ 'hyz' ], sno: 1, score: 90 }
stu.eating()
stu.running()
stu.studying()

console.log(stu.constructor.name);//* Person

41.png

这一步非常的重要:但是如果有很多函数都需要使用继承,这里我们实现一个工具类

 function inheritPrototype(subType,superType){
   subType.prototype=Object.create(superType);
   Object.defineProperty(subType.prototype,"constructor",{
     value:subType,
     configurable:true,
     enumerable:false,
     writable:true
   })
 }

整体实现变成了下面这种形式:

 function inheritPrototype(subType,superType){
   subType.prototype=Object.create(superType.prototype);
   Object.defineProperty(subType.prototype,"constructor",{
     value:subType,
     configurable:true,
     enumerable:false,
     writable:true
   })
 }
 function Person(name,age,friend){
   this.name=name;
   this.age=age;
   this.friend=friend
 }

 Person.prototype.running=function(){
   console.log("running");
 }

 Person.prototype.eating=function(){
  console.log("eating");
}

function Student(name,age,friend,sno,score){ //* 这个只是暂时实现了属性的继承
  Person.call(this,name,age,friend);
  this.sno=sno;
  this.score=score;

}

inheritPrototype(Student,Person);
Student.prototype.studying=function(){
  console.log("studying");
}

var stu=new Student("wjy",20,["hyz"],1,90)


console.log(stu);
stu.eating()
stu.running()
stu.studying()

console.log(stu.constructor.name);

2.对象的方法补充

  • hasOwnProperty

    • 对象是否有一个属于自己的属性(不是原型上的属性)
  • in操作符 for in操作符

    • 判断某个属性是否在某个对象或对象的原型上
  • instanceof

    • 用于检测构造函数的prototype,是否出现在某个实例对象的原型链
  • isPrototypeOf

    • 用于检测某个对象,是否出现在某个实例对象的原型链
var obj={
  name:"why",
  age:18
}
var info=Object.create(obj,{
  address:{
    value:"北京市",
    enumerable:true
  }
})
console.log(info);//* { address: '北京市' }
console.log(info.__proto__);//* { name: 'why', age: 18 }

//* hasOwnProperty:对象上是否有一份自己的属性,不是(原型上的属性)
console.log(info.hasOwnProperty("address"));//true
console.log(info.hasOwnProperty("name"));//false
console.log(info.hasOwnProperty("age"));//false

// * in 操作符 :不管是在当前对象上返回还是原型对象上返回 都是true
console.log("address" in info); //true
console.log("name" in info); //true
console.log("age" in info);//true

for(var key in info){
  console.log(key);
}
function inheritPrototype(subType,superType){
  subType.prototype=Object.create(superType.prototype);
  Object.defineProperty(subType.prototype,"constructor",{
    value:subType,
    configurable:true,
    enumerable:false,
    writable:true
  })
}
//* instanceof:用于检测构造函数的prototype,是否出现在某个实例对象的原型链上
function Person(){

}
function Student(){
  
}
inheritPrototype(Student,Person)

var stu=new Student();

console.log(stu instanceof Student);//true
console.log(stu instanceof Person);//true
console.log(stu instanceof Object);//true
var obj={
  name:"wjy",
  age:20
}
var info=Object.create(obj);
// * isPrototypeOf:检测某个对象,是否出现在某个实例对象的原型链
console.log(obj.isPrototypeOf(info));;//true

3.对象-函数-原型的继承关系

  • function Function

    • 其实var Function=new Function()
    • Function的[[prototype]]为Function.prototype
  • function Object

    • 其实 var Object=new Function()
    • Object的[[prototype]]为Function.prototype
  • function Foo

    • 其实var Foo=new Function()
    • Foo的[[prototype]]为Function.prototype
  • 直接通过Object创建的对象的原型都是Object.prototype

42.png

4.总结

继承、函数-对象-原型的关系.png
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容