什么是函数
函数其实就是一种代码的分组形式(理解:通过对代码不同的分组,达到不同的目的,以便于后续的使用,例如:try/catch 是一种代码分组形式,if/else 也是一种代码分组形式)。
function sum(a,b) {
return a + b
}
函数组成
- 关键词:function
- 函数名称: 即上面的sum,
- 函数的参数: 即a,b, 函数可以有一个或多个参数,参数之间用逗号分割。
- 代码块:函数要执行的代码块,称之为函数体。
- 返回值:一个函数只能有一个返回值,如果某个函数没有显式的返回值,默认返回 undefined.见下例。
// 函数若没有显式的返回值,默认返回值为 undefined
function sum() {
const a = 1;
console.log(a)
}
const res = sum();
console.log(res)
函数调用
调用函数的方式:在函数名后面加() 即可, 这种操作 在英文中会叫做"(to call)" 或者"(to invoke)"
参数
更少的参数
在定义函数的时候,设定了参数,但是在调用的过程中忘了传递相关的参数,JS 引擎会自动将参数设为 undefined.
function sum(a, b) {
return a + b
}
const res = sum(1);
console.log(res);
这里并没有传递第二个参数, 因此会吧第二个参数视为 undefined, 函数返回值 1 + undefined 结果为NaN.
更多的参数
sum(1,2,4,6,6)
多余的参数会被忽略掉,所以返回值为 1 + 2
arguments 变量
这个变量为函数的内建变量,在每个函数中都能调用,他能返回函数所接收的所有函数。
function sum() {
return arguments
}
const res = sum(1,'23.5', true);
console.log(res)
你会发现arguments 实际上并不是一个数组(虽然有很多数组的特性)而是一个类似数组的对象。
[Arguments] { '0': 1, '1': '23.5', '2': true }
预定义函数
JS 引擎中有一组已经定义好的,随时调用的函数.
- parseInt();
- parseFloat();
- isNaN();
- isFinite()
- encodeURI()
- decodeURI()
- encodeURIComponent()
- decodeURIComponent()
- eval()
eval(): //会将输入的字符串当做JavaScript代码执行。 处于安全性的考虑,若对于放在eval 中的代码没有把握,不要使用eval。
变量作用域
很重要的一点是,在JS 中,变量的定义不是以代码块作为作用域的,而是以函数左右作用域的。也就是说如果变量是在某个函数中定义的,那么它在函数意外的地方是不可见的。
如果该变量的定义实在if/else 这样的代码块中,那么它在代码块之外是可见的。
let globaal = 1;
function f() {
const local = 2
global++
return global;
}
- 函数f()是可以访问变量global
- 在函数f()以外, 变量local 不存在。
变量提升
var a = 123;
function f() {
console.log(a)
var a = 1;
console.log(a)
}
f();
// undefined;
// 1
这是因为函数域始终有限与全局与,所以局部变脸a 覆盖掉所有与他同名的全局变量,第一次log(a)时,a 还没有被正式定义(值为undefined),但是变量本身已经存在与本地空间了, 这种特殊的现象叫做提升(hoisting)。
在JS引擎进入函数内部时,这个函数内被声明的所有变量都会被移动到(提升到)函数最开始的地方。这是一个很重要 的概念。这也就以为着函数体内声明的变量在该函数执行的时候就已经存在了, 但是与之相关的赋值操作并不会被提升,它还在原来的位置上。
var a = 123;
function f() {
var a // 类似 var a = undefined
console.log(a) //undefined
var a = 1; // a = 1
console.log(a) // 1
}
f();
而在es6 中let,const并不会收到变量提升的约束,所以在代码书写中,避免使用var, 使用let ,const 来定义变量
匿名函数
const f = function() {
console.log('aaa')
}
匿名函数有两种优雅的用法
- 将匿名函数最为参数传递给其它函数,通过传递函数来完成功能
- 通过匿名函数来执行某些一次性任务。
回调函数
将函数A 传递给函数B,由B来执行A时,A就成了一个回调函数(callback function)
const A = function() {
return 1
}
const B = function() {
return 2
}
function invoke(a, b) {
return a() + b()
}
const res = invoke(A, B);
console.log(res) // 3
函数A,B都是回调函数。
什么时候使用回调函数
- 不做命名的情况下传递函数,节省变量名的使用
能重写自己的函数
function a() {
console.log('a');
a = function() {
console.log('b')
}
}
这样,第一次调用函数的时候会
- 先打印a,(可以把这个操作视为一次性的准备操作),
- 函数a 会被重新定义,被赋值为新的函数
闭包
关于闭包的讲解-请起步阮一峰老师的博客,阮一峰老师的博客讲解闭包最为清晰。
call() 和 apply()
在 JS 中,每个函数都有call() 和apply() 两个方法,可以用来触发函数,并指定相应的调用参数。
这两个方法的的功能就是 让一个对象去『调用』另一个对象的方法,并为己所用。
const obj = {
name: "Kobe",
sayName(who){
console.log(`${who}, I am ${this.name}`)
}
}
const my_obj = {
name: "Yao"
}
这里有两个对象,很明显,obj 的sayName方法也适用于my_obj,因此希望将 sayName 方法作为自身的方法去调用,这时可以使用call()、apply()
obj.say.call(myObj, 'call')
obj.say.apply(myObj, ['apply'])
call 和 apply 的区别就是参数的不同,apply 传参为数组。