面向对象与原型

  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());

 

posted @ 2018-12-07 16:16  WhiteSpace  阅读(698)  评论(0编辑  收藏  举报