一.四种方式定义函数

函数是一种对象

1.如何定义一个函数

  • 具名函数

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

  • 匿名函数

上面的具名函数,去掉函数名就是匿名函数
let a = function(x,y){return x+y}
也叫函数表达式
//a只是放了匿名函数的地址,这个函数没有名字
let f4=x=>({name:x }) //如果是对象,那么要加圆括号

  • 箭头函数
    • let f1 = x => x*x
    • let f2 = (x,y) => x+y //圆括号不可省略
    • let f3 = (x,y) =>{return x+y} //花括号不可省略
    • let f4 = (x,y)=>({name:x,age:y})//直接返回对象会出错,要加圆括号
  • 用构造函数
    • let f=new Function('x','y','return x+y')
    • 基本没人用,但是能让你知道函数是谁构造的
    • 所有的函数都是Function构造的
    • 包括Object,Array,Function
  • 函数自身VS函数调用,fn VS fn()

fn 是指函数本身,fn()是指调用函数

  • 函数自身
    • 代码
      • let fn = () => console.log(‘hi’)
      • fn不执行,而fn()才会执行
    • 结果
      • 不会有任何影响
      • 因为fn没有执行
  • image.png
    • 上面的fn 不是指那个函数,而是保存了,函数的地址
    • let fn2=fn,//把函数地址传递给fn2了
    • 真正的函数,既不是fn也不是fn2,因为fn和fn2都是保存了匿名函数的地址

    • 函数的要素

      每个函数都有这些东西

      调用时机

      作用域

      闭包

      形式参数

      返回值

      调用栈

      函数提升

      arguments

      this(除了箭头函数)

      二.调用时机

      先看几个例子:
      image.png
      image.png
      例5是等一会儿再执行setTimeout里面的代码,
      所以等了六次,当i=6的时候,for循环结束,i固定变成6了,这个时候,前面有六次setTimeout需要执行的函数,开始执行了,然后执行六次打印i(此时i为6)的操作,打印:6 6 6 6 6 6

image.png
例6是每次都会把i复制一份,留在setTimeout里面,所以打印0,1,2,3,4,5,很智障!!!

三.作用域:就近原则&闭包

作用域规则:

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

image.png

闭包:(看上面的图)

如果一个函数用到了外部的变量,那么这个函数加上这个变量就叫做闭包
左边的a和f3就组成了闭包
闭包的用途以后再说**

四.参数和返回值

1.形式参数

  • 形式参数的意思就是非实际参数

function add(x,y){
return x+y
}
//其中x,y就是形参,因为并不是实际的参数
add(1,2)
//调用add时,1和2是实际参数,会被赋值给x,y

  • 形参可认为是变量声明

//上面代码近似等价于下面代码
function add(){
var x = arguments[0]
var y = arguments[1]
return x+y
}

2.返回值

  • 每个函数都有返回值

function hi(){
console.log('hi')
}
hi()
没写return ,所以 返回值是undefined
function hi(){return console.log('hi')}
hi()
返回值为console.log('hi')的值,即undefined

  • 函数执行完了才会返回
  • 只有函数有返回值

    • 1+2值为3

      五.递归,调用栈与爆栈,寒素提升

      1.调用栈

  • 什么是调用栈

    • JS 引擎在调用一个函数前
    • 需要把函数所在的环境push到一个数组里
    • 这个数组叫做调用栈
    • 等函数执行完了,就会把环境pop出来
    • 然后return到之前的环境,继续执行后续代码
  • 举例
    • console.log(1)
    • console.log(‘1+2的结果为:’+add(1,2))
    • console.log(2)

2.爆栈

如果调用栈中压入的栈过多,程序就会崩溃
function computerMaxCallStackSize() {
try{
return 1 + computerMaxCallStackSize();
}catch(e){
//报错了说明stack overflow了
return 1
}
}

3.函数提升

函数永远会跑到最前面

  • 什么是函数提升

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

  • 什么不是函数提升

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

六.person.sayHi()的this

1.如果不给任何条件,this默认指向window(小写的),例如:

function fn(){
console.log(this)
}

2.如果传入的this不是对象,js会帮你自动封装成对象

3.arguments 和 this

  • 代码

function fn(){
console.log(arguments)
console.log(this)
}

  • 如何传arguments
    • 调用fn即可传arguments
    • fn(1,2,3)那么arguments就是[1,2,3]伪数组
  • 如何传入this
    • 目前可以用fn.call(xxx,1,2,3)传this和arguments
    • 而且xxx会被自动转化成对象(JS的糟粕)

image.png
image.png
image.png
上面图片里,person.sayHi()相当于
person.sayHi(person)
然后person被传给this了(person是个地址)
这样,每个函数都能用this获取一个未知对象的引用了

4.总结

  • 我们想让函数获取对象的引用
  • 但是并不想通过变量名做到
  • Python通过额外的self参数做到
  • JS通过额外的this做到:
    • person.sayHi()会把person自动传给sayHi,sayHi可以通过this引用person
  • 其他:
    • 注意peson.sayHi和person.sayHi()的区别
    • 注意person.sayHi()的断句(person.sayHi)()

另一种理解的思路:
image.png
this就相当于person.sayHi()中,点前面的person

image.png

上面的图片:
用person.sayHi()是对的
而person.sayHi(person)反而不对了

image.png

七.call指定this

forEach中用的this,例子如下所示:
image.png
array.forEach2.call(array,(item)=>console.log(item))//大师写法
等价于:
array.forEach(item=>console.log(item))//小白写法

image.png

this的两种使用方法:

隐式传递:

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

显示传递: 用call

  • fn.call(undefined,1,2)
  • fn.apply(undefined,[1,2])

绑定this

  • 使用.bind可以让this不被改变

function f1(p1,p2){
console.log(this,p1,p2)
}
let f2=f1.bind({name:’liulei’})
//那么f2就是f1绑定了this之后的新函数
f2() //等价于f1.call({name:’frank’})

  • .bind还可以绑定其他参数

let f3 = f1.bind({name:’liulei’},’hi’)
f3() //等价于f1.call({name:’liulei’},hi)

八.箭头函数

没有arguments和this

箭头函数

  • 里面的this就是外面的this
    • console.log(this) //window
    • let fn =() =>console.log(this)
    • 执行fn() //window
  • 就算你加call也么有用
    • fn.call({name:’liulei’}) //window

九.立即执行函数

只有JS有的变态玩意,现在用的少了

1.老版的js造一个局部变量的方法

js里面得到一个局部变量的方法,!加上括号
image.png
! function () {
var a = 2
console.log(a)
} ( )

2.新版的js造一个局部变量的方法

{
var a =1
console.log(a)
}

3.立即执行函数(最好使用感叹号)

原理:
image.png
推荐用感叹号 ! 来代替其他的符号!!!