JavaScript --------------继前面继承方式-------总结
创建对象方式:
工厂模式:使用简单的函数创建对象,为对象添加属性和方法,然后返回对象;
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); } 这样的方式来确实解决一个问题是,解决两个函数做同一件事情,但是当一个对象存在多个方法的时候,这对于自定的函数来说丝毫没有封装可言。好在这些问题可以通过原型来解决。
原型模式:
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方法不存在的情况下,才会将它添加到原型中。
寄生构造函数模式
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 操作符来确定对象类型。 如果可以使用其它模式不推荐使用。
稳妥结构函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象;使用场合在一些安全的环境中,或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一:创建的新对象的实例不引用this;二:不是用new操作符调用构造函数。上Person构造函数改写如下
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
原型式继承
寄生式继承
寄生组合式继承
不管是那种模式,最终都是为了创建对象。