JavaScript-闭包

38次阅读

共计 1998 个字符,预计需要花费 5 分钟才能阅读完成。

概念:什么是闭包(Closure)

  • MDN:闭包是函数和声明该函数的 ++ 词法环境 ++ 的组合。
    A closure is the combination of a function and the lexical environment within which that function was declared.
  • 百度百科 1:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数)。
  • 百度百科 2:闭包就是能够读取其他函数内部变量的函数,是“定义在一个函数内部的函数”。
  • 其他说法 1:闭包是词法闭包的的简称, 是引用了 ++ 自由变量 ++ 的函数。
  • 其他说法 2:闭包就是通过返回一个函数来保留某段作用域的一种方法,通过返回函数把本该消失的作用域保留到这个函数中。

可见:闭包并没有一个明确的、易于理解的概念,泛泛来看,闭包本质上是函数及作用域的一类问题。而实际上,我们也不太需要关注闭包的概念是什么,只需知道其表现、原理及常见的应用场景即可。

自由变量与作用域

  • 作用域分:全局作用域、函数作用域和块作用域(ES6 后);
  • 作用域是在函数定义时确定的,而不是在函数执行时确定的;
  • 在 A 作用域中使用的变量 X,却没有在 A 中声明,那么对于 A 作用域来说,变量 X 即自由变量;
  • 按照作用域链向上查找自由变量时,要先到创建该函数的作用域中去查找,而不是调用该函数的作用域中去查找;
var a = 10;
function sum(x) {return x + a;}
sum(1);
// 11
// 在函数 sum 的作用域里,a 就是自由变量 
var a = 100;
function fn() {
    var a = 10;
    return function(x) {return a + x;}
}
var f = fn();
a = 200;
f(1);
// 11
// 自由变量 a 的值是沿着作用域链向上一级一级找到的 
var x = 10;
function fn() {console.log(x);
}
function show(f) {
    var x = 20;
    (function() {f();
    })()}
show(fn);
// 10
// 自由变量 x 在函数 fn 中执行,而 fn 在全局中定义
// 按上文总结:x 的值要到全局中(定义上下文)查找,而不是到函数 show 的作用域(执行上下文)中查找 
var x = 10;
function show(y) {
    var x = 20;
    (function(z) {console.log(x+z)
    })(y)
}
show(30);
// 30 + 20 = 50

闭包的表现、原因及其应用

  • 闭包的两种表现:
// 函数作为返回值
function fn() {
    var max = 10;
    return function bar(x) {if (x > max) {console.log(x);
        }
    };
}
var f = fn();
f(20); 
// 20
// 函数作为参数
var max = 10;
function bar(x) {if (x > max) {console.log(x);
    }
}
(function(f) {
    var max = 100;
    f(15);
})(bar)
// 15
  • 闭包产生的原因:

正常来讲,一个函数 fn 执行完成后,其上下文环境会销毁。但存在一种意外:
fn 执行完后返回了另一个函数 f,而正巧,返回的函数体中存在一个属于 fn 上下文环境的自由变量,那么 fn 执行完后,其上下文环境不能销毁。
很显然,闭包会增加内容开销。

  • 闭包的实际应用:

首先需要知道每次调用外部函数,都会返回一个新的函数;而且返回函数的执行互不影响。

function lazy_sum(arr) {return function() {return arr.reduce((x,y) => {return x+y;})
    }
}
var f1 = lazy_sum([1,2,3,4,5]);
var f2 = lazy_sum([1,2,3,4,5]);
f1 === f2; // false

以上便是闭包的一种应用:返回一个函数,并延迟执行。

闭包更强大的一个功能是:模拟面向对象的程序设计语言,封装一个 private 变量

// 使用 js 创建一个开放的计数器
function create_counter(initial) {
    var x = initial || 0;
    return {inc: function() {return ++x;}
    }
}
var c1 = create_counter();
var c2 = create_counter(10);
c1.inc(); // 1
c1.inc(); // 2
c2.inc(); // 11
c2.inc(); // 12
c1.inc(); // 3
c2.inc(); // 13

闭包还可以把一个多参数的函数转化成单参数的函数:

function make_pow(n) {return function (x) {return Math.pow(x, n);
    }
}
var pow2 = make_pow(2);
var pow3 = make_pow(3);
console.log(pow2(5)); // 5^2=25
console.log(pow3(7)); // 7^3=343

正文完
 0

