函数是对象

定义一个函数
具名函数
function 函数名(形式参数1, 形式参数2) {
语句;
return 返回值
}
匿名函数
具名函数把函数名去掉就是匿名函数,也叫函数表达式
function(形式参数1, 形式参数2) {
语句;
return 返回值
}
由于匿名函数没有函数名,我们需要使用一个变量来储存此匿名函数,不然此匿名函数会消失;
let fn = function(形式参数1, 形式参数2) {
语句;
return 返回值
}
特别例子分析
let a = function fn(x,y){
return x+y
}
fn(1,2)
上面的代码运行会报“fn is not defined”错误,因为虽然这个函数有名字,但是一旦被赋给一个变量,那么此函数的名字的有效作用域为等号右边区域,此时函数的外部代表是变量a,运行a(1,2)
可正常运行;
箭头函数(非常帅)
1个输入参数,1个输出结果
let f1 = x => x*x
箭头的左边是输入参数,右边是输出结果;
多个输入参数,1个输出结果
let f2 = (x,y)=> x*y
多个输入参数,函数体多个语句
let f3 = (x,y) => {
x = x*3;
y = y*7;
return x*y;
}
如果函数体直接返回1个对象,此时比较特殊,需要加圆括号
let f4 = (x,y) => ({ name: x, age: y })
此时的圆括号不能省络,因为js看到{}会认为是代码块,会解析错误,如果加上(),会认为括号里面的内容是一个整体;
构造函数
let f5 = new Function('参数1', '参数2', '函数体代码')
这种方法创建函数,基本上没人使用,但是可以让我们知道 函数是由谁来构造的;
函数的调用与调用时机
我们定义了一个函数是没有效果的,只有在调用的时候才有效果,而且调用的时机不同,结果不同;
let a = 1
fn = function(){
console.log(a)
}
fn()
a=2
上面代码,打印结果为1;
let a = 1
fn = function(){
console.log(a)
}
a=2
fn()
上面代码,打印结果为2;
函数作用域
每个函数都会默认创建一个作用域;
全局变量
有两种情况下声明的变量是全局变量,其他都是局部变量;
- 在顶级作用域声明的变量是全局变量;
- window的属性是全局变量
其他都是局部变量
闭包
如果一个函数用到了外部的变量,那么这个函数加上这个变量就叫做闭包
let a = 1
fn = function(){
console.log(a)
}
上面的代码就叫做闭包,至于闭包的用途,我会另外再讲;
形式参数
形式参数的意思是非实际参数,在调用的时候传的参数是实参
let fn = function(x,y){
return x+y;
}
fn(2,3)
x,y为形参,2,3为实参
形参可被认为是变量声明
上面的代码等价于下面的代码
let fn = function(x,y){
var x = arguments[0];
var y = arguments[1];
return x+y;
}
fn(2,3)
返回值
只有函数才有返回值,也就是return 后面的语句;如果没有return关键字,则默认的是undefined
调用栈
js引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,这个数组叫做调用栈;等函数执行完了,就会把环境pop出来,然后return到之前的环境,继续执行后续代码;
console.log(1);
console.log('和为:', add(1,2);
console.log('over')
上面的这三行代码的压栈与弹栈过程如下:
在进入console.log(1)之前,js引擎会把此函数的位置即第一行的信息存到栈里面,等到执行完log函数后,栈就会弹出此条信息,已方便js知道自己所处的位置;同理在执行console.log('和为:', add(1,2)
时会先执行add(1,2)函数,同理,在调用add函数之前,会把add的位置,即第2行5列存到栈里面,等执行完add函数后,栈就会弹出此条信息,以方便js知道自己返回的位置;
爆栈
如果调用栈中压入的帧过多,程序就会崩溃
chrome调用栈最多为12578
firefox调用栈最多为26773
node 12536
递归函数
递归函数其实是先递进再回归

函数提升
当你使用具名函数方式声明函数时,不管你把函数放到哪里,它都会跑到第一行;
a(1);
function a(x){
console.log(x)
}
// 会打印1
var a = 2;
a(1);
function a(x){
console.log(x)
}
// 报错 a is not a function,因为此时a=2
let a = 2;
a(1);
function a(x){
console.log(x)
}
// 报错 Identifier 'a' has already been declared,因为函数a的定义会跑到第一行,此时又使用let定义a,会直接报a变量已经声明过的错误;
不会发生函数提升的情况
当你使用以下方式声明函数,不会发生函数提升的现象,因为这是赋值
fn(1);
let fn = function(x){
console.log(x)
}
// 报错:fn is not defined
函数的this与arguments
每个函数都有this与arguments,除了箭头函数;箭头函数认为this与arguments不好用就摒弃了this与arguments,就像js之父说的js原创之处不优秀;


那什么是伪数组呢?
没有数组共有属性的数组就是伪数组,其实就是其原型直接是根对象,而不是数组;
那怎么把伪数组变成数组呢?
使用Array.from()方法

如果不给条件,函数里面的this默认指向window,一般我们不需要这个默认的this,如果我们需要window,就直接使用window对象就可以了,这就是this不好的地方;

指定函数this
如果我们需要更改默认的this,可以使用call()方法,bind()方法与apply()方法
call()方法




bind()方法

apply()方法

箭头函数
里面的this就是外面的this,就算使用call方法也没有用;