今天,我们将发布JavaScript手册翻译的第四部分,该手册专门介绍函数。
→
第1部分:第一个程序,语言功能,标准→
第2部分:代码样式和程序结构→
第3部分:变量,数据类型,表达式,对象→
第4部分:功能→
第5部分:数组和循环→
第6部分:异常,分号,通配符文字→
第7部分:严格模式,此关键字,事件,模块,数学计算→
第8部分:ES6功能概述→
第9部分:ES7,ES8和ES9标准概述
JavaScript函数
让我们讨论一下JavaScript中的函数,对其进行一般性的回顾,并考虑有关它们的细节,这些知识将使您有效地使用它们。
函数是一个独立的代码块,一旦声明,就可以根据需要多次调用。 一个函数可以(尽管不是必需的)接受参数。 函数返回单个值。
JavaScript中的函数是对象,或者更确切地说,它们是
Function
类型的对象。 它们与普通对象的主要区别在于,它们可以调用函数,从而赋予它们所拥有的卓越功能。
另外,JavaScript中的函数称为“一流函数”,因为它们可以分配给变量,可以作为参数传递给其他函数,也可以从其他函数返回。
首先,我们考虑使用功能的特征以及对应于ES6标准之前的语言中仍然存在的语法结构。
这就是函数声明的样子。
function doSomething(foo) {
如今,这些功能被称为“正常”功能,与ES6中出现的“箭头”功能有所区别。
您可以将函数分配给变量或常量。 这种构造称为函数表达式。
const doSomething = function(foo) {
您可能会注意到,在上面的示例中,该函数被分配给一个常量,但是它本身没有名称。 这种功能称为匿名。 可以为类似的功能分配名称。 在这种情况下,我们谈论的是命名函数表达式(named function expression)。
const doSomething = function doSomFn(foo) {
使用此类表达式可提高调试的便利性(在执行堆栈跟踪的错误消息中,该函数的名称可见)。 在函数表达式中也可能需要函数的名称,以便函数可以调用自身,这对于实现递归算法是必不可少的。
在ES6标准中,出现了箭头函数,这些箭头函数特别方便以所谓的“内联函数”的形式使用-作为传递给其他函数(回调)的参数。
const doSomething = foo => {
箭头函数除了用于声明它们的结构之外,还比使用普通函数更为紧凑,但它们在某些重要功能上与它们不同,我们将在下面进行讨论。
功能参数
参数是在函数声明阶段设置的变量,将包含传递给它的值(这些值称为自变量)。 JavaScript中的函数可能没有参数,也可能有一个或多个参数。
const doSomething = () => {
这是箭头功能的一些示例。
从ES6标准开始,函数可以具有所谓的“默认参数”。
const doSomething = (foo = 1, bar = 'hey') => {
如果表示未设置某些参数的值,则它们代表由函数的参数设置的标准值。 例如,可以通过将其接收的所有两个参数传递给该函数以及通过其他方法来调用上述函数。
doSomething(3) doSomething()
在ES8中,可以在函数的最后一个参数之后加上逗号(这称为尾随逗号)。 使用此功能,可以在程序开发过程中使用版本控制系统时增加编辑代码的便利性。 关于此的详细信息可以在
这里和
这里找到。
传递给函数的参数可以表示为数组。 为了解析这些参数,可以使用看起来像三个点的运算符(这就是所谓的“扩展运算符”或“扩展运算符”)。 这是它的外观。
const doSomething = (foo = 1, bar = 'hey') => {
如果函数需要采用许多参数,则记住其顺序的顺序可能会很困难。 在这种情况下,将使用具有参数和解构ES6对象机会的对象。
const doSomething = ({ foo = 1, bar = 'hey' }) => {
该技术允许以对象属性的形式描述参数并将函数传递给对象,从而无需使用其他构造即可使函数按其名称访问参数。
在此处阅读有关此技术的更多信息。
从函数返回的值
所有函数均返回特定值。 如果未明确指定return命令,则该函数将返回
undefined
。
const doSomething = (foo = 1, bar = 'hey') => {
函数执行在它包含的所有代码执行完之后,或者在代码中遇到
return
关键字之后结束。 当在函数中遇到此关键字时,其操作将完成,控制权将转移到调用该函数的位置。
如果在
return
关键字之后指定了某个值,则此值将作为此函数的结果返回到函数调用的位置。
const doSomething = () => { return 'test' } const result = doSomething()
一个函数只能返回一个值。 为了能够返回多个值,您可以使用对象常量或作为数组将它们作为对象返回,并且在调用函数时,请使用破坏性赋值构造。 参数名称已保存。 同时,如果需要使用从函数返回的对象或数组,即对象或数组的形式,则可以进行无损分配。
const doSomething = () => { return ['Roger', 6] } const [ name, age ] = doSomething() console.log(name, age)
构造
const [ name, age ] = doSomething()
可以如下读取:“声明
name
和
age
常量,并为其指定函数将返回的数组元素的值。”
这是使用对象的相同外观。
const doSomething = () => { return {name: 'Roger', age: 6} } const { name, age } = doSomething() console.log(name, age)
嵌套函数
可以在其他函数内部声明函数。
const doSomething = () => { const doSomethingElse = () => {} doSomethingElse() return 'test' } doSomething()
嵌套函数的范围受其外部函数的限制;不能从外部调用它。
对象方法
当函数用作对象的属性时,此类函数称为对象方法。
const car = { brand: 'Ford', model: 'Fiesta', start: function() { console.log(`Started`) } } car.start()
这个关键词
如果比较箭头和用作对象方法的普通函数,我们会发现它们的重要区别,这就是关键字
this
的含义。 考虑一个例子。
const car = { brand: 'Ford', model: 'Fiesta', start: function() { console.log(`Started ${this.brand} ${this.model}`) }, stop: () => { console.log(`Stopped ${this.brand} ${this.model}`) } } car.start()
如您所见,调用
start()
方法会得到预期的结果,但是
stop()
方法显然不能正常工作。
这是由于以下事实:this关键字在箭头功能和普通功能中使用时的行为有所不同。 即,箭头函数中的
this
包含指向包含该函数的上下文的链接。 在这种情况下,当涉及到浏览器时,该上下文就是
window
对象。
这是在浏览器控制台中执行此类代码的方式。
const test = { fn: function() { console.log(this) }, arrFn: () => { console.log(this) } } test.fn() test.arrFn()
常规和箭头功能中的此关键字功能如您所见,在常规函数中调用它意味着调用对象,而在箭头函数中此指向
window
。
所有这些都意味着arrow函数不适合对象和构造方法的角色(如果尝试将arrow函数用作构造函数,则会
TypeError
)。
立即调用函数表达式
立即调用函数表达式(IIFE)是在声明后立即自动调用的函数。
;(function () { console.log('executed') })()
IIFE之前的分号是可选的,但是使用它可以确保避免与分号的自动放置相关的错误。
在上面的示例中,
executed
的单词将进入控制台,此后IIFE将退出。 与其他功能一样,IIFE可以返回其工作结果。
const something = (function () { return 'IIFE' })() console.log(something)
执行完此简单示例后,控制台将获得
IIFE
行,该行在执行立即调用的函数表达式后变成了
something
。 这种设计似乎没有特别的好处。 但是,如果在IIFE中执行了一些仅需执行一次的复杂计算,那么之后就不需要了相应的机制-IIFE的用途显而易见。 即,使用这种方法,在执行IIFE之后,程序中将仅提供函数返回的结果。 此外,我们可能还记得函数可以返回其他函数和对象。 我们正在谈论闭包,我们将在下面讨论它们。
功能升级
在执行JavaScript代码之前,必须对其进行重新组织。 我们已经讨论了使用
var
关键字声明的变量的提升机制。 类似的机制也可用于功能。 即,我们正在谈论这样一个事实,即在执行代码之前处理代码的过程中将函数声明移到其作用域的上部。 结果,例如,事实证明您可以在声明函数之前调用它。
doSomething()
如果移动函数调用使其在声明后进行,则不会发生任何变化。
如果在类似情况下使用函数表达式,则类似代码将引发错误。
doSomething()
在这种情况下,事实证明,尽管
doSomething
变量的声明上升到作用域的顶部,但这不适用于赋值操作。
如果在类似情况下使用
let
或
const
关键字代替
var
,则此代码也将不起作用,但是,系统将显示不同的错误消息(
ReferenceError
而不是
TypeError
),因为使用
let
和
const
时不会引发变量和常量声明。
箭头功能
现在,我们将更多地讨论已经遇到的箭头功能。 它们可以被视为ES6标准最重要的创新之一,它们不仅在外观上而且在行为上均与普通功能不同。 如今,它们的使用极为广泛。 也许没有一个现代项目可以在绝大多数情况下不使用它们。 可以说,它们的外观永远改变了JS代码的外观及其工作特性。
从纯粹的外部角度来看,声明箭头函数的语法比普通函数的语法更紧凑。 这是一个常规函数的声明。
const myFunction = function () {
这是箭头功能的公告,通常,如果您不考虑箭头功能的特性,则与前一个功能类似。
const myFunction = () => {
如果箭头函数的主体仅包含一个命令,该函数的结果将返回,则可以将其写成大括号且没有
return
关键字。 例如,这样的函数返回传递给它的参数的总和。
const myFunction = (a,b) => a + b console.log(myFunction(1,2))
如您所见,箭头函数的参数(与普通函数一样)在括号中描述。 而且,如果这样的函数仅使用一个参数,则可以在不带括号的情况下指定它。 例如,这是一个函数,该函数返回将传递给它的数字除以2的结果。
const myFunction = a => a / 2 console.log(myFunction(8))
结果,事实证明,在需要小的功能的情况下,使用箭头功能非常方便。
▍隐式返回函数结果
我们已经谈到了箭头功能的这一功能,但它非常重要,因此应进行更详细的讨论。 我们正在谈论一个事实,即单行箭头函数支持其工作结果的隐式返回。 我们已经看到了从单行箭头函数返回原始值的示例。 如果这样的函数应该返回一个对象怎么办? 在这种情况下,对象文字的花括号可能会使系统感到困惑,因此在函数的主体中使用了括号。
const myFunction = () => ({value: 'test'}) const obj = myFunction() console.log(obj.value)
this关键字this和箭头功能
上面,当我们查看
this
的功能时,我们比较了常规函数和箭头函数。 本节旨在提请您注意它们之间差异的重要性。
this
本身可能会导致某些困难,因为它取决于代码执行的上下文以及是否启用严格模式。
如我们所见,当在由常规函数表示的对象的方法中使用
this
时,
this
指向该方法所属的对象。 在这种情况下,我们讨论将关键字
this
绑定到表示函数上下文的值。 特别是,如果将函数称为对象方法,则this关键字将绑定到该对象。
对于箭头函数,事实证明未在其中执行
this
绑定;它们从其作用域中使用
this
。 因此,不建议将它们用作对象方法。
将函数用作DOM元素的事件处理程序时,会发生相同的问题。 例如,HTML元素
button
用于描述按钮。 用户单击按钮时将
click
事件。 为了在代码中响应此事件,您必须首先获得指向相应元素的链接,然后为它分配一个
click
事件处理函数作为函数。 作为此类处理程序,您可以同时使用常规函数和箭头函数。 但是,如果在事件处理程序中需要访问被调用的元素(即
this
),则arrow函数在这里将不起作用,因为其中可用的
this
值指向
window
对象。 为了在实践中进行测试,请创建一个HTML页面,其代码如下所示,然后单击按钮。
<!DOCTYPE html> <html> <body> <button id="fn">Function</button> <button id="arrowFn">Arrow function</button> <script> const f = document.getElementById("fn") f.addEventListener('click', function () { alert(this === f) }) const af = document.getElementById("arrowFn") af.addEventListener('click', () => { alert(this === window) }) </script> </body> </html>
在这种情况下,当您单击这些按钮时,将出现包含
true
窗口。 但是,在具有标识符
fn
的按钮的
click
事件处理程序中,将检查此按钮与按钮的相等性,在具有标识符
arrowFn
的按钮中,将检查该标识符与
window
对象的相等性。
结果,如果需要在HTML元素的事件处理程序中调用
this
函数,则箭头函数将不适用于此类处理程序的设计。
短路
闭包是JavaScript中的重要概念。 实际上,如果您编写了JS函数,那么您还使用了闭包。 在某些设计模式中使用闭包-如果您需要组织对某些数据或功能的访问的严格控制。
调用函数时,它可以访问其外部范围内的所有内容。 但是无法访问函数内部声明的内容。 也就是说,如果在一个函数中声明了一个变量(或另一个函数),则在执行该函数期间或完成其工作之后,外部代码将无法访问它们。 但是,如果从该函数返回了另一个函数,则此新函数将有权访问原始函数中声明的所有内容。 在这种情况下,所有这些都将在闭包的外部代码中隐藏。
考虑一个例子。 这是一个采用狗名的函数,然后将其显示在控制台中。
const bark = dog => { const say = `${dog} barked!` ;(() => console.log(say))() } bark(`Roger`)
该函数返回的值对我们不感兴趣,该文本使用IIFE在控制台中显示,在这种情况下,该文本没有特殊作用,但是,这将帮助我们查看此函数及其变体之间的联系,而不是调用显示该函数的函数。文本到控制台,我们将从重写的函数
bark()
返回此函数。
const prepareBark = dog => { const say = `${dog} barked!` return () => console.log(say) } const bark = prepareBark(`Roger`) bark()
在两种情况下,代码的结果是相同的。 但是在第二种情况下,被调用时转移到原始函数的内容(狗的名字,
Roger
)存储在闭包中,之后由原始函数返回的另一个函数使用。
让我们进行另一个实验-使用原始功能为不同的狗创建两个新的。
const prepareBark = dog => { const say = `${dog} barked!` return () => { console.log(say) } } const rogerBark = prepareBark(`Roger`) const sydBark = prepareBark(`Syd`) rogerBark() sydBark()
此代码将输出以下内容。
Roger barked! Syd barked!
事实证明,
say
常量的值与从
prepareBark()
函数返回的函数有关。
请注意,
say
,当您再次调用
prepareBark()
时,它将获得一个新值,而
say
第一次
prepareBark()
时记录的值不会改变。 关键是每次调用此函数都会创建一个新的闭包。
总结
今天,我们讨论了普通和箭头函数,它们的声明和使用功能,
this
关键字在不同情况下的行为以及闭包。 下次我们讨论数组和循环。
亲爱的读者们! 您如何看待JavaScript中的箭头功能?
