javascript面向对象(二)--创建对象
1 /创建对象: 2 //最基本的 Object构造以及字面量法 3 //一、工程模式创建:使用参数传值在构造 4 function createObject(name,age,job){ 5 var o = new Object(); 6 o.name = name; 7 o.age = age; 8 o.job = job; 9 o.sayName = function(){ 10 alert(this.name); 11 }; 12 return o; 13 } 14 var person = createObject('zhd',23,'Software Engineer'); 15 //该方式无法确定 该对象是一个什么样的类型(都是有Object创建的) 16 17 //二、构造函数模式:改进工厂模式 18 function Person(name,age,job){ //约定俗成 首字母大写 19 var o = new Object(); 20 this.name = name; 21 this.age = age; 22 this.job = job; 23 this.sayName = function(){ 24 alert(this.name); 25 }; 26 //也可以像下面这种方式定义,这种方式定义会导致不同的作用域链和标识符解析,例: 27 //alert(person1.sayName == person2.sayName) //false 28 //但创建机制还是一样的 29 //this.sayName = new Function("alert(this.name"); //没有大括号 30 } 31 var person = new Person('zhd',23,'Software Engineer'); //构造方法 32 //每个对象都有一个 constructor指向构造函数,因此 33 alert(person.constructor == Person); // true 34 alert(person instanceof Person); //true 35 alert(person instanceof Object); //true 所有的对象均继承自Object类 36 37 //构造函数也是函数,所有用new出来的函数,都可以做构造函数来看待 38 var person = new Person('zhd',23,'Software Engineer'); 39 person.sayName(); // zhd 40 41 Person('qi',23,'Software Engineer'); //普通函数来看待 42 window.sayName(); // qi 只能是Global(浏览器时window)调用 43 44 var o = new Object(); 45 Person.call(o,'qi',23,'Software Engineer'); //普通函数来看待 46 o.sayName(); // qi 在 对象o中调用 47 48 //可以把sayName()对象转移到函数体外部,this指针的存在,可以保证在构造函数调用外面的sayName()时, 49 //两个对象不会产生混乱 50 function Person(name,age){ 51 this.name = name; 52 this.age = age; 53 this.sayName = sayName; 54 } 55 function sayName(){alert(this.name);} 56 57 //三、原型方法 58 //对于构造函数模式来讲,可以把对象中若干个函数移出构造函数体外部,成为全局变量。 59 //然而,这些函数并不是真正意义上的全局变量,大多数情况下仍然是声明出来的某个对象所调用 60 //况且,方法过多让大量的方法在作为全局变量游离在构造方法之外,破坏了封装型 61 //可以使用原型模式来解决 62 63 //创建的每个函数都有一个 prototype属性,这是一个指针,指向一个对象,这个对象的用途就是 64 //包含可以由特定类型的所有实例共享的属性和方法 65 //通过调用构造函数而创建那个对象实例的原型对象 66 //好处就是让所有对象实例共享它所包含的属性和方法 67 //也就是说 不必在构造函数里定义对象的信息,可以将信息直接添加到原型对象中去 68 function Person(){}; 69 Person.prototype.name = "zhd"; 70 Person.prototype.age = 23; 71 Person.prototype.sayName = function(){ 72 alert(this.name); 73 }; 74 75 var person1 = new Person(); 76 person1.sayName(); //zhd 77 var person2 = new Person(); 78 person1.sayName(); //zhd //所有实例共享属性和方法,与构造函数模式不同 79 80 alert(person1.sayName == person2.sayName); //true 没有(),这里比较的是两个指针 81 82 //理解原型对象 (这个对象可以理解为实实在在存在的一个对象,并且与这个函数息息相关) 83 //只有创建一个函数,就会根据一组特定的规则为这个函数创建一个prototype属性指向函数的原型对象。 84 //默认情况下,所有的原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性 85 //所在函数的指针。例如:Person.prototype.constructor指向Person。 86 //创建自定义构造函数后,其原型对象默认只会获取constructor属性。至于其他方法都是Object继承而来 87 //每创建一个新实例,该实例都会包含一个指针指向构造函数的原型对象。 88 //ECMA-262第五版称之为 [[prototype]] 89 90 //它们之间的关系就是: 91 //每个新实例会有一个指针指向构造函数的原型对象。 92 //给构造函数的原型对象赋值。 93 //也就是说每个新实例都会得到原型对象中属性的值。 94 //因此 alert(person1.sayName == person2.sayName)返回true 95 //所有的实例共享构造函数的原型对象中的属性和方法 96 97 //可以通过 isPrototypeOf() 方法来确定是否有这么一层关系 98 alert(person.prototype.isPrototypeOf(person1)); //true 99 100 //ECMAScript 5中的 Object.getPrototypeOf() 可以很方便的获取一个实例的原型对象 101 alert(Object.getPrototypeOf(person1) == Person.prototype); //true 102 alert(Object.getPrototypeOf(person1).name); //zhd 103 104 //程序在读取实例属性的时候,会先后搜索两次属性的名称 以 name为例 105 //第一次在实例person1中查找name 106 //如果未找到,就会在person1的原型对象中查找name 107 //因此可以直接在实例中,定义一个与源性对象中属性相同的名字,程序就会屏蔽原型对象中的值 108 //只是在这个实例中屏蔽,不会改变原型对象的值 109 var person3 = new Person(); 110 person3.name = 'zhdqi'; 111 alert(person3.name); //zhdqi 112 alert(person1.name); //zhd 不会改变 113 114 //delete person3.name; 115 //alert(person3.name); //zhd 删掉实例同名的属性后,就会继续访问原型对象中的值 116 117 //可以使用 hasOwnProperty(); 判断访问的对象属性来自实例还是来自原型对象 118 alert(person1.hasOwnProperty("name")); //false 来自原型 119 alert(person3.hasOwnProperty("name")); //true 来自实例对象 120 121 //in操作符:可以确定在实例中能否访问某个属性,无论属性是实例还是原型对象 122 alert("name" in person1); //true name来自原型对象 只要能访问到 就是true 123 alert("name" in person3); //true name来自实例本身 124 125 //同时使用hasOwnProperty和in能够确定访问的属性的值来自与原型对象还是实例本身 126 function hasPrototypeProperty(object,name){ 127 return !hasOwnProperty(name)&&(name in object); 128 } 129 alert(hasPrototypeProperty(person1,'name')); //true 能访问 且来自原型对象 130 alert(hasPrototypeProperty(person3,'name')); //true 能访问 来自实例本身 131 132 //for-in能够访问对象中可枚举的属性(不论属性来自实例还是原型对象) 133 //如果原型对象某属性屏蔽了可枚举性,但实例中重新定义了该属性,那么for-in也是可以访问的 134 //使用 Object.keys(),会返回一个对象中所有可枚举实例属性的字符串数组 注意:是实例属性 135 alert(Object.keys(person.prototype)); //'name','age','job' 136 alert(Object.keys(person3)); //'name' 实例对象的属性 137 138 //Object.getOwnPropertyNames(Person.pertotype) 返回所有实例的属性 139 alert(Object.getOwnPropertyNames(Person.pertotype)); //constructor,name,age,job,sayName 140 141 //更简单的语法 142 function Person(){} 143 Person.prototype = { 144 name:'zhd', 145 age:23, 146 job:'Software Engineer', 147 sayName:function(){alert(this.name);} 148 } 149 150 //!!!这样相当于重写了 prototype对象,因此 constructor指向新的constructor对象(Object) 151 //原型对象是一个已经存在的对象,因此可以在上面随意的增加删除属性与方法 152 //但是使用字面量法重新定义之后,就相当于重写该方法 153 //使用instanceof操作符还会有正确的结果,但是constructor无法确定对象的类型了 154 var friend = new Person(); 155 alert(friend instanceof Object); // true 156 alert(friend instanceof Person); // true 157 alert(friend.constuctor == Person); // false 158 alert(friend.constuctor == Object); // true 159 //假如constuctor值很重要 160 Person.prototype = { 161 //constuctor:Person, //可以直接指定 但这样会变成可枚举类型,而默认是不可枚举的 162 name:'zhd', 163 age:23, 164 job:'Software Engineer', 165 sayName:function(){alert(this.name);} 166 } 167 Object.defineProperty(person.prototype,"constructor",{ 168 enumerable:false, //重设constructor,使其变成不可枚举的值 169 value:Person 170 }); 171 172 //实例与构造函数之间是一种松散关系,可以在原型对象上任意定义与删除方法都可以反映在实例上 173 //pertotype作为实例与构造函数之间的桥梁 174 //一旦重写了pertotype,就相当于切断了这层关系 175 function Person(){} 176 var friend = new Person(); 177 Person.prototype = { //重写了方法 桥梁被切断 178 constuctor:Person, 179 name:'zhd', 180 age:23, 181 job:'Software Engineer', 182 sayName:function(){alert(this.name);} 183 } 184 alert(friend.sayName); // undefined 他引用的依旧是最原始的原型对象 185 186 //原生对象的原型:即 Object,Array,String等原生引用类型的方法也是可以在其原型对象上找到 187 //例如 可以在 Array.prototype找到sort()方法,也可以在String.prototype中找到substring方法 188 189 //通过原生对象的原型,可以在原生类型上随时添加新的方法 190 //例 在String添加一个 startsWith() 191 String.prototype.startsWith = function(text){ 192 return this.indexOf(text) == 0; 193 } 194 var msg = "hello world"; 195 alert(msg.startsWith('hello')); //true 196 197 //原型对象的局限性 198 //这种创建对象的方式是基于共享的,在原型对象中,每一个实例都可以重新定义(修改)原型中已有的属性 199 //且不会共享。但是,如果原型中带有引用类型的变量,例如,原型中有一个数组,那么在对数组进行操作而没有 200 //重新定义的时候,那么这个被操作的数组就会被所有实例所共享 201 function Person(){} 202 Person.prototype = { //重写了方法 桥梁被切断 203 constuctor:Person, 204 name:'zhd', 205 age:23, 206 likecolor:['red','green'], 207 job:'Software Engineer', 208 sayName:function(){alert(this.name);} 209 } 210 var person1 = new Person(); 211 var person2 = new Person(); 212 person1.likecolor.push('blue'); //这里会共享 213 alert(person1.likecolor); //red green blue 214 alert(person2.likecolor); //red green blue 215 alert(person1.likecolor == person2.likecolor); //true 216 217 //四、结合构造函数模式以及原型对象模式创建对象 最流行的一种方法 218 //构造函数模式用于 定义实例属性 219 //原型对象模式 定义实例方法以及共享的属性 220 //每个实例都有自己的一份属性的副本,且共享着对方法的引用 221 function Person(name,age,job){ 222 this.name = name, 223 this.age = age, 224 this.job = 'Software Engineer' 225 } 226 Person.pertotype = { 227 constuctor:Person, 228 sayName:function(){ 229 alert(this.name); 230 } 231 } 232 //五、动态原型模式 233 //所谓动态原型,就是追求一种封装的效果。将原型对象的方法放到了构造函数中,通过if判断的方式动态的加载原型对象的方法 234 //判断只需要判断其中一个方法(如果有若干个方法的话),作用就是判断构造函数是否已经加载了原型对象中的方法 235 //与原型模式一样,使用字面量法重写原型会切断实例与构造函数之间的桥梁 236 function Person(name,age,job){ 237 this.name = name; 238 this.age = age; 239 this.job = job; 240 241 //if(Person.pertotype.method == undefined) /这样写也可以 242 if(typeof this.sayName != "function"){ 243 //加载原型对象的函数到构造函数 244 Person.pertotype.sayName = function(){ 245 alert(this.name); 246 }; 247 } 248 } 249 //六、寄生构造函数模式 与工厂模式基本相同 250 function Person(name,age){ //函数名称不再是 createObject 251 var o = new object(); 252 o.name = name; 253 o.age = age; 254 o.sayName = function(){ 255 alert(this.name); 256 }; 257 return o; 258 } 259 var person1 = new Person('zhd',23); 260 person1.sayName(); // zhd 261 262 //在特殊场合下使用这种方式,但无法通过 instanceof 确定对象的类型,与工厂模式类似 263 //比如创造一个具有一个额外方法的特殊数组,由于不能修改Array函数,那么可以这么做: 264 function SpecialArray(){ 265 var array = new Array(); //先创建一个数组 266 array.push.apply(array,arguments); //调用apply方法接受SpecialArray的参数给array的push赋值,进而给数组赋值 267 array.toPipeString = function(){ 268 return this.join("|"); 269 }; 270 return array; //返回数组 271 } 272 var colors = new SpecialArray("red","green","blue"); // red,green,blue 273 colors.toPipeString(); //red|green|blue 274 275 //七、稳妥构造函数模式 :与寄生构造模式类型,但 1.新创建的实例方法不引用this 2.不能使用new调用构造函数 276 //使用于较为安全的环境(禁止使用this于new) 277 //由于没有this和new,构造函数中的数据成员无法被外部访问,只能被函数体内定义好的方法来访问 278 //在外部添加方法时,不能使用this指针,也无法在方法体内引用函数的数据成员变量 279 function Person(name,age){ 280 var o = new Object(); 281 //定义若干方法 282 o.name = name; 283 o.age = age; 284 285 o.sayName = function () { 286 alert(name); 287 }; 288 return o; 289 } 290 var friend = Person('zhd',23); 291 friend.sayName(); //zhd 292 //alert(friend.name); //浏览器会忽略这条语句