作为 JavaScript 的一大特色,原型给很多初入前端的人带来了相当大的困扰(对,我说的就是我自己)。
JavaScript的数据类型
众所周知,JavaScript 中有 8 种数据类型
- 原始类型
undefinedbooleannumberstringnullbigintsymbol
object
更详细可查看 MDN
本次所说的原型,只跟 Object 有关。暂时不管其他几种原始类型了。
显式原型与隐式原型
- 显式原型
prototype - 隐式原型
__proto__
任意对象都有隐式原型,只有函数才有显式原型
显式原型
创建函数的时候,这个函数会自动带有 prototype 属性。这个属性值类型是一个也是一个对象,称之为原型对象。该对象中有一个 constructor 属性,属性值指向函数本身。
function fn () {}
console.log(fn.prototype.constructor === fn); // true
隐式原型
JavaScript 种任意对象都有一个隐式原型。在浏览器中,可通过 __proto__ 访问其隐式原型对象。对象的隐式原型总是指向其构造函数的原型对象
function fn () {}
console.log(fn.__proto__ === Function.prototype); // true
因为 fn 是 Function 的实例,也就是说其构造函数是 Function。
一个例外
Object 原型对象的隐式原型为 null。这是个规定,不解释。就好比,不能证明公理一样,这个规定是 JavaScript 的公理。
Object.prototype.__proto__ === null; // true
原型链
了解显式原型和隐式原型后,就可以很明白的解释清楚原型链了。原型链,这是一条链,体现在哪里?就体现在对象的隐式原型总是指向其构造函数的原型对象这句话上。
class Animal {}
class Dog extends Animal {}
class ChineseDog extends Dog {}
const chineseDog = new ChineseDog()
console.log(chineseDog.toString())
先想一下,关于 chineseDog 以及各个 function 的原型之间的联系怎样
graph TD
chineseDog --> chineseDog.__proto__
chineseDog --> ChineseDog.prototype
chineseDog.__proto__ --> ChineseDog.prototype.__proto__
chineseDog.__proto__ --> Dog.prototype
ChineseDog.prototype.__proto__ --> Dog.prototype.__proto__
ChineseDog.prototype.__proto__ --> Animal.prototype
Dog.prototype.__proto__ --> Animal.prototype.__proto__
Dog.prototype.__proto__ --> Object.prototype
Animal.prototype.__proto__ --> Object.prototype.__proto__ --> null
看到没有,这就是原型链。上图中,所有处于平级的,都是等价的。例如 chinedeDog.__proto__ === Chinese.prototype。
当访问一个 chineseDog 的 toString 时,就会顺着 __proto__ 这条链找下去,如果一直到最后还没找到,就会返回 null 。其实在本例中,最终会在 Object.prototype 也就是 Animal.prototype.__proto__ 上找到 toString 方法
一些代码
function Person () {}
var p = new Person();
Person.__proto__ === Function.prototype // true。因为 Person是function,是Function 的实例
Object.__proto__ === Function.prototype // true。因为 Object也是function,也是Function 的实例
Function.__proto__ === Function.prototype // true 因为 Function也是function,也是Function 的实例
最后,放一张神图~