建立对象方式:html
工厂模式:使用简单的函数建立对象,为对象添加属性和方法,而后返回对象;数组
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; return o; } var person1 = createPerson("Nicholas",20,"soft"); var person2 = createPerson("Greg",27,"IT"); //优势:可以无数次调用该函数,生成相同属性的对象。 // 缺点:但却没有解决对象识别的问题。
构造函数模式:安全
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Nicholas",20,"soft"); var person2 = new Person("Greg",27,"IT"); 在这个实例中,Person()函数取代了createPerson函数。咱们注意到,Person()中的代码除了与createPerson中相同的部分外,还存在如下不一样之处: 1 没有显示的建立对象 2 直接将属性和方法赋给了this对象 3 没有return语句 要建立Person的新实例,必须使用new 操做符。以这种方式调用构造函数实际上会经历如下4个步骤 (1) 建立一个新对象 (2) 将构造函数的做用域赋给新对象 (3) 执行构造函数中的代码 (4) 返回新对象 前面例子的最后,person1 、 person2 分别保存着Person的一个不一样的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person console.log(person1.constructor == Person); //true console.log(person2.constructor == Person); //true 最初标识对象类型的方式:constructor属性。可是,提到检测检测类型,仍是 instancof 操做符更靠谱些。 console.log(person1 instanceof Person); //true console.log(person2 instanceof Person); //true console.log(person1 instanceof Object); //true console.log(person2 instanceof Object); //true 建立自定义的构造函数意味着未来能够将它的实例标识为一种特定的类型;而这正是构造函数模式赛过工厂模式的地方。 在这个例子中,person1 和 person2 之因此同时是Object 的实例,是由于全部对象均继承自Object; 1 构造函数用法 将构造函数看成函数 构造函数与其它函数的区别,就是调用的方式不同。不过构造函数也是函数,也不存其定义方式不同。 任何函数经过new操做符来调用,那他就能够做为构造函数。若是任何函数不一样过new来调用,则和普通函数没有啥区别; <1> 看成为构造函数调用时 var person1 = new Person("Nicholas",20,"soft"); person1.sayName();// Nicholas <2> 看成为普通函数调用时 == window Person("Nicholas",20,"soft"); sayName();//Nicholas <3> 在另一个对象做用域中调用 var o = new Object(); Person.cell(o,"Nicholas",20,"soft"); o.sayName(); //Nicholas 2 构造函数问题 每一个函数在每一个实例上多建立了一遍; person1 与 person2 都有一个名为 sayName 的方法,但那两个方法不是相同的实例; function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)") } 在ECMScript中函数也是对象,所以定义一个函数也就是实例化了一个对象(Person.sayName); 从这个角度来看每一个实例对象是存在不一样的Function实例的本质。 建立两个彻底相同的Function实例确实没有必要;何况还有this,不要代码执行前就把函数绑定到特定的对象上面。所以大能够像这样 把函数转移到外部方式来解决这个问题; function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } 这样的方式来确实解决一个问题是,解决两个函数作同一件事情,可是当一个对象存在多个方法的时候,这对于自定的函数来讲丝毫没有封装可言。好在这些问题能够经过原型来解决。
原型模式:app
1 每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。 2 若是按照字面意思来理解,那么prototype就是经过调用构造函数而建立的那个对象实例的原型对象。 3 4 实例的原型对象, 5 原型对象的好处是可让全部对象实例共享它所包含的属性和方法。换句话说,没必要在构造函数中定义对象实例的信息,而是能够直接将这些信息直接添加到原型对象中,以下 6 7 function Person(){} 8 9 Person.prototype.name = "Nicholas"; 10 Person.prototype.age = 29; 11 Person.prototype.job = "Software Engineer"; 12 Person.prototype.sayName = function(){ 13 alert(this.name); 14 } 15 16 var person1 = new Person(); 17 person1.sayName(); //Nicholas 18 19 var person2 = new Person(); 20 person2.sayName(); //Nicholas 21 22 alert(person1.sayName == person2.sayName); //true 23 24 1 理解原型对象 25 不管何时,只要建立一个新函数,就会根据一组特定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。 26 在默认状况下,全部原型对象都会自动得到一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。 27 就拿前面来讲 Person.prototype.constructor 指向 person。而经过这个构造函数,咱们还能够继续为原型对象。 28 而经过这个构造函数,咱们还可继续为原型对象添加其余属性和方法。 29 30 建立自定义的构造函数以后,其原型对象默认只会取得constructor属性;至于其它方法,则都是从Object 继承而来的。 31 32 当调用构造函数建立一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMA-262第5版中管这个指针叫[[Prototype]]. 33 34 虽然脚本中没有标准的 方式访问,但 Firefox\ Safari\ Chrome 在每一个对象上都支持一个属性__proto__ 而在其它实现中,这个属性对象脚本则是彻底不可见的。 35 36 不过,要明确的 [[Prototype]] 链接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。 37 38 虽然在全部实现过程当中没法访问到[[Potottype]],但能够经过isPrototypeOf()方法来肯定对象与原型之间的关系。 39 从本质讲,若是[[Potottype]]指向调用isPrototypeOf()方法的对象(Person.property),那么这个方法就返回true 以下 40 console.log(Person.prototype.isPrototypeOf(person1)); //true 41 42 ECMScript5增长一个新方法 43 /* 44 @param1 对象 45 @return 对象关联的原型对象 46 IE9+ Firefox3.5+,Safari5+,Opera 12+ 和 Chrome 47 */ 48 Object.getPrototypeOf() 49 50 每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。 51 1 搜索首先从对象实例开始 找到则返回 否 继续 52 2 从指针指向的原型对象中搜索 53 54 注意:虽然能够经过对象实例访问保存在原型中的值,但却不能经过对象实例重写原型中的值。 若是在实例中添加一个属性,而该属性与实例原型中的一个属性同名, 55 那咱们就在实例中建立该属性,该属性将会屏蔽原型中的那个属性。 56 57 function Person(){} 58 59 Person.prototype.name = "Nicholas"; 60 Person.prototype.age = 29; 61 Person.prototype.job = "Software Engineer"; 62 Person.prototype.sayName = function(){ 63 alert(this.name); 64 } 65 66 var person1 = new Person(); 67 var person2 = new Person(); 68 person1.name = "Greg"; 69 console.log(person1.name); //Greg --- 来自实例 70 console.log(person2.name); //Nicholas --- 来自原型 71 72 该实例说明一个问题:实例中添加与原型中声明同名的变量,只会阻止其访问其原型。并不会修改那个属性。 即便将这个属性设置为null,也只会在实例中设置这个属性,而不会回复其指向原型的链接。 73 不过delete 操做符则能够彻底删除实例属性,从而让咱们可以从新访问原型中的属性, 74 75 function Person(){ 76 } 77 Person.prototype.name = "Nicholas"; 78 Person.prototype.age = 29; 79 Person.prototype.job = "Software Engineer"; 80 Person.prototype.sayName = function(){ 81 alert(this.name); 82 } 83 84 var person1 = new Person(); 85 person1.name = "Greg"; 86 delete person1.name; 87 console.log(person1.name); //Nicholas --- 来自原型 88 89 注意:判断一个属性是存在于实例中,仍是存在于原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。来看下面这个例子 90 91 function Person(){} 92 Person.prototype.name = "Nicholas"; 93 Person.prototype.age = 29; 94 Person.prototype.job = "Software Engineer"; 95 Person.prototype.sayName = function(){ 96 alert(this.name); 97 } 98 99 var person1 = new Person(); 100 var person2 = new Person(); 101 person1.hasOwnProperty("name"); //false 102 103 person1.name = "WJ"; 104 person1.hasOwnProperty("name"); //true 105 106 2 原型与in 操做符 107 108 function Person(){} 109 110 Person.prototype.name = "Nicholas"; 111 Person.prototype.age = 29; 112 Person.prototype.job = "Software Engineer"; 113 Person.prototype.sayName = function(){ 114 alert(this.name); 115 } 116 117 var person1 = new Person(); 118 var person2 = new Person(); 119 console.log(person1.hasOwnProperty("name")); //false 120 console.log("name" in person1) // true 121 122 person1.name = "Greg"; 123 console.log(person1.name); // 来自实例 124 console.log(person1.hasOwnProperty("name")); //true 125 console.log("name" in person1); //true 126 127 delete person1.name 128 console.log(person1.name); // Nicholas 来自原型 129 console.log(person1.hasOwnProperty("name")); // false 130 console.log("name" in person1) // true 131 132 133 从上面能够看出要么是从对象,要么是从原型中访问到的。所以,调用“name” in person1 始终都返回true,不管该属性存在于实例中仍是存在于原型中。 134 同时使用 hasOwnProperty()方法和in操做符,就能够肯定该属性到底存在于对象中,仍是存在于原型中,以下所示 135 136 function hasPrototypeProperty(object,name){ 137 return !object.hasOwnProperty(name) && (name in object); 138 } 139 function Person(){} 140 Person.prototype.name = "Nicholas"; 141 Person.prototype.age = 29; 142 Person.prototype.job = "Software Engineer"; 143 Person.prototype.sayName = function(){ 144 alert(this.name); 145 } 146 147 var person1 = new Person(); 148 hasPrototypeProperty(person1,"name"); // true 原型 149 person1.name = "Greg"; // 实例 150 hasPrototypeProperty(person1,"name"); // false 151 152 该属性显示存在于原型中时 hasPrototypeProperty 返回true 153 当第二次的时返回false 由于实例中存在该同名属性时,就不要原型中的同名属性 154 155 156 注意:在使用for-in循环时,返回的是全部可以经过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举的属性(即将[[Enumerable]]) 157 标记的属性)的实例实例属性也会在for-in循环中返回,由于根据规定,全部开发人员定义的属性是可枚举的----只有在IE8及更好版本中例外。 158 159 IE早起版本的实现中存在一个bug,既屏蔽不可枚举属性的实例属性不会出如今for-in循环中 160 161 例 162 var o = { 163 toString:function(){ 164 return "My Object"; 165 } 166 } 167 168 for(var prop in o){ 169 if(prop == "toString"){ 170 alert("Found toString"); //在IE中不会显示 171 } 172 }; 173 174 175 要得到对象上的可枚举属性,能够利用 ECMScript5 增长一个新方法 Object.keys(); 176 177 /* 178 @param object 179 */ 180 Object.keys(); 181 182 function Person(){ 183 } 184 185 Person.prototype.name = "Nicholas"; 186 Person.prototype.age = 29; 187 Person.prototype.job = "Software"; 188 Person.prototype.sayName = function(){ 189 alert(this.name); 190 }; 191 var keys = Object.keys(Person.prototype); 192 alert(keys); //"name,age,job,sayName" 193 194 var p1 = new Person(); 195 p1.name = "Rob"; 196 p1.age = 31; 197 var p1keys = Object.keys(p1); 198 alert(p1keys); // name age 199 200 201 若是想得到全部实例属性,不管它是否可枚举,均可以使用Object.getOwnPropertyNames()方法。 202 var keys = Object.getOwnPropertyNames(Person.prototype); 203 alert(keys); // constructor name age job sayName 204 205 keys()\getOwnPropertyNames Export: IE9+ Firefox4+ Safari5+ Opera12+ Chrome 206 207 3 更简单的原型语法 208 209 读者大概注意到了,前面例子中每添加一个属性和方法就要敲一遍 Person.prototype. 为减小没必要要的输入,也为了从视觉上更好的封装原型的功能, 210 更常见的作法是用一个包含全部属性和方法的对象自面量来重写整个原型对象,以下 211 212 function Person(){} 213 Person.prototype = { 214 name:"Nicholas", 215 age:29, 216 job:"Safari5", 217 sayName:function(){ 218 alert(this.name); 219 } 220 }; 221 222 在上面的代码中,咱们将Person.prototype 设置为等于一个以对象自面量形式建立的新对象。最终结果相同,但有一个例外:constructor 属性再也不指向Person了。在这里本质上彻底重写了默认的prototype对象, 224 所以constructor属性也就变成了新对象的constructor属性(指向Object构造函数),再也不指向Person函数。 225 此时, instanceof 操做符还能返回正确结果,但经过constructor已经没法肯定对象的类型了。 226 227 var friend = new Person(); 228 229 alert(friend instanceof Object); //true 230 alert(friend instanceof Person); //true 231 232 alert(friend.constructor == Object); //true 233 alert(friend.constructor == Person); //false 234 235 在此,用 instanceof 操做符测试 Object 和 Person 任然返回 true, 但 constructor 属性则等于 Object 而不等于Person 了。若是constructor的值真的很重要,能够像 236 下面这样特地将它设置回适当的值。 237 function Person(){} 238 Person.prototype = { 239 constructor:Person, 240 name:"Nicholas", 241 age:29, 242 job:"Safari5", 243 sayName:function(){ 244 alert(this.name); 245 } 246 }; 247 248 以上代码特地包含了一个constructor属性,并将它的值设置为Person,从而确保了经过该属性可以访问到适当的值。 249 可是这种方式设置constructor属性的[Enumerable]特性被设置为true。默认状况下,原生的constructor 属性是不可枚举的,所以若是你使用兼容ECMAScript5 的JavaScript引擎,能够试一试 250 Object.defineProperty().或 Object.defineProperties() 更改属性基本信息.
优势是属性函数共用;缺点:<1> 省略了构造函数的传参,生成全部实例在默认状况下都取得相同的属性值。<2> 最大问题仍是其共用本质引发来的函数
<1> 就是其共享本质问题 原型中全部属性是本不少实例共享的,这种共享对于函数很是合适。对于包含引用类型值得属性来讲,问题就比较突出了。 function Person(){ } Person.prototype = { constructor:Person, name:"Nicholas", age:29, job:"Software", friends:["Shelby","Court"], sayName:function(){ alert(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); alert(person2.friends); alert(person1.friends === person2.friends); 从上面的一个实例看出来,person1与perosn2共用一个字符串数组,其中一方做修改都会反映出来。假如咱们的初衷是两个对象实例共享一个数组 的话,那是没有问题;但是,实例通常是要由属于本身的所有属性,而这个问题正是咱们不多有人会单独使用原型;
组合使用构造函数模式和原型模式性能
1 组合使用构造函数和原型模式 2 3 1 构造函数定义实例属性 4 2 原型定义方法和共享的属性 5 每一个实例都有本身的实例属性的副本,但同时又共享着对象的引用,最大限度地节省了内存。 另外这种混成模式还支持想构造函数传递参数;可谓是集两种模式之长。 6 7 function Person(name,age,job){ 8 this.name = name; 9 this.age = age; 10 this.job = job; 11 this.friends = ["Shelby",Court]; 12 } 13 14 Person.prototype ={ 15 constructor:Person, 16 sayName:function(){ 17 alert(this.name); 18 } 19 } 20 var perosn1 = new Person("Nicholas",29,"Software"); 21 var perosn1 = new Person("Greg",27,"Software"); 22 23 person1.friends.push("Van"); 24 alert(person1.friends); //"Shelby,Count,Van" 25 alert(person2.friends); //"Shelby,Count" 26 alert(person1.friends === person2.friends); // false 27 alert(person1..sayName === person2.sayName); // true 28 29 这种方式,使用最多,最广,认同度最高的一种建立自定义类型的方式。
动态原型模式测试
1 对于OO语言经验的人来讲,因为构造函数、原型相互独立的时候,会感到很是的困惑。动态原型模式正是致力于解决这个问题的一个方案,它把全部信息封装在一个构造 2 函数中,而经过在构造函数中初始化原型(仅在必要的状况下),又保持了同时使用构造函数和原型的有点。换句话说,能够经过检查某个应该存在的方法是否有效,来决定是否 3 须要初始化原型。 4 5 function Person(){ 6 if(typeof this.sayName !="function"){ 7 Person.prototype.sayName = function(){ 8 alert(this.name); 9 } 10 } 11 } 12 13 var friend = new Person(); 14 friend.sayName(); 15 16 这种方式主要是:只有当sayName方法不存在的状况下,才会将它添加到原型中。
寄生构造函数模式this
1 一般是前面几种方式不适合的状况下,可使用这种方式。 2 基本思想: 3 建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象; 4 5 function Person(name,age,job){ 6 var o = new Object(); 7 o.name = name; 8 o.age = age; 9 o.job = job; 10 o.sayName = function(){ 11 alert(this.name); 12 } 13 return o; 14 } 15 var friend = new Person("Nicholas",29,"Software"); 16 friend.sayName(); //"Nicholas" 17 18 在这个实例中,Person 建立了一个新对象,并以相应的属性和方法初始化该对象,而后又返回这个对象。除了使用new操做符并把使用包装函数叫作构造函数以外,这个模式跟工厂模式实际上是如出一辙的。 19 构造函数在不返回值的状况下,默认会返回新对象实例。而经过在构造函数的未添加一个 return语句,能够重写构造的返回值。 20 21 例如使用场景:当但愿建立一个具备额外方法的特殊数组。因为不能直接修改Array构造函数,所以可使用这个模式。 22 23 function SpercialArray(){ 24 //建立数组 25 var values = new Array(); 26 values.push.apply(values,arguments); 27 28 values.toPipedString = function(){ 29 return this.join("|"); 30 } 31 return values; 32 } 33 var colors = new SpercialArray("red","blue","green"); 34 alert(colors.toPipedString()); //"red|blue|green" 35 关于寄生构造函数模式,有一点须要说明:首先返回的对象与构造函数或者与构造函数的原型属性之间没有关系; 36 也就是不能经过 instanceof 操做符来肯定对象类型。 若是可使用其它模式不推荐使用。
稳妥结构函数模式spa
所谓稳妥对象,指的是没有公共属性,并且其方法也不引用this的对象;使用场合在一些安全的环境中,或者在防止数据被其余应用程序改动时使用。稳妥构造函数遵循与寄生构造函数相似的模式,但有两点不一样:一:建立的新对象的实例不引用this;二:不是用new操做符调用构造函数。上Person构造函数改写以下prototype
1 function Person(name,age,job){ 2 var o = new Object(); 3 4 o.sayName = function(){ 5 alert(name); 6 } 7 return o; 8 }
注意:在以这种模式建立的对象中,除了使用sayName()方法以外,没有其余办法访问name的值。能够向下面使用的Person构造函数。
var friend = Person("Nicholas",29,"Software Engineer");
friend.sayName(); // "Nicholas"
这样变量friend中保存的是一个稳妥对象,而除了调用sayName()方法外,没有别的方式访问其余数据成员。即便有其余代码给这个对象添加方法或者数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。
稳妥构造函数模式提供安全性,使得它很是适合在某些安全执行环境中
继承模式
原型链
1 2 实现原型链有一种基本模式,其代码大体以下 3 4 function SuperType(){ 6 this.property = true; 7 } 8 9 SuperType.prototype.getSuperValue = function(){ 10 return this.prototype; 11 } 12 function SubType(){ 13 this.subproperty = false; 14 } 15 16 //继承了SuperType 17 18 SubType.prototype = new SuperType(); 19 20 SubType.prototype.getSubValue = function(){ 21 return this.subproperty; 22 } 23 24 var instance = new SubType(); 25 alert(instance.getSuperValue()); 26 27 28 29 30 function SuperType(){ 31 this.property = true; 32 } 33 34 SuperType.prototype.getSuperValue = function(){ 35 return this.property; 36 } 37 38 function SubType(){ 39 this.subproperty = false; 40 } 41 SubType.prototype = new SuperType(); 42 43 SubType.property = { 44 getSubValue:function(){ 45 return this.subproperty; 46 }, 47 someOtherMethod:function(){ 48 return false; 49 } 50 } 51 52 var instance = new SubType(); 53 alert(instance.getSuperValue()); 54 55 56 既在经过原型进行继承时,不能使用字面量的方法建立原型内容,由于这样会重写原型链 57 58 原型中存在的最大问题是: 59 1 引用类型值得共用问题 60 61 2 用原型实现的继承,因为属性共用,在建立子类型实例时,不能向超类型的构造函数中传递参数。
借用构造函数
借用构造函数 call() apply()
在解决原型中包含引用类型值所带来问题的过程当中 可使用一种叫借用构造函数的技术(有时候也叫作伪造对象或经典继承)。这种技术的基本思想至关简单,既在子类型构造函数的内部调用超类型构造函数。别忘了,函数 只不过是在特定环境中执行代码的对象,所以经过使用apply()和 call()方法也能够在新建立的对象上执行构造函数, function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this); } var instance = new SubType(); instance.colors.push("black"); alert(instance.colors); //red blue green black var instance1 = new SubType(); alert(instance1.colors); // red blue green 借用构造函数相比原型模式的优势: 传递参数 例如: function SuperType(name){ this.name = name; } function SubType(){ SuperType.call(this,"Nicholas"); this.age = 20; } var instance = new SubType(); alert(instance.name); // Nicholas alert(instance.age); //20 借用构造函数的问题:
若是仅仅是借用构造函数,那么也将没法避免构造函数模式存在的问题------ 方法都在构造函数中定义,所以函数复用就无从谈起了。
组合模式继承: 原型模式和借构造函数两种模式的组合,取它们两的有点, 原型来定义实例的共用属性和方法,构造函数来定义实例属性
组合继承(combination inheritance) 原型与借用构造函数两种方法取其长的一种组合方式 主要思想: 利用原型对属性和方法的继承 而经过借用构造函数来实现对实例属性的继承。 这样既然继承原型中属性,又能保证明例拥有本身的属性。 function SuperType(name){ this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ alert(this.name); } function SubType(name,age){ //继承 SuperType.call(this,name); this.age = age; } //继承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); } var instance1 = new SubType("Nicholas",20); instance1.colors.push("black"); alert(instance1.colors); instance1.sayName(); instance1.sayAge(); var instance2 = new SubType("Greg",27); alert(instance2.colors); instance2.sayName(); instance2.sayAge
原型式继承
寄生式继承
寄生组合式继承
无论是那种模式,最终都是为了建立对象。