目录

  • JavaScript闭包和this
    • 1 闭包
      • 1.1 变量作用域
      • 1.2 读取函数内部的局部变量
      • 1.3 闭包概念
    • 2 this
      • 2.1 关键点:
      • 2.2 四类调用方式
        • 1)作为对象方法的调用
        • 2)纯粹的函数调用
        • 3)作为构造函数调用
        • 4)使用apply、call、bind调用
      • 2.3 箭头函数中的this
    • 3 样例详解
    • 参考

JavaScript闭包和this

1 闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。—引自MDN

在JS中,通俗来讲,闭包就是能够读取外层函数内部变量的函数

1.1 变量作用域

变量的作用域为两种:全局作用域和局部作用域

1)函数内部可以读取全局变量

let code = 200;function f1() {console.log(code);
}f1(); // 200

2)函数外部无法读取函数内部的局部变量

function f1() {let code = 200;
}
console.log(code); // Uncaught ReferenceError: code is not defined

1.2 读取函数内部的局部变量

1)在函数内部再定义一个函数

function f1() {let code = 200;function f2() {console.log(code);}
}

函数f1内部的函数f2可以读取f1中所有的局部变量。因此,若想在外部访问函数f1中的局部变量code,可通过函数f2间接访问。

2)为外部程序提供访问函数局部变量的入口

function f1() {let code = 200;function f2() {console.log(code);}return f2;
}f1()();  // 200

1.3 闭包概念

1.2中的函数f2,就是闭包,其作用就是将函数内部与函数外部进行连接。

  • 闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的。
function f1() {let obj = {};function f2() {return obj;}return f2;
}let result1 = f1();
let result2 = f1();
console.log(result1() === result2()); // false
  • 不同的闭包,可以共享上层函数中的局部变量
function f() {let num = 0;function f1() {console.log(++num);}function f2() {console.log(++num);}return {f1,f2};
}let result = f();
result.f1(); // 1
result.f2(); // 2

从结果可以看出,闭包f1和闭包f2共享上层函数中的局部变量num

使用闭包的注意点:

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

2 this

2.1 关键点:

  1. this始终指向调用该函数的对象;
  2. 若没有指明调用的对象,则顺着作用域链向上查找,最顶层为global(window)对象;
  3. 箭头函数中的this是定义函数时绑定的,与执行上下文有关
  4. 简单对象(非函数、非类)没有执行上下文;
  5. 类中的this,始终指向该实例对象;
  6. 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

2.2 四类调用方式

1)作为对象方法的调用

function f() {console.log( this.code );
}
let obj = {code: 200,f: f
};
obj.f(); // 200

2)纯粹的函数调用

function f() {console.log( this.code );
}
// 此处,通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window上,因此下面的2次函数调用f(),会输出undefined
// let code = 200;
var code = 200;
f(); // 200
code = 404;
f(); // 404

复杂一点:

function doF(fn) {this.code = 404;fn();
}function f() {console.log(this.code);
}let obj = {code: 200,f: f
};var code = 500;
doF(obj.f); // 404

该列子中,为分析出this的指向,应找到关键点,哪个对象调用了函数f()。obj.f作为doF()的入参,将函数f传给了doF,而doF是由window对象调用的,所以函数doF中的this指向window,继而函数f中的this也指向window。

由于最终执行函数f时,其中的this指向window,所以在函数f中执行this.code = 401时,等同于window.code = 401

function doF(fn) {this.code = 404;fn();
}function f() {this.code = 401;console.log(this.code);
}let obj = {code: 200,f: f
};var code = 500;
doF(obj.f); // 401

3)作为构造函数调用

code = 404
function A() {this.code = 200this.callA = function() {console.log(this.code)}
}
A() // 返回undefined, A().callA会报错。callA被保存在window上
var a = new A()
a.callA() // 200, callA在new A返回的对象里

4)使用apply、call、bind调用

apply

var code = 404;
let obj = {code: 200,f: function() {console.log(this.code);}
}obj.f(); // 200, 实际上是作为对象的方法调用
obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window
obj.f.apply(obj); //200,this指向参数中设置的对象

call

function f() {console.log( this.code );
}
var obj = {code: 200
};
f.call( obj ); // 200

bind

