闭包函数

什么是闭包函数?

闭包函数是一种函数的使用方式,最常见的如下:

function fn1(){
function fn(){ }
return fn;
}

这种函数的嵌套方式就是闭包函数,这种模式的好处是可以让内层函数访问到外层函数的变量,并且让函数整体不至于因为函数的执行完毕而被销毁。

例如:

function fn1(){
var a =10;
function fn(){
console.log(a); // 10
}
return fn;
}

再比如下面的代码,随着函数的每次执行,变量的值都会进行递增1,原因是因为外层函数的变量处于内层函数的作用域链当中,被内层函数所使用着,当js垃圾回收机制读取到这一情况后就不会进行垃圾回收。

例如:

function fn1(){
var a = 1;
function fn(){
a++;
console.log(a);
}
return fn;
} // 调用函数
var x = fn1();
x(); // 2
x();//3

闭包函数在js的开发当中是非常常见的写法,例如下面这种写法,功能是实现了对数组的一些常规操作的封装,也是属于对闭包函数的一种应用。

let Utils = (function(){
var list = [];
return {
add:function(item){
if(list.indexOf(item)>-1) return; // 如果数组内元素存在,那么不在重复添加
list.push(item);
},
remove:function(item){
if(list.indexOf(item) < 0) return; // 如果要删除的数组数组之内不存在,那么就返回
list.splice(list.indexOf(item),1);
},
get_length:function(){
return list.length;
},
get_showData:function() {
return list;
}
}
})();
Utils.add("hello,world");
Utils.add("this is test");
console.log(Utils.get_showData());// ["hello,world","this is test"]

在上面的代码中,函数嵌套函数形成了闭包函数的结构,在开发中是比较常见的写法。

闭包的概念:

闭包是指有权限访问上一级父作用域的变量的函数.

立即执行函数(IIFE)

在js开发中,经常碰到立即执行函数的写法。大体如下:

// 下面的这种写法就是立即执行函数
// 在函数内部的内容会自动执行
(function(){
var a = 10;
var b = 20;
console.log(a+b); // 30
})();

我们也可以通过第二个括号内传入参数:

(function(i){
console.log(i);
})(i);

这种自调用的写法本质上来讲也是一个闭包函数。

通过这种闭包函数,我们可以有效的避免变量污染等问题,从而创建一个独立的作用域。

但是问题相对来说也很明显,就是在这个独立的作用域当中,我们没有办法将其中的函数或者变量让外部访问的到。所以如果我们在外部需要

访问这个立即执行函数中的变量或者方法,我们就需要通过第二个括号将window这个全局的变量对象传入,并且将需要外部访问的变量或者函数赋值

给window,这样做相当于将其暴露在了全局的作用域范围之内。

需要注意的是,通常情况下我们只需要将必要的方法暴露,这样才能保证代码并不会相互产生过多的影响,从而降低耦合度。

例如:

(function (window){
var a = 10; // 私有属性 function show(){
return a++;
} function sayHello(){ // 私有方法
alert("hello,world");
}
window.show = show;// 将show方法暴露在外部
})(window);

需要理解的是,在很多的代码中,总是在(function(){})()的最前面加上一个;,目的是为了防止合并代码的时候js将代码解析成(function(){})()(function(){})()这种情况。

闭包函数的变异

因为js的特殊性,所以很多时候我们在学习js的时候,除了js代码的语法以外,还要学习很多为了解决实际问题的方案,例如下面的这种写法就是为了

实现module的写法。

例如:

var testModule = function(){
var name = "张三"; // 私有属性,外部无法访问 return {
get_name:function(){ // 暴露在外部的方法
alert(name);
},
set_name:function(new_name){ // 暴露在外部的方法
name = new_name;
}
}
}

我们也可以将这种写法进行升级,和立即执行函数进行适度的结合也是常见的写法:

例如:

var blogModule = (function (my) {
my.name = "zhangsan";
// 添加一些功能
my.sayHello = function(){
console.log(this.name)
}
return my;
} (blogModule || {})); console.log(blogModule.sayHello())

自调用函数(自执行匿名函数(Self-executing anonymous function))和立即执行函数的区别

自调用函数其实也就是递归函数

自调用函数顾名思义,就是调用自身的函数,而立即执行函数则是立即会及执行的函数。

下面是二者的一些比较:

