28

赏金编辑:

我正在寻找以原型 OO 范式(想想 Self)编写的代码。不是原型 OO 和经典 OO 的混合体。我不想看到通用的 OO 包装器,而只是简单地使用原型 OO 技术并且使用原型 OO 技术。

参考相关问题:

JavaScript 中的原型 OO

在上面的问题中,我主要关注的是

可以这样写原型OO吗?

我们是否需要构造函数和初始化逻辑,有哪些替代方案?

新问题:

基本上在大型开源项目中是否有javascript 原型 OO 的好例子?

澄清:

我将不得不澄清原型 OO的含义:

  • 没有课程。只有对象。
  • 类概念的模拟为零同样只有对象和克隆对象来创建新对象。

原型 OO 的进一步说明:

JavaScript 中的原型 OO 与经典 OO 仿真之间的区别是一个非常灰色的区域。这并不是说我重视避免经典的 OO。我想以学术方式自己学习原型 OO,而不学习经典 OO 仿真和原型 OO 的(可能更优化)组合。

这就是我“禁止”类的原因,只是为了让我能够以纯粹的方式看到这些技术并扩展我自己的 OO 工具包。

例子:

像 jQuery 这样的流行示例不符合第二个标准。对象是一jQuery大类仿真。它专注于从类中创建新对象,而不是克隆现有对象。

如果我真的知道使用“纯”原型 OO 的任何示例,我会向您展示。我相信 99% 的 JavaScript OO 受经典仿真的影响太大。

奖励积分

如果

  • 它的评论很好/记录在案
  • 有单元测试
  • 在github上。

我还将接受有关如何编写超出您琐碎的 hello world 应用程序的原型 OO 代码的文章/教程和示例。

4

8 回答 8

9

你不会找到它。

不久前我去寻找这种东西,这就是我发现的:Self Paper Organizing Programs without Classes(查看Citeseer 的 PDF 版本。)这篇论文讨论了Self的最佳实践,原始的原型语言,最佳实践是使用“traits object idiom”,即让您的对象继承自“traits objects”,其中仅包含方法,不包含特定于对象的数据。换句话说,一个可疑地像一个类的对象。

甚至最初的原型语言也模拟类。

于 2011-07-08T20:57:54.343 回答
4

你看过 OMeta/JS 吗?OMeta 是一种基于 Smalltalk 和 Self 的实验研究模式匹配语言。OMeta/JS 是使用原型 OO 的 javascript 实现。

它通过许多示例得到很好的评论和记录。它也在 Github 上。

http://tinlizzie.org/ometa-js/

https://github.com/alexwarth/ometa-js

编辑:OMeta 是 Alexander Warth 博士论文的成果。

于 2011-07-12T13:38:57.403 回答
1

我不确定您在寻找什么,但我当前的框架允许您以 OO 方式进行编程,如下所示:

Cin.define({
    name: 'MyApp.Logger',
    extends: 'Cin.Component',
    implements: ['MyApp.ILogger'],
    mixes: {
        SomeMixin: 'MyApp.SomeMixin'
    },

    init: function() {
    },

    method: function() {
    },

    statics: {
        staticMethod: function() {}
    }
});

然后你可以编写如下代码:

var instance = new MyApp.Logger();
instance.method();

MyApp.Logger.staticMethod();

我不想在这里模仿经典的 OO。我正在尝试一种方便且有用的方式来声明继承、混合、接口和一般 OO 概念,以便开发人员可以轻松编写此类 OO 代码。这也让我有机会完成我的自动加载组件,这样您就不再需要处理依赖关系,并且您可以进行自定义构建并享受更快的开发,这要归功于每次页面加载不需要加载 100 个脚本。

如果您想学习原型 OO 概念,我认为您应该编写某种继承系统。看看Dojo ToolkitExtJS。要记住的一件好事是,基于原型的系统扭曲和混乱,它们比基于类的 OO 语言更强大。在我看来,没有一种正确的方法来编写原型代码。

恐怕大多数(如果不是全部的话)继承系统看起来都像是在模仿经典的 OO。在我看来,我的框架没有,但它甚至还没有完成。

于 2011-06-30T13:43:38.207 回答
1

