立即执行函数,逗号表达式,闭包深入
IIFE
IIFE(立即执行函数表达式) :英文Immediately-invoked function expression;
IIFE常见两种形式:
//1. W3C建议 World Wild Web Consortium
(function(){
console.log(1)
}());
(function(实参){
console.log(1)
}(形参));
//2.常见形式
(function(){
console.log(2)
})();
还有一种可以使用的方式
var num = function(){
return 1;
}();//能执行吗? √
console.log(num); //1
为什么引入IIFE呢?
IIFE的特点:
- 自动执行
- 执行完后立即释放
- 可以返回函数结果(可以有返回值被保存)
当我们使用一个函数,我们 只想要一次执行结果,以后再不使用 (主要原因);
若是全局中的函数,只要被声明就在GO中,一直存在,即使函数被执行结束,也仍然存在,浪费内存或加载过慢。
表达式
()括号包裹的任何东西(变量、函数、对象 …)都叫表达式。
问题来了:
//情况1
function test(){
}();//能执行吗? ×,语法错误
//情况2
var num = function(){
return 1;
}();//能执行吗? √
console.log(num); //1
//情况3
var num2 = function(){
console.log(2);
}();//能执行吗? √ 2
console.log(num2); //undefined
情况三中,控制台输出2代表函数立即执行了,而打印num2为undefined,代表函数执行完毕后,被销毁了。
情况二中,函数执行结束后,把值返回出去保存到变量中,这正是IIFE普遍的用法。
情况一中,函数声明无法立即执行。
再看一种情况:
function test(a){
}(6);//报错吗? ×
//这个组合被js引擎解析成->
function test(a){
}
(6);//把这里的东西当做表达式。 一定是表达式,才可以立即执行
表达式才可以被立即执行,表达式的形式为:
-
()括号包裹任何东西都叫表达式
-
+、-、!、||、&&、++… (数据+运算符+函数->表达式)
//例如: function test(){ }(); //报错 0 == function(){ console.log(2) }(); //2 false ++ function(){ console.log(2) }(); //2可立即执行, 会有报错
总结:将函数声明转化为表达式后,就可以使用执行符号,立即执行该函数,并且该函数声明的函数名自动就会被忽略掉。
表达式被立即执行时,JS引擎会自动忽略函数名
逗号表达式
逗号运算符,返回逗号最后的数据。
console.log((1,2,5,78)) //78
闭包深入(与IIFE结合)
想要打印0-9:
function test (){
arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(){
document.write(i + ' ');
}
}
return arr;
}
var arr = test();
for(j = 0; j < 10; j++) {
arr[j]();
} // '10' '10' 一共十个10
为什么?
当内部函数返回到外部并保存时,一定会产生闭包现象,闭包会使原来的作用域链不释放,可以访问或更改变量。(回答闭包问题的流程)
- for循环的第一个语句var i = 0,相当于是在函数test中声明了一个变量i。(把变量所属的位置找对)
- 在函数执行前一刻test的作用域链从顶往下依次为函数test的AO,全局的GO。(函数的作用域链是什么情况)
- 函数test执行前一刻(预编译),function被定义;此时它的作用域链上从顶部依次往下分别是函数test的AO,全局的GO。(函数执行时,内部函数被定义,此时内部函数的作用域链是什么情况)
- 函数test执行完毕,与自己的AO的连线断开,此时test的AO中的i值为10;(函数执行结束,函数AO并未被销毁,说明变量的情况)
- 函数test执行完毕以后,arr[]数组中存储着10个函数,产生了闭包的现象,在自己的作用域链上连接着test的AO不释放;(具体说明内部函数的作用域链上有什么产生了闭包的现象)
- 当这些函数被执行时,它们的作用域链上从顶部依次往下,自己函数的AO,函数test的AO,全局的GO。变量i值从作用域链顶部依次往下找,自己的函数的AO中没有找到i,就去test的AO中去找,此时i的值为10,输出结果为10;(作用域链查找变量的顺序)
打印0-9的正确方法:
//错误写法中的for循环
for(var i = 0; i < 10; i++){
arr[i] = function(){
document.write(i + ' ');
}
}
// 立即执行直接打印0-9
function test (){
var arr = [];
// 使arr中保存立即执行函数的结果
for(var i = 0; i < 10; i++){
arr[i] = (function(){
document.write(i + ' ');
}());
}
return arr;//IIFE中的函数立即执行了,这里其实都不用返回外部
}
var arr = test();
最最常用的做法:IIFE传参
//立即执行传参打印0-9
function test (){
var arr = [];
for(var i = 0; i < 10; i++){
(function(j){
// 使arr中保存函数,此函数定义时就随变量的变化而变化
arr[j] = function(){
document.write(j + ' ');
}
}(i));
}
return arr;
}
var arr = test();
for(j = 0; j < 10; j++) {
arr[j]();//IIFE中传参,使arr中的函数就保存当前值,这里函数被调用
}
// 依靠普通函数传参
function test (){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function(num){ //接收参数
document.write(num + ' ');
}
}
return arr;
}
var arr = test();
for(j = 0; j < 10; j++) {
arr[j](j);//传参
}
总结2种使用IIFE避免闭包影响情况:
-
内部函数使用IIFE:直接保存IIFE执行的结果,在外部访问就出结果,不需要使用()执行符号。
arr[i] = (function(){ document.write(i + ' '); }());
-
IIFE包裹内部函数并传参(zui最常用):使函数定义时依靠参数,定义时就保存值,在外部需要访问并执行。
(function(j){ // 使arr中保存函数,此函数定义时就随变量的变化而变化 arr[j] = function(){ document.write(j + ' '); } }(i));
闭包高级
函数被定义时生成[[scope]]->生成scope chain -> scope chain中存着上级的环境。
函数被执行的前一刻(预编译过程),生成自己的AO,排到scope chain的最顶端。
函数执行完毕的两种情况:
- 一般情况:函数执行完毕,函数的scope chain与自身AO连线中断,函数的AO也被销毁。
- 闭包现象:函数执行完毕,函数的scope chain与自身AO连线中断,但函数的AO没有被销毁,在内部函数的scope chain上存在。
再说一遍闭包的定义:
内部函数被返回到外部并保存时,一定会产生闭包现象,使原来的作用域链不释放(只要程序整个不被销毁,原来的作用域链就永不释放),从而可以对函数内部变量进行访问和更改。
闭包用途
- 封装插件
- 模块化编程