构造函数创建对象
我们首先使用构造函数来创建一个对象。
function A () { }; let a = new A(); a.value = 'a'; console.log(a.value); //输出的结果为a.
prototype
每一个函数都会有一个prototype属性(只有函数才具有的属性),prototype属性指向的是调用构造函数创建的实例的原型。原型指的是每一个javascript对象在创建的时候(null除外)都会与之关联的另一个对象。而每一个对象都会从原型中继承该属性。
function A () { }; A.prototype.value = 'A'; let a1 = new A(); let a2 = new A(); a1.value = 'a1'; console.log(a1); //输出a1 --来自实例。 console.log(a2); //输出A --来自原型。 delete a1.value; console.log(a1); //输出A --来自原型。
在以上例子中,需要访问a1中的value时,会在这该对象中搜索名为value的属性,因为value在该对象中的确存在,因此直接返回该值而不必再去搜索原型;同样的因为在a2中不存在value,因此会向上搜索原型,结果在原型中查找出了value属性。
当在对象中添加一个属性时,这个属性就会屏蔽在原型对象中保存的同名属性(a1中的value屏蔽了A中的value)。也就是说添加了这个属性阻止我们访问原型中的那个属性,但不会修改那个属性。不过使用delete操作符则可以完全删除实例的属性,从而重新访问到原型中的属性。
用一张图来表示构造函数和原型的关系:
_proto_
这是每一个对象都具有的属性(null除外),叫做_proto_,会指向该对象的原型。
function A () { }; let a = new A(); console.log(a.__proto__ === A.prototype); //true
由此我们能够更新一下关系图得到:
constructor
既然构造函数和实例对象都能够指向原型,那么原型是否也包含一个属性来指向构造函数和实例对象的呢? 指向实例对象是没有的,因为一个构造函数可以生成多个实例对象。但是指向构造函数的却是有一个,这就要谈到今天要说的第三个属性constructor了。每一个原型都含有一个constructor属性用来指向关联的构造函数。
function A() { }; console.log(A.prototype.constructor === A); //true
所以再次更新关系图:
综上我们可以得出一个结论就是:
function A () { }; let a = new A(); console.log(a.__proto__ === A.prototype); //true console.log(A.prototype.constructor === A); //true
原型的由来
原型对象是通过Object构造函数生成的,结合之前所讲的_proto_指向构造函数的prototype。
function A () { }; console.log(A.prototype.__proto__ === Object.prototype); //true
由此我们可以得出关系图为:
接下来我们再看看Object.prototpye的原型来自什么。
console.log(Object.prototype.__proto__); //null
引用阮一峰老师的《undefined和null的区别》
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:
总结:
A.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null A.protype__proto__ == Object.prototype