初探JS函数对象

252 阅读7分钟

1、定义函数

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

    • 上面的具名函数,去掉函数名就是匿名函数
    • let a = function(x,y){return x+y}
    • 右边部分也叫函数表达式,左边部分是声明一个变量a
    • 注意:如果在"="号右边的函数有函数名,那么这个函数名“fn”的作用域只存在于该等号右边,无法在其他地方使用
    • 示例1:
    • 示例2:
  • 箭头函数

let f1 = x => x*x
  • 示例:
let f2 = (x,y) => x+y  //不能省略圆括号
  • 示例:

let f3 = (x,y) => {return x+y}  //有花括号时,需要加上return
  • 示例:

let f4 = (x,y) => ({name:x,age:y})  //如果要返回一个对象,必须要在花括号外面再加一对圆括号,不然会出错
  • 示例:

  • 构造函数

    • let f = new Function('x','y','return x+y')
    • 没人用,但是能让你知道函数是谁构造的
    • 所有函数都是Function构造出来的
    • 包括Object、Array、Function 也是

2、函数的要素

每个函数都有这些东西

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

1. 调用时机

  • 例1:
let i=0
for(i=0; i<6;i++){
    setTimeout(()=>{console.log(i)},0)
}      //输出6个6
  • 例2:
for(let i=0; i<6;i++){
    setTimeout(()=>{console.log(i)},0)
}      //输出0,1,2,3,4,5
  • 这是因为JS在for和let一起用的时候会加东西,每次循环会多创建一个i,创建了6个i

2. 作用域

  • 全局变量:
    • 在顶级作用域中声明的变量
    • window的属性
    • 示例:
let a=1
function fn1(){    
    window.b = 2
}
fn1()
function fn2(){
    console.log(a,b)   //输出 1 2
}
  • 作用域规则:
    • 如果多个作用域有同名变量a
      • 那么查找a的声明时,就向上取最近的作用域
      • 也就是“就近原则”
      • 查找a的过程与函数执行无关
      • 这种与函数执行无关的作用域,称为“静态作用域”,也叫“词法作用域”
      • 但a的值与函数的执行有关

3. 闭包

function f1(){
    let a = 1
    function f2(){
        let a = 2
        function f3(){
            console.log(a)
        }
        a=22
        f3()
    }
    console.log(a)
    a = 100
    f2()
}
f1()
  • 如果一个函数用到了外部的变量,那么这个函数 + 这个变量,就叫做闭包,上面例子中的a 和f3 就组成了闭包

4. 形式参数

  • 形式参数的意思就是非实际参数
  • 示例:
function add(x,y){
    return x+y
}
//其中x 和y 就是形参,因为不是实际的参数
add(1,2)
//调用 add 时,1和2是实际参数,会被赋值给x 和 y
  • 形参可多可少,只是给参数取名字而已
  • 示例1:
function add(x){
    return x + arguments[1] 
}
  • 示例2:
function add(x,y,z){
    return x+y
}

5. 返回值

  • 每个函数都有返回值
  • console.log()的返回值为undefined
  • 函数执行完了后,才会返回
  • 只有函数有返回值

6. 调用栈

  • 什么是调用栈

    • JS引擎在调用一个函数前
    • 需要把函数所在的环境push到一个数组里
    • 这个数组叫做调用栈
    • 等函数执行完了,就会把环境弹(pop)出来
    • 然后return到值钱的环境,继续执行后续代码
  • 举例:

console.log(1)
console.log('1+2的结果为'+add(1,2))
console.log(2)

  • 进入函数前,会把一会要回来的地址写进栈中,这个过程叫做压栈

  • 执行完函数后,会根据栈中记录的地址回到原来的位置,这个过程叫做弹栈

  • 递归函数:

