(问题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
是如何通过的?
所以我决定必须要发生的事情是:
o
和arguments
变量(即使arguments
等于6)被传递给函数。只是当函数g
最终被调用时,arguments
变量被解释器重新定义为g
(在g(2)
中)的自变量,因此我试图传递的自变量的原始值被替换了。但这意味着,在此之前,它一直将函数存储为文本,因为否则o
和arguments
将只是程序中的数据,而不是可以重写的变量。这个解释正确吗?
(问题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