javascript 继承实例学习总结

js继承是用来干啥的:

js没有真正的跟其他面向对象的语言一样概念的继承,js里边所说的继承是指模拟继承。
具体js继承是干啥呢,刚开始做前端的时候我是用来面试的(最早写些小效果的时候基本用不到,为啥要看呢,因为面试官很爱问这个问题啊),所以就看看大概的,面试时候能说个大概,在这个问题上算是面试党了。后来跟着慢慢的实际上虽然概念不是很明确也用到一些。
真正是用来干啥的呢,主要是用来复用我们之前写过的代码。比如写过一个功能,一个对象,或者用别人写的东西,我们要自己加点儿什么,总不能改人家的东西吧,直接继承过来用一下就好了,这才是继承的真正用途。

js继承怎么实现:

先不上代码,先说说想法。其实继承呢就是想办法把其他对象(js里边一切皆对象哈)的属性或者方法搞到我们自己的对象上,让我们自己的这个对象可以用。这也就达到复用的目的了。
目的搞明白了,下面就是实现手段了。
根据js的特性,实现无非就是以下几种方法的其中一种或者组合使用。

1、构造函数,js好像没有严格的构造函数的定义,但是可以用new来创建新的对象。构造函数据说也是严格的面向对象的语言实现继承的方法,那么js当然可以模拟一下了,所以学过oop语言的人们会最先想到这个。

2、利用函数原型,利用原型链把两个对象链接起来,因为js原型链是比较独特所以想到这个也是很容易的。
原型也分几种,就是把什么作为继承对象的原型,被继承对象的原型或者被继承对象的实例,或者直接被继承者。这几种作为继承对象的原型得到的继承效果是不一样的。

3、复制属性和方法,把被继承对象的属性或者方法统统复制克隆过来变成我们自己对象的属性和方法就好了啊,我们就可以无比顺畅的用了。当然这个还分浅复制和深度复制两种情况。

4、利用call和apply这两个方法,这两个方法比较神奇,可以改变函数执行的上下文(this),所以利用这两个方法也可以实现对被继承对象的方法的继承复用。

总的来js实现继承的途径大概就是这些,千变万化的实现方法都是从这几种方法的基础上组合升级完善出来的,为毛大多数要组合使用呢,当然是因为使用单个方法实现的效果不太理想啊。当然可以根据自己项目中实际的需求选择使用哪种方式,只要满足自己的需求并没有说必须使用哪种方法去实现。就像说从北京去石家庄,最快当然是飞机啦。但是如果离机场远,算上到机场,从机场去市里,整体算下来还不如高铁快,那就可以做高铁。又比如自己有车可以开车,想要挑战一下还可以骑自行车,这个根据自己的实际情况来选就可以。

代码实现,下面结合代码说说上面的几种实现方法,有些是从其他地方摘来的,加点儿注释啥的。

 

一、构造函数实现(借用构造函数):

function Super(arg){
    this.arr1 = "I'm super "+arg;
    this.show = function(){
        alert(this.arr1);
    }
}

Super.prototype.say = function(){
    alert(this.arr1);
}

function suber(arg){
    Super.apply(this, arguments); //在suber的上下文中运行super
}

var sub =new suber("suber");
var sub2 = new suber("suber1");

console.log(sub.arr1); //I'm super suber
console.log(sub.show); //function (){ alert(this.arr1);}
console.log(sub.say); //undefined
console.log(sub.show === sub2.show); //false

 

哎呀,发现sub.say是undefined,这说明它没有被继承过来啊,下边两个对象sub,sub2的show不相等,说明两个函数指向了两个不同的对象,也就是说被复制了两份出来。

所以这个方法实现继承的话原型对象上的属性和方法没有被继承过来,Super上的属性和方法为每个new出来的对象分别复制一份。
所以单单使用这个方法来实现继承还是不妥啊,因为原型上的方法都没有被继承过来啊。于是大神们就想到了原型继承

 

二、原型继承:

function Super(arg){
    this.arr1 = "I'm super "+arg;
    this.show = function(){
        alert(this.arr1);
    }
}

Super.prototype.say = function(){
    alert(this.arr1);
}

