共计 7383 个字符,预计需要花费 19 分钟才能阅读完成。
“Code tailor”,为前端开发者提供技术相干资讯以及系列根底文章,微信关注“小和山的菜鸟们”公众号,及时获取最新文章。
前言
在开始学习之前,咱们想要告诉您的是,本文章是对 JavaScript
语言常识中 “ 函数 ” 局部的总结,如果您已把握上面常识事项,则可跳过此环节间接进入题目练习
- 函数简介
- 函数名称
- 函数重载
- 函数申明与函数表达式
- 函数作为值
- this
- 函数的递归
如果您对某些局部有些忘记,👇🏻 曾经为您筹备好了!
汇总总结
函数简介
函数是所有编程语言的外围局部,因为它们能够封装语句,且被定义后能够在任何中央、任何工夫执行。ECMAScript
中的函数应用 function
关键字进行申明,后跟一组参数,而后是函数体。
以下是函数的根本语法:
function functionName(arg0, arg1,...,argN) {// 表达式}
上面是一个例子:
function sayHi(name, message) {console.log('Hello' + name + ',' + message)
}
能够通过函数名来调用函数,要传给函数的参数放在括号里(如果有多个参数,则用逗号隔开)。
上面是调用函数 sayHi()
的示例:
sayHi('xhs', 'do you study today?')
调用这个函数的输入后果是 "Hello xhs, do you study today?"
。参数 name
和 message
在函数外部作为字符串被拼接在了一起,最终通过 console.log()
输入到控制台。
ECMAScript
中的函数不须要指定是否返回值,也不限度返回值的类型。任何函数在任何工夫都能够应用 return
语句来返回函数的值,用法是后跟要返回的值,返回值能够是任何类型。比方:
function sum(num1, num2) {return num1 + num2}
函数 sum()
会将两个值相加并返回后果。留神,除了 return
语句之外没有任何非凡申明表明该函数有返回值。而后就能够这样调用它:
const result = sum(5, 10)
值得注意的是,只有执行到 return
语句,函数就会立刻进行执行并退出。因而,return
语句前面的代码不会被执行。比方:
function sum(num1, num2) {
return num1 + num2
console.log('Hello world') // 不会执行
}
在这个例子中,console.log()
不会执行,因为它在 return
语句前面,解释器在执行到 return
语句后就进行对该函数的执行。
一个函数里能够有多个 return
语句,像这样:
function subtract(num1, num2) {if (num1 < num2) {return num2 - num1} else {return num1 - num2}
}
这个 subtract()
函数用于计算两个数值的差。如果第一个数值小于第二个,则用第二个减第一个;否则,就用第一个减第二个。代码中每个分支都有本人的 return
语句,返回正确的差值。return
语句也能够不带返回值。这时候,函数会立刻进行执行并返回 undefined
。这种用法最罕用于提前终止函数执行,并不是为了返回值。比方在上面的例子中,console.log()
不会执行:
function sayHi(name, message) {
return
console.log('Hello' + name + ',' + message) // 不会执行
}
依据开发教训而言,一个函数要么返回值,要么不返回值。只在某个条件下返回值的函数会带来麻烦,尤其是调试时。
函数名称
函数名是一个指向函数的指针,所以它们跟其余蕴含对象指针的变量具备雷同的行为。这意味着一个函数能够有多个函数名,如下所示:
function sum(num1, num2) {return num1 + num2}
console.log(sum(10, 10)) // 20
let xhsSum = sum
console.log(xhsSum(10, 10)) // 20
sum = null
console.log(xhsSum(10, 10)) // 20
以上代码定义了一个名为 sum
的函数,用于求两个数之和。而后又申明了一个变量 xhsSum
,并将它的值设置为等于 sum
。留神,应用不带括号的函数名会拜访函数指针名,而不会执行函数。此时,xhsSum
和 sum
都指向同一个函数。调用 xhsSum()
也能够返回后果。把 sum
设置为 null
之后,就切断了它与函数之间的关联。而 xhsSum()
还是能够照常调用,没有问题。
函数参数
ECMAScript
函数的参数跟大多数其余语言不同。ECMAScript
函数既不关怀传入的参数个数,也不关怀这些参数的数据类型。定义函数时要接管两个参数,并不意味着调用时就传两个参数。你能够传一个、三个,也能够一个也不传,解释器都不会报错。
之所以会这样,次要是因为 ECMAScript
函数的参数在外部体现为一个数组。函数被调用时总会接管一个数组,但函数并不关怀这个数组中蕴含什么。如果数组中什么也没有,那没问题;如果数组的元素超出了要求,那也没问题。
事实上,在应用 function
关键字定义(非箭头)函数时,能够在函数外部拜访 arguments
对象,从中获得传进来的每个参数值。arguments
对象是一个类数组对象(但不是 Array
的实例),因而能够应用中括号语法拜访其中的元素(第一个参数是 arguments[0]
,第二个参数是 arguments[1]
)。而要确定传进来多少个参数,能够拜访 arguments.length
属性。在上面的例子中,sayHi()
函数的第一个参数叫 name
:
function sayHi(name, message) {console.log('Hello' + name + ',' + message)
}
能够通过 arguments[0]
获得雷同的参数值。因而,把函数重写成不申明参数也能够:
function sayHi() {console.log('Hello' + arguments[0] + ',' + arguments[1])
}
在重写后的代码中,没有命名参数。name
和 message
参数都不见了,但函数照样能够调用。这就表明,ECMAScript
函数的参数只是为了不便才写进去的,并不是必须写进去的。与其余语言不同,在 ECMAScript
中的命名参数不会创立让之后的调用必须匹配的函数签名。这是因为基本不存在验证命名参数的机制。也能够通过 arguments
对象的 length
属性查看传入的参数个数。上面的例子展现了在每调用一个函数时,都会打印出传入的参数个数:
function getArgsNum() {console.log(arguments.length)
}
getArgsNum('string', 45) // 2
getArgsNum() // 0
getArgsNum(12) // 1
这个例子别离打印出 2
、0
和 1
(按程序)。既然如此,那么开发者能够想传多少参数就传多少参数。
比方:
function getTotle() {if (arguments.length === 1) {console.log(arguments[0] + 10)
} else if (arguments.length === 2) {console.log(arguments[0] + arguments[1])
}
}
getTotle(10) // 20
getTotle(30, 20) // 50
这个函数 getTotle()
在只传一个参数时会加 10
,在传两个参数时会将它们相加,而后返回。因而 getTotle(10)
返回 20
,而 getTotle(30,20)
返回 50
。尽管不像真正的函数重载那么明确,但这曾经足以补救 ECMAScript
在这方面的缺失了。
还有一个必须了解的重要方面,那就是 arguments
对象能够跟命名参数一起应用,比方:
function getTotle(num1, num2) {if (arguments.length === 1) {console.log(num1 + 10)
} else if (arguments.length === 2) {console.log(arguments[0] + num2)
}
}
在这个 getTotle()
函数中,同时应用了两个命名参数和 arguments
对象。命名参数 num1
保留着与 arugments[0]
一样的值,因而应用谁都无所谓。(同样,num2
也保留着跟 arguments[1]
一样的值。)arguments
对象的另一个有意思的中央就是,它的值始终会与对应的命名参数同步。来看上面的例子:
function getTotle(num1, num2) {arguments[1] = 10
console.log(arguments[0] + num2)
}
这个 getTotle()
函数把第二个参数的值重写为 10
。因为 arguments
对象的值会主动同步到对应的命名参数,所以批改 arguments[1]
也会批改 num2
的值,因而两者的值都是 10
。但这并不意味着它们都拜访同一个内存地址,它们在内存中还是离开的,只不过会放弃同步而已。
另外还要记住一点:如果只传了一个参数,而后把 arguments[1]
设置为某个值,那么这个值并不会反映到第二个命名参数。这是因为 arguments
对象的长度是依据传入的参数个数,而非定义函数时给出的命名参数个数确定的。对于命名参数而言,如果调用函数时没有传这个参数,那么它的值就是 undefined
。这就相似于定义了变量而没有初始化。
比方,如果只给 getTotle()
传了一个参数,那么 num2
的值就是 undefined
。严格模式下,arguments
会有一些变动。首先,像后面那样给 arguments[1]
赋值不会再影响 num2
的值。就算把 arguments[1]
设置为 10
,num2
的值依然还是传入的值。其次,在函数中尝试重写 arguments
对象会导致语法错误。(代码也不会执行。)
函数重载
ECMAScript
函数不能像传统编程那样重载。在其余语言比方 Java
中,一个函数能够有两个定义,只有签名(接管参数的类型和数量)不同就行。如前所述,ECMAScript
函数没有签名,因为参数是由蕴含零个或多个值的数组示意的。没有函数签名,天然也就没有重载。如果在ECMAScript
中定义了两个同名函数,则后定义的会笼罩先定义的。来看上面的例子:
function addSomeNumber(num) {return num + 100}
function addSomeNumber(num) {return num + 200}
let result = addSomeNumber(100) // 300
这里,函数 addSomeNumber()
被定义了两次。第一个版本给参数加 100
,第二个版本加 200
。最初一行调用这个函数时,返回了 300
,因为第二个定义笼罩了第一个定义。
函数申明与函数表达式
咱们能够通过 function
结构一个函数,也能够将一个变量赋值成一个函数,这两种办法在大多数状况下都能够失常调用执行。可是在 JavaScript
引擎在加载数据时对函数申明和函数表达式是区别对待的。JavaScript
引擎在任何代码执行之前,会先读取函数申明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。来看上面的例子:
// 没问题
console.log(sum(10, 10))
function sum(num1, num2) {return num1 + num2}
以上代码能够失常运行,因为函数申明会在任何代码执行之前先被读取并增加到执行上下文。这个过程叫作函数申明晋升(function declaration hoisting
)。在执行代码时,JavaScript
引擎会先执行一遍扫描,把发现的函数申明晋升到源代码树的顶部。因而即便函数定义呈现在调用它们的代码之后,引擎也会把函数申明晋升到顶部。如果把后面代码中的函数申明改为等价的函数表达式,那么执行的时候就会出错:
// 会出错
console.log(sum(10, 10))
var sum = function (num1, num2) {return num1 + num2}
函数作为值
因为函数名在 ECMAScript
中就是变量,所以函数能够用在任何能够应用变量的中央。这意味着不仅能够把函数作为参数传给另一个函数,而且还能够在一个函数中返回另一个函数。
function add10(num) {return num + 10}
let result1 = callSomeFunction(add10, 10)
console.log(result1) // 20
function getGreeting(name) {return 'Hello,' + name}
let result2 = callSomeFunction(getGreeting, 'Nicholas')
console.log(result2) // "Hello, Nicholas"
function fun1(x) {return function (y) {return function (z) {console.log(x * y * z)
}
}
}
fun1(2)(3)(4) //24
this
this
是一个非凡的对象,它在规范函数和箭头函数中有不同的行为。
在规范函数中,this
援用的是把函数当成办法调用的上下文对象,这时候通常称其为 this
值(在网页的全局上下文中调用函数时,this
指向 window
对象)。来看上面的例子:
window.color = 'red'
let o = {color: 'blue',}
function sayColor() {console.log(this.color)
}
sayColor() // 'red'
o.sayColor = sayColor
o.sayColor() // 'blue'
定义在全局上下文中的函数 sayColor()
援用了 this
对象。这个 this
到底援用哪个对象必须到函数被调用时能力确定。因而这个值在代码执行的过程中可能会变。如果在全局上下文中调用 sayColor()
,这后果会输入 red
,因为 this
指向 window
,而 this.color
相当于 window.color
。而在把 sayColor()
赋值给 o
之后再调用 o.sayColor()
,this
会指向 o
,即 this.color
相当于 o.color
,所以会显示 blue
。
在箭头函数中,this
援用的是定义箭头函数的上下文。箭头函数的内容咱们会在第二局部的函数扩大中具体提及。
函数的递归
递归函数通常的模式是一个函数通过名称调用本人,如上面的例子所示:
function factorial(num) {if (num <= 1) {return 1} else {return num * factorial(num - 1)
}
}
这是经典的递归阶乘函数。尽管这样写是能够的,但如果把这个函数赋值给其余变量,就会出问题:
let anotherFactorial = factorial
factorial = null
console.log(anotherFactorial(4)) // 报错
这里把 factorial()
函数保留在了另一个变量 anotherFactorial
中,而后将 factorial
设置为 null
,于是只保留了一个对原始函数的援用。而在调用 anotherFactorial()
时,要递归调用 factorial()
,但因为它曾经不是函数了,所以会出错。在写递归函数时应用 arguments.callee
能够防止这个问题。arguments.callee
就是一个指向正在执行的函数的指针,因而能够在函数外部递归调用,如下所示:
function factorial(num) {if (num <= 1) {return 1} else {return num * arguments.callee(num - 1)
}
}
把函数名称替换成 arguments.callee
,能够确保无论通过什么变量调用这个函数都不会出问题。因而在编写递归函数时 arguments.callee
是援用以后函数的首选。不过,在严格模式下运行的代码是不能拜访 arguments.callee
的,因为拜访会出错。此时,能够应用命名函数表达式(named function expression
)达到目标。比方:
const factorial = function f(num) {if (num <= 1) {return 1} else {return num * f(num - 1)
}
}
题目自测
一:以下代码输入什么
function callFunc(func, argument) {return func(argument)
}
function func1(argument) {console.log(argument + '.')
}
callFunc(func1, 'hello,world')
二:以下代码输入什么
function recursion(num) {if (num === 0) return 1
else if (num % 2 === 0) return recursion(num - 1) + 1
else return recursion(num - 1)
}
console.log(recursion(6))
三:两段代码别离执行各会输入什么
func1(1, 2)
function func2(x, y) {console.log(x + y)
}
let func1 = func2
func2(1, 2)
function func2(x, y) {console.log(x + y)
}
题目解析
一、
Answer: ‘hello,world.’
callFunc
函数的作用其实就是执行了第一个参数,并把第二个参数传给第一个参数当做参数。
二、
Answer: 4
recursion
是一个递归函数,如果 num
等于 0
就返回1
,如果 num
是偶数,就在下一个后果前 +1
,如果是奇数就返回下一个后果。其作用就是统计从 0
到 num
一共有多少个偶数。所以 recursion(6)
为 4
三、
func1(1,2)
输入 ReferenceError
,因为在调用时 func1
还未被定义和赋值。func2(1,2)
输入 3
,因为 function
的申明会被提前,能够在之前应用。