好问题!我会将其分为几个部分。由于您的问题涉及多个深层次的主题,因此在这里需要搜索大量材料。
1. 语句没有值
声明是语句。它们没有值,因此不能作为参数。这段代码...
let a = 1
...没有价值,这意味着这些都不会起作用:
doStuff(let a = 1)
let b = (let a = 1)
(let a = 1) + 5
名称a
,或a + 5
,或f(a)
都是表达式,与语句不同的是,表达式具有值。但是a
本身的声明并没有值。
请注意,你对此的直觉并不荒谬:在其他语言中,let a = 1
是一个求值为1
的表达式。但在Javascript中不是这样。
2. 函数是对象
然而,function
关键字确实有价值:它定义的Function
对象。与变量不同,变量是为你方便而创建的语言构造,Functions
是实际存在于运行程序中的对象。我们说函数是一等对象。
你可以做所有这些事情:
doStuff(function f() {})
let a = function f() {}
let b = (function f() {}) + 5
回到你的例子:
callback(
"",
function b1() { alert("empty") },
function b2() { alert("not empty") }
);
类似于这样:
function b1() { alert("empty") }
function b2() { alert("not empty") }
callback("", b1, b2)
但并不完全如此。让我们谈谈作用域。
3. 名称在作用域内被定义
名称(例如变量或函数)的作用域是具有该定义的代码部分。
例如:
let a = 1
if (a == 1) {
let b = 2
console.log(a, b)
}
console.log(a, b)
作用域存在于更大的作用域内。内部作用域可以访问周围的作用域(a和b在块内可见),但反过来则不行(所以b在外部不可见)。
当你在调用中创建你的function对象时...
<code>f(function a() { })
</code>
...他们被困在一个内部范围内,无法从外部引用。
4. 赋值是表达式
在你的示例代码中,你注意到像这样声明a
可以工作:
f(a = 5)
这真是不幸。这实际上是JavaScript历史的产物。在现代代码中,您应该始终使用let
或const
来定义变量。
那么它为什么起作用呢?有两个原因。首先,因为它是一个 赋值,而不是一个声明,就像这样:
let x = 1
f(x = 2)
赋值语句是表达式,它们的值是被赋的值。例如 x = 2
的值是 2
,并且会导致 x
的值发生改变。
5. 存在全局作用域
第二个原因则很不幸。当你避免使用 let
、var
或 const
关键字时,实际上隐式地使用了 global
作用域。
这是所有作用域的母体,其中存储的名称可以从代码的任何位置访问。所以,如果只是像这样写...
f(a = 5)
如果在当前作用域中没有声明a
,那么它会被隐含地声明在全局作用域中,并进行赋值。可以将其视为如下伪代码:
... without having declared a
anywhere in the current scope, it's implicitly declared in the global scope, and the assignment takes place. Think of it as this (pseudo-code):
global let a
f(a = 5)
当然,那并不是有效的 JavaScript。但你明白了重点。
function callback
是一个函数声明,但function b1
不是;它是一个函数表达式,因为它在表达式上下文中使用而不是语句上下文中。 - Paulconsole.log(x = 1)
会输出1
。console.log(function() {...})
会输出函数定义。然而,console.log(var x = 1)
是语法错误。虽然答案会详细解释,但定义一个函数或执行x=1
会返回一个值,而声明一个var
则不会。 - Tyler Roperfunction
关键字的问题在于,当它在期望表达式的上下文中使用时,它并不是声明一个函数,而是创建一个产生函数的函数表达式。顺便提一下,这里函数的名称除了帮助调试和错误报告外没有实际用途,这就是为什么在这些情况下通常使用匿名函数的原因。 - Lennholm