定义函数
//具名函数function 函数名(形式参数1, 形式参数2){语句return 返回值}//匿名函数let a = function(x, y){ return x+y }//箭头函数let f1 = x => x*xlet 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 = fnfn2()
fn保存了匿名函数的地址,这个地址被复制给了fn2,fn2()调用了匿名函数,fn2和fn都是匿名函数的引用而已,真正的函数既不是fn也不是fn2
函数的调用时机
let a = 1function fn(){console.log(a)}fn()// 1let a = 1function fn(){console.log(a)}a = 2fn()// 2let a = 1function fn(){console.log(a)}fn()a = 2// 1let a = 1function fn(){setTimeout(()=>{console.log(a)},0)}fn()a = 2// 2
上面是简单的例子,下面开始来一个有意思的例子
let i = 0for(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 = 0for(i = 0; i<6; i++){let j = isetTimeout(()=>{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 = 1function f2(){let a = 2function f3(){console.log(a)}a = 22f3()}console.log(a)a = 100f2()}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的产生
//假设没有thislet 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之后,因为引入了块级作用域的原因,已经不在需要这种写法。