可能是 JSLint(Crockford 是原型继承的支持者,但我并没有仔细研究它的每一寸)。它看起来也比面向对象更具功能性,但我希望这通常是真正包含原型继承的代码的情况。

于 2011-07-07T18:58:27.423 回答
1

在我的框架中,一切都是对象或“接口”。

接口定义了对象可能具有的常用功能(方法/property_gets/property_sets)。

你创建一个这样的界面:var some_interface = GetInterface(constructor, interface_setup, parent_interface..)

您可以指定任意数量的 parent_interfaces。因此,如果interface_A同时继承interface_Band interface_C,您可以这样创建 interface_A :GetInterface(constructor, interface_setup, interface_B, interface_C);

如果interface_A继承interface_Xinterface_X继承interface_Y,那么interface_A将具有两者兼有的所有interface_X功能interface_Y

不需要构造函数的接口会将构造函数参数保留为空。interface_setup 是一个如下所示的函数:

function(proto){
}

proto参数指向的对象有 4 个方法: SetMShadowMSetPShadowP

您使用这 4 种方法来设置您的界面。

该框架还提供了接口的惰性实例化。(换句话说,在实际第一次需要之前,设置代码永远不会真正运行)。

这个框架的局限性至少需要支持Object.keys,Object.getOwnPropertyDescriptorObject.defineProperty. (换句话说,它适用于最新版本的 FireFox、IE、Chrome、Safari 但不适用于 Opera)

测试页2.html:

<!doctype html>
<script src="js.js"></script>
<script>
var human = GetInterface(function(){
},function(proto){
    //alert("trace: initing human");
    proto.$SetM("Sleep",function(){
        alert(this.Name+" is sleeping");
    });
    proto.$SetP("Name",function(){
        return this._name;
    },function(value){
        this._name=value;
    });
});

var female = GetInterface(function(){
},function(proto){
    //alert("trace: initing female");
    proto.$SetM("Dance",function(){
        alert(this.Name+" is dancing");
    });
},human);

var male = GetInterface(function(){
},function(proto){
    //alert("trace: initing male");
    proto.$SetM("Fight",function(){
        alert(this.Name+" is fighting");
    });
    proto.$ShadowP("Name",function(parent_get){
        return "Mr. "+parent_get();
    },function(parent_set,value){
        parent_set(value);
    });
},human);

var child = GetInterface(function(){
},function(proto){
    //alert("trace: initing child");
    proto.$SetM("Play",function(){
        alert(this.Name+" is playing");
    });
},human);

var adult = GetInterface(function(){
},function(proto){
    //alert("trace: initing adult");
    proto.$SetM("Work",function(){
        alert(this.Name+" is working");
    });
},human);

var mammal = GetInterface(function(){
},function(proto){
    //alert("trace: initing mammal");
    proto.$SetM("DoMammalStuff",function(){
        alert("doing mammal stuff");
    });
});


var john=new male();
john.Name="john";
john.Sleep();

var mary=new female();
mary.$IsA(child);
mary.$IsA(mammal);
mary.$Setup(function(proto){
    proto.$ShadowP("Name",function(parent_get){
        return "Miss "+parent_get.call(this);
    },function(parent_set,value){
        parent_set.call(this,value);
    });
});
mary.Name="mary";
mary.Play();
</script>

测试页.html:

 <!doctype html>
<script src="js.js"></script>
<script>
var human_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing human");
    proto.$SetM("Sleep",function(){
        alert(this.Name+" is sleeping");
    });
    proto.$SetP("Name",function(){
        return this._name;
    },function(value){
        this._name=value;
    });
});

var female_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing female");
    proto.$SetM("Dance",function(){
        alert(this.Name+" is dancing");
    });
},human_interface);

var male_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing male");
    proto.$SetM("Fight",function(){
        alert(this.Name+" is fighting");
    });
},human_interface);

var child_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing child");
    proto.$SetM("Play",function(){
        alert(this.Name+" is playing");
    });
},human_interface);

var adult_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing adult");
    proto.$SetM("Work",function(){
        alert(this.Name+" is working");
    });
},human_interface);

