JavaScript函数

159 阅读6分钟

函数也是一种对象

具名函数

function 函数名(形式参数1,形式参数2){
    语句
    return 返回值
}

匿名函数

  • 上面的具名函数去掉函数名就是匿名函数
  • let a = function(){} 函数仍为匿名函数,只不过变量a存了匿名函数的地址
  • 也叫做函数表达
  • 考点
let add = function fn(){
    return 1
}
//将具名函数赋值给变量add,此时只能通过add调用
fn()
//使用函数名调用会报错

箭头函数

let f1 = x => x+1 
//参数只有一个,语句也只有一句可省略括号,
//return及花括号
f1(1) //2

let f2 = (x,y) =>{
    let temp = x+y+1
    return temp
}
f2(1,2) //4
//参数大于一个时需要使用括号包起来
//语句大于一句时,需要{}括起来且手动return设定返回值

let f3 = x=>{name:'ben'} //{}将视为代码块而不是对象
改写为:
let f3 = x=>({name:'ben'})
//可返回一个对象

构造函数

let fn = new Function('x','y','return x+y')
//没有人使用的语法,但是对了解函数有帮助
//所有函数都是由Function构造出来的
//包括Object,Array,Function

fn和fn()区别

  • fn是函数本身,fn()是调用函数fn
let fn = ()=>1
let fn2 = fn
fn2()

//解析步骤
fn存了匿名函数的地址
fn2也存了匿名函数的地址
fn()会调用匿名函数
fn和fn2都不是函数本身,只是存了匿名函数的引用

每个函数都有的要素

  • 调用时机
  • 作用域
  • 闭包
  • 形式参数
  • 返回值
  • 调用栈
  • 函数提升
  • arguments(箭头函数除外)
  • this(箭头函数除外)

调用时机

let a = 1
function fn(){
    console.log(a)
}
//请问打印出什么
//什么都没打印,因为没有调用

let a = 1
function fn(){
    console.log(a)
}
fn()
//请问打印出什么
//打印出1

let a = 1
function fn(){
    console.log(a)
}
a=2
fn()
//请问打印出什么
//打印出2


let a = 1
function fn(){
   setTimeout(()=>{
      console.log(a) 
   },0)
}
a=2
fn()
//请问打印出什么
//打印出2

调用时机写了一篇相关博客,可以参考 Javascript调用时机

作用域

  • 每个函数都会创建一个作用域
function fn(){
    let a = 0
}
console.log(a)
//报错,无法访问到fn函数作用域里的a
//let的作用域只在最近的花括号包围中

全局变量和局部变量

  • 在顶级作用域声明的是全局变量
  • window的属性是全局变量
  • 其他都是局部变量
  • 函数可以嵌套
function f1(){
    let a = 1
    function f2(){
        let a = 2
        function f3(){
            let a = 3
            console.log(a)
        }
		f3()
    }
    f2()
    console.log(a)
}
f1()
//打印f3函数作用域中的a,和f1函数作用域中的a
//3 1

作用域规则

如果多个作用域有同名变量a
  • 那么查找a的声明时,就向上取最近的作用域
  • 简称就近原则
  • 查找a的过程与函数无关
  • 但a的值y与函数执行有关

闭包

  • 如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包
  • let a和f3组成了闭包
function f1(){
    let a = 1 //a和f3组成了闭包
    function f3(){
        console.log(a)
    }
    f3()
}
f1()

参数

形式参数 就是非实际参数
function fn(x){
    return x
}
//x就是形式参数,并不是实际的参数
fn(1)
//调用fn时,1是实际参数,会被赋值给x
//形参可认为是变量声明

function fn(){
    var x = arguments[0]
    return x
}

let o = { name :'mike' }
function f(obj){
    obj.name = 'ben'
    return obj
}

f(o)
o //{name:'ben'}
//参数为对象时,传入的为对象的地址
形参可多可少
function fn(a){
    return a + arguments[1]
}
fn(1,2)
//3

function fn2(a,v,b){
    return a+v
}
fn2(1,2)
//3

//JavaScript的语法比较随意

返回值

  • 每一个函数都有返回值
  • 没写return,会返回undefined
  • 只有执行函数后才存在返回值,若不执行不存在返回值
  • 只有函数才有返回值 就是return后面的值

调用栈

什么是调用栈
  • JS引擎在调用一个函数前
  • 需要把函数所在的环境push(压栈)到一个数组里
  • 这个数组就叫做调用栈
  • 等函数执行完了,就会把环境pop(弹栈)出来
  • 然后return到之前的环境,继续执行后续代码
function add(x,q){
    return x+q
}

