验证我对作用域链的理解



(问题1)在Flanagan的JS Definitive Guide中,他定义了Function方法bind(),以防它不可用(在ECMAScript 3中不可用)。

它看起来像这样:

function bind(f, o) {
    if (f.bind) return f.bind(o);     // Use the bind method, if there is one
    else return function() {          // Otherwise, bind it like this
        return f.apply(o, arguments);
    };
}

他用一个例子说明了它的使用(我已经修改了它,将第三行从f.bind(o)改为)

function f(y) { return this.x + y; } // This function needs to be bound
var o = { x : 1 };                   // An object we'll bind to
var g = bind(f, o);                  // Calling g(x) invokes o.f(x)
g(2)                                 // => 3

当我第一次看到这一点时,我想"arguments不是指我们定义的绑定函数中的arguments变量吗?但我们想要最终应用它的函数的argument属性,就像上面例子中的g…"

我验证了他的例子确实有效,并推测直到上面的var g = bind(f, o)才评估行return f.apply(o, arguments)。也就是说,我想,当你返回一个函数时,你只是返回该函数的源代码吗?直到它被评估?所以我通过尝试一个稍微不同的绑定版本来测试这个理论:

function mybind2(f, o) {
    var arguments = 6;
    return function() {          // Otherwise, bind it like this
        return f.apply(o, arguments);
    };
}

如果它只是返回未求值的函数源,那么在以后求值时就不可能存储arguments = 6,对吧?检查后,我仍然得到g(2)=>3。但后来我意识到——如果它只是返回未评估的代码,那么return f.apply(o, arguments)中的o是如何通过的?

所以我决定必须要发生的事情是:

oarguments变量(即使arguments等于6)被传递给函数。只是当函数g最终被调用时,arguments变量被解释器重新定义为g(在g(2)中)的自变量,因此我试图传递的自变量的原始值被替换了。但这意味着,在此之前,它一直将函数存储为文本,因为否则oarguments将只是程序中的数据,而不是可以重写的变量。这个解释正确吗?

(问题2)在同一页的早些时候,他定义了以下函数,该函数使用apply方法跟踪函数进行调试:

function trace(o, m) {
    var original = o[m];  // Remember original method in the closure.
    o[m] = function() {   // Now define the new method.
        console.log(new Date(), "Entering:", m);      // Log message.
        var result = original.apply(this, arguments); // Invoke original.
        console.log(new Date(), "Exiting:", m);       // Log message.
        return result;                                // Return result.
    };
}

这里的this不是指我们正在定义的函数,而不是对象o吗?还是这两件事是一样的?

问题1

对于你的第一个问题,让我们简化这个例子,这样就可以清楚地知道要做什么:

function bind(func, thisArg) {
    return function () {
        return func.apply(thisArg, arguments);
    };
}

这里发生的是创建一个闭包,允许访问原始函数和传递的this值。返回的匿名函数会将原始函数保留在其作用域中,最终如下所示:

var func = function () {};
var thisArg = {};
func.apply(thisArg, [/*arguments*/]);

关于arguments的问题,该变量是在创建的所有函数的作用域中隐式定义的,因此内部arguments将覆盖外部arguments,使其按预期工作。

问题2

你的问题是对this的后期绑定的误解——对于习惯于使用更面向对象的语言(也有自己的this关键字)的人来说,这是JavaScript更令人困惑的事情之一。this的值仅在调用时设置,在调用的位置定义了调用时this的值。它只是调用函数时父对象所在位置的值this值被重写的情况除外。

例如,请参见:

function a() {return this;};
a(); // global object;
var b = {};
b.a = a;
b.a(); // object b

如果在定义函数时设置了this,那么调用b.a将产生全局对象,而不是b对象。让我们还简化第二个给定函数的情况:

function trace(obj, property) {
    var method = obj[property];  // Remember original method in the closure.
    obj[property] = function () {   // Now define the new method.
        console.log(1); // Log message.
        // Invoke original method and return its result
        return original.apply(this, arguments);
    };
}

在这种情况下,原始方法存储在闭包中。分配给方法最初所在的对象不会覆盖method对象。就像普通的方法赋值一样,this值的原理仍然相同——它将返回父对象,而不是您定义的函数。如果你做一个简单的任务:

var obj = {};
obj.foo = function () { return this; };
obj.foo(); // obj

它执行预期的操作,在调用时返回父对象。在这方面,将代码放在嵌套函数中没有什么区别。

一些好的资源

如果你想了解更多关于用JavaScript编写代码的知识,我强烈建议你看看Cody Lindley的《完全理解this关键字》,它更详细地介绍了this关键字在不同上下文中的行为以及你可以用它做什么

但这意味着,在此之前,它一直将函数存储为文本,因为否则o和参数将只是程序中的数据,而不是可能被覆盖的变量。这个解释正确吗?

没有。CCD_ 39和CCD_。他们不遵守正常的"关闭规则"。函数定义本身仍然会立即求值,并且bind返回一个函数对象
您可以使用以下工具轻松验证:

var g = bind(f, o);
console.log(typeof g);

这里有一个不涉及高阶函数的简单例子:

var arguments = 42;
function foo() {
    console.log(arguments);
}
foo(1, 2);

我想您已经看到foo的定义是按照您的预期进行评估的。然而,console.log(arguments)记录[1, 2]而不是42

这里的this不是指我们正在定义的函数,而不是对象o吗?还是这两件事是一样的?

this从不引用函数本身(除非显式设置为这样)。this的值完全由函数的调用方式决定。这就是为什么this经常被称为"上下文"的原因。MDN文档提供了有关this的大量信息。


阅读材料:

  • MDN-this
  • MDN-arguments

最新更新