快速读懂 JS 原型链

2022-12-15 0 293

前段时间参与了子公司外部控制技术撷取,撷取老师提及了 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__.study 也就是 Student.prototype.study。 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<v8::FunctionTemplate> Student = v8::FunctionTemplate::New(); v8::Local<v8::Template> proto_Student = Student->PrototypeTemplate(); // 设置蓝本上的方式 proto_Student->Set(“study”, v8::FunctionTemplate::New(InvokeCallback)); v8::Local<v8::ObjectTemplate> instance_Student = Student->InstanceTemplate(); // 设置示例的特性instance_Student->Set(“name”, String::New(xiaoming)); // 返回缺省 v8::Local<v8::Function> function = Student->GetFunction(); // 返回缺省示例 v8::Local<v8::Object> instance = function->NewInstance();

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

建立函数模板。在函数模板中,拿到函数蓝本,并赋值。在函数模板中,拿到函数示例,并赋值。返回缺省。返回缺省示例。

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

归纳与思索

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

最后,如果你对此有任何想法,欢迎留言评论!

快速读懂 JS 原型链

相关文章

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

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