《JavaScript高级程序设计第六章--面向对象》section_02
1. 原型与in操作符
有两种方式使用in操作符,单独使用和在for-in循环中。
单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中;
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person1.hasOwnProperty("name")); //true alert("name" in person1); //true alert(person2.name); //"Nicholas" ?from prototype alert(person2.hasOwnProperty("name")); //false alert("name" in person2); //true delete person1.name;//删除实例属性; alert(person1.name); //"Nicholas" - from the prototype alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true
在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性(包括实例中的属性和原型中的属性)。屏蔽了原型中不可枚举属性(即[[Enumerable]]标记为false的属性,默认值为false)的实例属性也会在for-in中循环返回.因为根据规定,所有开发人员定义的属性都是可枚举的(疑问:我实例中定义的属性,即使在原型中是不可枚举的属性,也能返回?是的)----(只有在ie8及更早版本例外)。
1 var o = { 2 toString : function(){ 3 return "My Object"; 4 } 5 } 6 for (var prop in o){ 7 f (prop == "toString"){ 8 alert("Found toString"); 9 } 10 } 11 //在IE8和之前版本不显示
上面实例,对象中定义的toString()方法,屏蔽了原型中(不可枚举)的toString()方法,所以会有提示框;
要取得对象上所有可枚举的实例属性,可以使用ES5的Object.keys()方法,接收一个对象作为参数,返回字符串数组(包含所有可枚举属性的名字)。
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; Person.prototype.toString= function(){ alert(this.age); }; var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName,toString" var p1=new Person(); p1.name="Rob"; p1.age=29; p1.aa="aa"; var p1keys=Object.keys(p1); alert(p1keys);//"name,age,aa" var newkey=Object.getOwnPropertyNames(Person.prototype); alert(newkey);//"constructor,name,age,job,sayName,toString"
如果想得到所有实例属性,无论是否可以枚举,ES5都使用Object.getOwnPropertyNames(),注意结果中包含了不可枚举的constructor. 构造函数内Person.prototype.toString =..也算定义属性。
2. 更简单的原型语法
就像用字面量的方式创建Person.prototype对象。区别在于constructor属性不再指向Person构造函数了。前面介绍过,每创建一个函数,就会同时创建他的prototype对象,这个对象也会自动获得constructor属性。然而这里的语法本质上完全重写了默认的prototype对象,因此constructor属性也就变成新对象的constructor。
(指向Object构造函数)
1 function Person(){ 2 } 3 Person.prototype = { 4 name : "Nicholas", 5 age : 29, 6 job: "Software Engineer", 7 sayName : function () { 8 alert(this.name); 9 } 10 }; 11 12 var friend = new Person(); 13 alert(friend instanceof Object); //true 14 alert(friend instanceof Person); //true 15 alert(friend.constructor == Person); //false 16 alert(friend.constructor == Object); //true
如果constructor的值很重要,可以特意将它设置回适当位置,即对象定义属性中constructor:Person;(注意:这种方式重设,会导致它的[[Enumerable]]特性被设置为true,而默认是不可枚举的。)所以,解决办法是,在支持ES5的浏览器中用Object.defineProperty(Person.prototype,"constructor",{ enumerable:false,value:Person});
3. 原型的动态性
我们对原型对象所做的任何修改都能够立即从实例上反映出来----即使是先创建了实例后修改原型也照样如此。其原因归结为实例与原型之间的松散连接关系。”依次搜索,指针连接"。
function Person(){ } var friend = new Person(); Person.prototype.sayHi = function(){ alert("hi"); }; friend.sayHi(); //"hi" ?works!
尽管可以随时为原型添加属性或方法啊,并在实例中立即反映出来。但如果重写整个原型对象,情况就不一样了。
例子1
function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; //先创建实例,然后又重写了原型链,构造函数的prototype就指向了新的原型对象,并且有自己新的属性; friend.sayName(); //出错
例子2
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; var friend = new Person(); friend.sayName(); //可以,并没有先创建实例
根据上面两个例子,调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,第一个例子,把原型修改为另一个对象就等于切断了构造函数与最初原型之间的联系?<原书是这么写>,(难道不是从图中看出切断了现有原型(重写的)与任何之前已经存在的对象实例之间的联系,引用仍是最初的原型?)待解决,
从图中看出切断了现有原型(重写的)与任何之前已经存在的对象实例之间的联系,引用仍是最初的原型
例子2中,是原型之后创建的对象,重写原型对象是可以的,没有切断联系,对吧?是的。很明显有输出啊。
4. 原生对象的原型
原型模式不仅在自定义类型上重要,连原生的引用类型,也采用这种模式。(Object、Array、String等等)都在其构造函数的原型上定义了方法,如Array.prototype中可以找到sort()方法。
更重要的是,通过原生对象的原型,不仅可以取得所有默认方法的引用,还可以定义新方法。(当然最好别这么做!)
5. 原型对象的问题
也不是没有缺点,首先省略了为构造函数传递初始化参数这一环节,结果所有实例默认情况都将取得相同的属性。最大问题是由于共享的本性所导致的。因为对于包含引用类型值的属性,有影响!
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
可是,实例一般都是要有属于自己的全部属性。所以很少看到有人单独使用原型模式的原因所在。解决办法,组合使用构造函数模式和原型模式。
6.2.4 组合使用构造函数模式和原型模式
所以创建自定义类型的最常见方式,就是组合使用。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。这样最大限度的节省了内存。还支持向构造函数床底参数。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor: Person, sayName : function () { alert(this.name); } }; var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
6.2.5 动态原型模式
就是把所有信息都封装在了构造函数中。主要是通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。
function Person(name, age, job){ //properties this.name = name; this.age = age; this.job = job; //methods if (typeof this.sayName != "function"){//如果这个方法不存在,就添加。只要有一个检查即可,就可以完成初始化。 Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
注意:使用动态原型模式时,不能使用对象字面量重写原型,前面已经解释过了,如果已经创建了实例的额情况下重写原型,那么就会切断现有实例和新原型之间的联系(看来上面红色背景的标记的话,确实出错了。)
还有其他模式,如寄生构造函数模式、稳妥构造函数模式。不过不常用!

浙公网安备 33010602011771号