// 这是一个自执行的函数,函数内部执行自身,递归
function foo() { foo(); } // 这是一个自执行的匿名函数,因为没有标示名称
// 必须使用arguments.callee属性来执行自己
var foo = function () { arguments.callee(); }; // 这可能也是一个自执行的匿名函数,仅仅是foo标示名称引用它自身
// 如果你将foo改变成其它的,你将得到一个used-to-self-execute匿名函数
var foo = function () { foo(); }; // 有些人叫这个是自执行的匿名函数(即便它不是),因为它没有调用自身,它只是立即执行而已。
(function () { /* code */ } ()); // 为函数表达式添加一个标示名称,可以方便Debug
// 但一定命名了,这个函数就不再是匿名的了
(function foo() { /* code */ } ()); // 立即调用的函数表达式(IIFE)也可以自执行,不过可能不常用罢了
(function () { arguments.callee(); } ());
(function foo() { foo(); } ());

作用域与作用域链

  1. 作用域

所谓的作用域,指的就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,

在JavaScript中变量的作用域有全局作用域和函数作用域以及ES6新增加的块级作用域。

例如,在函数外部通过var关键字声明的变量就是全局变量,作用域的范围也就是全局作用域,而在函数内部

通过var声明或者let声明的就是局部变量,作用域仅限于函数内部,在{}内部或者流程控制语句或者循环语句内部

通过let声明的变量作用域范围则仅限于当前作用域。函数的参数在()内部,只能在函数内部使用,作用域范围也仅限于函数。

同时window对象的所有属性也拥有全局作用域。

例如:


// 作用域范围
var a = 10; // 全局 function fn1(a,b){ // 函数fn1内 c = 30; // 全局
var x = 30; // 函数fn1内 function fn2(){
var s = "hello"; // 函数fn2内
console.log(x); // 30 函数内部可以访问外层的变量
}
} for(var i =0;i<10;i++){} // 循环体内声明的计数变量i也是一个全局 console.log(i); // 10 for(let j = 0;i<10;j++){} // let 声明的计数变量j 是一个局部 console.log(j);// 出错,访问不到
  1. 执行环境上下文

上面我们说到了作用域,下面再来说下执行环境(execution context)。

什么是执行环境呢?

简单点说,执行环境定义了变量或者函数有权访问的其他的数据,并且也决定了他们各自的行为。需要知道的

是,每一个执行环境当中,都有着一个与之关联的变量对象(variable object),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。

而全局执行环境是最外层的一个执行环境,在web浏览器中最外层的执行环境关联的对象是window,所以我们

可以这样说,所有的全局变量和函数 都是作为window对象的属性和方法创建的。

我们创建的每一个函数都有自己的执行环境,当执行流进行到函数的时候,函数的环境会被推入到一个函数执行栈

当中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。

  1. 作用域链

当代码在一个执行环境执行之时,会创建一个变量对象的一个作用域链(scope chain),来保证在执行环境中

,对执行环境有权访问的变量和函数的有序访问。

作用域第一个也d就是顶层对象始终是当前执行代码所在环境的变量对象(VO)。

例如:

function fn1(){}

fn1在创建的时候作用域链被添加进全局对象,全局对象中拥有所有的全局变量。

例如上面的fn1在创建的时候,所处的环境是全局环境,所以此时的this就指向window。

在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。

如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境上一层的包含环境...

也就是说所谓的作用域链,就是指具体的某个变量或者函数从其第一个对象(活动对象)一直到顶层执行环境。这中间的联系就是作用域链。

被人误解的闭包函数

谈及闭包函数的概念,经常会有人错误的将其理解为从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。

而实际来说,因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。

注意:这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。

闭包函数的应用

闭包函数是js当中非常重要的概念,在诸多的地方可以应用到闭包,通过闭包,我们可以写出很多优秀的代码,下面是一些常见的内容:

例如:

// 数组排序
[1, 2, 3].sort(function (a, b) {
... // 排序条件
}); // map方法的应用,根据函数中定义的条件将原数组映射到一个新的数组中
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6] // 常用的 forEach
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3

例如我们常用的call和apply方法,它们是两个应用函数,也就是应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):

例如:

(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

还有最常使用的写法:

var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);

当然,ajax的写法也就是回调函数其实本质也是闭包:

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// 当数据就绪的时候,才会调用;
// 这里,不论是在哪个上下文中创建
// 此时变量“x”的值已经存在了
alert(x); // 10
};
//...

当然也包括我们上边说的封装独立作用域的写法:

例如:

var foo = {};