// bind返回一个新的函数
function f(b) {console.log(this.a, b);return this.a + b;
}
var obj = {a: 2
};
var newF = f.bind(obj);
var result = newF(3); // 2 3
console.log(result); // 5

2.3 箭头函数中的this

箭头函数中的this是定义函数时绑定的,而不是在执行函数时绑定。若箭头函数在简单对象中,由于简单对象没有执行上下文,所以this指向上层的执行上下文;若箭头函数在函数、类等有执行上下文的环境中,则this指向当前函数、类。

1)箭头函数在普通对象中

var code = 404;
let obj = {code: 200,getCode: () => {console.log(this.code);}
}
obj.getCode(); // 404

2)箭头函数在函数中

var code = 404;
function f() {// 若此处为let code = 200; code不会绑定到函数f上,则函数getCode访问this.code时,会输出undefinedthis.code = 200;let getCode = () => {console.log(this.code);};getCode();
}
f(); // 200
let func = new f();
console.dir(func); // func中有属性code

3)箭头函数在类中

var code = 404;
class Status {constructor(code) {this.code = code;}getCode = () => {console.log(this.code);};
}
let status = new Status(200);
status.getCode(); // 200

不管是箭头函数还是普通函数,只要是类中,this就指向实例对象。

3 样例详解

1)

var code = 404;let status = {code : 200,getCode : function() {return function(){return this.code;};}
};console.log(status.getCode()()); // 404

执行status.getCode()时,返回函数,status.getCode()()表示执行当前返回的函数,其调用者为全局变量window,所以this.code为绑定在window中的code,值为404。

2)

var code = 404;let status = {code : 200,getCode : function() {let that = this;return function(){return that.code;};}
};console.log(status.getCode()()); // 200

执行status.getCode()时,this指向status,并通过局部变量that保存this的值,最后返回值为函数。status.getCode()()表示执行返回的函数,其that指向的status,所以返回值为200。

3)更复杂的例子

