使用Object.create和命名构造函数的Prototypal OO

时间:2012-02-28 23:14:28

标签: javascript oop prototype object-create

我是从Python和Smalltalk的背景来看Javascript,我很欣赏Self和Lisp在语言中的用法。使用ECMAScript5,我想在没有新操作符的情况下尝试使用原型OO。

约束:

  • 用于创建类的可选new运算符
  • 原型链必须是正确的
  • WebInspector调试支持的命名构造函数
  • alloc()。init()创建序列,如Objective-C和Python

以下是我在实施时尝试达到的标准:

function subclass(Class, Base) {
    "use strict";
    function create(self, args) {
        if (!(self instanceof this))
            self = Object.create(this.prototype);
        var init = self.__init__;
        return init ? init.apply(self, args) : self;
    }
    if (Base instanceof Function) Base = Base.prototype;
    else if (Base===undefined) Base = Object.prototype;

    Class.prototype = Object.create(Base);
    Class.prototype.constructor = Class;
    Class.create = create;

    Class.define = function define(name, fn) { return Class.prototype[name] = fn; };
    Class.define('__name__', Class.name);
    return Class;
}

它似乎在一个简单的模型中工作:

function Family(){return Family.create(this, arguments)}
subclass(Family, Object);
Family.define('__init__', function __init__(n){this.name=n; return this;});

function Tribe(){return Tribe.create(this, arguments)}
subclass(Tribe, Family);
function Genus(){return Genus.create(this, arguments)}
subclass(Genus, Tribe);
function Species(){return Species.create(this, arguments)}
subclass(Species, Genus);

将该类用作工厂函数:

var dog = Species('dog');
console.assert(dog instanceof Object);
console.assert(dog instanceof Family);
console.assert(dog instanceof Tribe);
console.assert(dog instanceof Genus);
console.assert(dog instanceof Species);

或使用new运算符:

var cat = new Species('cat');
console.assert(cat instanceof Object);
console.assert(cat instanceof Family);
console.assert(cat instanceof Tribe);
console.assert(cat instanceof Genus);
console.assert(cat instanceof Species);

console.assert(Object.getPrototypeOf(dog) === Object.getPrototypeOf(cat))

我是否在实施过程中忽略了原型OO所需的功能?是否有我应该进行更改的Javascript约定或交互?总之,这里有什么“问题”,是否有明显的改进?

我想使用构造函数定义成为DRYer,但我发现函数的name属性不可写,这就是支持WebKit Inspector的对象名称。我能够构建一个eval来完成我想要的东西,但是......哎呀。

4 个答案:

答案 0 :(得分:8)

编辑:哦,我现在看到了这个问题。答:不,你说的完全正确。设置函数名称的唯一方法是使用函数声明,这意味着在评估时。因此,您需要在源代码中使用它(eval是后门)。我先前回答了一个更简单的问题,但使用了相同的要点:Minor drawback with Crockford Prototypical Inheritance。关于此主题的另一个资源是http://kangax.github.com/nfe/

有使用displayName属性的动作,以便将函数的不可更改名称与其调试器外观分开。这是在Firefox和其他一些东西中实现的,并且是一个包含在es6中的稻草人,但它还不是暂定规范的一部分:http://wiki.ecmascript.org/doku.php?id=strawman:name_property_of_functions

以下是一些来自Chrome工作人员的论文,内容涉及命名函数http://querypoint-debugging.googlecode.com/files/NamingJSFunctions.pdf

以下是铬问题,讨论为什么它尚未实施:http://code.google.com/p/chromium/issues/detail?id=17356

原来的回答:

你打算完成的任务,你做得很好。我做过类似类型的几个例子:

首先是一个简单的'遗传'功能,它允许你做以下的事情:

var MyObjCtor = heritable({
  constructor: function MyObj(){ /* ctor stuff */},
  super: SomeCtor,
  prop1: val,
  prop2: val,
  /**etc..*/
});


function heritable(definition){
  var ctor = definition.constructor;
  Object.defineProperty(ctor, 'super', {
    value: definition.super,
    configurable: true,
    writable: true
  });
  ctor.prototype = Object.create(ctor.super.prototype);
  delete definition.super;

  Object.keys(definition).forEach(function(prop){
    var desc = Object.getOwnPropertyDescriptor(definition, prop);
    desc.enumerable = false;
    Object.defineProperty(ctor.prototype, prop, desc);
  });

  function construct(){
    var obj = new (ctor.bind.apply(ctor, [].concat.apply([null], arguments)));
    ctor.super.call(obj);
    return obj;
  }

  construct.prototype = ctor.prototype;

  return construct;
}

