什么是函数
函数是对象的一种,也是一段可以重复使用的代码块,开发人员为了完成某项功能,把相关代码块放到一起。
函数内部可以传参,也可以被当做参数传递
目前定义函数有五种方法
具名函数来定义
function f(x, y){
return x + y
}
f.name //'f'
匿名函数来定义
var f
f = function(x, y){
return x + y
}
f.name //'f'
具名函数定义了又赋值给了变量
var f1
f1 = function f(a, b){
return a + b
}
f1.name //'f'
要注意:虽然f1.name='f'
,但是f只在函数内部可用,实际上函数的名字还是f1
window.Function来构造
var f2 = new Function('x', 'y', 'return x + y')
f2.name //'anonymous'
箭头函数
var f3 = (x, y) => {return x - y}
var sum = (x, y) => x + y //函数体内只有一行代码,可以省略大括号和return
var n2 = n => n*n //只有一个参数,可以省略小括号
常用的定义方法是1、2、5这三种方法。
函数的一些必备知识
函数的name属性
由上面的五种定义方法,我们可以知道函数具有name
属性,而且不同的定义方法,name属性也很奇葩。
函数如何调用
为了理解后面的this,推荐使用call()
方法,而不是使用常见的f()
以第一种定义方法为例
f.call(undefined, 1, 3)
4
call()方法的第一个参数就是this,后面的参数才是函数的执行参数。
下面用代码检验一下
function f1(m, n){
console.log(this)
console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} //不是应该打印undefined,为啥是window呢?
4 //这才是函数的执行内容
执行f1.call(undefined, 1, 3)后,this不是应该打印出undefined吗,为啥打印了Window呢(注意实际上是个小写的window,不是浏览器打印的大写的Window),可以用代码验证打印的就是小写的window
function f1(m, n){
console.log(this === window)
console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
true //说明是小写得到window
4
function f1(m, n){
console.log(this === Window)
console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
false //并不是大写的Window
4
我真是服啦,那window和Window有啥区别呢。真是蛋疼啊,竟然考虑这个问题……
答案就是 var object = new Object,那var window = new Window。而且Window毫无探讨的意义,倒是这个window是个全局属性,多少有点用。
有时候自己真是有点钻牛角尖,钻进去后,还不会举一反三。如果立刻想到obj的例子就不用浪费时间了。
这就是藏着的this
这是因为浏览器捣的鬼,他把undefined变成了window。接下来使用严格模式,让undefined现身
function f1(m, n){
'use strict'
console.log(this)
console.log(m + n)
}
undefined
f1.call(undefined, 1, 3)
undefined //这个undefined就是call()方法的第一个参数undefined
4
- 而且call()的第一个参数是啥,this就是啥
function f1(m, n){
'use strict'
console.log(this)
console.log(m + n)
}
undefine
f1.call('我是啥this就是啥', 1, 3)
我是啥this就是啥 //打印的依然是call()的第一个参数
4
arguments
前面分析了call()的第一个参数,那后俩参数是啥呢。
对,你没猜错,那就是arguments。
当你写call(undefined, 1, 3)
的时候。undefined
可以被认为是this
,[1, 3]
就是arguments
函数的call stack
上面我们接触了call()方法,现在我们学习一下当有多个函数调用的时候,JavaScript解析器是如何调用栈的。
MDN的解释如下
调用栈是解析器(如浏览器中的的javascript解析器)的一种机制,可以在脚本调用多个函数时,跟踪每个函数在完成执行时应该返回控制的点。(如什么函数正在执行,什么函数被这个函数调用,下一个调用的函数是谁)
- 当脚本要调用一个函数时,解析器把该函数添加到栈中并且执行这个函数。
- 任何被这个函数调用的函数会进一步添加到调用栈中,并且运行到它们被上个程序调用的位置。
- 当函数运行结束后,解释器将它从堆栈中取出,并在主代码列表中继续执行代码。
- 如果栈占用的空间比分配给它的空间还大,那么则会导致“堆栈溢出”错误。
以下是通过三个方面去理解call stack这个概念的。
普通调用
代码如下,直观的动图可以看上述的链接
function a(){
console.log('a')
return 'a'
}
function b(){
console.log('b')
return 'b'
}
function c(){
console.log('c')
return 'c'
}
a.call()
b.call()
c.call()
如上的代码,先有三个函数声明,然后是三个调用。浏览器先执行a.call(),然后执行b.call(),c.call(),下面结合图具体详细分析。
[图片上传失败...(image-57cf0d-1515141332134)]
- 第一步:浏览器入口是a.call(),a函数入栈,执行a函数内部代码
- 第二步:console.log('a')执行完毕,就出栈,接着a函数结束,出栈死亡
- 第三步:b.call()入栈,执行b函数内部代码
- 第四步: console.log('b')执行完毕就出栈,接着b函数结束,出栈死亡
- 第五步:c.call()入栈,执行c函数内部代码
- 第六步:console.log('c')执行完毕就出栈,接着c函数结束,出栈死亡。
- 整个代码结束,浏览器恢复平静。
嵌套调用
function a(){
console.log('a1')
b.call()
console.log('a2')
return 'a'
}
function b(){
console.log('b1')
c.call()
console.log('b2')
return 'b'
}
function c(){
console.log('c')
return 'c'
}
a.call()
console.log('end')
- 第一步:浏览器的入口还是a.call(),a.call()入栈,执行a函数内部的代码
- 第二步: a函数的第一行语句console.log('a1'),入栈,打印出a1,这句话就出栈死亡。此时a函数继续执行下面的代码。
- 第三步: a函数的第二行语句b.call()入栈。执行b函数内部的代码。
- 第四步:进入b函数内部,b函数的第一行语句console.log('b1')入栈,打印出b1,就出栈死亡。
- 第五步:b函数的第二行c.call()入栈,又进入c函数内部
- 第六步:进入c函数的内部,第一行语句console.log('c')入栈,打印出c,就出栈死亡。
- 第七步:c函数执行完毕,出栈死亡。
- 第八步:回到b函数内部,执行第三行代码console.log('b2')入栈,打印出b2,出栈死亡。
- 第九步: b函数执行完毕,出栈死亡。
- 第十步: 回到a函数内部,执行第三行代码console.log('a2'),入栈,打印出a2,就出栈死亡。
- 第十一步:a函数执行完毕,出栈死亡。
- 第十二步:console.log('end')入栈,打印出end,出栈死亡。
- 整个代码运行完,浏览器归于平静。
递归调用
递归调用就是上面的嵌套调用的复杂变化,细心点,分析就能明白具体的代码顺序。
函数作用域
除了全局变量,其他变量只能在自己的函数内部被访问到,其他区域无法访问。通过几个面试题来学习一下。
- 第一道面试题
var a = 1
function f1(){
alert(a) // 是多少
var a = 2
}
f1.call()
问:alert出什么东西?
这种题切忌上去就做,容易打错成了 a是2 一定要先把变量提升。变成如下这样的
var a = 1
function f1(){
var a
alert(a)
a = 2
}
f1.call()
这样一提升就知道啦,答案:a是undefined。
- 第二道面试题
var a = 1
function f1(){
var a = 2
f2.call()
}
function f2(){
console.log(a) // 是多少
}
f1.call()
问:a是多少
这个题用就近原则好做。
用树形结构来分析,当上面的代码被浏览器渲染之后
- 全局变量里面有:var a = 1,f1、f2函数
- f1函数作用域里面又重新声明了一个var a = 2
- f2函数作用域里面是console.log(a)
所以打印的那个a就是全局的a,答案是a=1