在使用for-in循环时返回的是所有能够通过对象访问的、可枚举的属性,包括实例中的属性和原型中的属性。屏蔽了原型中不可枚举数据(即[[Enumerable]]标记为false的属性)的实例属性也会在for-in中返回,因为根据规定,开发人员定义的属性都是可枚举的。
要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。
function Person() { } Person.prototype.name = "xxyh"; Person.prototype.age = "20"; Person.prototype.job = "programmer"; Person.prototype.sayName = function () { alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys); // name, age, job, sayName var p1 = new Person(); p1.name = "oooo"; p1.age = 15; var p1_keys = Object.keys(p1); alert(p1_keys); // name, age
如果需要得到所有实例属性,可以使用Object.getOwnPropertyNames()方法
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); // "constructor,name,age,job,sayName"
3.3更简单的原型语法
为了精简输入,用一个包含所有属性和方法的对象字面量来重写整合原型对象。
function Person() { } Person.prototype = { name : "xxyh", age : 18, job : "programmer", sayName : function () { alert(this.name); } };
上面将Person.prototype设置为等于一个以对象字面量形式创建的新对象。结果相同,但是constructor属性不在指向Person了。
通过instanceof能返回正确结果,但是constructor无法确定对象的类型:
var boy = new Person(); alert(boy instanceof Object); // true alert(boy instanceof Person); // true alert(boy.constructor == Person); // false alert(boy.constructor == Object); // true
可以通过下面的方式设置constructor的值:
function Person() { } Person.prototype = { constructor : Person, name : "xxyh", age : 18, job : "programmer", sayName : function () { alert(this.name); } };
3.4原型链的动态性
由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何修改都会反映到实例上。但是如果重写整个原型对象,结果就不同了。调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另一个对象就等于切断了构造函数与最初原型的联系。实例中的指针仅指向原型,而不指向构造函数。
function Person() { } var boy = new Person(); Person.prototype = { constructor : Person, name : "xxyh", age : 29, job : "programmer", sayName : function () { alert(this.name); } }; boy.sayName(); // 错误
具体过程如下:
从上面可以看出,重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的是最初的原型。
3.5原生对象的原型
所有原生引用类型都是在构造函数的原型上定义了方法。通过原生对象的原型,不仅可以取得默认方法,而且可以定义新方法。
String.prototype.startsWith = function (text) { return this.indexOf(text) == 0; }; var msg = "good morning"; alert(msg.startsWith("good")); // true
3.6原型对象的问题
原型模式存在两个问题:
•在默认情况下都取得相同的属性值。
•原型中的所有属性是实例共享的
下面看一个例子:
function Person() { } Person.prototype = { constructor: Person, name: "xxyh", age : 18, job : "programmer", friends:["张三", "李四"], sayName: function () { alert(this.name); } }; var p1 = new Person(); var p2 = new Person(); p1.friends.push("王五"); alert(p1.friends); // 张三,李四,王五 alert(p2.friends); // 张三,李四,王五 alert(p1.friends == p2.friends); // true
上面通过p1.friends添加了一项,由于friends数组存在于Person.prototype中,所以在p2.friends也反映出来了。可是,实例一般都是要有属于自己的全部属性的。
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。这样,每个实例都会有自己的一份实例属性的副本,但是同时又共享着对方法的引用。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["张三", "李四"]; } Person.prototype = { constructor: Person, sayName: function () { alert(this.name); } } var p1 = new Person("萧萧弈寒", 18, "programmer"); var p2 = new Person("魁拔", 10, "捉妖"); p1.friends.push("王五"); alert(p1.friends); // 张三,李四,王五 alert(p2.friends); // 张三,李四 alert(p1.friends == p2.friends); // false alert(p1.sayName == p2.sayName); // true
上例中,实例属性都是在构造函数中定义的,共享属性constructor和方法sayName()则是在原型中定义的。p1.friends的修改并不会影响到p2.friends的结果。
动态原型模式