JavaScript中其实并没有类的概念。从es6通过class关键字才引入了Class这个概念,但是ES6的class可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到。。
es6之前,实现生成实例对象的传统方法是通过构造函数。将实例对象与构造函数连接起来的就是原型与原型链。
JavaScript中有两个原型:
__proto__:隐式原型prototype:显式原型但是,这两种原型并不存在于JavaScript的所有对象中。那么在哪里可以体现出这两个原型呢?先看代码:
var a = []
var o = {}
function fn(){}
console.log(a.__proto__); // 非undefined
console.log(a.prototype); // undefined
console.log(o.__proto__); // 非undefined
console.log(o.prototype); // undefined
console.log(fn.__proto__); // 非undefined
console.log(fn.prototype); // {constructor: f}
结果如下:

可以看到:
数组、对象、函数三个引用类型都有__proto__属性(隐式原型)
只有函数除了__proto__之外还有一个prototype属性(显式原型)
所有的函数默认都会拥有一个名为prototype的公有且不可枚举(enumerable)的属性,它会指向另一个空的对象。
下面用构造函数来说明:

可以看到,a是通过构造函数创建的一个新对象,通过a.__proto__可以看到foo.prototype的内容。其实a.__proto__就是foo.prototype的引用,它俩指向的都是同一个地址(a.__proto__ === foo.prototype // true)。
小结:
实例对象a只有隐式原型(__proto__);构造函数既有隐式原型(__proto__)也有显式原型(prototype)。
__proto__和prototype都是对象,是对象说明它们也都有.__proto__属性:
a.__proto__.__proto__
foo.prototype.__proto__
实例对象的隐式原型指向了构造函数的显式原型:a.__proto__ === foo.prototype
访问实例对象的某个属性,如果实例对象上没有,就通过实例对象.__proto__找到构造函数的prototype继续找。
function foo() { }
foo.prototype.name = "张三"
var a = new foo()
a.name // 张三
什么是原型弄懂了,原型链是什么大致也就明了了。先看如下代码的输出:
function foo() { }
foo.prototype.name = "张三"
var a = new foo()
console.log('1------', a.__proto__);
console.log('2------', a.__proto__.__proto__);
console.log('3------', a.__proto__.__proto__.__proto__);
console.log(a.name); // 张三
console.log(a.toString()); // [object Object]
结果如下:

可以看到:
a.__proto__:实例对象a的__proto__,指向的是构造函数foo的prototype
a.__proto__.__proto__:实例对象a的__proto__的__proto__指向的是Object()构造函数对象。
a.__proto__.__proto__.__proto__:Object对象的__proto__最终指向了null。
这样一级一级的__proto__相连接起来的链式结构就是**原型链*。*
而在最后两行的console中:
a.name输出的是"张三",但是实例对象a并没有name这个属性,它是js引擎通过原型链查找到a.__proto__(即foo.prototype)找到的。
a.toString()这个方法在foo构造函数以及a实例对象中均没有,它存在于原型链中的a.__proto__.__proto__找到了Object()构造函数对象的prototype(显式原型)上。
因此,在查找一个对象的属性时,JS引擎就是在这样的原型链上一级一级的向上查找的,如果找到顶层的Object对象的__proto__还是没有找到某个属性,就会抛出错误。
可以看到,不管是实例对象a还是构造函数foo,又或者是Object()构造函数。它们的__proto__(隐式原型)上都有一个constructor属性。
这个constructor属性是所有函数的prototype属性自带的一个属性,它不可枚举(enumerable = false)且通过new foo()创建的对象也有一个constructor属性,指向了这个对象的构造函数。所以,实例对象、构造函数的原型、构造函数三者的关系是这样的:
a.constructor === foo.prototype === foo
其实,构造函数的原型上的constructor就是当前这个构造函数的引用:
function foo() { }
console.log(foo.prototype.constructor === foo); // true