Home > Article > Web Front-end > In-depth analysis of interesting anti-currying in JavaScript_Basic knowledge
写在前面的话:国内对前端的研究在某些方面也不逊色于国外,这篇文章虽然看不太懂,但我很欣赏这种深入研究的精神!
反科里化的话题来自javascript之父Brendan Eich去年的一段twitter. 近几天研究了一下,觉得这个东东非常有意思,分享一下。先忘记它的名字,看下它能做什么.
不要小看这个功能,试想下,我们在写一个库的时候,时常会写这样的代码,拿webQQ的Jx库举例。
我们想要的,其实只是借用Array原型链上的一些函数。并没有必要去显式的构造一个新的函数来改变它们的参数并且重新运算。
如果用uncurrying的方式显然更加优雅和美妙,就像这样:
还能做很多有趣和方便的事情.
甚至还能把call和apply方法都uncurrying,把函数也当作普通数据来使用. 使得javascript中的函数调用方式更像它的前生scheme, 当函数名本身是个变量的时候, 这种调用方法特别方便.
再看看jquery库,由于jquery对象( 即通过$()创建的对象 )是一个对象冒充的伪数组,它有length属性,并且能够通过下标查找对应的元素,当需要给jquery对象添加一个成员时, 伪代码大概是:
如果用uncurrying的话, 就可以
借用了array对象的push函数, 让引擎去自动管理数组成员和length属性.
而且可以一次把需要的函数全部借过来, 一劳永逸. 一段测试代码:
总的来说, 使用uncurrying技术, 可以让任何对象拥有原生对象的方法. 好了,如果到这里依然没有引起你的兴趣,那么你可以去干点别的了。
接下来一步一步来看看原理以及实现。
在了解反currying化这个奇怪的名字之前,我们得先搞清楚currying。
Definition from Wikipedia: Currying; also known as partial evaluation, is to transform a function that accepts multiple parameters into a function that accepts a single parameter, and returns a new function that accepts the remaining parameters and returns the result. Function technology.
To put it simply, currying is similar to the installment payment method when buying a house. You first give a part of the down payment (part of the parameters), return a passbook (return a function), and then give the remaining parameters and evaluate the calculation when appropriate.
Let’s take a look at the currying we have all used. We often implement a Function.prototype.bind function when binding context.
Higher-order functions are the basis for currying. The so-called higher-order functions at least meet these two characteristics:
1. Functions can be passed as parameters;
2. Functions can be used as return values.
At the beginning of its design, Javascript referred to many features of the scheme language. Scheme is one of the two major dialects of Lisp, the originator of functional languages, so JavaScript also has some features of functional languages, including higher-order functions, closures, lambda expressions, etc.
When a function in JavaScript returns another function, a closure will be formed, and the parameters of the first operation can be saved in the closure. We use this idea to write a general currying function.
We agree that when parameters are passed in, currying will continue, and evaluation will only start when the parameters are empty.
Suppose we are implementing a function that calculates monthly expenses. Before the end of each day, we have to record how much money we spent today, but we only care about the total cost at the end of the month, and there is no need to calculate it every day.
Using the currying function, calculations can be delayed until the last moment. The benefits are self-evident. In many cases, unnecessary calculations can be avoided and performance is saved. It is also a solution to achieve lazy evaluation.
Okay, let’s get to the point now,
curring is pre-filling some parameters.
Anti-curring means deferring the originally fixed parameters or this context as parameters to the future.
In fact, it is to do something like this:
obj.foo( arg1 ) //foo is originally a function only on obj. Just like push is originally only on Array.prototype
Convert to this form
foo( obj, arg1 ) // Same as our first example. Convert [].push to push( [] )
Just like the socket that was originally connected to the TV plug, after removing it, it can actually be used to connect the refrigerator.
Each prototype method of Array and String on Ecma has such a paragraph after it, such as push:
NOTE The push function is intentionally generic; it does not require that its this value be an Array object.
Therefore it can be transferred to other kinds of objects for use as a method. Whether the concat function can be applied .
Why is Javascript designed this way? Let’s first review the important duck typing ideas in dynamic languages.
Tell a story:
A long time ago, there was an emperor who liked to hear ducks quacking, so he summoned his ministers to form a choir of a thousand ducks. The minister caught all the ducks in the country, but in the end there was still one missing. One day, a volunteer chicken finally came. This chicken said that it could also croak. Well, in the setting of this story, it did indeed croak. Later in the story it becomes clear that the chicken got mixed up in the duck's choir. - The emperor just wants to hear croaking, he doesn't care whether you are a duck or a chicken.
This is the concept of duck typing. In JavaScript, many functions do not do object type detection, but only care about what these objects can do.
The methods on the prototype of the Array constructor and String constructor are specially designed to be duck-typed. These methods do not perform any verification on the data type of this. This is why arguments can pretend to be array and call the push method.
Look at the code of Array.prototype.push in the v8 engine:
There is only one problem left that we need to solve, how to make an object pretend to be an array object in a general way.
The real implementation code is actually very simple:
Although this code is very short, it is still a bit difficult to understand at first time. Let’s take the push example to see what happens.