JS面向对象

来源:互联网 发布:java处理nullpointer 编辑:程序博客网 时间:2024/06/14 10:15
/*
 * 创建对象
 *创建一个对象,然后给这个对象新建属性和方法。
 */


var box=new Object();        //创建一个Object对象
box.name="zhang";        //创建一个name属性并赋值
box.age=22; //创建一个age属性并赋值
box.run=function(){      //创建一个run方法并返回值
return this.name+this.age+"运行中...";
}
alert(box.run());            //输出属性和方法的值


/*
 *上面创建了一个对象,并创建属性和方法,在run()方法里的this,就是代表box对象
 *本身,这种是JavaScript创建对象最基本的方法,但有个缺点,想要创建一个类似的对象,
 *就要产生大量的代码。
 */
var box2=box;
box2.name="Jack";
alert(box2.run());
box2.age=200;
box2.run=function(){
return this.name+this.age+"运行中...";
};
alert(box2.run());


/*
 *为了解决多个类似对象声明的问题,我们可以使用一种叫做 工厂模式的方法,这种方法
 *就是为了解决实例化对象产生大量重复的问题。
 * /
function createObject(name,age){   //集中实例化函数
var obj=new Object;
obj.name=name;
obj.age=age;
obj.run=function(){
return this.name+this.age+"运行中..."
}
return obj;
}
var box1=createObject("zhang",22);  //第一个实例
var box2=createObject("wang",20);   //第二个实例
alert(box1.run());
alert(box2.run());                  //保持独立
 
/*工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
 *搞清楚他们到底是哪个对象的实例。
 */


alert(typeof box1);      //Object
alert(box1 instanceof Object) //true


/*
 * ECMAScript 中可以采用 构造函数(构造方法)可用来创建特定的对象。类型于 Object 对
 *象。
 */
function Box(name,age){   //构造函数模式
this.name=name;
this.age=age;
this.run=function(){
return this.name+this.age+"运行中...";
};
}


var box1=new Box("zhang",22);
var box2=new Box("wang",20);


alert(box1.run());
alert(box1 instanceof box);        //box1从属于box


使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题
是,这里并没有 new Object(),为什么可以实例化 Box(),这个是哪里来的呢?
使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给 this 对象;
3.没有 renturn 语句。
构造函数的方法有一些规范:
1.函数名和实例化构造名相同且大写,(PS:非强制,但这么写有助于区分构造函数和
普通函数);
2.通过构造函数创建对象,必须使用 new 运算符。
既然通过构造函数可以创建对象,那么这个对象是哪里来的,new Object()在什么地方
执行了?执行的过程如下:
1.当使用了构造函数,并且 new 构造函数(),那么就后台执行了 new Object();
2.将构造函数的作用域给新对象,(即 new Object()创建出的对象),而函数体内的 this 就
代表 new Object()出来的对象。
3.执行构造函数内的代码;
4.返回新对象(后台直接返回)。
关于 this 的使用,this 其实就是代表当前作用域对象的引用。如果在全局范围 this 就代
表 window 对象,如果在构造函数体内,就代表当前的构造函数所声明的对象。




var box=2;
alert(this.box);


/*
 *构造函数和普通函数的唯一区别,就是他们调用的方式不同。只不过,构造函数也是函
 *数,必须用 new 运算符来调用,否则就是普通函数
 */
var box=new Box("zhang",100);  //构造模式调用
alert(box.run());


Box("zhang",20);               //普通模式调用,无效


var o=new Object();
Box.call(o,"zhang",200);       //对象冒充调用
alert(o.run());


探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相
等。


var box1=new Box('zhang',100); //传递一致
var box2=new Box('zhang',100); //同上
alert(box1.name==box2.name);   //true ,属性的值相等
alert(box1.run==box2.run);     //false,方法是一种引用地址
alert(box1.run()==box2.run()); //true,方法的值相等,因为传参一致


