(С открытым исходным кодом) Примеры прототипов JavaScript OO

Bounty Edit:

Я ищу код, написанный в чистой прототипической парадигме OO (думаю, Self). Не смесь прототипического ОО и классического ОО. Я не хочу видеть общие обертки OO, а просто использовать прототипные методы OO и только прототипные методы OO.

Связанный с этим вопрос:

Прототипическое OO в JavaScript

В приведенном выше вопросе я в основном сосредоточился на

Можно написать прототипное OO, как это?

Нужны ли нам конструкторы и логика инициализации, Каковы альтернативы?

Новый вопрос:

В принципе, есть ли хорошие примеры прототипического OO javascript в больших проектах с открытым исходным кодом?

Разъяснение:

Мне нужно будет уточнить, что я имею в виду с прототипом OO:

  • Нет классов. Есть только объекты.
  • Существует нулевая эмуляция понятий классов, снова есть только объекты и клонирующие объекты для создания новых объектов.

Дальнейшее уточнение прототипического ОО:

Различие между прототипом OO в JavaScript и классической эмуляцией OO - очень серая область. Это не то, что я ценю, избегая классического ОО. Я хочу изучать прототипное ОО в академической манере самостоятельно, не изучая (возможно, более оптимальную) комбинацию классической эмуляции ОО и прототипического ОО.

Вот почему я "запрещаю" классы, чтобы я мог видеть эти методы в чистом виде и расширять свой собственный набор инструментов OO.

<сильные > Примеры:

Популярные примеры, такие как jQuery, не соответствуют второму критерию. Объект jQuery - это одна эмуляция большого класса. Он фокусируется на создании новых объектов из класса, а не клонирования существующих объектов.

Если бы я действительно знал какой-нибудь пример использования "чистого" прототипа ОО, я бы показал вам. Я считаю, что 99% JavaScript OO слишком сильно зависит от классической эмуляции.

Бонусные очки

Если

  • Это хорошо соответствует/документировано
  • Имеет модульные тесты
  • Входит в github.

Я также буду принимать статьи/учебные пособия и примеры того, как писать прототипный код OO, который выходит за рамки вашего тривиального всемирного приложения hello.

Ответ 1

Вы не найдете его.

Я давно искал такого рода вещи, и вот что я нашел: Self Paper Организация программ без занятий (Look на Citeseer для PDF-версии.) В этом документе обсуждаются лучшие практики для Self, оригинальный прототипный язык, и наилучшей практикой является использование "traits object idiom", который должен наследовать ваши объекты от "объектов объектов", которые содержат только методы и не содержат конкретных объектов. Другими словами, объект, который подозрительно похож на класс.

Даже оригинальный прототипный язык эмулирует классы.

Ответ 2

Вы взглянули на OMeta/JS? OMeta - экспериментальная модель, соответствующая языку, основанному на Smalltalk и Self. OMeta/JS - это реализация в javascript с использованием прототипа OO.

Он хорошо комментируется и документируется многими примерами. Это также на Github.

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

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

Изменить: OMeta является результатом диссертация Александра Варта.

Ответ 3

Я не совсем уверен, что вы ищете, но моя текущая структура позволяет вам программировать в стиле 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 Toolkit или ExtJS, Хорошо помнить, что системы на основе прототипов скручиваются и калечат, они более мощны, чем языки OO на основе классов. На мой взгляд, нет единого правильного способа написать прототипный код.

Я боюсь, что большинство, если не все системы наследования, могут выглядеть так, как будто они имитируют классический OO. На мой взгляд, моя структура не делает, но потом она даже не закончилась.

Ответ 4

Вероятно JSLint (Крокфорд - сторонник прототипического наследования, но я не прочесывал его каждый дюйм). Он также выглядит более функциональным, чем объектно-ориентированный, но тогда я ожидаю, что в общем случае с кодом, который действительно охватывает прототипное наследование.

Ответ 5

В моих рамках все является объектом или "интерфейсом".

Интерфейсы определяют общие функции (методы/property_gets/property_sets), которые могут иметь объекты.

Вы создаете такой интерфейс: var some_interface = GetInterface(constructor, interface_setup, parent_interface..)

