在Javascript中访问内部函数变量

在Javascript中访问内部函数变量,javascript,unit-testing,private-methods,Javascript,Unit Testing,Private Methods,例如,在许多框架中,内部函数变量用作私有变量 Raphael = (function(){ var _private = function(a,b) {return a+b;}; var _public = function(a) {return _private(a,a);} var object = {mult2:_public}; return object; })(); 在这里,我们不能从全局名称空间访问名为private的变量,因为它是第一行中匿名函数

例如,在许多框架中,内部函数变量用作私有变量

Raphael = (function(){
    var _private = function(a,b) {return a+b;};
    var _public = function(a) {return _private(a,a);}
    var object = {mult2:_public};
    return object;
})();
在这里,我们不能从全局名称空间访问名为
private
的变量,因为它是第一行中匿名函数的内部变量

有时,这个函数包含一个大的Javascript框架,这样它就不会污染全局名称空间

我需要对Raphael在内部使用的一些对象进行单元测试(在上面的示例中,我希望对对象
private
运行单元测试)。我如何测试它们

编辑:我收到了关于单元测试的评论,单元测试应该测试公共接口

让我指定一个用例。我正在写一个名为拉斐尔的库。这个库应该只向全局名称空间添加一个名称,而不是其他名称。这是Javascript的特殊要求,因为Javascript没有名称空间

让我们假设
Raphael
使用一个链表。如果Javascript有包的概念,我会这么做

require 'linked_list'
Raphael = (function(){/* use linked list */})();
然而,Javascript不允许我以任何不会污染链表对象的全局范围的方式来做这件事!因此,我必须将
链表
内联到拉斐尔的局部范围中:

Raphael = (function(){
    /* implement linked list */
    var linked_list = function(){/*implementation*/};
})();
现在我想测试链表的实现。

试试这个:

var Raphael;
var test = true; //or false;

Raphael = (function(){
    var private = function(a,b) {return a+b;};
    var public = function(a) {return private(a,a);}
    var object = {mult2:public};

    if (test) Raphael.private = private;

    return object;
})();
var adder = function(a,b) {
    return a + b;
}

Raphael = function(fn){
    var _private = function(a,b) {
        fn(a,b);
    }

    var _public = function(a) {
        return _private(a,a);
    }

    var object = {doubleIt: _public};

    return object;
}(adder);

只需一点函数注入

您仍然没有抓住要点

单元测试的目的是验证对象的公共接口是否符合预期。单元测试展示了代码是如何工作的

唯一需要测试的是对象的公共接口。这样,当开发人员想要更改对象的实现方式时,您只需担心被测试的对象是否仍能达到预期效果

如果您觉得该闭包中的对象需要测试,那么就进行测试,但在外部进行测试,然后将其传递到闭包中

var Raphael= function(listIterator) {
  listIterator.method();
}(new ListIterator());
下面所示的虚假黑客是完全不合适的(在单元测试或任何地方)

测试函数应该简单,只测试一件事,并且有一个断言。这通常发生在三到十行测试代码中

当您的测试函数变得复杂时,因为它们将遵循您所询问的方法,那么 (1) 认识到你的设计可能不是你想要的,并改变它,使之成为现实,或者(2)在测试中改变你的期望

关于您发布的代码,您忘记了
var
,漏掉了一个分号,并使用了两个保留字作为标识符:
private
public

不使用
var
的后果是有可能触发与非标准
globalScopeInnoiser
类型对象(“对象不支持此属性或方法”见IE)的各种实现相关的错误和问题。使用FutureReservedWord的结果是
语法错误
。实现可能会提供一个语法扩展,以允许FutureReservedWord作为标识符,事实上很多实现都是这样,但是最好不要依赖这些扩展,如果出现错误,那完全是您的错

您提到了向用户交付代码。我建议你在获得更多的经验和理解之前不要这样做

// DO NOT USE THIS CODE.
var Raphael = (function(){
    var _private = function(a,b) {return a+b;};
    var _public = function(a) {return _private(a,a);};
    var object = {mult2:_public};
    return object;
})();

var leakedFunction;

// Spurious hack:
//   Give valueOf a side effect of leaking function.
//   valueOf is called by the _private function as a
//   side effect of primitive conversion, where 
//   ToPrimitive(input argument, hint Number) results 
//   in calling valueOf.

function valueOfSnoop(){ 
    leakedFunction = leakedFunction || valueOfSnoop.caller || function(){};
    return 2;
}

var a = {
  valueOf : valueOfSnoop
};

Raphael.mult2(a, 3);
var privateMathod = leakedFunction;
alert(leakedFunction(1, 2));

那个示例代码只是为了证明这样的事情是可能的。考虑到选择,它是前面提到的替代方案的一个糟糕的替代方案;要么更改设计,要么更改测试

我想出的最佳解决方案是:

在源代码中使用Javascript文件

Raphael = (function(){
// start linked_list
    var linked_list = function() {/*...*/};
// end linked_list
    var object = {mult2:_public};
    return object;
})();
现在,使用一个脚本来提取
//开始([a-zA-Z]*)
//结束([a-zA-Z]*)
之间的对象,并对提取的代码进行单元测试


显然,从外部作用域访问函数内部作用域中的变量是不可能的。正如Jason在评论中链接到的SO问题中所述。

unittests背后的思想是测试公共函数/方法only@Andrey,Javascript不是你的日常语言!您无法以正常的方式导入其他模块,因此如果我将包含一个小的(比如)链表实现供Raphael使用,那么它必须内联到Raphael的内部函数中,这样它就不会向Raphael的用户公开。我不能只
#包含它
请参见:等等,当我将库交付给用户时,如何取消“测试”代码,
#ifdef
;-)是的,您必须在文件的顶部声明测试变量或其他什么。在函数体中使用
private
作为变量的整个想法是为了不污染全局范围。在您的解决方案中,
Raphael
adder
污染了全局范围。如果您打算使用“私有属性”,您必须有一个传感变量/函数来测试私有属性。或者你必须坚持使用公共界面。此外,您还可以向代码中添加另一个名称空间层,并将其私有化,而不是其所在位置。单元测试较低级别,然后在新层API上运行集成测试。@Garrett,声明函数(){}而不仅仅是{}的任何原因。我喜欢!通常在声明
var
时,我喜欢只为防御编码而对类型进行编码-我这样做是因为
leakedFunction
稍后会被调用。但只有在您使用此
参数重新分配它之后。callee.caller
。是吗?嗯,是的,
leakedFunction被设置为
arguments.callee.caller`inside
valueOf
。我这样做是为了防止function.caller或arguments.callee不可用。任务应该在里面,我可以重用一个函数。。。编辑。。。