function f() {setTimeout(() => {console.log(">>>" + this); // >>>[object object],语句5this.code = 401;}, 0)console.log( this.code );
}let obj = {code: 200,foo: f
};var code = 500;obj.foo(); // 200,语句1
obj.foo(); // 200,语句2
console.log("--" + obj.code); // --200,语句3
setTimeout(()=>{console.log("---" + obj.code);}, 0); // ---401,语句4

知识补充:函数setTimeout用于创建一个定时器,在同一个的对象上,各个定时器使用用一个编号池(这点很关键),不同的对象使用独立的编号池,同一个对象上的多个定时器有不同的定时器编号。所以,setTimeout到了执行时间点时,其内部的this指向定时器所绑定的对象。

结果分析:函数setTimeout中传入的函数句柄,由于js是单线程执行,即使延时为0,仍需等到本次执行的所有同步代码执行完毕,才能执行。所以在两次执行obj.foo()的过程中,其内部的setTimeout的入参函数(也就是语句5)都未执行。同理,执行语句3时,语句5同样未执行。知道执行语句4,当前同步代码块执行完毕,语句5执行(并且执行了2次,因为语句1语句2分别执行1次),obj上绑定的code被更新为401。最终,语句4的入参函数执行,输出obj.code的值为401。

4)由上面的例子继续扩展

function doFoo(fn) {this.code = 404;fn();
}function f() {setTimeout(() => {console.log(">>>" + this); // >>>[object window],语句3this.code = 401; // 语句4}, 0)console.log( this.code ); // 404,语句2
}let obj = {code: 200,foo: f
};var code = 500;doFoo( obj.foo ); // 语句1
setTimeout(()=>{console.log(obj.code)}, 0); // 200,语句5
setTimeout(()=>{console.log(window.code)}, 0); // 401,语句6

结果分析obj.foo为函数句柄,作为入参传入函数doFoodoFoo的调用房为全局变量window,所以,语句2、3、4中的this均指向window。

参考

  1. MDN闭包
  2. 学习JavaScript闭包—阮一峰
  3. 深入理解ES6箭头函数中的this
  4. 一次搞定闭包和this

JavaScript闭包和this相关推荐

  1. Javascript闭包和闭包的几种写法及用途

    好久没有写博客了,过了一个十一长假都变懒了,今天总算是恢复状态了.好了,进入正题,今天来说一说javascript里面的闭包吧!本篇博客主要讲一些实用的东西,主要将闭包的写法.用法和用途.  一.什么 ...

  2. JavaScript学习总结(十六)——Javascript闭包(Closure)

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.很早就接触过闭包这个概念了,但是一直糊里糊涂的,没有能够弄明白JavaScript的闭包到底是什 ...

  3. 全面理解Javascript闭包和闭包的几种写法及用途【转】

    一.什么是闭包和闭包的几种写法和用法 1.什么是闭包 闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.闭包的特点: 1. ...

  4. 全面理解Javascript闭包和闭包的几种写法及用途

     一.什么是闭包和闭包的几种写法和用法 1.什么是闭包 闭包,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.闭包的特点: 1. ...

  5. JavaScript闭包如何工作?

    您将如何向了解其闭包概念(例如函数,变量等)的人解释JavaScript闭包,但却不了解闭包本身? 我已经在Wikipedia上看到了Scheme示例 ,但是不幸的是它没有帮助. #1楼 我知道已经有 ...

  6. 让你分分钟理解 JavaScript 闭包

    原文:https://www.cnblogs.com/onepixel/p/5062456.html 让你分分钟理解 JavaScript 闭包 闭包,是 Javascript 比较重要的一个概念,对 ...

  7. 全面理解Javascript闭包和闭包的几种写法及用途--转载自https://www.cnblogs.com/yunfeifei/p/4019504.html...

    全面理解Javascript闭包和闭包的几种写法及用途 好久没有写博客了,过了一个十一长假都变懒了,今天总算是恢复状态了.好了,进入正题,今天来说一说javascript里面的闭包吧!本篇博客主要讲一 ...

  8. [转载]深入理解JavaScript闭包(closure)

    最近在网上查阅了不少Javascript闭包(closure)相关的资料,写的大多是非常的学术和专业.对于初学者来说别说理解闭包了,就连文字叙述都很难看懂.撰写此文的目的就是用最通俗的文字揭开Java ...

  9. [转]Javascript 闭包

    [转]Javascript 闭包 简介 Closure 所谓"闭包",指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 闭 ...

  10. JavaScript 闭包的详细分享(三种创建方式)(附小实例)

    JavaScript闭包的详细理解 一.原理:闭包函数--指有权访问私有函数里面的变量和对象还有方法等:通俗的讲就是突破私有函数的作用域,让函数外面能够使用函数里面的变量及方法. 1.第一种创建方式 ...

最新文章

  1. 室内设计木地板材质合集包 Arroway – Design Craft Vol.4
  2. 医疗信息安全再添新保障
  3. 应对海量并发请求,首席布道师谈微服务的应用架构设计
  4. asp.net使用easyUI 前后台数据交互
  5. Solaris 11的ip地址配置
  6. CentOS7+CDH5.14.0安装全流程记录,图文详解全程实测-1虚拟机安装及环境初始化
  7. HJ12 字符串反转
  8. 安装完CentOS可以不做的事
  9. 如何能在git bash中使用mvn命令_使用Github Actions完成CI/CD工作
  10. 5G NR QCL准共址详解
  11. JDK7新特性简单翻译介绍
  12. POJ - 1251(最小生成树.krustal)
  13. (1)zynq FPGA简介
  14. 周鸿祎吐槽乘坐达美航空奇葩经历:飞机飞到半路 机组说要下班
  15. 边界条件(求解偏微分方程的边界条件)
  16. linux上python3的安装
  17. python入门神器 知乎_如何处理 Python 入门难以进步的现象?
  18. 【转载】pyinstaller的使用和几个坑
  19. 爬虫基本知识(转载)
  20. 为你的简书和 GitHub 设定个性域名

热门文章

  1. 局域网安全----接入层安全
  2. python中浪漫的代码_python浪漫表白源码
  3. android R Variable Refresh Rate 可变帧率 VRR
  4. java 传绝对路径无效_痛心!侯耀文弟子突发肺栓塞在医院抢救无效身亡,年仅36岁...
  5. 2019年的最后两个月
  6. JS-斜杠和反斜杠的转换
  7. 金蝶拓展报表教程链接地址
  8. 巨型机的计算机语言主要应用题,《计算机应用基础》复习资料
  9. 50道JavaScript基础面试题(附答案)
  10. 动态语言和静态语言的区别(如python与c++)