一、函数
1)函数定义及调用
函数有三种定义方式,除了常见的函数声明和函数表达式,一个函数就是一个Function对象,我们也可以使用Function的构造函数来创建一个新函数。
- 函数声明
var name = 'mary'
function foo(name) {
name = 'mike'
console.log(name)
}
//调用
foo(name) //输出:mike
console.log(name) //输出:mary
可以看到,当一个值被作为参数传递给函数,函数内部对该参数的改变不影响原值。
- 函数表达式
//匿名函数
let foo = function(name) {
console.log(name)
}
//提供函数名的函数
let func = function foo(name) {
console.log(name)
}
//-----------------
var print = function(f, str) {
f(str)
}
print(func, 'jack')
如上实例所展示,表达式定义法可以创建匿名函数和具名函数,当我们会把一个函数作为变量进行传递时,函数表达式定义法就比较好用了。
- new Function()
function foo(x, y) {
console.log(x+y)
}
//上边的函数就等同于 (以下三种写法都正确)
var foo = new Function('x', 'y', 'console.log(x+y)')
var foo = new Function('x, y', 'console.log(x+y)')
var foo = new Function('x,y', 'console.log(x+y)')
函数一定要处于调用它们的域中,但是函数的声明可以被提升 (出现在调用语句之后),不过需要注意的是,函数提升仅适用于声明式函数,不适用于表达式函数。
----------关于几种定义的区别---------
- 声明式函数有函数提升,其他的没有。
- 第一种定义方式,函数声明的过程中创建了一个和函数名相同的变量,我们可以通过函数名访问定义的函数;而第二种函数表达式的定义方式,函数名和被赋值的变量名毫无关系,并且不能在函数外部通过函数名访问该函数,会报错undefined。
function foo1(){
console.log('foo1')
}
foo1 //打印结果:ƒ foo1(){ console.log('foo1') }
var func = function foo2() {
console.log('foo1')
}
func //打印结果:ƒ foo2(){ console.log('foo2') }
foo2 //打印结果:Uncaught ReferenceError: foo2 is not defined
- 通过Function构造函数定义的函数是没有名称的。
----------有时候看似是函数声明,其实是函数表达式---------
// 函数表达式
(function bar() {})
// 函数表达式
x = function hello() {}
if (x) {
// 函数表达式
function world() {} //非source element
}
// 函数声明
function a() {
// 函数声明
function b() {}
if (0) {
//函数表达式
function c() {} //非source element
}
}
函数声明在一些意外的情况下可能转换成函数表达式:
- 成为表达式的一部分;
- 不再是函数或者脚本自身的“源元素”(source element,是脚本或函数体中的非嵌套语句)。
2)函数作用域
会在下一篇详细介绍作用域和作用域链的时候一起介绍。
3)闭包
1、什么是闭包?
闭包是由函数以及声明该函数的词法环境组合而成的,该环境包含了这个闭包创建时作用域内的任何局部变量。嵌套函数为闭包的形成提供了条件。也就是说,内部函数可以访问外部函数的作用域,而外部函数无法访问内部函数的参数和变量。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
function makeAdder(x) {
return function add(y) {
console.log(x+y, this.x+y)
};
}
var x = 50
var add5 = makeAdder(5)
var add10 = makeAdder(10)
console.log(add5(2)) // 7 52
console.log(add10(2)) // 12 52
-
内部函数add()在执行前从外部函数被返回,我们很容易认为,外部函数makeAdder()的局部变量x只存在于该函数执行期间,在其执行完后就不能再被访问了,所以执行add5()和add10()的时候,打印x+y会报错。但是结果是还能正常访问,为什么?
执行过程中该嵌套函数形成了闭包,闭包包含外部函数的词法环境,词法环境存储的是外部函数的任何局部变量。创建的内部函数实例add5和add10中,有对这个词法环境的引用,因此他们执行的时候能通过引用找到目标变量。 -
add5和add10的执行结果不一样,尽管是通过同一个嵌套函数执行得到的实例,但是两者之间没有关系。
每执行一次外部函数都会形成一个独立的闭包,然后返回的内部函数实例的执行,仅仅是基于其独立的闭包。从本质上讲,makeAdder 是一个函数工厂 ,每调用一次他就创建一个新函数。
2、闭包的使用
1. 多个对象调用同一个方法对同一个变量进行操作的时候。
//如果有三个不同的按钮,点击分别给变量globalVal赋值10,20,30
var globalVal = 1
function changeVal(val) {
return function setVal() {
globalVal = val
}
}
var setVal1 = changeVal(10)
var setVal2 = changeVal(20)
var setVal3 = changeVal(30)
//然后给三个按钮的点击事件分别绑定setVal1、setVal2、setVal3
2. 模拟私有方法。
var obj = (function() {
var num = 0
function change(val) {
num += val
}
return {
increment: function() {
change(1)
},
decrement: function() {
change(-1)
},
value: function() {
return num
}
}
})();
console.log(obj.value()) //0
obj.increment()
console.log(obj.value()) //1
obj.decrement()
console.log(obj.value()) //0
上边的函数示例中,通过一个立即执行的匿名函数只创建了一个词法环境,三个返回的公共函数共享该此法环境。词法环境中包含一个私有变量num和一个私有函数change(),这两个私有项都是不能在匿名函数外边直接访问的,只能通过三个公共函数访问。
------这里补充一下访问函数内部变量的方法------
a. 通过return返回
function foo(param) {
var str = 'sss'
return str+param
}
console.log(foo('yyy')) //sssyyy
b. 通过闭包返回
function foo() {
var str = 'sss'
return function(param) {
return str+param
}
}
let func = foo()
console.log(func('yyy')) //sssyyy
3. 在循环中的使用
经典错误示范:
//省略html内容,直接看js代码吧
function showHelp(help) {
//获取id为help的元素,设置元素显示的内容
document.getElementById('help').innerHTML = help
}
function setupHelp() {
//三个input的id以及获取焦点后各自需要显示的信息
var helpText = [
{'id': 'email', 'help': 'Your e-mail address'},
{'id': 'name', 'help': 'Your full name'},
{'id': 'age', 'help': 'Your age (you must be over 16)'}
]
for (var i = 0; i < helpText.length; i++) {
var item = helpText[i]
//onfocus方法回调改变显示内容的事件
document.getElementById(item.id).onfocus = function() {
showHelp(item.help)
}
}
}
setupHelp()
分析上面一段代码,执行结果会是什么样子呢?
答案是:三个input的onfocus回调时,显示的信息都是关于年龄的信息Your age (you must be over 16)。
原因是,onfocus事件绑定的函数本身和setupHelp()函数的词法环境形成了一个闭包,被绑定函数中访问了词法环境中的item变量。请注意,这里三个不同的input访问的是同一个词法环境!
由于变量item是var声明的、可覆盖的,其作用域不是块级作用域而是函数setupHelp()中最高层的作用域。循环中只是给onfocus绑定了函数并没有执行,所以循环完成的时候,item变量最终被赋值为helpText[2],当用户触发该事件的时候才会执行相应的showHelp()函数,但是词法环境中的item变量已经变成最后一个了。
解决方法:可以使用const声明item变量,这样每个闭包都绑定的块作用域的item变量。