8

我是从 Python 和 Smalltalk 的背景开始接触 Javascript 的,我很欣赏 Self 和 Lisp 在语言中的传承。使用 ECMAScript5,我想在没有 new 运算符的情况下尝试原型 OO。

约束:

  • 用于创建类的可选 new 运算符
  • instanceof 的原型链必须正确
  • 用于 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

4 回答 4

10

编辑:哦,我现在看到了这个问题。答:不,你完全正确。设置函数名称的唯一方法是在使用函数声明时,这意味着在评估时。因此,您需要将它包含在源代码中(其中 eval 是进入的后门)。我之前回答了一个更简单的问题,但要点相同: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 ,所以如果您需要命名构造函数,那就是这样。我毫不犹豫地在需要的地方使用它。

于 2012-03-02T21:51:40.513 回答
4

Shane 我可以告诉你来自你的背景 Javascript 的行为方式与你试图做的事情不同。在您立即讨厌该答案之前,请继续阅读...

我喜欢 OOP 并且来自对 OOP 非常友好的背景。我花了一些时间来理解 Prototype 对象(将原型方法视为静态函数......仅适用于初始化对象)。来自使用良好安全性和封装性的语言的许多用 javascript 完成的事情感觉非常“错误”。

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

我知道这不是答案......在评论中输入太多了。要真正获得更好的视角,您应该创建对象和变量并查看您的 DOM ......您将意识到您可以从任何上下文中获得任何地方,因此。我发现这些文章非常适合填写详细信息。

祝你好运。

更新

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

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

了解这两件事可以帮助您解决解决方案。要保持 DOM 干净并遵循良好的 javascript 开发实践,请保持全局命名空间干净(DOM 中的窗口引用)。这也会让你对所有你觉得你正在做的事情“错误”的事情感觉“更好”。并保持一致。

这些都是我通过反复试验学到的。Javascript 是一种伟大而独特的语言,如果你能理解它与经典 OO 语言的区别。祝你好运!

于 2012-03-03T04:41:50.313 回答
4

这可能无法回答您的问题,也可能无法满足您的所有需求,但这是了解 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();
于 2012-03-06T22:57:19.113 回答
1

没有 eval 就不能创建命名构造函数。如果库实现了命名空间,那么您会发现一个带有隐藏属性的构造函数。

回答剩下的问题,有很多 javascript 类库。您可以自己编写,但如果您想以正确的方式进行操作,理解和实现正确的继承系统会有点混乱。

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

于 2012-03-06T14:52:12.407 回答