js函数全解

280 阅读5分钟

函数是对象

定义一个函数

具名函数

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;

函数作用域

每个函数都会默认创建一个作用域;

全局变量

有两种情况下声明的变量是全局变量,其他都是局部变量;

  1. 在顶级作用域声明的变量是全局变量;
  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()方法

call方法,也可以同时传参数

bind()方法

apply()方法

箭头函数

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