js--闭包 本是古典 何须时尚 2021-09-27 04:24 486阅读 0赞 # 前言 # 前一篇博客是介绍的作用域与作用域链,已经开始了JavaScript高级部分的学习,那么这篇博客简单的介绍一下js的闭包。 # 内容 # 所谓闭包就是函数 函数作用域中的变量无法在外键访问,使用return将一个可以访问这个变量的对象返回到外界就形成一个闭包 ## 一、何为闭包 ## “闭包”这个词并非是JavaScript特有的,实际上闭包是一个特有的概念. 至于概念本身我不过多介绍,百度一下什么都有. 我主要说说JavaScript中闭包是什么. 在JavaScript中闭包就是函数闭包就是函数,这个概念似乎感觉有点迷惑. 实际上很简单,闭包就是一个封闭包裹的范围. 前文咱们提到过,函数可以限定变量的作用域. 一个变量在函数内部声明,那么在函数外部是无法访问的. 那么这个就是一个封闭的范围. 广义上说就是一个闭包了! 那么这个样子其实没有什么意义. 因为没有什么特别的地方, 但是如果函数中又定义了 函数,并将这个函数以返回值的形式返回,那么,在JavaScript中”子域访问父域”的规则就会打破了. 因为这个时候,在函数外就可以访问函数内的变量. 看下面代码: var func = function() { var num = 10; return function() { alert(num); }; }; var foo = func(); foo(); 这段代码中,函数foo是0级链,而变量num是在1级链中,这个时候,0级链的函数就访问了1级链中的变量num,这段代码运行结果是打印出10. 这样就实现了JavaScript中的闭包.小结一下,JavaScript中的闭包就是在函数中定义变量,然后通过返回值,将可以访问这个变量的函数返回,这样在函数外就可以访问函数内的变量了. 这样就形成了闭包. ## 二、闭包的使用案例及其说明 ## 闭包的案例非常的多. 在JavaScript中,使用闭包就像C语言中使用指针一样. 其基本语法简单,但是使用灵活多变,使用其灵活的语法与特征就能实现许多非常强大的功能. 在此不能阐述闭包的所有用法,但是对于刚刚接触闭包的朋友,下面的案例足够理解一段时间了. ### 2.1 模拟私有成员 ### 这个案例是JavaScript实现面向对象的基础. 看下面代码 var Person = function(name, age, gender) { return { get_name : function() { return name; }, set_name : function(value) { name = value; }, get_age : function(){ return age; }, get_gender : function(){ return gender; } }; }; 这段代码就是一个函数,函数带有三个参数,也就是说在函数内部有三个局部变量,分别表示姓名(name)、年龄(age)和性别(gender). 然后在返回值中,返回一个对象,该对象提供四个方法. 分别给年龄提供读写方法,给性别与年龄提供读取的方法. 这四个函数都是这个函数的子域. 因此返回的这个对象就可以直接访问这三个变量. 但是有了读写的访问权限的限制. ### 2.2 Fibonacci数列 ### Fibonacci数列就是:1, 1, 2, 3, 5, 8, 13, …这个案例是面试题中经常考到的案例,也算是具有代表性的算法题. 看下面代码: // 为了简单就不做n的判断处理了 var Fib = (function() { var fibArr = [1,1]; return function( n ) { var res = fibArr[n]; if(res) { return res; } else { res = arguments.callee(n - 1) + arguments.callee(n - 2); fibArr.push(res); return res; } }; })(); 这个案例一般传统的做法就是使用递归,但是递归的性能问题十分可怕,如果大家有兴趣可以计算一下这个数列的第20项结果是多少,并统计一下这个函数递归调用了多少次. 如下面代码: var count = 0; var fib = function(n) { count++; // 为了简单就不做n的判断处理了 if(n == 0 || n == 1) return 1; return fib(n-1) + fib(n-2); }; var res = fib(20); alert("fib(20)的结果为:" + res + ", 函数调用了 " + count + " 次"); 然后再用新方法,计算同样的结果,并统计一下次数. var count = 0; // 为了简单就不做n的判断处理了 var Fib = (function() { var fibArr = [1,1]; return function( n ) { count++; var res = fibArr[n]; if(res) { return res; } else { res = arguments.callee(n - 1) + arguments.callee(n - 2); fibArr.push(res); return res; } }; })(); var res = Fib(20); alert("Fib(20)的结果为:" + res + ", 函数调用了 " + count + " 次"); 这个结果,我不在这里揭晓,请大家自己下去运行看看. 下面分析一下这段新方法的代码. 在这段代码中,绑定在Fib中的函数,实际上是后面函数运行的返回结果. 后面这个函数有一个私有变量,是一个数组. 保存着第0项和第1项数组的值. 然后返回一个函数. 在调用 Fib(20) 的时候就是在执行这个被返回的函数. 这个函数中,首先访问数组的第n项值,如果数组中有这个数据,就直接返回,否则实现递归计算这个值,并将值加到数组中,最后返回计算的结果. 在JavaScript中,递归使用arguments.callee()表示当前调用函数(即递归函数). 那么这么做最直接的结果是,存在一个缓存,将计算得到的结果保存在缓存中,并且实现所有的计算只计算一次,那么可以大大的提高性能. ### 2.3 html字符串案例 ### 这个是许多js库使用的办法,在很多js库中需要使用正则表达式处理一些数据,而如果每次执行都在方法中保存需要处理匹配的字符串,那么会大量的消耗内存,影响性能. 因此可以将重复使用的表达式都保存在闭包中,每次使用都是访问的这个字符串. 例如: String.prototype.deentityify = function() { var entity = { lt : '<', gt : '>' }; return function() { return this.replace(/&([^;]+);/g, function(a,b) { var r = entity[b]; return typeof r === 'string' ? r : a; }); }; }(); 这段代码会将任何一个字符串中的 < 和 > 都替换成尖括号<和>,对于页面html代码的复制非常好用. ### 2.4 事件处理方法的追加与移除 ### 在JavaScript中并不支持事件处理函数的追加. 大师 Jeremy Keith 给出了一个办法: var loadEvent = function( fn ) { var oldFn = window.onload; if( typeof oldFn === "function" ) { window.onload = function() { oldFn(); fn(); }; } else { window.onload = fn; } }; 不过这段代码没有办法移除已经追加的方法,那么使用闭包的缓存功能就可以轻易的实现. var jkLoad = (function() { var events = {}; var func = function() { window.onload = function() { for(var i in events) { events[i](); } }; }; return { add : function(name, fn) { events[name] = fn; func(); }, remove : function(name) { delete events[name]; func(); } }; })(); 这段代码就是得到用来追加和移出load事件的对象. 如果要追加事件,可以使用 jkLoad.add("f1", function() { // 执行代码1 }); 如果要移除事件处理函数,就是用代码 jkLoad.remove("f1"); 那么这个案例还可以扩展到对应以对象追加指定的事件,那么怎么实现,请大家自己考虑吧!!! ## 三、小结 ## 到此,我们已经分析了闭包是什么,以及闭包的实现一般方式,最后又分析了几个闭包的案例. 我想大家应该对闭包有了更为深刻的理解. 那么在后面的面向对象等高级内容中,我们将再次看到闭包的强大之处. 下面对前面问题做个解答: 第一个问题: var func = function() { alert("调用外面的函数"); }; var foo = function() { func(); var func = function() { alert("调用内部的函数"); }; func(); }; 这段代码在IE下会报错,而在FF和Chrome中会没有任何效果,因为在foo中第一个函数的调用func()就会报错,出现异常,因此后面代码不在执行. 如果需要修改,只需要try-catch一下就好. 如: var func = function() { alert("调用外面的函数"); }; var foo = function() { try { func(); } catch ( e ) { alert( e ); } var func = function() { alert("调用内部的函数"); }; func(); }; 第二个问题: if(! "a" in window) { var a = "定义变量"; } alert(a); 这段代码会返回 undefined. 首先,这段代码中没有函数,因此在if中定义的变量会提前,即等价于 var a; if(! "a" in window) { var a = "定义变量"; } alert(a); 而 in 运算符是用来判断左边的字符串表示的属性是否是右边对象的成员. 在浏览器中JavaScript的全局对象就是window,而直接定义的变量实际上就是全局对象的一个属性,因此如果已经定义了变量a,那么 “a” in window 就返回true,然后取反,即为false,所以if中的代码不会执行,就不会给a赋值,所以打印结果为 undefined.上面代码就等价于: var a; if( false ) { a = "定义变量"; } alert(a); # 总结 # 看了这篇博客,您对闭包有所了解了吗? # end # 谢谢您的阅读!
相关 js闭包 所谓闭包,就是变量 从外部开始,到达父函数体,然后再从父函数体内的函数(子函数) 返回到父函数体,简单一句话概括:函数外部调用函数内部的变量。 通过下面的代码给出结果,解释一 朱雀/ 2022年08月13日 14:00/ 0 赞/ 241 阅读
相关 js闭包 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。 一、变量的作用域 要理解闭包,首先必须理解Javascri Myth丶恋晨/ 2022年07月27日 16:38/ 0 赞/ 138 阅读
相关 JS——闭包 //在函数外部读取函数内部的变量 function c(){ var a=1000; function x(){ 以你之姓@/ 2022年06月04日 09:48/ 0 赞/ 264 阅读
相关 JS闭包 JS闭包 写在闭包之前: 上下文(context) 是一段程序运行所需要的最小数据集合。我们可以从上下文交换(context switch)来理解上下文,在多进程或多线 一时失言乱红尘/ 2022年05月25日 02:05/ 0 赞/ 293 阅读
相关 js闭包 闭包指的是:能够访问另一个函数作用域的变量的函数。 清晰的讲:闭包就是一个函数,这个函数能够访问其他函数的作用域中的变量。 闭包就是将函数内部和函数外部连接起来的一座桥 偏执的太偏执、/ 2022年03月09日 01:36/ 0 赞/ 368 阅读
相关 js--闭包 前言 前一篇博客是介绍的作用域与作用域链,已经开始了JavaScript高级部分的学习,那么这篇博客简单的介绍一下js的闭包。 内容 所谓闭包就是函数 函数作 本是古典 何须时尚/ 2021年09月27日 04:24/ 0 赞/ 487 阅读
相关 js 闭包 闭包 > 闭包就是能够读取其他函数内部变量的函数。闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。 ![6587 ╰半夏微凉°/ 2021年09月10日 07:52/ 0 赞/ 466 阅读
相关 JS闭包 JS闭包 一、什么是闭包 二、闭包的作用 三、闭包的案例 1.保留for循环中的var声明的i 2.循环里的定时器 一、什 分手后的思念是犯贱/ 2021年09月07日 06:10/ 0 赞/ 469 阅读
相关 js 闭包 <html> <head> <title>闭包</title> <meta charset="utf-8"> 梦里梦外;/ 2021年08月28日 01:13/ 0 赞/ 471 阅读
相关 js闭包 Js语言在函数内部可以直接读取全局变量,但函数外部无法读取函数内的局部变量 <script type="text/javascript"> var n=100 朱雀/ 2021年06月24日 16:00/ 0 赞/ 498 阅读
还没有评论,来说两句吧...