有关JS继承

1、原型链继承

function Parent() {
  this.name = "hehe";
  this.play = [1, 2, 3];
}

function Child() {
  this.type = "child2";
}

Child.prototype = new Parent();

let a = new Child();
let b = new Child();

a.play.push(4);

console.log(a.play); //[1,2,3,4]
console.log(b.play); //[1,2,3,4]

可以看出,上面的例子中,a与b两个实例,使用的是同一个原型对象,他们的内存空间是共享的,当一个发生变化的时候,另一个也随之发生了变化,这是使用原型链继承的一个缺点;

 2、构造函数继承(借助call)

function Parent() {
  this.name = "hehe";
  this.play = [1, 2, 3];
}

function Child() {
  Parent.call(this);
  this.type = "child";
}

let a = new Child();
let b = new Child();

a.play.push(4);

console.log(a.play); //[1,2,3,4]
console.log(b.play); //[1,2,3]

截止到这里,Parent.call(this)完美解决了上面的问题,但是看一下下面的代码:

Parent.prototype.getName = function () {
  return this.name;
};

function Child() {
  Parent.call(this);
  this.type = "child";
}

let a = new Child();

console.log(a); //正常显示 name  play
console.log(a.getName()); // 报错 Uncaught TypeError: a.getName is not a function

这里说明构造函数继承优化了原型链继承的缺点,但是同样它本省只能继承父类的实例属性和方法,不能继承原型属性或者方法。

3、组合继承(前两种的组合)

function Parent() {
  this.name = "hehe";
  this.play = [1, 2, 3];
}

Parent.prototype.getName = function () {
  return this.name;
};

function Child() {
  // 第二次调用 Parent3()
  Parent.call(this);
  this.type = "child3";
}

// 第一次调用 Parent3()
Child.prototype = new Parent();

// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child;

let a = new Child();
let b = new Child();

a.play.push(4);

console.log(a.play);//[1,2,3,4]
console.log(a.play);//[1,2,3]
console.log(a.getName());//"hehe"

这种写法解决了上面的两个写法带来的问题,但是,我们可以看到,Parent3 执行了两次,多了一次性能开销,下面介绍到的第六种方式可以更好地解决这个问题;

4、上边三种更多的是围绕着构造函数的方式,下面介绍介绍普通对象的继承 (Object.create)

首先介绍一下Object.create的用法

语法:

Object.create(proto, [propertiesObject])

方法创建一个新对象,使用现有的对象来提供新创建的对象的proto。

参数:

proto : 必须。表示新建对象的原型对象,即该参数会被赋值到目标对象(即新对象,或说是最后返回的对象)的原型上。该参数可以是null, 对象, 函数的prototype属性 (创建空的对象时需传null , 否则会抛出TypeError异常)。

propertiesObject : 可选。 添加到新创建对象的可枚举属性(即其自身的属性,而不是原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()的第二个参数。

 返回值:

在指定原型对象上添加新属性后的对象。

举个例子:

Object.create() 方式创建
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b)  // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}

 回到继承:

let parent = {
  name: "parents1",
  friends: [1, 2, 3],
  getName: function () {
    return this.name;
  },
};

let parent1 = Object.create(parent);

parent1.name = "hehe";
parent1.friends.push(4);

let parent2 = Object.create(parent1);

parent1.name = "hehehehhe";
parent1.friends.push(5);

console.log(parent1.name); // hehehehhe
console.log(parent1.name === parent1.getName()); // true
console.log(parent2.name); // hehehehhe
console.log(parent1.friends); // [1,2,3,4,5]
console.log(parent2.friends); // [1,2,3,4,5]

不难看出,这种继承方式的缺点也很明显,多个实例的引用类型属性指向相同的内存,存在篡改的可能;

5、寄生式继承:

let parent5 = {
  name: "parent5",
  friends: ["p1", "p2", "p3"],
  getName: function () {
    return this.name;
  },
};

function clone(original) {
  let clone = Object.create(original);
  clone.getFriends = function () {
    return this.friends;
  };
  return clone;
}

let person5 = clone(parent5);

// let person5 = Object.create(parent5);
// person5.getFriends = function () {
//   return this.friends;
// };
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // [p1,p2,p3]

这里是寄生式继承。但是还是多个实例的引用类型属性指向相同的内存。

 6、寄生组合式继承,究极继承方式

function Parent() {
  this.name = "hhe";
  this.play = [1, 2, 3];
}

Parent.prototype.getName = function () {
  return this.name;
};

function Child() {
  Parent.call(this);
  this.name = "hahhahaa";
}

// 与第四个方法相比,这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
Child.prototype = Object.create(Parent.prototype);

Child.prototype.constructor = Child;

let a = new Child();
let b = new Child();

a.play.push(4);

console.log(a.play); // [1,2,3,4]
console.log(b.play); // [1,2,3]
console.log(a.getName()); // hahhahaa

这玩意得上手敲,光看看不那么透。

补充说一下ES6中extends的用法

class A {
  constructor(name, age) {
      this.name = name;
      this.age = age;
  }
  getName() {
      return this.name;
  }
}

class B extends A {
  constructor(name, age, heh) {
    super();
    this.job = "IT";
  }
  getJob() {
    return this.job;
  }
  getNameAndJob() {
    return super.getName() + this.job;
  }
}

var b = new B("Tom", 20, 4);
console.log(b.age);
console.log(b.getName());
console.log(b.getJob());
console.log(b.getNameAndJob());
//输出:Tom,20,Tom,IT,TomIT

 上面定义了一个B类,通过extends关键字,继承了A类的所有属性和方法,A类中的所有方法是默认添加到B的原型上,所以extends继承的实质仍然是原型链。

super这个关键字,既可以当做函数使用,也可以当做对象使用,当作函数使用时,super代表父类的构造函数,并在子类中执行Parent.call(this);可以参照上面提到的第六种继承方式;

注意:

1、子类必须在constructor中调用super方法,如果子类中没有定义constructor方法,constructor方法及其内部的super方法会被默认添加。

例:

上面可以直接写:class B extends A{}

2、在子类的constructor方法中,只有调用super之后,才可以使用this关键字,否则会报错;

3、super()智能用在子类的constructor方法之中,用在其他地方会报错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值