- 前言
- 关于 ECMASCRIPT 发展史和现状
- ES6 带来的重大特性
- ES2016(ES7)的改进
- ES2017(ES8)带来的重大新特性
- ES2018(ES9)带来的重大新特性
- JavaScript 编码风格指南
- JavaScript 词法结构(构建块)
- JavaScript 变量
- JavaScript 数据类型
- JavaScript 表达式
- 原型继承
- 如何使用 JavaScript 中的 Classes(类)
- JavaScript 异常处理
- JavaScript 中的分号(;)
- JavaScript 中的引号
- JavaScript 字面量模板(Template Literals)指南
- JavaScript 中的 function(函数)
- JavaScript 箭头函数(Arrow Function)
- JavaScript 中的闭包(Closures)
- JavaScript 数组(Arrays)
- JavaScript 中的循环(Loops)
- JavaScript 中的事件(Events)
- JavaScript 中的事件循环(Event Loop)
- JavaScript 异步编程和回调
- 理解 JavaScript 中的 Promises
- 用 async 和 await 编写现代 JavaScript 异步代码
- JavaScript 中的 循环(Loops) 和 作用域(Scope)
- JavaScript 定时器 setTimeout() 和 setInterval()
- JavaScript 中的 this
- JavaScript 严格模式(Strict Mode)
- JavaScript 中的 立即执行函数表达式(IIFE)
- JavaScript 中的数学运算符
- JavaScript 中的 Math 对象
- 介绍 ES Modules(模块)
- 介绍 CommonJS
- JavaScript 术语表
JavaScript 中的 function(函数)
现在我们将学习 JavaScript 中所有关于函数的知识,从概述到小细节帮助你更好的使用 JavaScript 函数。注:本文没有一句废话,对于新手,可以作为该知识点的入门指南,对于有经验的开发人员可以作为一次很好的回顾。
JavaScript 中的所有内容都在函数中执行。
函数是一个自包含的代码块,可以定义一次,并运行任意次数。
函数可以选择接受参数,并返回一个值。
JavaScript 中的函数是 对象 ,一种特殊的对象: function objects (函数对象)。
另外,函数在 JavaScript 中是一等公民,因为它们可以被赋值给一个值,它们可以作为参数传递并用作返回值。
让我们从“旧的”,ES6 / ES2015 之前的语法开始。 这是一个函数声明:
function dosomething(foo) { // do something }
在 ES6 / ES2015 流行的当下,简称为常规函数。
可以将函数分配给变量(这称为函数表达式):
const dosomething = function(foo) { // do something }
命名函数表达式类似,但在堆栈调用跟踪中更好用,这在发生错误时很有用 – 它保存函数的名称:
const dosomething = function dosomething(foo) { // do something }
ES6 / ES2015 引入了箭头函数,在使用内联函数时,它们特别适合用作参数或回调函数:
const dosomething = foo => { //do something }
箭头函数与上面的其他函数定义有很大的不同,我们会在后面的章节中详细介绍。
参数
一个函数可以有一个或多个参数。
const dosomething = () => { //do something } const dosomethingElse = foo => { //do something } const dosomethingElseAgain = (foo, bar) => { //do something }
从 ES6 / ES2015 开始,函数可以具有参数的默认值:
const dosomething = (foo = 1, bar = 'hey') => { //do something }
这允许您在不填充所有参数的情况下调用函数:
dosomething(3) dosomething()
ES2017 引入了参数的尾随逗号,这个功能有助于减少因移动参数时丢失逗号而导致的错误(例如,移动中间的最后一个):
const dosomething = (foo = 1, bar = 'hey') => { //do something } dosomething(2, 'ho!')
您可以将所有参数包装在一个数组中,并在调用函数时使用展开运算符:
const dosomething = (foo = 1, bar = 'hey') => { //do something } const args = [2, 'ho!'] dosomething(...args)
使用许多参数,记住顺序可能很困难。这时使用对象解构这个对象允许保留参数名称:
const dosomething = ({ foo = 1, bar = 'hey' }) => { //do something console.log(foo) // 2 console.log(bar) // 'ho!' } const args = { foo: 2, bar: 'ho!' } dosomething(args)
注意这里用的是 { foo = 1, bar = 'hey' }
,用的是等号 ,不是 { foo : 1, bar : 'hey' }
。
返回值
每个函数都返回一个值,默认情况下是 undefined
。
任何函数在其代码行结束时或执行流找到 return
关键字时终止执行。
当 JavaScript 遇到 return
关键字时,它退出函数执行并将控制权交还给其调用者。
如果 return
后面跟一个值,则该值将作为函数的结果返回:
const dosomething = () => { return 'test' } const result = dosomething() // result === 'test'
您只能返回一个值。
要模拟返回多个值,可以返回对象字面量或数组,并在调用函数时使用解构赋值。
使用数组:
使用对象:
嵌套函数
可以在函数中定义其他函数:
const dosomething = () => { const dosomethingelse = () => {} dosomethingelse() return 'test' }
被嵌套函数的作用域是嵌套它的函数,不能从外部调用。
对象方法
当用作对象属性时,函数称为方法:
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}`) } }
stop()
方法无法正常工作。
这是因为在两个函数声明风格中处理 this
的方式是不同的。 this
在箭头函数中指的是封闭的函数上下文,在这种例子中是 window
对象:
this
,是指使用 function()
的宿主对象
这意味着箭头函数不适合用于对象方法和构造函数(箭头函数构造函数实际上会在调用时引发 TypeError
)。
IIFE,立即调用函数表达式
IIFE 是一个在声明后立即执行的函数:
;(function dosomething() { console.log('executed') })()
您可以将结果分配给变量:
const something = (function dosomething() { return 'something' })()
它们非常方便,因为您无需在定义后单独调用该函数。
函数提升(Hoisting)
执行代码之前的 JavaScript 会根据某些规则对其进行重新排序。
特别需要记住的一点是函数会被移动到其作用域的顶部。所以下面的写法是合法的:
dosomething() function dosomething() { console.log('did something') }
在内部,JavaScript 在调用之前移动函数,以及在同一作用域内找到的所有其他函数:
function dosomething() { console.log('did something') } dosomething()
看下面的代码,如果您使用命名函数表达式,因为您正在使用 JavaScript 变量 ,所以会发生不同的事情。我们说的变量提升,其实是变量声明被提升,但不是值被提升,因此下面的代码中不是那个函数被提升。
dosomething() const dosomething = function dosomething() { console.log('did something') }
上面代码不会工作:
上面代码内部发生的事情是这样的:
const dosomething dosomething() dosomething = function dosomething() { console.log('did something') }
let
声明也是如此。 var
声明也不起作用,但有不同的错误:
这是因为 var
声明被提升并初始化为 undefined
作为值,而 const
和 let
被提升但未初始化。
以下内容 2018-11-30 更新
注:关于 const
和 let
是否被 hoisting(提升)的问题争论了好几年了至今没有定论,主要原因是 hoisting(提升)和 TDZ (temporal dead zone,暂时性死区) 都是非官方的解释,只是帮助大家理解 JavaScript 机制的说法。
本文最后说到的示例有两种解释:
第一种解释: const
和 let
声明的变量是不会提升,因为没有提升,所以报 not defined 错误,var 提升了,初始值为 undefined ,但是被当做了函数运算,undefined 不是函数,所以说变量不是函数。
第二种解释:JavaScript 中所有的声明 (var, let, const, function, function*, class) 都会被 hoisting(提升)。var / function / function*声明和 let / const / class 声明之间的区别是初始化。 const
和 let
有一个 TDZ,初始化被推迟,也就是变量保持未初始化。 所以访问它时会引发 ReferenceError 异常。 只有在碰到 let / const / class 语句时,才会初始化变量,这叫临时死区。和变量提升是两码事
参考文档:
- https://github.com/getify/You-Dont-Know-JS/issues/767
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone
- https://zhuanlan.zhihu.com/p/28140450
反正就是很乱!写文档的说文档不正确,MDN 的文档也是一会这个解释,一会那个解释。大家觉得哪个解释适合自己理解,就用哪个解释吧!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论