1. 全局函数
全局函数都是保存保存在Go对象中(全局的执行期上下文中),在内存中不释放,始终都在Go中,想调用的话随时都能够调用;所以就和广义的闭包定义相符合,在全局环境中定义一个函数,这个函数就是闭包函数,因为这个函数与全局环境捆绑在一起,并且能够直接访问到全局的作用域,这个函数的作用域链不释放GO执行期环境,所以在全局环境中,想什么时候调用就什么时间调用;
function test1(){
console.log(1);
}
function test2(){
console.log(2);
}
test2();
function test3(){
test1();
}
test3();
2. IIFE(immediately invoked function expression)立即执行函数
1. 立即执行函数解决了什么?
- 下列代码中,add函数不能自动执行,并且也不能够在内存中释放;因为function add函数是闭包函数,在全局环境下,定义属性和方法又能够映射成为window对象的属性,window.add === function add;此时就发挥了闭包函数的功能,在内存中不释放;
立即执行函数解决了:
- 页面加载时,自动执行;
- 执行完成之后,立即释放,立即销毁;
console.log(add(1, 2));
function add(a, b) {
return a + b;
}
2. 立即执行函数定义和声明
(function(){}()); // w3c推荐写法
(function(){})(); // 热门写法
(function(){})
(function(){}) // 立即执行函数不加;报错
3. 立即执行函数会忽略函数名称
- 具名的立即执行函数
立即执行函数会忽略函数名称,原因是:立即执行函数本身的特点,执行完成之后在内存中销毁,导致在之后环境中找不到该函数;
console.log(test); // Uncaught ReferenceError: test is not defined;
(function test(){
var a = 1,
b = 2;
console.log(a + b);
})();
console.log(test); // Uncaught ReferenceError: test is not defined;
4. 立即执行函数的参数
- 立即执行函数的参数,和普通的函数传递参数相同,都是形式参数与实际参数;
(function(a, b){
console.log(a + b);
})(1, 2);
5. 立即执行函数的返回值
立即执行函数也是拥有返回值的,如果需要立即执行函数的返回值,需要创建变量进行保存返回值的;
var num = (function(a, b){
return a + b;
}(2, 4));
3. 表达式和执行符号
执行符号(),()括号括任何东西进去都是表达式,它也都能转换成表达式;
1; // 表达式
(1); // 表达式
(1 + 2); // 表达式
(function(){}); // 表达式
(a = 1); // 表达式
1. 表达式、()执行符号的关系
一定是表达式才能够被执行符号执行;
function test(){}(); // Uncaught SyntaxError: Unexpected token
var test = function(){}(); // 成功执行
(function test(){})(); // 成功执行
2. 表达式会自动忽略函数名
()括号括任何东西进去都是表达式,表达式是会自动忽略函数名称;只有表达式才能被执行符号执行,表达式会忽略函数名;
- 表达式忽略函数名的情况
var test = function fn(){
console.log(1);
}
console.log(fn); // Uncaught ReferenceError: fn is not defined;
- 表达式立即执行之后,在匿名函数在内存中被销毁,所以test打印的值是undefined
var test = function(){}作为函数表达式,与执行符号()组合,此时函数表达式会立即执行,而test变量接收的结果就是匿名函数function(){}立即执行返回的结果;
var test = function(){
console.log(2)
}
console.log(test); // fn
var test = function(){
console.log(2);
}();
console.log(test); // undefined
3. 将函数声明转为表达式的方式
让一个函数声明变为表达式的方法,函数声明前面加上 + - ! || &&符号;
将函数声明转化为表达式后,就可以使用执行符号,立即执行该函数,并且该函数声明的函数名自动就会忽略;
!function test(){
console.log(1);
}();
-function test(){
console.log(1);
}();
1 || function test(){
console.log(1);
}();
- 下列的代码会报错吗?
不会报错,因为JS引擎在解析的时候,认为(6)是表达式,并不是一个执行的符号,所以并不会报错;如果()中不传递参数的话,那么就会抛出语法异常,此时JS引擎认为它是一个执行符号;
function test(a) {
console.log(1);
}(6);
-->
function test(a){
console.log(1);
}
(6);
4. 逗号运算符","
逗号运算符,只会返回最后一个运算的结果;
console.log((6, 5)); // 5
var num = (2 - 1, 6 + 5);
console.log(num); // 11
5. 经典面试题解析
首先循环10次,把数组的每一项都保存一个新的匿名函数,但是这些匿名函数都没有执行,此时这些匿名函数都是一个闭包函数(实用性闭包),与test函数的AO(环境执行期上下文)捆绑到一起,一同返回到全局环境下保存,发挥了实用性闭包的特点;最后遍历循环执行数组中的每一个匿名函数,此时每一个匿名函数的作用域链中捆绑着test函数的AO执行期上下文,获取到变量i,此时由于第一次的for循环,将i的变量自增到10,所以每一个匿名函数都会打印10;
- 打印10个10
function test() {
var arr = [];
var i = 0;
for(;i < 10;) {
arr[i] = function(){
document.write(i + '');
}
i++;
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j](); // 10 个 10
}
- 打印0 - 9
立即执行函数(function(idx){})(i),每一次for循环都会立即执行,每一次循环都会借助立即执行函数的实际参数向立即执行函数内部传递形式参数,而立即执行函数的内部匿名函数是闭包函数,此时匿名函数捆绑着立即执行函数的AO对象(函数执行期上下文),所以当外界匿名函数执行的时候,能够访问到每一个立即执行函数内部的变量i的值;此时立即执行函数本质上执行完成后销毁,但是由于闭包函数的特性,导致立即执行函数的函数执行期上下文被闭包函数所捆绑,所以不会释放;
function test() {
var arr = [];
var i = 0;
for(;i < 10;) {
(function(idx){
arr[idx] = function(){
document.write(i + '');
}
})(i);
i++;
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++) {
myArr[j](); // 10 个 10
}
- 打印10undefined
首先if()语句中存在隐式类型的转换,Boolean(function b(){})返回true,所以进入if语句,但是由于if()条件判断语句中的(function b(){})是表达式,表达式是会忽略函数名称,所以typeof(b)返回undefined,变量a的结果打印'10undefined'
var a = 10;
if(function b(){}) {
a += typeof(b);
}
console.log(a); // 10undefined
- 打印string
(,)表达式内部的逗号运算符(',')返回的结果是函数function test2(){},所以此时变量fn = function test2(){}();函数表达式 + ()执行符号会立即执行,所以执行function test2(){}函数返回字符串'2',所以变量fn在函数function test2(){}执行后,返回字符串'2';
var fn = (
function test1() {
return 1;
},
function test2() {
return '2';
}
)();
console.log(fn); // string
6. 函数表达式为什么要叫函数表达式
因为根据表达式的定义,表达式分为两种类型:赋值和单纯求值的。这就相当于将函数赋值给一个变量,从而符合表达式的定义,所以又叫做函数表达式
var num = function fn(){};
7. 问题:函数表达式的形式也能实现和立即执行函数同样的效果,为啥JS还要搞个()呢?
我的理解: 立即执行函数有多种的形式和写法,但是立即执行函数的核心是立即执行和执行后销毁,通过 表达式 + ()执行符号 的方式能够实现和立即执行函数的效果,但是规范的写法毕竟相对的可读性和可维护性更高。
// 二者执行效果相同
var num = function fn() {
console.log(1);
}();
console.log(num); // undefined
var num = (function fn2(){
console.log(1);
})();
console.log(num); // undefined