干货 | 快速读懂 JS 原型链

2023-05-27 0 1,051

前段时间参与了子公司外部控制技术撷取,撷取老师提及了 Js 蓝本链的难题,并从 V8 的视点进行收敛,创下了我以后对蓝本链的重新认识,听瘤果下定决心徐广呵呵蓝本链,稳固呵呵此基础。

认知蓝本链

深入细致蓝本链

归纳与思索

认知蓝本链

Js 中的蓝本链是两个较为有趣的热门话题,它选用了两套别出心裁的方式,化解了 Js 中的承继难题。

按我的认知,蓝本链能拆分为:

蓝本(prototype)

链(__proto__)

蓝本(prototype)

原型(prototype)是两个一般的第一类,它为缺省的示例共享资源了特性和方式。在大部份的示例中,提及到的蓝本都是同一第一类。

比如:

function Student(name) {

this.name = name;

this.study = function () {

console.log(“study js”);

};

}

// 建立 2 个示例

const student1 = new Student(“xiaoming”);

const student2 = new Student(“xiaohong”);

student1.study();

student2.study();

上面的代码中,我们建立了 2 个 Student 示例,每个示例都有两个 study 方式,用来打印 “study js”。

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

所以 Js 创造了 prototype。

function Student(name) {

this.name = name;

}

Student.prototype.study = function () {

console.log(“study js”);

};

// 建立 2 个示例

const student1 = new Student(“xiaoming”);

const student2 = new Student(“xiaohong”);

student1.study();

student2.study();

使用 prototype 之后, study 方式存放在 Student 的蓝本中,内存中只会存放一份,大部份 Student 示例都会共享资源它,内存难题就迎刃而解了。

但这里还存在两个问题。

为什么 student1 能够访问到 Student 蓝本上的特性和方式?

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

链(__proto__)

链(__proto__)能认知为两个指针,它是示例对象中的两个特性,指向了缺省的蓝本(prototype)。

我们来看两个案例:

function Student(name) {

this.name = name;

}

Student.prototype.study = function () {

console.log(“study js”);

};

const student = new Student(“xiaoming”);

student.study(); // study js

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

从打印结果能得出:函数示例的__proto__指向了缺省的 prototype,上文中遗留的难题也就化解了。

但很多老师可能有这个疑问。

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

答案在蓝本链中,我们接着往下看。

蓝本链

蓝本链指的是:两个示例第一类,在调用特性或方式时,会依次从示例本身、缺省蓝本、缺省蓝本的蓝本… 上去寻找,查看是否有对应的特性或方式。这样的寻找方式就好像两个链条一样,从示例第一类,一直找到 Object.prototype ,专业上称之为蓝本链。

还是来看两个案例:

function Student(name) {

this.name = name;

}

Student.prototype.study = function () {

console.log(“study js”);

};

const student = new Student(“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 类型,typeof Student.prototype是object。

object的构造函数是 Object。

得出 Student.prototype 的缺省是 Object。

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

这个推导方法很实用,除了自定义缺省第一类之外,其他第一类都能推导出正确答案。

蓝本链常见难题

蓝本链中的难题很多,这里再列举几个常见的难题。

Function.__proto__ 是什么?

找 Function 的构造函数。

判断 Function 类型,typeof Function是function。

函数类型的缺省就是 Function。

得出 Function 的缺省是 Function。

所以Function.__proto__= Function.prototype。

Number.__proto__ 是什么?

这里只是稍微变了呵呵,很多老师就不知道了,其实和上面的难题是一样的。

找 Number 的构造函数。

判断 Number 类型,typeof Number是function。

函数类型的缺省就是 Function。

得出 Number 的缺省是 Function。

所以Number.__proto__= Function.prototype。

Object.prototype.__proto__ 是什么?

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

为了化解这个难题,Js 的造物主就直接在规定了Object.prototype.__proto__为 null,打破了蓝本链的无线循环。

明白了这些难题之后,看呵呵这张经典的图,我们应该都能认知了。

干货 | 快速读懂 JS 原型链

深入细致蓝本链

介绍完传统的蓝本链判断,我们再从 V8 的层面认知呵呵。

V8 是怎么建立第一类的

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

比如:

ObjectTemplate 用来建立第一类

FunctionTemplate 用来建立函数

PrototypeTemplate 用来建立函数蓝本

干货 | 快速读懂 JS 原型链

干货 | 快速读懂 JS 原型链

干货 | 快速读懂 JS 原型链

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

Js 中的函数都是 FunctionTemplate 建立出来的,返回值的是 FunctionTemplate 示例。

Js 中的第一类都是 ObjectTemplate 建立出来的,返回值的是 ObjectTemplate 示例。

Js 中函数的蓝本(prototype)都是通过 PrototypeTemplate 建立出来的,返回值是 ObjectTemplate 示例。

所以 Js 中的第一类的蓝本能这样判断:

大部份的第一类的蓝本都是 Object.prototype,自定义缺省的示例除外。

自定义缺省的示例,它的蓝本是对应的缺省蓝本。

在 Js 中的函数蓝本判断就更加简单了。

大部份的函数蓝本,都是 Function.prototype。

下图展示了大部份的内置缺省,他们的蓝本都是 Function.prototype。

干货 | 快速读懂 JS 原型链

看到这里,你是否也能一看就看出任何第一类的蓝本呢?

附:V8 中的函数解析案例

了解完蓝本链之后,我们看呵呵 V8 中的函数解析。

function Student(name) {

this.name = name;

}

Student.prototype.study = function () {

console.log(“study js”);

};

const student = new Student(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::Local function = Student->GetFunction();

// 返回缺省示例

v8::Local instance = function->NewInstance();

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

建立函数模板。

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

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

返回缺省。

返回缺省示例。

V8 中的整体执行流程是符合正常预期的,这里了解呵呵即可。

归纳与思索

本文分别从传统 Js 方面、V8 层面组件剖析了蓝本链的本质,希望大家都能有所收获。

阿里的秒杀系统是怎么设计的?

2021-01-05

2020年全球程序员收入报告出炉,字节跳动成唯一上榜中国子公司

2021-01-02

2020年全球知名开源项目更新大盘点

2021-01-01

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务