我们创建的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
原型对象实际就是一个构造函数的实例对象,与普通的实例对象没有什么本质上的区分,js中每一个对象都有一个原型对象。不过他比较特殊,该对象所包含的所有属性和方法能够供构造函数的所有实例共享,这就是其他语言所说的继承,而js通过原型对象来实现继承,简称原型集成。静态原型继承:Object.prototype.[method field]
首先,考虑以下代码用构造函数的方式来创建实例对象:
//构造函数方式
function Person(name , age){
this.name = name ;
this.age = age ;
this.sayName = function(){alert('我是姓名!')};
}
var p1 = new Person('z3',20);
var p2 = new Person('z4',21);
p1.sayName();
p2.sayName();
//alert(p1.sayName == p2.sayName); // false
sayName 是p1和p2都拥有的函数,但是却被创建了两次,属于两个完全不相同的函数。如果通过这种方式创建很多的对象,毫无疑问性能会大大降低。因此我们可以将该函数抽取出来让其只会编译一次,如下:
function Person(name , age){
this.name = name ;
this.age = age ;
//this.sayName = function(){alert('我是姓名!')};
this.sayName = sayName ;
}
function sayName(){
alert(this.name);
}
//alert(p1.sayName == p2.sayName); true
现在基本上满足了我们以上的需求,函数只会在多个对象之间创建一次,但是这个函数是全局的,可能会被其他对象引用到,这也不是我们的初衷,因此我们可以引入原型对象
prototype 创建每一个函数都有一个prototype属性,这个属性其实是一个指针,而这个指针总指向一个对象,这个对象的用途就是将特定的属性和方法包含在内,起到一个所有实例所共享的作用
function Person(){
}
var obj = Person.prototype;
//alert(typeof obj); //object (obj实际上就是Person构造函数)
obj.name = 'z3';
obj.age = 20 ;
obj.sayName = function(){alert(this.name);};
var p1 = new Person();
var p2 = new Person();
//alert(p1.age); //20
//alert(p2.age); //20
//p1.sayName(); //z3
//p2.sayName(); //z3
//alert(p1.sayName == p2.sayName) //true
//因此,我们做到了共享原型对象中定义的属性和方法
构造函数 原型对象 实例对象之间的关系:
//构造函数.prototype = 原型对象
//原型对象.constructor = 构造函数
//alert(obj.constructor);
//原型对象.isPrototypeOf(实例对象)
//alert(obj.isPrototypeOf(p1));
原型中一些重要的方法
function Person(){
}
Person.prototype.name = 'z3';
Person.prototype.age = 20 ;
Person.prototype.sayName = function(){alert('我是原型对象的方法!')};
//ECMA5: Object.getPrototypeOf():根据实例对象获得原型对象
//isPrototypeOf(new instance); 判断原型的方法
var p1 = new Person();
alert(p1.name); // z3
var prototypeObj = Object.getPrototypeOf(p1);
alert(prototypeObj == Person.prototype); //true
alert(Person.prototype.isPrototypeOf(p1)); //true
//每次代码读取一个对象的属性的时候: 首先会进行一次搜索:搜索实例对象里name的属性,看看有没有如果没有,再去p2的实例所对应的原型对象里去搜索name属性 如果有就返回 没有返回undefined
var p2 = new Person();
p2.name = 'w5'; // 实例对象的name
delete p2.name ;
alert(p2.name); // 先删除实例中的属性,然后获得原型对象的name属性
//判断一个对象属性 是属于原型属性 还是属于实例属性
//hasOwnProperty只判断对象的属性是否属于实例属性(不包括原型)
var p3 = new Person();
p3.name = 'z6';
alert(p3.hasOwnProperty('name')); //true
delete p3.name;
alert(p3.hasOwnProperty('name')); //false
//for in操作可以遍历出实例对象和原型对象的所有属性 因此可以使用in来判断
for(var index in p1){
alert(index); // name age sayName
}
var p1 = new Person();
alert('name' in p1); // true
var p2 = new Person();
p2.name = 'w3';
alert('name' in p2); // true
//因此可以综合判断出属性是否属于原型中
function hasPrototypeProperty(object , name){
return !object.hasOwnProperty(name) && name in object ;
}
var p3 = new Person();
p3.name = 'xiao A';
alert(hasPrototypeProperty(p3,'name')); //false
// ECMA5新特性 Object.keys();拿到当前对象里的所有keys 返回一个数组
var p1 = new Person();
p1.name = 'z3';
p1.age = 20 ;
var attributes = Object.keys(p1);
alert(attributes); //name,age
var attributes2 = Object.keys(Person.prototype);
alert(attributes2); //name,age,sayName
//ECMA5 constructor属性: 该属性是不能被枚举的[eable = false];Object.getOwnPropertyNames 枚举对象所有的属性 :不管该内部属性能否被枚举(不会包括原型对象)
var attributes3 = Object.getOwnPropertyNames(Person.prototype);
alert(attributes3); //constructor,name,age,sayName
var p1 = new Person();
alert(Object.getOwnPropertyNames(p1)); //空
//ECMA5中的Object.defineProperty()方法可以为原型对象重新加入构造器
function Person(){
}
Person.prototype = {
//constructor : Person , //必须得表示原型对象的构造器
name: 'z3' ,
age : 20 ,
job : '程序员' ,
say : function(){
alert('我是原型的函数!');
}
};
//如果不加constructor对象,则alert(Person.prototype.constructor)为Object;如果加入该行对象定义,可以满足我们的要求结果是Person,但是当我们进行for-in遍历的时候constructor对象也会被遍历出来,这跟实际的构造函数不可遍历有冲突,因此我们可以使用defineProperty来给原型对象重新设置构造器的方法
Object.defineProperty(Person.prototype , 'constructor' , {
enumerable : false ,
value : Person
});
//参数1 :重设构造器的对象
//参数2:设置什么属性
//参数3:options配置项