前段时间参与了子公司外部控制技术撷取,撷取老师提及了 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,打破了蓝本链的无线循环。
明白了这些难题之后,看呵呵这张经典的图,我们应该都能认知了。
深入细致蓝本链
介绍完传统的蓝本链判断,我们再从 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 中的函数解析。
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