JavaScript闭包

38次阅读

共计 963 个字符,预计需要花费 3 分钟才能阅读完成。

基本概念

函数和声明该函数的词法环境的组合。闭包包含了函数也包含了声明该函数的词法环境(作用域)。

闭包实际上是将函数与其所操作的某些数据(环境)关联起来,这些数据或者是环境可以理解为它的一个作用域。因此我们可以达到一个能够访问另一个函数作用域的变量的函数的目的。

// 定义局部变量
function count() {
    let count = 0;
    return function() {
        count = count + 1;
        console.log(count);
    }
}

var getCount = count();

getCount();
getCount();
// 1
// 2

特点

闭包一定 return 一个 function,只有 function 才会有一个封闭的命名空间。

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

用途

  • 匿名自执行函数 – 只需要执行一次,其内部变量无需维护

      (function(){//})();
  • 封装回调保存作用域 (缓存结果)

      for(var i = 1; i < 5; i++) {setTimeout((function(i) {return function() {console.log(i);        
              } 
          })(i), i * 1000)
      }
  • 模拟私有方法

      var myNameSpace = (function () {
          // 私有计数器变量 
          var myPrivateVar = 0;
      
          /* 记录所有参数的私有函数 */
          var myPrivateMethod = function (foo) {console.log(foo + myPrivateVar);
          };
      
          return {
              // 公有变量 
              myPublicVar: 'foo',
              // 调用私有变量和方法的公用函数 *
              myPublicFunction: function (bar) {
                  // 增加私有计数器值 
                  myPrivateVal ++;
      
                  myPrivateMethod(bar);
              }
          };
      })();

常见误区

  • 在循环中创建闭包

      for (var i = 1; i <= 5; i++) {setTimeout(function() {console.log(i)
          }, i * 1000)
      }
  • 添加监听事件

      function func() {var element = document.getElementById("app");
          element.onclick = function() {alert(element.id);
          }
      }

正文完
 0

javascript-闭包

38次阅读

共计 2621 个字符,预计需要花费 7 分钟才能阅读完成。

一、定义:

常规定义:

 闭包的定义:有权利访问外部函数作用域的函数。

通俗定义:

1、函数内部包含了函数。然后内部函数可以访问外部函数的作用域。2、内部函数可以访问 父级函数的作用域。... 等等等

二、思考:

1、我们在日常的开发过程中会应用到 闭包么?

 以之前的知识对于 闭包的理解来讲是这样的

    (function(){for(var i=0; i<10; i++) {console.log(i)
        }
    })()
    
 或者说是这样的

    var fnX = function() {
        var x = 123
        function y() {alert(x)
        }
        y()}

    fnX()   // 123

总结下之前的理解就是:内部函数能访问外部函数作用域,能够保存变量不被销毁而一直存在。

三、作用:

在 JavaScript 中有作用域和执行环境的问题,在函数内部的变量在函数外部是无法访问的,在函数内部却可以得到全局变量。由于种种原因,我们有时候需要得到函数内部的变量,可是用常规方法是得不到的,这时我们就可以创建一个闭包,用来在外部访问这个变量。

通过将一个方法或者属性声明为私用的,可以让对象的实现细节对其他对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束,这样可以是代码更可靠,更易于调试。封装是面向对象的设计的基石。

3.1 什么是作用域
3.1.1 ES5 的作用域问题

在 ES5 中 我们常常会说的一个概念是 局部变量 和 全局变量

那么 局部变量 和 全局变量 所这个 局部 和 全局则为 作用域。这个概念其实介绍起来还是比较多虚无。但是我记得有一本书 叫《你不知道的 JS》在这本书的 上册 作者详细的介绍了 作用域 这个概念。

作用域是什么

  1. 现代 JavaScript 已经不再是解释执行的,而是编译执行的。但是与传统的编译语言不同,它不是提前编译,编译结果不能进行移植。编译过程中,同样会经过分词/词法分析, 解析/语法分析, 代码生成三个阶段。2. 以 var a = 2; 语句为例,对这一程序语句对处理,需要经过引擎, 编译器, 作用域三者的配合。其中,引擎从头到尾负责整个 javascript 程序的编译和执行过程;编译器负责语法分析和代码生成;作用域负责收集并维护由所有声明的标识符组成的系列查询,并实施一套规则,确定当前执行的代码对这些标识符的访问权限。3. 对于 var a = 2; 编译器首先查找作用域中是否已经有该名称的变量,然后引擎中执行编译器生成的代码时,会首先查找作用域。如果找到就执行赋值操作,否则就抛出异常

  4. 引擎对变量的查找有两种:LHS 查询和 RHS 查询。当变量出现中赋值操作左侧时是 LHS 查询,出现中右侧是 RHS 查询

词法作用域

1. 词法作用域就是定义在词法阶段的作用域。词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,词法处理器分析代码时会保持作用域不变

2. 作用域查找会在找到第一个匹配的标识符时停止

3.eval 和 with 可以欺骗词法作用域,不推荐使用

函数作用域和块作用域

1.JavaScript 具有基于函数的作用域,属于这个函数的变量都可以在整个函数的范围内使用及复用
2.(function fun(){})() 函数表达式和函数声明的区别是看 function 关键字出现在声明中的位置。如果 function 是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式

3.with,try/catch 具有块作用域,方便好用的实现块级作用域的是 es6 带来的 let 关键字

提升

    1. 变量的提升
    2. 函数提升(这里就不过多的赘述了)

动态作用域

  1. 词法作用域是一套引擎如何寻找变量以及会在何处找到变量的规则。词法作用域最重要的特征是它的定义过程发生中代码的书写阶段

  2. 动态作用域让作用域作为一个在运行时就被动态确定的形式,而不是在写代码时进行静态确定的形式。eg:
function foo(){console.log(a);  // 2 
} 
function bar(){ 
    var a = 3; 
    foo();} 
var a = 2; 
bar(); 
 词法作用域让 foo() 中的 a 通过 RHS 引用到了全局作用域中的 a,所以输出 2;动态作用域不关心函数和作用域如何声明以及在何处声明,只关心从何处调用。换言之,作用域链是基于调用栈的,而不是代码中的作用域嵌套。如果以动态作用域来看,上面代码中执行时会输出 3


  3.JavaScript 不具备动态作用域,但是 this 机制中某种程度上很像动态作用域,this 关注函数如何调用。
3.1.2 ES6 的作用域问题
 在 ES6 中 出现了块级作用域的概念

let const 在()内则()内的作用域 为 块级作用域。
3.2 什么是执行环境
 执行环境 即为 当前作用域内的环境。
3.3 什么是作用域链
 这个概念其实 也是比较虚的概念,不太好理解。但是一旦理解就不会忘记了。所谓 链 其实就是链条,将需要链接在一起的东西链接在一起(感觉说了一句废话)

作用域链的通俗理解:

 在函数内部作用域 通过 作用域链 可以访问 函数外部作用域 的属性或者方法。一层层的 作用域链 往外走  到最后 则为 window 对象的全局作用域。然后这一条条的 作用域链 就形成了一整条关联的链条。
四、具体案例的分析:

这里 我们举了一个栗子 ????

eg1:

function Person(name) {
    this.name = name
    this.getName = function() {return this.name}
}

var one = new Person('zhang')
one.getName() // zhang
one.name      // zhang

var two = new Person('wang')
two.getName() // wang
one.name      // wang

eg2:

function Person(name) {
    var _name = name
    this.getName = function() {return _name}
} 

var one = new Person('zhang')
one.getName() // zhang

var two = new Person('wang')
two.getName() // wang

eg1 vs eg2

 这二个例子进行对比,虽然 都拿到了自己想要的 name 但是 eg1 的方式会比 eg2 获取 name 
的方式要多一个,即为 作为对象的属性来 获取到 当前的 name(one.name)

那如果 你想让你的 name 属性只能通过 getName 方法来获取,不希望有别的方法来获取 甚至是改变的话,那么 闭包 设置私有属性就是一个很安全的做法,那么这个时候闭包的作用就体现出来了。

正文完
 0

javascript 闭包

38次阅读

共计 1832 个字符,预计需要花费 5 分钟才能阅读完成。

闭包
一、闭包是什么?
将一个 词法作用域 中的 内部函数 作为一个 一级值类型 到处传递,就形成了闭包。
怎么去理解呢?这里要敲黑板划重点了,上面的概念性文字介绍了三个点:

词法作用域(函数)
内部函数
一级值类型传递

1、先说词法作用域
形成一个作用域最常见的就是函数了,函数内部会形成一个内部作用域,然后还有 let、const 以及像 try/catch 结构中的 catch 分句形成的块作用域。
let 就是为其声明的变量隐式劫持了所在的块作用域,这个在后面讲 let 和闭包的时候会详细说明 let 和闭包结合的用法。
通过了解可以知道,这里的作用域其实就是函数的内部作用域。
2、内部函数
内部函数不用介绍了吧,在词法作用域中定义的函数,传递后具有涵盖自身所在作用域的闭包。
3、一级值类型传递
值类型传递方式有很多种啊,函数里面的一级值传递无非就是:返回值(return)、赋值(赋值给外部变量)、参数传递(作为参数传递给外部函数)。
现在可以画一个基本的闭包出来了:
// 三种传递方法①②③分开看,你可以的。

var fn; // 定义全局变量,用于内部赋值 —②
function foo() {
var a = 2;
function bar() {
console.log(a);
};
return bar; // 返回值 —①
fn = bar; // 赋值 —②
baz(bar); // 参数传递 —③
};

// 定义外部函数,用于使用内部分配给全局变量的函数 —②
function cat() {
fn();
};

// 定义外部函数,用于内部参数传递 —③
function baz(func) {
func();
};

foo(); //2 —①
cat(); //2 —②
baz(); //2 —③

再来一例:
function wait(message) {
setTimeout(function timer(){
console.log(message);
},1000);
}
wait(“Hi Baby”);
解析一下,按照我们前面的思路可以贯穿下来:
首先 wait(..) 里面的作用域,作用域内部的 timer(..) 函数,再将内部函数 timer(..) 传递给内置工具函数 setTimeout(..),setTimeout(…) 有参数引用(也就是我们传递的 timer(…)),然后调用它。
整个过程行云流水,然后词法作用域在这个过程中保持完璧之身。OK!
二、循环中的闭包
说到循环闭包就要掏出大家耳熟能详的栗子了。
for(var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
},i*1000)
}

