原型模式
一、原型对象的理解
(1)创建的每个函数都有一个prototype(原型)属性,每个构造函数都有一个原型对象Person.prototype,原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针[[prototype]]
prototype:是一个指针,指向Person.prototype原型对象(该对象的用途是包含可以由特定类型的所有实例共享的属性和方法)
constructor:创建了自定义的构造函数之后,其原型对象默认只会取得construtor属性;至于其他方法,则是从Object继承而来的。
[[prototype]]:当调用构造函数创建一个新实例后,该实例的内部将包含一个指针[[prototype]],指向构造函数的原型对象
_proto_:该链接只存在域实例与构造函数的原型对象之间,不存在于实例与构造函数之间
使用原型对象的好处就是可以让所有的对象实例共享它所包含的属性和方法
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值
(2)如果我们在实例中添加一个属性,而该属性与实例原型中的一个属性同名,那么我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。
function Person(){ } Person.prototype.name = "psx"; Person.prototype.sayName = function(){ alert(this.name) } var person1 = new Person(); var person2 = new Person(); person1.name="wjj" console.log(person1.name)//wjj console.log(person2.name)//psx
person1添加该属性只会阻止我们访问原型中的那个属性,但不会修改原型中的那个属性
可以使用delete删除该属性,然后重新访问原型中的属性
(1)可以使用hasOwnProperty()方法可以检测一个属性是存在于实例中(返回true),还是存在于原型中 所欲实现中都无法访问到[[Prototype]],但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系,如果[[prototype]]指向第哦啊用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true alert(Person.prototype.isPrototypeOf(person));//true (2)Object.getPrototypeOf(),这个方法返回[[prototype]]的值, alert(Object.getPrototypeOf(person1)== Person.prototype);//true(该行代码取得了一个对象的原型)
二、原型与in操作符
有两种方式使用in操作符:单独使用和for-in循环中使用。
alert("name" in person1)//"true"
同时使用hasOwnProperty()方法和in操作符,可以确定该属性到底存在于对象中还是原型中
1 function hasPrototypeProperty(object,name){ 2 3 return !object.hasOwnProperty(name) && (name in object); 4 5 }
由于in操作符只要通过对象能够访问到属性就返回true,hasOwnProperty()只在属性存在于实例中才返回true,所以hasPrototypeProperty()返回true,该属性就存在于原型中
- 要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。该方法接受一个参数作为参数,返回一个包含所有可枚举属性的字符串数组; alert(Object.keys(Person.prototype))
- 如果想得到所有实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames()方法获得
三、重写原型对象
function Person(){ } Person.prototype = {//重写整个原型对象 name:"Nicholas", age:29, sayName: function(){ alert(this.name); } }
将字面量对象重写了Person.prototype后,constructor属性不再指向Person了。前面介绍过:每创建一个函数,就会同时创建它的prototype对象,这个对象会自动获取到constructor属性。在这块代码中,重写了,默认的prototype对象,因此constructor属性也
就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
解决方法:可以手动将新的constructor指向Person,
注意:我们可以随时为原型对象添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,情况就不一样了。因为:调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象就等于切断
了构造函数与最初原型之间的联系(因为此时的constructor属性指向Object函数,可以使用constructor:Person来指向Person),而实例对象中的[[prototype]]指针仍然是指向最初的原型对象,构造函数中的prototype属性指向新的原型对象,重写原型对象切断了
现有原型与任何之前已经存在的对象实例之间的联系;因为他们引用的仍然是最初的原型。
function Person(){ } var friend = new Person(); Person.prototype = {//重写整个原型对象
constructor:Person, name:"Nicholas", age:29, sayName: function(){ alert(this.name); } } friend.sayName();//error
//该代码会报错,因为实例对象的[[prototype]]指针仍然会指向最初的原型对象,可以将var friend = new Person();放在Person.prototype原型对象下面
四、原型对象的问题
原型模式的缺点:它省略了构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型最大的问题是就是原型中所有属性被很多实例共享,当原型中包含引用类型值的属性的时候,就会出问题:
function Person(){ } Person.prototype = { constructor:Person, name:"Ni", friends:["Shel","psx"], } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); console.log(person1.friends) console.log(person2.friends);
console.log(person1.friends === person2.friends)//true
person1和person2会指向同一个数组中,这个问题也正是很少人单独使用原型模式的原因
五、组合使用构造函数模式和原型模式(这是创建自定义类型的最常见方式,是用来定义引用类型的一种默认模式)
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性
function Person( name){ this.name = name; this.friends=["Shel","psx"]; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name) } } var person1 = new Person("psx"); var person2 = new Person("wjj"); person1.friends.push("Van"); console.log(person1.friends);//"Shel", "psx", "Van" console.log(person2.friends);//"Shel", "psx" console.log(person1.friends === person2.friends)//false console.log(person1.sayName === person2.sayName)//true
六、寄生构造函数模式
该模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象(很像典型的构造函数,工厂模式)
function Person(name, age, job){ console.log(this) var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name) }; } var friend = new Person("Nicho",29,"software"); friend.sayName();//Nicho
构造函数在不返回值的情况下,默认会返回新对象实例。可以通过构造函数末尾添加一个return语句,可以重写调用构造函数时返回的值
注意:当构造函数中有返回对象时候,最终new出来的对象会是构造函数的返回值,而不是new过程中生成的对象。仅当构造函数返回值是对象时有效,当不是对象时依旧返回new过程中形成的对象(无论如何new 构造函数之后都会返回一个对象值)。