关于"softBind"功能如何工作的困惑

Tho*_*hor 5 javascript binding prototype function this

我正在学习javascript,遵循"你不懂的js"系列.

在" 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;
    };
}
Run Code Online (Sandbox Code Playgroud)

对于这个冗长的问题,我感到非常抱歉,但我想的不是把问题分成几个帖子,如果将它们放在一个地方就更方便了.

aph*_*hid 4

步骤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();
Run Code Online (Sandbox Code Playgroud)

输出应该是:

10
5
Run Code Online (Sandbox Code Playgroud)

首先,fn1创建一个名为 的类型的对象V。它有一个u保存值的成员变量5。然后,我们创建一个fn2名为Winside类型的对象fn1。它还有一个成员变量u,但这里保存的是值10。如果我们想要insideprint的值,那么我们需要一个指向 的指针。调用inside会输出它的 u 值 (10),这不是我们想要的。所以我们在类的范围内定义了一个变量,为我们保存指针。现在可以访问inside的成员了。V.uWVthis.uWxfn1thisfn1fn2

步骤4

第一个参数是绑定到的对象。您不想将其传递给被绑定的函数,这会破坏其功能,因为它不希望在其正常参数列表前面添加额外的参数。因此,必须删除第一个参数。

第6步:我不太明白“!this”是如何工作的。

!this只是检查是否this已定义的一种方法。如果不是,则该值为true。否则,由于this 将是一个对象(true当转换为布尔值时,其计算结果为),那么它就是false

第7步:完全混乱……

在这里,原作者检查是否this等于window, 或global。笔记; 在现代浏览器中,检查就window足够了,但 IE 存在(非浏览器 JavaScript 环境也是如此)。因此,完整的声明评估为:

如果我不是从对象内部调用的,或者如果我是从对象window或调用的,则返回创建的global对象。softbind否则,返回调用我的对象

请注意,这正是原文章作者想要的。当使用这种特殊绑定调用库函数时,我们可以确定无论该库做什么;它无法global通过使用变量来访问上下文this。但是,它可以访问任何其他对象,从而允许您与库进行交互。

第9步:我不能写“curried.concat(arguments)”吗?

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);
Run Code Online (Sandbox Code Playgroud)

诚然,简洁无助于这个例子的理解。只需将参数重新命名为 CalledArguments and curried(与解释无关的高级数学术语)为 bedefaultArgumentsfor在每个参数上使用简单的循环就会更容易理解,尽管稍微冗长一些。估计作者是想搞点奇葩吧。

步骤10:为什么将“fn.prototype”作为原型分配给“bound.prototype”?

文章上一点到作者谈论默认函数及其工作原理的部分:基本上,在函数调用期间用默认原型bind替换后面的最终结果意味着当使用启用的函数调用时运算符将被设置为其自身,而不是默认的绑定对象。仅调用绑定函数时将不起作用。prototypesoftbindnewthisprototype

它还支持继承softbind,这意味着使用其为启用的函数创建事物prototype不会使该原型softbind被绑定时的原型所否决。(这会使 softbind 与原型不兼容)。相反,两个原型都被使用。

另请参阅此 reddit 帖子

警告的话

我们在这里用新功能扩展了该语言。并非完全必要的功能,并且主要与语义有关。如果您只是对学习该语言感兴趣,那么这确实太过分了,您并不完全需要特殊的绑定语义。this更糟糕的是,如果它的行为不符合您的预期,可能会令人困惑。

更简单的替代方案

启用严格模式。现在this将默认为undefined每当它指向全局对象时。防止这个复杂的代码试图解决的问题(通常会导致尝试访问成员变量或函数的函数产生错误undefined),同时更容易使用,同时它会抱怨很多语法是有效的常规 JavaScript,但在任何正常用例中都是一个错误。另请参阅MDN相关文章。它会为你捕获很多潜在的错误,而不是默默地做无意义的事情。

另一种选择

bind当您将对象的成员函数传递给另一个函数(例如setTimeout. 另一种方法是使用匿名函数。不使用(软)绑定,而是假设obj一个对象持有一个fn正在传递参数的函数param

setTimeout(obj.fn(param), 500);
Run Code Online (Sandbox Code Playgroud)

您可以使用:

setTimeout(function(param){obj.fn(param);}, 500);
Run Code Online (Sandbox Code Playgroud)

这通过传递匿名函数的间接层避免了问题。另见这个问题

  • 关于“柯里化”的含义:它与步骤 4 中未解释的“1”有关。另请参阅 https://en.wikipedia.org/wiki/Currying :将采用多个参数的函数转换为一系列参数的过程采用单个参数的函数。 (2认同)