function suber(arg){}

suber.prototype = new Super();

var sub = new suber("suber1");
var sub2 = new suber("suber2");

console.log(sub.arr1); //I'm super undefined
console.log(sub.show);  //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show);   //true;
console.log(sub.say === sub2.say);   //true;

这次是arr1继承过来了,但是参数没有添加进来,是undefined,所以这个方法子类声明时候这个参数传进来付类继承过来的这个属性没法收到。其他的都还算正常。show和say都继承过来了。但是有一点儿需要注意,say是通过super的原型对象继承过来的,而show是新建super对象实例时实例的属性。

那么怎么实现参数传输又能把原型里边的东东继承过来呢,当然上边两种方法组合一下就好了啊,于是前辈们又发明了下面这种方法

 

三、组合继承(借用构造函数并设置原型):

function Super(arg){
    this.arr1 = "I'm super "+arg;
    this.show = function(){
        alert(this.arr1);
    }
}

Super.prototype.say = function(){
    alert(this.arr1);
}

function suber(arg){
    Super.apply(this, arguments);
}

suber.prototype = new Super();

var sub = new suber("suber1");
var sub2 = new suber("suber2");

console.log(sub.arr1); //I'm super suber1
console.log(sub.show);  //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show);   //false;
console.log(sub.say === sub2.say);   //true;

 

这次几乎完美了,但是可以发现sub.show 和sub2.show并不相等啊,这是为啥呢,因为apply这个地方使得show成为了suber的自己的属性,那么就吧suber原型里的show(Super的当做suber原型对象的实例对象的show)给覆盖了,所以又变成每次复制一个,当然这个地方没有办法避免啊。为了不产生这种多余的开消这种可以共用的函数可以多放到原型对象里边。
因为suber的构造里边的调用,和给suber原型对象赋值时候的调用导致Super被调用了两次,那么每次new suber对象时候就调用了两次Super,调用两次就会产生两个实例对象,需要消耗多余的资源了。

于是前辈们为了解决这个问题又开了开脑洞,开发出来下面这种方法。

 

四、寄生组合继承:

该方法跟方法三最主要的不同就是把父类原型赋给了子类原型而不是父类示例,看例子

function Super(arg){
    this.arr1 = "I'm super "+arg;
    
}

Super.prototype.show = function(){  //这个方法放到了原型对象上。
        alert(this.arr1);
    }
Super.prototype.say = function(){
    alert(this.arr1);
}

function suber(arg){
    Super.apply(this, arguments);
}

/*inherit函数的作用,使用一个新的空函数,来切断父类对象的原型对象与子类原型对象的直接联系,而是通过这个空构造的实例对象实现继承,这样可以避免更改子类原型的属性或者方法而影响了父类原型对象的属性或者方法。*/

function inherit(obj){  
    function F(){}
    F.prototype = obj;
    return new F();   
}

suber.prototype = inherit(Super.prototype);

var sub = new suber("suber1");
var sub2 = new suber("suber2");

console.log(sub.arr1); //I'm super suber1
console.log(sub.show);  //function (){ alert(this.arr1);}
console.log(sub.say); //function (){ alert(this.arr1);}
console.log(sub.show === sub2.show);   //true;
console.log(sub.say === sub2.say);   //true;

 

好了,这样就把三方法的弊端干掉了,这个可以完美的使用了吧。

五、复制属性实现

拷贝我们可以写一个拷贝函数来实现。

function extend(Super,suber){
  suber = suber || {};
  for(var i in Super){
    if(Super.hasOwnProperty(i)){
      suber[i] = Super[i];
    }
   }
  return suber;
}

var parent = {
    name:"dad",
    num:[1,2,3],
    say:function(){alert("dad");}
}

var child = {
    age:5,
    sex:"boy"
};

child = extend(parent, child);

//以下测试
console.log(child); /*{
                         age:5,
                         sex:"boy",
                         name:"dad",
                         num:[1,2,3],
                         say:function(){alert("dad");}
                       }*/
console.log(child.say === parent.say); //true
console.log(child.num === parent.num); //true