console.log(1+add(1,2))
//console.log的所在环境压栈,进入函数
//add的所在环境压栈,进入函数
//执行完add函数后弹栈,回到add的所在环境,也就是console.log函数中
//执行完console.log函数后打印4,弹栈回到console.log的所在环境,也就是顶级作用域

递归函数

乘阶
function f(n){
    return n!==1?n * f(n-1):1
}

f(4)
//=4 * f(3)
//=4 * (3 * f(2))
//=4 * (3 * (2 * f(1))
//=4 * (3 * (2 * (1))
//=4 * (3 * (2))
//=4 * (6)
//=24
//先递进再回归
//f(10000) 就要压10000次栈
栈有多大?
function f(n){
    return n!==1?n + f(n-1):1
}
f(20000) //爆栈
//VM189:1 Uncaught RangeError: Maximum call stack size exceeded

//可用此函数测试不同环境下的调用栈有多大
function computeMaxCallStackSize(){
    try{
        return 1 + computeMaxCallStackSize()
    }catch(e){
        return 1
    }
}

函数提升

  • 不管把具名函数放到哪里,都会跑到第一行
a()
function a(){
    return 1
}
//执行后返回1

let b = 1
function b(){}
//-------相当于以下代码
function b(){}
let b = 1
//使用let会报错,不允许重复声明

var a = 1
function a(){}
a //1 因为函数会跑到第一行,a的值后续才被赋值为1

let m = function(){}
//这是赋值,不是变量提升

this和arguments (箭头函数没有)

arguments
function a(){
    console.log(arguments)
}
a() //输出一个伪数组
a(1,2,3) //arguments就是包含[1,2,3]的伪数组
this
function b(){
    console.log(this)
}
b() //window 如果不给任何条件,this默认指定为window

//可以用call传入this和arguments,第一个参数为this,后面的为arguments
function c(){
    console.log(this)
    console.log(arguments)
}
c.call(1,2,3)
//Number对象 如果传入的this不是对象,js会自动封装成对象
//包含[2,3]的伪数组
this是隐藏的参数,arguments是普通参数
假设没有this
let obj = {
    name : 'ben'
    sayName(){
        console.log('my name is : '+ obj.name)
    }
}
obj.sayName() //my name is : ben

//我们可以用直接保存了对象地址的变量获取'name',这种方式叫做引用
//如果obj改名了,函数就挂了
//sayName函数可能在另一个文件中
//需要一种方式拿到对象的引用

//土办法,用参数,python用了这种方式
let obj = {
    name : 'ben'
    sayName(o){
        console.log('my name is : '+ o.name)
    }
}
obj.sayName(obj) //my name is : ben

js在每个函数中加了this

  • 用this可以获取还未知道名字的对象
let obj = {
    name : 'ben'
    sayName(){
        console.log('my name is : '+ this.name)
    }
}
obj.sayName()
//相当于
obj.sayName(obj) //obj的地址被传给了this
//这样每个函数都能用this获取一个未知对象的引用了
obj.sayName()会隐式的把obj作为this传给sayName,this就是最终调用sayName的对象

两种调用

  • obj.sayName()
  • 会自动的把obj传到函数里,作为this
  • obj.sayName.call(obj),如果this不重要可用null或undefined占位,一定要传入this,apply和caal使用一致,入参改为数组形式
  • 手动的将obj传到函数里,作为this
重写forEach
Array.prototype.forEachTest = function(fn){
    for(let i = 0;i<this.length;i++){
        fn(this[i],i)
    }
}

[1,2,3].forEachTest(function(value){
	console.log(value)
})
[1,2,3].forEachTest.call([1,2,3],function(value){
    console.log(value)
})
//输出123

绑定this

function f1(p1,p2){
    console.log(this)
}
let f2 = f1.bind({name:'ben'}) //指定this为{name:'ben'}
//f2相当于就是f1绑定了this的新函数
f2() //等价于f1.call({name:'ben'})

箭头函数没有arguments和this

  • 里面的this就是外面的this
console.log(this) //window
let fn()=()=>{console.log(this)}
fn() //window

fn.call({name:'ben'}) //call指定也没用

let b = 2
let fn2 = ()=>{
    console.log(b)
}
//如同b的值,是外面的值

let fn3 = ()=>{
    console.log(arguments)
}
fn3() //报错,因为没有arguments

立即执行函数

只有js才有的东西,目前用的比较少,作用在不想生成全局变量
//语法前面需要有操作符(!,-,+,())
! function(){
  var a = 2  
}()

新版js造局部变量
{
    let a = 1
}

console.log('1')
(function(){
  var a = 2  
}())
//因为console.log返回值为undefined,所以相当于undefined(function(){})