先说结论,JavaScript的原型链就是该编程语言为了实现面对对象编程的一种设计,基于原型链,可以让JavaScript对象拥有封装、继承和多态等众多面对对象特性。
如果你还单身着,或者你曾经单身过,那么你应该对计算机科学界中的面对对象编程有所耳闻。经验尚浅的杀杀觉得谈起找对象的话题来,Java和C#才是老大哥,当然C++也很的class也很经典,但今天它们都不是主角,今天要讲的是JavaScript在面对对象时的原型链设计。
0、序言
要讲雷锋塔,我们还是得先从雷锋开始说起(Java和JavaScript的关系与雷锋和雷锋塔之间的关系差不多)。
Java的面对对象风格是这样的:
//HelloWorld.java public class HelloWorld{ public static void main(String[] args){ System.out.println("hello world"); } }
//HelloWorld.cs
//HelloWorld.cpp
是不是觉得C#和Java的容易阅读一点?毕竟人家语言诞生的目的就是纯粹的面对对象编程,而C++只是是在C语言之上在增加了类和对象的特性,为了兼容C风格的代码,就还留着独立的入口main函数。
最后,JavaScript的对象一般是这样的:
//HelloWorld.js
我的天呐,这是一个对象?
对,看起来它好像一个python的字典对吧,但在雷锋塔中,它确实是一个叫做“对象”的妖怪。
在传统写法中,JavaScript生成实例对象的方法是通过构造函数:
也可以是这样:
但它还是长着妖怪的模样,所以ES6引入了class关键字这个语法糖:
虽然看起来舒服多了,但它实际上只是相当于涂了点胭脂,好看了一些,“妖怪”的本质还是没有变化。那这个本质是什么呢?就是原型链。
所以,到底什么是JavaScript的原型链?
JavaScript的原型链就是该编程语言为了实现面对对象编程的一种设计,基于原型链,可以让JavaScript对象拥有封装、继承和多态等面对对象特性。
如果你是从Java或者C++开始学习编程的,那一定会对此感到混乱。所以接下来,就讲一下原型链是什么。
1、prototype
在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。
上述例子中,函数的prototype指向了一个对象,而这个对象正是调用构造函数时创建的实例的原型,也就是person1和person2的原型。
原型的概念:每一个javascript对象(除null外)创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中“继承”属性。
让我们用一张图表示构造函数和实例原型之间的关系:
2、__proto__
这是每个对象(除null外)都会有的属性,叫做__proto__,这个属性会指向该对象的原型。
同样是用一张图表示:
3、constructor
每个原型都有一个constructor属性,指向该关联的构造函数。
所以再更新下关系图:
4、实例与原型
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
在这个例子中,我们给实例对象 person 添加了 name 属性,当我们打印 person.name 的时候,结果自然为 Daisy。
但是当我们删除了 person 的 name 属性时,读取 person.name,从 person 对象中找不到 name 属性就会从 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸运的是我们找到了 name 属性,结果为 Kevin。
但是万一还没有找到呢?原型的原型又是什么呢?
5、原型的原型
在前面,我们已经讲了原型也是一个对象,既然是对象,我们就可以用最原始的方式创建它,那就是:
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 __proto__ 指向构造函数的 prototype ,所以我们再更新下关系图:
6、原型链
简单的回顾一下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立。如此层层递进,就构成了实例与原型的链条。这就是所谓的原型链的基本概念。——摘自《javascript高级程序设计》
其实简单来说,就是上述4-5的过程。
继上述五中所说,那 Object.prototype 的原型呢?
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
7、一张图秒懂 JavaScript 原型链
8、结尾
相信上述1-7可以让你对JavaScript的原型链有所了解,但原型链存在的意义是什么呢?
让我们回到开头,再联系一下序幕中Java、C#、C++的面对对象原理,容易知道,JavaScript的原型链就是该编程语言为了实现面对对象编程的一种设计,基于原型链,可以让JavaScript对象拥有封装、继承和多态等众多面对对象特性。
就好像,继承关系在这里,变成了一条链表一样的数据结构,冥冥之中,影响着众多web开发者。