js 从Vue中看偏函数

126 阅读4分钟

此文立志详细描述立即执行函数。

问题起源

在分析Vue源码(Vue2.6.8)的过程中遇到了这样一串函数调用。

template = idToTemplate(template);

正常逻辑意为调用idToTemplate吧,没错就是去调用idToTemplate了。那好,我们来看一下idToTemplate的逻辑。

var idToTemplate = cached(function (id) {
      var el = query(id);
      return el && el.innerHTML
    });

哈哈,如你所见,idToTemplate是调用cached return的结果的引用。不仅如此,cached传入的实参是个函数。接下来我们先去看看cached的具体实现吧。

function cached(fn) {
  var cache = Object.create(null);
  return (function cachedFn(str) {
    var hit = cache[str];
    return hit || (cache[str] = fn(str))
  })
}

如果要理解执行过程:哎!定睛一看,这里会涉及到()操作符、闭包和匿名函数表达式的知识,只有这三点都懂了才能彻底理解函数之间调用的过程。

为什么要以函数作为参数?

为什么要这样写(以函数作为参数,以函数作为返回值)?

想要理解这个问题,需要理解其背后的思想:函数柯里化。

一个难缠的Question:为什么noop需要用()括起来?

在这里把这个问题提出来是因为,这个问题已经困扰我很久了,一直没有得到解决。感觉背后应该是有某种思想在,因为语法上只牵涉()操作符、函数表达式、如果noop返回函数,还会牵涉一个闭包。

这里有源码的连接

var generateComponentTrace = (noop); // work around flow check

首先希望读者能够明白这不是立即执行函数的问题。这应该是work out flow check的问题。

这个问题我推送到了社区,有兴趣的朋友可以查阅。

()操作符

ECMAScript6th 规范中称()为group operator

ParenthesizedExpression : ( Expression )

  1. Return the result of evaluating Expression. This may be of type Reference.

NOTE:

    This algorithm does not apply GetValue to the result of evaluating Expression. The principal motivation for this is so that operators such as delete and typeof may be applied to parenthesized expressions.

()的语义

语义1,函数声明时参数表

function func(arg1,arg2){
  // ...
}

语义2,和一些语句联合使用以达到某些限定作用

// 和for in一起使用
for(var a in obj){
  // ...
}
 
// 和if一起使用
if(boo){
  //...
}
 
// 和while一起使用
while(boo){
  // ...
}
 
// 和do while一起使用
do{
  // ...
}while(boo)

语义3,和new一起使用用来传值(实参)

// 假设已经定义了类Person,它有两个字段姓名(name),年龄(age)
var p1 = new Person('Jack',26);

语义4,作为函数或对象方法的调用运算符(如果定义了参数也可与语义3一样传实参)

// 假设已经定义了函数func
func();
 
// 假设已经定义了对象obj,且拥有func方法
obj.func();

语义5,强制表达式运算

// 大家最熟悉的莫过于使用eval解析JSON
function strToJson(str){
     // eval 中字符串两旁加了强制运算符()
     var json = eval('(' + str + ')');
     return json;
}
// 又如使用较多的是匿名函数自执行
(function(){
  // ...
})();

闭包

闭包是个挺难的知识点,但是理解了下图之后,就可以看到闭包是很简单的。

  • 闭包就是外部函数已经离栈,内部函数仍然能够访问外部函数的变量/属性。内嵌的那个函数叫做闭包函数。
  • 闭包通过语法糖的形式实现了Java中private的作用。背后的思想是封装,目的是易于维护。

匿名函数表达式

想要了解匿名函数表达式,学习路线:函数怎样运行?method:参考规范,多次调试。---->函数表达式是什么?method:参考规范、多次测试。---->函数表达式怎样运行的?method:多次调试。

举个例子吧。下面是测试代码:

<!DOCTYPE html>
<html>
  <head>
    <title>Create an Instance Example1</title>
    <script src="../Vue分析结果.js"></script>
  </head>
  <body>
    <div>idToTemplate call cached</div>
    <script>
      var idToTemplate = cached(function (id) {
        return id
      });
      function cached(fn) {
        var cache = Object.create(null);
        return (function cachedFn(str) {
          var hit = cache[str];
          return hit || (cache[str] = fn(str))
        })
      }
      template = idToTemplate('template');
      console.log(template);
    </script>
  </body>
</html>

下面是运行内存图(该内存图是代码从上之下运行到cached()函数结束的时候 没有展示调用 template = idToTemplate('template');的内存图):

image.png

图中展示了闭包现象的原理和匿名函数表达式执行的的原理。

函数柯里化

什么是柯里化?

    In mathematics and computer science, currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument. For example, currying a function {\displaystyle f}f that takes three arguments creates three functions: image.png

啊哈!!读到这里,感觉这更像一种思想,JS里面有单独为柯里化设计的数据结构吗?没有。

多参变一参,arguments算吗?算,但是不是用来辅助实现柯里化的啊。

所以我们通过已有的数据结构去实现这种思想,啊哈,要耍点花花肠子了。就像闭包一样。怎样做?

这篇文章讲的挺好的。比较容易理解,这里就不详细介绍了。