浅谈JS闭包和this关键字

问题背景:由于JS中闭包的存在和this的特殊性使得很多时候无法判断出变量的值。

一.闭包中的变量

例1.

  1. var name="The Window";
  2. var getName=(function(){
  3.         var name = "My function";
  4.         return function(){
  5.             return name;
  6.         }
  7.     })();
  8.  
  9. console.log(getName());//My function
  10. console.log(name);//The Window
  11. getName=null;//解除对匿名函数的引用,释放因闭包占用的多余内存

JS中对闭包的概念解释是:闭包是指有权访问另一个函数作用域中的变量的函数。

例1中getName右侧函数表达式就是一个立即执行函数并且返回匿名函数,这个匿名函数就是一个闭包。对于一般的函数而言,其执行结束之后就会释放其所占内存,但对于闭包就不是这样子了。getName是返回的匿名函数,它有用到外部匿名函数的name,所以立即执行函数执行结束后也是释放不了内存的,必须确定闭包不可能再执行的情况下才会释放内存(闭包占用大量内存的原因)。

为何code5的name是解析成My function而code10却解析成The Window呢?

这就要从执行环境(execution context,俗称"上下文")说起,当代码在执行环境中执行时,先创建变量对象,然后再创建变量对象(VO)的一条作用域链,保证对执行环境有权访问的所有变量和函数的有序访问。而作用域链的前端始终是当前执行的代码所在执行环境的变量对象,下一个变量对象来自下一个包含执行环境,一直延续到全局执行环境(window)的变量对象。标识符的解析也是从作用域链最前端(当前执行环境的VO)一层层解析的,直到作用域链的末端(全局执行环境的VO)。故要看变量的作用域,只要看它所在的执行环境的作用域链范围。------由于函数执行环境比较特殊,其变量对象称为活动对象(AO)。全局执行环境比较特殊,其变量对象始终存在!

执行getName(),其中name所在执行环境(闭包函数执行环境)的作用域链包含自身(即闭包函数)活动对象、外部函数(即立即执行函数)活动对象、全局变量对象,由内而外进行搜索,从外部函数活动对象找到name定义。执行console.log(name),其中name所在执行环境(全局执行环境)的作用域链只包含全局变量对象,从中找到name定义。

例2.

  1. var result = [];
  2. function createFunctions(){
  3.     for(var i=0; i<3 ;i++){
  4.         result[i]=function(){
  5.             return i;
  6.         };
  7.     }
  8. }
  9.  
  10. createFunctions();
  11. console.log(result[0]()); // 3
  12. console.log(result[1]()); // 3
  13. console.log(result[2]()); // 3

作用域链的这种配置机制会导致闭包只能取得包含函数中任何变量的最后一个值!

解决方法:

  1. var result = [];
  2. function createFunctions(){
  3.     for(var i=0; i<3 ;i++){
  4.         result[i]=(function(num){
  5.             return function(){
  6.                 return num;
  7.             };
  8.         })(i);
  9.     }
  10. }
  11.  
  12. createFunctions();
  13. console.log(result[0]()); // 0
  14. console.log(result[1]()); // 1
  15. console.log(result[2]()); // 2

使用3个立即执行函数给每个function(){return num};制作闭包,把i的副本存进num中,有3个i的副本,值分别为0,1,2,result[i]分别取对应的副本即可。

扩展:由于JS中没有块作用域的概念,但可以使用立即执行函数通过闭包的机制来制作块作用域!

例3.

  1. /*JS没有块级作用域导致,局部变量影响全局变量*/
  2. i=2;
  3. for(var i=0;i<4;i++){}
  4. alert(i);//4

例4.

  1. /*使用立即执行函数通过闭包机制模仿块级作用域,不影响外部变量*/
  2. i=2;
  3. (function(){
  4. for(var i=0;i<4;i++){}
  5. })();
  6. alert(i);//2

二.This的特殊性

先来个比较简单的例子:

例5.

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         var name = "My Function";
  6.         return this.name;
  7.     }
  8. }
  9.  
  10. console.log(object.getName());//My object
  11. console.log((object.getName)());//My object
  12. console.log((object.getName = object.getName)());//The Window

this是基于执行环境绑定:全局函数中,this指代window;函数作为对象方法调用时,this指代那个对象;

code10和code11方式都一样,比较好理解。(注意:this.是取属性,getName中的var name="My Function"不能理解为给该函数添加属性name并且该属性的值为"My Function"!)

code12大家可能会比较困惑,为何不是输出"My object"呢"?

  1. console.log((object.getName = object.getName)());//The Window

(object.getName = object.getName)这个函数表达式的意义是:

1.为 object 设置 getName 属性/方法。

2. object.getName的值赋值给它

3.故该表达式的值就是函数本身:function(){var name = "My Funcition";return this.name;}

等同于:

  1. console.log((function(){var name = "My Function";return this.name;})());//The Window

全局函数,this为window,所以this.name为"The Window"!

例子6.

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         return function(){
  6.             return this.name;
  7.         };
  8.     }
  9. }
  10.  
  11. console.log(object.getName()());//The Window
  12. console.log((object.getName = object.getName)()());//The Window

匿名函数比较特殊,this指代window!;

比较系统的分类是《JavaScript语言精粹》中的,分为函数调用模式(this绑定全局对象window)和方法调用模式(this绑定调用方法的主体)

可以看出当函数以函数调用模式使用时,this竟然是固定为window!(如code11所示),这个其实是JS语言设计缺陷,倘若语言设计正确,应该是:当内部函数被调用时,内部函数的this应该绑定到外部函数this对象!

