函数是对象
定义一个函数
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
函数的要素
每个函数都有这些东西 ⬇
- 调用时机
- 作用域
- 闭包
- 形式参数
- 返回值
- 调用栈
- 函数提升
- arguments (除了箭头函数)
- 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 = 1;
function 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 到之前的环境,继续执行后续代码
栗子:
递归函数
栗子:
阶层
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
递归函数的调用栈很长
调用栈最长:
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
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);
可以看到打印出的 this 是 new Number(1) 创造出来的一个 Number 对象
如果我们不希望 JS 把数字变成对象,可以在函数里写一句**'use strict'**
如果传进去的 this 是 Undefined,JS 会让 this 是 window
小结:this 的值:
- 如果什么都不传,默认是 window
- 如果传一个对象,this 就是这个对象
- 如果传一个数字,JS 会把数字封装成对象
- 如果传 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));
打印出来 1,2,3
8.3 bind 的用法:
call和bind的区别
call是改变this的指向,并立即调用
bing是绑定this,得到一个函数表达式,赋值给某个变量 再调用
绑定 this 和参数
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)}