zoukankan      html  css  js  c++  java
  • 13.面向对象与继承

    面向对象与原型

    学习要点:
    1.学习条件
    2.创建对象
    3.原型
    4.继承

    ECMAScript有两种开发模式:1.函数式(过程化) 2.面向对象(OOP)。面向对象的语言有一个标志,那就是
    类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。但是ECMASxript没有类的概念,因此它的
    对象也与基于类的语言中的对象有所不同。

    一、学习条件

    在JavaScript视频课程第一节课,就已经声明,Javascript课程需要大量的基础。这里我们详细探讨一下:

    1.xhtml基础:Javascript方方面面需要用到。
    2.和代码基础:比如XHTML,ASP,PHP课程中的项目都有JS扣代码的过程。
    3.面向对象基础:JS的面向对象是非正统且怪异的,必须有正统面向对象基础。
    4.以上三大基础,必须是基于项目中掌握的基础,只是学习基础知识不够牢固,必须在项目中掌握上面的基础即可。

    以上基础可以推荐的教程:xhtml、asp、php也可以选择市面上比较优秀的java教程,java教程都是面向对象的。

    二、创建对象
    创建一个对象,然后给这个对象新创建属性和方法,
    var box = new Object(); //创建一个Object对象
    box.name = 'Lee'; //创建一个name属性并赋值
    box.age = 23; //创建一个age属性并赋值
    box.run = function(){ //创建一个run()方法并返回值
    return this.name + this.age + '运动中...'; //this表示当前作用域下的对象,就是new Object()实例化出来的那个对象
    };
    //this要放在一个作用域下,比如box.run(){},这个是box作用域下的方法,方可用this来表示box本身
    alert(box.run()); //输出属性和方法的值

    上面创建了一个对象,并且创建属性和方法,在num()方法里的this,就是代表box对象本身。这种是Javascript
    创建对象最基本的方法,但有个缺点,想创建一个类似的对象,就会产生大量的代码。

    var box2 = box; //得到box的引用
    box2.name = 'journey'; //直接改变了name属性

    alert(box2.run()); //用box.run()发现name也改变了,因为它们指向的是同一对象

    var box2 = new Object();
    box2.name = 'journey';
    box2.age = 200;
    box2.run = function(){
    return this.name + this.age + '运行中...';
    };
    alert(box2.run()); //这样才能避免和box混肴,从而保持独立


    为了解决多个类似对象声明的问题,我们可以使用一种叫做工厂模式的方法,这种方法就是为了解决实例化对象
    产生大量重复的问题。

    /*工厂模式*/
    function createObject(name , age){ //集中实例化的函数
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.run = function(){
    return obj.name + obj.age + '运行中...';
    };
    return obj; //要返回对象引用
    }
    var box1 = createObject('Lee' , 100); //第一个实例
    var box2 = createObject('journey' , 200); //第二个实例
    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('Lee' , 100);
    var box2 = new Box('journey' , 200); //new Box()即可
    alert(box1.run());
    alert(box2.run());
    alert(box1 instanceof Object); //很清晰的识别他从属于Box


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

    使用了构造函数的方法,和使用工厂模式的方法它们不同之处如下:
    1.构造函数方法没有显示的创建对象(new Object),但是后台自动var obj = new Object();
    2.直接将属性和方法赋值给this对象,this代表后台的obj;
    3.没有return语句,不需要返回对象引用,是后台自动返回的。

    构造函数的方法有一些规范:
    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); //全局,代表windows


    构造函数和普通函数的唯一区别,就是它们调用的方式不同。只不过,构造函数也是函数,必须用new运算符来
    调用,否则就是普通函数。

    var box = new Box('Lee' , 100);
    alert(box.run); //构造模式调用

    Box('Lee' , 200); //普通模式调用,无效

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

    探讨构造函数内部的方法(或函数)的问题,首先看下两个实例化后的属性或方法是否相等。
    function Box(name, age){ //创建一个对象
    this.name = name; //添加一个属性,在构造函数体内的属性我们称为实例属性
    this.age = age;
    this.run = function(){ //实例方法
    return this.name + this.age + '运行中...';
    };
    }
    var box1 = new Box('Lee' , 100); //传递一致,实例化后地址为1
    var box2 = new Box('Lee' , 100); //同上,实例化后地址为2

    alert(box1.name == box2.name);
    alert(box1.run == box2.run); //false , 因为它们比较的是引用地址
    alert(box1.run() == box2.run()); //构造函数体内的方法的值是相等的

    可以把构造函数里的方法(或函数)用new Function()方法来代替,得到一样的效果,更加证明,它们最终判断
    的是引用地址,唯一性。
    function Box(name ,age){
    this.name = name;
    this.age = age;
    this.run = new Function("return this.name + this.age + '运行中...'");//说明是引用类型
    }

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

    function Box(name , age){
    this.name = name;
    this.age = age;
    this.run = run; //这边的run不能加( )因为是要整体的赋给,而不是里面值
    }
    function run(){ //通过外面调用,保证引用地址一致
    return this.name + this.age + '运行中...';
    }

    虽然使用了全局的函数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.name);
    alert(box1.run());

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

    alert(box.1run == box2.run); //true

    为了进一步了解构造函数的声明方式和原型模式的声明方式,我们可以通过图示来了解下:

    构造函数方式
    box1 ——》Box
    name Lee
    age 100
    run Function

    box2——》Box
    name Jack
    age 200
    run Function


    原型模式方式

    box1 ——》Box
    __proto__ ————————————|
    Box prototype |
    box2——》Box constructor <——|
    __proto__ ——| name Lee |
    | age 100 |
    | run Function |
    |_______________________|


    在原型模式声明中,多了两个属性,这两个属性都是创建对象时自动生成的。__proto__属性是实例指向原型对象
    的一个指针,它的作用就是指向构造函数的原型属性constructor。通过这两个属性,就可以访问到原型里的属
    性和方法了。
    alert(box1.constructor); //返回构造函数本身,构造属性,可以获取构造函数本身作用是被原型指针定位,然后得到构造函数
    本身其实就是对象实例对应原型对象的作用

    PS:IE浏览器在脚本访问__proto__会不能识别,火狐和谷歌浏览器及其他浏览器均能识别,虽然可以输出,但无
    法获取内部信息。
    alert(box1.__proto__); //这个属性是一个指针指向prototype原型对象
    alert(box1.prototype); //这个属性是一个对象,访问不到


    判断一个对象是否指向了该构造函数的原型对象,可以使用isPrototypeOf()方法来测试。
    alert(Box.prototype.isPrototypeOf(box)); //只要实例化对象,即都会指向
    再比如:
    var obj = new Object();
    alert(Object.prototype.isPrototypeOf(obj)); //返回true,只要实例化了即都会指向

    function Box(){} //声明一个构造函数,函数体内什么都没有,如果有叫做实例属性,实例方法

    Box.prototype.name = 'Lee'; //在原型里添加属性,这个叫做原型属性
    Box.prototype.age = 100;
    Box.prototype.run = function (){ //在原型里添加方法,这个是原型方法
    return this.name + this.age + '运行中...';
    };

    比较一下原型内的方法地址是否一致;
    var box1 = new Box();
    box1.name = 'journey';
    alert(box1.name); //打印出来的是journey,就近原则

    var box2 = new Box();


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

    虽说我们可以通过对象实例访问保存在原型中的值,但却不能访问通过对象实例重写原型中的值。
    var box1 = new Box();
    alert(box1.name); //Lee,原型里的值
    box1.name = 'journey'; //实例属性,并没有重写原型属性
    alert(box1.name); //journey,就近原则

    var box2 = new Box(); //实例属性是不会共享的,所以box2访问不到实例属性,就只能返回Lee
    alert(box2.name); //Lee,原型里的值,没有被box1修改

    如果向要box1也能在后面继续访问到原型里的值,可以把构造函数里的属性删除即可。

    具体如下:
    function Box(){} //声明一个构造函数,函数体内什么都没有,如果有叫做实例属性,实例方法

    Box.prototype.name = 'Lee'; //在原型里添加属性,这个叫做原型属性
    Box.prototype.age = 100;
    Box.prototype.run = function (){ //在原型里添加方法,这个是原型方法
    return this.name + this.age + '运行中...';
    };
    var box1 = new Box();
    box1.name = 'journey';
    alert(box1.name);
    delete box1.name; //删除实例中的属性
    delete Box.prototype.name; //删除原型中的属性
    alert(box1.name); //打印出原型中的属性

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

    alert(box.hasOwnProperty('name')); //实例里有返回true,否者返回true

    function Box(){}

    Box.prototype.name = 'Lee';
    Box.prototype.age = 100;
    Box.prototype.run = function(){
    return this.name + this.age + '运行中...';
    }
    var box1 = new Box();
    box1.name = 'journey';
    alert(box1.hasOwnProperty('name')); //返回true,hasOwnProperty是用来判断实例中是否存在指定属性

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


    我们可以通过hasOwnProperty()方法检测属性是否存在实例中,也可以通过in来判断实例或原型中是否存在属性。
    那么结合这两种方法,可以判断原型中是否存在属性。


    function isProperty(object , property){ //判断原型中是否存在属性
    return !object.hasOwnProperty(property)&&(property in object);
    }

    var box = new Box();
    alert(isProperty(box , 'name')); //true,如果原型有


    function Box(){}

    var box = new Box();
    alert(box.prototype); //使用对象实例无法访问到prototype
    alert(box.__proto__); //使用对象实例访问prototype的指针
    alert(Box.prototype); //使用构造函数名(对象名)访问prototype

    为了让属性和方法更好的体现封装的效果,并且减少不必要的输入,原型的创建可以使用字面量的方式:
    function Person(){};
    Person.prototype={ //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{}
    name : 'journey',
    age : 100,
    run : function(){
    return this.name + this.age + '运行中...';
    }
    };

    function Box(){};
    Box.prototype={ //使用字面量的方式创建原型对象,这里{}就是对象,是Object,new Object就相当于{}
    name : 'journey',
    age : 200,
    run : function(){
    return this.name + this.age + '运行中...';
    }
    };
    使用构造函数创建原型对象和使用字面量创建对象在使用上基本相同,但还是有一些区别,字面量创建的方式
    使用constructor属性不会指向实例,而会指向Object,构造函数创建的方式相反。

    var box = new Box();
    alert(box instanceof Box);
    alert(box instanceof Object);
    alert(box constructor == Box); //字面量方式,返回false,否则,true
    alert(box constructor == Object); //字面量方式,返回true,否则,false

    如果想让字面量方式的constructor指向实例对象,那么可以这么做:

    Box.prototype = {
    constructor : Box, //直接强制指向即可
    };

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

    原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。

    function Box(){};

    Box.prototype = { //原型被重写了
    constructor : Box, //将原型对象强制指向Box
    name : 'Lee',
    age : 100,
    fun : function(){
    return this.name + this.age + '运行中...';
    }
    }

    //重写了原型对象

    Box.prototype={
    age : 200 //这里不会保留之前原型的任何信息了。
    //把原来的原型对象和构造函数对象实例之前的关系切断了,也就是原型对象消失了
    }
    var box = new Box();
    alert(box.name);

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

    var box = [1,3,4,6,7,5];
    alert(box.sort());
    //查看sort是否是Array原型对象里的方法
    alert(Array.prototype.sort); //返回值,sort就是Array类型的原型方法
    alert(String.prototype.substring); //substring就是String类型的原型方法

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

    PS:尽管给原生的内置引用类型添加方法使用起来特别方便,但我们不推荐使用这种方法,因为它可能会导致
    命名冲突,不利于代码维护。

    原型模式创建对象也是自己的缺点,它省略了构造函数传参初始化这一过程,带来的缺点就是初始化的值都是
    一致的。而原型最大的缺点,那就是共享。
    原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性
    包含引用类型,就存在一定的问题:

    function Box(){};
    Box.prototype= {
    constructor : Box,
    name : 'journey',
    age : 100,
    family : ['父亲' , '母亲' , '姐姐'], //添加一个数组属性
    run : function(){
    return this.name + this.age + this.family;
    }
    };
    var box1 = new Box();
    alert(box1.family);
    box1.family.push('弟弟'); //是在实例中添加的,原型中不好添加
    alert(box1.family);
    var box2 = new Box();
    alert(box2.family); //共享了box1添加后的引用类型的原型

    PS:数据共享的缘故,导致很多开发者放弃使用原型,因为每次实例化的数据需要保留自己的特性,而不能共享。

    为了解决构造传参和共享问题,可以组合构造函数+原型模式:
    function Box(name,age){ //不共享的保持独立的使用构造函数
    this.name = name;
    this.age = age;
    this.family = ['父亲' , '母亲' , '姐姐'];
    };
    Box.prototype = { //共享的使用原型模式
    constructor : Box, //强制指向Box
    run : function (){
    return this.name + this.age + this.family;
    }
    };
    var box1 = new Box('Lee' , 100);
    alert(box1.run());
    var box2 = new Box('journey' , 200);
    alert(box2.run());
    box1.family.push('弟弟');
    alert(box1.family);
    alert(box2.family); //引用类型没有使用原型所以没有共享

    PS:这种混合模式很好的解决了传参和引用共享的大难题。是创建对象比较好的方法。

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

    function Box(name , age){ //将所有信息封装到函数体内
    this.name = name,
    this.age = age,
    if(typeof this.run !='function'){ //判断this.run是否存在。没有这个判断,下面的原型就会初始化两次完全没有必要
    Box.prototype.run = function(){
    return this.name + this.age + '运行中...';
    };
    }
    }
    var box1 = new Box('Lee' , 100);
    alert(box.run());
    //原型的初始化,只要第一次初始化,就可以了,没有必要每次构造函数实例化的时候都初始化


    PS:使用动态原型模式,要注意一点,不可以再使用字面量的方式重写原型,因为会切断实例和新原型之间的联
    系。

    以上讲解了各种方式对象创建的方法,如果这几种方式都不能满足需求,可以使用一开始那种模式:寄生构造函数。
    function Box(name ,age){ //Box是构造函数名
    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);
    alert(box1.run());

    var box2 = new Box('journey' , 200);
    alert(box2.run());

    寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使
    用之前所说的模式时,不建议使用此模式。
    在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议
    直接String.prototype.addstring,可以通过寄生构造的方式添加。

    function myString(string){
    var str = new String(string);
    str.addstring = function(){
    return this + ',被添加了!';
    }
    return str;
    }
    var box = new myString('Lee'); //比直接在引用类型添加要繁琐好多
    alert(box.addstring());


    在一些安全的环境中,比如禁止使用this和new,这里的this是构造函数里不使用this,这里的new是在外部实例
    化构造函数时不使用new。这种创建方式叫做稳妥构造函数。
    function Box(name ,age){
    var obj = new Object();
    obj.run = function(){
    return name + age + '运行中...'; //直接打印参数即可
    };
    return obj;
    }
    var box = Box('Lee' , 100); //直接调用函数
    alert(box.run());


    PS:稳妥构造函数和寄生类似。


    四、继承
    继承有1.原型链继承 2.借用构造函数继承(对象冒充继承) 3.组合继承(结合前两种) 4.原型式继承
    5.寄生式继承(原型式+工厂模式) 6.寄生组合继承(临时中转函数+寄生函数)

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

    function Box(){ //Box构造,被继承的函数叫做超类型(父类,基类)
    this.name = 'Lee';
    }

    function Desk(){ //Desk构造,继承的函数叫做子类(子类,派生类)
    this.age = 100;
    }


    Desk.prototype = new Box(); //通过原型链继承,超类型实例化后的对象实例,赋值给子类型的原型属性即可
    //new Box()会将Box构造里的信息和原型里的信息都交给Desk
    //Desk的原型,得到的是Box的构造+原型里的信息
    var desk = new Desk();
    alert(desk.age);
    alert(desk.name); //得到被继承的属性

    function Table(){ //Table构造
    this.level = 'AAAAA';
    }

    Table.prototype= new Desk();

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

    var box = new Box();
    alert(box.constructor); //得到原型构造


    如果实例化table,那么Desk实例中有age = 100, 原型中增加相同的属性 age = 200,最后结果是多少呢?

    Desk.prototype.age = 200, //实例和原型中均含age

    PS:以上原型链继承还缺少一环,那就是Object,所有的构造函数都继承自Object,而继承Object是自动完成
    的,并不需要程序员自己手动继承。

    经过继承后的实例,它们的从属关系会怎么样呢?
    //子类型从属于自己或者它的超类型
    alert(table instanceof Object); //true
    alert(table instanceof Table); //false,desk是table的超类
    alert(table instanceof Desk); //true
    alert(table instanceof Box); //true

    在Javascript里,被继承的函数称为超类型(父类,基类也行,其他语言的叫法),被继承的函数称为子类型(
    子类,派生类)。继承也有之前问题,比如字面量重写原型会中断关系,使用引用类型的原型,并且子类型还
    无法给超类型传递参数。
    为了解决引用共享和超类型无法传参的问题,我们采用一种叫借用构造函数的技术,或者称为对象冒充(伪造
    对象、经典继承)的技术来解决这两种问题。
    function Box(name,age){
    this.name = name;
    this.age = age;
    this.family = ['爸爸','妈妈','姐姐']; //引用类型放在构造里就不会被共享
    }
    Box.prototype.family='家庭'; //对象冒充只能继承构造里的,原型里的继承不了

    function Desk(name,age){
    Box.call(this,name,age); //对象冒充,给超类型传参,其中的this就代表的是Desk本身
    }

    var desk = new Desk('journey' , 200);
    alert(desk.name);
    alert(desk.family);


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

    function Box(age){
    this.name = ['Lee' , 'Jack' , 'Hello'];
    this.age;
    }
    //构造函数里的方法,放在构造里,每次实例化,都会分配一个内存地址,浪费,所以最好放在原型里,保证多次实例化只有一个地址
    Box.prototype.run = function(){
    return this.name + this.age;
    };

    function Desk(age){
    Box.call(this,age); //对象冒充
    }

    Desk.property = new Box(); //原型链继承

    var desk = new Desk(); //添加的新数据,只给desk
    alert(desk.run());

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

    function obj(o){ //传递一个字面量函数,临时中转函数,o表示将要传递进入的一个对象
    function F(){ //创建一个构造函数,F构造是一个临时新建的对象,用来存储传递过来的对象
    F.prototype = o; //把字面量函数赋值给构造函数的原型,也就是将o对象实例赋值给F构造的原型对象
    return new F(); //最终返回出实例化的构造函数,也就是得到传递过来对象的对象实例
    }
    }

    //F.prototype = o其实就相当于Desk.prototype = new Box();

    var box = { //这是字面量的声明方式,相当于var box = new box();
    name : 'journey',
    family : ['哥哥' , '姐姐' ,'弟弟']
    };

    var box1 = obj(box); //传递,box1就等于new F()
    alert(box1.name);

    alert(box1.family);
    box1.family.push('妹妹');
    alert(box1.family);

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

    寄生式继承把原型式+工厂模式结合起来,目的就是为了封装创建对象的过程。
    function create(o){ //封装创建过程
    var f=obj(o);
    f.run = function(){
    return this.arr; //同样会共享引用
    };
    return f;
    }

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

    function Box(name){
    this.name = name;
    this.arr = ['哥哥' , '姐姐' ,'弟弟'];
    }

    Box.prototype.run = function(){
    return this.name;
    };
    function Desk(name,age){
    Box.call(this,name); //第二次调用Box,对象冒充
    this.age = age;
    }

    Desk.prototype = new Box(); //第一次调用Box

    以上代码是之前的组合继承,那么寄生组合继承,解决了两次调用的问题。

    function create(box,desk){
    var f = obj(box.prototype);
    f.constructor = desk;
    desk.prototype = f;
    }

    function Box(name,age){
    this.name = name;
    thsi.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);
    alert(desk.run());
    alert(desk.constructor);

  • 相关阅读:
    为什么puppeteer比selenium好?
    Puppeteer
    js跳出多层循环
    webpack loader- 图片处理
    webpack的loader的原理和实现
    Webpack中Loader的pitch方法
    url-loader和file-loader区别
    Vue中强制组件重新渲染的正确方法
    ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_DISPOSITION :浏览器下载报错
    JSBridge的原理及使用
  • 原文地址:https://www.cnblogs.com/journey-IT/p/5260259.html
Copyright © 2011-2022 走看看