JS-函数表达式-绝对不简单

134 阅读3分钟

什么是表达式

表达式的特征,

  1. 参与计算
  2. 有值的返回
  3. 放在括号里面
  4. 非变量声明的语句

下面我们看几个例子

//参与计算
1 + 1;
1 || 0;

//有值得返回
a = 1 * 1;
b = 1 && 0//括号里面
(a)
(1)

//等号右边,非变量声明的语句
let a = 1let b = a;

注意:JS引擎对表达式的操作就是为了获取表达式的值,也就是执行RHS的操作

也可以理解,执行RHS的语句叫,表达式。也就是JS引擎只关注变量的值,而不关注变量容器本身在什么地方

什么函数声明

在理解函数表达式之前,我们先理解什么是函数声明

  1. 具名函数声明,形如function foo(){}的语句
  2. 匿名函数声明,形如function (){}语句

也就是说,函数声明分成两种,具名和匿名

什么是函数表达式

  1. 声明的同时,参与了计算
  2. 声明语句被放进了括号中
  3. 声明放在等号的右边

也就是说,符合表达式特征的函数声明,就是函数表达式

也就是说,JS引擎执行代码的时候,只关心函数声明语句的值的时候,该语句就是函数表达式

下面举个例子

//参与了计算
+ function foo(){};
0 || function(){};

//放进了括号
(function foo(){});
(function(){});

//放在了等号的右边
var bar = function foo(){};
var bar_2 = function(){};

上面的例子都是函数表达式,因为其符合表达式的特征。

好吧,知道了这些有什么用呢?

仅仅是学会一个名词吗? 并不是,函数声明和函数表达式有天壤之别

  1. 代码生效的时间不同。

    • 函数声明,是在代码编译时期,由编译器完成的动作
    • 函数表达式,在JS引擎执行到该语句的时候,该代码才会生效
  2. 代码生效之后,作用域也不一样

    • 函数声明后,当前作用域会存放一个变量,用来指向生成的函数
    • 函数表达式生效之后,当前作用域并不会存在一个变量,用来指向该函数。无论该函数表达式是具名的,还是匿名的。(前提是该函数没有被赋值给另外一个变量)
//参与了计算
+ function foo(){};
0 || function(){};

console.log( foo );	// ReferenceError: foo is not defined

//放进了括号
(function foo2(){});
(function(){});
(function(){})();

console.log( foo2 );	// ReferenceError: foo2 is not defined

//放在了等号的右边
var bar = function foo3(){};
var bar_2 = function(){};

console.log( foo3 );	// ReferenceError: foo3 is not defined
console.log( bar );	// [Function: bar]

你可能会对结果感到奇怪,为什么会这样?要解释这个现象,我们要回到表达式的本质,JS引擎对表达式处理的本质。

该本质,就是只关心表达式的值,只对表达式进行求值操作,只对表达式进行RHS的查询。在面对匿名函数是如此,具名函数也是如此。

JS引擎并不关心你这函数叫什么名字,它只关心你这个函数的值,更准确的,是要拿到存放在堆区中的函数对象。拿到该对象之后,就可以参与计算,或者调用该函数,或者将其赋值给另外一个变量等一系列的操作

如果你还不熟悉LHS和RHS的概念,推荐你看我的这篇文章: JS-一定要知道的两种变量查询


上面对于函数表达式本质的描述,已经很精彩了。但是有一个漏洞。

具名函数表达式内部是可以访问函数本身的。递归调用会用到此功能。

var a = function b() {
    console.log('b.name: ', b.name);
    console.log('arguments.name: ', arguments.callee.name);
}

a();
//	b.name:  b
//	arguments.name:  b

很神奇吧,这是为啥呢?这个大家可以好好想想。这个问题我放在IIFE(立即函数表达式)这一章节了。

总结:

  1. 什么是表达式
  2. 什么是函数声明
  3. 什么是函数表达式
  4. 通过RHS和LHS去理解表达式,函数表达式