原型对象随笔
prototype、proto和constructor 三者之间的关系
首先来看三个的各自含义
- prototype
构造函数有一个prototype属性,指向实例对象的原型对象。通过同一个构造函数实例化的多个对象具有相同的原型对象 - constructor
原型对象有一个constructor属性,指向该原型对象对应的构造函数 - proto
实例对象有一个proto属性,指向该实例对象对应的原型对象,参照构造函数prototype属性来的。
原型对象
基本理解
无论什么时候,只要创建函数,就会根据规则为该函数创建一个 prototype属性,这个属性指向函数的原型对象。在默认情况下,所有的原型对象,都会自动获得一个constructor属性。这个属性包含一个指向 prototype 属性所在函数的指针。
在创建了自定义构造函数之后,其原型对象默认只会取得 constructor 属性和从Object继承一些方法。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象
例如:
// 定义函数. 会默认为该函数创建prototype属性
function Person(){}
// 创建实例对象, 实例对象会有默认的proto 属性 指向原型对象
var p = new Person()
图示:
实际情况
- 原型对象也是实例对象,实际上,任何对象都可以看做是通过Object()构造函数的new操作实例化的对象。
- 函数也是对象,只不过是具有特殊功能的对象而已。任何函数都可以看做是通过Function()构造函数的new操作实例化的结果。
- Object.prototype的原型对象是null (注意不是Object的原型对象)
- 特殊就是Fuction函数了, 所有的函数都可以看成是构造函数Function()的new操作的实例化对象。那么,Function可以看成是调用其自身的new操作的实例化的结果
function foo(){}
f1 = new foo;
图示:
注意:
原型对象Function.prototype的constructor属性指向构造函数Function();实例对象Object和Foo本身没有constructor属性,需要继承原型对象Function.prototype的constructor属性。
function Foo(){};
var f1 = new Foo;
console.log(Function.prototype.constructor === Function);//true
console.log(Foo.constructor === Function);//true
console.log(Foo.hasOwnProperty('constructor'));//false
console.log(Object.constructor === Function);//true
console.log(Object.hasOwnProperty('constructor'));//false
原型链
其实,在对象去查找一个属性的时候,会现在对象本身上查找,如果不存在,则在其原型对象上查找,一直查找到null为止。这种通过原型层层连接起来的就称为成了原型链
注意: 原型对象可以被修改,可以被重写。注意修改和重写的区别:
修改原型对象
function Foo(){}
foo.prototype.name = 'fun'
var f1 = new Foo;
alert(f1.name); // fun 可以访问到原型对象上的属性
字面量改变
function Foo(){}
Foo.prototype = {
name:"foo"
}
var f1 = new Foo;
alert(f1.name); // fun 可以访问到原型对象上的属性
注意当以这种字面量来创建,虽然结果是正确的但是 constructor 并不在指向 Foo 函数了。 而现在我们完全重写了prototype 对象了。(就是改变了Foo.prototype的指向一个新创建的对象)因此,constructor属性指向新对象的constructor属性(新对象由Objecct创建出来的,因此指向了Object构造函数)
console.log(f1 instanceof Objecct) // true
console.log(f1 instanceof Foo) // true
console.log(f1.constructor == Foo) // false
console.log(f1.constructor == Object) // true
所以通常会在改变prototype时指定constructor属相
unction Foo(){}
Foo.prototype = {
constructor: Foo,
name:"foo"
}
var f1 = new Foo;
alert(f1.name); // fun 可以访问到原型对象上的属性
动态改变
其实原型对象和实例对象之间连接就一个指针,而非副本,这样松散的连接关系, 就可让我们在原型对象做的任何操作,在实例上立即反应出来。
function Foo(){}
f1 = new Foo;
// 这里改变对象本身, 此时 实例对象和构造函数的原型对象是同一地址
Foo.prototype.sayHello = function(){
console.log("hello")
}
f1.sayHello() // 'hello'
请记住,实例中的prototype指向原型对象,在创建时就构造函数已经决定了。 不会随着构造函数的原型对象指针的改变而发生改变。
function Foo(){}
f1 = new Foo;
// 这里改变了指向。 此时 实例对象和构造函数的原型对象不在指向同一地址了
Foo.prototype = {
constructor: Foo,
sayHello : function(){
console.log("hello")
}
}
f1.sayHello() // erro
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系,之前任何实例对象它们的引用仍然是最初的原型。而现有构造函数,以及之后创建的实例对象会指向重写之后的原型对象。