原型模式
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,通俗的讲,prototype就是通过构造函数创建的实例对象的原型对象,使用原型对象的好处是可以让所有的实例对象共享它的属性和方法
1 function Person(){ 2 Person.prototype.name = "Nicholas"; 3 Person.prototype.age = 29; 4 Person.prototype.job = "Software Engineer"; 5 Person.prototype.sayName = function(){ 6 console.log(this.name); 7 }; 8 }; 9 10 var person1 = new Person(); 11 person1.sayName(); //Nicholas 12 13 var person2 = new Person(); 14 person2.sayName(); //Nicholas 15 16 console.log(person1.sayName == person2.sayName); //true
1、理解原型对象
无论什么时候,只要创建了一个新的函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,原型对象有一个constructor属性,指向prototype属性所在的函数指针
isPrototypeOf()方法可以确定对象之间是否存在这种关系
1 console.log(Person.prototype.isPrototypeOf(person1)); //true
ECMAScript5新增加了一个方法Object.getPrototypeOf()
1 console.log(Object.getPrototypeOf(person1));//Object {name: "Nicholas", age: 29, job: "Software Engineer"}
每当代码读取某个对象的某个属性时,都会执行一次搜索。首先搜索从对象实例本身开始。如果在实例对象本身找到了具有给定名字的属性,则返回该属性的值,如果没有找到,在继续搜索指针指向的原型对象
虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例改写原型中的值,使用delete可以完全删除实例属性,从而让我们能够重新访问原型中的属性
1 function Person(){ 2 Person.prototype.name = "Nicholas"; 3 Person.prototype.age = 29; 4 Person.prototype.job = "Software Engineer"; 5 Person.prototype.sayName = function(){ 6 console.log(this.name); 7 }; 8 }; 9 10 var person1 = new Person(); 11 12 var person2 = new Person(); 13 14 person1.name = "Greg"; 15 console.log(person1.name); //Greg 16 console.log(person2.name); //Nicholas 17 18 delete person1.name; 19 console.log(person1.name); //Nicholas
hasOwnProperty()方法可以检测一个属性是否在实例中,还是在原型中,在实例中返回true
1 function Person(){ 2 Person.prototype.name = "Nicholas"; 3 Person.prototype.age = 29; 4 Person.prototype.job = "Software Engineer"; 5 Person.prototype.sayName = function(){ 6 console.log(this.name); 7 }; 8 }; 9 10 var person1 = new Person(); 11 var person2 = new Person(); 12 console.log(person1.hasOwnProperty("name")); //false 13 person2.name = "Greg"; 14 console.log(person2.hasOwnProperty("name")); //true
2、原型与in操作符
有两种方式使用in操作符,单独使用和在for-in中使用,单独使用时,in操作符在通过对象能够访问到指定属性时返回true,无论在实例中还是在原型中
1 function Person(){ 2 Person.prototype.name = "Nicholas"; 3 Person.prototype.age = 29; 4 Person.prototype.job = "Software Engineer"; 5 Person.prototype.sayName = function(){ 6 console.log(this.name); 7 }; 8 }; 9 10 var person1 = new Person(); 11 var person2 = new Person(); 12 console.log(person1.hasOwnProperty("name")); //false 13 console.log("name" in person1); //true 14 person1.name = "Greg"; 15 console.log(person1.hasOwnProperty("name")); //true 16 console.log("name" in person1); //true
同时使用hasOwnProperty()和in操作符,就可以确定该属性到底是存在于实例中还是存在于原型中
1 function hasPrototypeProperty(object,name){ 2 return (!object.hasOwnProperty(name) && (name in object)); 3 } 4 5 //hasPrototypeProperty()返回true则表示该属性在原型中 6 7 function Person(){ 8 Person.prototype.name = "Nicholas"; 9 Person.prototype.age = 29; 10 Person.prototype.job = "Software Engineer"; 11 Person.prototype.sayName = function(){ 12 console.log(this.name); 13 }; 14 }; 15 16 var person3 = new Person(); 17 console.log(hasPrototypeProperty(person3,"job")); //true 18 person3.job = "Teacher"; 19 console.log(hasPrototypeProperty(person3,"job")); //false
在使用for-in循环时,返回的是所有能够通过对象访问的,可枚举属性,其中包括在实例中的属性、原型中的属性、屏蔽了原型中不可枚举属性的实例属性。
要取得对象上所有可枚举的属性,可以使用ECMAScript5的Object.keys()方法,这个方法接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
1 function Person(){ 2 Person.prototype.name = "Nicholas"; 3 Person.prototype.age = 29; 4 Person.prototype.job = "Software Engineer"; 5 Person.prototype.sayName = function(){ 6 console.log(this.name); 7 }; 8 }; 9 10 // Object.defineProperties(Person.prototype,{ 11 // name:{ 12 // enumerable: true, 13 // }, 14 // age:{ 15 // enumerable: true, 16 // }, 17 // }); 18 19 var keys = Object.keys(Person.prototype); 20 console.log(keys); 21 var person1 = new Person(); 22 person1.name = "Greg"; 23 person1.age = 22; 24 var person1Keys = Object.keys(person1); 25 console.log(person1Keys);
如果想要得到所有的实例属性,无论它是否可枚举,都可以使用Object.getOwnPropertyNames()方法
1 var keys = Object.getOwnPropertyNames(Person.prototype); 2 console.log(keys); //["constructor", "name", "age", "job", "sayName"]
3、更简单的原型语法
是用一个包含所有属性和方法的对象字面量来重写整个原型对象
1 function Person(){ 2 3 } 4 5 Person.prototype = { 6 name : "Nicholas", 7 age : 29, 8 job : "SoftWare Engineer", 9 sayName : function(){ 10 console.log(this.name); 11 }, 12 };
这里使用的语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),此时尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了
1 var friend = new Person(); 2 3 console.log(friend instanceof Object); //true 4 console.log(friend instanceof Person); //true 5 console.log(friend.constructor == Person); //false 6 console.log(friend.constructor == Object); //true
可见此时的constructor属性等于Object,如果constructor的值很重要,可以将它设置为适当的值
1 function Person(){ 2 3 } 4 5 Person.prototype = { 6 constructor : Person, 7 name : "Nicholas", 8 age : 29, 9 job : "SoftWare Engineer", 10 sayName : function(){ 11 console.log(this.name); 12 }, 13 };
注意:以这种方式重设constructor属性会导致他的[[Enumerable]]特性被设置为true。默认情况下,原生的
constructor属性是不可枚举的
4、原型的动态性
1 var friend = new Person(); 2 Person.prototype.sayHi = function(){ 3 alert("hi"); 4 }; 5 friend.sayHi(); //hi
虽然friend实例是在添加新方法之前创建的,但是它可以访问这个方法,原因在于每当代码读取某个对象的某个属性时,都会执行一次搜索。首先搜索从对象实例本身开始。如果在实例对象本身找到了具有给定名字的属性,则返回该属性的值,如果没有找到,在继续搜索指针指向的原型对象
尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,如果是重新写整个原型对象,那么情况就不一样了
1 function Person(){ 2 } 3 4 var friend = new Person(); 5 6 Person.prototype = { 7 constructor : Person, 8 name : "Nicholas", 9 age : 29, 10 job : "SoftWare Engineer", 11 sayName : function(){ 12 console.log(this.name); 13 }, 14 }; 15 16 friend.sayName(); //error
5、原生对象模型
通过对原生对象的原型,不仅可以取得所有默认方法的引用,而且也可以定义新方法
1 String.prototype.ll = function(text){ 2 return text.length; 3 }; 4 var msg = "Hello"; 5 console.log(msg.ll("SoftWare")); //8
6、原型对象的问题
首先,它省略了为构造函数传递参数这一环节,结果所有的实例在默认情况下都将取得相同的属性值。原型模式最大的问题是由共享的本性所导致的,对于包含基本值的属性可以再实例中添加一个同名属性而对于包含引用类型的属性来说,问题就比较突出了
1 function Person(){ 2 } 3 4 Person.prototype = { 5 constructor : Person, 6 name : "Nicholas", 7 age : 29, 8 job : "SoftWare Engineer", 9 friend : ["Shelby","Court"], 10 sayName : function(){ 11 console.log(this.name); 12 }, 13 }; 14 15 var person1 = new Person(); 16 var person2 = new Person(); 17 18 person1.friend.push("Van"); 19 console.log(person1.friend); //["Shelby", "Court", "Van"] 20 console.log(person2.friend); //["Shelby", "Court", "Van"] 21 console.log(person1.friend ==person2.friend); //true
而这个问题正是我们看到很少有人单独使用原型模式的原因所在
组合使用构造函数模式和原型模式
1 function Person(name,age,job){ 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.friend = ["Shelby","Court"]; 6 } 7 8 Person.prototype = { 9 constructor : Person, 10 sayName : function(){ 11 console.log(this.name); 12 }, 13 }; 14 15 var person1 = new Person(); 16 var person2 = new Person(); 17 18 person1.friend.push("Van"); 19 console.log(person1.friend); //["Shelby", "Court", "Van"] 20 console.log(person2.friend); //["Shelby", "Court"] 21 console.log(person1.sayName === person2.sayName); //true
这是目前在ECMAScript中使用最广泛的一种创建自定义类型的方法,可以说,这是用来定义引用类型的一种默认模式
动态原型模式
把所有信息都封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点
1 function Person(name,age,job){ 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 if(typeof this.sayName != "function"){ //只有在初次调用构造函数时才会执行 6 Person.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 }; 10 }; 11 12 var friend = new Person("Nicholas",29,"Software Engineer"); 13 friend.sayName(); //Nicholas
寄生构造函数模式
1 function Person(name,age,job){ 2 var o = new Object(); 3 o.name = name; 4 o.age = age; 5 o.job = job; 6 o.sayName = function(){ 7 console.log(this.name); 8 }; 9 return o; 10 } 11 12 var friend = new Person("Nicholas",29,"Software Engineer"); 13 friend.sayName(); //Nicholas
这个模式在特殊情况下来为对象创建构造函数,假设我们想创建一个具有额外方法的特殊数组
1 function SpecialArray(){ 2 var values = new Array(); 3 values.push.apply(values,arguments); 4 values.toPipedString = function(){ 5 return this.join("|"); 6 }; 7 return values; 8 }; 9 10 var colors = new SpecialArray("red","blue","green"); 11 console.log(colors.toPipedString()); //red|blue|green
稳妥构造函数模式
所谓稳妥对象,值得是没有公共属性,而且其方法也不引用this对象
1 function Person(name,age,job){ 2 3 //创建要返回的对象 4 var o = new Object(); 5 6 //可以再这里定义私有变量和函数 7 8 //添加方法 9 o.sayName = function(){ 10 console.log(name); 11 }; 12 13 //返回对象 14 return o; 15 16 }; 17 18 var friend = Person("Nicholas",29,"Software Engineer"); 19 friend.sayName(); //Nicholas
这样,变量friend中保存着一个稳妥对象,而除了调用sayName()方法外,没有别的方式可以访问其数据成员。