// 初始化
(function (object) { var x = 10; object.getX = function _getX() {
return x;
}; })(foo); alert(foo.getX()); // 获得闭包 "x" – 10

不可不知的JavaScript - 闭包函数的更多相关文章

  1. Javascript闭包函数快速上手

    闭包函数是什么?在开始学习的闭包的时候,大家很能都比较难理解.就从他的官方解释来说,都是比较概念化的. 不过我们也还是从闭包的含义出发. 闭包是指函数有自由独立的变量.换句话说,定义在闭包中的函数可以 ...

  2. JavaScript闭包函数&箭头函数调用与执行

    一.标准的闭包函数 //一.标准的闭包函数 function A() { var i=0; ++i; console.log('i : ' + i); return function b() { re ...

  3. javascript闭包函数

    JavaScript中的匿名函数及函数的闭包   1.匿名函数 2.闭包 3.举例 4.注意 1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没 ...

  4. JavaScript闭包函数的写法

    <script type="text/javascript"> //通过js内置的函数构造器创建函数 var func=new Function('a','b','re ...

  5. JavaScript闭包函数的理解

    闭包就是一个函数能够访问其函数外部作用域中的变量,即在外面可以调用函数中的函数的变量,其实他就是将函数内外部连接起来的桥梁 闭包三大特点: 1. 函数嵌套函数 2. 内部函数可以访问外部函数的变量 3 ...

  6. Javascript闭包和C#匿名函数对比分析

    C#中引入匿名函数,多少都是受到Javascript的闭包语法和面向函数编程语言的影响.人们发现,在表达式中直接编写函数代码是一种普遍存在的需求,这种语法将比那种必须在某个特定地方定义函数的方式灵活和 ...

  7. 闭包----你所不知道的JavaScript系列(4)

    一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...

  8. JavaScript 闭包系列二(匿名函数及函数的闭包)

    一. 匿名函数 1. 函数的定义,可分为三种 1) 函数声明方式 function double(x) {     return 2*x; } 2)Function构造函数,把参数列表和函数体都作为字 ...

  9. 第一百一十节,JavaScript匿名函数和闭包

    JavaScript匿名函数和闭包 学习要点: 1.匿名函数 2.闭包 匿名函数就是没有名字的函数,闭包是可访问一个函数作用域里变量的函数.声明:本节内容需要有面向对象和少量设计模式基础,否则无法听懂 ...

随机推荐

  1. 自签名ssl

    openssl  req -nodes -newkey rsa:1024 -out myreq.pem -keyout privatekey.pem openssl req -in myreq.pem ...

  2. 内置对象-Math

    1.随机数 Math.random() 1)获得0-1之间的随机数 2)0到100:Math.round(Math.random()*100) 2.max:求最大值 ,,,) console.log( ...

  3. js中toFixed()函数出现小数点后的多位数的原因

    不演示示例了. 强调一点就是:toFixed()返回的str类型,所以如果想写成百分之多少的情况下,建议直接在分子上乘100后,在除以总和,再使用toFixed()函数保留几位小数.不然会出现如下:7 ...

  4. [Spring Boot] Aspect Intro

    Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another ...

  5. js自定义事件CustomEvent、Event、TargetEvent

    1.Event Event 对象代表事件的状态,比如事件在其中发生的元素.键盘按键的状态.鼠标的位置.鼠标按钮的状态. 事件通常与函数结合使用,函数不会在事件发生前被执行! Event的事件都是系统自 ...

  6. 什么是favicon.ico?

    ㈠定义 ⑴所谓favicon,即Favorites Icon的缩写,顾名思义,便是其可以让浏览器的收藏夹中除显示相应的标题外,还以图标的方式区别不同的网站. ⑵根据浏览器的不同,Favicon显示也有 ...

  7. 《剑指offer》算法题第七天

    今日题目: 复杂链表的复制 二叉搜索树与双向链表 序列化二叉树 字符串的排序 1.复杂链表的复制 题目描述: 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向 ...

  8. Genymotion 配置

    配置Android的SDK

  9. web上传整个文件夹

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 先说下要求: PC端全平台支持,要求支持Windows,Mac,Linux 支持所 ...

  10. learning armbian steps(10) ----- armbian 源码分析(五)

    在上一节的分析当中,已经知道了uboot kernel的源代码路径及编译选项,以及rootfs的版本,相关信息如下所示: ## BUILD CONFIGURATION Build target: Bo ...