可以把构造函数里的方法(或函数)用 new Function()方法来代替,得到一样的效果,更加
证明,他们最终判断的是引用地址,唯一性。






//我们可以通过构造函数外面绑定同一个函数的方法来保证引用地址的一致性,但这种做
//法没什么必要,只是加深学习了解:


function Box(name,age){
this.name=name;
this.age=age;
this.run=run;
}
function run(){
return this.name+this.age+"运行中...";
}
var box1=new Box("zhang",20);
alert(box1.run());
虽然使用了全局的函数 run()来解决了保证引用地址一致的问题,但这种方式又带来了
一个新的问题,全局中的 this 在对象调用的时候是 Box 本身,而当作普通函数调用的时候 ,
this 又代表 window。




三. 原型
我们创建的每个函数都有一个 prototype(原型)属性,这个属性是一个对象,它的用途是
包含可以由特定类型的所有实例共享的属性和方法。逻辑上可以这么理解:prototype 通过
调用构造函数而创建的那个对象的原型对象。使用原型的好处可以让所有对象实例共享它所
包含的属性和方法。也就是说,不必在构造函数中定义对象信息,而是可以直接将这些信息
添加到原型中。


function Box(){}               //声明一个构造函数


Box.prototype.name='Lee';      //在原型里添加属性
Box.prototype.age=100;
Box.prototype.run=function(){  //在原型里添加方法
return this.name+this.age+'运行中...';
};
var box1=new Box();
var box2=new Box();
alert(box1.run());
alert(box1.run==box2.run);   //true,方法的引用地址保持一致


原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;


//虽然我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值
var box1=new Box();
//alert(box1.name);              //Lee,原型中的值
box1.name='zhang'; 
//alert(box1.name);              //Jack,就近原则


var box2=new Box();            
alert(box2.name);               //Lee,原型里的值没有被box1修改
/*如果想要 box1 也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可 ,
    具体如下:
 */
delete box1.name;
alert(box1.name);


如何判断属性是在构造函数的实例里,还是在原型里?可以使用 hasOwnProperty()函数
来验证:
var box1=new Box();
//alert(Box.hasOwnProperty('name'));  //实例里返回true


in  操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原
型中
alert('name' in box1);   //true,存在实例或原型中,只要有就返回true


我们可以通过  hasOwnProperty()方法检测属性是否存在实例中,也可以通过 in 来判断
实例或原型中是否存在属性。那么结合这两种方法,可以判断原型中是否存在属性。
function isPrototype(object,property){   //判断原型中是否存在属性
return !object.hasOwnProperty(property)&&(propery in object);
}


alert(isProperty(box1,'name'));   //true,如果有






//为了属性和方法更好的体现封装效果,并且较少不必要的输入,原型的创建可以使用字面量方式:
function Box(){};
Box.prototype={
name:'zhang',
age:200,
run:function(){
return this.name+this.age+"运行中...";
}
};
/*
 使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区
别,字面量创建的方式使用 constructor 属性不会指向实例,而会指向 Object,构造函数创建
的方式则相反。
 */
var box1=new Box();
alert(box1.run());
alert(box1 instanceof Box);
alert(box1 instanceof Object);
alert(box1.constructor==Box);
alert(box1.constructor==Object);


//如果想让字面量方式的constructor指向实例对象,操作如下:
Box.prototype={
constructor:Box,
}
var box2=new Box();
alert(box2.constructor==Box);




PS:字面量方式为什么 constructor 会指向 Object?因为 Box.prototype={};这种写法其实
就是创建了一个新对象。而每创建一个函数,就会同时创建它 prototype,这个对象也会自
动获取 constructor 属性。所以,新对象的 constructor 重写了 Box 原来的 constructor,因此会
指向新对象,那个新对象没有指定构造函数,那么就默认为 Object。


/*
 * 原型的声明是有先后顺序的,所以重写的原型会切断之前的原型。
 */
