理解原型链

在JS中,原型链有时候让人觉得很胡里花哨,又是prototype、proto又是各种指向什么的,让人觉得很头疼。如果你也有这种感觉,或许这篇文章可以帮助到你

一、认识原型

1、先来一串代码

var Person = function(msg){
    this.msg = msg;
}
var person1 = new Person("wanger")

person1.constructor===Person;    //true
Person === Person.prototype.constructor; //true
person1.__proto__ === Person.prototype; //true
person1.__proto__.constructor === person1.constructor //true

看晕了吧?是不是很胡里花哨?不用担心,其实一张图就能了明白这其中的关系:

Alt text
Alt text
  • 蓝色的是构造函数
  • 绿色的是构造函数实例出来的对象
  • 橙色的是构造函数的prototype,也是构造函数实例出来的对象的原型(它其实也是一个对象)

2、这里特别要注意的是prototype__proto__的区别,prototype是函数才有的属性,而__proto__是每个对象都有的属性。(__proto__不是一个规范属性,只是部分浏览器实现了此属性,对应的标准属性是[[Prototype]])。

二、认识原型链

1、我们刚刚了解了原型,那原型链在哪儿呢?不要着急,再上一张图:

Alt text
Alt text

通过这张图我们可以了解到,person1的原型链是:

person1 ----> Person.prototype ----> Object.prototype ----> null

2、事实上,函数也是一个对象,所以,Person的原型链是:

Person ----> Function.prototype ----> Object.prototype ----> null

由于Function.prototype定义了apply()等方法,因此,Person就可以调用apply()方法。

3、如果把原型链的关系都显示清楚,那会复杂一些,如下图:

Alt text
Alt text

这里需要特别注意的是:所有函数的原型都是Function.prototype,包括Function构造函数和Object构造函数(如图中的标红部分)

三、原型链的继承

1、假设我们要基于Person扩展出Student,Student的构造如下:

function Student(props) {
    // 调用Person构造函数,绑定this变量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}

但是,调用了Person构造函数不等于继承了PersonStudent创建的对象的原型是:

new Student() ----> Student.prototype ----> Object.prototype ----> null

示意图如下所示:

Alt text
Alt text

必须想办法把原型链修改为:

new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null

示意图如下所示:

Alt text
Alt text

那我们应该怎么修改呢?仔细观察两张图的差异,我们会发现,如果我们将Studentprototype改成person1对象不就大功告成了?于是有了下面的代码:

Student.prototype = person1 ;

但是这时候有个问题:

Student.prototype.constructor === Student; //false

原来Student.prototype(即person1)的constructor指向的还是Person,这时候还需要我们再改一下代码:

Student.prototype.constructor = Student;

这样就能把Student的原型链顺利的修改为: new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null 了;

完整的代码显示如下:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 调用Person构造函数,绑定this变量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
var person1 = new Person("wanger")
Student.prototype = person1 ;
Student.prototype.constructor = Student;

三、用以上原型链继承带来的问题

1、如果在控制台执行一遍上述的代码,我们会发现一些问题,如图所示:

Alt text
Alt text

Student.prototype上含有之前person1带有的属性,那么,这样的继承的方法就显得不那么完美了

2、这个时候,我们可以借助一个中间对象来实现正确的原型链,这个中间对象的原型要指向Person.prototype。为了实现这一点,参考道爷(就是发明JSON的那个道格拉斯)的代码,中间对象可以用一个空函数F来实现:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 调用Person构造函数,绑定this变量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}

// 空函数F:
function F() {
}

// 把F的原型指向Person.prototype:
F.prototype = Person.prototype;

// 把Student的原型指向一个新的F对象,F对象的原型正好指向Person.prototype:
Student.prototype = new F();

// 把Student原型的构造函数修复为Student:
Student.prototype.constructor = Student;

// 继续在Student原型(就是new F()对象)上定义方法:
Student.prototype.getGrade = function () {
    return this.grade;
};

// 创建wanger:
var wanger = new Student({
    name: '王二',
    grade: 9
});
wanger.msg; // '王二'
wanger.grade; // 9

// 验证原型:
wanger.__proto__ === Student.prototype; // true
wanger.__proto__.__proto__ === Person.prototype; // true

// 验证继承关系:
wanger instanceof Student; // true
wanger instanceof Person; // true

这其中主要用到了一个空函数F作为过桥函数。为什么道爷会用过桥函数?用过桥函数F(){}主要是为了清空构造的属性。如果有些原Person的构造用不到,那么过桥函数将是一个好的解决方案

这样写的话,Student.prototype上就没有任何自带的私有属性,这是理想的继承的方法

3、如果把继承这个动作用一个inherits()函数封装起来,还可以隐藏F的定义,并简化代码:

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

封装后,写起来就像这样:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 调用Person构造函数,绑定this变量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
inherits(Student,Person) ;

这样再一封装的话,代码就很完美了。

事实上,我们也可以在inherits中使用Object.create()来进行操作,代码如下:

function inherits(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}

如果有兴趣了解Object.create()的其他用法,可以参考我的这篇博客JS中Object.create的使用方法;

四、ES6的新关键字class

在ES6中,新的关键字class,extends被正式被引入,它采用的类似java的继承写法,写起来就像这样:

class Student extends Person {
    constructor(name, grade) {
        super(msg); // 记得用super调用父类的构造方法!
        this.grade = grade || 1;
    }
    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

这样写的话会更通俗易懂,继承也相当方便。读者可以进入廖雪峰的官方网站详细了解class的用法

参考文献:廖雪峰的官方网站
原文地址:王玉略的个人网站

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容