Вы можете указать любое количество родительских интерфейсов. Поэтому, если interface_A наследует как interface_B, так и interface_C, вы можете создать интерфейс_A как таковой: GetInterface(constructor, interface_setup, interface_B, interface_C);

Если interface_A наследует interface_X и interface_X наследует interface_Y, тогда interface_A будет иметь все функции, которые имеют как interface_X, так и interface_Y.

Интерфейсы, не требующие конструктора, оставят аргумент конструктора нулевым. Интерфейс_setup - это функция, которая выглядит так:

function(proto){
}

Объект, на который указывает аргумент proto, имеет 4 метода: SetM, ShadowM, SetP и ShadowP.

Вы используете эти 4 метода для настройки вашего интерфейса.

Эта структура также обеспечивает ленивую реализацию интерфейсов. (другими словами, установочный код никогда не будет запущен, пока он на самом деле не понадобится).

Ограничения этой структуры требуют, по крайней мере, поддержки для Object.keys, Object.getOwnPropertyDescriptor и Object.defineProperty. (Другими словами, он работает в последних версиях FireFox, IE, Chrome, Safari, но не в Opera)

TestPage2.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>

TestPage.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);
    };
    //================================================================================//
})();

Ответ 6

ExtJS - отличный пример JavaScript OO. Он реализует действительно сложную иерархию OO на уровне предприятия в JavaScript, которая делает много вещей из коробки. Это может быть сложное чтение (последнее я проверил в 3.X, это было более 1 МБ сырого, несжатого JavaScript), но это даст вам массу идей. Вы можете начать с просмотр документации, чтобы получить представление высокого уровня.

Ответ 7

Вот пример, показывающий основу программирования OO, которое вы ищете. Лучшим примером для реального мира может быть jQuery.

При изучении JavaScript вы должны иметь в виду, что он на самом деле ближе к Схеме, чем к C или Java. В основном это схема в синтаксисе C.

Практически никогда не бывает случая, когда вы должны использовать "новый" в JavaScript, особенно если вы пишете API. Кажется, что добавлен "новый" оператор, потому что JavaScript не был уверен в своей прототипной структуре. Для большинства из нас, которые начинают программировать на классических языках, таких как C, С++ и Java, это кажется странным, так как "новое", как правило, именно то, что мы ищем, потому что оно легко переводится.

Почему я не должен использовать "новый", который вы спрашиваете? Ну, из-за реализации "нового" вы можете непреднамеренно начать уничтожать свои глобальные данные (которые помнят, все ли не в функции в JavaScript). Если вы попали в жертву этому, вы не увидите никаких ошибок или уведомлений, но увидите только непредсказуемое поведение в вашей программе. Кроме того, может быть или не быть понятно, что "this" действительно связано с вашим "классом".

Уничтожьте свою глобальную память, не зная, что проблема в основном возникает, когда вы пишете функцию, предназначенную для вызова с "новым", и пользователь не использует "новый". Подсказки, почему использование этого в API могут привести к несчастным пользователям.

Правильный способ объектно-ориентированных "классов" и наследования - использовать наиболее мощный атрибут JavaScript... объект.

Вы можете написать функцию, чтобы вернуть объект для создания "класса". Все, что вы помещаете в этот объект (числа, строки, методы и т.д.), Являются общедоступными свойствами вашего "класса". Все, что вы пишете внутри своей функции, которая не находится внутри возвращаемого объекта, является частной.

Чтобы наследовать ваш "класс", вы можете просто инициализировать свой объект, который будет возвращен к вашему "базовому классу", а затем расширить его функциональность.

В следующих разделах кода будет показано, как построить базовый класс с частными и общедоступными переменными и сделать 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'

EDIT: мне сообщили, что я не использовал объект "prototype". Я сделал это, чтобы избежать использования "нового" оператора, как указано в тексте выше. Для полноты я приведу пример использования объекта прототипа ниже...

Наследование с помощью объекта Prototype

//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'

Пожалуйста, не стесняйтесь спрашивать меня о любом из этих сообщений. Наслаждайтесь.

Ответ 8

В настоящее время я использую модель плагина наследования, которая пытается совместить прототипный шаблон OO с шаблоном плагина jQuery. Его подробно изложено в моем ответе здесь: прикрепление класса к объекту jQuery

Примечание: Не отворачивайтесь от упоминания слова Class