function Box(){};
Box.prototype={
constructor:Box,   //原型被重写了,强制指向Box
name:'zhang',
age:100,
run:function(){
return this.name+this.age+"运行中...";
}
};
Box.prototype={
age:200
}
var box1=new Box();      //在这里声明
alert(box1.run());       //box 只是最初声明的原型


原型对象不仅仅可以在自定义对象的情况下使用,而 ECMAScript 内置的引用类型都可
以使用这种方式,并且内置的引用类型本身也使用了原型。


var box=[4,8,5,9,3,4];
alert(box.sort());


//查看sort是否是Array类型中的原型方法
alert(Array.prototype.sort);
//查看sunstring是不是String中的方法
alert(String.prototype.substring);


String.prototype.addstring=function(){ //给String类添加一个方法
return this+',被添加了!';         //this代表调用的字符串
};
alert('Lee'.addstring());  //使用这个方法




PS:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种
方法。因为它可能会导致命名冲突,不利于代码维护。
原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺
点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性
也还可以。但如果属性包含引用类型,就存在一定的问题:




function Box(){};
Box.prototype={
constructor:Box,
name:'zhang',
age:100,
family:['父亲','母亲','妹妹'],
run:function(){
return this.name+this.age+this.family;
}
};


var box1=new Box();
box1.family.push('哥哥');        //在实例中添加哥哥
alert(box1.run());


var box2=new Box();
alert(box2.run());             //共享带来的麻烦也有了哥哥




PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化出的数据需要
保留自己的特性,而不能共享。
为了解决构造传参和共享问题,可以 组合构造函数+ + + + 原型模式:


function Box(name,age){    //不共享的使用构造函数
this.name=name;
this.age=age;
this.family=['父亲','母亲','妹妹'];
};
Box.prototype={            //共享的使用原型模式
constructor:Box,
run:function(){
return this.name+this.age+this.family;
}
};


var box1=new Box('zhang',22);
alert(box1.run());


PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。
原型模式,不管你是否调用了原型中的共享方法,它都会初始化原型中的方法,并且在
声明一个对象时,构造函数+原型部分让人感觉又很怪异,最好就是把构造函数和原型封装
到一起。为了解决这个问题,我们可以使用 动态原型模式。


function Box(name,age){          //将所有信息封装的函数体内
this.name=name;
this.age=age;

if(typeof this.run!='function'){  //仅在第一次调用的初始化
Box.prototype.run=function(){
return this.name+this.age+'运行中...';
};
}
}
var box1=new Box('zhang',22);
alert(box1.run());


在一些安全的环境中,比如禁止使用 this 和 new,这里的 this 是构造函数里不使用 this ,
这里的 new 是在外部实例化构造函数时不使用 new。这种创建方式叫做稳妥构造函数。


function Box(name,age){
var obj=new Object();
obj.run=function(){
return name+age+'运行中...';  //直接打印参数
}
return obj;
}
var box1=Box('zhang',100);           //直接调用函数
alert(box1.run());




四. 继承
继承是面向对象中一个比较核心的概念。其他正统面向对象语言都会用两种方式实现继
承:一个是接口实现,一个是继承。而 ECMAScript 只支持继承,不支持接口实现,而实现
继承的方式依靠原型链完成。


function Box(){           //Box()构造
this.name='zhang';
}
function Desk(){          //Desk构造
this.age=100;
}
function Table(){         //Table构造
this.level='AAAAA';
}
Desk.prototype=new Box(); //Desk继承Box,通过原型,形成链条
var desk=new Desk();
alert(desk.age);
alert(desk.name);         //得到被继承的属性


Table.prototype=new Desk();      //继续原型链继承


var table=new Table();
alert(table.name);               //继承Box和Desk


如果要实例化 table,那么 Box 实例中有 name='zhang',原型中增加相同的属性 name='jack',
最后结果是多少呢?
function Box(){           //被继承的函数叫做父类型(父类,基类)
//this.name='zhang';
}
Box.prototype.name='jeck';
function Desk(){          //继承的函数叫做子类型(子类,派生类)
this.age=100;
}
Desk.prototype=new Box();  //通过原型链继承
var desk=new Desk();
alert(desk.name);       //就近原则,实例有就返回,没有再查看原型中。


