JS函数

一、函数定义

1.1function命令

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

1.2匿名函数

匿名函数也叫函数表达式

let a = function(x,y){
    return x+y
}

复制代码
  • 采用函数表达式声明函数时,function 命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

    let a = function fn(x,y){
        return x+y
    }
    fn(1,2)//会报错
    复制代码

1.3箭头函数

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
}
f3(8,9) // hi  72

let f4 = x => ({name:x})  //如果需要直接返回对象,需要加上圆括号
f4('frank') // name:'frank'
复制代码

1.4构造函数

所有的函数都是 Function 构造出来的

var add = new Function(
  'x',
  'y',
  'return x + y'
);

// 等同于
function add(x, y) {
  return x + y;
}
复制代码

二、函数自身和函数调用

2.1fn

let fn = () => console.log('hi')
fn 
复制代码
  • 不会有任何结果,因为fn没有执行

2.2fn()

let fn = () => console.log('hi')
let fn2 = fn
fn2()
复制代码
  • fn 保存了匿名函数的地址
  • 这个地址被复制给了 fn2
  • fn2() 调用了匿名函数
  • fnfn2 都是匿名函数的引用
  • 真正的函数不是 fn 也不是 fn2

三、函数的要素

3.1调用的时机

函数的调用时机决定了变量的值

let a = 1
function fn(){
    setTimeout(()=>{
        console.log(a)
    },0)
}
fn()
a = 2 
//2 设置了定时器,先把a=2执行完,在调用fn
复制代码
let i = 0
for(i = 0;i<6;i++){
        setTimeout(()=>{
        console.log(i)
    },0)
}
//6个6  先执行完循环,再执行定时器
复制代码
  • setTimeout 为异步任务(放在任务队列中等待同步任务执行完才执行), for 循环被认定为同步任务(主线程上排队等待执行的任务)
  • 只有同步任务for循环完全结束,JS才会去任务队列中找到未执行的6个 setTimeout 任务,并顺序执行
for(let i = 0;i<6;i++){
        setTimeout(()=>{
        console.log(i)
    },0)

//0、1、2、3、4、5 
复制代码

3.2作用域

function fn(){
    let a = 1
}
fn()
console.log(a) //a不存在
复制代码
3.2.1全局变量与局部变量
  • 在顶级作用域声明的变量是全局变量

  • window 的属性是全局变量

  • 其他都是局部变量

3.2.2作用域嵌套
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 的值与函数执行有关

3.3闭包

如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包

function f1(){
  let a = 1
  function f2(){
    let a = 2
    function f3(){
      console.log(a) //左边的 a 和 f3 组成了闭包
    }
    a = 22
    f3()
  }
  console.log(a)
  a = 100
  f2()
}
f1()
复制代码

3.4形参

简单理解为变量的声明,形参可多可少

3.5返回值

每个函数都有返回值,只有函数执行完了才会有返回值

3.6调用栈

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

简单说明:

  • JS每次进入一个函数,都要记下来等会要回到哪里,所以要把回到的地址写到栈里面,如果进入一个函数要回到另一个函数,要把地址再放到栈里面,等函数执行完就弹栈,知道回到哪里
3.6.2递归函数
function fn(n){
    return n !== 1 ? n*f(n-1) : 1
}
复制代码

  • 如果调用栈中压入的帧过多,程序会崩溃

3.7函数提升

  • function fn(){} 不管你把具名函数声明在哪里,它都会跑到第一行

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

3.8arguments和this

每个函数都有 thisarguments,除了箭头函数

function fn(){
  console.log(arguments)
  console.log(this)
}
复制代码
  • 调用 fn 即可传 arguments,fn(1,2,3) 那么 arguments 就是 [1,2,3] 伪数组

  • 如果没有设置其他条件,this默认指向 Window

  • 可以用 fn.call(xxx, 1,2,3) 传 this 和 arguments ,this 是第一个参数,arguments 是剩下的参数

  • xxx 会被自动转化成对象(JS 的糟粕),这时要在函数声明时加上’use strict’

function fn(){
  'use strict'
  console.log(arguments)
  console.log(this)
}
复制代码
  • this 是隐藏参数,arguments 是普通参数
3.8.1形式参数、this获取对象引用
let person = {
  name: 'frank',
  sayHi(){
    console.log(`你好,我叫` + person.name)
  }
}
//可以用直接保存了对象地址的变量获取 'name',这种办法简称为引用
复制代码
  • //问题1:
    let sayHi = function(){
      console.log(`你好,我叫` + person.name)
    }
    let person = {
      name: 'frank',
      'sayHi': sayHi
    }
    /*
    person 如果改名,sayHi 函数就挂了
    sayHi 函数甚至有可能在另一个文件里面
    所以我们不希望 sayHi 函数里出现 person 引用
    */
    复制代码
  • //问题2:
    class Person{
      constructor(name){
        this.name = name 
        // 这里的 this 是 new 强制指定的
      }
      sayHi(){
        console.log(???)
      }
    }
    /*
    这里只有类,还没创建对象,故不可能获取对象的引用
    那么如何拿到对象的 name ?
    */
    
    复制代码

在不知道对象的引用下,如何获取对象的 name 属性

  • //方法1:形式参数(python采用)
    
    //对象
    let person = {
      name: 'frank',
      sayHi(p){
        console.log(`你好,我叫` + p.name)
      }
    }
    person.sayHi(person)
    
    //类
    class Person{
      constructor(name){ this.name = name }
      sayHi(p){
        console.log(`你好,我叫` + p.name)
      }
    }
    
    复制代码
  • //方法2:用 this 获取那个对象(JS采用)
    let person = {
      name: 'frank',
      sayHi(this){
        console.log(`你好,我叫` + this.name)
      }
    }
    /* 
    person.sayHi()相当于person.sayHi(person)
    然后 person 被传给 this 了(person 是个地址)
    这样,每个函数都能用 this 获取一个未知对象的引用了
    */
    
    person.sayHi.call(person)
    //需要自己手动把 person 传到函数里,作为 this(建议使用这种方法调用)
    
    person.sayHi()
    //会自动把 person 传到函数里,作为 this
    
    
    复制代码
3.8.2 call 指定 this
Array.prototype.forEach2 = function(fn){
  for(let i=0;i<this.length;i++){
    fn(this[i], i, this)
  }
}

//调用
array.forEach2.call(array,(item)=>console.log(item))

复制代码
3.8.3 this 的两种使用方法
//隐式传递
fn(1,2) // 等价于 fn.call(undefined, 1, 2)
obj.child.fn(1) // 等价于 obj.child.fn.call(obj.child, 1)

//显示传递
fn.call(undefined, 1,2)//undefined就是this
fn.apply(undefined, [1,2])//undefined就是this

复制代码
3.8.4绑定 this
//使用 .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'})

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

复制代码

3.9箭头函数

箭头函数没有 arguments 和 this

fn.call({name:'frank'}) // window

let fn = () => console.log(this) 
fn() // window

复制代码

3.10立即执行函数

ES 5 时代,为了得到局部变量,必须引入一个函数,但是这个函数如果有名字,就得不偿失

function fn(){
    var a = 1
    console.log(a)
}
复制代码

于是这个函数必须是匿名函数声明匿名函数,然后立即加个 () 执行它

function (){
    var a = 1
    console.log(a)
}()
复制代码

但是 JS 标准认为这种语法不合法,所以 JS 程序员寻求各种办法最终发现,只要在匿名函数前面加个运算符即可
!、~、()、+、- 都可以

! function (){
    var a = 1
    console.log(a)
}()
//一般使用!
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享