1 定义函数
1.1 具名函数
function 函数名(形参1,形参2){
语句
return 返回值
}
1.2 匿名函数
具名函数不写函数名就是匿名函数,也叫函数表达式,等号右边叫函数表达式:
let a = function(形参1,形参2){
语句
return 返回值
}
1.3 箭头函数
let f1 = x => x*x //一个参数,一个语句,可省略圆括号、花括号、return;
let f2 = (x,y) => x+y //两个参数,要加圆括号;
let f3 = (x,y) => {
console.log('xxx')
return x+y
} //多个语句要加花括号,而且return不能省略;
let f4 = (x,y) => ({name:x,age:y}) //返回对象,需要在花括号外面加圆括号;
1.4 用构造函数
let f = new Function('x','y','return x+y')
基本没人用,但是通过上面代码可以知道,所有函数都是Function构造出来的,包括Object、Array、Function也是。
2 fn和fn()
let fn = () => console.log('hi')
let fn2 = fn
fn2()
3 函数调用时机
- 例1:
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2 //打印1
- 例2:
let a = 1
function fn(){
console.log(a)
}
a = 2 //打印2
fn()
- 例3:
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a = 2 //打印2
- 例4:
let i = 1
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
} //打印6个6
- 例5:
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
} //打印0,1,2,3,4,5
- 例6:
let i
for(i = 0; i<6; i++){
const x = i
setTimeout(()=>{
console.log(x)
})
}
- 例7:
let i = 0
for(i = 0; i<6; i++){
!function(i){
setTimeout(()=>{
console.log(i)
},0)
}(i)
}
- 例8:
let i = 0
for(i = 0; i<6; i++){
setTimeout((i)=>{
console.log(i)
},0,i)
}
4 作用域
每个函数都会默认创建一个作用域;
4.1 全局变量、局部变量
-
在顶级作用域声明的变量是全局变量;
-
window的属性是全局变量;
-
其他的都是局部变量;
4.2 作用域嵌套
function f1(){
let a = 1
function f2(){
let a = 2
console.log(a)
}
console.log(a)
a = 3
f2()
}
f1() //打印 1 2
4.3 作用域规则
- 如果多个作用域有同名变量a,那么:
-
查找a的声明时,就向上取最近的作用域,简称:就近原则;
-
查找a的过程与函数执行无关,与函数在哪里声明有关;
-
但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
let a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
let a=3
fn2() //fn3调用fn2时,因为fn2中没有定义a,所以往声明fn2函数的外面找,即在fn1里面找变量 a ,此时 a=2。而不是在调用 fn2 的地方,即 fn3 里面找。
}
let a=2
return fn3
}
let fn = fn1()
fn() // 2
- 函数fn的作用域
函数声明式在等于号右边的话,则函数fn的作用域也只在等号右边,出了这个范围,函数fn就不存在,要想使用函数,只能用a(),没有等于号,函数就是全局作用域,在哪都能用;
- 和函数执行没有关系的作用域叫静态作用域(词法作用域);反之叫动态作用域;在确定a是哪个变量的时候不看执行,只有在确定a的值时才要看执行顺序;
5 闭包
如果一个函数用到了外部的变量,那么这个函数加这个变量就叫做闭包。如上面代码的部分:
6 形式参数
- 形参只是给参数取名字,形参可多可少;
7 返回值
-
只有函数有返回值,每个函数有且只有一个返回值:
-
函数执行完了后才会返回,函数没有return,则返回undefined;
8 调用栈
8.1 定义
8.2 递归函数的调用栈
- 调用栈最长有多少:
function computeMaxCallStackSize(){
try {
return 1 + computeMaxCallStackSize();
} catch (e) {
// 报错说明 stack overflow 了
return 1;
}
}
8.3 爆栈
- 如果调用栈中压入的帧过多,程序就会崩溃;
9 函数提升
10 arguments和this
-
每个函数都有,除了箭头函数;
-
arguments就是包含所有参数的伪数组;
-
每次调用函数时,都会对应产生一个 arguments ;
-
尽量不要改 arguments 内的元素;
-
在 new fn() 调用中,fn 里的 this 指向新生成的对象,这是 new 决定的;
-
在 fn() 调用中,this 默认指向 window,这是浏览器决定的;
10.1 如何传arguments和this
function fn(){
console.log(arguments)
console.log(this)
}
10.2 this解决什么问题
在写一个函数时,需要得到一个对象,但我们不知道那个对象叫什么名字,因为那个对象可能还没有出生,怎么在不知道一个对象的名字的情况下,拿到一个对象的应用(保存了对象地址的变量);
10.3 this如何解决
- person.sayHi()会隐式地把 person 作为 this 传给 sayHi ,sayHi 可以通过 this 引用 person;
10.4 JS的两种调用法
-
隐式传递: person.sayHi():会自动把 person 传到函数里,作为 this ; obj.child.fn(1):等价于 obj.child.fn.call(obj.child,1)
-
显式传递: fn.call(undefined,1,2);
fn.apply(undefined,[1,2])
- call与apply的区别:apply的参数以数组的形式传递,其余的都相同;
function add(x,y){
return x+y
}
add.call(undefined, 1,2)
// call的第一个参数要作为this,但是函数中没有用到this,所以用undefined占位,也可以用null,后面的参数则是arguments.
Array.prototype.forEach2 = function(fn){
for(let i=0;i<this.length;i++){
fn(this[i],i,this)
}
}
let array = [1,2,4]
array.prototype.forEach2.call(array,(item)=>console.log(item)) //this就是array;
Array.prototype.forEach2.call({0:'a';1:'b',length:1},(item)=>console.log(item)) //this不一定是数组;
10.5 用.bind绑定this
- .bind可以让 this 不被改变:
function f1(p1,p2){
console.log(this,p1,p2)
}
let f2 = f1.bind({name:'frank'}) //f2就是f1绑定了this之后的新函数;
f2() //等价于f1.call({name:'frank'}),打印出{name: 'frank'} undefined undefined
- .bind还可以绑定其他参数:
let f3 = f1.bind({name:'frank'},'hi')
f3() //等价于 f1.call({name:'frank'},hi),打印出{name: 'frank'} hi undefined
- fn.bind(x,y,z)不会执行fn,而是会返回一个新的函数。新的函数执行时,会调用fn,调用形式为 fn.call(x,y,z),其中 x 是 this,y 和 z 是其它参数。
11 箭头函数
-
没有arguments 和 this,箭头函数中,this就是一个普通的字符串,和变量a 没有区别;
-
里面的 this 就是外面的this
console.log(this) //window
let fn = ()=> console.log(this)
fn() //window
-
用 call 也没用
-
箭头函数返回对象
let f = x=>({name:x})
f('xx') // 得到:{name: 'xx'}
let f2 = x=>{name:x}
f2('frank') // 得到:undefined
要想返回对象,花括号外边要加圆括号,不加的话高亮部分会被看作是代码块;
fn.call({name:'frank'}) //打印出来的还是window;
12 立即执行函数
- 为什么:ES5时期,为了得到局部变量,原理:
- return一个undefined,undefined前面有+ 号,就变成NaN,
- 匿名函数加():JS 代码中唯一需要加分号的地方,其他地方不需要,()会向上看,log返回值为undefined,undefined(function())就会报错;而 !不会向上看,只会向后看,所以,如果有人用()做立即执行函数,最好在函数前面加分号,最好只用 ! 。
- ES6之后,要想得到局部变量,直接在 {} 里面用 let 声明即可;