三、理解原型对象
只要创建一个新的函数,都会根据特定的规则为该函数创建一个prototype
属性,这个属性指向函数的原型对象,默认情况下,原型对象都会自动获得一个constructor
(构造函数),这个属性是指向prototype
所在函数的指针。
function Person(){
Person.prototype.name = "余嘉";
Person.prototype.age = "27";
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
console.log(this.name);
};
}
var person = new Person();
var person1 = new Person();
console.log(Person.prototype);
//{name: "余嘉", age: "27", job: "Software Engineer", ....}
console.log(person1.__proto__);
//{name: "余嘉", age: "27", job: "Software Engineer", , ....}
console.log(person.__proto__.constructor == person.constructor) //true
console.log(person.constructo == person1.constructo) //true
- 如上代码,创建了自定义的构造函数后,其原型对象默认只会取得
constructor
属性,至于其他的方法,都是从Object
继承而来的。- 当调用构造函数创建一个新的实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。
- 我们称原型的指针叫
「prototype」
,但是实例中无法直接访问它,但是在每个对象上都支持_proto_
这个属性去访问它。

-
根据上图,我们理清楚关系
prototype
指向的是构造函数的原型,可以用Person.prototype
直接访问到它的原型.- 原型的
constructor
指向prototype
所在的构造函数这个方法Person
.- 而通过构造函数
Person
创建的实例person1
,可通过_proto_
访问到构造函数Person
的原型。- 创建的实例
person1
是没有包含属性和方法的,但我们可以调用person1.sayName()
, 这是通过查找对象属性的过程来实现的(下面补充)
。
Person(){
Person.prototype.name = "余嘉",
Person.prototype.age = "27";
Person.prototype.job = "Software Engineer";
}
var person1 = new Person();
var person2 = new Person();
person1.name = "李刚"
console.log(person1.name) // "李刚" 来自实例
console.log(person2.name) // "余嘉" 来自原型
//判断是否为实例属性
person1.hasOwnProperty('name') //true 来自实例
person2.hasOwnProperty('name') //false 来自原型
//判断是否为原型属性(in操作符)
"name" in person1 //true 来自实例
"name" in person2 //true 来自原型
//删除实例属性
delete person1.name;
console.log(person1.name) // "余嘉" 来自原型
查找对象属性过程中---当我们访问person1.name
的时候,需要读取它的值,因此就会在这个实例上搜索一个名为name
的属性,当在实例上存在时,就会停止搜索原型,实例优先。
- 实例上的属性与原型同名时,实例属性会屏蔽原型对象保存的同名属性。
- 在通过
delete
操作符删除实例上的同名属性后,屏蔽解除。- 通过
hasOwnProperty()
方法可以判断属性是否来自于实例。in
操作符只要能在对象上查找到该属性(无论是实例还是原型)
,就会返回true
- 在使用
for-in
循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated
标记为false的属性).
-
更简单的原型语法
function Person(){
};
Person.prototype = {
name:"余嘉",
age:27,
job: "Software Engineer",
sayName:function(){
console.log(this.name)
}
};
var person1 = new Person();
person1 instanceof Person; //true
person1 instanceof Object; //true
- 上面将
Person.prototype
以对象字面量形式创建新的对象,最终的结果相同。
接上面的代码
var person1 = new Person();
person1 instanceof Person; //true
person1.constructor == Person //false;
console.log(person1.constructor) // ƒ Object() { [native code] }
- 注意,
constructor
不再指向Person
了,因为prototype
在默认生成的情况下,constructor
才会指向构造函数本身,但是上面是直接Person.prototype = {}
完全重写了默认的原型对象,因此constructor
指向了Object
,因为Person的原型,是Object
的实例(以对象字面量形式创建的新的对象
)。
下面这种,直接设置constructor
指向,导致constructor
变成了可枚举。
function Person(){
};
Person.prototype = {
constructor:Person,
name:"余嘉",
age:27,
job: "Software Engineer",
sayName:function(){
console.log(this.name)
}
};
var person1 = new Person();
var arr = []
for(var i in person1){
arr.push(i)
}
console.log(arr)
['constructor','name','age','job','sayName']
我们可以使用Object.definedProperty()
去设置constructor
属性,来解决上面的枚举问题。
-
原型的动态性
我们对原型对象上做任何修改,都能从实例上反映(先创建实例,后修改也是)
//Person吐了
function Dog(){
this.prototype.sayHi = function(){
console.log('Hi')
}
}
var dog = new Dog();
Dog.prototype.sayHi = function(){
console.log("wang wang wang")
}
dog.sayHi(); // wang wang wang
但是上面的情况里,重新设置构造函数的prototype
,情况就不一样了。
function Dog(){
}
var dog = new Dog(); //这里实例已经指向Dog开始原型了
Dog.prototype={
sayHi:function(){
console.log("wang wang wang")
}
}
dog.sayHi(); // error
- 这里说明实例和构造函数是没有直接关系的,上面其实是修改了
Dog
方法的原型指向,但是实例的原型指针还是修改之前的原型对象。
-
原生对象的原型
讲了那么多自定义类型的原型模式的方面,原生的引用类型,都是采用这种模式创建的。原生引用类型(Object,Array,String,等等
)都在其构造函数的原型上定义了方法。
typeof Array.prototype.sort //function
typeof String.prototype.substring //function
通过原生对象的原型,可以取得所有默认方法的引用,而且可以定义新的方法,和自定义对象的原型一样。
String.prototype.startsWith = function(text){
return this.indexOf(text) == 0
}
var msg = "hello world!";
msg.startsWith("hello") // true
不推荐在原生对象的原型上自定义方法,可能会导致命名冲突,也可能会意外地重写原生方法。
-
原型对象的缺点
- 它省略了为构造函数传递初始化参数环节,导致所有实例在默认情况下都将取得相同的属性值。
- 原型的共享本质是最大问题,看下面例子
function Person(){
}
Person.prototype = {
name:"余嘉",
age: 27,
firends:['Dog','Cat'],
}
var person1 = new Person();
var person2 = new Person();
person1.firends.push("Mouse");
console.log(person2.firends)
// ['Dog','Cat',"Mouse"]
这种共享带来的问题, 才导致很少有人单独使用原型模式,推荐下面的组合模式。
-
组合使用
和
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。两种结合使用,每个实例都会有自己的一份实力属性的副本,但同时又共享着对方发的引用,最大限度节省内存,集两种模式的长处。
function Person (name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Dog','Cat'];
}
Person.prototype = {
constructor : Person,
sayName : function(){
console.log(this.name) ;
}
}
var person1= new Person("余嘉",27,"Software Engineer");
var person2= new Person("狗子",2,"看家护院");
上面生成的两个实例,把所有信息属性都封装在了构造函数中,而通过初始化构造函数的原型,既保持了自己某些方面的独立性,又公用着同一个方法。