上一章介绍了构造函数创建对象,可以看做工厂制造汽车
function Car(color) {
this.brand = 'BMW';
this.hieght = 1400;
this.lang = 4900;
this.color = color;
}
var car = new Car('black');
var car1 = new Car('red');
它们不仅有着相同的基本属性,还有着可选配的属性如颜色等。
可按照代码来看,即使是相同的属性,在创建新对象时,总是要重新执行构造函数的语句,这样显得代码冗余,有什么优化方法吗?
原型
定义
原型(也称显式原型 )是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先,通过构造函数产生的对象,可以继承该原型的属性和方法,原型也是对象。
用法
于是我们可以把原型当做“模具”,将相同的属性写在原型中。
Car.prototype.brand = 'BMW';
Car.prototype.height = 1400;
Car.prototype.lang = 4900;
function Car(color) {
this.color = color;
}
var car = new Car('black');
var car1 = new Car('red');
console.log(car.lang);//4900
console.log(car1.height);//1400
我们把属性值相同的属性brand,height,lang添加给构造函数的原型对象中。创建的对象car,car1会继承构造函数Car()的原型对象Car.prototype里的属性
值得注意的是,要是构造函数中定义了属性名相同的属性,则不会继承原型对象的属性(就近原则)
Car.prototype.color = 'red';
function Car() {
this.color = 'black';
}
var car = new Car();
console.log(car.color);//black
对原型对象属性(或方法)进行增删改查,要直接对原型对象进行操作,对继承了原型对象属性的新对象操作无效
Car.prototype.color = 'red';
Car.prototype.lang = 4900;
function Car() {
}
var car = new Car();
car.color = 'black';
delete car.lang;
console.log(Car.prototype.color);//red
console.log(Car.prototype.lang);//4900
既然原型本来就是个对象,我们可以简化写法
Car.prototype = {
brand: 'BMW',
height: 1400,
lang: 4900
}
function Car(color) {
this.color = color;
}
var car = new Car('black');
var car1 = new Car('red');
console.log(car.lang);//4900
console.log(car1.height);//1400
constructor
对象里的constructor属性,它能找到构造这个对象的构造函数
function Car(){
}
var car = new Car();
console.log(car.constructor);//ƒ Car(){}
可我们并没有给对象定义这个属性,我们去构造函数的原型对象中找找看
console.log(Car.prototype);//{constructor: ƒ}
原来系统自带的原型对象中自带一个constructor属性,来指向对应的构造函数
constructor属性可手动修改
function Person(){
}
function Car(){
}
Car.prototype = {
constructor: Person
}
var car = new Car();
console.log(car.constructor);//ƒ Person(){}
__proto__
既然构造函数创建的对象可以继承构造函数的原型对象中的属性和方法,那么它们是怎样被关联起来的呢?
在前文中,我们讲过构造函数三段式。实际上,在第一步中创建的对象并不是空对象,而是一个包含了由系统自己创建属性名为__proto__的属性(也称隐式原型),属性值为构造函数的原型对象的引用
Car.prototype.brand = 'BMW';
function Car(){
//var this = {
// __proto__ : Car.prototype
//}
}
var car = new Car();
console.log(car.brand);//BMW
在访问car.brand属性时,对象会先在自己的构造函数中查找这个属性。若没有找到,则会通过__proto__属性找到构造函数的原型对象,在原型对象中查找该属性。
可通过修改__proto__来验证这点
Car.prototype.brand = 'BMW';
function Car(){
//var this = {
// __proto__ : Car.prototype
//}
}
var obj = {
brand: 'abc'
}
var car = new Car();
car.__proto__ = obj;
console.log(car.brand);//abc
在访问car.brand时,在自己的构造函数中并无此属性,修改car的__proto__属性,它指向另一个对象obj,在obj中查找到brand属性值为abc,故输出结果为abc。
原型链
我们可以手动修改prototype来模拟祖孙三代的继承关系
Grand.prototype.lastName = 'Lee';
function Grand() {
//this {
// __proto__: Grand.prototype
//}
}
var grand = new Grand();
//grand.__proto__ = Grand.prototype ;
Father.prototype = grand;
function Father() {
//this {
// __proto__: Father.prototype;
//}
}
var father = new Father();
//father.__proto__ = Father.prototype;
Son.prototype = father;
function Son() {
//this {
// __proto__: Son.prototype
//}
}
var son = new Son();
//son.__proto__ = Son.prototype;
console.log(son.lastName);
console.log(son.toString);
访问son对象的lastName属性,首先在自身查找,未找到该属性,通过son.__proto__属性,于father对象中查找,还是未找到该属性,再通过father.__proto__属性进入grand对象中查找,最后通过grand.__proto__属性进入Grand.prototype中找到Grand.prototype.lastName = ‘Lee’;故输出结果为Lee。
这种通过__proto__属性把原型连成链,按照链的顺序去访问的方式就是原型链。
然而这还不是终点,原型对象Grand.prototype中也有一个__proto__属性,指向Object.prototype,这是所有对象的最终原型。
查找顺序:
son{}对象
--> 通过son.__proto__来查找Son.prototype{}(也就是father{}对象)
--> 通过father.__proto__查找Father.prototype{}(也就是grand{}对象)
--> 通过grand.__proto__查找Grand.prototype{}
--> 通过Grand.prototype.__proto__查找Object.prototype{}(终点)
--> undefined
Object.create(原型)
Object上有个create方法,也可用于创建对象,参数为给新建对象制定的原型对象,不能省略
var obj = {name: 'Sunny'};
var obj1 = Object.create(obj);
console.log(obj1.name);//Sunny
用对象字面量的方式创建一个name属性为’Sunny’的对象obj,再用Object.create()方法创建一个新对象obj1,将obj对象作为参数。访问obj1的name属性,得到的结果与obj相同。
还可以给参数赋值为null,这样得到的新对象没有原型
var obj = Object.create(null);
即使人为的给新对象加上__proto__属性,仍然不会被系统承认。
obj.__proto__ = {name: 'Sunny'};
console.log(obj.name);//undefined
所以绝大多数对象最终都会继承Object.prototype,并不是全部对象都会(比如上述无原型的对象)。
我们也可以解释为什么undefined和null没有toString()方法了。
undefined与null既不是对象,又无法进行包装类,没有原型,故无法继承Object.prototype里的toString()方法。
而number类型调用toString()方法的过程则是这样
var num = 123;
num.toString();
-->new Number(num).toString();
-->Number.prototype.toString()
注意,Number.prototype中就可以查找到toString()方法,并不需要再沿着原型链寻找到Object.prototype中
这种原型上有这个方法,而自身又写了一个和原型上的方法同名却不同功能的方法称做方法的重写。