从V8的角度理解原型链

理解原型链

Js 中的原型链是一个比较有意思的话题,它采用了一套巧妙的方法,解决了 Js 中的继承问题。

按我的理解,原型链可以拆分成:

原型(prototype)

链(__proto__)

原型(prototype)

原型(prototype)是一个普通的对象,它为构造函数的实例共享了属性和方法。在所有的实例中,引用到的原型都是同一个对象。

例如:

functionStudent(name){

this.name = name;

this.study =function(){

console.log("study js");

};

}

// 创建 2 个实例

conststudent1 =newStudent("xiaoming");

conststudent2 =newStudent("xiaohong");

student1.study();

student2.study();

上面的代码中,我们创建了 2 个 Student 实例,每个实例都有一个 study 方法,用来打印 "study js"。

这样写会有个问题:2 个实例中的 study 方法都是独立的,虽然功能相同,但在系统中占用的是 2 份内存,如果我创建 100 个 Student 实例,就得占用 100 份内存,这样算下去,将会造成大量的内存浪费。

所以 Js 创造了 prototype。

functionStudent(name){

this.name = name;

}

Student.prototype.study =function(){

console.log("study js");

};

// 创建 2 个实例

conststudent1 =newStudent("xiaoming");

conststudent2 =newStudent("xiaohong");

student1.study();

student2.study();

使用 prototype 之后, study 方法存放在 Student 的原型中,内存中只会存放一份,所有 Student 实例都会共享它,内存问题就迎刃而解了。

但这里还存在一个问题。

为什么 student1 能够访问到 Student 原型上的属性和方法?

答案在 __proto__ 中,我们接着往下看。

链(__proto__)

链(__proto__)可以理解为一个指针,它是实例对象中的一个属性,指向了构造函数的原型(prototype)。

我们来看一个案例:

functionStudent(name){

this.name = name;

}

Student.prototype.study =function(){

console.log("study js");

};

conststudent =newStudent("xiaoming");

student.study();// study js

console.log(student.__proto__ === Student.prototype);// true

从打印结果可以得出:函数实例的 __proto__ 指向了构造函数的 prototype,上文中遗留的问题也就解决了。

但很多同学可能有这个疑问。

为什么调用 student.study 时,访问到的却是 Student.prototype.study 呢?

答案在原型链中,我们接着往下看。

原型链

原型链指的是:一个实例对象,在调用属性或方法时,会依次从实例本身、构造函数原型、构造函数原型的原型... 上去寻找,查看是否有对应的属性或方法。这样的寻找方式就好像一个链条一样,从实例对象,一直找到 Object.prototype ,专业上称之为原型链。

还是来看一个案例:

functionStudent(name){

this.name = name;

}

Student.prototype.study =function(){

console.log("study js");

};

conststudent =newStudent("xiaoming");

student.study();// study js。

// 在实例中没找到,在构造函数的原型上找到了。

// 实际调用的是:student.__proto__.say 也就是 Student.prototype.say。

student.toString();// "[object Object]"

// 在实例中没找到。

// 在构造函数的原型上也没找到。

// 在构造函数的原型的原型上找到了。

// 实际调用的是 student.__proto__.__proto__.toString 也就是 Object.prototype.toString。

可以看到, __proto__ 就像一个链一样,串联起了实例对象和原型。

同样,上面代码中还会存在以下疑问。

为什么 Student.prototype.__proto__ 是 Object.prototype?

这里提供一个推导步骤:

先找 __proto__ 前面的对象,也就是 Student.prototype 的构造函数。

得出 Student.prototype 的构造函数是 Object。

object 的构造函数是 Object。

判断 Student.prototype 类型, typeof Student.prototype 是 object。

所以 Student.prototype.__proto__ 是 Object.prototype。

这个推导方法很实用,除了自定义构造函数对象之外,其他对象都可以推导出正确答案。

原型链常见问题

原型链中的问题很多,这里再列举几个常见的问题。

Function.__proto__ 是什么?

找 Function 的构造函数。

a. 判断 Function 类型,typeof Function 是 function。

b. 函数类型的构造函数就是 Function。

c. 得出 Function 的构造函数是 Function。

所以 Function.__proto__ = Function.prototype。

Number.__proto__ 是什么?

这里只是稍微变了一下,很多同学就不知道了,其实和上面的问题是一样的。

找 Number 的构造函数。

a. 判断 Number 类型,typeof Number 是 function。

b. 函数类型的构造函数就是 Function。

c. 得出 Number 的构造函数是 Function。

所以 Number.__proto__ = Function.prototype。

Object.prototype.__proto__ 是什么?

这是个特例,如果按照常理去推导,Object.prototype.__proto__ 是 Object.prototype,但这是不对的,这样下去原型链就在 Object 处无限循环了。

为了解决这个问题,Js 的造物主就直接在规定了 Object.prototype.__proto__ 为 null,打破了原型链的无线循环。

明白了这些问题之后,看一下这张经典的图,我们应该都能理解了。

深入原型链

介绍完传统的原型链判断,我们再从 V8 的层面理解一下。

V8 是怎么创建对象的

Js 代码在执行时,会被 V8 引擎解析,这时 V8 会用不同的模板来处理 Js 中的对象和函数。

例如:

ObjectTemplate 用来创建对象

FunctionTemplate 用来创建函数

PrototypeTemplate 用来创建函数原型

细品一下 V8 中的定义,我们可以得到以下结论。

Js 中的函数都是 FunctionTemplate 创建出来的,返回值的是 FunctionTemplate 实例

Js 中的对象都是 ObjectTemplate 创建出来的,返回值的是 ObjectTemplate 实例

Js 中函数的原型(prototype)都是通过 PrototypeTemplate 创建出来的,返回值是 ObjectTemplate 实例

所以 Js 中的对象的原型可以这样判断:

所有的对象的原型都是 Object.prototype,自定义构造函数的实例除外。

自定义构造函数的实例,它的原型是对应的构造函数原型。

在 Js 中的函数原型判断就更加简单了。

所有的函数原型,都是 Function.prototype。

下图展示了所有的内置构造函数,他们的原型都是 Function.prototype。

看到这里,你是否也可以一看就看出任何对象的原型呢?

附:V8 中的函数解析案例

了解完原型链之后,我们看一下 V8 中的函数解析。

functionStudent(name){

this.name = name;

}

Student.prototype.study =function(){

console.log("study js");

};

conststudent =newStudent('xiaoming')

这段代码在 V8 中会这样执行:

// 创建一个函数

v8::Local Student = v8::FunctionTemplate::New();

// 获取函数原型

v8::Local proto_Student = Student->PrototypeTemplate();

// 设置原型上的方法

proto_Student->Set("study",v8::FunctionTemplate::New(InvokeCallback));

// 获取函数实例

v8::Local instance_Student = Student->InstanceTemplate();

// 设置实例的属性

instance_Student->Set("name",String::New('xiaoming'));

// 返回构造函数

v8::Localfunction=Student->GetFunction();

// 返回构造函数实例

v8::Localinstance=function->NewInstance();

以上代码可以分为 4 个步骤:

创建函数模板。

在函数模板中,拿到函数原型,并赋值。

在函数模板中,拿到函数实例,并赋值。

返回构造函数。

返回构造函数实例。

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