面向对象与原型
ECMAscript有两种开发模式,1.函数式(过程化),2.面向对象(OOP)。面向的对象的语言有一个标志,那就是类的概念,而通过类可以创建任意多个具有相同属性和方法的对象,但是,ECMAscript没有类的概念,因此他的对象也与基于类的语言中的对象有所不同。
var box =new Object(); //创建一个对象 box.id="杜伟"; //给对象添加一个属性 box.ag=33; box.show=function(){ //为对象添加一个方法 alert("你好,我的名字叫"+this.id+"我今年"+this.ag+"岁!"); } //使用对象初始化器来创建对象 // var box={ // id:"杜伟", // agg:33, // show:function(){ // alert(this.id); // } // }; // box.show(); alert(box.show()); //输出方法 alert(box.id); //输出属性
上面创建了一个对象,并且创建属性和方法,在show方法里的this,就是代表box对象本身。这种事javascript创建对象最基本的方法没,但是有个缺点,想创建一个类似的对象,就会产生大量的代码。
工厂模式
function createobj(id,agg){ var obj=new Object(); //创建对象 obj.id=id; //添加属性 obj.agg=agg; obj.show=function(){ //添加方法 return this.agg; } return obj; //返回对象 } var box=createobj("杜伟",33); //创建第一个对象
var box1=createobj("王玥",34); //创建第二个对象 alert(box.show());
alert(box1.show()); alert(box.id);
工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清楚他们到底是哪个对象的实例
构造函数创建
function Box(user,age){ //创建一个构造方法,所有构造方法的对象就是Object this.user=user; //添加一个属性 this.age=age; this.run=function (){ //添加一个方法 return this.user+this.age+"运行中。。。。"; }; } var box=new Box("杜伟",33); //实例化box var box1=new Box("王玥",35); alert(box.run()); //输出box alert(box1.run());
1.构造函数没有工厂模式中的new Object,但它会在后台自动var obj=new Object();
2.this就相当于obj
3.构造函数不需要返回对象引用,它是后台自动返回的
构造方法的规范
1.构造函数也是函数,但函数名第一个字母大写
2.必须new构造函数名(),new Box(),而这个Box第一个字母也是大写的
3.必须使用new 运算符
function Box(user,age){ //创建一个构造方法 this.user=user; //添加一个属性 this.age=age; this.run=function (){ //添加一个方法 return this.user+this.age+"运行中。。。。"; }; } function Des(user,age){ //创建一个构造方法 this.user=user; //添加一个属性 this.age=age; this.run=function (){ //添加一个方法 return this.user+this.age+"运行中。。。。"; }; } var box=new Box("杜伟",33); //实例化box var box1=new Box("王玥",35); var box2=new Des("中国",6000); alert(box.run()); //输出box alert(box1.run()); alert(box2.run()); alert(box instanceof Object); //true
function Box(user,age){ //创建一个构造方法 this.user=user; //添加一个属性 this.age=age; this.run=function (){ //添加一个方法 return this.user+this.age+"运行中。。。。"; }; } //对象冒充 var o=new Object(); Box.call(o,"wangyue",23); //对象冒充 alert(o.run()); //o对象就有了Box的对象了
alert(o.run); //打印出run方法的引用地址
function Box(user,age){ //创建一个构造方法 this.user=user; //添加一个属性 this.age=age; this.run=run; } function run(){ //把构造函数内部的方法,通过全局来实现引用地址一至 return this.user+this.age+'运行中。。。。' } var box=new Box("lee",100); var box1=new Box("lee",100); alert(box.run==box1.run);//返回true ,如果是在构造方法中直接写内部函数就不相等,而内部方法每次实例化地址都不一样
三、原型
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个对象,它的用途是包含可以由特定类型的所有实例共享的属性和方法,逻辑上可以这么理解,prototype通过调用构造函数创建的那个对象的原型对象,使用原型的好处可以让所有对象实例共享它所有包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
//构造函数 function Box(user,age){ this.name=user; this.id=age; this.run=function(){ return this.name+this.id+"运行中。。 。"; }; }; var box=new Box("duw",33); alert(box.run()); //原型 function Box(){} //构造函数体内什么的都没有 Box.prototype.user="Lee"; //原型属性 Box.prototype.age=33; Box.prototype.run=function(){ //原型方法 return this.user+this.age+"运行中。。。"; }; var fei=new Box(); alert(fei.run());
alert(box.run==fei.run); //地址相等
//如果是实例方法,不同的方法的实例化,他们的方法的地址是不一样的,是唯一的
//如果是原形方法,那么她们的地址是共享的,大家都一样
在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的,__proto__属性是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor,通过这两个属性,就可以访问到原型里的属性和方法了。
ps:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他某些浏览器均能识别,虽然可以输出,反无法获取内部信息
//原型 function Box(){} //构造函数体内什么的都没有 Box.prototype.user="Lee"; //原型属性 Box.prototype.age=33; Box.prototype.run=function(){ //原型方法 return this.user+this.age+"运行中。。。"; }; var fei=new Box(); alert(fei.prototype); //访问不到 这是一个对象 返回undefined alert(fei.__proto__); //可以访问 这是prototype的指针 返回Object alert(fei.constructor); //构造属性,可以获取构造函数本身,作用是被原型 //指针定位,然后得到构造函数本身,其实就是实例对象 //对应的原型对象的作用
var lei=new Box(); //判断一个对象实例(对象引用)是不是指向了原型对象,基本上只要实例化了,它自动指向的 alert(Box.prototype.isPrototypeOf(lei)); //true
function Box(){ //如果这里这么写 打印出来的就是sdfsdf this.user="sdfsdf"; }; Box.prototype.user="Lee"; //原型属性 Box.prototype.age=33; var box1=new Box(); //实例化 box1.user="duwei"; //添加实例属性 实例属性并没有重写原型属性 alert(box1.user); // 打印出duwei ,就近原则
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回:
function Box(){ //如果这里这么写 打印出来的就是sdfsdf this.user="sdfsdf"; }; Box.prototype.user="Lee"; //原型属性 Box.prototype.age=33; var box1=new Box(); //实例化 box1.user="duwei"; //添加实例属性 实例属性并没有重写原型属性 alert(box1.user); // 打印出duwei ,就近原则 delete box1.user; //删除实例属性 删除原型属性 delete Box.prototype.user alert(box1.user); //打印Lee
原型的缺点
原型模式创建对象也有自己的缺点,它省略构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是他最大的有点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本只得属性也还可以,但如果属性包含引用类型,就存在一定的问题:
// 原型缺点 function Box(){}; Box.prototype={ user:"杜伟", age:33, family:['父亲','母亲','子女'], //添加一个数组属性 run:function(){ return this.user+this.age+this.family; } }; var box1=new Box(); //box1.family[0]="姐妹"; 如果我修改数组中的属性,box1 box2都会改变 alert(box1.run()); var box2=new Box(); alert(box2.run()); //因为与原型是共享的,实例化第二个的时候会和第一个一样
为了解决构造传参和共享问题,可以组合构造函数+原型模式:
// 构造函数+原型模式 function Box(user,age){ //不共享的使用构造函数 this.user=user; this.age=age; this.family=['父亲','姐妹','子女']; }; Box.prototype={ //共享的使用原型 constructor:Box, run:function (){ return this.user+this.age+this.family } }; var box1=new Box('杜伟',33); //实例1 alert(box1.run()); var box2=new Box('王玥',34); //实例2 alert(box2.run());
PS:这种混合模式很好的解决了传参和引用共享的大难题,是创建对象比较好的方法。
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在声明一个对象时,构造函数+原型部分让人感受又很怪异,最好就是把构造函数和原型封装到一起,为了解决这个问题,我们可以使用动态原型模式。
//动态原型模式 function Box(user,age){ //将所有信息封装到函数体内 this.user=user; this.age=age; if (typeof this.run!='function') //仅在第一次调用的初始化 判断类型 run 是不是函数 Box.prototype.fun=function(){ return this.user+this.age }; }; var box1=new Box('杜伟',33); alert(box1.fun()); alert(box1.run()); //错误 不存在
当第一次调用构造函数时,run()方法发现不存在,然后初始化原型,当第二次调用,就不会初始化,。并且第二次创建新对象,原型也不会再初始化,这样即得到了封装,又实现了原型方法共享,并且属性都保持独立。
PS:使用动态原型模式,要注意一点,不可以在使用字面量的方式重写原型,因为会切断实例和新原型之间的联系
以上讲解了各种方式对象创建的方法,如果这几种方式都不能满足需求,可以使用一开始那种模式:寄生构造函数:
寄生构造函数,其实就是工厂模式+构造函数模式,这种模式比较通用,单不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?架设要创建一个具有额外方法的引用类型,由于之前说明不建议直接String.addstring,可以通过寄生构造函数的方式添加。
// 寄生构造函数 function Box(user,age){ var obj=new Object(); obj.user=user; obj.family=age; obj.run=function(){ return this.user+this.family+"运行中!"; }; return obj; }; var box1=new Box('杜伟',33); alert(box1.run());
稳妥构造函数:
在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,这里的new是在外部实例化构造函数不使用new,这种创建方式叫做稳妥构造函数。
// 稳妥构造函数 function Box(user,age){ var obj=new Object(); obj.user=user; obj.age=age; obj.run=function(){ return user+age+"运行中!"; }; return obj; }; var box1=Box('杜伟',33); alert(box1.run());