function Person() {}
Person.prototype.name = 'kobe'
Person.prototype.age = '23'
Person.prototype.job = 'player'
Person.prototype.sayName = function() {
console.log(this.name)
}
var person1 = new Person()
var person2 = new Person()
person1.sayName() // 'kobe'
person2.sayName() // 'kobe'
我们创建的每一个函数都有一个prototype属性,这个属性指向该函数的原型对象。那么什么是原型对象呢?
原型对象就是无论任何时候我们创建了一个函数,这个函数都会有prototype属性,而这个属性指向函数的原型对象。也就是说只要创建了一个函数就会自动有一个该函数的原型对象。上面创建了一个Person函数,那么它的原型对象就是Person Prototype。而原型对象中也有一个constructor属性指向有prototype属性的函数。当我们为这个函数创建了实例person1和person2,person1和person2中就会有一个[[prototype]]属性指向原型对象。该属性无法访问,但是浏览器中表现为_proto_。
图像表示如下:
当要查找属性时,先会在实例中查找,如果没有找到,就会在原型对象中查找,如果找到就返回,如果没有找到就返回undefined。
例如代码中的person1.sayName(),首先解析器会问:“person1实例中有sayName属性吗”,答:“没有”。然后继续搜索,再问:“person1的原型中有sayName属性吗”,答:“有”。
如果我们在实例中添加了与原型同名的属性,那么实例中的属性将会屏蔽原型中的那个属性。
关于原型检测的相关方法
1.isPrototypeOf()
[[prototype]]指向调用isPrototypeOf()方法的对象,那么就返回true。
意思就是,[[prototype]]指向一个对象。那是哪个对象呢,就是调用isPrototypeOf()方法的对象。
alert(Person.prototype.isPrototypeOf(person1)) //true
2.getPrototypeOf()
获取一个对象的原型。
alert(Object.getPrototypeOf(person1)) // Person.prototype
3.hasOwnPrototype()
检测一个属性时存在于实例中还是原型中,只在给定属性存在于实例中才会返回true
alert(person1.hasOwnPrototype("name")) //false
4.in操作符
判断对象是否能访问该属性。不伦属性是在实例还是原型中,只要能访问就返回true
alert("name" in person1) //true
alert("sex" in person1) //false
"name"属性虽然不在实例中,但是在原型中,依然可以访问到,返回true。但是sex在实例和原型中都不存在,无法访问,返回false。
对象字面量的形式重写原型对象
Person.prototype = {
constructor: Person,
name: "kobe",
age: 23,
job: "player",
sayName: function() {
alert(this.name)
}
}
上面每次向原型对象添加属性都要写一次Person.prototype,非常繁琐,所以可以通过一个对象字面量的形式,将属性都写在对象字面量里面。但是这样写有一个需要注意的地方,就是这样写的话,相当于我们重写了原型对象,此时Person.prototype中的constructor属性不再指向Person。而是指向Object构造函数。我们可以通过添加constructor属性,并赋值为Person,保证可以访问到正确的值。
如果我们重写了原型对象的话,如果我们想要访问里面的属性,那么我们就必须在重写原型对象之后实例化对象,因为重写原型对象时切断了现有原型与任何之前已经存在的对象实例之间的联系。之前存在的对象实例仍然指向原来的原型对象。
原型对象的缺点:
由于原型中的属性都是共享的,基本类型的属性可以通过同名属性覆盖,但是我们通过其中一个实例修改了原型中的引用类型的属性,那么在其他实例中也是实时更新修改的数据。这或许不是我们想要的结果。
function Person() {}
Person.prototype = {
constructor: Person,
name: "kobe",
age: 23,
job: "player",
friends: ["111","222","333"],
sayName: function() {
console.log(this.name)
}
}
var person1 = new Person()
var person2 = new Person()
person1.friends.push("444")
console.log(person1.friends) // ["111","222","333","444"]
console.log(person2.friends)// ["111","222","333","444"]
也许我们只是想要在person1的friends中添加一个“444”,而person2中不用。
解决方法:
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。好处就是每个实例都会有自己的一份实例属性副本,同时又共享着对方法的使用。
动态原型模式
function Person(name,age,job) {
//property
this.name = name;
this.age = age;
this.job = job;
//method
if(typeof this.sayName != "function") {
// Person.prototype.sayName = function() {
//console.log(this.name);
//}
Person.prototype = {
sayName: function() {
console.log(this.name);
}
} //这样的写函数的方式报错说没有 sayName 这个函数
}
}
var person1 = new Person("lhd",23,"lhf")
var person2 = new Person("111",23,"222")
person2.sayName()
person1.sayName() //person1中是没有sayName这个方法的,所以这里会报错
这个模式就是将原型的初始化在构造函数中执行。if条件判断里面不需要检查每个属性和方法,只要检查其中一个就可以了。
就是if里面有多个方法函数。
寄生构造函数模式
function SpecialArray(){
var values = new Array();
values.push.apply(values,arguments)
values.toPipedString = function(){
return this.join("|");
};
return values;
}
var colors = new SpecialArray("red", "blue", "green");
console.log(colors.toPipedString()); //"red|blue|green"
目的是给原生的构造函数定义方法,这里就是给array构造函数添加了一个toPipedString方法,这样不会对原生构造函数造成污染,减少不必要的麻烦。不建议直接在原生构造函数里面添加方法。SpecialArray与Array之间的区别就是比Array多了一个方法。
稳妥构造函数模式
稳妥对象:没有公共属性,方法也不引用this的对象。
function Person(name,age,job){
var o = new Object();
o.sayName = function() {
alert(name)
},
return o
}
var friends = Person("LHD",23,"player")
friends.sayName();
这里除了调用sayName方法,没有其他办法可以访问name属性。