js继承机制详解

想要学好一种语言,一定要了解其语言特性,因为我们 not just coding,js本身就是一种函数式的编程语言,它不与java或者其他面向对象一样拥有继承机制,但是我们可以利用其留下的prototype库进行曲线救国,这就不能不找一段经典的继承代码来研究一下了。以下是John Resig实现的经典继承实现代码,

 // 自执行的匿名函数创建一个上下文,避免引入全局变量
    (function() {
        // initializing变量用来标示当前是否处于类的创建阶段,
        
        var initializing = false, fnTest = /xyz/.test(function() { xyz; }) ? /\b_super\b/ : /.*/;
        // 基类构造函数
        // 这里的this是window,所以这整段代码就向外界开辟了一扇窗户 - window.Class
        this.Class = function() { };
        // 继承方法定义
        Class.extend = function(prop) {
           
            var _super = this.prototype;
            // 通过将子类的原型指向父类的一个实例对象来完成继承
            // - 注意:this是基类构造函数(即是Class)
            initializing = true;
            var prototype = new this();
            initializing = false;
            for (var name in prop) {
                prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function"  ?
                        (function(name, fn) {
                            return function() {
                                var tmp = this._super;
                                this._super = _super[name];
                                var ret = fn.apply(this, arguments);

                                return ret;
                            };
                        })(name, prop[name]) :
                        prop[name];
            }
            
            function Class() {
                // 在类的实例化时,调用原型方法init
                if (!initializing && this.init)
                    this.init.apply(this, arguments);
            }
            // 子类的prototype指向父类的实例(完成继承的关键)
            Class.prototype = prototype;
            // 修正constructor指向错误
            Class.constructor = Class;
            // 子类自动获取extend方法,arguments.callee指向当前正在执行的函数
            Class.extend = arguments.callee;
            return Class;
        };
    })();
    var Person = Class.extend({
        name:null,
        init:function(){
            alert("init person");
        },
        alerting:function(){
            alert("Person");
        }
    })
    var Student = Person.extend({
        init:function(){
            this._super();
            alert("init");
        },
        alerting:function(){
            this._super();
            alert("Student");
        }
    })
    var p = new Student();
    p.alerting();

首先通过创建一个自执行函数避免引入全局变量,主要来分析一下Class.extend这个方法,该方法中首先将父类(也就是Class)这个类的原型prototype属性赋给_super这个变量,initializing主要是用于控制父类实例化时,prototype = new this()时,防止进入if条件,也就是这段代码,
if (!initializing && this.init)
                    this.init.apply(this, arguments);

是否执行接下来声明一个prototype的变量,指向父类的一个实例,prototype = new this(),这句代码其实是将prototype的__proto__属性指向了父类的原型,接下来将initializing这个变量=false,是为了志明自执行方法中的return的Class这个类在后期实例化时进入if条件,例如Person就可以。相信大家都注意到了for循环中的代码,这就是继承机制的核心,看这个条件语句 typeof prop[name] == "function" && typeof _super[name] == "function" ,是说明如果prop对象中的name属性是一个function并且父类中存在同名的属性并且同样是function,那么执行下面这段代码:


(function(name, fn) {
                            return function() {
                                var tmp = this._super;
                                this._super = _super[name];
                                var ret = fn.apply(this, arguments);

                                return ret;
                            };
                        })(name, prop[name])


这段代码是什么意思呢,这里面其实做了两件事情,首先将父类的同名方法赋给了this绑定的_super属性,然后就是将子类的方法绑定到this作用域执行,我们回过头来看看,这段代码是赋给了prototype[name],也就是子类的该同名方法,该方法必定是由定义该方法类的实例调用,因此this作用域指向了目标类的实例,此时,this作用域中还有一个_super属性,该属性此时需要注意的是该属性不是不变的,举例来说,var p = new Student()这个实例,当实例p调用init()函数时,this._super指向了Person的init(),当p调用alerting()时,this._super指向了Person的alerting(),当我们调用p.alerting()的时候,执行this._super()其实就是调用父类的同名方法,若typeof prop[name] == "function" && typeof _super[name] == "function"不为true的时候,那么prototype[name] = proto[name],这其中还有一点点不好理解,如上代码我改动一点点,将Person类中的alerting方法变成一个属性,那么这个条件也不成立,就执行prototype[‘alerting’]=proto['alerting'],这就有问题了,之前我们在prototype=new this()的时候,prototype的__proto__属性指向了父类的prototype,说明其中必定有父类中的alerting属性,这下prototype又有了一个alerting属性,那么我们的对象p在调用p.alerting()的时候调用的到底是alerting方法还是alerting属性呢,其实还是alerting()方法,因为首先对象p会在第一个__proto__属性中找alerting()方法,若找不到,则在下一层中寻找,若找到了该方法,就不会进入下一层的__proto__属性中继续寻找了,所以这里不会找到从父类继承下来的alerting属性,调用的只是自己的方法。到此为止,其实只是实现了将父类的方法属性继承到了我们自定义的局部变量prototype属性中,后面我们定义的Class方法,该方法不同于之前的Class方法,

function Class() {
                // 在类的实例化时,调用原型方法init
                if (!initializing && this.init)
                    this.init.apply(this, arguments);
            }

我们将Class的prototype指向了我们定义的prototype变量,其实就是继承了父类的方法和属性,此时Class.constructor指向了prototype变量的构造函数,所以我们显式地改回来,Class.constructor=Class,并且使用Class.extend=arguments.callee将Class.extend方法指向了外层的Class.extend方法,方便我们进行深度扩展,最后将Class返回。

这个意思就是无论是Person或者是Student类,本质上都是Class.extend方法内部定义的Class方法(也就是类),只是我们将父类(外层的Class,也就是调用extend方法的类)和子类(也就是prop对象)的属性揉到了内部的Class类中,最后将Class返回给我们外部,就实现了继承了。

以上就是该继承机制的详解,不知道讲清楚了没有,若有疑问或者有不对之处,敬请指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值