理解JavaScript的立即调用函数表达式(IIFE)
首先这是js的一种函数调用写法,叫立即执行函数表达式(IIFE,即immediately-invoked function expression)。顾名思义IIFE可以让你的函数立即得到执行(废话)。
一般来说,IIFE有以下几种用途:
1. 创建只使用一次的函数,并立即执行它。
2. 创建闭包,保存状态,隔离作用域。
3. 作为独立模块存在(例子如jQuery),防止命名冲突,命名空间注入(模块解耦)。
1. 创建只使用一次的函数,并立即执行它
创建只使用一次的函数比较好理解,在需要调用函数的地方使用IIFE,类似内联的效果:
(function(){
var a = 1, b = 2;
console.log(a+b); //
})();
还可以传入参数:
(function(c){
var a = 1, b = 2;
console.log(a+b+c); //
})(3);
IIFE比较常见的形式是匿名函数,但是也可以是命名的函数:
(function adder(a, b){
console.log(a+b); //
})(3, 4);
在js中应该尽量使用命名函数,因为匿名函数在堆栈跟踪的时候会造成一些不便。
2. 创建闭包,保存状态,隔离作用域
隔离作用域比较复杂一点,在ES6以前,JS没有块级作用域,只有函数作用域,作为一种对块级作用域的模拟就只能用function模拟一个作用域,比如如下代码:
var myBomb = (function(){
var bomb = "Atomic Bomb"
return {
get: function(){
return bomb
},
set: function(val){
bomb = val
},
}
})()
console.log(myBomb.get()) // Atomic Bomb
myBomb.set("h-bomb")
console.log(myBomb.get()) // h-bomb
console.log(bomb) // ReferenceError: bomb is not defined
bomb = "none"
console.log(bomb) // none
可以看到一个比较奇特的现象,按照常理,一个函数执行完毕,在它内部声明的变量都会被销毁,但是这里变量bomb却可以通过myBomb.get和myBomb.set去读写,但是从外部直接去读和写却不行,这是闭包造成的典型效果。
要清楚解释闭包到底是什么,这里有一篇文章学习Javascript闭包(Closure),上面的代码已经用到了闭包。所有闭包都有一个特点,就是可以通过导出方法从函数外部改变函数内部变量的值,因此可以利用这个特点来隔离作用域,模拟一种“私有”的效果。
举一个IIFE保存变量的例子,我们要写入三个文件,先定义了一个内容数组,然后用for循环遍历这个数组写入文件,最后依次用for循环的下标打印出"File i is written.":
var fs = require('fs');
var fileContents = ["text1", "text2", "text3"];
for (var i = 0; i < fileContents.length; i++) {
fs.writeFile("file"+i+".txt", fileContents[i], function(err){
if (err) {
console.log(err)
}
console.log("File " + i + " is written.")
})
}
这段代码结果是:
File 3 is written.
File 3 is written.
File 3 is written.
很明显和我们的意愿相违背,打印了3次"File 3 is written."。
我们希望的是每个文件的下标索引打印一次。
原因在于写文件是个异步操作,在写完文件调用回调函数时,for循环已经遍历完毕,此时i=3。
要解决这个问题,可以使用IIFE:
var fs = require('fs');
var fileContents = ["text1", "text2", "text3"];
for (var i = 0; i < fileContents.length; i++) {
(function(index){
var fileIndex = index;
fs.writeFile("file"+fileIndex+".txt", fileContents[fileIndex], function(err){
if (err) {
console.log(err)
}
console.log("File " + fileIndex + " is written.")
})
})(i)
}
这次结果是正确的(尽管不是按序,这不在我们考虑范围内):
File 1 is written.
File 2 is written.
File 0 is written.
可以看到这里用IIFE做了一个变量捕获,或者说保存。
再回到myBomb那个例子,这其中用到了一个模式,叫Module模式,很多js模块都是这么写,在IIFE中定义一些私有变量或者私有函数,然后在return的时候导出(一般用一个Object导出)需要暴露给外部的方法。另外在IIFE中定义的变量和函数也不会污染全局作用域,它们都通过统一的入口访问。
3. 作为独立模块存在,防止命名冲突,命名空间注入(模块解耦)
可以使用以下代码为ns这个命名空间注入变量和方法:
var ns = ns || {};
(function (ns){
ns.name = 'Tom';
ns.greet = function(){
console.log('hello!');
}
})(ns);
console.log(ns); // { name: 'Tom', greet: [Function] }
还可以扩展到更多的用途:
(function (ns, undefined){
var salary = 5000; // 私有属性
ns.name = 'Tom'; // 公有属性
ns.greet = function(){ // 公有方法
console.log('hello!');
}
ns.externalEcho = function(msg){
console.log('external echo: ' + msg);
insideEcho(msg);
}
function insideEcho(msg){ // 私有方法
console.log('inside echo: ' + msg);
}
})(window.ns = window.ns || {});
console.log(ns.name); // Tom
ns.greet(); // hello
ns.age = 25;
console.log(ns.age); //
console.log(ns.salary); // undefined
ns.externalEcho('JavaScript'); // external echo: JavaScript/inside echo: JavaScript
insideEcho('JavaScript'); // Uncaught ReferenceError: insideEcho is not defined
ns.insideEcho('JavaScript'); // Uncaught TypeError: ns.insideEcho is not a function
在这里,命名空间可以在局部被修改而不重写函数外面的上下文,起到了防止命名冲突的作用。
注(如果不感兴趣可以直接忽略):还需要解释一下上面IIFE中第二个参数undefined。在js中,undefined表示值的空缺,是预定义的全局变量,它并不是关键字:
console.log(typeof a); // undefined
var a;
console.log(a); // undefined
undefined有多重含义,第一种是一个数据类型叫做undefined,另一种是表示undefined这个数据类型中的唯一值undefined。我们在js代码中看到的undefined一般是全局对象的一个属性,该属性的初始值就是undefined,另一种情况是,这个undefined是个局部变量,和普通变量一样,它的值可以是undefined,也可以是别的。
在ECMAScript 3中undefined是可变的,这意味着你可以给undefined赋值,但在ECMAScript 5标准下,无法修改全局的undefined:
console.log(window.undefined); // undefined
window.undefined = 1;
console.log(window.undefined); // undefined
严格模式下则会直接报错:
'use strict' console.log(window.undefined); // undefined
window.undefined = 1;
console.log(window.undefined); // Uncaught TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
因此我们需要保护这个局部的undefined:
(function (window, document, undefined) {
// ...
})(window, document);
这时候就算有人给undefined赋值也没有问题:
undefined = true;
(function (window, document, undefined) {
// undefined指向的还是一个本地的undefined变量
})(window, document);
不过随着ECMAScript 5的普及(现在几乎没有哪款浏览器不支持ECMAScript 5了),这种担忧基本没有必要了,jQuery也是为了最大程度的兼容性才这么做。
以上例子说明我们可以把命名空间作为参数传给IIFE,以对其进行扩展和装饰:
(function (ns, undefined){
var salary = 5000; // 私有属性
ns.name = 'Tom'; // 公有属性
ns.greet = function(){ // 公有方法
console.log('hello!');
}
ns.externalEcho = function(msg){
console.log('external echo: ' + msg);
insideEcho(msg);
}
function insideEcho(msg){
console.log('inside echo: ' + msg);
}
})(window.ns = window.ns || {});
(function (ns, undefined){
ns.talk = function(){
console.log(ns.name + ' says hello.');
console.log(ns.name + ' says goodbye.');
// 注意这里不能调用私有函数insideEcho,否则会报错,因为talk和insideEcho不在同一个闭包中
}
})(window.ns = window.ns || {});
ns.talk(); // Tom says hello. Tom says goodbye.
命名空间注入
命名空间注入是IIFE作为命名空间的装饰器和扩展器的一个变体,使其更具有通用性。作用是可以在一个IIFE(这里可以把它理解成一个函数包装器)内部为一个特定的命名空间注入变量/属性和方法,并且在内部使用this指向该命名空间:
var app = app || {};
app.view = {};
(function (){
var name = 'main';
this.getName = function(){
return name;
}
this.setName = function(newName){
name = newName;
}
this.tabs = {};
}).apply(app.view);
(function (){
var selectedIndex = 0;
this.getSelectedIndex = function(){
return selectedIndex;
}
this.setSelectedIndex = function(index){
selectedIndex = index;
}
}).apply(app.view.tabs);
console.log(app.view.getName()); // main
console.log(app.view.tabs.getSelectedIndex()); //
app.view.tabs.setSelectedIndex(1);
console.log(app.view.tabs.getSelectedIndex()); //
我们还可以写一个模块构造器来批量生产模块:
var ns1 = ns1 || {}, ns2 = ns2 || {};
var creator = function(val){
var val = val || 0;
this.getVal = function(){
return val;
}
this.increase = function(){
val += 1;
}
this.reduce = function(){
val -= 1;
}
this.reset = function(){
val = 0;
}
}
creator.call(ns1);
creator.call(ns2, 100);
console.log(ns1.getVal()); //
ns1.increase();
console.log(ns1.getVal()); //
console.log(ns2.getVal()); //
对某个私有变量,用API的形式对其进行读写,这其实就是OOP的一些思想在js的应用了。
理解JavaScript的立即调用函数表达式(IIFE)的更多相关文章
- 【javaScript基础】马上调用函数表达式
在javaScript中,每一个函数被调用时,都会创建一个新的运行上下文.由于在一个函数里面定义的变量和函数仅仅能在里面訪问.在外面是不行的.上下文提供了一种非常easy的方法来创建私有性. //ma ...
- 理解javascript中的回调函数(callback)【转】
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- 立即调用的函数表达式---IIFE
有些人则称为“自执行的匿名函数” 在闭包中,我们经常需要使用到匿名函数,我感觉闭包就是一种匿名函数,子集. 但是直接在匿名函数后面调用函数是会出错的.比如: function () { alert(& ...
- javascript模块化编程-详解立即执行函数表达式IIFE
一.IIFE解释 全拼Imdiately Invoked Function Expression,立即执行的函数表达式. 像如下的代码所示,就是一个匿名立即执行函数: (function(windo ...
- 立即调用函数(IIFE)
定义: IIFE:立即调用的函数表达式,声明函数的同时立即调用这个函数. 语法: IIFE的常用写法:这两种写法的作用相同,只是表现形式不同而已,()只是起了自执行的作用 (function(){.. ...
- 深入理解JavaScript执行上下文、函数堆栈、提升的概念
本文内容主要转载自以下两位作者的文章,如有侵权请联系我删除: https://feclub.cn/post/content/ec_ecs_hosting http://blog.csdn.net/hi ...
- 理解javascript中的回调函数(callback)
以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs ...
- javascript笔记05:函数表达式和函数语句的区别
1.首先是函数语句: myfunc(); function myfunc() { //执行一些语句 } 当函数语句被定义的时候,在一个脚本代码被优先考虑,因此,无论该函数是定义之前或者定义之后都可以被 ...
- 立即调用的函数表达式IIFE
1.写法 (function () { alert("IIFE");})();//或者(function () { alert("IIFE"); }());
随机推荐
- JPush简单Java服务端案例实现
一.激光推送准备工作 1.注册极光推送开发者账号,创建应用: 2.完成推送设置,填写应用名提交生成安装包: 3.扫码安装简单的测试apk,查看应用信息会有AppKey和Master Secret用于推 ...
- 学习java应该了解一些html超文本标记语言(前端)
在自己学习的过程中遇到一些内容,怕忘记所以借助博客加深印象也方便查找! html超文本标记语言中,分行级元素和块级元素. 行级元素的含义:行级元素不独占一行,相邻的行级元素在一行排列:行级元素可以控制 ...
- CJOJ 1494 【网络流24题】 搭配飞行员(二分图最大匹配)
CJOJ 1494 [网络流24题] 搭配飞行员(二分图最大匹配) Description 飞行大队有若干个来自各地的驾驶员,专门驾驶一种型号的飞机,这种飞机每架有两个驾驶员,需一个正驾驶员和一个副驾 ...
- Spring Boot 系列(五)web开发-Thymeleaf、FreeMarker模板引擎
前面几篇介绍了返回json数据提供良好的RESTful api,下面我们介绍如何把处理完的数据渲染到页面上. Spring Boot 使用模板引擎 Spring Boot 推荐使用Thymeleaf. ...
- vue+webpack项目实际工作中需要生成一个配置文件供生产环境使用
大家都知道webpack打包十分方便,但是在工作中,前端写好的项目需要后端进行部署,就需要有一个配置文件. 使用插件 : GenerateAssetPlugin , 使用方法 : 1 在项目中安装 ...
- 实现一个栈类,类似STL中的栈
1.思路讲解 stack集合类是一个简单的堆栈的实现. 这里有两个模板参数,T和size,T用于指定堆栈中的元素类型,my_size用于表示堆栈中项数的最大值. 类中添加方法isempty.isful ...
- Docker进阶使用1
容器间共享文件 Docker 的容器和外部环境是相对隔离的,并且容器是一次性的,运行结束后并不会有任何的持久化的文件或者数据.所以当我们需要做应用数据的持久化,或者保留应用的日志文件时,我们需要用到 ...
- FTP服务器 Serv-u 环境搭建
一.安装 *Windows 10 *Serv-u Windows-v15.1.2 *Mysql Mysql 5.7 安装成功后开始配置serv-u. 二.配置 1.新建域(test) 点击新建域,开始 ...
- eclipse中tomcat 中server location灰色,如何修改?
Eclipse中tomcat service设置选择window ----show view---services可以看到服务的面板双击tomcat进入配置界面Service Locations(Sp ...
- Linux学习-->linux系统在移动硬盘的安装
由于自己看了一些文章和linux的好奇,想来一窥Linux的奥秘,因此自己准备学习使用Linux系统,这里记录下自己的安装过程,方便以后自己重装系统时进行查阅. 参考的书籍是鸟哥的<Linux私 ...