让你分分钟学会Javascript中的闭包
Javascript中的闭包
前面的话:
闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它。下面是作者从作用域链慢慢讲到闭包以及在后面提到了一些闭包的高级用法。下面大家一起来学习Javascript中的闭包。
谈一谈JavaScript作用域链
当执行一段JavaScript代码(全局代码或函数)时,JavaScript引擎会创建为其创建一个作用域又称为执行上下文(Execution Context),在页面加载后会首先创建一个全局的作用域,然后每执行一个函数,会建立一个对应的作用域,从而形成了一条作用域链。每个作用域都有一条对应的作用域链,链头是全局作用域,链尾是当前函数作用域。
作用域链的作用是用于解析标识符,当函数被创建时(不是执行),会将this、arguments、命名参数和该函数中的所有局部变量添加到该当前作用域中,当JavaScript需要查找变量X的时候(这个过程称为变量解析),它首先会从作用域链中的链尾也就是当前作用域进行查找是否有X属性,如果没有找到就顺着作用域链继续查找,直到查找到链头,也就是全局作用域链,仍未找到该变量的话,就认为这段代码的作用域链上不存在x变量,并抛出一个引用错误(ReferenceError)的异常。
看下面的例子:
//定义全局变量color,对于全局都适用,即在任何地方都可以使用全局变量color var color = "red";function changeColor(){//在changeColor()函数内部定义局部变量anotherColor,只在函数changeColor()里面有效var anotherColor = "blue";function swapColor(){//在swapColor()函数内部定义局部变量tempColor,只在函数swapColor()里面有效var tempColor = anotherColor;anotherColor = color;color = tempColor;//这里可以访问color、anotherColor和tempColorconsole.log(color); //blueconsole.log(anotherColor); //redconsole.log(tempColor); //blue }swapColor();//这里只能访问color,不能访问anotherColor、tempColorconsole.log(color); //blueconsole.log(anotherColor); //anotherColor is not definedconsole.log(tempColor); //tempColor is not defined }changeColor(); //这里只能访问color console.log(color); //blue console.log(anotherColor); //anotherColor is not defined console.log(tempColor); //tempColor is not defined
还有几个坑需要注意一下:
1、var和函数的提前声明
var color = "red";function changeColor(){var color = "yellow";return color; }var result = changeColor(); console.log(result);
再如:
function fn(a) {console.log(a); var a = 2;function a() {}console.log(a); } fn(1); //输出:function a() {} ,2
2、Javascript中没有块级作用域,但是有词法作用域,比如:
function f1(){var a=1;f2();} function f2(){return a;} var result = f1(); console.log(result); //输出结果:a is not defined
3、在函数内部不用var关键字申明变量,则默认该变量为全局变量,比如:
function add(a,b){var sum = a+b;//次世代sum为add函数内部的变量,仅限在函数内部使用,在函数外面不可以使用return sum; } var result = add(1,2); console.log(result); //3 console.log(sum); //sum is not defined //不使用var关键字声明变量 function add(a,b){sum = a+b;//此时的sum为全局变量,在函数之外也可以调用return sum; } var result = add(1,2); console.log(result); //3 console.log(sum); //3
补充:
在JavaScript中如果不创建变量,直接去使用,则报错:
console.log(xxoo);
// 报错:Uncaught ReferenceError: xxoo is not defined
JavaScript中如果创建值而不赋值,则该值为 undefined,如:
var xxoo;
console.log(xxoo);
// 输出:undefined
在函数内如果这么写:
function Foo(){console.log(xo);var xo = 'seven';
}Foo();
// 输出:undefined
上述代码,不报错而是输出 undefined,其原因是:JavaScript的函数在被执行之前,会将其中的变量全部声明,而不赋值。所以,相当于上述实例中,函数在“预编译”时,已经执行了var xo;所以上述代码中输出的是undefined。
注意:我们平时在声明变量时一定要注意!!!还有不要滥用全局变量(在forin循环的时候特别注意)!!!
4、词法作用域是不可逆的,我们可以从下面的例子中看到结果:
// name = undefined var scope1 = function () {// name = undefinedvar scope2 = function () {// name = undefinedvar scope3 = function () {var name = 'Todd'; // locally scoped };}; };
前面我们了解了作用域的一些基本知识,我们发现有作用域的存在能帮我们省去不少事,但是于此同时,也给我们带来了很多麻烦,比如说我们想在下面的函数A中,调用函数B,我们该怎么办呢?
function A(){
function B(){
//
}
}
思路:我们给函数B设一个返回值,然后在函数A中调用,代码如下:
function A(){function B(){console.log("Hello foodoir!");}return B; } var c = A(); c();//Hello foodoir!
这样我们就可以得到我们想要的结果。这样,我们基本上到了一个最简单的闭包形式。我们再回过头分析代码:
(1)定义了一个普通函数A
(2)在A中定义了普通函数B
(3)在A中返回B(确切的讲,在A中返回B的引用)
(4)执行A(),把A的返回结果赋值给变量 c
(5)执行 c()
把这5步操作总结成一句话:函数A的内部函数B被函数A外的一个变量 c 引用。当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包。
思考:我们还有没有其他的方法?
思路:使用匿名函数
function A(){//匿名函数var B = function(x,y) {return x+y;}console.log(B(1,2));//3return B(1,2); } var c = A(); console.log(c);//3
然而,在Javascript高级程序设计中是这样描述闭包的“闭包是指有权访问另一个函数作用域中的变量的函数”,但是我们看匿名函数的例子,很明显,这种方法不可取!
通过这个例子,能让我们更好的理解闭包。
下面我们再来看下面的几种闭包
demo1:
function fn(){var b = "foodoir";return function(){console.log(b);//foodoirreturn b;} } //console.log(b);//b is not defined var result = fn(); console.log(result());//foodoir
demo2:
var n; function f(){var b = "foodoir";n = function(){return b;} } f(); console.log(n());//foodoir
demo3:
//相关定义与闭包 function f(arg){var n = function(){return arg;};arg++;return n; } var m = f(123); console.log(m());//124 //注意,当我们返回函数被调用时,arg++已经执行过一次递增操作了,所以m()返回的是更新后的值。
demo4:闭包中的读取与修改
//闭包中的设置与修改 var getValue,setValue; (function(){var n = 0;getValue = function(){return n;};setValue = function(x){n = x;} })(); //console.log(n); console.log(getValue());//0 console.log(setValue());//undefined setValue(123); console.log(getValue());//123
demo5:用闭包实现迭代效果
//用闭包实现迭代器效果 function test(x){//得到一个数组内部指针的函数var i=0;return function(){return x[i++];}; } var next = test(["a","b","c","d"]); console.log(next());//a console.log(next());//b console.log(next());//c console.log(next());//d
demo6:循环中的闭包
//循环中的闭包 function fn(){var a = [];for(var i=0;i<3;i++){a[i] = function(){return i;}}return a; } var a = fn(); console.log(a[0]());//3 console.log(a[1]());//3 console.log(a[2]());//3/** 我们这里创建的三个闭包,结果都指向一个共同的局部变量i。* 但是闭包并不会记录它们的值,它们所拥有的只是一个i的连接,因此只能返回i的当前值。* 由于循环结束时i的值为3,所以这三个函数都指向了3这一个共同值。* */
思考:如何使结果输出分别为0、1、2呢?
思路一:我们可以尝试使用自调用函数
function fn(){var a = [];for(var i=0;i<3;i++){a[i] = (function(x){return function(){return x;}})(i);}return a; } var a = fn(); console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2
思路二:我们将i值本地化
function fa(){function fb(x){return function(){return x;}}var a = [];for(var i=0;i<3;i++){a[i] = fb(i)}return a; } console.log(a[0]());//0 console.log(a[1]());//1 console.log(a[2]());//2
------------------------------------------------------分界线-------------------------------------------------------
在这里,我们来对闭包进行更深一步的操作
我们再将demo1的例子进行扩展
代码示例如下:
function funcTest(){var tmpNum=100; //私有变量 //在函数funcTest内//定义另外的函数作为funcTest的方法函数 function innerFuncTest({alert(tmpNum);//引用外层函数funcTest的临时变量tmpNum }return innerFuncTest; //返回内部函数 }//调用函数 var myFuncTest=funcTest(); myFuncTest();//弹出100
到样,我们对闭包的概念和用法有更加熟悉
闭包和this相关
闭包应用举例,模拟类的私有属性,利用闭包的性质,局部变量只有在sayAge方法中才可以访问,而name在外部也访问,从而实现了类的私有属性。
function User(){this.name = "foodoir"; //共有属性var age = 21; //私有属性this.sayAge=function(){console.log("my age is " + age);} } var user = new User(); console.log(user.name); //"foodoir" console.log(user.age); //"undefined" user.sayAge(); //"my age is 21"
关于闭包更深入的了解
前面在demo6中,我们了解了用自调用方法来实现闭包,下面我们用这种方法来进行更复杂的操作(写一个简单的组件)。
(function(document){var viewport;var obj = {init:function(id){viewport = document.querySelector("#"+id);},addChild:function(child){viewport.appendChild(child);},removeChild:function(child){viewport.removeChild(child);}}window.jView = obj; })(document);
这个组件的作用是:初始化一个容器,然后可以给这个容器添加子容器,也可以移除一个容器。功能很简单,但这里涉及到了另外一个概念:立即执行函数。 简单了解一下就行。主要是要理解这种写法是怎么实现闭包功能的。
闭包并不是万能的,它也有它的缺点
1、闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页性能问题。另外在IE下有可能引发内存泄漏 (内存泄漏指当你的页面跳转的时候 内存不会释放 一直占用你的CPU 只有当你关闭了浏览器才会被释放);
2、闭包会在父函数外部改变父函数内部的变量的值,所以不要随便改动父函数内部的值。
更多参考资料:
《Javascript高级程序设计(第三版)》第四章、第七章
《Javascript面向对象编程指南》第三章
作者的话:
这篇文章主要先是通过几个简单的例子介绍作用域链(顺便补充了几个和作用域链相关的易出错的小知识),然后通过提问慢慢过渡到闭包(在闭包这部分介绍了几种常见闭包的例子),后面又进一步讲到了关于闭包的更高级的用法。后面遇到关于闭包的较好的用法会继续更新。
转载于:https://www.cnblogs.com/foodoir/p/5943752.html
让你分分钟学会Javascript中的闭包相关推荐
- [译]Javascript中的闭包(closures)
本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...
- 【javascript笔记】关于javascript中的闭包
最开始看<javascript高级程序设计>的时候就看到了javascript中的闭包,在第七章第二节....好大概知道了,过了段时间,好了又忘了... 我们来看这本书里面关于闭包是怎么描 ...
- 解析面试常问题之JavaScript中的闭包概念及应用,顺便普及一下大家口中常说的内存泄漏问题
JavaScript中的闭包是一个面试中经常被考到的问题,大家可能都对这个概念多多少少都有一些模糊的概念或者一点都不了解,那么今天就来给大家讲解一下. 公众号:前端印象 不定时有送书活动,记得关注~ ...
- 一篇文章把你带入到JavaScript中的闭包与高级函数
在JavaScript中,函数是一等公民.JavaScript是一门面向对象的编程语言,但是同时也有很多函数式编程的特性,如Lambda表达式,闭包,高阶函数等,函数式编程时一种编程范式. funct ...
- javascript中的闭包这一篇就够了
什么是闭包 维基百科中的概念 在计算机科学中,闭包(也称词法闭包或函数闭包)是指一个函数或函数的引用,与一个引用环境绑定在一起,这个引用环境是一个存储该函数每个非局部变量(也叫自由变量)的表. 闭包, ...
- javascript中的闭包closure详解
文章目录 简介 函数中的函数 Closure闭包 使用闭包实现private方法 闭包的Scope Chain 闭包常见的问题 闭包性能的问题 总结 简介 闭包closure是javascript中一 ...
- 闭包的示例_用示例解释JavaScript中的闭包
闭包的示例 什么是封包? (What are Closures?) A closure is the combination of a function and the lexical environ ...
- 深入理解JavaScript中的闭包
闭包没有想象的那么简单 闭包的概念在JavaScript中占据了十分重要的地位,有不少开发者分不清匿名函数和闭包的概念,把它们混为一谈,我希望借这篇文章能够让大家对闭包有一个清晰的认识. 大家都知道变 ...
- JavaScript中的闭包原理
关于闭包 函数可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性被称之为闭包 闭包的原理 函数内部的变量函数外部是无法获取的,如果我们要访问函数内部的某个变量或是变量值的 ...
最新文章
- selenium工具的安装
- 基于软件开发对嵌入式开发的思考
- 第09课:项目实战——让你的神经网络模型越来越深
- Myeclipse的使用方法-添加,修改,删除JRE,修改项目中的jre不显示问题
- 华为云MVP付健权:从机械工程师到AI开发者的华丽转身
- JAVA实现网页版斗地主_使用Java实现简单的斗地主案例
- github微信小程序服务器,GitHub - cp871202/nideshop: NideShop 开源微信小程序商城服务端(Node.js + ThinkJS)...
- java浮动广告_浮动的广告代码
- win10下虚拟机VMware安装PhoenixOS(凤凰OS)
- Linux系统基本操作及命令详解
- 泰森多边形算法 java_泰森多边形构建原理
- 在不损坏硬盘数据情况下,MBR格式转GPT格式,手动创建EFI和MSR分区,安装win8/win10
- 请假时间计算(支持任何时间段)
- 机器人视觉系统分为哪几种,主要包括哪些关键技术?
- cvte java_cvte java面试题
- c语言京东购物系统,仿京东商城: 自主使用微信小程序实现的网上商城案例(包括前端和后台),利用了微信小程序的云数据库...
- 重温Python基础——字符串
- 在远程linux服务器上用wget命令下载nuscenes数据集出现HTTP request sent, awaiting response... 403 Forbidden错误
- 一款由c++开发的计时器
- frp实现花生壳+阿里云内网穿透,实现公有IP映射到本地(支持 TCP、UDP、HTTP、HTTPS 等多种协议)
热门文章
- 为什么要叫python-为什么我要学习python?
- python工作招聘-学习Python 能找到工作?1300+条招聘信息告诉你答案
- python3.8.2安装教程-在服务器上安装python3.8.2环境
- 学python用什么软件开发-5款Python程序员高频使用开发工具推荐
- python资料库-python 资源库
- python处理表格数据-Python数据处理(二):处理 Excel 数据
- python入门指南百度云-Python入门指南
- python timer使用-python下timer定时器常用的两种实现方法
- jupyter安装插件,以及远程访问服务器上的jupyter notebook
- UVa10639 Square Puzzle(WA)