var mammal_interface = GetInterface(function(){
},function(proto){
    alert("trace: initing mammal");
    proto.$SetM("DoMammalStuff",function(){
        alert("doing mammal stuff");
    });
});

var john={};
john.$IsA(adult_interface);
//the above 2 lines are equal to simply doing:
//var john=new adult_interface();
//you can think of it as a shortcut
john.$IsA(mammal_interface);
john.DoMammalStuff();
john.Name="john";
john.Sleep();

var mary=new female_interface();
mary.$IsA(child_interface);
mary.$IsA(mammal_interface);
mary.DoMammalStuff();
mary.Name="mary";
mary.Play();
mary.Dance();
</script>

js.js:

"use strict";
var GetInterface;
(function(){
    //================================================================================//
    //(constructor:Function, setup:Function?, parent_interfaces:Function..):Function
    GetInterface = function (constructor, setup) {
        var parent_classes = GetParray(arguments, 2);
        var output = function () {
            output.$Init();
            for (var x = parent_classes.length - 1; x >= 0; --x) {
                parent_classes[x](this);
            }
            if(constructor===null){
                constructor.apply(this, arguments);
            }
        };
        output.$Init = Mize(function () {
            var output_proto = output.prototype;
            parent_classes.forEach(function (parent_class) {
                parent_class.$Init();
                Infect(output_proto, parent_class.prototype);
            });
            init_proto(output_proto,setup);
            if(setup!==undefined){
                setup(output_proto);
            }
        });
        return output;
    };
    var init_proto=function(proto){
        $defineProperty(proto, "$SetM", { value: set_m, writable: true, configurable: true });
        $defineProperty(proto, "$ShadowM", { value: shadow_m, writable: true, configurable: true });
        $defineProperty(proto, "$SetP", { value: set_p, writable: true, configurable: true });
        $defineProperty(proto, "$ShadowP", { value: shadow_p, writable: true, configurable: true });
    };
    var set_m = function (method_name, method) {
        this[method_name] = method;
    };
    var set_p = function (property_name, getter, setter) {
        $defineProperty(this, property_name, { get: getter, set: setter, enumerable: true, configurable: true });
    };
    var shadow_m = function (method_name, supplied_method) {
        var old_method = this[method_name];
        this[method_name] = function () {
            var args = GetParray(arguments);
            args.unshift(old_method.bind(this));
            supplied_method.apply(this, args);
        };
    };
    var shadow_p = function (property_name, getter, setter) {
        var old_descriptor = $getOwnPropertyDescriptor(this, property_name);
        var old_get = old_descriptor.get;
        var old_set = old_descriptor.set;
        $defineProperty(this, property_name, { get: function () {
            return getter.call(this, old_get.bind(this));
        }, set: function (value) {
            setter.call(this, old_set.bind(this), value);
        }, enumerable: true, configurable: true
        });
    };
    var $slice=Array.prototype.slice;
    var $defineProperty=Object.defineProperty;
    var $getOwnPropertyDescriptor=Object.getOwnPropertyDescriptor;
    if($defineProperty===undefined){
        throw "Object.defineProperty, Object.getOwnPropertyDescriptor, Object.keys are required";
    }
    //================================================================================//
    //(victim:Object, disease:Object):void
    var Infect=function (victim, disease, excludes) {
        var keys=Object.keys(disease);
        if(excludes!==undefined){
            excludes.forEach(function(exclude){
                ForEach(keys,function(key,x){
                    if(key===exclude){
                        keys.splice(x,1);
                        return false;
                    }
                });
            });
        }
        keys.forEach(function(key){
            $defineProperty(victim, key, $getOwnPropertyDescriptor(disease, key));
        });
    };
    //================================================================================//
    //(args:Object # arguments object #, start_index:int?):Array
    var GetParray = function (args, start_index) {
        if (start_index === undefined) {
            start_index = 0;
        }
        return $slice.call(args, start_index);
    };
    //================================================================================//
    //(array:Array, f:Function(item:Object|null, index:pint):boolean?):Object
    var ForEach=function(array,f){
        for (var x = 0, xx = array.length, last_index=xx-1; x < xx; ++x) {
            var result = f(array[x], x, last_index);
            if (result !== undefined) {
                return result;
            }
        }
    };
    //================================================================================//
    //provides memoization.
    //(f:Function, arity_fixed:boolean?true):Function
    //caching is done according to the inputs. the results of calling function(undefined) and function() are cached differently.
    //if arity is fixed, optimizations can be done
    var Mize=function(f, arity_fixed) {
        if (arity_fixed === undefined) {
            arity_fixed = true;
        }
        var used; //for 0 arg
        var result; //for 0 arg
        var results; //for >0 args
        var used_params; //for 1 arg
        var used_param_sets; //for >1 args
        var f_length = f.length;
        var use_generic = !arity_fixed || f_length > 3;
        if (use_generic) { //if `f_length` <= 3, it will be optimized (i.e. not using generic function)
            results = [];
            used_param_sets = [];
            return function () {
                var params = GetParray(arguments);
                var result_found = false;
                var result = ForEach(used_param_sets,function (used_param_set, x) {
                    if (used_param_set.length === params.length) {
                        var params_match = true;
                        ForEach(params,function (param, y) {
                            if (used_param_set[y] !== param) {
                                params_match = false;
                                return false;
                            }
                        });
                        if (params_match) {
                            result_found = true;
                            return results[x];
                        }
                    }
                });
                if (!result_found) {
                    used_param_sets.push(params);
                    result = f.apply(null, params);
                    results.push(result);
                }
                return result;
            };
        }
        if (f_length === 0) {
            used = false;
            return function () {
                if (!used) {
                    result = f();
                    used = true;
                }
                return result;
            };
        }
        if (f_length === 1) {
            used_params = [];
        } else {
            used_param_sets = [];
        }
        results = [];
        switch (f_length) {
            case 1:
                return function (arg) {
                    var result_found = false;
                    var result = ForEach(used_params,function (used_param, x) {
                        if (arg === used_param) {
                            result_found = true;
                            return results[x];
                        }
                    });
                    if (!result_found) {
                        used_params.push(arg);
                        result = f(arg);
                        results.push(result);
                    }
                    return result;
                };
                break;
            case 2:
                return function (arg1, arg2) {
                    var result_found = false;
                    var result = ForEach(used_param_sets,function (used_param_set, x) {
                        if (arg1 === used_param_set[0] && arg2 === used_param_set[1]) {
                            result_found = true;
                            return results[x];
                        }
                    });
                    if (!result_found) {
                        used_param_sets.push([arg1, arg2]);
                        result = f(arg1, arg2);
                        results.push(result);
                    }
                    return result;
                };
                break;
            case 3:
                return function (arg1, arg2, arg3) {
                    var result_found = false;
                    var result = ForEach(used_param_sets,function (used_param_set, x) {
                        if (arg1 === used_param_set[0] && arg2 === used_param_set[1] && arg3 === used_param_set[2]) {
                            result_found = true;
                            return results[x];
                        }
                    });
                    if (!result_found) {
                        used_param_sets.push([arg1, arg2, arg3]);
                        result = f(arg1, arg2, arg3);
                        results.push(result);
                    }
                    return result;
                };
                break;
            default:
                throw "Invalid `f_length`: " + f_length;
        }
    };
    //================================================================================//
    Object.prototype.$Setup=function(setup){
        setup(Object.getPrototypeOf(this));
    };
    //================================================================================//
    Object.prototype.$IsA=function(_interface){
        var excludes=GetParray(arguments,1);
        if(this.$SetM===undefined){
            this.$SetM=set_m;
            this.$SetP=set_p;
            this.$ShadowM=shadow_m;
            this.$ShadowP=shadow_p;
        }
        _interface.$Init();
        /*var this_proto={};
        init_proto(this_proto);
        Infect(this_proto,Object.getPrototypeOf(this));
        this.__proto__=this_proto;*/
        Infect(Object.getPrototypeOf(this),_interface.prototype,excludes);
    };
    //================================================================================//
})();
于 2011-07-12T08:32:59.093 回答
0