复制成功,那么child成功继承了parent的一些属性,但是后面两个测试发现他们是相等的,就表明了他们在公用同一个数组,同一个函数,函数这个可以,但是数组这个就有问题了,如果一个chiild改变了数组,几个被继承对象的数组也跟着变了,这就不给力了啊。
为什么会发生这种情况呢,js里边对象存储的是指针,然后这个指针指向这个值,我们在这复制的实际是指向该对象的指针的值,所以继承对象和被继承对象都指向了同一个对象,接下来看看如何使用深度复制来解决这个问题。

深度复制对象属性:

function extenddeep(Super, suber){
    var tostr = Object.prototype.toString, astr = "[object Array]";
    suber = suber || {};

    for(var i in Super){
        if(Super.hasOwnProperty(i)){
            if(typeof Super[i] === "object"){
                suber[i] = (tostr.call(Super[i]) == astr) ? [] : {};
                extenddeep(Super[i],suber[i]);
            }else {
                suber[i] = Super[i];
            }
        }
    }

    return suber;
}

var parent = {
    name:"papa",
    num:[1,2,3],
    say:function(){alert("I'm father of my son!");}
}

var child = {
    name:"jone",
    sex:"boy",
}

var kid = extenddeep(parent, child);

console.log(kid);   // {name: "papa"
                        num: Array[3]
                        say: ()
                        sex: "boy"
                    //  }

console.log(kid.say === parent.say);  //true
console.log(kid.num === parent.num);   //false
console.log(kid.name);  //papa

 

好了,深度复制完毕,但似有木有发现问题,name是parent的,也就是说如果继承对象有和被继承对象一样的属性名的属性如果不做处理就会被替换掉。那么我们可以做一下处理,先声明一个属性,保存parent里的东西,剩下的的当然就是child自己的东西了,最后再把属性给child对象就可以了。

六、利用call和apply这两个方法(借用方法)。

这个就是通过call和apply来复用其他对象的方法,达到复用的目的。

var one = {
    name:"object",
    say: function(greet){
        return greet + ', ' + this.name;
    }
}

var tow = {
    name:"two"
}

one.say.call(tow, "hi");   //hi, two

这个就是借用了,好了,下课。

好吧,好吧,其实这里边还有其他东西要看。可以借用并不“带表”可以随便把某个方法赋值给谁然后跟没发生什么似的继续用。所以我们平时使用借用时要注意一下上下文,下面看下那些容易出错的地方。

//赋值给一个变量时候上下文会变化
var say = one.say;
console.log(say('hoho')); // "hoho, undefined"

//作为回调函数时也会发生变化
var yetother = {
    name:"yetother obj",
    method:function(callback){
        return callback("Hola");
    }
}

console.log(yetother.method(one.say)); //"Hola, "

神马意思呢,就是this.name是undefined,当one.say赋值给say是,实际上是say存储了指向函数对象的指针,say这个变量明显又是全局变量的一个属性,那么它运行的时候实际的上下文就变成了windows了,当然这个时候name就变成undefined了。回调这个也是一样,return 的是函数运行的结果。如果我们事先设置 windows.name="windows" 那么得到的结果就变成了 "hoho, windows" 和"Hola, windows" 了。

 

function bind(o, m){
    return function(){
        return m.apply(o, [].slice.call(arguments));
    }
}

var othersay = bind(yetother, one.say);

othersay("Hola");  //"Hola, yetother obj"

 通过apply可以改变方法执行的上下文,那么我们构建一个函数来实现这样一个功能,通过使用方法调用实现上下文的改变,这样就不会出现上下文不是我们期望的上下文的情况了。

 
//这段是直接复制过来的。
// ECMAScript 5给Function.prototype添加了一个bind()方法,以便很容易使用apply()和call()。

    if (typeof Function.prototype.bind === 'undefined') {
        Function.prototype.bind = function (thisArg) {
            var fn = this,
    slice = Array.prototype.slice,
    args = slice.call(arguments, 1);
            return function () {
                return fn.apply(thisArg, args.concat(slice.call(arguments)));
            };
        };
    }

    var twosay2 = one.say.bind(two);
    console.log(twosay2('Bonjour')); // "Bonjour, another object"

    var twosay3 = one.say.bind(two, 'Enchanté');
    console.log(twosay3()); // "Enchanté, another object"

 

