javascript 继承学习心得总结


Posted in Javascript onMarch 17, 2016

看了不少js继承的东西也该总结总结了。
先说一下大概的理解,有不对的还望指正,也好更正一下三观。另外说明下,下面的例子并非原创基本就是改了个变量名啥的,有的甚至直接拿过来用的。

js继承是用来干啥的:

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

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

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

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

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

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

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

看了不少js继承的东西也该总结总结了。
先说一下大概的理解,有不对的还望指正,也好更正一下三观。另外说明下,下面的例子并非原创基本就是改了个变量名啥的,有的甚至直接拿过来用的。

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;
      };

以上这篇javascript 继承学习心得总结就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
document.createElement()用法及注意事项(ff下不兼容)
Mar 13 Javascript
JavaScript改变CSS样式的方法汇总
May 07 Javascript
JQuery中ajax方法访问web服务实例
Jul 18 Javascript
jQuery Mobile操作HTML5的常用函数总结
May 17 Javascript
javascript简单实现等比例缩小图片的方法
Jul 27 Javascript
详解Vue 动态添加模板的几种方法
Apr 25 Javascript
socket.io与pm2(cluster)集群搭配的解决方案
Jun 02 Javascript
JavaScript闭包原理与用法实例分析
Aug 10 Javascript
微信小程序使用setData修改数组中单个对象的方法分析
Dec 30 Javascript
Vue实例的对象参数options的几个常用选项详解
Nov 08 Javascript
vue实现路由懒加载的3种方法示例
Sep 01 Javascript
javascript实现简单留言板案例
Feb 09 Javascript
Node.js实用代码段之正确拼接Buffer
Mar 17 #Javascript
Node.js实用代码段之获取Buffer对象字节长度
Mar 17 #Javascript
jquery基础知识第一讲之认识jquery
Mar 17 #Javascript
Angularjs 滚动加载更多数据
Mar 17 #Javascript
基于jquery实现智能提示控件intellSeach.js
Mar 17 #Javascript
JavaScript函数内部属性和函数方法实例详解
Mar 17 #Javascript
基于jquery实现简单的分页控件
Mar 17 #Javascript
You might like
VOLVO车载收音机
2021/03/02 无线电
PHP在网页中动态生成PDF文件详细教程
2014/07/05 PHP
php实现保存周期为1天的购物车类
2017/07/07 PHP
PHP进阶学习之Geo的地图定位算法详解
2019/06/19 PHP
WordPress免插件实现面包屑导航的示例代码
2020/08/20 PHP
js汉字排序问题 支持中英文混排,兼容各浏览器,包括CHROME
2011/12/20 Javascript
jquery获取div宽度的实现思路与代码
2013/01/13 Javascript
使用JavaScript 实现对象 匀速/变速运动的方法
2013/05/08 Javascript
JS下载文件|无刷新下载文件示例代码
2014/04/17 Javascript
JavaScript跨平台的开源框架NativeScript
2015/03/24 Javascript
js实现缓冲运动效果的方法
2015/04/10 Javascript
JS组件Form表单验证神器BootstrapValidator
2016/01/26 Javascript
JavaScript html5 canvas绘制时钟效果
2016/03/01 Javascript
基于javascript实现的快速排序
2016/12/02 Javascript
ES6新特性二:Iterator(遍历器)和for-of循环详解
2017/04/20 Javascript
react-router中的属性详解
2017/06/01 Javascript
ES6中箭头函数的定义与调用方式详解
2017/06/02 Javascript
angular学习之从零搭建一个angular4.0项目
2017/07/10 Javascript
详解jQuery获取特殊属性的值以及设置内容
2018/11/14 jQuery
详解vue高级特性
2020/06/09 Javascript
如何在微信小程序中使用骨架屏的步骤
2020/06/12 Javascript
使用cx_freeze把python打包exe示例
2014/01/24 Python
python常用web框架简单性能测试结果分享(包含django、flask、bottle、tornado)
2014/08/25 Python
Python实现变量数值交换及判断数组是否含有某个元素的方法
2017/09/18 Python
对python xlrd读取datetime类型数据的方法详解
2018/12/26 Python
Python实现使用request模块下载图片demo示例
2019/05/24 Python
利用python开发app实战的方法
2019/07/09 Python
django 做 migrate 时 表已存在的处理方法
2019/08/31 Python
Python 时间戳之获取整点凌晨时间戳的操作方法
2020/01/28 Python
任意一块网页内容实现“活”的背景(目前火狐浏览器专有)
2014/05/07 HTML / CSS
工会工作先进事迹
2014/08/18 职场文书
三严三实对照检查材料
2014/09/22 职场文书
维稳承诺书
2015/01/20 职场文书
农村婚礼司仪主持词
2015/06/29 职场文书
庆祝教师节主题班会
2015/08/17 职场文书
Python爬虫实战之爬取京东商品数据并实实现数据可视化
2021/06/07 Python