function Person(name, age, job) { this.name = name; this.age = age; this.job = job; } Person.prototype={ sayName:function(){ return this.name; } } var p=new Person("李明",30,"诗人"); for(var prop in p){ console.log(prop);//name、age、job、sayName } console.log(Object.keys(p));//["name", "age", "job"] console.log(Object.keys(Person.prototype));//["sayName"] console.log(Object.getOwnPropertyNames(Person.prototype)) // ["constructor", "sayName"]
Object.keys():取得实例对象上所有可枚举的属性。 Object.getOwnPropertyNames(): 获取实例对象所有属性,无论它是否可枚举。
注意:使用对象字面量来重写整个原型对象时,本质上完全重写了默认的 prototype 对象,因此 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),不再指向 Person。但是可以通过在重写原型对象时指定constructor属性,使之还是指向原来的constructor。此时,尽管 instanceof 操作符还能返回正确的结果,但通过 constructor 已经无法确定对象的类型了。
object instanceof constructor:检测 constructor.prototype 是否存在于参数 object 的原型链上。
function Person() {} var friend2 = new Person(); Person.prototype = { //constructor : Person, name: "Nicholas", age: 29, job: "Software Engineer", sayName: function() { alert(this.name); } }; var friend = new Person(); console.log(friend2 instanceof Object); //true console.log(friend2 instanceof Person); //false, console.log(friend2.constructor == Person); //true console.log(friend2.constructor == Object); //false console.log(friend instanceof Object); //true console.log(friend instanceof Person); //true console.log(friend.constructor == Person); //false console.log(friend.constructor == Object); //true
由于原型的动态性,调用构造函数时会为实例添加一个指向最初原型的Prototype指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。看下面的例子
function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; var friend2=new Person(); friend.sayName(); //Uncaught TypeError: friend.sayName is not a function friend2.sayName();//Nicholas console.log(friend instanceof Person);//false console.log(friend instanceof Object);//true console.log(friend2 instanceof Person);//true
结果分析:这是因为friend1的prototype指向的是没重写Person.prototype之前的Person.prototype,也就是构造函数最初的原型对象。而friend2的prototype指向的是重写Person.prototype后的Person.prototype。如下图所示
6、 原型链
基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。最直观的表现就是让原型对象等于另一个类型的实例。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //继承了 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
SubType.prototype = new SuperType();这句代码使得原来存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype 中。使得instance的constructor指向了SuperType。
console.log(instance.constructor===SuperType);//true
总结: 访问一个实例属性时,首先会在实例中搜索该属性。如果没有找到该属性,则会继续搜索实例的原型。在通过原型链实现继承的情况下,搜索过程就得以沿着原型链继续向上。在找不到属性或方法的情况下,搜索过程总是要一环一环地前行到原型链末端才会停下来。
就拿上面的例子来说,调用 instance.getSuperValue()会经历4个搜索步骤:
搜索instance实例;
搜索 SubType.prototype;
搜索SuperType的实例;
搜索 SuperType.prototype,最后一步才会找到该方法。