function fn(){
    return n !== 1? n*(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
  • 注意:不是调用自己就是递归,递归就是先递进,再回归
  • 递归的调用栈
    • 递归函数的调用栈很长
    • 递进的过程,就是压栈的过程
    • 回归的过程,就是弹栈的过程
  • 测调用栈最长是多少:
function computeMaxCallStackSize(){
    try{
        return 1+computeMaxCallStackSize()
    }catch(e){
        //报错说明 stack overflow 了
        return 1
    }
}
  • 可以得到:

    • Chrome大约是 12578
    • Firefox大约是26773
    • Node大约是12536
  • 爆栈:如果调用栈中,压入的栈过多,程序就会崩溃

7. 函数提升

  • 什么是函数提升

    • function fn(){}
    • 不管把这个具名函数声明在哪里,它都会跑到第一行
    • 示例:
  • 什么不是函数提升

    • let fn = function(){}
    • 这是赋值,右边的匿名函数声明不会提升
    • 示例:

8. arguments(每个函数都有,除了箭头函数)

  • arguments:包含所有参数的伪数组(没有数组的共有属性)
  • 如何传arguments
    • 调用fn即可传arguments
    • fn(1,2,3) 那么arguments 就是[1,2,3]伪数组
    • 示例:

9. this(每个函数都有,除了箭头函数)

  • this:this默认指向window(基本不使用),示例:
  • 如何传this:
    • 目前可以使用fn.call(xxx,1,2,3)传this和arguments,其中“xxx”就是this,“1,2,3”就是arguments,而且“xxx”会被自动转化为对象,
    • 示例:
    • 如果传的this不是对象,那么JS会自动把它封装成对象,示例:
    • 如果不让它转化为对象,需要在函数中添加语句:"use strict",示例:
  • 结论:this是隐藏参数,arguments是普通参数,this是参数
  • JS在每个函数里都加了this,用this获取那个对象
let person = {
    name: 'wbh' ,
    sayHi(this){
        console.log(`你好,我叫` + this.name)
    }
}
  • person.sayHi()也就相当于person.Hi(person)
  • 然后 person 被传给了 this 了(person是一个地址)
  • 这样,每个函数都能用 this 来获取一个位置对象的引用了
  • person.sayHi() 会隐式的把 person 作为 this 传给 sayHi
  • sayHi 可以通过 this 引用person
  • 也就是说,this就是最终调用sayHi的对象

两种调用方法:

  • 小白调用法
    • person.sayHi()
    • 会自动把 person 传到函数里,作为this
  • 大师调用法
    • person.sayHi.call(person)
    • 需要自己手动把 person 传到函数里,作为this
    • 示例:
    • 也就是说,把 {'name':'xxx'} 这个对象,作为 this,那么 console.log(this.name) 的输出结果就是 xxx

例1:

function add(x,y){
    return x+y
}
add.call(undefined,1,2)  //   3 ,使用了大师调用法
  • 为什么要用一个undefined:
  • 因为第一个参数要作为this
  • 但是代码中没有this
  • 所以用undefined占位,null也可以

例2:

Array.prototype.forEach2 = function(fn){
   console.log(this)
}
let array = [1,2,3]
array.forEach2.call(array)   //[1,2,3],大师写法
array.forEach2()     //[1,2,3],小白写法
Array.prototype.forEach2 = function(fn){
    for(let i=0;i<this.length;i++){
        fn(this[i],i,this)
    }
}
let array =[1,2,3]
array.forEach2.call(array,function(x,y,z){
    console.log(x,y,z)    //1 0 [1,2,3]
})                        //2 1 [1,2,3]
                          //3 2 [1,2,3]
                          
array.forEach2((x,y,z)=>console.log(x,y,z)) //小白写法

示例:

  • this 一定是数组吗?不一定,如:
Array.prototype.forEach2.call({0:'a',1:'b',length:2},(item)=>console.log(item))   //a b

其中 {0:'a',1:'b',length:2} 是一个伪数组

  • this 的两种使用方法:

隐式传递:

fn(1,2)  //等价于fn.call(undefined,1,2)
obj.child.fn(1)  //等价于obj.child.fn.call(obj.child,1)

显式传递:

fn.call(undefinde,1,2)
fn.apply(undefined,[1,2])
  • 绑定this
    • 使用 .bind 可以让 this 不被改变
function f1(p1,p2){
    console.log(this,p1,p2)
}
let f2 = f1.bind({name:'wbh'})  //那么 f2 就是 f1 绑定了 this 后的新函数
f2()                            //等价于f1.call({name.'wbh'})

示例1:

示例2:
示例3:

可以看到{name.'wbh'}已经被绑定在f2上

  • .bind 还可以绑定其他参数
let f3 = f1.bind({name:'wbh'},'h1')   //'hi'相当于形参p1
f3()                                  //等价于 f1.call({name:'wbh'},'h1')
let f4 = f1.bind({name:'wbh'},'h1','hello')  //'hello' 相当于形参p2
f4()                                         //等价于f1.call({name:'wbh'},'h1','hello')  

示例:

3、箭头函数(没有arguments和this)

  • 里面的this就是外面的this
console.log(this)    //window
let fn =()=> console.log(this)
fn()                //window
  • 就算你加call都没用
fn.call({name:'wbh'})   //window
  • arguments,示例:

4、立即执行函数

  • 原理:
    • 在ES5时代,为了获得一个局部变量,必须引入一个函数,这个函数必须是一个匿名函数,声明后,立即加个 () 执行它,示例:
function (){     //但是JS认为这样的语法不合法
    var a = 2
    console.log(a)
}() 
- 于是,只要在匿名函数前加一个运算符即可,!、~、()、+、- 都可以
- 优先推荐加 !
!function (){     
    var a = 2
    console.log(a)
}()       //2
  • 现在新的标准局部变量声明方式:
{
    let a = 1    console.log(a)   //2
}
console.log(a)       //报错

5、本文内容为本人与饥人谷共同出品,转载请注明出处