我目前正在按照"你不知道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;
};
}
对于这个冗长的问题,我感到非常抱歉,但我想的不是将问题分成几个帖子,如果将它们放在一个地方就更方便了。
答案 0 :(得分:4)
第3步:分配"这是什么"变量" 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
首先,创建一个名为fn1
的{{1}}类型的对象。它有一个成员变量V
,其值为u
。然后,我们在5
中创建一个名为fn2
的{{1}}类型的对象。它还有一个成员变量W
,但在这里它包含值fn1
。如果我们想在u
内10
print
的值,那么我们需要一个指向V.u
的指针。在W
内调用V
会输出其u值(10),这不是我们想要的。因此,我们在类this.u
的范围内定义变量W
,为我们保留x
指针。现在,您可以在fn1
内访问this
的成员。
第4步
第一个参数是绑定的对象。您不希望将其传递给绑定的函数,这将破坏其功能,因为它不期望在其正常的参数列表之前添加额外的参数。因此,必须删除第一个参数。
第6步:我真的不明白"!这个"的工作原理。强>
fn1
只是一种检查fn2
是否已定义的方法。如果不是,那么该值将为!this
。否则,因为this
将是一个对象(在转换为布尔值时评估为true
),然后它是this
。
第7步:完全混淆......
此处,原作者会检查true
是否等于false
或this
。注意;在现代浏览器中,仅检查window
就足够了,但IE存在(非浏览器javascript环境也是如此)。所以,完整的陈述评估这件事:
如果我没有从对象中调用,或者我从对象global
或window
调用,则返回创建的对象window
用。否则,从
请注意,完全原始文章的作者想要的内容。当使用这种特殊绑定调用库函数时,我们可以确定无论库是什么;它无法通过使用global
变量访问softbind
上下文。但是,它可以访问任何其他对象,允许您与库进行交互。
第9步:我不能写" curried.concat(参数)"代替吗
global
包含调用原始this
函数的所有参数,第一个参数除外。此时Curried
在前一次调用中不等于softbind
。这里,它引用了绑定函数的参数,而不是它绑定的参数。此行集成了两组参数,允许您提供默认参数。这里使用的技巧是连接参数,例如假设您的函数具有默认参数arguments
并且您提供arguments
:
[1,2,3,4]
生成[5,6]
。
为什么不简单地连接,并使用原型?数组通过javascript中的引用传递,因此这将使[1,2,3,4].concat([5,6])
保持相同,同时将[1,2,3,4,5,6]
连接到调用。同样地,你可以这样写:
curried
不可否认,简洁性并没有帮助这个例子的可理解性。只需将参数重新命名为calledArguments并将arguments
(与解释无关的高级数学术语)重命名为curried2 = curried.concat(arguments);
return fn.apply(
(.....)
curried2);
,并在每个参数上使用简单的curried
循环将更容易理解,如果更冗长一点。我想作者想要看上去。
第10步:为什么要分配" fn.prototype"作为" bound.prototype"?
的原型在文章上稍微介绍一下作者谈论默认defaultArguments
函数及其工作原理的部分:基本上,将for
替换为默认原型的最终结果函数调用意味着当调用bind
启用的函数时,prototype
运算符softbind
将被设置为自身,而不是默认的绑定对象。简单地调用绑定函数时,new
将不起作用。
它还启用继承,这意味着使用this
为prototype
启用的函数创建内容不会被softbind
的原型推翻什么时候绑定。 (这会使softbind与原型不兼容)。而是使用两个原型。
警告语
我们在此处使用新功能扩展语言。功能并非完全必要,并且主要与语义有关。如果您只是对学习语言感兴趣,这实在太过分了,您并不需要特殊的绑定语义。更糟糕的是,如果prototype
没有按照您期望的方式行事,可能会造成混淆。
更简单的替代方案
启用严格模式。现在,softbind
只要指向全局对象,就会默认为this
。防止这个复杂的代码试图解决的问题(通常导致试图访问成员变量或this
的函数的函数出错),同时更容易使用,同时它会抱怨很多语法都是有效的常规javascript,但是在任何正常用例中都会出现错误。另请参阅MDN article。它会为你捕获很多潜在的错误,而不是默默地做无意义的事情。
另一种选择
undefined
试图解决'失败'将对象的成员函数传递给另一个函数时的对象,例如undefined
。另一种方法是使用匿名函数。假设bind
是一个持有函数setTimeout
的对象,而不是使用(软)绑定,而是传递参数obj
;
fn
您可以使用:
param
通过传递匿名函数,通过一层间接避免了这个问题。另请参阅this question。