JavaScript:继承

JS 实现继承的方式:

1. 通过构造函数实现继承:

function Parent1 () {
  this.name= '111';
}
function Child1 () {
  Parent1.call(this);
  this.type='222';
} 

通过这种方式,只能继承定义在父类构造函数内的属性与方法,定义在 prototype 原型对象内的属性与方法则无法继承,因此对其改进:

2. 通过原型对象进行继承:

function Parent2 () {
  this.name= '111';
}
function Child2 () {
  this.type='222';
} 
Child2.prototype = new Parent2();

通过这种方式,则既能继承构造函数内的属性与方法,也能继承原型链上的属性与方法。但是,由于令其原型对象指向父类的一个实例对象,使得所有子类的实例对象所访问到的属性指向同一个对象,所以会出现改变一个子类实例对象的父类中的属性,另一个子类对象的属性也跟着改变。因此我们有下一个方法来改进:

3. 组合方法进行继承:

function Parent3 () {
  this.name= '111';
}
function Child3 () {
  Parent3.call(this);
  this.type='222';
} 
Child3.prototype = new Parent3();

但是这种方法,使得父类的构造函数执行了两次,为了减少父类的构造函数的不必要的多次执行,如下修改代码:

4. 组合方法进行继承优化:

function Parent4 () {
  this.name= '111';
}
function Child4 () {
  Parent4.call(this);
  this.type='222';
} 
Child4.prototype = Parent4.prototye;

这样解决了前面提到的问题,但是这样简单粗暴的继承,使子类的原型对象指向了父类的原型对象,会导致当子类实例对象通过 constructor 属性获取其构造函数时,获得的是父类的构造函数(因为 constructor 属性在 prototype 属性中被继承),因此再进行优化:

5. 组合方法进行继承优化2:

function Parent5 () {
  this.name= '111';
}
function Child5 () {
  Parent5.call(this);
  this.type='222';
} 
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;

由于 Object.create() 这个 api 的特性,父类的原型对象会继承在子类的原型对象的原型对象上,实现了子类原型对象与父类原型对象的隔离,这时再给子类的原型对象的 constructor 属性赋值。

为什么直接在第四种方式的后面直接赋值呢?因为这是子类与父类的原型对象指向同一个对象,修改属性会同时修改子类与父类的原型对象。



🧙‍♂️ JavaScript 使用基于原型链的继承。

访问一个对象的属性时若该属性在对象上不存在,则会沿原型链向下搜寻每个原型对象。

每个构造器都拥有 prototype 属性,代表该构造器的原型对象,初始为一个 Object 实例。所有用此构造器 new 出的对象都可以访问该对象的属性。

function Foo(bar) { this.bar = bar }
Foo.prototype.baz = 'test'
let qux = new Foo('hello')
qux.bar // => 对象本身的属性:'hello'
qux.baz // => 对象原型链上 Foo 的原型属性:'test'

// qux 的原型链:(qux ->) Foo.prototype -> Object.prototype

如果一个构造器 A 的 prototype 属性是另一个构造器 B 的实例,那么 B 的原型链会被接到 A 上,此时我们就说 A 继承了 B,A 实例可以访问所有 B 原型链上的属性。

function Foo2() {}
Foo2.prototype.baz2 ='test2'
Foo2.prototype = new Foo() // 将 Foo 的原型链接到 Foo2 上。Foo 来自上一个例子
qux = new Foo2()
qux.baz // 原型链上 Foo 的原型属性:'test'
qux.baz2 // 原型链上 Foo2 的原型属性:'test2'

// qux 的原型链:(qux ->) Foo2.prototype -> Foo.prototype -> Object.prototype



JavaScript 基于原型链实现的继承,简单来说就是通过对象的 __proto__ 实现的向上查找。

比如你从未定义过 toSing() 方法,但是你却可以在任何地方使用它,原因就是当你使用 xx.toString() 时,他会先在自身查找看看没有这个方法,如果没有就根据 __proto__ 寻找他的原型对象,看看他的原型对象上有没有… 直到找到为止。

var arr = [1,2,3];
console.log(arr.toString()); //1,2,3

可以肯定的是,新创建的 arr 上没有定义 toString() 方法,我们知道数组的构造函数是Array(),可以重写一下toString() 方法:

Array.prototype.toString = function(){ 
   return 'Hello world' 
}
var arr = [1,2,3];
console.log(arr.toString()); //'Hello world'



ES6 中增加 class 语法糖,本质上没什么区别,但是统一了 ES6 之前五花八门的继承写法。

 class Person{
   constructor(name){
     this.name = name;
   }
   sayHi(){
     console.log(`my name is ${this.name}`);
   }
 }

 class Man extends Person{
   constructor(name,age){
     super(name);
     this.age = age;
   }
   sayAge(){
     console.log(`I am ${this.name}, I am ${this.age} years old`);
   }
 }

 let p_man = new Man('tom',19);
 p_man.sayAge(); // I am tom, I am 19 years old
 p_man.sayHi(); // my name is tom
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值