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

  • 相关阅读:
    1、编写一个简单的C++程序
    96. Unique Binary Search Trees
    python 操作redis
    json.loads的一个很有意思的现象
    No changes detected
    leetcode 127 wordladder
    django uwsgi websocket踩坑
    you need to build uWSGI with SSL support to use the websocket handshake api function !!!
    pyinstaller 出现str error
    数据库的读现象
  • 原文地址:https://www.cnblogs.com/jagusOu/p/3838346.html
Copyright © 2011-2022 走看看