一、构造函数创建对象
我们先使用构造函数创建一个对象,先来认识下构造函数
function Person() {
}
var person1 = new Person();
person1.name = '张三';
console.log(person1.name) // 张三
在这个例子中,Person 就是一个构造函数,我们使用 new 创建了一个实例对象 person1
prototype
每个函数都有一个 prototype 属性,它指向的是一个对象,既然它是一个对象,我们就手动给上面例子中的Person.prototype对象增加一个color属性:
function Person() { //构造函数Person
}Person.prototype.color = 'white'; //给Person.prototype对象增加一个color属性
const person1 = new Person(); // 创建第一个实例对象person1
const person2 = new Person(); // 创建第二个实例对象person2
console.log(person1.color) // white
console.log(person2.color) // white
其实,函数的 prototype 属性指向的这个对象,正是调用该构造函数而创建的实例对象的原型(原型对象),也就是这个例子中的 person1 和 person2 的原型(原型对象)
那什么是原型呢?你可以这样理解:每一个JavaScript对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中"继承"属性,就是因为会"继承"原型对象中的属性,所以上述例子中创建的两个实例对象person1和person2都默认拥有了color属性,这个color属性就来源于Person.prototype这个原型对象
让我们用一张图表示构造函数和实例原型之间的关系:
这时候,有部分思考的同学就会问了,那你刚才举的例子是通过构造函数创建的实例对象是这样的,那如果直接通过字面量的形式创建的对象也会是这样的吗?
好,我们就来看下:
上述代码可以看出通过字面量形式直接创建的对象obj,它也是"继承"其原型对象上的属性(toString),可能有些同学对于_proto_属性不太清楚,没关系,我们马上就讲,这里你只需要知道对象中的_proto_属性指向其原型对象就可以了,所以这也印证了之前说的那句话:每一个对象都会从原型中"继承"属性
_ proto _
这是每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型。
为了再次验证该说法,我们还是以第一个例子为例:
于是我们更新下三者(构造函数-原型对象-实例对象)的关系图:
既然实例对象和构造函数都可以指向原型,那么原型是否有类似属性指向构造函数或者实例呢?
constructor
每个原型对象都有一个 constructor 属性指向关联的构造函数。
function Person() {}
console.log(Person === Person.prototype.constructor); // true
所以再更新下关系图:
实例属性查询机制
当读取实例的属性时,如果找不到,就会查找与该实例对象关联的原型中的属性,如果还查不到,就去找原型的原型,直到最顶层为止(Object.prototype)。
function Person() { // 构造函数Person
}Person.prototype.color = 'white';
const person1 = new Person(); //创建一个实例对象person1.color = 'black'; // 给该实例对象赋值一个color属性,值为black
console.log(person1.color) // blackdelete person1.color; //删除实例对象上的color属性
console.log(person1.color) // white
在这个例子中,我们给实例对象 person1 添加了 color 属性,当我们打印 person1.color 的时候,结果自然为 black。
但是当我们删除了 person1 的 color 属性时,读取 person1.color,此时person1 对象中找不到 color 属性时就会从 person1 的原型也就是 person1.__ proto __ ,也就是 person1.prototype中查找,幸运的是我们找到了 color 属性,结果为 white。
原型的原型是谁(最终源头是谁?)
我们知道原型对象其实也就是一个对象,那既然是对象它就是可以通过new Object()的方式来创建,所以原型对象其实就是Object的实例对象,所以上述例子中Person.prototype原型对象的原型就是Object.prototype
function Person() { // 构造函数Person
}
Person.prototype.__proto__ === Object.prototype // true
原型链
那 Object.prototype 的原型呢?
是null,我们可以打印看看:
console.log(Object.prototype.__proto__ === null) // true
所以 Object.prototype._ proto _ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。所以查找属性的时候查到 Object.prototype 就可以停止查找了。
最后一张关系图也可以更新为:
其实上图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
特殊注意点
1、我们看个例子:
function Person (){}
const person3 = new Person();
person3.constructor === Person
上面例子中可以看出创建的实例对象中也有constrctor属性,它也是指向创建它的构造函数,看到这有些同学有点蒙了,刚才不是说原型对象中有一个constrctor属性指向构造函数吗?现在怎么实例对象中也有该属性,怎么这么乱呢?
其实这里的constructor属性就是"继承"原型对象中的constructor属性,实例对象本身没有该属性,它自身没有就会去它的原型对象中找,这样解释是不是就立马通顺了…
person.constructor === Person.prototype.constructor // true
2、全篇一直对于"继承"这个词语用引号括起来了,这是因为这种说法并不恰当,因为继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。