一、匿名函数
声明一个没有函数名的函数,就是匿名函数,而有函数名的函数就是具名函数。
匿名函数的常用情形:
1、标签绑定事件(如点击事件、触摸事件等)
var btn = document.getElementById("btn");
btn.onclick = function () {
alert("点我干吗");
}
2、在定时器中使用(如超时调用和周期调用)
var showTimeArar = document.getElementsByTagName("h1")[0];
setInterval(function () {
showTimeArar.innerHTML = new Date().toLocaleString();
}, 1000);
3、对象中的方法
var person = {
name : "凤姐",
age : 30,
play : function () {
alert(this.name + "在美国玩");
}
}
4、匿名函数的自调用
(function () {
alert("匿名函数立即执行")
})();
二、变量的作用域
变量作用域指变量能起作用的有效范围,分为:全局变量和局部变量。
1、全局变量:a、定义在函数外的变量称为全局变量;b、变量的作用域为当前整个文档;c、变量一旦声明,声明会自动提前,且早于该文档的一切代码;d、但是赋值的位置不会改变
var a = 10;//包括了声明和赋值两部分
2、局部变量:a、定义在函数内的变量称为局部变量;b、变量的作用域为当前函数内,外部不能访问到;c、变量一旦声明,声明会自动提前,且早于该函数的一切代码;d、但是赋值的位置不会改变;f、变量重名时,在函数内访问全局变量的方式:window.全局变量名;e、局部和全局的声明变量重名时,局部函数变量声明或赋值会覆盖全局变量。(全局变量不起作用)
3、JS没有块级作用域(if、for),变量作用域是按照函数来划分的;所以变量声明在块级语句内也会自动提前
4、函数声明同变量声明一样,一旦创造函数会自动提前,执行位置即何时调用
三、作用域链
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中。
1、全局执行环境是最外围的一个执行环境,在 Web浏览器中,全局执行环境被认为是 window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
a、对全局执行环境变量来说,变量对象 就是window对象
b、对函数来说,变量对象就是这个函数的活动对象(活动对象是在函数调用时创建的一个内部变量 )
c、每个函数都有自己的执行环境,当执行流进入一个函数时,函数的执行环境就会被推入一个执行环境栈中。而在函数执行之后,栈将执行结束的函数的执行环境弹出,把控制权返回给之前的执行环境。
例1:
var b = 20;
function foo(){
var c = 10;
var a = 30;
var b = 50;
f1();
console.log(b); // 100
function f1(){ //函数提前
var f = 60;
console.log(f) //60
console.log(b); //找声明变量 之后看复制 50
b = 100; // b为全局变量(相对foo),b为局部变量(相对window)
}
}
foo();
console.log(b); //20
执行循序:60(f)->50(b)->100(b)->20(b)
注意:a、执行环境表示在执行的过程中,所能访问的变量的所有区域;b、全局的代码处在一个全局的执行环境中,全局的执行环境中有一个全局的变量对象window。
var i =10;
function foo(){
i=1;
i++;
fo1();
var i =2;
function fo1(){
i++;
console.log(i);
}
}
i=20;
console.log(i);//20
i=30;
console.log(i);//30
foo();//3
根据函数声明的位置查找变量作用域链,之后根据调用位置进行层层计算
var i =10;
function foo(){
i =2;
i++;
return function fo1(){
i++;
return i;
}
}
i=20;
foo();
console.log(i);//3;
i=30;
console.log(i);//30
foo()();
console.log(i);//4
var i = 2;
document.querySelector("button").onclick = function (){
i = 1;
i++;
foo();
}
function foo(){
i++;
console.log(i) //为3
}
console.log(i);//为什么是10而不是3,因为点击时页面已加载完,相当于在文档最后执行
四、闭包
1、闭包是指有权访问另一个函数作用域中变量的函数。
2、闭包是一种特殊的对象。它由两部分构成: 函数,以及创建该函数的环境 。环境由闭包创建时在作用域中的任何局部变量组成。
3、闭包的应用
var i = 30;
function foo1(){
return i;
}
function foo2(){
var i = 20;
return foo1
}
console.log(foo2()());//30 从函数声明的位置开始找
i = 40;
console.log(foo2()());//40
//-----------------------------
function foo(){
var a = 10;
return function (){
a++;
console.log(a);//11
return a;
}
}
a = 20;
var fun = foo(); //fun为闭包
console.log(a);//20
a = 30;
var r = fun(); //从作用域链顶层往下找(自己函数(闭包)的变量->能访问的另一个函数变量->全局作用域变量)
a = 40;
console.log(r);//11
function outer () {
var num = 5;
//定义一个内部函数
function inner () {
//内部函数的返回值是外部函数的一个局部变量
return num;
}
//把局部变量的值++
num++;
// 返回内部函数
return inner;
}
var num = outer()(); // 6,如果num++在return之后则返回5.
alert(num);
4、闭包的经典问题(for循环)
<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
btns[i].onclick = function () {
alert("我是第" + (i + 1) + "个按钮");
};
}
</script>
</body>
每循环一次都会有一个匿名函数设置点击事件,闭包总是保持的变量的最后一个值,所以点击的时候,总是读到 i 的最后一个值4.
三种解决方案
1、将for循环var改成let(let的作用域为块级内部,且不会声明提前)
2、将i赋值给点击事件的下标
btns[i].index = i;
btns[i].onclick = function () {
alert("我是第" + (this.index + 1) + "个按钮");
};
}
3、匿名函数自执行,先执行再循环赋值
(function (num) {
btns[i].onclick = function () {
alert("我是第" + (num + 1) + "个按钮");
}
})(i);
五、总结
1、闭包:
a、闭包是一个有权访问另外一个函数变量的函数;如果一个函数访问了他的外部函数中定义的变量,就是一个闭包。
b、闭包的特点:在闭包中,访问到的外部变量的值,总是这个变量的最新的值!
2、作用域链
a、访问一个变量的时候,总是从作用域链的顶端开始查找,如果找到就得到结果,如果找到不到就一直查找,直到作用域链的末端。
b、因为在方法内的存在变量和函数的声明提前现象,所以函数一旦执行 函数的活动对象(变量对象)中总是保存了这个函数中声明的所有变量和函数。
c、如果在函数中又定义了一个内部函数(还没有执行),则这个时候内部函数的作用域,是包含了外部函数的作用域。 一旦内部函数开始执行则把自己的活动对象添加到了这个作用域的顶端。
d、闭包是作用域链的一个应用