探索JS原型链的起源

一、一切皆为对象

JavaScript是一个面向对象(原型对象)语言,除了一些基础类型,一切皆为对象,所有的对象都是函数(Function也是对象)创建的。

面向对象的继承等一些特性,像极了人类的传承和繁衍。我们先解释下一切皆为对象:都是爹养娘生的。对于JS原型链的鼻祖Object.prototype就像是人类的祖先一样,是其他物种进化而来的,或者是女娲捏出来的,就不是Function构造出来的。

1.1 原型对象

原型对象是面向对象中的一种。区别于面向对象,JavaScript中的原型对象是无类型(class)的,虽然ES6中加入了class这一特性,但是这个class就只是一个语法糖,并不是一个面向对象类的实现。JavaScript通过原型链这一特性来实现对象的继承。

  /**
   * 通过构造函数创建对象
   */
  function Obj() {}; // Obj.propotype 的构造函数
  console.log(Obj.propotype.constructor); // function Obj
  var obj = new Obj(); // Obj.propotype 的实例对象
  console.log(obj.__proto__); // Obj.propotype

以上代码实现了用Obj.propotype原型对象的构造函数Obj创建了实例对象obj的一个示例,他们的关系如下图:


Obj.propotype示意图

解释一下Obj.propotype示意图:

 function Obj();  // 构造函数
 Obj.propotype; // 原型对象
 var obj = new Obj(); // 实例对象

1.2 原型对象与面向对象的区别

一些后端开发的同学对于面向对象的第一印象可能就是Object。像Java这样的语言中类都是继承Object的,恰好JavaScript也有Object,大家可能先入为主的认为JS中的Object就是Java中的Object,这种想法是错误的。

非常典型的一个例子(反例)就是许多人在问:Function和Object到底是什么关系?为什么Object是Function构造出来的?为什么Function能够构造出它自己?Object大兄弟,你的家庭关系有点乱哦。

Object.__proto__ === Function.prototype;  // true
Function.__proto__ === Function.prototype; // true

说好的实例的__proto__(隐式原型)等于对象的prototype(原型)啊;说好的一切都是对象,函数也是对象啊;为什么Object反而是Function构造出来的?这不是逗我玩呢?

引起这样的误解是因为平时我们都以构造函数的名称来简称一个原型对象,其实Function和Object都只是构造函数,JavaScript的所有对象都是继承Object.prototype的,并不是Object!不是Object!不是Object!Function和Object都是function声明的函数,所以它们是继承Function.prototype的,是Function构造出来的。这里就不举人类起源的例子了,有点不可描述,留在第三节说。

不知道上面的反例是把你们掰直了还是掰得更弯了。。。。

举一反三这种能力对于学习是很重要的,但是非常忌讳先入为主的思想,虚心使人进步。

二、继承

继承的意思不难理解,就是子承父业或者是子承祖业。在ES6之前,JavaScript没有明确规定继承的概念,ES6中才使用了extentds来规范继承的方式。

2.1 new 的实现原理

看了很多同行实现js继承的方案,很多都是基于new和Object.create。其实在构造函数没有返回值的情况下,new和Ojbect.create(Obj.prototype)实现的效果是差不多的,都能够继承Obj.prototype,大家可以跑一下下面的代码对比效果:

function Obj() {
    console.log('I am Obj constructor...');
    this.name = 'Obj';
}

// 这里的参数绝对不是Obj,如过是Obj,则是继承了Function
var obj2 = Object.create(Obj.prototype);
var obj3 = new Obj();

我们可以看到,对比于Object.create,new不仅继承了Obj.prototype,还调用了构造函数Obj。由此我们可以试着推理下new的实现:

function fnew(constructor) {
    var newObj = Object.create(constructor.prototype);  //  继承Obj.prototype
    constructor.call(newObj);    //   调用父对象的构造函数
    return newObj;
}
var obj4 = fnew(Obj);

我们通过fnew得到的obj4和new实现的obj3是一样的!

2.2 extends 的实现原理

按照我们对于原型链的理解,new出来的实例对象是不是也是js的继承呢?我理解的继承是祖传父,父传子,子传孙。但是new出来的实例对象因为缺少构造函数而失去了再继承和实例化的能力,就是说子不能传孙了,这不是我想要的继承!我要的继承是像Function这样继承Object后还具备‘生育能力’的,像extends这样的。

结合我们人类的行为来思考下,要想生孩子得有爹和娘啊。现在new出来的实例对象再嫁给一个构造函数就可以愉快的生孩子了,让我们改造下fnew:

function fextends(protoConstructor, constructor) {
    var newObj = Object.create(protoConstructor.prototype);  //  继承Obj.prototype
    protoConstructor.call(newObj); //  调用父对象的构造函数
    constructor.prototype = newObj;
    constructor.prototype.constructor = constructor;
    return constructor;
}
var ObjChild = fextends(Obj, function() {});

这样ObjChild就实现了Function extend Object的效果啦。那我们以此分析下ES6的class和extends其实就是类似于fextends(Object, function() {})的语法糖,class A extends B 就是类似于 var A = fextends(B, function A(){})的效果咯。

2.3 instansof原理和实现

MDN对instansof的解释是:instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置。比如Function instanceof Object就是判断Object.prototype是否出现在Function的原型链( __proto__ )中。

instansof示意图
// instansof
function finstansof(a, b) {
    if(a.__proto__) {
        if(a.__proto__ === b.prototype) {
            return true;
        } else {
            return finstansof(a.__proto__, b);
        }
    }
    return false;
}

看完instansof示意图和代码,相信大家都掌握了instansof这一特性的原理。下次再有人跟你说A instansof B的意思是判断A是否继承B的这样不严谨的谬论,你告诉我,我给他寄一盒的刀片。

下面再给大家举一个反例:世界上是先有Object还是先有Function?

Function instanceof Object //  true
Object instanceof Function // true

毋庸置疑,Function(Function.prototype的构造函数)和Object(Object.prototype的构造函数)都是函数,那就是继承Function.prototype的;结论就是先有Object.prototype的。

参考

深入理解javascript原型和闭包(完结)

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

推荐阅读更多精彩内容

  • JavaScript,通常缩写为 JS,是一种解释执行的编程语言。它是现在最流行的脚本语言之一。 JavaScri...
    神齐阅读 4,833评论 1 32
  • 一、 JS面向对象编程 1、 面向对象介绍 什么是对象? Everything is object (万物皆对象)...
    宠辱不惊丶岁月静好阅读 825评论 0 2
  • 学习目标: 理解面向对象开发思想 掌握 JavaScript 面向对象开发相关模式 掌握在 JavaScript ...
    金桔柠檬加冰阅读 405评论 0 0
  • 夸克浏览器是我非常喜欢的一款浏览器,整体非常简洁,UI做的也很精致。今天我就来仿写主页底部的工具栏。先来看看原本的...
    大头呆阅读 2,364评论 3 17
  • 很忙很累却没有效率,是我最近的状态,有没有什么方法结束这种状态呢?今天晨读文章分享的戴维·艾伦的《Getting ...
    唯其时物阅读 190评论 0 2