定义函数

  1. //具名函数
  2. function 函数名(形式参数1, 形式参数2){
  3. 语句
  4. return 返回值
  5. }
  6. //匿名函数
  7. let a = function(x, y){ return x+y }
  8. //箭头函数
  9. let f1 = x => x*x
  10. let f2 = (x,y) => x+y // 圆括号不能省
  11. let f3 = (x,y) => {return x+y} // 花括号不能省
  12. let f4 = (x,y) => ({name:x, age: y})
  13. //构造函数
  14. let f = new Function('x', 'y', 'return x+y')

函数的调用

  1. let fn = () => console.log('hi')
  2. let fn2 = fn
  3. fn2()

fn保存了匿名函数的地址,这个地址被复制给了fn2,fn2()调用了匿名函数,fn2和fn都是匿名函数的引用而已,真正的函数既不是fn也不是fn2

函数的调用时机

  1. let a = 1
  2. function fn(){
  3. console.log(a)
  4. }
  5. fn()
  6. // 1
  7. let a = 1
  8. function fn(){
  9. console.log(a)
  10. }
  11. a = 2
  12. fn()
  13. // 2
  14. let a = 1
  15. function fn(){
  16. console.log(a)
  17. }
  18. fn()
  19. a = 2
  20. // 1
  21. let a = 1
  22. function fn(){
  23. setTimeout(()=>{
  24. console.log(a)
  25. },0)
  26. }
  27. fn()
  28. a = 2
  29. // 2

上面是简单的例子,下面开始来一个有意思的例子

  1. let i = 0
  2. for(i = 0; i<6; i++){
  3. setTimeout(()=>{
  4. console.log(i)
  5. },0)
  6. }
  7. // 6,6,6,6,6,6

原因是setTimeout的意思是在完成当前事情之后再执行该函数,我的理解是由于JS本身是单线程的,循环和setTimeout不能同步执行,在这种情况下会由于先到先执行,循环先执行,在循环执行完成后立即执行循环内部的语句,所以输出6个6。

  1. for(let i = 0; i<6; i++){
  2. setTimeout(()=>{
  3. console.log(i)
  4. },0)
  5. }
  6. // 0,1,2,3,4,5

每次循环会多创建一个i,把这个i的值复制一份留在setTimeout函数中,所以输出0,1,2,3,4,5
那么还有什么方法可以同样的输出0,1,2,3,4,5呢?在mdn中关于let的讲解中我们可以得知,可以通过如下方式实现

  1. let i = 0
  2. for(i = 0; i<6; i++){
  3. let j = i
  4. setTimeout(()=>{
  5. console.log(j)
  6. },0)
  7. }
  8. // 0,1,2,3,4,5

这也再一次印证了我们对于上面一个例子的理解,在for循环里声明let i会创建在括号里的一个隐藏作用域,在每次执行循环体时,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次,相当于在for循环第一行中有这样一行隐形代码——let i = 隐藏作用域中的i。

作用域

就近原则

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

闭包

  1. function f1(){
  2. let a = 1
  3. function f2(){
  4. let a = 2
  5. function f3(){
  6. console.log(a)
  7. }
  8. a = 22
  9. f3()
  10. }
  11. console.log(a)
  12. a = 100
  13. f2()
  14. }
  15. f1()

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

形参

  1. function add(x, y){
  2. return x+y
  3. }
  4. add(1,2)
  5. // 上面代码近似等价于下面代码
  6. function add(){
  7. var x = arguments[0]
  8. var y = arguments[1]
  9. return x+y
  10. }

返回值

  1. //每个函数都有返回值
  2. function hi(){ console.log('hi') }
  3. hi()
  4. //返回值为undefined
  5. //只有函数才有返回值

调用栈

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

爆栈

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

函数提升

  1. //会发生函数提升
  2. function fn(){}
  3. //不会发生函数提升
  4. let fn = function(){}

不管你把具名函数声明在哪里,他都会跑到第一行。

arguments和this

  1. function fn(){
  2. console.log(arguments)
  3. console.log(this)
  4. }

arguments在函数调用的时候传入,fn(1,2,3)那么arguments就是[1,2,3]的伪数组。
this可以利用fn.call(xx,1,2,3)传入,xx会自动转化为对象。

this的产生

  1. //假设没有this
  2. let person = {
  3. name: 'frank',
  4. sayHi(){
  5. console.log(`你好,我叫` + person.name)
  6. }
  7. }

我们可以用直接保存了对象地址的变量获取’name’,这种方法叫引用。

  1. let sayHi = function(){
  2. console.log(`你好,我叫` + person.name)
  3. }
  4. let person = {
  5. name: 'frank',
  6. 'sayHi': sayHi
  7. }

person如果改名,sayHi就挂了

  1. class Person{
  2. constructor(name){
  3. this.name = name
  4. // 这里的 this 是 new 强制指定的
  5. }
  6. sayHi(){
  7. console.log(???)
  8. }
  9. }

这里只有类,还没有创建对象,所以不可能获取对象的引用,那么如何拿到对象的name,下面这种方法应运而生了。

  1. let person = {
  2. name: 'frank',
  3. sayHi(p){
  4. console.log(`你好,我叫` + p.name)
  5. }
  6. }
  7. person.sayHi(person)
  8. class Person{
  9. constructor(name){ this.name = name }
  10. sayHi(p){
  11. console.log(`你好,我叫` + p.name)
  12. }
  13. }

上面这种方式就是python的方法,但是JS里引入了this。

  1. let person = {
  2. name: 'frank',
  3. sayHi(this){
  4. console.log(`你好,我叫` + this.name)
  5. }
  6. }

this被隐藏了,person.sayHi()会隐式的把person作为this传给sayHi。

调用方法

  1. person.sayHi()
  2. person.sayHi.call(person)//手动指定this

重写forEach

  1. Array.prototype.forEach = function(fn){
  2. for(let i=0;i<this.length;i++){
  3. fn(this[i], i, this)
  4. }
  5. }

两种传递

  1. //隐式传递
  2. fn(1,2) // 等价于 fn.call(undefined, 1, 2)
  3. obj.child.fn(1) // 等价于 obj.child.fn.call(obj.child, 1)
  4. //显示传递
  5. fn.call(undefined, 1,2)
  6. fn.apply(undefined, [1,2])

绑定this

  1. function f1(p1, p2){
  2. console.log(this, p1, p2)
  3. }
  4. let f2 = f1.bind({name:'frank'})
  5. // 那么 f2 就是 f1 绑定了 this 之后的新函数
  6. f2() // 等价于 f1.call({name:'frank'})
  7. let f3 = f1.bind({name:'frank'}, 'hi')
  8. f3() // 等价于 f1.call({name:'frank'}, hi)

箭头函数

箭头函数里面的this就是外面的this,就算用call指定也没有用

立即执行函数

在匿名函数前加上一个运算符,一般是!,在最后加上一个(),这个函数就是一个立即执行函数,它的作用是得到局部变量,但是在es6之后,因为引入了块级作用域的原因,已经不在需要这种写法。