介绍完了,该说说自己的疑惑了,当复制属性方法遇到的被继承对象里边存在方法,如何单独复制出来呢,现在的是直接共用了,因为毕竟方法一般不会经常改动。求解答?

下面是转载过来的jQuery的extend方法,好像也没有特殊处理函数这块,继承完了两个函数也是共用的。

$.extend源码

jQuery.extend = jQuery.fn.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
          target = arguments[0] || {},
          i = 1,
          length = arguments.length,
          deep = false ;

    // Handle a deep copy situation
    //如果第一个参数是boolean类型
    //修正参数,将第二个参数作为target
    if ( typeof target === "boolean" ) {
          deep = target;

           // skip the boolean and the target
          target = arguments[ i ] || {};
           //i++是为了后续 i === length的判断
          i++;
   }

    // Handle case when target is a string or something (possible in deep copy)
    //如果目标既不是对象也不是方法(例如给基本类型扩展属性方法和属性不会报错但是是无用的),修正target为 js对象
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
          target = {};
   }

    // extend jQuery itself if only one argument is passed
    //如果只有一个参数,修正对象为JQuery函数或JQuery对象
    if ( i === length ) {
          target = this ;
           //修正target所在位置,后面的都是要添加给target的对象
          i--;
   }

    for ( ; i < length; i++ ) {
           // Only deal with non-null/undefined values
           if ( (options = arguments[ i ]) != null ) {
                  // Extend the base object
                  for ( name in options ) {
                       src = target[ name ];
                       copy = options[ name ];

                        // Prevent never-ending loop
                        //如果target和copy是同一个对象,略过,防止自己的属性引用了本身对象导致的循环引用,以致GC无法回收
                        if ( target === copy ) {
                               continue ;
                       }

                        // Recurse if we're merging plain objects or arrays
                        //如果是deep为true,并且要添加给target的copy为对象获数组
                        if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                               if ( copyIsArray ) {
                                     copyIsArray = false ;
                                     clone = src && jQuery.isArray(src) ? src : [];

                              } else {
                                     clone = src && jQuery.isPlainObject(src) ? src : {};
                              }
                
                               // Never move original objects, clone them
                               //很巧妙 ,用一个递归,实现引用对象的深克隆,递归的返回条件是属性石基本类型,基本类型都是深克隆
                              target[ name ] = jQuery.extend( deep, clone, copy );

                        // Don't bring in undefined values
                       } else if ( copy !== undefined ) {
                               //浅克隆
                              target[ name ] = copy;
                       }
                 }
          }
   }

    // Return the modified object
    return target;
};

时间: 2024-10-26 14:57:22

javascript 继承实例学习总结的相关文章

理解JavaScript继承

对于JavaScript的继承和原型链,虽然之前自己看了书也听了session,但还是一直觉得云里雾里,不禁感叹JavaScript真是一门神奇的语言.这次经过Sponsor的一对一辅导和自己回来后反复思考,总算觉得把其中的精妙领悟一二了. 1. JavaScript创建对象 在面向对象语言中,通常通过定义类然后再进行实例化来创建多个具有相同属性和方法的对象.但是在JavaScript中并没有类的概念,不过 ECMAScript中的构造函数可以用来创建特定类型的对象.因此,在JavaScript

Javascript教程:重温Javascript继承机制

文章简介:重温Javascript继承机制. 上段时间,团队内部有过好几次几次给力的分享,这里对西风师傅分享的继承机制稍作整理一下,适当加了写口语化的描述,留作备案. 一.讲个故事吧 澄清在先,Java 和Javascript是雷锋和雷峰塔的关系.Javascript原名Mocha,当时还叫做LiveScript,创造者是Brendan Eich,现任Mozilla公司首席技术官. 1994年,历史上第一个比较成熟的网络浏览器--Navigator0.9版诞生在网景公司(Netscape),极为

Javascript继承(下) 为何要继承