666!好!输出了几个 6,老铁有点懵逼,不知应该扎心还是双击 666。
为何?
你大爷还是你大爷,即使你在每次迭代都定义了函数,但是都在共享全局作用域中,i 还是这个 i
那要怎么解决?
这时候在每个迭代的时候加上一个闭包作用域,并且你得把这个 i 大爷放进作用域中。
// 放法可以是传参①,可以是赋值②

for(var i = 1; i <= 5; i++) {
// 这里先搞一个闭包作用域,派出我们的 IIFE
(function(j) {// —①
setTimeout(function timer() {
console.log(j);
},j*1000)
})(i); // —①

(function() {
var j = i; // —②
setTimeout(function timer() {
console.log(j);
},j*1000)
})();
}
上面这个是用了闭包作用域,每次迭代都生成一个新的作用域,来封闭内部变量。
说到这里,前面提到的 let 应该还有人记得,let 干嘛用的,不就是劫持变量形成块作用域吗?放在这里不是恰到好处?来一发。
for(let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
},i*1000)
}
直接在定义 i 大爷的地方就 “ 绑架 ” 了他。
或者,你也可以麻烦一点,先让他上迭代车,上车之后再 let 定义一个变量把 i 大爷赋给他,两种都行,简单点好。
三、总结一下闭包应用
定时器、事件监听器、Ajax 请求、跨窗口通信、Web Workers 或者其他的异步(或同步)任务中(balabala~~~~),只要使用了回调函数,就是在使用闭包。
还有一处重要的 模块。
模块的两个重要特征:

有外部包装函数(创建内部作用域)且需要被调用。
外部包装函数返回值至少引用一个内部函数(创建包装函数内部作用域闭包)。

正文完
 0

JavaScript 闭包

38次阅读

共计 345 个字符,预计需要花费 1 分钟才能阅读完成。

什么是闭包
一个变量已经过了它的作用域但还能得到它,这就是闭包
例如
var a = (function() {
var _foo = 1
return {
get: function(){
return _foo
},
set: function(v){
_foo = v
}
}
})()
console.log(‘ 闭包 get’, a.get())
a.set(‘hello’)
console.log(‘ 闭包 get’, a.get())
1,a 是这个匿名函数调用的结果,这个匿名函数只会执行一次,这个匿名函数结束后 _foo 变量理论上应该就消失了 2,但是这个匿名函数返回一个对象,这个对象中的 get() 方法返回了这个对象外面的 _foo 变量 3,通过 a.get() 依然能得到这个 _foo 变量,说明这个变量并没有消失,但是只能通过 a.get() 得到这个变量

正文完
 0