zoukankan      html  css  js  c++  java
  • 小结JS中的OOP(下)

    关于JS中OOP的具体实现,许多大神级的JS专家都给出了自己的方案。

    一:Douglas Crockford

    1.1 Douglas Crockford实现的类继承

    /**
     * 原文地址:http://javascript.crockford.com/inheritance.html
     */
    Function.prototype.method = function (name, func) {
        this.prototype[name] = func;
        return this;
    };
    
    Function.method('inherits', function (parent) {
        var d = {},
            p = (this.prototype = new parent());
        this.prototype.constructor = parent;
        this.method('uber', function uber(name) {
            if (!(name in d)) {
                d[name] = 0;
            }
            var f, r, t = d[name], v = parent.prototype;
            if (t) {
                while (t) {
                    v = v.constructor.prototype;
                    t -= 1;
                }
                f = v[name];
            } else {
                f = p[name];
                if (f == this[name]) {
                    f = v[name];
                }
            }
            d[name] += 1;
            r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
            d[name] -= 1;
            return r;
        });
        return this;
    });

    DC实现的这个类继承的思路是:

    1. this.prototype = new parent()通过重写构造函数的原型对象来实现继承

    2. 增加实例方法uber(),通过instance.uber()可以访问父类中的某个方法。当然uber方法有点复杂,分析之前我们要先明白以下几点:

    a. 闭包变量不会被销毁,同时函数的作用域是在函数定义时确定的。

    b. 每个函数都有prototype属性,指向一个对象,这个对象会在函数作为构造函数创建对象时,作为创建对象的模板,这个对象我们称之为原型对象。

    c. 每个对象都有一个__proto__指针,指向此对象在创建时所依赖的原型对象。Object.prototype.__proto__ === null;

    d. 在对一个对象求某个键值时,如果对象本身不存在这个键,则会在对象的原型(链)上面寻找

    Function.method('inherits', function (parent) {
        //d, p, parent都是一直存在的闭包变量,可供uber()方法使用
        
        var d = {},
                
            //通过 this.prototype = new parent() 实现继承
            p = (this.prototype = new parent());
        this.prototype.constructor = parent;
        
        //通过增加原型方法uber()实现对象父类方法的调用(特别是子父类中的同名方法)
        //类似Java中的super,调用其直接父类中的方法
        this.method('uber', function uber(name) {
            //d[name]是标识某方法在原型链中的层级,以便在原型链中正确得到相应的父类方法
            //使用d[name]这样的map是因为在一个方法中可能调用多个父类的同名方法
            if (!(name in d)) {
                d[name] = 0;
            }
            var f, r, t = d[name], v = parent.prototype;
            if (t) {
                //如果层级标识不为0,则要循环在原型链上找到对象的原型对象,再确定对应的父方法name
                while (t) {
                    v = v.constructor.prototype;
                    t -= 1;
                }
                f = v[name];
            } else {
                f = p[name]; // p === this.prototype === new parent;当前类的原型对象
    
                //如果当前类的原型对象上的方法name与对象实例的name方法相等。
                //这里要注意,对于引用类型的相等,比较的是指针。引用类型值a == 引用类型值b 只能说明a,b都指向同一块内存
                
                //原型对象上的方法name与对象实例的方法name相等,只能说明对象本身没有这个方法,这个方法是存放在对象原型对象中的
                if (f == this[name]) {
                    f = v[name]; // v = parent.prototype 是父类的原型对象。上面的属性会被父类的实例与子类的实例共享
                }
            }
            
            //uber()方法层级+1
            d[name] += 1;
            
            //使用apply调用父类的方法f,并设置其上下文为this,即子类实例
            r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
            
            //层级还原
            d[name] -= 1;
            return r;
        });
        return this;
    });

      

    //测试
    function Animal() {}
    
    Animal.prototype.getInfo = function() {
        return 'Animal';
    }
    Animal.prototype.getAge = function() {
        return '10';
    }
    
    function Dog() { }
    //注意Dog.prototype上新增的方法一定要写在调用了inherits()后面
    Dog.inherits(Animal);
    Dog.prototype.getInfo = function() {
        var a = 'dogName:' + this.uber('getInfo');
        var b = 'dogAge:' + this.getAge();
        return a +'|'+ b;
    }
    
    function Collie() {}
    Collie.inherits(Dog);
    Collie.prototype.getInfo = function() {
        return this.uber('getInfo') + '|Collie';
    }
    
    var c = new Collie();
    console.log(c.getInfo()); //dogName:Animal|dogAge:10|Collie
    console.log(c instanceof Collie);// true
    console.log(c instanceof Dog); // true
    console.log(c instanceof Animal); //true
    
    console.log(c.constructor === Animal); //false
    console.log(c.constructor === Dog); //true
    console.log(c.constructor === Collie); //false
    
    //c.constructor 应该等于Collie才对,现在却指向了Dog.错误原因:this.prototype.constructor = parent;

    在玉伯早期的一篇文章中(http://www.iteye.com/topic/248933),有下面一个例子。至于结果,找出完整的调整栈,看一下就完成明白了。结果与原文有点不一样,可能DC修改过他的代码吧。

    // 例2  
    function D1() {}  
    D1.prototype.getName = function() { return 'D1' }; // @4  
      
    function D2() {}  
    D2.inherits(D1);  
    D2.prototype.getName = function () { return this.uber('getName') + ',D2'; }; // @5  
      
    function D3() {}  
    D3.inherits(D2);  
      
    function D4() {}  
    D4.inherits(D3);  
      
    function D5() {}  
    D5.inherits(D4);  
    D5.prototype.getName = function () { return this.uber('getName') + ',D5'; }; // @6  
      
    function D6() {}  
    D6.inherits(D5);  
      
    var d6 = new D6();  
    println(d6.getName()); // => D1,D2,D2,D2,D5,D5   
    println(d6.uber('getName')); // => D1,D2,D2,D2,D5

    发现结果是D1,D2,D2…这样。上面已经说过:在对一个对象求某个键值时,如果对象本身不存在这个键,则会在对象的原型(链)上面寻找。这就是产生多个D2的原因。但这结果与我们期望的super效果不一样。下面是玉伯加了patch的代码:

    // patched by lifesinger@gmail.com 2008/10/4  
    Function.method('inherits', function (parent) {  
        var d = { },   
            p = (this.prototype = new parent());  
            // 还原constructor  
            p.constructor = this;  
            // 添加superclass属性  
            p.superclass = parent;  
                      
        this.method('uber', function uber(name) {  
            if (!(name in d)) {  
                d[name] = 0;  
            }  
            var f, r, t = d[name], v = parent.prototype;  
            if (t) {  
                while (t) {  
                    // 利用superclass来上溯,避免contructor陷阱。要注意parent没有被修改过,所以v在每次进入uber时的值是一样的。始终指向父类原型对象
                    v = v.superclass.prototype;  
                    // 跳过“断层”的继承点。不会因为“对象本身没有向原型(链)拿”造成重复执行  
                    if(v.hasOwnProperty(name)) {  
                        t -= 1;  
                    }  
                }  
                f = v[name];  
            } else {  
                f = p[name];  
                if (f == this[name]) {  
                    f = v[name];  
                }  
            }  
            d[name] += 1;          
            if(f == this[name]) { // this[name]在父类中的情景  
                r = this.uber.apply(this, Array.prototype.slice.apply(arguments));  
            } else {  
                r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));  
            }  
            d[name] -= 1;  
            return r;  
        });  
        return this;  
    });
     
    // 例3
    function F1() { }
    F1.prototype.getName = function() { return 'F1'; };
    
    function F2() { }
    F2.inherits(F1);
    F2.prototype.getName = function() { return this.uber('getName') + ',F2'; };
    
    function F3() { }
    F3.inherits(F2);
    F3.prototype.getName = function() { return this.uber('getName') + ',F3'; };
    
    function F4() { }
    F4.inherits(F3);
    F4.prototype.getName = function() { return this.uber('getName') + ',F4'; };
    
    document.write('<hr />')
    var f3 = new F3();
    document.write(f3.getName()); // => F1,F2,F3
    document.write('<hr />')
    var f4 = new F4();
    document.write(f4.getName()); // => F1,F2,F3,F4
    
    console.log(f3 instanceof F3);//true
    console.log(f3 instanceof F2);//true
    console.log(f3 instanceof F1);//true
    console.log(f3.constructor === F3);//true
    console.log(f3.constructor === F2);//false
    console.log(f3.constructor === F1);//false
    console.log(f4.constructor === F4);//true
    console.log(f4.constructor === F3);//false
    console.log(f4.constructor === F2);//false
    console.log(f4.constructor === F1);//false

    至此,可以发现已经实现:

    》实现了继承

    》修正了实例的constructor属性指向错误;

    》instanceof能正常运行

    》能使用uber调用父类的方法

    当然也会有缺点:

    》每一次继承都会创建多个闭包变量,内存占用多

    》每一次继承都要先创建一个父类的实例,(new parent())

    》子类与父类必须先定义好,为子类增加实例方法也必须放到inherits()方法调用之后。

    参考文章:

    http://javascript.crockford.com/inheritance.html

    http://www.iteye.com/topic/248933

    1.2 Douglas Crockford实现的基于原型的继承

    if (typeof Object.create !== 'function') {
        Object.create = function (o) {
            //定义类
            function F() {}
            //重写原型,实现继承
            F.prototype = o;
            //返回类的实例
            return new F();
        };
    }

    这样也可以实现继承。不过没有类,实例相关的概念。当然也不具有什么uber()方法能力。不过DC说, 这才是JavaScript的“本性”。JavaScript本身就是无类的,基于原型的。ES5已经实现这个方法。

    二:John Resig

    jQuery的作者John Resig的继承实现思路是:实现一个全局对象Class(其实是一个函数,但在JS中函数也是对象),这个对象具有静态方法extends()。extends()需要传入一个对象作为返回(构造)函数的原型对象依据。同时也实现了与uber()类似的_super()方法。

    /* Simple JavaScript Inheritance
     * By John Resig http://ejohn.org/
     * MIT Licensed.
     */
    // Inspired by base2 and Prototype
    (function(){
        var initializing = false,
            // 摘自http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html
            // fnTest是一个正则表达式,可能的取值为(/_super/ 或 /.*/)
            // - 对 /xyz/.test(function() { xyz; }) 的测试是为了检测浏览器是否支持test参数为函数的情况
            // - 不过我对IE7.0,Chrome2.0,FF3.5进行了测试,此测试都返回true。
            // - 所以我想这样对fnTest赋值大部分情况下也是对的:fnTest = /_super/;
            fnTest = /xyz/.test(function(){xyz;}) ? /_super/ : /.*/;
    
        //这里的this指向window, 相当于window.Class = function(){}
        this.Class = function(){}; //@1
    
        /**
         * 向全局对象Class(其实是一个函数,但在JS中函数也是特殊的对象,可以在上面挂属性或方法)上面增加静态方法extend
         * extend方法需要一个对象作为返回类的原型对象的模板
         * @param {*} prop
         * @returns {Function}
         */
        Class.extend = function(prop) {
            //上面说过,Class是一个(函数)对象,函数作为对象的方法调用时,this指向函数所属的对象
            //所以this指向Class对象:@1。其实就是一个匿名函数
            var _super = this.prototype;
    
            initializing = true;
    
            //new this()其实就是包含父类原型的一个空对象。这个为继承打了基础。保证了instanceof能得到正确的结果
            var prototype = new this(); //@2
    
            initializing = false;
    
            /**
             * 把传进来的prop对象上面的属性复制到@2对象上
             * 这时候有三个对象:
             * prop:用户作为extend方法的参数传进来的对象
             * _super:父类的原型对象
             * prototype: 要返回的类的原型对象
             * 它们之间的关系是:prop用来扩展prototype; prototype用于实现继承;
             * _super用于实现调用父类的方法
             */
            for (var name in prop) {
                /**
                 * 注意:true && true && 1 > 0 ? 1: -1; === (true && true && 1 > 0 ) ? 1: -1;
                 * 所以下面的赋值过程为:
                 * 1. 如果prop[name]不是一个函数: prototype[name] = prop[name]
                 * 2. 如果prop[name]是一个函数,但_super[name]不是一个函数:prototype[name] = prop[name]
                 * 3. 如果prop[name], _super[name]都是一个函数,且fn.Test.test(prop[name])为假:prototype[name] = prop[name]
                 * 4. 其它情况:prototype[name] = 匿名函数自执行的结果
                  *
                 * 备注:/_super/.test(function () {}) === > /_super/.test((function() {}).toString());
                 * 即要测试的函数代码中包含_super这个字符串都会返回true;
                 * /_super/.test(function() {var a = '_super'}) === true
                 * /_super/.test(function() {var a = '_supe'}) === false
                 */
    
                //在prototype上面加入自己的原型属性
                prototype[name] =
                        typeof prop[name] == "function" &&
                        typeof _super[name] == "function" &&
                        //如果传入对象的某个属性中包含'_super',则要做特殊处理
                        fnTest.test(prop[name]) ? (function(name, fn){
                            //这个name, fn会成为闭包变量
                            return function() {
                                var tmp = this._super;
    
                                //把所有的父类中有'_super'字符串的方法都用闭包变量name保存起来
                                //同时_super也是一个闭包变量,这样就可以找到在调用this._super()时要调用父类的那个方法
                                //uber()方法要手动传入一个方法名,但_super()方法却能自动找到父类的同名方法
                                this._super = _super[name];
    
                                //在前面已经准备好this._super的指向,然后调用包含this._super的实例方法,
                                //就会直接转到父类方法
                                var ret = fn.apply(this, arguments);
                                //将this._super还原。this._super也可能包含其它值
                                this._super = tmp;
    
                                return ret;
                            };
                        })(name, prop[name]) : prop[name];
            }
    
            //定义类Klass。Jhon Resig原代码是用的Class,增加了理解难度
            function Klass() {
                //所有的初始化都是在构建函数的init方法里面进行的。
                if ( !initializing && this.init )
                    this.init.apply(this, arguments);
            }
    
            //重写类的原型,实现继承
            Klass.prototype = prototype;
    
            //修正类原型对象的constructor指向
            Klass.prototype.constructor = Klass;
    
            //为类增加静态方法extend
            Klass.extend = arguments.callee;
    
            //返回类,其实是构造方法
            return Klass;
        };
    })();

    分析:

    1. 在Object的上增加了一层:实现了一个Class类,这个类会作为所有使用extend产生类的第二基类,上面有一个extend方法。这个方法会返回一个类(构造函数),返回时已经设置好原型对象。这个原型对象由 extend传入的参数对象与此类父类的原型共同生成

    2. 当调用new Constructor()时,会检测对象是否有init方法,如果有,会调用这个init方法对实例进行初始化,否则返回一个空对象

    3. 可以使用返回的类再产生一个类

    var Person = Class.extend({
        init: function(isDancing){
            this.dancing = isDancing;
        },
        flag:'PersonProp'
    });
    
    var Ninja = Person.extend({
        init: function(){
            //能自动找到父类中的init方法
            this._super( false );
        },
        flag:'NinjaProp'
    });
    
    var p = new Person(true);
    p.dancing; // => true
    
    var n = new Ninja();
    n.dancing; // => false
    
    p instanceof Object; //=> true
    p instanceof Class;//=> true
    p instanceof Person; //=> true
    p.constructor === Person;//=>true

    参考文章:

    http://ejohn.org/blog/simple-javascript-inheritance/

    http://www.cnblogs.com/sanshi/archive/2009/07/14/1523523.html

  • 相关阅读:
    python3中try异常调试 raise 异常抛出
    基于 k8s-搭建 Kubernetes 的 web 管理界面
    PostgreSQL SERIAL创建自增列
    C++之同名覆盖、多态
    golang实现路由中间件middleware
    fastjson源码分析之序列化
    AJPFX实践 java实现快速排序算法
    AJPFX关于IO流的简单总结
    AJPFX关于多态中的动态绑定和静态绑定的总结
    关于java的arrays数组排序示例AJPFX的分享
  • 原文地址:https://www.cnblogs.com/jagusOu/p/3838346.html
Copyright © 2011-2022 走看看