注:本文首发于个人博客,移步 获取更好的阅读体验。git
抛开封面图,咱们先以 MDN 的一句话做为开端,github
对于使用过基于类的语言 (如 Java 或 C++) 的开发人员来讲,JavaScript 有点使人困惑,由于它是动态的,而且自己不提供一个 class
实现。虽然在 ES2015/ES6 中引入了 class
关键字,但那只是语法糖,JavaScript 仍然是基于原型的。浏览器
那么到底什么是原型?less
咱们先用构造函数建立一个实例对象,函数
其实理解原型,就是理解构造函数,实例对象和原型对象之间的关系,ui
function Engineer(name) {
this.name = name
}
Engineer.prototype.coding = function() {
console.log('write less, do more.')
}
const engineer = new Engineer('campcc')
复制代码
JavaScript 中,每个构造函数都有一个 prototype
属性,它指向构造函数的原型对象:this
Engineer.prototype // {coding: ƒ, constructor: ƒ}
复制代码
原型对象中有一个 constructor
属性指回构造函数:spa
Engineer.prototype.constructor === Engineer // true
复制代码
而每个实例对象都有一个 __proto__
属性,当咱们使用构造函数建立实例时,实例的 __proto__
属性就会指向构造函数的原型对象:prototype
engineer.__proto__ === Engineer.prototype // true,__proto__ 实际上是一个 JavaScript 的非标准但许多浏览器都实现的属性,从 ECMAScript 6 开始,支持经过符号 [[Prototype]] 或者方法 Object.getPrototypeOf() 访问
复制代码
构造函数,实例对象与原型对象的关系为:3d
为了更好的理解什么是原型链,咱们尝试调用实例对象的几个方法,
engineer.coding() // write less, do more.
engineer.toString() // "[object Object]"
engineer.map() // Uncaught TypeError: engineer.map is not a function
复制代码
结果看似很出乎意料,由于咱们其实并无在实例里定义 coding
和 toString
方法啊,可是它们却可以被成功调用,为何?由于在 JavaScript 中,当咱们试图访问一个对象的属性或方法时,它不单单在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索:
咱们尝试访问 map
方法时报错了,由于原型链上找不到此方法。原型链查找会一直持续,直到找到一个名字匹配的属性或方法,或者达到原型链的末尾,而根据定义,null
就是原型链的末尾:
Object.prototype.__proto__ === null // true,null 在这里能够理解为 “没有对象”
复制代码
若是查找过程当中遇到同名属性或方法,位于原型链底端的属性或方法会被优先应用,这叫作 “属性遮蔽(property shadowing)”。好比咱们在 Engineer
的原型对象上声明一个同名的 name
属性:
Engineer.prototype.name = 'engineer'
engineer.name // campcc,这里不会打印 'engineer',由于在原型链查找的过程当中,实例对象中就已经存在 name 属性了
复制代码
总结一下,原型链其实就是对象或原型对象的 __proto__
组成的一条原型查找链。
几乎全部 JavaScript 中的对象都是位于原型链顶端的 Object 的实例,但有两个例外:
关于封面图,其实隐喻了 JavaScript 中一直存在争议的一个问题,
Function.__proto__ === Function.prototype // true
复制代码
先来看看在 Chorme V8 中的打印结果:
Function.__proto__ // ƒ () { [native code] }
Function.prototype // ƒ () { [native code] }
Object.__proto__ // ƒ () { [native code] }
复制代码
从打印结果来看,根本不存在 "Function 也是 Function 自己的一个实例" 的说法,由于无论是 Function.__proto__
, Function.prototype
仍是 Object.__proto__
都是引擎建立的,Function
也是由引擎建立的,至于为何会相等,只能说每一个语言都存在缺陷,这个问题就像为何 typeof null === "object"
同样,不用过于纠结。
JavaScript 中,
prototype
属性,表明构造函数的原型对象__proto__
属性,指向构造函数的原型对象constructor
属性,指向构造函数,标识原型对象的是由哪一个函数构造的__proto__
组成了一条原型链,原型链其实也是一条查找链null
若是有疑问或者发现错误,能够在相应的 issues 进行提问或勘误
若是喜欢或者有所启发,欢迎 star,对做者也是一种鼓励
(完)