具名函数
function 函数名 (形参1, 形参2){
语句
return 返回值
}
匿名函数
等号右边叫函数表达式let a = function (x, y) {
语句;
return 返回值;
};
特殊情况
let a = function fn(x, y) {
return x + y;
};
fn(1, 2);
//会报错,=右边的fn只在=右边有效,全局调用没有fn,只能用a
箭头函数
let f1 = (x) => x * y;
let f2 = (x, y) => {
console.log("hi");
return x * y;
};
let f3 = (x) => ({ name: "x" });
构造函数
- 基本没人用,但是能让你知道函数是谁构造的
- 所有函数都是 Function 构造出来的
- 包括 Object、Array、Function
let f1 = new Function("x", "y", console.log('hi'); "return x + y");
函数与函数调用
- fn 与 fn()
-
二、函数的要素
每个函数都有这些东西
let 在 for 外面
let i;
for (i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//打印6个6
let 在 for 里面
for (let i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i);
}, 0);
}
//打印 0,1,2,3,4,5
因为JS在for 和let一起用的时候会加东西
- 每次循环会多创建一个i,记录本次i的值2. 作用域
function fn() {
let a = 1;
}
console.log(a);
//报错 a 不存在,a为局部变量,只作用在fn里面
全局变量 局部变量
- 全局变量
- 在顶级作用域声明的变量
- window 的属性是全局变量
- 其他的都是局部变量
- 全局变量
函数可嵌套
function f1() {
let a = 1;
function f2() {
let a = 2;
console.log(a);
}
console.log(a);
a = 3;
f2();
}
f1();
- 作用域规则
- 如果多个作用域有同名变量 a
- 那么查找 a 的声明时,就向上取最近的作用域
- 简称[就近原则]
- 查找 a 的过程与函数执行无关
- 但 a 的值与函数执行有关
-
3. 闭包
如果一个函数用到它作用域外的变量,那么这个函数加这个变量,就叫做闭包
下面 f2 里的 a 和 f3 组成了闭包
function fi() {
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() {
var x = arguments[0];
var y = arguments[1];
return x + y;
}
-
5. 返回值
每个函数都有返回值
- 注意:console.log(‘hi’) 的值是 undefined
-
6. 调用栈
什么是调用栈
- JS 引擎在调用一个函数前
- 需要把函数所在的环境 push 到一个数组里
- 这个数组叫做调用栈
- 等函数执行完了,就会把环境弹出来(pop)
- 然后 return 到之前的环境,继续执行后续代码
- 举例
图片来自杭州饥人谷教程 递归函数(如阶乘)
function fn(n) {
return n === 1 ? 1 : n * fn(n - 1);
}
递归的调用栈
- 递进 - 压栈 - 一只递进一直压栈,递一次压一次栈,一直累积
- 回归 - 弹栈 - 开始回归才开始弹栈
递归压栈的贞超过最大值就会爆栈,报错
调用栈最大值测试代码:function computeMaxCallStackSize() {
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
//报错说明 stack overflow 了
return 1;
}
}
Chrome 12578 、Firefox 26773 、Node 12536 (取决于浏览器 JS 引擎)
7. 函数提升
函数提升
- function fn() {}
- 不管你把具名函数声明在哪里,他都会跑到第一行
不是函数提升
每个函数都有,除了箭头函数
argument 包含所有参数的伪数组,传参的同时会复制到 argument 里
9. this
每个函数都有,除了箭头函数
- 如果不给任何条件,this 默认指向 window
- 目前只能通过 call 来指定 this
如果传的 this 不是对象,JS 会自动帮你封装成对象
禁用此功能:在声明函数时加上字符串’use strict’function fn() {
"use strict";
console.log(this);
}
但是,没有人会在定义函数的时候加上这句话。。
- call fn.call(xxx,2,3,4)
xxx(第一个),是 this
1,2,3(后面几个) 是 argument 假设没有 this
- 我们可以用直接保存了对象地址的变量获取’name’
- 我们把这种方法简称为引用
代码示例
let person = {
name: "frank",
sayHi() {
console.log("你好,我叫" + person.name);
},
};
但是:
- 如果 person 改名了,sayHi 函数就挂了
- sayHi 函数甚至有可能在另一个文件里面
- 所以我们不希望 sayHi 函数出现 person 引用
用 this 获取那个对象(person)
let person = {
name: "frank",
sayHi() {
//sayHi(隐藏了this)
console.log(`你好,我叫` + this.name);
},
};
- this 两种调用法
- 小白调用法
person.sayHi() 默认传 person - 大师调用法person.sayHi.call(person) 可以传别的 this以后所有函数调用都用这种写法
- 如果函数里没有 this,call(里面要多传第一个参数 undefined,用来占位)
- 有 this,第一个参数传函数作用的东西(也就是 this 对应的),后面再传其他参数
- 小白调用法
- .call fn.call(xxx,2,3,4)
xxx(第一个),是 this
1,2,3(后面几个) 是 argument - .apply fn.apply(xxx,[2,3,4])
除了 this 后面参数加中括号[],其他的和 call 一样 .bind
- 让 this 不被改变
‘frank’})function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f2.bind({name:
- 让 this 不被改变
f2 就是 f1 绑定了 this 之后的新函数
f2() 等价于 f1.call({name: ‘frank’})- .bind 还可以绑定其他参数
let f3 = f1.bind({ name: "frank" }, "hi");
- .bind 还可以绑定其他参数
f3() 等价于 f1.call({name: ‘frank’}, ‘hi’)
10. 箭头函数
没有 argument 和 this
箭头函数里的 this 就是一个普通变量,相当于一个 a、b 之类的普通变量
console.log(this); //打印window
let fn = () => console.log(this);
fn(); //还是打印window
就算你加上 call 都没有用
fn.call({ name: "frank" }); //仍然打印window
11.立即执行函数
- 只有 JS 才有的变态玩意,现在很少用,一般用来声明局部变量
!(function () {
var a = 2;
console.log(a);
})();
函数前面加+-!~都行,不加报错,最好用!,其他可能出 bug
可以不加,也尽量不要加最外面的括号(),如果加(),上一句必须用;
vscode 格式化了自动加了()
- 新版 JS,有新的方法声明局部变量
{
let a = 2;
console.log(a);
}