ExtJS是 JavaScript OO 的一个很好的例子。它在 JavaScript 中实现了一个非常复杂的企业级 OO 层次结构,可以开箱即用地做很多事情。这可能是一个令人生畏的阅读(上次我检查了 3.X,它是超过 1MB 的原始未压缩 JavaScript),但它会给你很多想法。您可以从查看文档开始以获得高级视图。

于 2011-06-30T13:47:40.340 回答
0

这是一个示例,显示了您正在寻找的 OO 编程的基础。最好的真实示例可能是 jQuery。

在学习 JavaScript 时,你必须记住它实际上更接近于 Scheme,而不是 C 或 Java 的根源。基本上它是C语法中的Scheme。

几乎从来没有在 JavaScript 中使用“new”的情况,尤其是在编写 API 时。似乎添加了“新”运算符,因为 JavaScript 不确定其原型框架。对于我们大多数开始使用 C、C++ 和 Java 等经典语言进行编程的人来说,这似乎很奇怪,因为“新”通常正是我们正在寻找的,因为它很容易翻译。

你问我为什么不应该使用“新”?好吧,由于“新”的实现,您可能会无意中开始清除您的全局数据(请记住,JavaScript 中的所有内容都不在函数中)。如果您碰巧遇到这种情况,那么您将看不到任何错误或通知,而只会看到程序中不可预知的行为。此外,您的“类”中的“this”实际上与什么绑定可能不清楚,也可能不清楚。

