在JS中,函数就是特殊的对象,典型的对象有:Object对象、Array数组对象,函数对象。
1. 概念
1.1 什么是函数
- 是由事件驱动的或者当它被调用时执行的可重复使用的代码块;也可以理解为函数是一组拥有某些特定功能的、能够被重复调用的、闭合代码块。函数的意义在于出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句。
1.2 什么是参数
-
定义在函数内部的语句,都是相同的,但可以通过“参数”这个东西,来让语句有差别。
-
定义函数的时候,内部语句可能有一些悬而未决的量,就是变量,这些变量,我们要求在定义的时候都罗列在小括号中(形式参数),形参(非实际参数)只用来在函数内部使用,在函数外部形参失效。通常形式参数不用var声明,直接写变量名即可,形参可认为是变量声明。
-
调用的时候,要把这个变量的真实的值(实际参数),一起写在括号里,这样随着函数的调用,这个值也传给了a
1.3 什么是返回值
函数可以通过参数来接收东西,更可以通过return的语句来返回值,函数执行完了以后才会返回,“吐出”东西。函数已经return后面的语句就不执行。没写return,返回值为undefined
1.4 什么是函数的作用域与变量
- 作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:
- 全局作用域:变量在整个程序中一直存在,所有地方都可以读取。
- 函数作用域:变量只在函数内部存在,
只在花括号里面生效{},在函数外部变量失效。
- 和作用域与之对应的,javascript中有两种变量:
- 全局变量(global variable):在函数外部声明的变量,它可以在函数内部读取。定义在全局范围内的,没写在任何function里面的,叫做全局变量:主要有两种:
在顶级作用域声明的变量是全局变量;
window的属性是全局变量
let b = 1; //在顶级作用域声明的变量
window.c = 2; //window的属性是全局变量,在任意地方都声明有效,能够读取
- 局部变量(local variable):在函数内部定义的变量,外部无法读取。在function里面直接用var声明的变量,叫做局部变量,只在function里面有定义,出了function没有定义的;函数的参数,会默认定义为这个函数的局部变量
- 作用域规则
如果多个作用域有同名变量a:
查找a的声明时,就向上取最近的作用域,简称"就近原则"
查找a的过程与函数执行无关(静态作用域,也叫词法作用域);
但a的值与函数执行有关
- 函数能封闭住作用域:
一个变量如果定义在了一个function里面,那么这个变量就是一个局部变量,只在这个function里面有定义。出了这个function,就如同没有定义过一样。
1.5 什么是嵌套函数
- 局部作用域嵌套局部作用域,遵循就近原则
function f1(){
let a = 1
function f2(){
let a = 2
console.log(a) //输出结果2
}
console.log(a) //输出结果1
a = 3
f2()
}
f1()
1.6 闭包
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。
2. 定义一个函数:
浏览器JS引擎构造了Function,然后指定它的构造者是自己,所有函数都是由window.Function构造的
let fn = new Function('x','y','return x+y')
// x为第一个形参,y为第二个形参,return x+y 为函数体
函数对象自身属性和共用属性
自身属性:
- 'name'/'length'
共用属性:
- 'call'/'apply'/'blind'
2.1 具名函数
function 函数名(形式参数1,形式参数2,...){
函数体语句
return 返回值
}
- 定义一个函数,用关键字function来定义,function就是“功能”的意思。表示这里面定义的语句,完成了一些功能。function后面有一个空格,后面就是函数名字,函数的名字也是关键字,命名规范和变量命名是一样的。名字后面有一对儿圆括号,里面放置参数,然后就是花括号,花括号里面是函数的语句,举例如下:
function fn(x,y){
return x*x+y
}
fn(2,5) // 9
2.2 匿名函数
也就是去掉函数名(fn)的具名函数,但应该给一个变量来容纳它
let a =function(x,y){
return x+y
}
这种声明函数的方式没有函数名,而是用变量a来指代函数。调用函数的时候通过访问变量名来调用函数。声明一个变量a,容纳了这个函数的地址,并且赋值,等号右边的也叫函数表达式
2.3 具名+匿名结合写法:
let a =function fn(x,y){
return x+y
}
fn(1,2)
Uncaught ReferenceError: fn is not defined
at <anonymous>:1:1
出现报错的原因是上述代码中的fn的作用域只在function fn(x,y){xx+y}这一块地方,其他地方都未定义,需要用的时候只能借助于变量a,如果没有等于号,只剩下function fn(x,y){xx+y},那就是全局作用域。
2.4 箭头函数
这是ES6的一个新语法,一些用法如下:
let f1 = x =>x*x //箭头左边为输入参数,右边为输出参数
f1(3) // 9
-----------------------------------------------------
let f2 = (x,y) => x*y // 注意圆括号不能省
f2(6,8) // 48
-----------------------------------------------------
let f3 = (x,y) => { // 花括号、return都不能省
console.log('hello')
return x*y
}
f3(6,8) // hello 48
------------------------------------------------------
let f4 = x => ({ name:x }) // 注意圆括号
f4('xinhai') // {name: 'xinhai'} 对象
------------------------------------------------------
let f5 =(x,y)=>({name:x,age:y})
f5('xinhai',18) // {name: 'xinhai', age: 18} 对象
2.5 构造函数
let f = new Function('x','y' 'return x+y')
用的较少,不过用的好处是能让你清楚函数是由谁构造出来的。所有函数都是Function构造出来的,Function是由Function构造出来的。
3. 函数自身和函数调用
fn是函数自身,而f()是函数调用。相较于函数声明的多种多样,函数的调用方式就简单很多。只要函数已经被声明,直接写出函数名和函数参数即可调用函数。函数如果不调用,那么里面的语句就一辈子不会执行,不调用就等于白写。
举例1:
let fn =()=>console.log(1)
undefined
fn
上例中,fn将不会有任何结果,这是因为fn并没有调用/执行,而fn()将会打印出1,有()才是调用。
举例2:
let fn = ()=> console.log('hi')
let fn2 = fn
fn2()
fn保存了匿名函数的地址,这个地址被复制给了fn2 fn2()调用了匿名函数 fn 和 fn2 都是匿名函数的引用而已 真正的函数既不是fn也不是fn2
4. 函数的要素
4.1 调用时机
函数的调用时机不同,得到的结果也不同。
- 例1:
let a = 666
function fn(){
console.log(a)
}
上面只是创建了函数,并没有调用它,故不会打印出什么来。
- 例2:
let a = 666
function fn(){
console.log(a)
}
fn() // 666
上例已经调用了函数,打印出666
- 例3:
let a =1
function fn(){
console.log(a)
}
a = 2
fn() //2
- 例4:
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2 //1,因为调用的时候就是1
- 例5:
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a=2
上例中是,setTimeout定时器设置的是要把整个代码执行完,马上就做的事,此时a=2,再打印出a
- 例6:
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
上例中是,循环先执行完,此时i=6,再去执行定时器,打印出6次,6个6
- 例7
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
答案是0,1,2,3,4,5
上例7其实与例6,并无本质区别,ES6却把此代码的执行结果改为每次循环会多创建一个i,也就是因为for与let一起用的时候会加东西。
4.2 调用栈
JS引擎在调用一个函数前,需要把函数所在的环节push到一个数组里,这个数组叫做调用栈。记录了进入了一个函数,怎么回去,回到哪里的问题。
相关概念:弹栈、压栈、爆栈
4.3 递归函数
阶乘:
function f(n){
return n!== 1 ? n*f(n-1) : 1
} //如果n不等于1,则n*f(n-1),否则(n=1),结果就是1