PS:以上原型链继承还缺少一环,那就是 Obejct,所有的构造函数都继承自 Obejct。而
继承 Object 是自动完成的,并不需要程序员手动继承。
经过继承后的实例,他们的从属关系会怎样呢?
alert(table instanceof Object); //true
alert(desk instanceof Table); //false,desk 是 table 的超类
alert(table instanceof Desk); //true
alert(table instanceof Box); //true
在 JavaScript 里,被继承的函数称为超类型(父类,基类也行,其他语言叫法),继承的
函数称为子类型(子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使
用引用类型的原型,并且子类型还无法给超类型传递参数。
为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或
者成为对象冒充(伪造对象、经典继承)的技术来解决这两种问题。


function Box(age){
this.name=['lee','zhang','hello']
this.age=age;
}
function Desk(age){
Box.call(this,age);  //this代表Desk本身,给超类传参
}
var desk=new Desk(200);
alert(desk.age);        
alert(desk.name);
desk.name.push('AAAA');   //添加新数据只给desk
alert(desk.name);


借用构造函数虽然解决了刚才两种问题,但没有原型,复用则无从谈起。所以,我们需
要 原型链+ + + + 借用构造函数的模式,这种模式成为 组合继承。


function Box(age){
this.name=['lee','zhang','hello']
this.age=age;
}
Box.prototype.run=function(){
return this.name+this.age;
};
function Desk(age){
Box.call(this,age);
}
Desk.prototype=new Box();
var desk=new Desk(100);
alert(desk.run());


还有一种继承模式叫做: 原型式继承;这种继承借助原型并基于已有的对象创建新对象 ,
同时还不必因此创建自定义类型。


/*
 * 1.原型链继承 2.借用构造函数继承(对象冒充继承)  3.组合继承(结合前两种) 4.原型式继承
 */
//临时中转函数
function obj(o){     //传递一个字面量函数,o表示将要传递进入嘚一个对象
function F(){}   //创建一个构造函数,F构造是一个临时新建的对象,用来储存传递过来的对象
F.prototype=o;   //把字面量函数复制给构造函数,o对象实例赋值给F构造的原型对象
return new F();  //最终返回实例化构造函数,最后返回这个得到传递过来的对象的对象实例
}
var box={            //字面量对象
name:'zhang',
arr:['哥哥','妹妹','姐姐']
}
var box1=obj(box);    //传递
alert(box1.name);
box1.arr.push('弟弟');
alert(box1.arr);


var box2=obj(box);
alert(box2.arr)        //应用类型的属性被共享了


寄生式继承把原型式+工厂模式结合而来,目的是为了封装创建对象的过程。


//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
//寄生函数
function create(o){
var f=obj(o);
f.run=function(){
return this.name+'方法';
}
return f;
}
var box={
name:'zhang',
age:100,
family:['哥哥','姐姐','妹妹']
};
var box1=create(box);
alert(box1.run());


组合式继承是 JavaScript 最常用的继承模式;但,组合式继承也有一点小问题,就是超
类型在使用过程中会被调用两次:一次是创建子类型的时候,另一次是在子类型构造函数的
内部。


/*
 * 寄生组合继承
 */
//临时中转函数
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
//寄生函数
function create(box,desk){
var f=obj(box.prototype);
f.constructor=desk;     //调整原型构造指针
desk.prototype=f;
}
function Box(name,age){
this.name=name;
this.age=age;
}
Box.prototype.run=function(){
return this.name+this.age+'运行中...';
}
function Desk(name,age){
Box.call(this,name,age);  //对象冒充
}
//通过寄生组合模式继承实现继承
create(Box,Desk);             //这句代替了Desk.prototype=new Box();
var desk=new Desk('zhang',100);
alert(desk.run());
alert(desk.constructor);

0 0