另一个是创建用于libffi(node-ffi)的结构。从本质上讲,你有构造函数继承和原型继承。您可以创建从构造函数继承的构造函数,这些构造函数创建结构的实例。 https://github.com/Benvie/node-ffi-tools/blob/master/lib/Struct.js

为了创建一个命名函数,需要使用eval,所以如果你需要一个命名的构造函数。我毫不犹豫地在需要的地方使用它。

答案 1 :(得分:4)

Shane我可以告诉你来自你的背景Javascript的行为方式与你尝试的方式不同。在你立即讨厌回答这个问题之前......

我喜欢OOP并且来自非常友好的背景。我花了一些时间来围绕Prototype对象(将原型方法看作静态函数......只能在初始化对象上工作)。在javascript中完成的许多事情都来自使用良好安全性和封装的语言非常“错误”。

使用“new”运算符的唯一真正原因是,如果要创建多个实例或尝试阻止某种类型的逻辑运行,直到触发事件。

我知道这不是答案......输入评论太过分了。要真正获得更好的视角,你应该创建对象和变量并查看你的DOM ......你会发现你可以从任何上下文中获得任何地方。我发现these articles非常有资源填写详细信息。

祝你好运。

<强>更新

因此,如果你确实需要一个工厂方法来创建同一个对象的不同实例,这里有一些我希望我听过的提示:

  • 请您忘记您从OO封装中学到的所有规则。我这样说是因为当你将原型对象,文字对象和实例对象结合在一起时,会有很多方法以一种对你来说不自然的方式访问它。
  • 如果您计划使用任何类型的抽象或继承,我强烈建议您使用DOM。创建一些对象并在DOM中查找它们,看看发生了什么。你可以使用现有的原型模拟你的继承(它杀死了我没有接口和抽象类来扩展!)。
  • 知道javascript中的所有内容都是一个对象...这允许你做一些非常酷的东西(传递函数,返回函数,创建简单的回调方法)。查看javascript调用'.call()','。apply()'和'.bind()'。

了解这两件事可以帮助您解决问题。为了保持DOM清洁并遵循良好的javascript开发实践,可以保持全局命名空间的清洁(DOM中的窗口引用)。这也会让你对所做的事情感觉“更好”,你觉得你所做的事情是“错误的”。并保持一致。

这些是我通过反复试验所学到的东西。如果你可以把它与经典的OO语言区别开来,Javascript是一种伟大而独特的语言。祝你好运!

答案 2 :(得分:3)

这可能无法解答您的问题,这可能并不是您想要的,但这是另一种关于OO JavaScript的方法。我喜欢它主要是因为语法;我更容易理解,但我没有你的背景。

// ------- Base Animal class -----------------------
var Animal = function(name) {
    this.name = name;
};

Animal.prototype.GetName = function() {
    return this.name;
};
// ------- End Base Animal class -----------------------

// ------- Inherited Dog class -----------------------
var Dog = function(name, breed) { 
    Animal.call(this, name);
    this.breed = breed;
};

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

Dog.prototype.GetBreed = function() {
  return this.breed; 
};

Dog.prototype.Bark = function() {
    alert(this.GetName() + ' the ' + this.GetBreed() + ' says "Arf!"');
};
// ------- End Inherited Dog class -----------------------

// Usage
var harper = new Dog('Harper', 'miniature pinscher');
harper.Bark();

答案 3 :(得分:1)

如果没有eval,则无法创建命名构造函数。所有你会发现的是一个构造函数,如果库实现名称空间,则带有一个带有名称的隐藏属性。

回答其余问题,有很多javascript类库。你可以自己编写,但如果你想以正确的方式去理解和实现正确的继承系统,那就太麻烦了。

我最近编写了自己的类库,试图改进所有库:Classful JS。我认为值得一看,因为它简单的设计,用法和其他一些改进,比如从子类中调用任何超级方法(我没有看到任何可以做到这一点的库,所有这些都只能调用正在进行的超级方法压倒一切)。