您当前的位置: 首页 > 学无止境 > JS经典实例 网站首页JS经典实例
javascript学习笔记-面向对象与原型
发布时间:2018-04-05 17:54:38编辑:雪饮阅读()
工厂模式(模拟正统语言的面向对象)
Javascript的面向对象不支持像大型语言一样的方式,实例化一个对象都是直接new Object()
所以可以通过一些手段,如工厂模式来实现
function createObject(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return 'name:'+this.name+',age:'+this.age;
}
return obj;
}
var obj1=createObject('雪饮',24);
var obj2=createObject('杜敏捷',25);
alert(obj1.run());
alert(obj2.run());
工厂模式带来的弊端
//模拟正统面向对象中的类
function createObject(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return 'name:'+this.name+',age:'+this.age;
}
return obj;
}
//模拟正统面向对象中的类
function createObject2(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return 'name:'+this.name+',age:'+this.age;
}
return obj;
}
//用两个类分别创建对象
var obj1=createObject('雪饮',24);
var obj2=createObject2('杜敏捷',25);
//但这两个对象却属于相同一个类Object,没有办法区分
alert(typeof obj1);
alert(typeof obj2);
alert(obj1 instanceof createObject);
alert(obj1 instanceof Object);
alert(obj2 instanceof createObject);
alert(obj2 instanceof Object);
构造函数(解决工厂模式的弊端)
//构造函数(模拟正统面向对象的类)
/*
构造函数的定义 :
构造函数没有new Object,但它后台后自动var obj=new Object
this就相当于obj
构造函数不需要返回对象引用,它是后台自动返回的
构造函数也是函数,但函数名第一个字母必须大写
调用时必须使用new运算符
*/
function Box(name,age){
this.name=name;
this.age=age;
this.run=function(){
return 'name:'+this.name+',age:'+this.age;
};
}
function Box2(name,age){
this.name=name;
this.age=age;
this.run=function(){
return 'name:'+this.name+',age:'+this.age;
};
}
var obj1=new Box('雪饮',24);
var obj2=new Box2('杜敏捷',25);
//两个对象可以分别属于各自的类
alert(obj1.run());
alert(obj1 instanceof Box);
alert(obj2 instanceof Box);
alert(obj2 instanceof Box2);
构造函数允许对象冒充
//构造函数(模拟正统面向对象的类)
function Box(name,age){
this.name=name;
this.age=age;
this.run=function(){
return 'name:'+this.name+',age:'+this.age;
};
}
//构造函数允许对象冒充调用
var obj3=new Object();
Box.call(obj3,'雪饮杜敏捷',48);
alert(obj3.run());
构造函数的缺陷(方法的引用地址不一致)
在正统面向对象中两个实例化的参数相同,并且都是同一个类,那么实例化后的两个对象的同一个方法的引用地址应该是相同的。而由于javascript中function实则上也是每次调用就实例化一个对象的。如:
function Box(name,age){
this.name=name;
this.age=age;
this.run=new Function("return 'name:'+this.name+',age:'+this.age");
}
和
function Box(name,age){
this.name=name;
this.age=age;
this.run=function(){
return 'name:'+this.name+',age:'+this.age;
};
}
这两者实际上效果是相同的都是实例化
所以同一个类,相同的实例化参数分别实例化的两个对象的相同方法的引用地址不一致
var obj1=new Box('杜敏捷',28);
var obj2=new Box('杜敏捷',28);
//这里不执行方法,只比较方法的引用地址
alert(obj1.run==obj2.run);
构造函数方法的引用地址不一致的解决
我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性
function Box(name,age){
this.name=name;
this.age=age;
this.run=run;
}
function run(){
return 'name:'+this.name+',age:'+this.age;
}
var obj1=new Box('杜敏捷',28);
var obj2=new Box('杜敏捷',28);
//这里不执行方法,只比较方法的引用地址
alert(obj1.run==obj2.run);
虽然使用了全局的函数 run()来解决了保证引用地址一致的问题,但这种方式又带来了 一个新的问题,全局中的 this 在对象调用的时候是 Box 本身,而当作普通函数调用的时候, this 又代表 window。
原型
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个对象,它的用途是 包含可以由特定类型的所有实例共享的属性和方法。
构造函数中的属性和方法叫做实例属性和实例方法,而原型中的属性和方法叫做原型属性和原型方法。
构造函数中为了保持方法引用地址一致而导致的全局调用问题得解,由于原型是由特定类型的所有实例共享的属性和方法,所以本身就是保持了一致性的引用地址。验证如:
//原型
//构造函数体内什么都没有,如果有就叫做实例属性或实例方法
function Box(){}
Box.prototype.name='雪饮';
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box1=new Box();
var box2=new Box();
alert(box1.run==box2.run);
原型实例的两个重要的附加属性__proto__和constructor
function Box(){}
Box.prototype.name='雪饮';
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box1=new Box();
//每个实例中都有一个指针__proto__,该指针指向(引用于)原型对象prototype
alert(box1.__proto__);
//每个实例中都有一个constructor属性,该属性返回原型构造函数本身
alert(box1.constructor);
判断一个对象是否属于一个原型的实例
function Box(){}
Box.prototype.name='雪饮';
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
function Box2(){
this.name='雪饮';
this.age=24;
this.run=function(){
return this.name+this.age+'运行中';
};
}
var box1=new Box();
var box2=new Box2();
//判断一个对象是否属于该原型的实例
alert(Box.prototype.isPrototypeOf(box1));
alert(Box.prototype.isPrototypeOf(box2));
alert(Box2.prototype.isPrototypeOf(box2));
alert(Box2.prototype.isPrototypeOf(box3));
alert(Object.prototype.isPrototypeOf(box3));
原型模式的执行流程
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
function Box(){
//实例属性
this.name='杜敏捷';
}
//原型属性
Box.prototype.name='雪饮';
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box=new Box();
//实例属性
box.age=28;
alert(box.name);
alert(box.age);
原型属性与实例属性的删除
function Box(){}
//原型属性
Box.prototype.name='雪饮';
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box=new Box();
//实例属性
box.name='杜敏捷';
box.age=28;
//删除实例属性
delete box.name;
//删除原型属性
delete Box.prototype.age;
alert(box.name);
alert(box.age);
判断一个属性是否存在于实例中(仅判断实例不判断原型)
实例里有返回 true,否则返回 false
function Box(){}
//原型属性
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box=new Box();
//实例属性
box.name='杜敏捷';
//判断一个属性是否存在于实例中
alert(box.hasOwnProperty('name'));
判断一个属性是否存在于实例或原型中
function Box(){}
//原型属性
Box.prototype.age=24;
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
var box=new Box();
//实例属性
box.name='杜敏捷';
//判断一个属性是否存在于原型或实例中
alert('name' in box);
alert('age' in box);
字面量方式和构造函数方式创建原型对象对constructor的影响
字面量方式创建原型对象后该原型的实例访问constructor属性返回的是Object构造函数,而构造函数方式创建原型对象后该原型的实例访问constructor属性返回的是该构造函数。
//构造函数方式创建原型对象
function Box(){}
Box.prototype.age=24;
Box.prototype.name='杜敏捷';
Box.prototype.run=function(){
return this.name+this.age+'运行中';
};
//字面量方式创建原型对象
function Box2(){}
Box2.prototype={
age:24,
name:'杜敏捷',
run:function(){
return this.name+this.age+'运行中';
}
};
//构造函数方式创建的原型对象的实例
var box=new Box();
//字面量方式创建的原型对象的实例
var box2=new Box2();
alert(box.constructor);
alert(box2.constructor);
PS:字面量方式为什么 constructor 会指向 Object?因为 Box.prototype={};这种写法其实 就是创建了一个新对象。
字面量方式创建原型对象的实例属性constructor指向问题的解决
字面量方式创建原型对象的实例属性constructor不能指向构造函数自身,但是可以强制指向构造函数自身,也可以强制指向其它构造函数。
function Box(){}
//constructor强制指向构造函数自身
Box.prototype={
constructor:Box,
age:24,
name:'杜敏捷',
run:function(){
return this.name+this.age+'运行中';
}
};
function Box2(){}
//constructor强制指向自定义类(构造函数)
Box2.prototype={
constructor:String,
age:24,
name:'杜敏捷',
run:function(){
return this.name+this.age+'运行中';
}
};
var box=new Box();
var box2=new Box2();
alert(box.constructor);
alert(box2.constructor);
javascript内置类也使用了原型
alert(Array.prototype.sort);
alert(String.prototype.substring);
内置类也可以被扩展
String.prototype.xy=function(){
return this+'杜敏捷love雪饮';
}
var box='1314';
alert(box.xy());
组合构造函数+原型模式(可传参的原型)
function Box(name,age){
//每个对象的独立属性
this.name=name;
this.age=age;
this.family=['哥哥','姐姐','妹妹'];
}
Box.prototype={
//每个对象的共享属性
constructor:Box,
run:function(){
return this.name+this.age+'岁';
}
};
var box1=new Box('雪饮',24);
var box2=new Box('杜敏捷',28);
//这样以来某个对象的属性变更就不会影响其它对象的属性
box2.family.splice(0,1);
alert(box1.run());
alert(box1.family);
alert(box2.run());
alert(box2.family);
动态原型模式(组合构造函数+原型模式的封装)
function Box(name,age){
this.name=name;
this.age=age;
Box.prototype.run=function(){
return this.name+this.age+'岁';
};
}
var box1=new Box('雪饮',24);
var box2=new Box('杜敏捷',28);
alert(box1.run());
alert(box2.run());
动态原型模式的性能优化
function Box(name,age){
this.name=name;
this.age=age;
if(typeof this.run != 'function'){
//这样以来原型初始化就只执行一次
alert("原型初始化");
Box.prototype.run=function(){
return this.name+this.age+'岁';
};
}
}
var box1=new Box('雪饮',24);
var box2=new Box('杜敏捷',28);
alert(box1.run());
alert(box2.run());
寄生构造函数
看起来和工厂模式一样,但是仔细观察方法名会发现是构造函数,称之为寄生构造函数。效果和工厂模式一样,只是看起来更正统一些。
function Box(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return this.name+this.age+'岁';
}
return obj;
}
var box1=new Box('雪饮',24);
var box2=new Box('杜敏捷',28);
alert(box1.run());
alert(box2.run());
稳妥构造函数
在一些安全的环境中,比如禁止使用 this 和 new,这里的 this 是构造函数里不使用 this, 这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。
function Box(name,age){
var obj=new Object();
obj.name=name;
obj.age=age;
obj.run=function(){
return this.name+this.age+'岁';
}
return obj;
}
//直接调用函数
var box1=Box('雪饮',24);
var box2=Box('杜敏捷',28);
alert(box1.run());
alert(box2.run());
PS:稳妥构造函数和寄生类似。
通过原型链实现继承
//父类
function Box(){
this.name='Lee';
}
//子类
function Desk(){
this.age=100;
}
//通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性
//子类实例化后的对象将会得到父类构造里的信息和原型里的信息
Desk.prototype=new Box();
var desk=new Desk();
alert(desk.name);
原型链实现继承的缺陷
对于在父类构造里面的信息应该都是独立的而不是共享的,只有原型里面才允许共享。而原型链实现继承的方式中父类构造中的引用类型不是独立的。
另外原型链实现继承时无法传递参数。
//父类
function Box(){
this.name='Lee';
this.fm=['gg','jj'];
}
//子类
function Desk(){
this.age=100;
}
Desk.prototype=new Box();
var desk=new Desk();
var desk2=new Desk();
//改变了一个实例的一个引用类型属性信息
desk2.fm.push('mm');
//而另外一个实例的引用类型属性信息也被修改了
alert(desk.fm);
通过对象冒充继承(借用构造函数、伪造对象、经典继承)
通过对象冒充继承解决了原型链继承的无法传参问题和构造中引用类型共享问题
function Box(name,age){
this.name=name;
this.age=age;
this.fm=['gg','jj'];
}
function Desk(name,age){
Box.call(this,name,age);
}
var desk=new Desk('Lee',100);
var desk2=new Desk('Lee',100);
desk2.fm.push('mm');
alert(desk.fm);
alert(desk2.fm);
对象冒充继承的缺陷
对象冒充继承只能继承构造里的信息
function Box(name,age){
this.name=name;
this.age=age;
}
Box.prototype.family='家庭';
function Desk(name,age){
Box.call(this,name,age);
}
var desk=new Desk('Lee',100);
alert(desk.name);
//对象冒充继承无法继承原型中的信息
alert(desk.family);
组合继承(原型链+借用构造函数)
组合继承解决了对象冒充继承只能继承构造的问题
function Box(name,age){
this.name=name;
this.age=age;
this.fm=['gg','jj'];
}
//原型
Box.prototype.run=function(){
return this.name+this.age+'岁';
}
function Desk(name,age){
Box.call(this,name,age);
}
//继承原型
Desk.prototype=new Box();
var desk=new Desk('lee',100);
var desk2=new Desk('lee',100);
desk2.fm.push('mm');
alert(desk.run());
alert(desk.fm);
alert(desk2.fm);
组合继承带来的性能问题
function Box(name,age){
this.name=name;
this.age=age;
this.fm=['gg','jj'];
}
//原型
Box.prototype.run=function(){
return this.name+this.age+'岁';
}
function Desk(name,age){
//第二次调用Box
Box.call(this,name,age);
}
//第一次调用Box
Desk.prototype=new Box();
var desk=new Desk('lee',100);
var desk2=new Desk('lee',100);
desk2.fm.push('mm');
alert(desk.run());
alert(desk.fm);
alert(desk2.fm);
原型式继承
//原型式继承
//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
//F.prototype=o其实就相当于之前原型链继承中Desk.prototype=new Box();
return new F();
}
var box={
name:'lee',
age:100,
family:['gg','jj']
}
//原型式继承也存在原型链继承的缺陷
var box1=obj(box);
var box2=obj(box);
alert(box1.family);
box1.family.push('mm');
alert(box2.family);
寄生式继承
//寄生式继承
//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
//寄生函数
function create(o){
var f=obj(o);
f.run=function(){
return this.name+this.age+'岁';
}
return f;
}
var box={
name:'lee',
age:100,
family:['gg','jj']
}
var box1=create(box);
alert(box1.run());
寄生组合继承(组合继承性能问题的解决)
//寄生组合继承
//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
//寄生函数
function create(box,desk){
//产生一个新实例该实例原型指向Box的原型(原型共享)
var f=obj(box.prototype);
//新实例由于共享了Box的原型所以构造也变成了Box的构造,这里需要将新实例的构造重新修正
f.constructor=desk;
//Desk的原型指向产生的这个新实例(相当于共享了Box的原型属性和新实例的实例属性)
desk.prototype=f;
}
function Box(name,age){
this.name=name;
this.age=age;
this.family=['gg','jj'];
}
Box.prototype.run=function(){
return this.name+this.age+'岁';
}
function Desk(name,age){
Box.call(this,name,age);
}
//通过寄生组合继承来实现继承(若没有指向create则Box原型的run是继承不过来的,只能继承其实例属性)
create(Box,Desk);
var desk=new Desk('杜敏捷',28);
var desk2=new Desk('雪饮',24);
alert(desk.constructor);
alert(desk.name);
alert(desk.run());
alert(desk.family);
desk.family.push('mm');
alert(desk.family);
alert(desk2.name);
alert(desk2.family);
关键字词:javascript,面向对象,原型