您写道,现有答案没有足够的细节,但即使在阅读了您的具体问题之后,我也不能完全确定代码的哪些方面让您陷入了循环——它有许多棘手的部分——所以如果这个答案过于详细地说明了你已经理解的事情,我提前道歉!
由于makeClass 总是意味着以相同的方式调用,如果我们删除一个间接级别,它会更容易推理。这个:
var MyClass = makeClass();
等价于:
function MyClass(args)
{
if ( this instanceof arguments.callee )
{
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
}
else
return new arguments.callee( arguments );
}
由于我们不再处理匿名函数,我们不再需要arguments.callee 表示法:它必然引用MyClass,所以我们可以用MyClass 替换它的所有实例,给出:
function MyClass(args)
{
if ( this instanceof MyClass )
{
if ( typeof this.init == "function" )
this.init.apply( this, args.callee ? args : arguments );
}
else
return new MyClass( arguments );
}
其中args 是MyClass 的first 参数的标识符,而arguments 一如既往地是一个类似数组的对象,其中包含all MyClass 的论点。
只有当“类”在其原型中有一个名为 init 的函数(这将是“构造函数”)时,才会到达您所询问的行,所以让我们给它一个:
MyClass.prototype.init =
function (prop)
{
this.prop = prop;
};
一旦我们这样做了,考虑一下:
var myInstance1 = new MyClass('value');
在对MyClass 的调用中,this 将引用正在构造的对象,因此this instanceof MyClass 将为真。而typeof this.init == "function" 将是真实的,因为我们使MyClass.prototype.init 成为一个函数。所以我们到达了这一行:
this.init.apply( this, args.callee ? args : arguments );
这里args等于'value'(第一个参数),所以它是一个字符串,所以它没有callee属性;所以args.callee 是未定义的,在布尔上下文中意味着它是假的,所以args.callee ? args : arguments 等价于arguments。因此,上面一行等价于:
this.init.apply(this, arguments);
相当于这个:
this.init('value');
(如果您还不知道apply 的工作原理以及它与call 的区别,请参阅https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/apply)。
到目前为止,这有意义吗?
要考虑的另一种情况是:
var myInstance2 = MyClass('value');
在对MyClass 的调用中,this 将引用全局对象(通常为window),因此this instanceof MyClass 将是假的,所以我们到达这一行:
return new MyClass( arguments );
其中arguments 是一个包含单个元素的类数组对象:'value'。请注意,这不与new MyClass('value') 相同。
术语说明: 所以对MyClass('value') 的调用会导致对MyClass 的第二次调用,这次调用new。我将把第一个调用(没有new)称为“外部调用”,将第二个调用(使用new)称为“内部调用”。希望这是直观的。
在对MyClass 的内部调用中,args 现在指的是外部调用的arguments 对象:而不是args 是'value',它现在是一个包含'value' 的类数组对象。而不是args.callee 是未定义的,它现在指的是MyClass,所以args.callee ? args : arguments 等价于args。所以对MyClass的内部调用是调用this.init.apply(this, args),相当于this.init('value')。
因此对args.callee 的测试旨在区分内部调用(MyClass('value') → new MyClass(arguments))和正常的直接调用(new MyClass('value'))。理想情况下,我们可以通过替换这一行来消除该测试:
return new MyClass( arguments );
假设一些看起来像这样的东西:
return new MyClass.apply( itself, arguments );
但 JavaScript 不允许这种表示法(也不允许任何等效的表示法)。
顺便说一句,你可以看到 Resig 的代码存在一些小问题:
- 如果我们定义了一个构造函数
MyClass.prototype.init,然后我们通过编写var myInstance3 = new MyClass(); 来实例化“类”,那么args 将在对MyClass 的调用中未定义,因此对args.callee 的测试将引发一个错误。我认为这只是 Resig 的一个错误。无论如何,通过在args && args.callee 上进行测试很容易解决此问题。
- 如果我们的构造函数的第一个参数恰好有一个名为
callee 的属性,那么对args.callee 的测试将产生误报,并且错误的参数将被传递给构造函数。这意味着,例如,我们不能将构造函数设计为将arguments 对象作为其第一个参数。但这个问题似乎很难解决,而且可能不值得担心。