这个设计缺陷有一个很容易的解决方案:

  1. var name="The Window";
  2. var object = {
  3.     name:"My object",
  4.     getName:function(){
  5.         var that = this;
  6.         return function(){
  7.             return that.name;
  8.         };
  9.     }
  10. }
  11.  
  12. console.log(object.getName()());//My object
  13. console.log((object.getName = object.getName)()());//The Window

把this对象赋值给变量that,使得闭包可以取到外部变量that(注意:每个执行环境都有自己的this,所以得采用这种方式来取得外部的this对象)

例7

  1. var name="The Window";
  2. getName=(function(){
  3.        var name = "My function";
  4.         return function(){
  5.             var name="function";
  6.             return this.name;
  7.         }
  8.     })();
  9.  
  10. console.log(getName());//The Window
  11. console.log((getName)());//The Window

最后检验一下:

Code11和code10是一样的,code10属于函数调用,所以this指代window

时间: 2024-11-05 16:36:39

浅谈JS闭包和this关键字的相关文章

浅谈js中的this关键字

浅谈js中的this关键字 全局作用域中的this 函数作用域中的this 不同函数调用方法下的this 直接调用 作为对象的方法调用 作为构造函数调用 通过call或apply方法调用 嵌套函数作用域中的this 浅谈js中的this关键字 this是JavaScript中的关键字之一,在编写程序的时候经常会用到,正确的理解和使用关键字this尤为重要.接下来,笔者就从作用域的角度粗谈下自己对this关键字的理解,希望能给到大家一些启示,权当交流之用. 全局作用域中的this 本文将以作用域由

浅谈js闭包

闭包是js学习中的一个重要知识点,那么js闭包是什么? Js闭包就是一个函数.但是这个函数与js众一般的函数又有一点不同,不同之处在于闭包能够获取其他函数体内的变量.这里简单回顾一下js中有关变量的学习内容:变量声明.变量标识符和变量作用域 .下面一一解释: 变量作用域 ::js中的变量作用域只有两种:局部变量和全局变量:这两者的区别是局部变量是在函数体内定义(js中只有函数能够创建新作用域).全局变量是不在函数内创建或     者不使用var关键字声明的变量称之为局部变量. 变量声明 :: 变

浅谈JS闭包中的循环绑定处理程序

初学者经常碰到的,即获取HTML元素集合,循环给元素添加事件.在事件响应函数中(event handler)获取对应的索引.但每次获取的都是最后一次循环的索引.原因是初学者并未理解JavaScript的闭包特性. 前几天工作中写前端js代码时,遇到了遍历元素给它添加单击事件. (PS:之前也在<jQuery基础教程>第四版中看过讲循环绑定处理程序的内容,当时估计也没怎么用心看,所以没记起来.) 大神要是知道这类情况,可以关掉窗口,写这些主要是给像我一样的小白看的,谢谢! 先贴上错误的例子让大家

浅谈Js闭包现象

一.1.我们探究这个问题的时候如果按照正常的思维顺序,需要知道闭包是什么它是什么意思,但是这样做会让我们很困惑,了解这个问题我们需要知道它的来源,就是我们为什么要使用闭包,先不管它是什么意思!      2.我们使用闭包是因为js的作用域问题,前面我们已经对作用域了解了一些,在函数中,外部不能读取到内部的变量,而内部可以读取到外部的变量,这其实也是js特殊的一个地方!(这个特殊其实是因为函数的作用域是一条作用域链,而且作用域链是有顺序的,我们称之为链式作用域结构!)那么问题来了,如果我们想要从函

浅谈 js eval作用域

就简单聊下如何全局 eval 一个代码. var x = 1; (function () { eval('var x = 123;'); })(); console.log(x); 这个代码得到的是 1 而不是 123如果想让 eval 执行的代码是全局的,那么有几种方法. var x = 1; (function () { window.eval('var x = 123;'); })(); console.log(x); 这个方法标准浏览器都可以得到 123 而IE6-8则依然是 1 相同的

浅谈 js 正则之 test 方法

原文:浅谈 js 正则之 test 方法 其实我很少用这个,所以之前一直没注意这个问题,自从落叶那厮写了个变态的测试我才去看了下这东西.先来看个东西吧. ? 1 2 3 4 5 var re = /\d/; console.log( re.test("1") ); console.log( re.test("1") ); console.log( re.test("1") ); console.log( re.test("1"

浅谈 js 语句块与标签

原文:浅谈 js 语句块与标签 语句块是什么?其实就是用 {} 包裹的一些js代码而已,当然语句块不能独立作用域.可以详细参见这里<MDN block> 也许很多人第一印象 {} 不是对象字面量么?怎么成了语句块了?如果在赋值语句或者表达式里用的时候,确实是对象字面量,如: var a = {}; ({toString:function(){return "hehe"}}) + "..."; 是不是很有意思..但是直接使用如: {toString: fu

浅谈JS之AJAX

0x00:什么是Ajax? Ajax是Asynchronous Javascript And Xml 的缩写(异步javascript及xml),Ajax是使用javascript在浏览器后台操作HTTP和web服务器进行数据交换(用户不知道也感觉不出来,就跟桌面应用程序似的进行数据交互),它不会导致页面重新加载,这样才有更好的用户体验. Ajax是基于以下开放标准: javascript(DOM) css html xml(json) 通俗的说就是使用了javascript(DOM)的XMLH

浅谈 js 下 with 对性能的影响

这几天多次看到有博主们在写 with 的文章,这货确实非常方便,但是却是个性能杀手,所以一直都是上不得台面的.那么他究竟会让效率低下到什么程度呢?先来看下 with 是如何的便捷吧.. // 正常调用 console.log(location.host); console.log(location.pathname); // 在 with 下 with (location) { console.log(host); console.log(pathname); } 如果不影响性能,确实是非常霸气