我目前正在通过"你不知道js"系列来学习javascript。
在"this & object prototype"一节中,作者提出了一种软绑定this
的方法。
但是,我对代码感到非常困惑。所以我想知道是否有人可以一步一步地向我解释一下,代码到底做了什么?
//step 1: if "softBind" property does not exist on `Function.prototye`
if (!Function.prototype.softBind) {
//step 2: create a property named "softBind" on "Function.prototype" and assign to "softBind" the following function
Function.prototype.softBind = function(obj) {
//step 3: what is the point of assigning "this" to the variable "fn"?
//what does "this" represent at this point in time?
var fn = this,
//step 4: I understand that "arguments" is an array-like object, i.e. "arguments" is not a true array.
//But you can convert "arguments" to an true array by using "[].slice.call(arguments)".
//The thing I dont understand here is, why the 1?
//I understand it tells "slice" method to start slicing at index 1, but why 1?
//And what is the purpose of "curried" variable?
curried = [].slice.call( arguments, 1 ),
bound = function bound() {
//step 5: I understand what "apply" function does
return fn.apply(
//step 6: I dont really understand how "!this" works.
(!this ||
//step 7: utterly confused...
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
//step 8: if the above statements evaluates to "true", then use "obj",
//otherwise, use "this"
) ? obj : this,
//step 9: can't I write "curried.concat(arguments)" instead?
//why the convoluted syntax?
curried.concat.apply( curried, arguments )
);
};
//step 10: Why assign the "fn.prototype" as the prototype to "bound.prototype"?
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
对于这个冗长的问题,我很抱歉,但我认为与其将问题分成几个帖子,不如将它们放在一个地方更方便。
第 3 步:将"this"分配给变量"fn"有什么意义?
this
的值包含指向当前正在执行的函数对象的指针。只能保存函数对象,因此只能保存通过 new() 或等效表示法实际创建的内容。这对于将对外部对象的引用传递到在该外部对象中创建的内部对象非常有用。
下面是一个最小示例:
function fn1() {
var x = this; // x = fn1.
this.u = 5;
function fn2() {
this.u = 10;
console.log(this.u); // Prints the member of fn2.
console.log(x.u); // Prints the member of fn1.
};
var W = new fn2();
}
var V = new fn1();
输出应为:
10
5
首先,创建一个名为V
的fn1
类型的对象。它有一个成员变量u
保存值5
。然后,我们在fn1
中创建一个名为W
的fn2
类型的对象。它还有一个成员变量u
,但这里它保存的值10
。如果我们想在W
内print
V.u
的值,那么我们需要一个指向V
的指针。在W
中调用this.u
将输出其 u 值 (10),这不是我们想要的。所以我们在类fn1
的范围内定义一个变量x
,为我们持有this
指针。现在可以在fn2
内访问fn1
的成员。
步骤 4
第一个参数是绑定到的对象。您不希望将其传递给被绑定的函数,这会破坏其功能,因为它不希望在其正常的参数列表前面附加额外的参数。因此,必须删除第一个参数。
第 6 步:我真的不明白"!this"是如何工作的。
!this
只是一种检查是否定义了this
的方法。如果不是,则值将为true
。否则,由于this
将是一个对象(当转换为布尔值时计算结果为true
),那么它是false
.
第 7 步:完全困惑...
在这里,原作者检查this
是否等于window
或global
。注意;在现代浏览器中,只检查window
就足够了,但IE存在(非浏览器JavaScript环境也是如此)。因此,完整的语句评估了这件事:
如果我不是从对象内部调用的,或者如果我是从对象window
或global
调用的,则返回创建softbind
的对象。否则,返回从中调用我的对象
请注意,这正是原始文章的作者想要的。当使用这个特殊绑定调用库函数时,我们可以确定无论库做什么;它无法通过使用this
变量访问global
上下文。但是,它可以访问任何其他对象,允许您与库进行交互。
第 9 步:我不能写"curried.concat(参数)"吗?
Curried
保存调用原始softbind
函数时使用的所有参数,但第一个参数除外。arguments
,此时不等于前面调用中的arguments
。在这里,它指的是调用绑定函数的参数,而不是绑定函数的参数。此行集成了两组参数,允许您提供默认参数。这里使用的技巧是连接参数,例如,假设您的函数[1,2,3,4]
具有默认参数,并且您提供[5,6]
:
[1,2,3,4].concat([5,6])
产生[1,2,3,4,5,6]
。
为什么不简单地连接并使用原型?数组在 javascript 中通过引用传递,因此这将保持curried
相同,同时将arguments
连接到调用。等价地,你可以这样写:
curried2 = curried.concat(arguments);
return fn.apply(
(.....)
curried2);
诚然,简洁无助于这个例子的可理解性。简单地将参数重命名为 calledArguments 和curried
(与解释无关的高级数学术语)以defaultArguments
,并在每个参数上使用简单的for
循环,如果更冗长一点,也会更容易理解。我猜作者想花哨。
第 10 步:为什么要将"fn.prototype"作为原型分配给 "bound.prototype"?
在文章中稍微上一点,到作者讨论默认bind
函数及其工作原理的部分:基本上,在函数调用期间将prototype
替换回默认原型的最终结果意味着,当使用new
运算符调用启用softbind
函数时,this
将设置为自身, 而不是默认绑定对象。prototype
在简单地调用绑定函数时不起作用。
它还支持继承,这意味着使用其prototype
为启用softbind
的函数创建内容不会使该原型在绑定时被softbind
的原型否决。(这将使软绑定与原型不兼容)。相反,使用这两个原型。
另请参阅此reddit帖子。
一句警告
我们将在此处使用新功能扩展语言。并非完全必要的功能,主要与语义有关。如果你只是对学习语言感兴趣,这真的太过分了,你并不完全需要特殊的绑定语义。更糟糕的是,如果this
没有按照您期望的方式运行,可能会令人困惑。
更简单的选择
启用严格模式。现在,每当this
指向全局对象时,它将默认为undefined
。防止这个复杂的代码试图解决的问题(通常会导致尝试访问成员变量或undefined
函数的函数出错),同时更容易使用,同时它会抱怨很多语法是有效的常规 javascript,但在任何正常用例中都是错误。另请参阅有关它的 MDN 文章。它会为你捕捉到很多潜在的错误,而不是默默地做无意义的事情。
另一种选择
bind
尝试解决在将对象的成员函数传递给另一个函数(如setTimeout
)时"丢失"对象的问题。另一种方法是使用匿名函数。而不是使用(软)绑定,假设obj
是一个持有函数fn
传递参数param
的对象;
setTimeout(obj.fn(param), 500);
您可以使用:
setTimeout(function(param){obj.fn(param);}, 500);
它通过传递匿名函数通过间接层避免了问题。另请参阅此问题。