理解 javascript 的原型链及继承

理解 javascript 的原型链及继承

class Shape{}
class Circle extends Shape{}
const circle = new Circle();

console.log(circle.__proto__ === Circle.prototype);
console.log(Circle.prototype.__proto__ === Shape.prototype);
console.log(Shape.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);

console.log(Circle.__proto__ === Shape);
console.log(Shape.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__ === null);

console.log(circle.constructor === Circle)

以上所有的运行结果都是 true;

三种构造对象的方法:

  1. 通过对象字面量构造。
var person1 = {
    name: 'cyl',
    sex: 'male'
};

形如这个形式的叫做对象字面量。这样子构造出的对象,其[[prototype]]指向Object.prototype

  1. 构造函数构造。
function Person(){}
var person1 = new Person();

通过new操作符调用的函数就是构造函数。由构造函数构造的对象,其[[prototype]]指向其构造函数的prototype属性指向的对象。每个函数都有一个prototype属性,其所指向的对象带有constructor属性,这一属性指向函数自身。(在本例中,person1的[[prototype]]指向Person.prototype)

  1. 函数Object.create构造的。
var person1 = {
    name: 'cyl',
    sex: 'male'
};

var person2 = Object.create(person1);

本例中,对象person2的[[prototype]]指向对象person1。在没有Object.create函数的日子里,人们是这样做的:

Object.create = function(p) {
    function f(){}
    f.prototype = p;
    return new f();
}

Object 对象的属性

属性 描述
constructor 返回创建该对象的构造函数。
prototype 返回创建该对象的函数的原型对象。

一、原型

__proto__(隐式原型)

每个JS对象一定对应一个原型对象,并从原型对象继承属性和方法。

对象proto属性的值就是它所对应的原型对象,隐式原型指向创建这个对象的函数(constructor)的prototype。

JavaScript中任意对象都有一个内置属性[[prototype]],在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过proto来访问。ES5中有了对于这个内置属性标准的Get方法Object.getPrototypeOf().
Object.prototype 这个对象是个例外,它的proto值为null

var one = {x: 1};
var two = new Object();
console.log(one.__proto__ === Object.prototype)//true
console.log(two.__proto__ === Object.prototype)//true

作用:

隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当我们访问obj这个对象中的x属性时,如果在obj中找不到,那么就会沿着proto依次查找。

prototype(显式原型)

不像每个对象都有proto属性来标识自己所继承的原型,只有函数才有prototype属性。

JS不像其它面向对象的语言,它没有类(class,ES6引进了这个关键字,但更多是语法糖)的概念。JS通过函数来模拟类。

当你创建函数时,JS会为这个函数自动添加prototype属性,值是空对象。而一旦你把这个函数当作构造函数(constructor)调用(即通过new关键字调用),那么JS就会帮你创建该构造函数的实例,实例继承构造函数prototype的所有属性和方法(实例通过设置自己的proto指向承构造函数的prototype来实现这种继承)。

JS是单继承的,Object.prototype是原型链的顶端,所有对象从它继承了包括toString等等方法和属性。Object.prototype.proto === null,说明原型链到Object.prototype终止。

作用:

显式原型的作用:用来实现基于原型的继承与属性的共享。


Object本身是构造函数,继承了Function.prototype;Function也是对象,继承了Object.prototype。

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

instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的prototype 属性。

ES规范是怎么说的?

Function本身就是函数,Function.proto是标准的内置对象Function.prototype。
Function.prototype.proto是标准的内置对象Object.prototype。

例一

//函数声明创一个空函数
function foo(){}

3229842-48de74a3cfec7e9d

foo这个函数就有个prototype属性,并且它默认有两个属性:constructor和proto。constructor属性会指向它本身foo,proto是在chrome中暴露的(不是一个标准属性),那么foo.prototype的原型会指向Object.prototype。因此Object.prototype上的一些方法toString,valueOf才会被每个一般的对象所使用。

例二

function foo(){}
foo.prototype.x = 1;
var obj3 = new foo();//实例对象
console.log(obj3.x); //1

我们这里有个foo函数,这个函数有个prototype的对象属性,它的作用就是当使用new foo()去构造实例的时候,这个构造器的prototype属性会用作new出来的这些对象的原型。

在这个例子中,obj3.proto===foo.prototype,即,每个对象都有一个proto属性,指向创建该对象的函数的prototype。Object.prototype是一个特例,它的proto指向的是null。

例三

// 声明构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 通过prototye属性,将方法放到原型对象上
Person.prototype.getName = function() {
    return this.name;
}

var p1 = new Person('tim', 10);
var p2 = new Person('jak', 22);
3229842-3e7187f42cdfcda9

构造函数的prototype与所有实例对象的proto都指向原型对象。而原型对象的constructor指向构造函数。

二、原型链

每一个构造函数都有一个原型对象,当我们让某一原型对象等于另一构造函数的实例,此时该原型对象就包含一个指针,该指针指向这一构造函数的原型对象,该指针指向的原型对象中包含一个指向这一构造函数的指针,同样我们可以令该指针指向的原型对象等于另一构造函数的实例,如此递进,则形成一条实例与原型的链条,即原型链。

 function Parent() {
        this.name = 'parent';
    };
    Parent.prototype.getName = function() {
        return this.name;
    };
    function Child() {
        this.childname = 'child';
    }
    Child.prototype = new Parent();
    Child.prototype.getChildName = function() {
        return this.childname;
    };
    var child = new Child();
    console.log(child.getName()); //输出parent
    console.log(child.getChildName());  //输出child

在上面的例子中,child实例指向Child原型,Child原型等于Parent实例,即指向Parent原型。可见本质即是以一个构造函数的实例重写原型对象,形成原型链。

总结

1、所有的对象都有 proto 属性,该属性对应该对象的原型;
2、所有的函数对象都有 prototype 属性,该属性的值会被赋值给该函数创建的对象的 proto属性;
3、所有的原型对象都有constructor属性,该属性对应创建所有指向该原型的实例的构造函数;
4、函数对象和原型对象通过prototype和constructor属性进行相互关联。

extends

用关键词extends声明子类关系:

    class Circle extends Shape {}

extends后面可以接驳任意合法且拥有prototype属性的构造函数。它可以是:

另一个类
源自现有继承框架(译者注:作者指的是原型继承,即使在JavaScript中类继承的本质也是原型继承)的近类函数
一个普通的函数
一个包含一个函数或类的变量
一个对象上的属性访问
一个函数调用

图示

  1. 在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。即:对象具有属性proto,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。
  2. 方法(Function)方法这个特殊的对象,除了和其他对象一样有上述proto属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

上图解析

1.构造函数Foo()构造函数的原型属性Foo.prototype指向了原型对象,在原型对象里有共有的方法,所有构造函数声明的实例(这里是f1,f2)都可以共享这个方法。
2.原型对象Foo.prototypeFoo.prototype保存着实例共享的方法,有一个指针constructor指回构造函数。
3.实例f1和f2是Foo这个对象的两个实例,这两个对象也有属性proto,指向构造函数的原型对象,这样子就可以像上面1所说的访问原型对象的所有方法啦。另外:构造函数Foo()除了是方法,也是对象啊,它也有proto属性,指向谁呢?指向它的构造函数的原型对象呗。函数的构造函数不就是Function嘛,因此这里的proto指向了Function.prototype。其实除了Foo(),Function(), Object()也是一样的道理。原型对象也是对象啊,它的proto属性,又指向谁呢?同理,指向它的构造函数的原型对象呗。这里是Object.prototype.最后,Object.prototype的proto属性指向null。

总结:

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

推荐阅读更多精彩内容