「JS」 函数

292 阅读6分钟

函数是对象

定义一个函数


1、具名函数

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

2、匿名函数(函数表达式)


具名函数去掉函数名就是匿名函数

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

3、箭头函数

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 }); //直接返回对象


直接返回对象会报错

4、构造函数(基本没人用)


let f = new Function('x','y','return x+y')
所有函数都是 function 构造出来的,包括 Object、Array、Function 也是


函数的调用


栗子:

let fn = () => console.log("hi");
fn;
//不会有任何结果,fn没有执行
let fn = () => console.log('hi')
fn()
打印出hi,有圆括号才是调用
let fn = () => console.log("hi");
let fn2 = fn;
fn2();
// fn保存了匿名函数的地址,这个地址被复制给了fn2
//fn2()调用了匿名函数
//fn和fn2都是匿名函数的引用而已,真正的函数既不是fn也不是fn2

函数的要素


每个函数都有这些东西 ⬇

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

1.调用时机


时机不同,结果不同
栗子:

let a = 1;
function fn() {
  console.log(a);
}
//undefined 没有调用函数
let a = 1;
function fn() {
  console.log(a);
}
fn();
// 1
let a = 1;
functiong fn(){
	console.log(a)
}
a = 2
fn()
// 2  a的值在函数调用前已改
let a = 1;
function fn() {
  console.log(a);
}
fn();
a = 2;
// 1  a的值在函数调用之后才改
let a = 1function fn(){
	setTimeout(()=>{
  	console.log(a)
  },0)
}
fn()
a = 2
//2  setTimeout执行完当前的任务
let i = 0;
for (i = 0; i < 6; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
// 6个6 循环先执行完再打印出值
for (let i = 0; i < 6; i++) {
  setTimeout(() => {
    console.log(i);
  }, 0);
}
// 0、1、2、3、4、5
//js在for和let一起用的时候回加东西,每循环会多创建一个i

2.作用域


每个函数都会默认创建一个作用域

全局变量:在顶级作用域声明的变量时全局变量,window 的属性时全局变量,函数内外都是
局部变量:函数内部声明变量

函数可嵌套,作用域也可嵌套

规则:如果多个作用域有同名变量 a
查找 a 的声明时,就向上取最近的作用域(就近原则)
查找 a 的过程与函数执行无关

栗子:

function fn() {
  let a = 1;
}
console.log(a);
// undefined  访问不到作用域里的a
function fn() {
  let a = 1;
}
fn();
console.log(a);
// undefined  就算声明了也不存在,依然访问不到作用域里的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  22

3.闭包


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

如上一个栗子 a 和 f3 组成了闭包
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()

4.形式参数


非实际的参数
形参可多可少,形参只是给参数取名

function add(x, y) {
  return x + y;
}
//x 和 y 就是形参,并不是实际的参数
//当调用add(1,2)时,1 和 2 就是实际参数,会被赋值给 x 和 y
//形参可认为是变量声明
//可近似等价于以下代码
function add() {
  var x = arguments[0];
  var y = arguments[1];
  return x + y;
}



5.返回值


每个函数都有返回值(只有函数有返回值)
函数执行完了后才会返回
栗子

function hi() {
  console.log("hi");
}
hi();
//underfined  没写return
function hi() {
  return console.log("hi");
}
hi();
//underfined

6.调用栈


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

递归函数
栗子:
阶层

function f(n){
	return n !== 1 ? n* f(n-1) : 1
}
// 先递进,再回归
//例:f(4)
=> 4 * f(3)
=> 4 * (3 * f(2))
=> 4 * (3 * (2 * f(1)))
=> 4 * (3 * (2 * (1)))
=> 4 * (3 * (2))
=> 4 * (6)
=> 24


image.png
递归函数的调用栈很长
调用栈最长:
Chrome :12578
Firefox: 26773
Node: 12536
爆栈:如果调用栈中压入的帧过多,程序就会崩溃

7.函数提升


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

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

8./9.arguments/this(除箭头函数外)


所有函数都有 arguments 和 this, 除了箭头函数
箭头函数没有 arguments,** 箭头函数没有 this!!!箭头函数默认 this 是 window,拿 call 或者 apply 改了也没用**

function fn(x, y, z) {
  console.log(arguments);
  console.log(this);
}


arguments 是函数形式参数的伪数组
如果不指定 this, this 默认是 window
image.png

8.1 用 call 传 this

call()里的第一个参数是this指定的对象,且是显式指定this
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)
fn.apply(undefined,[1,2])



但如果传进去的 this 不是一个对象,而是一个数字,默认情况下,JS 会自动把数字装箱成对象

function fn(x, y, z) {
  console.log(arguments);
  console.log(this);
}
fn.call(1);


image.png
可以看到打印出的 this 是 new Number(1) 创造出来的一个 Number 对象
如果我们不希望 JS 把数字变成对象,可以在函数里写一句**'use strict'**
image.png

如果传进去的 this 是 Undefined,JS 会让 this 是 window
image.png

小结:this 的值:

  1. 如果什么都不传,默认是 window
  2. 如果传一个对象,this 就是这个对象
  3. 如果传一个数字,JS 会把数字封装成对象
  4. 如果传 Undefined, JS 会把 this 设置为 this => 其实和第一种情况等价, 即不传 this

关于 this 的究极真理:谁调用函数,谁就是 this


如果不传 this,就默认 this 是 window;
this的值是调用的时候确定的,比如call也是



tips:一个变量保存了对象的地址则为引用
this是为了解决未来对象的引用

8.2 call 的用法


fn.call(xxx, 1, 2, 3)
第一个参数是 this, 后面所有的参数是 arguments

Array.prototype.forEach2 = function (fn) {
  for (let i = 0; i < this.length; i++) {
    fn(this[i], i);
  }
};

let arr = [1, 2, 3];
arr.forEach2.call(arr, (item) => console.log(item));


image.png
打印出来 1,2,3

8.3 bind 的用法:

call和bind的区别
call是改变this的指向,并立即调用
bing是绑定this,得到一个函数表达式,赋值给某个变量 再调用

绑定 this 和参数
image.png

8.4 apply 的用法:


该方法的语法和作用与 call 方法类似,只有一个区别,就是  call()  方法接受的是一个参数列表,而  apply()  方法接受的是一个包含多个参数的数组。

求数组里的的最大值和最小值:

const numbers = [5, 6, 2, 3, 7];

const max = Math.max.apply(null, numbers);

console.log(max);
// expected output: 7

const min = Math.min.apply(null, numbers);

console.log(min);
// expected output: 2

8.5 立即执行

造一个局部变量,而造一个匿名函数立即执行,太麻烦了

新版的方法

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