JS函数(箭头函数、调用时机、作用域、闭包、调用栈、函数提升、this(函数调用call、apply、bind)、立即执行函数)

316 阅读5分钟

函数是对象,所有的函数都是Function构造出来的,包括(Object,Array,Function)

如何定义一个函数

具名函数

function 函数名(形式参数1,形式参数2){
  语句
  return 返回值
}
function fn(x,y){
  console.log(a)
  return(x + y)
}

匿名函数

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

**去掉上面的函数名就是匿名函数
let a = function(x,y){
                  ↓           
                 参数
  return x+y
          ↓
          返回这个参数
  }
  
 *声明一个变量a并且把它赋值为,左边为赋值,右边是函数表达式
 *也叫函数表达式
let a = function fn(x,y){
  return x+y
  }
fn(2,3)

问: fn能执行吗
* 报错 fn的作用域只在右边,非常有限。 如果没有let a 则可以

箭头函数

let f1 = x => x*x

*在f1里输入一个x参数,输出x*x

*如果有两个输入参数,要用()括起来

let f2 = (x,y) => x*y
*如果有两个语句
let f1 = (x,y) => {
  console.log('hi')
  return x*y
}

*加了{}必须写return,否则js不知道执行啥
  • 变态,记得⚠️加括号就行
let f1 = x => { name:x }
*执行f1为undefined ,js认为后面不是一个整体,只是一个标签,要加() ,反正这个语法是错的,不要纠结太多,
let f1 = x => ({ name:x })

fn 和 fn()的区别

let fn = () => console.log('hi')
fn

*不会有任何结果,因为fn没有执行,fn()才能执行
let fn = () => console.log('hi')
let fn2 = fn
fn2()

*fn 保存了匿名函数的地址

*这个地址被复制给了 fn2

*fn2() 调用了匿名函数

*fnfn2 都是匿名函数的引用而已

*真正的函数既不是 fn 也不是 fn2

构造函数

let f = new Function('x', 'y', 'return x+y')

基本没人用,但是能让你知道函数是谁构造的

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

包括 ObjectArrayFunction 也是

函数的要素

  • 每个函数都有这些东西

    1. 调用时机
    2. 作用域
    3. 闭包
    4. 形式参数
    5. 返回值
    6. 调用栈
    7. 函数提升
    8. arguments(除了箭头函数)
    9. this(除了箭头函数)

调用时机

2.png

1

let a = 1
function fn(){
  console.log(a)
}
*上面代码不会打印出什么,因为没有调用fn这个函数

2

let a = 1
function fn(){
  console.log(a)
}
a = 2
fn()

* 上面代码会打印出2,因为a已经变成了2

3

let a = 1
function fn() {
  setTimeout(() => {
    console.log(a)
},0)
}
fn()
a = 2

问:会打印出什么
答:2

4

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}

上面代码问打印出什么,答案是6个6,首先i后面只有一个值,setTimeout的意思是你去数苹果有多少个,数着数着,你妈妈问你一共有几个苹果,你告诉妈妈最终的数字就行,即让你运行完i的值,过一会马上打印出i的最终值,当i为6时,刚好满足i<6,所以循环了6次

那怎么让它打印出0,1,2,3,4,5呢?

  • 方法一
for(let i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
},0)
}

只需要把let放到里面即可

  • 方法二
for(let i = 0; i < 6; i++){
 setTimeout(i => console.log(i), 0,  i )
}

作用域

全局变量和局部变量

  1. 在顶级作用域声明的变量是全局变量
  2. window 的属性是全局变量,在哪都可以用,其他都是局部变量
  • let b 为局部变量 ,只能在{}中使用 1.png

多个作用域有同名变量a

  1. 查找a时,向上取最近的作用域,就近原则

4.png

什么是闭包,我的押题里有

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

形式参数的本质就是变量声明

3.png

什么是调用栈

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

5.png

  • 递归函数
  • 爆栈 如果调用栈中压入的帧过多,程序就会崩溃

6.png

函数提升

7.png

add(1,2)
function add(x,y){
  return x+y
}

执行为:3 
因为函数(function add)永远会跑到第一行

8.png

arguments(伪数组,没有数组共有属性的数组,如push、shift等没有)和this

  • argunments
Array.from()
可以把不是数组的伪数组变成数组

this

  1.  在 new fn() 调用中,fn 里的 this 指向新生成的对象,这是 new 决定的
  2.  在fn()调用中, this 默认指向 window,这是浏览器决定的
  3.  在 obj.fn() 调用中,this默认指向 obj,这是 JS 的隐式传 this
  4.  在 fn.call(xxx) 调用中,this 就是 xxx,这是开发者通过 call 显式指定的 this
  5.  在 arrow() 调用中,arrow 里面的 this 就是 arrow 外面的 this,因为箭头函数里面没有自己的 this
  6.  在 arrow.call(xxx) 调用中,arrow 里面的 this 还是 arrow 外面的 this,因为箭头函数里面没有自己的 this

函数调用

建议所有函数调用都用call、apply写法

call

let person = {
  name:'frank',
  sayHi(){
    console.log(this.name)
  }
}
person.sayHi.call({name:'jack'})
----------------
  call后面传什么this就是什么,即上面的this是jack
  
person.sayHi.call(person)
-----------------
  即上面的this是person
function add(x,y){
  return x+y
}
add.call(undefined,1,2)
----------
如果不用this通过call调用函数,需要用undefined(或者其它)来占位

apply

function add(x,y){
  return x+y
}
add.apply(undefined,[1,2])
------------
apply的用法就是在后面加上[],因为后面要用数组的方式

bind

  • 使用.bind可以让this不被改变
function f1(p1,p2){
  console.log(this,p1,p2)
}
let f2 = f1.bind({name:'frank'})
------------
f2就是f1绑定this之后的新函数,即frank

箭头函数

  • (箭头函数里面没有this)换句话说 (箭头函数里面的this就是外面的this)

10.png

let a = () => console.log(this)

立即执行函数

11.png

! function (){
  var a = 2
console.log(a)
} ()

new

  • p 拥有People函数的属性
function People(name,age) {
  this.name = name
  this.age = age
}
let p = new People('小明',7)
console.log(p)