prototype,原型的初览
function Person(){
Person.prototype.name="小花";
Person.prototype.age=18;
Person.prototype.say=function(){alert("姓名:"+this.name+",年龄:"+this.age);
}
}
var p1=new Person();
p1.say();
say();//报错了
这样我们发现window就无法访问到say方法了,此时say方法只属于person对象独有的方法。很好的解决了封装破坏的情况。
什么是原型
上面我们烂了基于prototype创建对象的方式很好的解决了我们前面遇到的一系列问题,那么到底什么是原型,原型又是如何解决以上的问题的呢?我们下面来研究研究。
原型是js中非常特殊一个对象,当一个函数创建之后,会随之产生一个原型对象,当通过这个函数的构造函数创建了一个具体的对象之后,在这个具体的对象中就会有一个属性指向原型。这就是原型的概念。
鉴于原型的概念比较难以理解,我们就以上面的代码为例,画图为大家讲解。
第一步:function Person(){}之后,内存中创建了一个Person对象,有一个prototype属相,指向了Person对象的原型对象,而原型对象中存在了一个constructor的属性,指向了Person对象。
第三种状态:当根据Person构造函数创建一个对象后,该对象中存在一个_prop_的属性,也指向了Person对象的原型对象,当我们调用对象的属性或者方法时,首先在自己里面找,找不到的话,就会去Person对象的原型对象中找
原型的基本知识到这里也就差不多了,只有对上面的图和代码能够很好的理解,那么原型的理解就没问题,下面介绍几种原型的检测方式。
alert(Person.prototype.isprototypeof(p1)) //检测p1的构造函数是否指向Person对象 alert(p1.constructor==Person) //检测某个属性是不是自己内存中的 alert(p1.hasOwnProperty("name"));可以使用delete语句来删除我们赋予对象的自己属性(注意:原型中的是无法删除的)如:
delete p1.name; p1.say(); alert(p1.hasOwnProperty("name"));
检测在某个对象自己或者对应的原型中是否存在某个属性。
alert("name" in p1);//true delete p2.name;//虽然删除了自己的name属性,但是原型中有 alert("name" in p2);//true //原型和自己中都有sex属性 alert("sex" in Person)//false
那么问题来了。如果检测只在原型中,不在自己的属性呢?
function hasPP(obj,prop){ if(!obj.hasOwnProperty(prop)){ if(prop in obj){ return true; } } return false; } alert(hasPP(p1,"name")); alert(hasPP(p2,"name"));
原型重写
在上面的写法中,我们已经解决了大量的问题,使用原型。但是如果我们的对象中存在大量的属性或者方法的时候,使用上面的方式,感觉要写大量的【对象.prototype.属性名】这样的代码,感觉不是很好,那么我们可以使用JSON的方式来写:
function Person(){} Person.prototype={ name:"刘帅哥"; age:18; say:function(){ alert("姓名:"+this.name+",年龄:"+this.age); } } var p1=new Person(); p1.say(); var p2=new Person(); p2.age=20; p2.name="小明"; p2.say();
但是这种写法,我们是将该对象的原型覆盖(注意:这两种写法不一样的,第一种是扩充,第二种是覆盖),就会出现如下的问题:
function Person(){} Person.prototype={ name:"刘帅哥"; age:18; say:function(){ alert("姓名:"+this.name+",年龄:"+this.age); } } var p1=new Person(); p1.say(); var p2=new Person(); p2.age=20; p2.name="小明"; p2.say(); //此时p1的构造器不再指向Person,而是指向了Object //因为我们覆盖了Person的原型,所以如果constructor比较重要的话, //我们可以收到指向 alert(p1.constructor==Person)
此时就没有问题了。但是原型重写会给我们带来非常有趣的现象。
这些代码要研究明白,必须配合之前原型的图来看,下面我画图来说明问题:function Person(){} var p1=new Person(); Person.prototype.sayHello=function(){ alert("名字:"+this.name+",年龄:"+this.age); } Person.prototype={ constructor:Person, name:"huahua", age:18, friends:["haha","xxx"], say:function(){ alert("名字:"+this.name+",年龄:"+this.age); } } var p2=new Person(); p2.say();//正确 p1.sayHello();//此时找不到name和age,但是代码正确 p1.say();//错误,因为原型重写 p2.sayHello();//错误
因为原型重写,需要大家根据原型的原理图来理解,原型的知识也就这些了。
封装——原型创建对象
因为原型存在,我们事先了对象的封装,但是这种封装也同样可能存在问题的。
1.我们无法像使用构造函数的那样将属性传递用于设置值
2.当属性中引用类型,可能存在变量值的重复
function Person(){}; Person.prototype={ constructor:Person, name:"huahua", age:18, friends:["haha","xxx"], say:function(){ alert(this.name+this.age+this.friends); } } var p1=new Person(); p1.say(); var p2=new Person; p2.name="xiaocao"; p2.say(); p1.friends.push("qq"); alert(p1.friends);//qq alert(p2.friends);//qq //因为p1和p2对象都指向了同一个原型链,所以当p1属性值发生变化时,p2也变化,p2的输出也和p1一样
终极方案——基于组合的对象定义
为了解决原型所带来的问题,需要通过组合构造函数和原型来实现对象的创建将:属相在构造函数中定义,将方法在原型中定义。这种有效集合了两者的优点,是目前最为常用的一种方式。
//所以需要通过组合的封装构造函数和原型来实现对象的创建 //基于组合的对象定义 //即属性在构造方法中定义,方法在原型中定义 function Person(name,age,friends){ this.name=name; this.age=age; this.friends=friends; } //此时所有的属性都保存在自己的内存中 Person.prototype={ constructor:Person, say:function(){ alert(this.name+this.age+this.friends); } } //方法定义在原型中 var p1=new Person("花猫",19,["小花","小妹"]); p1.friends.push("豆豆"); alert(p1.friends); var p2=new Person("黑猫",19,["小草","小妹"]); p2.friends.push("南瓜"); alert(p2.friends); //基于动态原型的对象定义 function Person(name,age,friends){ this.name=name; this.age=age; this.friends=friends; //判断不存在的时候写 //如果存在就不写,减少内存消耗 if(!Person.prototype.say){ Person.prototype.say=function(){ alert(this.name+this.age+this.friends); } } } var p1=new Person("花猫",19,["小花","小妹"]); p1.friends.push("豆豆"); alert(p1.friends); var p2=new Person("黑猫",19,["小草","小妹"]); p2.friends.push("南瓜"); alert(p2.friends);
JavaScript面向对象对应一个对象的方式,上述两种都行,根据个人习惯而定。这也是JavaScript中面向对象的封装。将属性和方法封装所对应的对象中,其他对象无法得到和访问。