为了账号安全,请及时绑定邮箱和手机立即绑定

JavaScript继承之原型链继承

基本概念

原型链是JavaScript实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。实现原型链的基本模式是:让当前构造函数的原型对象等于另一个构造函数的实例。下面通过例子来具体分析:

例子:

function A(name, age) {
    this.name = name;
    this.age = age;
    this.family = ["爸爸", "妈妈"];
    if (typeof(this.ageName) != "function") {
        A.prototype.getName = function() {
            return this.name;
        }
    }
}

function B() {
    this.job = "IT";
    if (typeof(this.getJob) != "function") {
        B.prototype.getJob = function() {
            return this.job;
        }
    }
}

B.prototype = new A("Tom", 20);

var p = new B();

//实例 p 的属性name、age是继承自原型 B.prototype
console.log(p.name);
console.log(p.age);
console.log(p.hasOwnProperty("name"));
console.log(p.hasOwnProperty("age"));
//输出:Tom,20,false,false

//实例 p 的原型 B.prototype 被重写,所以 p 的构造函数指向 A
console.log(p.constructor == B);
console.log(p.constructor == A);
//输出:false,true

//一个完整的原型链
// p.__proto__ >> B.prototype.__proto__ >> A.prototype.__proto__ >> Object.prototype
console.log(B.prototype.isPrototypeOf(p));
console.log(A.prototype.isPrototypeOf(B.prototype));
console.log(Object.prototype.isPrototypeOf(A.prototype));
//输出:true,true,true

重点分析

在例子中,我们让函数B的原型对象B.prototype等于函数A的实例new A(),那么此时B.prototype相当于被重写,B.prototype会拥有new A()中的所有属性与方法。我们知道,实例对象的内部默认都会有一个指针,也就是__proto__属性,会指向自己的原型对象。而此时,这个指针也会存在于B.prototype之中。最后的结果就是:B的实例new B().__proto__指向B.prototypeB.prototype.__proto__指向A.prototype,最后别忘了所有函数的默认原型都是Object的实例,所以,A.prototype.__proto__指向Object.prototype

通过原型链实现继承

通过原型链,扩展了原型的搜索机制。当访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。在找不到属性或方法的情况下,搜索过程会一环一环地前行到原型链末端,也就是所有引用类型默认都继承的Object.prototype,这也正是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。

最理想的继承方式

借用构造函数 + 原型链 + Object.create()

不了解借用构造函数Object.create()可以参考以下手记:

JavaScript创建对象之构造函数

JavaScript创建对象之Object.create()方法

虽然说构造函数+原型链的模式是最常用的继承方式,但它也有一个不足之处,原先的实例属性变成了现在的原型属性,当所有实例共享一个原型属性的时候,就会互相影响。例如上面的例子,我们加上如下的测试:

p.family.push("爷爷");
console.log(p.family);
//输出:["爸爸", "妈妈", "爷爷"]

var p1 = new B();
console.log(p1.family);
//输出:["爸爸", "妈妈", "爷爷"]

基于此,构造函数+原型链的继承方式并不是最理想的方式。结合前面所学,如果我们通过借用构造函数来继承实例属性,通过原型链+Object.create()来继承方法或共享的属性,就是最理想的继承模式。

例子:

function A(name, age) {
    this.name = name;
    this.age = age;
    this.family = ["爸爸", "妈妈"];
    if (typeof(this.ageName) != "function") {
        A.prototype.getName = function() {
            return this.name;
        }
    }
}

function B(name, age, job) {
    //复制函数A的实例属性
    A.call(this, name, age);
    this.job = job;
}

//以A的原型对象为原型创建对象并赋值给B的原型对象,从而继承A原型上的方法
B.prototype = Object.create(A.prototype);

//B的原型对象被重写,需要重新指定B的原型的构造函数
B.prototype.constructor = B;

//测试1
var p1 = new B("Tom", 40, "Doctor");
console.log(p1.name);
console.log(p1.age);
console.log(p1.job);
console.log(p1.getName());
p1.family.push("爷爷");
console.log(p1.family);
console.log(p1.constructor == B);
//输出:Tom,40,Doctor,Tom,["爸爸", "妈妈", "爷爷"],true

//测试2
var p2 = new B("Amy", 30, "Teacher");
console.log(p2.name);
console.log(p2.age);
console.log(p2.job);
console.log(p2.getName());
p2.family.push("姥姥");
console.log(p2.family);
console.log(p2.constructor == B);
//输出:Amy,30,Teacher,Amy,["爸爸", "妈妈", "姥姥"],true

文中的代码部分,带有“例子”和“测试代码”字样的,只是用来学习或测试某一功能用的代码,不可以直接用于项目的开发中。带有“代码如下”字样的,都是经过本人测试,简单修改即可用于项目开发中的代码,如有错误,欢迎指出。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消