资讯专栏INFORMATION COLUMN

学习JavaScript闭包和作用域笔记

Meils / 2516人阅读

摘要:当函数被调用的时候,作用域链就会包含多个作用域对象。但是当函数要访问时,并没有找到,于是沿着作用域链向上查找,在的作用域找到了对应的标示符,就会修改的值。

JS JavaScript闭包和作用域 闭包
JavaScript高级程序设计中对闭包的定义:闭包是指有权访问另外一个函数作用域中变量的函数。

从概念上,闭包有两个特点:

函数

能访问另外一个函数的作用域中的变量

ES6之前,JavaScript只有函数作用域的概念,没有块级作用域(但catch捕获的异常,只能在catch中访问)的概念。每个函数都是封闭的,外部访问不到函数作用域中的变量。

function getName() {
  var name = "LHH";
  console.log(name);     //"LHH"
}
function displayName() {
    console.log(name);  //报错
}

把代码改成以下:

function getName() {
  var name = "LHH";
  function displayName() {
    console.log(name);   
  }
  return displayName;
}
var getLHH = getName();  
getLHH()  //"LHH"
函数是一个闭包,外部就可以访问函数中的变量

对于闭包有下面三个特性:

1.闭包可以访问当前函数以外的变量
function getOuter(){
  var date = "815";
  function getDate(str){
    console.log(str + date);  //访问外部的date
  }
  return getDate("今天是:"); //"今天是:815"
}
getOuter();
getData是一个闭包,该函数执行时,会形成一个作用域AA中并没有定义变量data,但它能从父一级作用域中找到该变量的定义。
2.即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function getOuter(){
  var date = "815";
  function getDate(str){
    console.log(str + date);  //访问外部的date
  }
  return getDate;     //外部函数返回
}
var today = getOuter();
today("今天是:");   //"今天是:815"
today("明天不是:");   //"明天不是:815"
3.闭包可以更新外部变量的值
function updateCount(){
  var count = 0;
  function getCount(val){
    count = val;
    console.log(count);
  }
  return getCount;     //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816
作用域链

JavaScript中有一个执行环境(execution context)的概念,它定义了变量或函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。可以修改它的属性,但不能引用它。

变量对象也是有父作用域的。当访问一个变量时,解释器会首先在当前作用域查找标识符,如果没有找到,就去父作用域找,直到找到该变量的标识符或者不再存在父作用域链了,这就是作用域链。

作用域链和原型继承有点类似:如果去查找一个普通对象的属性时,在当前对象和其原型中都找不到时,会返回undefined,但查找的属性在作用域中不存在的话就会抛出ReferenceError

作用域顶端是全局对象。对于全局环境中的代码,作用域中只包含一个元素:全局对象。所以,在全局环境中定义变量的时候,它们会被定义到全局对象中。当函数被调用的时候,作用域链就会包含多个作用域对象。

1.全局环境

看一个例子:

// my_script.js
"use strict";
var foo = 1;
var bar = 2;

在全局环境中,创建了两个简单地变量。此时变量对象是全局对象:

执行上述代码,my_script.js本身会形成一个执行环境,以及它所引用的变量对象。

2.无嵌套函数(Non-nested functions)
"use strict";
var foo = 1;
var bar = 2;
function myFunc() {
  //-- define local-to-function variables
  var a = 1;
  var b = 2;
  var foo = 3;
  console.log("inside myFunc");
}
console.log("outside");
//-- and then, call it:
myFunc();

myFunc被定义的时候,myFunc的标识符(identifier)就被加到了当前的作用域对象中(在这里就是全局对象),并且这个标识符所引用的是一个函数对象(function object)。函数对象中所包含的是函数的源代码以及其他的属性。内部属性[[scope]]指向的就是当前的作用域对象。也就是指的就是函数的标识符被创建的时候,我们所能够直接访问的那个作用域对象(在这里就是全局对象)。

myFune所引用的函数对象,其本身不仅仅含有函数的代码,并且还含有指向其创建的时候的作用域对象。

myFunc函数被调用的时候,一个新的作用域对象的被创建了。新的作用域对象中包含了myFunc函数所定义的的本地变量,以及其参数(arguments)。这个新的作用域对象的父作用域对象就是在运行myFunc时我们所能直接访问的那个作用域对象(即全局对象)。

所以,当myFunc被执行的时候,对象之间的关系如下图:

3.有嵌套的函数(Nested functions)

当函数返回没有被引用的时候,就会被垃圾回收器回收。但是对于闭包(函数嵌套是形成闭包的一种形式),即使外部函数返回了,函数对象仍会引用它被创建时的作用域对象。

"use strict";
function createCounter(initial) {
  var counter = initial;
  function increment(value) {
    counter += value;
  }
  function get() {
    return counter;
  }
  return {
    increment: increment,
    get: get
  };
}
var myCounter = createCounter(100);
console.log(myCounter.get());   // 返回 100
myCounter.increment(5);
console.log(myCounter.get());   // 返回 105

当调用createCounter(100)时,对象之间的关系如下图所示:

内嵌函数incrementget都有指向createCounter(100) scope的应用。如果createCounter(100)没有任何返回值,那么createCounter(100) scope不再被引用,于是就可以被垃圾回收。但是因为createCounter(100)实际上是有返回值的,并且返回值被存储在了myCounter中,所以对象之间的引用关系变成了如下图所示:

即使createCounter(100)已经返回,但是其作用域仍在,并能且只能被内联函数访问。可以通过调用myCounter.increment()myCounter.get()来直接访问createCounter(100)的作用域。

myCounter.increment() myCounter.get()被调用时,新的作用域对象会被创建,并且该作用域对象的父作用域对象会是当前可以直接访问的作用域对象。此时,引用关系如下:

当执行到return counter;时,在get()所在的作用域并没有找到对应的标示符,就会沿着作用域链往上找,直到找到变量counter,然后返回该变量。

当多带带调用increment(5)时,参数value会存贮在当前的作用域对象。函数要访问value,能马上在当前作用域找到该变量。但是当函数要访问counter时,并没有找到,于是沿着作用域链向上查找,在createCounter(100)的作用域找到了对应的标示符,increment()就会修改counter的值。除此之外,没有其他方式来修改这个变量。闭包的强大也在于此,能够存贮私有数据。

相同的函数,不同的作用域

//myScript.js
"use strict";
function createCounter(initial) {
  /* ... see the code from previous example ... */
}
//-- create counter objects
var myCounter1 = createCounter(100);
var myCounter2 = createCounter(200);

myCounter1 myCounter2创建之后,关系图如下:

在上面的例子中,myCounter1.incrementmyCounter2.increment的函数对象拥有着一样的代码以及一样的属性值(namelength等等),但是它们的[[scope]]指向的是不一样的作用域对象。

这才有了下面的结果:

var a, b;
a = myCounter1.get();   // a 等于 100
b = myCounter2.get();   // b 等于 200
myCounter1.increment(1);
myCounter1.increment(2);
myCounter2.increment(5);
a = myCounter1.get();   // a 等于 103
b = myCounter2.get();   // b 等于 205

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108870.html

相关文章

  • 学习笔记JavaScript 闭包是怎么通过作用链霸占更多内存的?

    摘要:闭包是怎么通过作用域链霸占更多内存的本文是作者学习高级程序设计第一小节的一点个人理解,详细教程请参考原教材。函数执行过程创建了一个函数的活动对象,作用域链的最前端指向这个对象。函数执行完毕返回值后执行环境作用域链和活动对象一并销毁。 JavaScript 闭包是怎么通过作用域链霸占更多内存的? 本文是作者学习《JavaScript 高级程序设计》7.2第一小节的一点个人理解,详细教程请...

    HmyBmny 评论0 收藏0
  • [学习笔记] JavaScript 闭包

    摘要:但是,必须强调,闭包是一个运行期概念。通过原型链可以实现继承,而与闭包相关的就是作用域链。常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。所以此时,的作用域链虽然销毁了,但是其活动对象仍在内存中。 学习Javascript闭包(Closure)javascript的闭包JavaScript 闭包深入理解(closure)理解 Javascript 的闭包JavaScript ...

    sunsmell 评论0 收藏0
  • Js学习笔记闭包

    摘要:一前言这个周末,注意力都在学习基础知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。因此要注意闭包的使用,否则会导致性能问题。五总结闭包的作用能够读取其他函数内部变量。 # 一、前言 这个周末,注意力都在学习基础Js知识上面,刚好看到了闭包这个神圣的东西,所以打算把这两天学到的总结下来,算是巩固自己所学。也可能有些不正确的地方,也请大家看到了,麻...

    Crazy_Coder 评论0 收藏0
  • JS学习笔记(第7章)(函数表达式)

    摘要:递归闭包模仿块级作用域私有变量小结在编程中,使用函数表达式可以无需对函数命名,从而实现动态编程。匿名函数也称为拉姆达函数。函数声明要求有名字,但函数表达式不需要。中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。 1、递归 2、闭包 3、模仿块级作用域 4、私有变量 5、小结 在JavaScript编程中,使用函数表达式可以无需对函数命名,从而实现动态编程。匿名函数也称...

    xiaokai 评论0 收藏0
  • 重学前端学习笔记(十八)--JavaScript闭包执行上下文

    摘要:申明与赋值立即执行的函数表达式,通过创建一个函数,并且立即执行,来构造一个新的域,从而控制的范围。函数接受一个的形参,该参数是一个对象引用,并执行了。在最新的标准中,引入了一个新概念。 笔记说明 重学前端是程劭非(winter)【前手机淘宝前端负责人】在极客时间开的一个专栏,每天10分钟,重构你的前端知识体系,笔者主要整理学习过程的一些要点笔记以及感悟,完整的可以加入winter的专栏...

    silencezwm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<