• javascript组件开发之基类继承实现


    上一篇文章大概的介绍了一下关于javascript组件的开发方式,这篇文章主要详细记一下基类的编写,这个基类主要是实现继承的功能

    为什么要封装基类?

    由于这次重构项目需要对各种组件进行封装,并且这些组件的实现方式都差不多,为了便于管理,让代码尽量统一,所以到对组件封装一个base基类(javascript没有类的概念,暂且这样叫吧),关于javascript的oo实现:可以参考这篇文章javascript oo实现;写得很赞,膜拜,我改写的这个基于John Resig的实现方式。

    基类的封装方式以及继承有哪些方法?

    这个在各大库的源码中都有对应的实现,比如jq里面$.extend()实现浅拷贝和深拷贝,prototype.js里面的Object.extend的实现,以及Ext.extend等,他们的实现各不相同,在这里简单的看看prototype.js的extend实现方案:

     1 //Prptotype库的extend,为Object类添加静态方法,
     2 Object.extend=function(destination,source) {
     3     for(property in source) {
     4         destination[property]=source[property];
     5     }
     6     return destination;
     7 }
     8 //通过Object类,为每个对象,添加方法
     9 Object.prototype.extend = function(object) {
    10   return Object.extend.apply(this, [this, object]);
    11 }

    第一个函数Object.extend,可以简单的理解为对象复制.目标对象将拥有源对象的所有属性和方法

    第二个函数Object.prototype.extend,在prototype对象实例化对象中加上一个extend函数,

    其中精华语句Object.extend.apply(this, [this, object]); 将Object对象中的extend作为静态方法调用,

    第一个参数this,指向调用对象本身,第二个参数,为一个数组,为调用对象本身和传递进来的对象参数object(一般为方法,下面有例子)

    例子:

     1 //定义父类father
     2 function father(){}
     3 father.prototype={
     4     //各种方法
     5 }
     6 //定义子类son
     7 function son(){}
     8 son.prototype=(new father()).extend({
     9     //新添加方法
    10     ....
    11 })

    上面的方法有一些不好的地方,比如污染Object对象的属性,比如不可以(new father()).extend({},{});不可以扩展多个对象

    下面看jq的实现方式:

     1 jQuery.extend = jQuery.fn.extend = function() {
     2     var options, name, src, copy, copyIsArray, clone,
     3         target = arguments[0] || {},
     4         i = 1,
     5         length = arguments.length,
     6         deep = false;
     7 
     8     // Handle a deep copy situation处理深拷贝的情况
     9     if ( typeof target === "boolean" ) {
    10         deep = target;
    11         target = arguments[1] || {};
    12         // skip the boolean and the target跳过深拷贝boolean参数和目标参数(源对象)
    13         i = 2;
    14     }
    15 
    16     // Handle case when target is a string or something (possible in deep copy)处理目标参数(源对象)不是Object或者function的情况
    17     if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
    18         target = {};
    19     }
    20 
    21     // extend jQuery itself if only one argument is passed如果只有目标参数(源对象)和boolean参数的处理
    22     if ( length === i ) {
    23         target = this;
    24         --i;
    25     }
    26 
    27     for ( ; i < length; i++ ) {
    28         // Only deal with non-null/undefined values处理不为空和不为undefined的值
    29         if ( (options = arguments[ i ]) != null ) {
    30             // Extend the base object开始向源对象扩展
    31             for ( name in options ) {
    32                 src = target[ name ];
    33                 copy = options[ name ];
    34 
    35                 // Prevent never-ending loop如果源对象中map到拷贝对象key的value则跳出此次循环
    36                 if ( target === copy ) {
    37                     continue;
    38                 }
    39 
    40                 // Recurse if we're merging plain objects or arrays 处理copy是对象和数组的情况并且是深拷贝
    41                 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
    42                     if ( copyIsArray ) {
    43                         copyIsArray = false;
    44                         clone = src && jQuery.isArray(src) ? src : [];
    45 
    46                     } else {
    47                         clone = src && jQuery.isPlainObject(src) ? src : {};
    48                     }
    49 
    50                     // Never move original objects, clone them
    51                     target[ name ] = jQuery.extend( deep, clone, copy );
    52 
    53                 // Don't bring in undefined values处理value不是对象和数组的情况
    54                 } else if ( copy !== undefined ) {
    55                     target[ name ] = copy;
    56                 }
    57             }
    58         }
    59     }
    60 
    61     // Return the modified object
    62     return target;
    63 };

    jq的实现就避免了prototype实现的缺点,但是在我们的组件开发中不可能依赖于jq,所以下面根据John Resig的实现方式有了下面的Class超级父类,具体看注释

     1 //javascript简单的实现类和继承
     2 var Class = (function() {
     3   //模拟extend继承方式
     4   var _extend = function() {
     5     //属性混入函数,不混入原型上的属性,原型上的属性就多了哈
     6     var _mixProto = function(base, extend) {
     7       for (var key in extend) {
     8         if (extend.hasOwnProperty(key)) {
     9           base[key] = extend[key];
    10         }
    11       }
    12     };
    13     //这里是一个开关,目的是为了在我们继承的时候不调用父类的init方法渲染,而把渲染放在子类
    14     //试想没这个开关,如果在继承的时候父类有init函数就会直接渲染,而我们要的效果是继承后的子类做渲染工作
    15     this.initializing = true;
    16     //原型赋值
    17     var prototype = new this();
    18     //开关打开
    19     this.initializing = false;
    20     //for循环是为了实现多个继承,例如Base.extend(events,addLog)
    21     for (var i = 0,len = arguments.length; i < len; i++) {
    22       //把需要继承的属性混入到父类原型
    23       _mixProto(prototype, arguments[i].prototype||arguments[i]);
    24     }
    25 
    26     /*也可以这样去循环
    27     var items = [].slice.call(arguments) || [];
    28     var item;
    29     //支持混入多个属性,并且支持{}也支持 Function
    30     while (item = items.shift()) {
    31       _mixProto(prototype, item.prototype || item);
    32     }
    33     */
    34 
    35     //继承后返回的子类
    36     function SonClass() {
    37       //检测开关和init函数的状态,看是否做渲染动作
    38       if (!SonClass.initializing && this.init)
    39         //这里相当于是一个虚函数,--在传统面向对象语言中,抽象类中的虚方法必须先被声明,但可以在其他方法中被调用。而在JavaScript中,虚方法 就可以看作该类中没有定义的方法,但已经通过this指针使用了。和传统面向对象不同的是,这里虚方法不需经过声明,而直接使用了。这些方法将在派生类(子类)中 实现调用返回的子类的init方法渲染,把传给组件的配置参数传给init方法,这里的apply主要是为了传参而非改变this指针
    40         this.init.apply(this, arguments);
    41     }
    42     //把混入之后的属性和方法赋值给子类完成继承
    43     SonClass.prototype = prototype;
    44     //改变constructor引用,不认子类的构造函数将永远是Class超级父类
    45     SonClass.prototype.constructor = SonClass;
    46     //给子类页也添加继承方法,子类也可以继续继承
    47     SonClass.extend = arguments.callee;//也可以是_extend
    48     //返回子类
    49     return SonClass
    50   };
    51   //超级父类
    52   var Class = function() {};
    53   
    54   Class.extend = _extend;
    55   //返回超级父类
    56   return Class
    57   
    58 })();

    附John Resig类的实现:

     1 /* Simple JavaScript Inheritance
     2  * By John Resig http://ejohn.org/
     3  * MIT Licensed.
     4  */
     5 // Inspired by base2 and Prototype
     6 (function(){
     7   //initializing是为了解决我们之前说的继承导致原型有多余参数的问题。当我们直接将父类的实例赋值给子类原型时。是会调用一次父类的构造函数的。所以这边会把真正的构造流程放到init函数里面,通过initializing来表示当前是不是处于构造原型阶段,为true的话就不会调用init。
     8   //fnTest用来匹配代码里面有没有使用super关键字。对于一些浏览器`function(){xyz;}`会生成个字符串,并且会把里面的代码弄出来,有的浏览器就不会。`/xyz/.test(function(){xyz;})`为true代表浏览器支持看到函数的内部代码,所以用`/_super/`来匹配。如果不行,就不管三七二十一。所有的函数都算有super关键字,于是就是个必定匹配的正则。
     9   var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /_super/ : /.*/;
    10  
    11   // The base Class implementation (does nothing)
    12   // 超级父类
    13   this.Class = function(){};
    14  
    15   // Create a new Class that inherits from this class
    16   // 生成一个类,这个类会具有extend方法用于继续继承下去
    17   Class.extend = function(prop) {
    18     //保留当前类,一般是父类的原型
    19     //this指向父类。初次时指向Class超级父类
    20     var _super = this.prototype;
    21    
    22     // Instantiate a base class (but only create the instance,
    23     // don't run the init constructor)
    24     //开关 用来使原型赋值时不调用真正的构成流程
    25     initializing = true;
    26     var prototype = new this();
    27     initializing = false;
    28    
    29     // Copy the properties over onto the new prototype
    30     for (var name in prop) {
    31       // Check if we're overwriting an existing function
    32       //这边其实就是很简单的将prop的属性混入到子类的原型上。如果是函数我们就要做一些特殊处理
    33       prototype[name] = typeof prop[name] == "function" &&
    34         typeof _super[name] == "function" && fnTest.test(prop[name]) ?
    35         (function(name, fn){
    36           //通过闭包,返回一个新的操作函数.在外面包一层,这样我们可以做些额外的处理
    37           return function() {
    38             var tmp = this._super;
    39            
    40             // Add a new ._super() method that is the same method
    41             // but on the super-class
    42             // 调用一个函数时,会给this注入一个_super方法用来调用父类的同名方法
    43             this._super = _super[name];
    44            
    45             // The method only need to be bound temporarily, so we
    46             // remove it when we're done executing
    47             //因为上面的赋值,是的这边的fn里面可以通过_super调用到父类同名方法
    48             var ret = fn.apply(this, arguments);  
    49             //离开时 保存现场环境,恢复值。
    50             this._super = tmp;
    51            
    52             return ret;
    53           };
    54         })(name, prop[name]) :
    55         prop[name];
    56     }
    57    
    58     // 这边是返回的类,其实就是我们返回的子类
    59     function Class() {
    60       // All construction is actually done in the init method
    61       if ( !initializing && this.init )
    62         this.init.apply(this, arguments);
    63     }
    64    
    65     // 赋值原型链,完成继承
    66     Class.prototype = prototype;
    67    
    68     // 改变constructor引用
    69     Class.prototype.constructor = Class;
    70  
    71     // 为子类也添加extend方法
    72     Class.extend = arguments.callee;
    73    
    74     return Class;
    75   };
    76 })();
  • 相关阅读:
    redis应用场景之文章投票设计思路
    Redis存储的5种数据结构
    v+=e 不等价于 v=v+e
    WebMagic
    指针函数和函数指针的区别
    为什么说StringBuilder不安全?
    sql注入
    Autowired报错处理
    SpringBoot入门最简单的一个项目示例
    MVC中Cookie的用法(二)---CookieHelper
  • 原文地址:https://www.cnblogs.com/cdwp8/p/4397272.html
Copyright © 2020-2023  润新知