JavaScript面向对象系列:四、构造函数和原型对象

[[Prototype]] 属性

一个对象实例通过内部属性 [[Prototype]] 跟踪其原型对象。该属性是一个指向该实例使用的原型对象的指针,当你new创建一个新的对象时,构造函数的原型对象会被赋值给该对象的 [[Prototype]] 属性。

可以调用对象的 Object.getPrototypeOf() 方法读取[[Prototype]] 属性的值。

var obj = {};
var prototype = Object.getPrototypeOf(obj);
console.log(prototype === Object.prototype); //true

大部分js引擎都会提供一个 __ proto __属性。该属性使你可以直接读写 ___[[Prototype]]___ 属性。

可以使用 isPrototypeOf() 方法检查某个对象是否是另一个对象的原型对象,该方法在所有对象中都有.

var obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); //true

因为obj是一个泛用对象,原型是Object.prototype

当读取一个对象的属性时,js引擎首先在该对象的自有属性中查找属性名。如果找到则返回。如果自有属性中不包含该名字,则js引擎会搜索[[Prototype]] 中的对象,如果找到则返回。如果找不到,则返回undefined。

var obj = {};
console.log(obj.toString()); //"[object Object]"

obj.toString = function(){
return "[object Custom]";
}
console.log(obj.toString()); //"[object Custom]"

delete obj.toString;
console.log(obj.toString()); //"[object Object]"

delete obj.toString;
console.log(obj.toString()); //"[object Object]"

上述例子也说明了,仅当自有属性被删除时,原型属性才会再一次被使用。delete操作符仅对自有属性起作用,无法删除原型对象的属性。

在构造函数中使用原型对象

使用对象字面量形式改写原型对象时要注意 constructor 属性

例如

function Person(name){
this.name = name;
}

Person.prototype = {
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");

console.log(person1 instanceof Person); //true
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true

使用这种方式创建原型对象,constructor 属性会有误差。每一个原型对象都有一个 constructor 属性,这是其他对象实例没有的。

当一个函数被创建时,它的 prototype 属性也被创建,且该原型对象的 constructor 属性指向该函数。当使用对象字面量形式改写原型对象的时候,constructor 被置为泛对象Object。得手动修正。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

console.log(person1 instanceof Person); //true
console.log(person1.constructor === Person); //true
console.log(person1.constructor === Object); //false

console.log(person2 instanceof Person); //true
console.log(person2.constructor === Person); //true
console.log(person2.constructor === Object); //false

构造函数、原型对象和对象实例之间的关系最有趣的一个方面也许就是对象实例和构造函数之间没有直接联系。不过对象实例和原型对象以及原型对象和构造函数之间都有直接联系。

改变原型对象

给定类型的所有对象实例功效一个原型对象,所以可以一次性扩充所有对象实例。_[[Prototype]] 属性只是包含了一个指向原型对象的指针。任何对原型对象的改变都立即反映到所有引用它的对象实例上。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

console.log("sayHi" in person1); //false
console.log("sayHi" in person2); //false

Person.prototype.sayHi = function(){
console.log("Hi");
};

person1.sayHi(); //"Hi"
person2.sayHi(); //"Hi"

对象封印和对象冻结只能操作对象的自有属性。封印或者冻结之后,无法添加自有属性或者改变冻结对象的自有属性。

例如

function Person(name){
this.name = name;
}

Person.prototype = {
constructor:Person,
sayName:function(){
console.log(this.name);
},
toString:function(){
return "[Person " + this.name + "]";
}
}

var person1 = new Person("laowang");
var person2 = new Person("xiaowang");

Object.freeze(person1);

console.log("sayHi" in person1); //false
console.log("sayHi" in person2); //false

Person.prototype.sayHi = function(){
console.log("Hi");
};

person1.sayHi(); //"Hi"
person2.sayHi(); //"Hi"

其实,[[Prototype]] 属性是对象实例的自有属性,属性本身被冻结,但是其指向的值(原型对象)并没有被冻结。

实际开发中,可能不会频繁的使用原型对象,但是理解对象实例及其原型对象之间的关系是非常重要的。

改变内建原型对象

所有的內建对象都有构造函数,因此也都有原型对象可以改变。

例如,简单修改Array的原型对象

Array.prototype.sum = function(){
return this.reduce(function(pre,cur){
return pre + cur;
});
};

var numbers = [1,2,3,4,5];
var result = numbers.sum();

console.log(result); //15

总结

构造函数就是用 new 操作符调用的普通函数。可以随时定义自己的构造函数来创建多个具有同样属性的对象。可以用 instanceof 操作符或直接访问 constructor 属性来鉴别对象是被哪个构造函数创建的。

每一个函数都有都具有 prototype 属性,它定义了该构造函数创建的所有对象共享的属性。通常,共享的方法和原始值属性被定义在原型对象里,而其他属性都定义在构造函数里。 constructor 属性实际上被定义在原型对象里供所有对象实例共享。

原型对象被保存在对象实例内部的 [[Prototype]] 属性中。这个属性时一个引用而不是副本。由于js查找属性的机制,对原型对象的修改都立刻出现在所有对象实例中。当试图访问一个对象的某个属性时,js首先在自有属性里查找该名字,如果自有属性中没有找到则在原型属性中查找。这样的机制意味着原型对象可以随时改变而引用它的对象实例则立即反映出这些改变。

內建对象也有可以被修改的原型对象。

发表评论

电子邮件地址不会被公开。 必填项已用*标注