Quiz1 Javascript真的需要类(Class)么? 我们首先先看下其他有类(Class)的面向对象语言(如: Java)的一些特性. 父类与子类 父类(Superclass)和子类(Subclass),并不是为了解决父亲与儿子的 问题,而是为了解决类的包含关系的,我们用Sub表示"子类",用Sup表示"父类",则有: Sub Sup 这是有区别的,例如通常我们能够将子类当成父类来使用,但认人的时候我们并不能把儿子当成父亲. 或者可以这 么说,父类和子类不是

javascript继承机制

  澄清在先,Java 和Javascript是雷锋和雷峰塔的关系.Javascript原名Mocha,当时还叫做LiveScript,创造者是Brendan Eich,现任Mozilla公司首席技术官. 1994年,历史上第一个比较成熟的网络浏览器--Navigator0.9版诞生在网景公司(Netscape),极为轰动. 但是,Navigator0.9只能用来浏览,不具备与访问者交互的能力,比如,用户提交一个数据表单,如果表单为空,浏览器是无法判断的,只能直接提交给服务器端,再把空值的错误返

Cocos2d-JS中JavaScript继承

JavaScript语言本身没有提供类,没有其它语言的类继承机制,它的继承是通过对象的原型实现的,但这不能满足Cocos2d-JS引擎的要求.由于Cocos2d-JS引擎是从Cocos2d-x演变而来的,在Cocos2d-JS的早期版本Cocos2d-HTML中几乎全部的API都是模拟Cocos2d-x API而设计的,Cocos2d-x本身是有C++编写的,其中的很多对象和函数比较复杂,JavaScript语言描述起来有些力不从心了.在开源社区中John Resiq在他的博客(http://e

javascript继承—继承的实现原理(1)

原文:javascript继承-继承的实现原理(1) 打算针对js的继承写一系列文章,详细的分析js里继承原理,实现方式,各种继承方式的优缺点,以及最优继承方案,还有多继承的问题等-. 面向对象的编程的核心是封装.继承和多态,js可以看作是一种面向对象的语言,而面向对象的扩展性最核心的部分是多态,多态的必要条件有三个,首先就是继承,其次父类的引用指向子类,最后是方法重写.对于js来说,由于其创建对象的方式多种多样,因此,需要对父类的多种属性和方法实现很好的继承,就必须找到一个比较完善的方法.本篇

JavaScript 继承详解 第一篇_javascript技巧

面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++.C#.Java)的开发经验. 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则是类的一个具体实现. 我们还知道,面向对象编程有三个重要的概念 - 封装.继承和多态. 但是在JavaScript的世界中,所有的这一切特性似乎都不存在. 因为JavaScript本身不是面向对象的语言,而是基于对象的语言. 这里面就有一些有趣的特性,比如JavaScript中所有事物都是对象, 包

JavaScript 继承使用分析_js面向对象

深入学习javascript继承之前,先了解下面的几个概念: 父类:被继承的类 子类:由继承得来的类 超类:也就是父类 抽象类:一般不用来实例化的类,它的用途是用来给其他类继承. 基类:提供给其他类可以继承的类 派生类:由基类继承而来的类 javascript对象继承通常有下面的5种方式: 1.对象冒充 2.call()方式 3.apply()方式 4.原型链 5.混合方式 A.对象冒充 所谓对象冒充,就是新的类冒充旧的类(旧的类必须采用构造函数方式),从而达到继承目的. eg.1 复制代码 代

javascript 继承学习心得总结_基础知识

看了不少js继承的东西也该总结总结了. 先说一下大概的理解,有不对的还望指正,也好更正一下三观.另外说明下,下面的例子并非原创基本就是改了个变量名啥的,有的甚至直接拿过来用的. js继承是用来干啥的: 首先说js没有真正的跟其他面向对象的语言一样概念的继承,js里边所说的继承是指模拟继承. 具体js继承是干啥呢,刚开始做前端的时候我是用来面试的(最早写些小效果的时候基本用不到,为啥要看呢,因为面试官很爱问这个问题啊),所以就看看大概的,面试时候能说个大概,在这个问题上算是面试党了.后来跟着慢慢的