【JavaScript】--作用域 ^ 立即执行函数IIFE ^闭包

时间:2024-10-07 10:05:13

作用域

ES5中,

函数作用域:JS函数中声明的变量,会成为函数的局部变量,函数内部声明的变量外部无法访问。

全局作用域:函数外声明的变量,会成为全局变量,在函数内部也可以访问。

当函数嵌套,在这个时候,内部函数与外部函数的这个变量就组成了闭包

注意点:在ES5中没有块级作用域

  1. var a = 10;
  2. function foo() {
  3. // 当函数内部有变量a时,会产生局部作用域,外界全局作用域中的a不会对函数内部造成影响
  4. console.log(a); //undefined
  5. var a = 100;
  6. // 如果把函数内部的变量a注释掉是,函数内部的a输出的就是全局作用域中的a = 10
  7. console.log(a); // 100
  8. function fn() {
  9. console.log(a); //undefined
  10. var a = 200;
  11. console.log(a); // 200
  12. }
  13. fn()
  14. }
  15. foo()
'
运行

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

作用域链

首先要知道什么是*变量

*变量就是当前作用域没有定义的变量,那么*变量的值如何得到 —— 要到创建这个函数的那个父级作用域寻找,如果没有就一直向上级祖先元素寻找(这就是所谓的"静态作用域",静态作用域是指函数的作用域在函数定义时就已经确定。

  1. var a = 100
  2. function fn() {
  3. var b = 200
  4. console.log(a) // 这里的a在这里就是一个*变量 // 100
  5. console.log(b)
  6. }
  7. fn()
'
运行

那么什么是作用域链呢

如果父级也没有这个变量呢?那就继续一层一层向上寻找,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链 。

 立即执行函数 IIFE

作用

  • 页面加载完成后只执行一次的设置函数。

  • 将设置函数中的变量包裹在局部作用域中,不会泄露成全局变量。

 IIFE的使用:

  1. // 这是不采用IIFE时的函数声明和函数调用:
  2. function foo(){
  3. var a = 10;
  4. console.log(a);
  5. }
  6. foo();
  7. //如果普通函数和IIFE函数现在一起 记得使用;进行分隔
  8. // 下面是IIFE形式的函数调用:
  9. (function foo(){
  10. var a = 10;
  11. console.log(a);
  12. })();
'
运行

区别:

在函数的声明中,我们首先看到的是function关键字,而IIFE我们首先看到的是左边的(  ,也就是说,使用一对()将函数的声明括起来,使得JS编译器不再认为这是一个函数声明,而是一个IIFE,即需要立刻执行声明的函数。两者达到的目的是相同的,都是声明了一个函数foo并且随后调用函数foo。

IIFE的写法

1.对返回结果不进行处理

  1. (function(形参){
  2. 函数体内容
  3. })(实参);

2.对返回结果不进行处理

  1. (function(形参){
  2. 函数体内容
  3. }(实参));

以上两种用法是比较常用的,区别是后一个括号的位置。 

3.返回的是一个布尔值,然后进行取反

  1. !function(形参){
  2. return true
  3. }(实参)

4.对于数字返回的是原来的结果,非数字返回NaN

  1. +function(形参){
  2. return 123
  3. }(实参)

5.对于数字返回的是正负符号相反,非数字返回NaN

  1. -function(形参){
  2. return 123
  3. }(实参)

6.对于数字返回的是正负符号相反再减1,非数字返回-1

  1. ~function(形参){
  2. return 123
  3. }(实参)

7.返回的结果是undefined

  1. void function(形参){
  2. 函数体内容
  3. }(实参)

IIFE的基本使用

  1. // 就像其它任何函数一样,一个立即执行函数也能返回值并且可以赋值给其它变量。
  2. var sum = (function (a,b) {
  3. return a + b;
  4. }(1,2))
  5. console.log(sum);
'
运行

!!!经典面试题

为什么需要IIFE?

如果只是为了立即执行一个函数,显然IIFE所带来的好处有限。实际上,IIFE的出现是为了弥补JS(ES5)在作用域方面的缺陷:JS只有全局作用域(global scope)函数作用域(function scope),从ES6开始才有块级作用域(block scope)。在JS中为了实现作用域的隔离,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

闭包

什么是闭包?

简单讲,闭包就是指有权访问另一个函数作用域中的变量的函数,函数嵌套函数,内部引用外部。

MDN 闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

闭包的生成的必要条件:

  • 函数嵌套函数

  • 内部函数引用了外部函数中的数据(属性、函数)

  • 参数和变量不会被回收

 如何产生闭包?

在一个函数内部创建另外一个函数

  1. function func() {
  2. var a = 1, b = 2;
  3. function closure() {
  4. return a + b;
  5. }
  6. return closure;
  7. }
  8. console.log(func()()); // 3
'
运行

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

用途:

  1. 可以读取函数内部的变量,
  2. 让这些变量的值始终保持在内存中。

案例:

  1. function f1() {
  2. var n = 999;
  3. nAdd = function () { n += 1 }
  4. function f2() {
  5. console.log(n);
  6. }
  7. return f2;
  8. }
  9. var result = f1();
  10. result(); // 999
  11. nAdd();
  12. result(); // 1000
'
运行

 解析:

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

闭包使用注意点: 

  1. 闭包会使函数中的变量都保存在内存中不会被销毁,因此会消耗很大的内存空间,滥用闭包会导致网页的性能问题,也有可能造成内存泄露(IE)。解决方法:在退出函数时将不需要的局部变量全部删除。
  2. 闭包会在父函数我不改变父函数内部的值。如果将父函数当作对象使用,把闭包当作公用方法,把内部变量当作它的私有属性,那么不要随便修改父函数内部变量的值,当父函数变量被修改时,所有子函数都会被影响。
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <script>
  11. var name = 'window-name'
  12. var obj = {
  13. name: 'obj-name',
  14. say: function () {
  15. return function () {
  16. console.log(this.name);
  17. }
  18. }
  19. }
  20. var x = obj.say()
  21. // 只看谁调用了x(),由于在全局作用域下被调用,因此this指向全局变量window
  22. x() // window-name
  23. </script>
  24. </body>
  25. </html>