标准库 Function
函数的本质
函数是一段可以反复调用的代码块。函数还能接受输入的参数,不同的参数会返回不同的值。
函数本质上就是一个可以执行代码的对象
f 表示这个对象,f.call() 表示执行这个对象的函数体(f.functionBody)
函数的 5 种声明方式
- 具名函数
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 f2(x,y){
return x+y
}
f1.name // 'f2'
f2.name // 报错 f2 is not defined
console.log(f2) // 报错 f2 is not defined
问:为什么console.log(f2)
会报错?
答:见下图(不一致性是 JS 的一个垃圾之处)
- window.Function(函数对象)
var f = new Function('x','y','return x+y')
f.name // "anonymous"
- 箭头函数
var f = (x,y) => {
return x+y
}
var sum = (x,y) => x+y // 右边只有一个表达式,可以不写括号和 return
var n2 = n => n*n // 左边参数只有一个,可以不加括号
注意:
console.log()
也是一个函数,它的返回值是 undefined,它返回什么跟它打印什么一点关系都没有
例如,var a = console.log(1)
,问 a 的值是多少?
虽然console.log(1)
打印出1,但它返回的是undefined,因此 a 的值为 undefined
如何调用函数
f.call(asThis, input1,input2)
其中 asThis 会被当做 this,[input1,input2] 会被当做 arguments
this 和 arguments
function f(){
'use strict'
console.log(this)
console.log(arguments)
return undefined
}
f.call(1,2,3) // this 为 1,arguments 为 [2,3]
- call 的第一个参数可以用 this 得到
普通模式下,如果 this 为 undefined 或空,浏览器会自动把 this 变成 window 对象
普通模式严格模式(use strict)下,才会打印出 this 原本的值,如果 this 为 undefined,此时 this 的值才真正的是 undefined
use strict- call 后面的参数可以用 arguments 得到
call stack(调用堆栈)
原则:stack 总是先进后出的
查看几种常见的函数调用过程:
stack overflow(堆栈溢出)
如果堆栈占用的空间超过分配给它的空间,则会导致“堆栈溢出”错误。
作用域(scope)
- 按照语法树一层一层往上找声明,遵循就近原则
问:如果在声明函数的时候没有加 var,就一定是在声明全局变量吗?
答:很多人都会这么认为,但这种说法其实是错误的。
例如 a = 3 ,会优先认为它是一个赋值,此时并没有声明;
然后它会寻找自己当前所在的作用域内是否存在 a 的声明,若不存在声明,则会沿着作用域树一层一层地向上寻找声明;
当在全局范围找到 a 的声明时,就将 a = 3 的值赋给全局变量 a;
那么,a = 3 在什么情况下是在声明一个全局变量呢?
在整个作用域树中都找不到对 a 的声明(包括全局范围),此时的 a = 3 就是在声明一个全局变量并给它赋值
- 我们只能确定变量是哪个变量,但是不能确定变量的值
例如
var a = 1
function f(){
console.log(a) // 1
}
f.call()
var a = 1
function f(){
console.log(a) // 2
}
a = 2
f.call()
上面两段代码中console.log(a)
中的 a 是同一个 a (都为全局范围中的 a),但最终打印出的结果却不同。因为第二段代码中 a = 2 覆盖了之前的 a = 1,console.log(a)
会最后打印出 2。
由此可以看出,即便是确定的同一个变量,最后的值可能也是会发生变化的
以下是几个面试题~~
拿到代码直接做——必然会错。请先提升声明
题目一:
var a = 1
function f1(){
alert(a) // 是多少
var a = 2
}
f1.call()
提升声明后
var a
a = 1
function f1(){
var a
alert(a) // 弹框弹出并提示 undefined
a = 2
}
f1.call()
题目二:
var a = 1
function f1(){
var a = 2
f2.call()
}
function f2(){
console.log(a) // 是多少
}
f1.call()
变量提升后
var a
a = 1
function f1(){
// f1 的作用域
var a
a = 2
f2.call()
}
function f2(){
// f2 的作用域
console.log(a) // f2 的作用域中不存在对 a 的声明,此时应为全局范围中的 a ,因此打印出值 1
}
f1.call()
题目三:
var a = 1
function f1(){
f2.call()
console.log(a) // 是多少
var a = 2
function f2(){
var a = 3
console.log(a) // 是多少
}
}
f1.call()
console.log(a) // 是多少
变量提升后
var a
a = 1
function f1(){
var a
function f2(){
var a
a = 3
console.log(a) // 3
}
f2.call()
console.log(a) // undefined
a = 2
}
f1.call()
console.log(a) // 1
题目四:
var liTags = document.querySelectorAll('li')
for(var i = 0; i<liTags.length; i++){
liTags[i].onclick = function(){
console.log(i) // 点击第 3 个 li 时,打印 2 还是打印 6?
}
} // 点击第 3 个 li 时,i 取值为 2,但 i 遍历完成后变为 6,console.log(i) 会最后打印,因此会打印出 6
闭包
如果一个函数使用了它范围外的变量,那么(这个函数 + 这个变量)就叫做闭包
var a = 1
function f(){
console.log(a)
}
这里只简单对闭包的概念做一下简单说明,如想更详细地了解,请阅读文章 闭包