前情纪要:上次的公司例会上的分享会上我准备了一个关于“闭包”的ppt,然后自己向部门的同事们讲解了一下我对于这部分内容的理解,但是讲完了之后发现自己还有很多地方说的不是很透彻。
一:背景知识小解:
这里要讲的闭包其实是“javascript 中的闭包”要理解闭包的概念首先要知道一些关于js(javascript的简称,下面就js)的基础知识
Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
另一方面,在函数外部自然无法读取函数内的局部变量。
介绍到这里可能有人要问,为啥不能读取
这就是Javascript语言特有的“链式作用域”结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,
对子对象都是可见的,反之则不成立。
垃圾回收机制(garbage collection):垃圾收集器会定期(周期性)找出
那些不在继续使用的变量,然后释放其内存。
不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,
全局变量的生命周期直至浏览器卸载页面才会结束。
二:初始闭包:
这就是最简答的一个“闭包”,
函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。
编程界崇尚以简洁优雅唯美,很多时候 如果你觉得一个概念很复杂,那么很可能是你理解错了。
还要稍微复杂一点的闭包:
三:如何理解闭包:
闭包常常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。 假设我们在做一个游戏,在写其中关于「还剩几条命」的代码。 如果不用闭包,你可以直接用一个全局变量: window.lives = 30 // 还有三十条命 这样看起来很不妥。万一不小心把这个值改成 -1 了怎么办。所以我们不能让别人 「直接访问」这个变量。怎么办呢? 用局部变量。 但是用局部变量别人又访问不到,怎么办呢? 暴露一个访问器(函数),让别人可以「间接访问」。那么在其他的 JS 文件,就可以使用 window.奖励一条命() 来涨命,使用 window.死一条 命() 来让角色掉一条命。
其实我觉得闭包就是这样一个很简单的概念,这种概念不光光是应用在js中,
在PHP、Scala、Scheme、Common Lisp、Smalltalk、Groovy、JavaScript、Ruby、 Python、Go、Lua、objective c、swift 以及Java(Java8及以上)等语言中都能找到对闭包不同程度的支持。
所以说对于技术来说,基本的原理都是相同的,就像现在的js中有很多在使用面向对象的思路在写框架、写接口、写插件。
四:闭包的几种表现形式
1、匿名自执行函数
var data= { table : [], tree : {} }; (function(dm){ for(var i = 0; i < dm.table.rows; i++){ var row = dm.table.rows[i]; for(var j = 0; j < row.cells; i++){ drawCell(i, j); } } })(data);
( function() {}() ); ( function() {} )(); [ function() {}() ]; ~ function() {}(); ! function() {}(); + function() {}(); - function() {}(); var f = function() {}(); 下面再讲-
2:结果缓存
3:封装
4:继承
简单讲一下3:封装
var person = function(){ //变量作用域为函数内部,外部无法访问 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }(); alert(person.name);//直接访问,结果为undefined alert(person.getName()); person.setName("abruzzi"); alert(person.getName()); (演示一下吧!) 得到结果如下: undefined default abruzzi
小总结:学习过有关“面向对象“原理的高级语言的同学应该看出来了,这就是面向对象的思想,所以再次强调,技术是相同的
五:闭包的优缺点
优点:
1:可以读取函数内部的变量,
2:让这些变量的值始终保持在内存中,
缺点:
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。--但是在这里有个疑问:究竟怎么定义内存泄漏,要是用的内存、变量还叫做泄漏吗?
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
六:一点延伸:什么是函数申明、函数表达式
function fnName () {… }; var fnName = function () { …};
函数声明:function fnName () {…}; 使用function关键字声明一个函数,再指定一个函数名,叫函数声明。
函数表达式: var fnName = function () {…}; 使用function关键字声明一个函数,但未给函数命名,最后将匿名函数赋予一个变量,叫函数表达式,这是最常见的函数表达式语法形式。
匿名函数:function () {}; 使用function关键字声明一个函数,但未给函数命名,所以叫匿名函数,匿名函数属于函数表达式,匿名函数有很多作用,赋予一个变量则创建函数,赋予一个事件则成为事件处理程序或创建闭包等等。
函数声明和函数表达式不同之处在于,一、Javascript引擎在解析javascript代码时会‘函数声明提升’(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式,二、函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用
总结:函数声明提升是重点,能理解了这个概念就懂了声明和表达式的区别了。
七:关于这次分享的原因
引发这次技术学习的一段代码我贴出来
var Eplus365Verifiy = (function (self) { self.FrameworkName = 'Eplus365Verifiy.js'; self.FrameworkVersion = '1.0.0'; //手机号的正则验证 self.MobileVerifiy = function (val) { var myreg = /^1[3,4,5,7,8]\d{9}$/; if (myreg.test(val)) { return true; } return false; } //长度验证,区分中文和字符-如果数据类型设置为varchar才需要这方法验证,如果是nvarchar就不要用了 self.LengthVerifiy = function (val, length) { var len = getStringLen(val); if (len > length) { return false; } else { return true; } } return self; } (Eplus365Verifiy || {})); var Eplus365Verifiy = (function (self) { self.ss = "ss"; return self; } (Eplus365Verifiy || {})) console.log(Eplus365Verifiy.MobileVerifiy("15235382691")); console.log(Eplus365Verifiy.ss);
这里需要理解的是
1:匿名自执行函数
2:Eplus365Verifiy || {} 没有值得时候的初始化
3:return self ;//把本身作为一个返回值,而不是方法。
4:两次定义,但是给同意对象。
总结:
这次关于闭包的技术分享,其实更多的是关于js基础知识点:变量作用域、链式作用域、垃圾回收机制、函数声明、函数表达式、匿名自执行函数等等概念的理解,闭包是一个知识点,很简单但也不是很好理解,希望对大家有帮助吧。
这次学习参考了很多技术大牛的经验,再次鸣谢。