JS函数

194 阅读5分钟

函数是一种特殊的对象

定义一个函数

  • 具名函数
function 函数名(形参1,形参2){
    语句
    return 返回值
}
  • 匿名函数(去掉函数名)
let a =function(x,y){
    return x+y
}//也叫函数表达式

note:

let a =function fn(x,y){
    return x+y
}
fn(1,2)//会报错,因为fn函数在等号右边,则作用域范围只在等号右边
  • 箭头函数
let f1 = x =>x*x//箭头左边为输入,右边为输出
f1(9)//输出81
let f2 = (x,y) => x*y//若箭头左边有两个输入参数,需要括号括起来
f2(8,9)//输出72
let f3 = (x,y) => {
    console.log('hi')
    return x*y
}//若箭头右边有多个语句,必须加花括号和return 返回值
let f4 = x =>({
    name:x
})//若要在箭头右边直接返回一个对象,需要加括号
  • 构造函数(很少用)
let fn1 = new function('x''y',
    'console.log(\'hi\');
    return x*y'
)  

所有函数都是Function构造出来的,包括Object,Array,Function都是。

函数自身v函数调用

  • 函数自身
let fn = () =>console.log('hi')
fn//没有结果,因为fn没有执行(调用)
  • 函数调用
let fn = () =>console.log('hi')
fn()//打印出hi,有圆括号才是调用
let fn = () =>console.log('hi')//fn只保存了匿名函数的地址
let fn2 = fn//地址复制给fn2
fn2()//fn和fn2都是匿名函数的引用而已

函数的要素

  • 调用时机

    调用时机不同,结果不同

let a = 1
function fn(){
    console.log(a)
}
a = 2
fn()//打印出2
let a = 1
function fn(){
  setTimeout(() => {
    console.log(a)
  },0)
}
fn()
a = 2//先执行完程序,再打印出2
 let i= 0 
 for(i = 0;i < 6;i++){
    setTimeout(() =>{
        console.log(i)
    },0)
 }//打印出6个6,而不是0,1,2,3,4,5
 for(let i = 0;i < 6;i++){
    setTimeout(() =>{
        console.log(i)
    },0)
 }//打印出0,1,2,3,4,5,因为JS在forlet一起用时会加东西,每次循环会多创建(复制)一个i(迎合新手想法)。
  • 作用域

    • 全局变量和局部变量

    在顶级作用域声明的变量就是全局变量,window的属性是全局变量,其他都局部变量。

    • 函数嵌套
    function f1(){
        let a = 1
        function f2(){
            let a = 2
            console.log(a)
        }
        console.log(a)
        a = 3
        f2()
    }
    f1()//打印出1,2
    

    当多个作用域有同名变量a,那么查找a的声明时,就向上取最近的作用域(就近原则),查找a的过程与函数执行无关(静态作用域或词法作用域),但a的值和函数执行有关。

  • 闭包

      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()//打印出1,2
    

    如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包,如上面的a和f3组成了闭包。

  • 形式参数

function add(x,y){
    return x+y
}//x和y为形参,因为不是实际的参数
add(1,2)//调用add时,1和2为实参,会被赋值给 x,y
function add(){
    var x = arguments[]
    var y = arguments[]
    return x+y
}//形参其实可认为是变量声明
  • 返回值

每个函数都有返回值

function hi(){
    console.log('hi')
}
hi()//返回值为undefined,因为没写return
function hi(){
    return console.log('hi')
}
hi()//返回值为console.log('hi')的值,即undefined,只是打印了hi

函数执行完后才会返回,只有函数才有返回值。

  • 调用栈
  1. JS引擎在调用一个函数前,需要把函数所在的环境push到一个数组里,数组即为调用栈。

  2. 等函数执行完后,就会把环境pop出来。

  3. 然后return到之前的环境,继续执行后续代码。

    • 递归函数

      1. 阶乘

        function f(n){
            return n === 1 ? 1 : n*f(n-1)
        }
        f(100)//压100次栈
        
      2. 递归的调用栈最长为

        Chrome:12578

        Firefox:26773

        Node:12536

      3. 爆栈

        调用栈中压入得帧过多,程序会崩溃。

  • 函数提升
function fn(){}

不管将具名函数声明在哪,都会跑到第一行。

let fn = function(){}//为赋值,右边的匿名函数声明不会提升到第一行
  • arguments(箭头函数没有)

伪数组

Array.from()//可以将伪数组变成数组
  • this(箭头函数没有) 如果不给任何条件,this默认指向window,如果传的不是对象,JS会自动封装成对象。

    • 传this
    fn.call(xxx,1,2,3)//传this或arguments
    

    如果非要传数字,不想自动封装成对象

    function fn(){
        'use strict'
        console.log('this:'+this)
    }
    

    this是一个隐藏参数, arguments是普通参数。

    • 假如没有this

      let person = {
          name: 'frank',
          sayHi(){
              console.log(`你好,我叫` + person.name)
          }
      }
      //可以直接保存对象地址的变量获取'name',简称引用
      
      1. 如果person改名了,sayHi函数就挂了。
      2. 或者sayHi函数有可能在另一个文件里。
      3. 而JS中person.sayHi()会隐式地person作为this传给sayHi,方便sayHi通过this获取person对应的对象。
  • 新手调用法

person.sayHi()
//会自动把person传到函数里,作为this
  • 老手调用法

    person.sayHi.call(person)
    //要手动把person传到函数里,作为this
    

    例如:

    function add(x,y){
        return x+y
    }
    add.call(undefined,1,2)//3
    

    第一个参数要作为 this,而原函数没有 this,只能用 undefined 占位,null 也可以。

    再如:

    Array.prototype.forEach2 = function(fn){
        for(let i=0;i<this.length;i++){
            fn(this[i],i)
        }
    }
    //因为使用forEach2时总会用arr.forEach2,所以arr就被自动传给了forEach2
    
    array.forEach2.call(array,(item)=>console.log(item))
    //1,2,3
    array.forEach2.call({0:'a',length: 1 },(item)=>console.log(item))
    //this不一定是数组
    
    • this 的两种使用方法
    1. 隐式传递
    fn(1,2)
    obj.child.fn(1)
    
    1. 显示传递
    fn.call(undefined,1,2)或fn.apply(undefined,[1,2])
    //使用apply需要在参数上加中括号[](需要数组形式)
    obj.child.fn.call(obj.child,1)
    
    • 绑定 this
    1. 使用.bind 可以让 this 不被改变
    function f1(p1,p2){
            console.log(this,p1,p2)
        }
        let f2 = f1.bind({name:'frank'})
        //f2就是f1绑定了this之后的函数
        f2()
        //等价于f1.call({name:'frank'})
    
    1. .bind 还可以绑定其他参数
     let f3 = f1.bind({name:'frank'},'hi')
     f3()//等价于f1.call({name:'frank'},'hi')
    
  • 箭头函数(没有 this 和 arguments)

console.log(this)
//window
let a = () => console.log(this)
//箭头函数的this就是外部window的this,一个普通的变量
a.call(1)//箭头函数的this不能指定,还是外部的this,除非外部this改变
  • 立即执行函数(现在用的很少)

获取局部变量,使用()立即执行,只需要在匿名函数前加个运算符即可,推荐感叹号!

! function (){
    var a =2
    console.log(a)
} ()//2,+,—可以,1* 也可以,只要加个运算

新版 JS 只需要 let 外加{ }即可

{
    let a = 2
    console.log(2)
}//2

Note:

function 外有()时,才会用;分隔开,也是唯一需要加分号的地方。

console.log('hi');
(function (){
    var a =2
    console.log(a)
} ())