在不知情的情况下清除全局内存的问题主要发生在您编写一个打算用“new”调用的函数并且用户不使用“new”时。提示为什么在 API 中使用它会导致用户不满意。

面向对象的“类”和继承的正确方法是使用 JavaScript 最强大的属性……对象。

您可以编写一个函数来返回一个对象以建立一个“类”。你放入这个对象的任何东西(数字、字符串、方法等)都是你的“类”的公共属性。您在函数中编写的不在返回的对象内的任何内容都是私有的。

要从您的“类”继承,您可以简单地初始化您将返回到您的“基类”结果的对象,然后扩展其功能。

以下代码部分将展示如何使用私有和公共变量构造一个基类,以及 do 2 级继承。

基础对象

//Base Object
var Animal = function(spec) {

    //This is our output object
    //Everything provided from 'spec' and
    //everything not addded to 'that' will
    //be 'private'. Everything added to
    //'that' is 'public'.
    var that = {};

    //Private Methods
    function extend(obj1,obj2) {
        for(var key in obj2) {
            obj1[key] = obj2[key];
        }
    }

    //Private Variables
    var defaults = {
        name : 'Default Name',
        food : 'Default Food',
        saying : 'Default Saying',
    }

    extend(defaults,spec);

    //Public Methods
    that.name = function() {
        return defaults.name;
    }

    that.eats = function() {
        if(typeof(defaults.food) === 'string') {
            return defaults.food;
        } else if(typeof(defaults.food) === 'object') {
            return defaults.food.join(', ');
        }
    }

    that.says = function() {
        return defaults.saying;
    }

    return that;
}

var myAnimal = Animal();       //Create a new instance
alert(myAnimal.name());        //Alerts 'Default Name'
alert(myAnimal.eats());        //Alerts 'Default Food'
alert(myAnimal.says());        //Alerts 'Default Saying'
alert(myAnimal.saying);        //Alerts 'undefined'
//alert(myAnimal.extend());    //Has No Method Error

var myAnimal2 = Animal({       //Create a new instance using a spec
    name : 'Mike',
    food : ['Chicken','Duck'],
    saying : 'Rawr',
});    
alert(myAnimal2.name());        //Alerts 'Mike'
alert(myAnimal2.eats());        //Alerts 'Chicken, Duck'
alert(myAnimal2.says());        //Alerts 'Rawr'

遗产

//Inheritance Object
var Mammal = function(spec) {

    //Private Methods

    //Have to redefine this since
    //I decided to use this as an
    //example of a private method
    function extend(obj1,obj2) {
        for(var key in obj2) {
            obj1[key] = obj2[key];
        }
    }

    //Private Variables
    //New list of defaults
    var defaults = {
        name : 'Mammal',
        attributes : ['fur'],
    }

    extend(defaults,spec);

    //Inherrit from our Animal Object
    //Use Mammal defaults
    var that = Animal(defaults);


    that.attributes = function() {
        if(typeof(defaults.attributes) === 'string') {
            return defaults.attributes;
        } else if(typeof(defaults.attributes) === 'object') {
            return defaults.attributes.join(', ');
        } else {
            return false;
        }
    }

    return that;
}

