zoukankan      html  css  js  c++  java
  • 【一次面试】再谈javascript中的继承

    前言

    面向对象编程是每次面试必问的知识点,而前端js如何实现继承每次命中率高达80%

    这不,近两天我们面试时候,同事就问道面试者此问题,但是,不论之前自己做的回答,还是面试者的回答,基本都不太令人满意

    很大的原因是多数时候前端并不需要实现继承,就jquery来说也基本上是一码到底,没有实现继承,据我所知,也就prototype与ext实现过继承

    所以继承对前端来说似乎不太适用

    近两年来情况有所变化,SPA的兴起以及前端逻辑的复杂化,让前端代码愈发的多,愈发的重,所以继承慢慢的进入了一些初级一点的前端视野

    所以,好好的了解如何实现继承,继承的几个用法,是非常有意义的,就算只是为面试都是很有用的

    文章只是个人见解,有误请提出,demo未做检测,有误请提出

    实现继承

    当一个函数被创建时,Function构造函数产生的函数会隐式的被赋予一个prototype属性,prototype包含一个constructor对象

    而constructor便是该新函数对象(constructor意义不大,但是可以帮我们找到继承关系)

    每个函数都会有一个prototype属性,该属性指向另一对象,这个对象包含可以由特定类型的所有实例共享的属性和方法

    每次实例化后,实例内部都会包含一个[[prototype]](__proto__)的内部属性,这个属性指向prototype

    ① 我们通过isPrototypeOf来确定某个对象是不是我的原型
    ② hasOwnPrototype 可以检测一个属性是存在实例中还是原型中,该属性不是原型属性才返回true
    var Person = function (name, age) {
        this.name = name;
        this.age = age;
    };
    Person.prototype.getName = function () {
        return this.name;
    };
    var y = new Person('叶小钗', 30);

    通俗一点来说,prototype是一模板,新创建对象就是对他一个拷贝,里面的属性或者方法都会赋值给实例

    这里说是模板赋值其实不太合理,反正由类产生的所有实例的__proto__都会共享一个prototype,这里我做一个例子

    我们在断点情况下是没有name2属性的,但是我们如果在断点下加上这个代码的话,a.name2,就有值了

    Klass.prototype.name2 = '222';

    所以,这里说模板,不如说是指针指向,都是共享一个对象;继承的情况的话就是这样

    (function () {
        var Person = function (name) {
            this.name = name;
        };
        //Person.prototype = {};//这句将影响十分具有constructor属性
        Person.prototype.getName = function () {
            return this.name;
        };
    
        var Student = function (name, sex, id) {
            this.name = name || '无名氏';
            this.sex = sex || '不明';
            this.id = id || '未填'; //学号
        };
        //相当于将其prototype复制了一次,若是包含constructor的话将指向Person
        Student.prototype = new Person();
        Student.prototype.getId = function () {
            return this.id;
        }
        var y = new Person();
        var s = new Student;
        var s1 = y instanceof Person;
        var s2 = s instanceof Student;
        var s3 = s instanceof Person;
        var s4 = Student.prototype.constructor === Person;
        var s5 = Student.constructor === Person;
        var s6 = Student.constructor === Function;
    
        var s = '';
    })();
    View Code

    prototype实现继承

    我们在具体项目中,真正复杂一点的项目可能就会对继承进行封装,让自己更好的使用,我们下面就来看看prototype怎么干的

    PS:我这里做了一点小的修改:

      1 var Class = (function () {
      2   function subclass() { };
      3 
      4   //我们构建一个类可以传两个参数,第一个为需要继承的类,
      5   //如果没有的话就一定会有第二个对象,就是其原型属性以及方法,其中initialize为构造函数的入口
      6   function create() {
      7 
      8     //此处两个属性一个是被继承的类,一个为原型方法
      9     var parent = null;
     10     var properties = $A(arguments);
     11 
     12     if (Object.isFunction(properties[0]))
     13       parent = properties.shift();
     14 
     15     //新建类,这个类最好会被返回,构造函数入口为initialize原型方法
     16     function klass() {
     17       this.initialize.apply(this, arguments);
     18     }
     19 
     20     //扩展klass类的“实例”对象(非原型),为其增加addMethods方法
     21     Object.extend(klass, Class.Methods);
     22 
     23     //为其指定父类,没有就为空
     24     klass.superclass = parent;
     25 
     26     //其子类集合(require情况下不一定准确)
     27     klass.subclasses = [];
     28 
     29     //如果存在父类就需要继承
     30     if (parent) {
     31       //新建一个空类用以继承,其存在的意义是不希望构造函数被执行
     32       //比如 klass.prototype = new parent;就会执行其initialize方法
     33       subclass.prototype = parent.prototype;
     34       klass.prototype = new subclass;
     35       parent.subclasses.push(klass);
     36     }
     37 
     38     //遍历对象(其实此处这样做意义不大,我们可以强制最多给两个参数)
     39     //注意,此处为一个难点,需要谨慎,进入addMethods
     40     for (var i = 0, length = properties.length; i < length; i++)
     41       klass.addMethods(properties[i]);
     42 
     43     if (!klass.prototype.initialize)
     44       klass.prototype.initialize = Prototype.emptyFunction;
     45 
     46     klass.prototype.constructor = klass;
     47     return klass;
     48   }
     49 
     50   /**
     51   由于作者考虑情况比较全面会想到这种情况
     52   var Klass = Class.create(parent,{},{});
     53   后面每一个对象的遍历都会执行这里的方法,我们平时需要将这里直接限定最多两个参数
     54   */
     55   function addMethods(source) {
     56 
     57     //当前类的父类原型链,前面被记录下来了
     58     var ancestor = this.superclass && this.superclass.prototype;
     59 
     60     //将当前对象的键值取出转换为数组
     61     var properties = Object.keys(source);
     62 
     63     //依次遍历各个属性,填充当前类(klass)原型链
     64     for (var i = 0, length = properties.length; i < length; i++) {
     65 
     66       //property为键,value为值,比如getName: function(){}的关系
     67       var property = properties[i], value = source[property];
     68 
     69       /****************
     70       这里有个难点,用于处理子类中具有和父类原型链同名的情况,仍然可以调用父类函数的方案(我这里只能说牛B)
     71       如果一个子类有一个参数叫做$super的话,这里就可以处理了,这里判断一个函数的参数使用了正则表达式,正如
     72       var argslist = /^s*functions*(([^()]*?))s*?{/i.exec(value.toString())[1].replace(/s/i, '').split(',');
     73       ****************/
     74       if (ancestor && Object.isFunction(value) && value.argumentNames()[0] == "$super") {
     75         
     76         //将当前函数存下来
     77         var method = value;
     78         /****************
     79         第一步:
     80 
     81         这里是这段代码最难的地方,需要好好阅读,我们首先将里面一块单独提出
     82         value = (function (m) {
     83         return function () { return ancestor[m].apply(this, arguments); };
     84         })(property)
     85         这里很牛B的构建了一个闭包(将方法名传了进去),任何直接由其父类原型中取出了相关方法
     86         然后内部返回了该函数,此时其实重写了value,value
     87         ***这里***有一个特别需要注意的地方是,此处的apply方法不是固定写在class上的,是根据调用环境变化的,具体各位自己去理解了
     88         
     89         第二步:
     90         首先value被重新成其父类的调用了,此处可以简单理解为(仅仅为理解)$super=value
     91         然后下面会调用wrap操作vaule将,我们本次方法进行操作
     92         wrap: function (wrapper) {
     93           var __method = this;
     94           return function () {
     95             return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
     96           }
     97         }
     98         可以看出,他其实就是将第一个方法(value)作为了自己方法名的第一个参数了,后面的参数不必理会
     99         ****************/
    100         value = (function (m) {
    101           return function () { return ancestor[m].apply(this, arguments); };
    102         })(property).wrap(method);
    103       }
    104       //为其原型赋值
    105       this.prototype[property] = value;
    106     }
    107     return this;
    108   }
    109 
    110   return {
    111     create: create,
    112     Methods: {
    113       addMethods: addMethods
    114     }
    115   };
    116 })();

    下面来一个简单的例子:

     1 var Person = Class.create({
     2   initialize: function (name) {
     3     this.name = name;
     4   },
     5   getName: function () {
     6     console.log('我是父类');
     7     return  this.name;
     8   },
     9   getAge: function () {
    10     return this.age;
    11   }
    12 });
    13 
    14 var Employee = Class.create(Person, {
    15   initialize: function ($super, name, age) {
    16     $super(name);
    17     this.age = age;
    18   },
    19   getName: function ($super) {
    20     return '我是子类:' + $super();
    21   }
    22 });
    23 
    24 var C = Class.create(Employee, {
    25   getAge: function ($super) {
    26     return $super();
    27   }
    28 });
    29 
    30 var y = new C("叶小钗", 25);
    31 console.log(y.getName() + ': ' + y.getAge());
    View Code

    这里,我们根据自己的需求重写写了下继承相关代码,表现基本与上述一致,各位可以自己试试

    PS:当然如果有问题请指出

    简化prototype继承

     1 var arr = [];
     2 var slice = arr.slice;
     3 
     4 function create() {
     5   if (arguments.length == 0 || arguments.length > 2) throw '参数错误';
     6 
     7   var parent = null;
     8   //将参数转换为数组
     9   var properties = slice.call(arguments);
    10 
    11   //如果第一个参数为类(function),那么就将之取出
    12   if (typeof properties[0] === 'function')
    13     parent = properties.shift();
    14   properties = properties[0];
    15 
    16   function klass() {
    17     this.initialize.apply(this, arguments);
    18   }
    19 
    20   klass.superclass = parent;
    21   klass.subclasses = [];
    22 
    23   if (parent) {
    24     var subclass = function () { };
    25     subclass.prototype = parent.prototype;
    26     klass.prototype = new subclass;
    27     parent.subclasses.push(klass);
    28   }
    29 
    30   var ancestor = klass.superclass && klass.superclass.prototype;
    31   for (var k in properties) {
    32     var value = properties[k];
    33 
    34     //满足条件就重写
    35     if (ancestor && typeof value == 'function') {
    36       var argslist = /^s*functions*(([^()]*?))s*?{/i.exec(value.toString())[1].replace(/s/i, '').split(',');
    37       //只有在第一个参数为$super情况下才需要处理(是否具有重复方法需要用户自己决定)
    38       if (argslist[0] === '$super' && ancestor[k]) {
    39         value = (function (methodName, fn) {
    40           return function () {
    41             var scope = this;
    42             var args = [function () {
    43               return ancestor[methodName].apply(scope, arguments);
    44             } ];
    45             return fn.apply(this, args.concat(slice.call(arguments)));
    46           };
    47         })(k, value);
    48       }
    49     }
    50 
    51     klass.prototype[k] = value;
    52   }
    53 
    54   if (!klass.prototype.initialize)
    55     klass.prototype.initialize = function () { };
    56 
    57   klass.prototype.constructor = klass;
    58 
    59   return klass;
    60 }

    如此,我们就完成了自己的继承了

    实战继承

    知道原型可以实现继承是皮毛,知道各个库是怎样干的也只是入门
    因为,要在项目中用到才能算真正掌握继承,这里我们便来点实战的小例子
    这里我写一个简单的view用于下面各种继承

     1 var AbstractView = create({
     2   initialize: function (opts) {
     3     opts = opts || {};
     4     this.wrapper = opts.wrapper || $('body');
     5 
     6     //事件集合
     7     this.events = {};
     8 
     9     this.isCreate = false;
    10 
    11   },
    12   on: function (type, fn) {
    13     if (!this.events[type]) this.events[type] = [];
    14     this.events[type].push(fn);
    15   },
    16   trigger: function (type) {
    17     if (!this.events[type]) return;
    18     for (var i = 0, len = this.events[type].length; i < len; i++) {
    19       this.events[type][i].call(this)
    20     }
    21   },
    22   createHtml: function () {
    23     throw '必须重写';
    24   },
    25   create: function () {
    26     this.root = $(this.createHtml());
    27     this.wrapper.append(this.root);
    28     this.trigger('onCreate');
    29     this.isCreate = true;
    30   },
    31   show: function () {
    32     if (!this.isCreate) this.create();
    33     this.root.show();
    34     this.trigger('onShow');
    35   },
    36   hide: function () {
    37     this.root.hide();
    38   }
    39 });
    40 
    41 var Alert = create(AbstractView, {
    42 
    43   createHtml: function () {
    44     return '<div class="alert">这里是alert框</div>';
    45   }
    46 });
    47 
    48 var AlertTitle = create(Alert, {
    49   initialize: function ($super) {
    50     this.title = '';
    51     $super();
    52 
    53   },
    54   createHtml: function () {
    55     return '<div class="alert"><h2>' + this.title + '</h2>这里是带标题alert框</div>';
    56   },
    57 
    58   setTitle: function (title) {
    59     this.title = title;
    60     this.root.find('h2').html(title)
    61   }
    62 
    63 });
    64 
    65 var AlertTitleButton = create(AlertTitle, {
    66   initialize: function ($super) {
    67     this.title = '';
    68     $super();
    69 
    70     this.on('onShow', function () {
    71       var bt = $('<input type="button" value="点击我" />');
    72       bt.click($.proxy(function () {
    73         alert(this.title);
    74       }, this));
    75       this.root.append(bt)
    76     });
    77   }
    78 });
    79 
    80 var v1 = new Alert();
    81 v1.show();
    82 
    83 var v2 = new AlertTitle();
    84 v2.show();
    85 v2.setTitle('我是标题');
    86 
    87 var v3 = new AlertTitleButton();
    88 v3.show();
    89 v3.setTitle('我是标题和按钮的alert');
    View Code

    http://sandbox.runjs.cn/show/ddlp7nlt

    结语

    希望这次继承的文章对各位有帮助,此外文中错误请指出

    亲爱的道友们,我其实在我们团队只是中等水平,我们上海携程无线是一个优秀的团队,
    如果你现在正在找工作,请加入我们吧!!!
    在我们团队,你可以肆无忌惮的黑自己的老大,你会体会到和谐的氛围,当然妹子多多的!
     
    要求:靠谱前端,吃苦耐劳,待遇刚刚的!!!
    
    需要的朋友可以私信我
    
    顺便推广道友的jquery技术交流群:239147101
  • 相关阅读:
    HDU 4738——Caocao's Bridges——————【求割边/桥的最小权值】
    POJ 3177——Redundant Paths——————【加边形成边双连通图】
    亲历:IT 从业者避免猝死攻略 v1.0
    Linux如何统计进程的CPU利用率
    gcp – 源于CP的高级命令行文件拷贝工具
    丰田栽了的原因,嵌入式软件工程师都该看看
    四件在我步入职业软件开发生涯那天起就该知道的事情
    浅谈自底向上的Shell脚本编程及效率优化
    实用硬件产品集锦
    [置顶] openHAB 体系结构与编程模型 (1) --- 术语
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3530269.html
Copyright © 2011-2022 走看看