zoukankan      html  css  js  c++  java
  • JS面向对象与原型

    创建对象

    var box = new Object();//创建对象
    box.name = 'Lee';      //添加属性
    box.age = 100;
    box.run = function(){
        return this.name + this.age + "运行中";  //this 表示当前作用域下对象
    }
    
    // this 表示new Object()实例出来的那个对象
    alert(box.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('Lee',20);
    var box2 = createObject('Jack',30);
    console.log(box1.run());
    console.log(box2.run());

    工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法搞清他们到底是哪个对象的实例。

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

    构造函数

    ECAMScript中采用构造函数(构造方法)可用来创建特定的对象。类似于Object对象

    //构造函数
    function Box(name,age){
        this.name = name;
        this.age = age;
        this.run = function(){
            return this.name + this.age +"运行中...";
        };
    };
    
    var box1 = new Box('Lee',100);
    var box2 = new Box('Jack',200);
    
    console.log(box1.run());
    console.log(box2.run());

    使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,但问题是,这里并没有new Object(),为什么可以实例化Box(),这个是哪里来的呢?

    使用了构造函数的方法,和使用工厂模式的方法他们不同之处如下:

    • 1.构造函数方法没有显示的创建对象(new Objectt()),但它在后台自动var obj = new Object();
    • 2.直接将属性和方法赋值给this对象,this就相当于obj;
    • 3.没有return语句,不需要返回对象引用,它是在后台自动返回的。
    //构造函数
    function Box(name,age){
        this.name = name;
        this.age = age;
        this.run = function(){
            return this.name + this.age +"运行中...";
        };
    };
    
    function Dack(name,age){
        this.name = name;
        this.age = age;
        this.run = function(){
            return this.name + this.age +"运行中...";
        };
    };
    
    var box1 = new Box('Lee',100);
    var box2 = new Box('Jack',200);
    var box3 = new Dack('MrLee',300);
    
    console.log(box1.run());
    console.log(box2.run());
    console.log(box3.run());
    
    //解决了对象识别问题
    console.log(box1 instanceof Box); //true
    console.log(box2 instanceof Box); //true
    console.log(box3 instanceof Box); //false
    console.log(box3 instanceof Dack);//true

    对象冒充:使用call()方法

    var o= new Object();
    Box.call(o,'Lee',100);
    console.log(o.run());

    看下一个问题:

     var box1 = new Box('Lee',100); //实例化后地址为1
     var box2 = new Box('Lee',100); //实例化后地址为2
    
     console.log(box1.name == box2.name); //true
     console.log(box1.age == box2.age);      //true
     console.log(box1.run() == box2.run());//true //构造函数体内的方法的值是相当的
     console.log(box1.run == box2.run); //false //因为他们比较的是引用地址

    上面的代码运行说明引用地址不一样,那么构造函数内的方法也可以这样写:

    this.run = new Function("return this.name + this.age +'运行'") 

    如何让他们的引用地址一样,下面代码:

    function Box(name,age){
        this.name = name;
        this.age = age;
        this.run = run;
    };
    
    function run(){
        return this.name +this.age+"运行中...";
    }
     var box1 = new Box('Lee',100); //实例化后地址为1
     var box2 = new Box('Lee',100); //实例化后地址为2
     console.log(box1.run == box2.run); //true //因为他们比较的是引用地址  

    把构造函数内部的方法通过全局来实现引用地址一致。
    虽然使用了全局函数run()来解决了保证引用地址一致的问题,但是这种方式又带来了一个新的问题,全局中的this在对象调用的时候是Box本身,而当普通函数调用的时候,this又代表window

    原型

    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();
    console.log(box1.run == box2.run); //true
    console.log(box1.prototype);//这个属性是一个对象,访问不到
    console.log(box1.__proto__);//这个属性是一个指针指向prototype原型对象。 

    如果是实例方法,不同的实例化,他们的方法地址是不一样的,是唯一的。
    如果是原型方法,那么他们的地址是共享的,大家都一样。

    console.log(box1.constructor); //构造属性,可以获取构造函数 

    PS:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌及其他某些浏览器能识别。虽然可以输出,但是无法获取内部信息。

    判断一个对象是否指向该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。

    console.log(Box.prototype.isPrototypeOf(box1)); //true //只要实例化对象,即都会指向

    原型模式的执行流程:

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

    如何判断属性时构造函数的实例里,还是原型里?可以使用hasOwnProperty()函数来验证:

    console.log(box1.hasOwnProperty('name'));//如果实例里有返回true,否则返回false  

    如何判断属性是原型里的?

    function Box(){
           
    } 
    
    Box.prototype.name="Lee"; //原型属性
    Box.prototype.age=100;
    Box.prototype.run=function(){ //原型方法
        return this.name+this.age+"运行中...";
    }
    
    function isProperty(object,property){
        return !object.hasOwnProperty(property) && (property in object);
    }
    var box1=new Box();
    console.log(isProperty(box1,'name'));

    为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式

    使用字面量的方式创建原型对象,这里的{}就是对象,是objectnew Object就相当于{}

    function Box(){} 
    
    Box.prototype={
        name:'Lee',
        age:100,
        run:function(){
            return this.name+this.age+"运行中...";
        }
    }
    
    var box = new Box();
    console.log(box.constructor == Box); //false 

    字面量创建的方式使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式则相反。
    这里的Box.prototype={}就相当于创建了一个新的对象,所以 box.constructor是Object
    如何让box.constructor指向Box呢?

    function Box(){} 
    
    Box.prototype={
        constructor:Box,//直接强制指向即可
        name:'Lee',
        age:100,
        run:function(){
            return this.name+this.age+"运行中...";
        }
    }
    
    var box = new Box();
    console.log(box.constructor == Box); //true

    重写原型,不会保留之前原型的任何信息,把原来的原型对象和构造函数对象的实例切断了。

    function Box(){} 
    
    Box.prototype={
        constructor:Box,
        name:'Lee',
        age:100,
        run:function(){
            return this.name+this.age+"运行中...";
        }
    }
    
    //重写原型
    Box.prototype={
        age:200
    }
    var box = new Box();
    console.log(box.name); //undefined

    查看sort是否是Array原型对象里的方法

    alert(Array.prototype.sort);

    在如下 判断String原型对象里是否有substring方法

    alert(String.prototype.substring);

    String 添加addstring方法:

    String.prototype.addstring=function(){
        return this+',被添加了!';
    }
    var box="Lee";
    console.log(box.addstring());

    注:原型模式创建对象也有自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。

    原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题:

    function Box(){}
    
    Box.prototype={
        constructor:Box,
        name:'Lee',
        age:100,
        family:['哥哥','姐姐','妹妹'],
        run:function(){
            return this.name+this.age+"运行中...";
        }
    };
    
    var box1 = new Box();
    console.log(box1.family); //'哥哥','姐姐','妹妹'
    box1.family.push("弟弟");
    console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'
    
    var box2 = new Box();
    console.log(box2.family);//'哥哥','姐姐','妹妹','弟弟'

    从上面代码可以看出,在第一个实例修改后引用类型,保持了共享。box2.family共享了box1添加后的引用类型的原型。

    为了解决构造传参和共享问题,可以组合构造函数+原型模式:

    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('Lee',100);
    console.log(box1.family); //'哥哥','姐姐','妹妹'
    box1.family.push("弟弟");
    console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'
    
    
    
    var box2 = new Box('Jack',200);
    console.log(box2.family); //'哥哥','姐姐','妹妹' //引用类型没有使用原型,所以没有共享

    动态原型模式

    //把原型封装到构造函数里
    function Box(name,age){
        this.name=name;
        this.age=age;
        this.family=['哥哥','姐姐','妹妹'];
    
        console.log('原型初始化开始');  //执行了两次
        Box.prototype.run=function(){
            return this.name+this.age+"运行中...";
        }
        console.log('原型初始化结束'); //执行了两次
    }
    
    //原型的初始化,只要第一次初始化就可以了,没必要每次构造函数实例化的时候都初始化
    var box1 = new Box('Lee',100);
    var box2 = new Box('Jack',200);

    为了只让第一次初始化,那么就判断

    function Box(name,age){
        this.name=name;
        this.age=age;
        this.family=['哥哥','姐姐','妹妹'];
    
        if(typeof this.run!='function'){
            console.log('原型初始化开始'); //执行了一次次
            Box.prototype.run=function(){
                return this.name+this.age+"运行中...";
            };
            console.log('原型初始化结束'); //执行了一次
        }
    }
    
    //原型的初始化,只要第一次初始化就可以了,没必要每次构造函数实例化的时候都初始化
    var box1 = new Box('Lee',100);
    var box2 = new Box('Jack',200);

    寄生构造函数
    如果以上都不能满足需要,可以使用一下寄生构造函数。
    寄生构造函数=工厂模式+构造函数

    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('Lee',100);
    var box2 = new Box('Jack',200);

    稳妥构造函数

    在一些安全的环境中,比如禁止使用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('Lee',100);
    var box2 = Box('Jack',200);

    继承

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

    function Box(){
        this.name="Lee";
    }
    
    function Jack(){
        this.age=100;
    }
    
    Jack.prototype = new Box();
    
    var jack = new Jack();
    console.log(jack.name); //Lee

    为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者成为对象冒充(伪造对象、经典继承)的技术解决这两个问题。

    function Box(name){
        this.name=name;
    }
    
    Box.prototype.age=200;
    
    function Jack(name){
        Box.call(this,name);
    }
    
    var jack = new Jack('Lee');
    
    console.log(jack.name);//Lee
    console.log(jack.age);//undefined   

    但是上面的代码可以看出,对象冒充没有继承原型链上的age属性。所以要继承Box的原型,就出现下面的组合继承。
    组合继承即是原型链+借用构造函数的模式

    function Box(name){
        this.name=name;
    }
    
    Box.prototype.age=200;
    
    function Jack(name){
        Box.call(this,name);
    }
    
    Jack.prototype = new Box();
    
    var jack = new Jack('Lee');
    
    console.log(jack.name);//Lee
    console.log(jack.age);//200

    原型式继承

    //临时中转函数
    function obj(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
    
    //这是字面量的声明方式,相当于var box = new Box();
    var box={
        name:'Lee',
        age:100,
        family:['哥哥','姐姐','妹妹']
    };
    
    var box1 = obj(box);
    console.log(box1.family);//'哥哥','姐姐','妹妹'
    box1.family.push('弟弟');
    console.log(box1.family);//'哥哥','姐姐','妹妹','弟弟'
    
    var box2 = obj(box);
    console.log(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+"方法";
        }
        return f;
    }
    
    
    //这是字面量的声明方式,相当于var box = new Box();
    var box={
        name:'Lee',
        age:100,
        family:['哥哥','姐姐','妹妹']
    };
    
    var box1 = create(box);
    console.log(box1.run());

    寄生组合继承

    //临时中转函数
    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('Lee',100);
    console.log(desk.run());
  • 相关阅读:
    作业要求 20200924-5 四则运算试题生成,结对
    作业要求 20200924-1 每周例行报告
    作业要求20200924-3 单元测试,结对
    作业要求20200924-4 代码规范,结对要求
    20200910-1每周例行报告
    20200910-博客作业
    通读《构建之法》
    20200910-3 命令行和控制台编程
    20200924-2 功能测试
    20200924-5 四则运算试题生成,结对
  • 原文地址:https://www.cnblogs.com/moqiutao/p/5263433.html
Copyright © 2011-2022 走看看