//Second-Level Inheritance
var Cat = function(spec) {

    //Private Methods

    //Have to redefine this since
    //I decided to use this as an
    //example of a private method
    function extend(obj1,obj2) {
        for(var key in obj2) {
            obj1[key] = obj2[key];
        }
    }

    //Private Variables
    //New list of defaults
    var defaults = {
        name : 'Cat',
        saying : 'Meow',
        food : ['fish','birds','frogs','MeowMix'],
        fur_color : 'Default Fur Color',
        attributes : ['fur','claws','crazy eyes','long tail'],
    }

    extend(defaults,spec);

    //Inherrit from our Mammal Object
    //We use our defaults for the cat
    var that = Mammal(defaults);

    that.fur_color = function() {
        return defaults.fur_color; 
    }

    that.purr = function(n) {
        var str = '';

        for(var i=0;i<n;i++) {
            if(i === 0) {
                str = 'p-';
            } else if(i === n-1) {
                str += 'r';
            } else {
                str += 'r-';
            }
        }

        return str
    };

    return that;
}


var myMammal = Mammal();
alert(myMammal.name());        //Alerts Mammal
alert(myMammal.attributes());  //Alerts 'fur'

var myCat = Cat();
alert(myCat.name());            //Alerts Cat
alert(myCat.says());            //Alerts Meow

var toonces = Cat({
    name : 'Toonces the Driving Cat',
    food : ['Whiskas','ham'],
    saying : 'Meeeooooowww',
    fur_color : 'Black',
    attributes : [ 
        'Can Drive a Car', 'Claws',
        'fur','crazy eyes','long tail',
        'Steals Hub Caps',
    ],
});

alert(toonces.name());            //Alerts 'Toonces the Driving Cat'
alert(toonces.says());            //Alerts 'Meeooooowww'
alert(toonces.eats());            //Alerts 'Whiskas, ham'
alert(toonces.fur_color());       //Alerts 'Black'
alert(toonces.attributes());      //Alerts 'Can Drive a Car, Claws,
                                  //fur, crazy eyes, long tail,
                                  // Steals Hub Caps',
alert(toonces.purr(5));           //Alerts 'p-r-r-r-r'

编辑:我被告知我没有使用“原型”对象。我这样做是为了避免必须使用“new”运算符,如上文所述。为了完整起见,我将使用下面的原型对象举一个例子......

原型对象的继承

//Building a class to use the prototype object
var Dog = function(spec) {

var that = this;

//Private Methods

    //Have to redefine this since
    //I decided to use this as an
    //example of a private method
    function extend(obj1,obj2) {
        for(var key in obj2) {
            obj1[key] = obj2[key];
        }
    }

    //Private Variables
    //New list of defaults
    var defaults = {
        name : 'Dog',
        saying : 'Woof',
        food : ['bacon'],
        fur_color : 'Default Fur Color',
        attributes : ['fur','Barks at Mailman'],
    }


    //Attach the properties of a Mammal to "self"
    this.self = new Mammal(defaults);

    //Add a function to get the name
    this.getName = function() {
        return that.self.name();
    }
}

//Extend the prototype
Dog.prototype.growl = "grrrrrrr";

//Make a new dog...HAVE TO CALL NEW HERE OR ELSE BAD THINGS CAN HAPPEN
d= new Dog();

alert(d.growl);            //Alerts 'grrrrrrr'
alert(d.getName());        //Alerts 'Dog'
alert(d.self.says());      //Alerts 'Woof'

请随时向我询问有关此帖子的任何内容。享受。

于 2011-07-11T20:03:28.950 回答
0

我目前正在使用一个继承插件模型,它试图将原型 OO 模式与 jQuery 插件模式结合起来。它在我的答案中详细发布:将类附加到 jQuery 对象

注意:不要因为提到这个词而被拒之门外Class

于 2011-07-12T00:49:07.023 回答