定义函数
//具名函数
function 函数名(形式参数1, 形式参数2){
语句
return 返回值
}
//匿名函数
let a = function(x, y){ return x+y }
//箭头函数
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})
//构造函数
let f = new Function('x', 'y', 'return x+y')
函数的调用
let fn = () => console.log('hi')
let fn2 = fn
fn2()
fn保存了匿名函数的地址,这个地址被复制给了fn2,fn2()调用了匿名函数,fn2和fn都是匿名函数的引用而已,真正的函数既不是fn也不是fn2
函数的调用时机
let a = 1
function fn(){
console.log(a)
}
fn()
// 1
let a = 1
function fn(){
console.log(a)
}
a = 2
fn()
// 2
let a = 1
function fn(){
console.log(a)
}
fn()
a = 2
// 1
let a = 1
function fn(){
setTimeout(()=>{
console.log(a)
},0)
}
fn()
a = 2
// 2
上面是简单的例子,下面开始来一个有意思的例子
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 6,6,6,6,6,6
原因是setTimeout的意思是在完成当前事情之后再执行该函数,我的理解是由于JS本身是单线程的,循环和setTimeout不能同步执行,在这种情况下会由于先到先执行,循环先执行,在循环执行完成后立即执行循环内部的语句,所以输出6个6。
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
// 0,1,2,3,4,5
每次循环会多创建一个i,把这个i的值复制一份留在setTimeout函数中,所以输出0,1,2,3,4,5
那么还有什么方法可以同样的输出0,1,2,3,4,5呢?在mdn中关于let的讲解中我们可以得知,可以通过如下方式实现
let i = 0
for(i = 0; i<6; i++){
let j = i
setTimeout(()=>{
console.log(j)
},0)
}
// 0,1,2,3,4,5
这也再一次印证了我们对于上面一个例子的理解,在for循环里声明let i会创建在括号里的一个隐藏作用域,在每次执行循环体时,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次,相当于在for循环第一行中有这样一行隐形代码——let i = 隐藏作用域中的i。
作用域
就近原则
如果多个作用域有同名变量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()
如果一个函数用到了外部的变量,那么这个函数加这个变量,a和f3就组成了闭包
形参
function add(x, y){
return x+y
}
add(1,2)
// 上面代码近似等价于下面代码
function add(){
var x = arguments[0]
var y = arguments[1]
return x+y
}
返回值
//每个函数都有返回值
function hi(){ console.log('hi') }
hi()
//返回值为undefined
//只有函数才有返回值
调用栈
JS引擎在调用一个函数前需要把函数所在的环境push到一个数组里,这个数组就叫做调用栈,等函数执行完了,就会把环境弹出来,然后return到之前的环境,继续执行后续代码。
爆栈
函数提升
//会发生函数提升
function fn(){}
//不会发生函数提升
let fn = function(){}
arguments和this
function fn(){
console.log(arguments)
console.log(this)
}
arguments在函数调用的时候传入,fn(1,2,3)那么arguments就是[1,2,3]的伪数组。
this可以利用fn.call(xx,1,2,3)传入,xx会自动转化为对象。
this的产生
//假设没有this
let person = {
name: 'frank',
sayHi(){
console.log(`你好,我叫` + person.name)
}
}
我们可以用直接保存了对象地址的变量获取’name’,这种方法叫引用。
let sayHi = function(){
console.log(`你好,我叫` + person.name)
}
let person = {
name: 'frank',
'sayHi': sayHi
}
person如果改名,sayHi就挂了
class Person{
constructor(name){
this.name = name
// 这里的 this 是 new 强制指定的
}
sayHi(){
console.log(???)
}
}
这里只有类,还没有创建对象,所以不可能获取对象的引用,那么如何拿到对象的name,下面这种方法应运而生了。
let person = {
name: 'frank',
sayHi(p){
console.log(`你好,我叫` + p.name)
}
}
person.sayHi(person)
class Person{
constructor(name){ this.name = name }
sayHi(p){
console.log(`你好,我叫` + p.name)
}
}
上面这种方式就是python的方法,但是JS里引入了this。
let person = {
name: 'frank',
sayHi(this){
console.log(`你好,我叫` + this.name)
}
}
this被隐藏了,person.sayHi()会隐式的把person作为this传给sayHi。
调用方法
person.sayHi()
person.sayHi.call(person)//手动指定this
重写forEach
Array.prototype.forEach = function(fn){
for(let i=0;i<this.length;i++){
fn(this[i], i, 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
function f1(p1, p2){
console.log(this, p1, p2)
}
let f2 = f1.bind({name:'frank'})
// 那么 f2 就是 f1 绑定了 this 之后的新函数
f2() // 等价于 f1.call({name:'frank'})
let f3 = f1.bind({name:'frank'}, 'hi')
f3() // 等价于 f1.call({name:'frank'}, hi)
箭头函数
箭头函数里面的this就是外面的this,就算用call指定也没有用
立即执行函数
在匿名函数前加上一个运算符,一般是!,在最后加上一个(),这个函数就是一个立即执行函数,它的作用是得到局部变量,但是在es6之后,因为引入了块级作用域的原因,已经不在需要这种写法。