需要了解Javascript对象引用

Need to understand Javascript object references

本问题已经有最佳答案,请猛点这里访问。

我正在从JohnResig的网站上查看这段代码。我不明白的是,当忍者对象设置为空对象时,Yell方法仍然对武士可用。

是因为在忍者身边仍然有一个参考资料,它不是垃圾收集的吗?

1
2
3
4
5
6
7
8
9
10
11
12
var ninja = {
  yell: function(n){
    return n > 0 ? yell(n-1) +"a" :"hiy";
  }
};


var samurai = { yell: ninja.yell };

ninja = {};

console.log(samurai.yell(2)); //hiy

http://ejohn.org/apps/learn/14(原始源代码,我稍微修改了一下以删除命名函数表达式)。


在以下代码中:

1
2
3
4
5
var ninja = {
  yell: function(n){
    return n > 0 ? yell(n-1) +"a" :"hiy";
  }
};

ninja.yell的值是对函数的引用。作业:

1
var samurai = { yell: ninja.yell };

为samurai.yell指定一个值,该值是对同一个函数的引用(即ninja.yell引用的函数)。然后:

1
ninja = {};

为忍者分配一个新的空对象值。它对指定给samurai.yell的值没有影响,该值仍引用函数。

变量有值,值有类型。有一个称为引用类型的特殊类型,"…用于解释诸如delete、type of和赋值运算符等运算符的行为"。因此,当一个对象在赋值表达式中时,赋值是类型引用。

因此变量仍然有一个值,但它的值是一个引用。


断开"忍者"的"Yell"属性引用的匿名函数:

1
2
3
4
5
6
7
function yell(n) {
  return n > 0 ? yell(n-1) +"a" :"hiy";
}

var ninja = {
  yell: yell
};

现在,在重新分配"忍者"时,更容易看到函数"yell"不会被"deleted"。

当你这样做的时候:

1
var samurai = { yell: ninja.yell };

你把任何忍者的参考(即function yell()号)指定为"武士。叫喊"。


以javascript解释器将要执行的方式运行执行:

  • 创建一个新的匿名空对象(标识为obj1)。
  • 创建一个名为func1的匿名新函数,该函数执行return n > 0 ? ...操作。
  • 向这个对象添加一个名为yell的新属性obj1,它是函数func1的别名。
  • 将对该对象的引用赋给名为ninja的新根属性(变量可以看作字段属性)。
  • 此时,"堆"看起来如下:

    1
    2
    3
    func1 = n => return n > 0 ? yell(n-1) +"a" :"hiy"
    obj1  = { yell: func1 }
    ninja = obj1
  • 创建一个新的匿名空对象(标识为obj2)。
  • 向这个对象添加一个新属性obj2,称为yell,它是函数func1的别名,而不是ninja.yell的别名,对"函数值"本身的引用被复制到obj2.yell中,而不是对a-reference-to-a-function的引用。
  • obj2分配给samurai
  • 创建一个新的匿名空对象(标识为obj3)。
  • 将对该对象的引用赋给ninja
  • 此时,"堆"看起来如下:

    1
    2
    3
    4
    5
    6
    func1   = n => return n > 0 ? yell(n-1) +"a" :"hiy"
    obj1    = { yell: func1 } // this no-longer has any references and will be GC'd at some point
    obj2    = { yell: func1 }
    obj3    = {}
    samurai = obj2
    ninja   = obj3
  • 打电话给samurai.yell,取消obj2,然后打电话给func1,呼叫成功。

  • 重要的是要查看此源的原始未修改源:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
     1. | var ninja = {
     2. |   yell: function(n){
     3. |     return n > 0 ? ninja.yell(n-1) +"a" :"hiy";
     4. |   }
     5. | };
     6. | assert( ninja.yell(4) =="hiyaaaa","A single object isn't too bad, either." );
     7. |  
     8. | var samurai = { yell: ninja.yell };
     9. | var ninja = null;
    10. |  
    11. | try {
    12. |   samurai.yell(4);
    13. | } catch(e){
    14. |   assert( false,"Uh, this isn't good! Where'd ninja.yell go?" );
    15. | }

    ninja.yell更改为yell是对脚本的无效编辑。

    What I don't understand is when the ninja object is set to an empty object, the yell method is still available to samurai.

    理解JavaScript中的赋值方式很重要。Javascript有很多方便的速记符号,当你刚接触到该语言时,这些符号会让你很难理解。

    1
    var samurai = { yell: ninja.yell };

    是说:

    1
    2
    3
    var samurai;
    samurai = new Object();
    samurai.yell = ninja.yell;

    调用samurai.yell = ninja.yell时,将对ninja.yell函数的引用添加到samurai中。

    在JavaScript中,函数也是对象。它们是通过引用传递的。samurai.yell = ninja.yell没有做的是复制任何类型的对ninja的引用。

    在示例的第9行,var ninja = null不以任何方式修改ninja.yell处的函数。它也不会以任何方式修改存储在ninja上的对象。它所做的是删除对存储在ninja的对象的引用,并将其替换为值null。这意味着在ninja中引用的对象的任何其他副本仍将指向在忍者中引用的对象。

    举个例子更容易看到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var foo,
        bar;

    foo = {
        fizz: 'buzz'
    };
    bar = foo;
    foo = null;
    console.log(bar.fizz); //buzz

    Is it because since there is still a reference lying around to ninja, it wasn't garbage collected?

    在执行示例脚本的第9行之后,不再有任何对已位于ninja的对象的引用。这里有一个关于在ninja.yell上的函数的引用。这意味着ninja对象可以被垃圾收集,但ninja.yell对象(恰好是一个函数)不能。