JS继承的原理和几种方式

210 阅读4分钟

改变构造函数的继承并不是继承

例如:

function User(){}
  function Member(){}
  Member.prototype = User.prototype;
  function Admin(){}
  Admin.prototype = User.prototype
  Admin.prototype.role = function(){
    console.log('Admin In');
  }
  Member.prototype.role = function(){
    console.log('Member In');
  }
  let admin = new Admin()
  let user = new User();
  admin.role();//Member In
  user.role();//Member In

从上面可知,此时的role都是输出Member In,这是因为大家的prototye都指向了User.prototype,此时便无法分清大家各自的原型,Admin和Member都各自有自己的role属性,但是因为指向了User.prototype,Member覆盖了Admin的role属性。此时的关系为:

在这里插入图片描述

举个例子:现实生活中关于继承财产的问题,上面这样导致大家都去继承User的财产了,自己的财产却消失了,显然这是不符合逻辑的!

继承是关于原型的继承

如上述所说,我们需要保留自己的财产,那么如何保留呢,答案是利用__proto__,既然我们要保留自己的prototype,那么用Admin.prototype.proto = User.prototype即可实现

//继承是关于原型的继承
  function User(){}
  function Member(){}
  Member.prototype.__proto__ = User.prototype;
  function Admin(){}
  Admin.prototype.__proto__ = User.prototype
  Admin.prototype.role = function(){
    console.log('Admin In');
  }
  Member.prototype.role = function(){
    console.log('Member In');
  }
  let admin = new Admin()
  admin.role();//Admin In

此时的情况为:

在这里插入图片描述

此时我们即可得到admin自己的role方法而不被member覆盖

JS继承的几种方式

  1. 构造函数+原型链实现继承
function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj () {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = new fun()

var O1 = new obj()
var O2 = new obj()
O1.arr.push('123')

console.log(O1.arr)  // [1, 2, 3, '123']
console.log(O2.arr)  // [1, 2, 3]
console.log(O1.myLog());//1


  • 原理:通过fun.call(this)改变上下文this指向,父类构造函数上的属性和方法设置到了子类上,相互独立避免影响;通过 obj.prototype = new fun() 实现了继承父类原型上的属性和方法。

  • 缺点:这种方法实现继承,父类构造函数会被执行两次分别在 fun.call(this) 和 obj.prototype = new fun(),而且父类构造函数上的属性在子类自身和子类的原型上都存在,这导致执行了 delete O1.arr 只是删除了O1自身上的arr属性,O1原型上依然存在,根据原型链向上查找机制O1.arr依然可以访问到。

上述版本的优化:

function fun() {
    this.name = 'fun'
    this.arr = [1, 2, 3]
  }
  fun.prototype.myLog = function() { console.log(1) }

  function obj () {
    fun.call(this)
    this.type = 'obj'
  }
  obj.prototype = fun.prototype

  var O1 = new obj()
  O1.arr.push('123')
  console.log(O1.arr) // [1, 2, 3, "123"]
  delete O1.arr
  console.log(O1.arr) //undefined
  console.log(O1.myLog());//1

  • 缺点:因为obj.prototype = fun.prototype,导致父类和子类的实例无法做出区分。

上述版本的大概区别在于:

在这里插入图片描述 2. 改变原型继承

function Father(name){
    this.name = name
  }
  Father.prototype.role = function(){
    console.log('role');
  }
  function User(name){
    Father.apply(this,arguments);
  }
  User.prototype.__proto__ = Father.prototype
  let user = new User('han')
  Father.prototype.role = function(){
    console.log('father role');
  }
  User.prototype.role = function(){
    console.log('user role');
  }
  user.role()//user role
  console.log(user.name);//han

在这里插入图片描述

  1. 组合寄生式继承 这是利用Object.create 根据MDN的说法:

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

举个例子:

let obj = {
    name: 'han'
  }
  let obj1 = Object.create(obj)
  console.log(obj1);

此时输出为:

在这里插入图片描述 obj1是一个空对象,但是他继承了obj的属性

那么我们可以用其特性可以改变原型而不会让自己的原型影响到父类:

function fun() {
  this.name = 'fun'
  this.arr = [1, 2, 3]
}
fun.prototype.myLog = function() { console.log(1) }

function obj() {
  fun.call(this)
  this.type = 'obj'
}
obj.prototype = Object.create(fun.prototype)//通过Object.create创建新对象并原型指向父类的prototype
obj.prototype.constructor = obj//Object.create会丢失construct

var O1 = new fun()
var O2 = new obj()

O1 instanceof obj  // false
O2 instanceof obj  // true
(new fun()).__proto__.constructor  // 父类函数 fun()
(new obj()).__proto__.constructor  // 子类函数 obj()

Object.create会丢失construct,一定记得加上

此时对应关系相当于

在这里插入图片描述

注意:此时这种方法和上面改变原型的差别在于,上面没有抛弃原来的prototype,这个是抛弃了原来的prototye去创建一个新对象,这个新对象再指向父类的prototype,这样会导致如果在Admin.prototype = Object.create(User)之前新添加一个属性例如

let admin = new Admin()
Admin.prototype.name = 'han'
Admin.prototype = Object.create(User)
clg(admin.name)//error name is not define

因为抛弃了原来的prototye导致使用一开始定义的prototype的属性会报错

这样看上去貌似完美无缺了,其实还有一个点要考虑,我们是直接把constructor当作属性直接添加到prototype,此时这个属性在for in会被遍历出来,因为for in会遍历原型上的属性,此时我们利用Object.defineProperty来使其不可遍历

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

实现:

Object.defineProperty(Admin.prototype,"constructor",{ value: Admin, enumerable: false })