深入了解 JavaScript 闭包

SolidWorks参数化建模实战:从规则定义到智能装配 本文深入探讨SolidWorks参数化建模的实战技巧,从规则定义到智能装配的全流程。通过实际案例展示如何提升设计效率300%,确保一致性,并加速系列化开发。重点解析草图阶段植入设计意图、装配体级参数化及高级技巧,帮助工程师掌握三维建模的核心竞争力。 阅读详情

图片来源:https://pic3.zhimg.com/v2-d879aabf40880d65ee9099e0d507a56f_1200x500.jpg

前言

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

词法作用域

请看下面的代码:

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();

init() 创建了一个局部变量 name 和一个名为 displayName() 的函数。displayName() 是定义在 init() 里的内部函数,并且仅在 init() 函数体内可用。请注意,displayName() 没有自己的局部变量。然而,因为它可以访问到外部函数的变量,所以 displayName() 可以使用父函数 init() 中声明的变量 name

运行该代码后发现, displayName() 函数内的 alert() 语句成功显示出了变量 name 的值(该变量在其父函数中声明)。这个词法作用域的例子描述了分析器如何在函数嵌套的情况下解析变量名。词法(lexical)一词指的是,词法作用域根据源代码中声明变量的位置来确定该变量在何处可用。嵌套函数可访问声明于它们外部作用域的变量。

闭包

现在来考虑以下例子 :

function makeFunc() {
    var name = "Mozilla";
    function displayName() {
        alert(name);
    }
    return displayName;
}
 
var myFunc = makeFunc();
myFunc();

运行这段代码的效果和之前 init() 函数的示例完全一样。其中不同的地方(也是有意思的地方)在于内部函数 displayName() 在执行前,从外部函数返回。

第一眼看上去,也许不能直观地看出这段代码能够正常运行。在一些编程语言中,一个函数中的局部变量仅存在于此函数的执行期间。一旦 makeFunc() 执行完毕,你可能会认为 name 变量将不能再被访问。然而,因为代码仍按预期运行,所以在 JavaScript 中情况显然与此不同。

原因在于,JavaScript中的函数会形成了闭包。闭包是由函数以及声明该函数的词法环境组合而成的。该环境包含了这个闭包创建时作用域内的任何局部变量。在本例子中,myFunc 是执行 makeFunc 时创建的 displayName 函数实例的引用。displayName 的实例维持了一个对它的词法环境(变量 name 存在于其中)的引用。因此,当 myFunc 被调用时,变量 name 仍然可用,其值 Mozilla 就被传递到alert中。

下面是一个更有意思的示例 — 一个 makeAdder 函数:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}
 
var add5 = makeAdder(5);
var add10 = makeAdder(10);
 
console.log(add5(2));  // 7
console.log(add10(2)); // 12

在这个示例中,我们定义了 makeAdder(x) 函数,它接受一个参数 x ,并返回一个新的函数。返回的函数接受一个参数 y,并返回x+y的值。

从本质上讲,makeAdder 是一个函数工厂 — 他创建了将指定的值和它的参数相加求和的函数。在上面的示例中,我们使用函数工厂创建了两个新函数 — 一个将其参数和 5 求和,另一个和 10 求和。

add5add10 都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在 add5的环境中,x 为 5。而在 add10 中,x 则为 10。

实用的闭包

闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。

在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。

假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}
 
h1 {
  font-size: 1.5em;
}
 
h2 {
  font-size: 1.2em;
}

我们的文本尺寸调整按钮可以修改 body 元素的 font-size 属性,由于我们使用相对单位,页面中的其它元素也会相应地调整。

以下是 JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}
 
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

size12size14size16 三个函数将分别把 body 文本调整为 12,14,16 像素。我们可以将它们分别添加到按钮的点击事件上。如下所示:

document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>

用闭包模拟私有方法

编程语言中,比如 Java,是支持将方法声明为私有的,即它们只能被同一个类中的其它方法所调用。

而 JavaScript 没有这种原生支持,但我们可以使用闭包来模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

下面的示例展现了如何使用闭包来定义公共函数,并令其可以访问私有函数和变量。这个方式也称为 模块模式(module pattern):

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();
 
console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:

Counter.increment,``Counter.decrementCounter.value

该共享环境创建于一个立即执行的匿名函数体内。这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

这三个公共函数是共享同一个环境的闭包。多亏 JavaScript 的词法作用域,它们都可以访问 privateCounter 变量和 changeBy 函数。

你应该注意到我们定义了一个匿名函数,用于创建一个计数器。我们立即执行了这个匿名函数,并将他的值赋给了变量Counter。我们可以把这个函数储存在另外一个变量makeCounter中,并用他来创建多个计数器。

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
};
 
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */

请注意两个计数器 Counter1Counter2 是如何维护它们各自的独立性的。每个闭包都是引用自己词法作用域内的变量 privateCounter

每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另外一个闭包中的变量。

以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

在循环中创建闭包:一个常见错误

在 ECMAScript 2015 引入 let 关键字 之前,在循环中有一个常见的闭包创建问题。参考下面的示例:

<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
 
function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];
 
  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}
 
setupHelp();

数组 helpText 中定义了三个有用的提示信息,每一个都关联于对应的文档中的input 的 ID。通过循环这三项定义,依次为相应input添加了一个 onfocus 事件处理函数,以便显示帮助信息。

运行这段代码后,您会发现它没有达到想要的效果。无论焦点在哪个input上,显示的都是关于年龄的信息。

原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。这是因为变量help使用var进行声明,由于变量提升,所以具有函数作用域。当onfocus的回调执行时,item.help的值被决定。由于循环在事件触发之前早已执行完毕,变量对象item(被三个闭包所共享)已经指向了helpText的最后一项。

解决这个问题的一种方案是使用更多的闭包:特别是使用前面所述的函数工厂:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
 
function makeHelpCallback(help) {
  return function() {
    showHelp(help);
  };
}
 
function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];
 
  for (var i = 0; i < helpText.length; i++) {
    var item = helpText[i];
    document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
  }
}
 
setupHelp();

这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, makeHelpCallback 函数为每一个回调创建一个新的词法环境。在这些环境中,help 指向 helpText 数组中对应的字符串。

另一种方法使用了匿名闭包:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
 
function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];
 
  for (var i = 0; i < helpText.length; i++) {
    (function() {
       var item = helpText[i];
       document.getElementById(item.id).onfocus = function() {
         showHelp(item.help);
       }
    })(); // 马上把当前循环项的item与事件回调相关联起来
  }
}
 
setupHelp();

避免使用过多的闭包,可以用let关键词:

function showHelp(help) {
  document.getElementById('help').innerHTML = help;
}
 
function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];
 
  for (var i = 0; i < helpText.length; i++) {
    let item = helpText[i];
    document.getElementById(item.id).onfocus = function() {
      showHelp(item.help);
    }
  }
}
 
setupHelp();

这个例子使用let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。

性能考量

如果不是某些特定任务需要使用闭包,在其它函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是,每个对象的创建)。

考虑以下示例:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };
 
  this.getMessage = function() {
    return this.message;
  };
}

在上面的代码中,我们并没有利用到闭包的好处,因此可以避免使用闭包。修改成如下:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

但我们不建议重新定义原型。可改成如下例子:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

在前面的两个示例中,继承的原型可以为所有对象共享,不必在每一次创建对象时定义方法。

想查看更多精彩文章,请关注“悲伤日记”

点个在看不迷路,感谢您的查阅

闭包】前端的“保护神”——闭包详解+底层原理 它们能够保持对外部数据(如事件触发时的状态、函数参数等)的访问,即使在异步操作完成后,闭包仍然能够访问这些数据。你可以将某些功能封装在闭包内部,外部只能通过公开的方法调用它们,从而达到隐藏细节、减少外部依赖的目的。闭包提供了数据封装的能力,可以将状态和行为封装在一个函数内部,并通过暴露的接口与外部进行交互。闭包可以用于权限管理和角色管理的场景中,通过闭包来封装不同角色的权限信息,从而提供灵活的权限控制。是私有的,外部无法直接访问。,即使这个函数在外部被调用时,依然能访问到其定义时的父函数的局部变量。 阅读详情

相关推荐

了解 JavaScript 中的闭包

闭包允许函数在其父作用域中保留对变量的访问,即使在父函数执行完毕后也是如此。闭包Javascript 中一个强大的概念,它允许函数保留对其父作用域中变量的访问。2.Memoisation:闭包可以创建函数工厂,这些函数工厂可以生成具有预设值的自定义函数(记忆函数)。每个生成的函数都保留自己的一组变量。考虑在另一个函数内部声明的函数,然后内部函数与其父(外部)函数形成闭包。由于闭包Javascript 获得了以更健壮的方式实现代码的能力,允许程序员生成有效的设计模式,从而使代码更高效。

我的博客,不一样的自我表达 91

闭包的实现原理和作用、以及内存泄露

一、闭包的实现原理和作用,可以列举几个开发中闭包的实际应用 1、闭包的概念:指有权访问另一个函数作用域中的变量的函数,一般情况就是在一个函数中包含另一个函数。 2、闭包的作用:访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理 因为函数内部声明 的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的,这就是作用域链的特点了。 子级可以向父级查找变量,逐级查找,...

alegria_x的博客 4066

结构化数据特征构建神器openfe的实战代码

最近订阅专栏的几位同学一起参加了AI比赛,该比赛的数据形式是结构化的。随着特征和模型的不断迭代优化,达到了一个难以跨越的瓶颈。希望我能够给出一些有效的方法,帮助他们提高成绩。在个人的调研和体验后,向他们推荐了结构化数据特征构建神器openfe,最终也帮助他们提升了十来名的成绩,个人也感到十分的欣慰。特意写此博客,希望能够帮助更多的同学。 文章目录 1. 安装 2. 实战例子一 3. 实战例子二

herosunly的博客 3万+

闭包的原理

闭包的原理

MCCLS的博客 1465

详解闭包:原理及作用

闭包是指能够访问自由变量的函数。在JavaScript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数”。要能够彻底了解闭包首先要先理解什么是作用域和作用域链。

weixin_42069587的博客 512

闭包的定义,原理,应用场景,优点,缺点

闭包的定义,原理,应用场景,优缺点,作用域链,变量提升

weixin_52148548的博客 3901

JavaScript】一文了解JS的闭包

详解JavaScript闭包使用及其日常开发遇到的常见问题

ztK63LrD的博客 4029

JavaScript基础】深入了解JS的闭包

闭包是JS中一个重要的概念,也是面试中的重点,相信大家在面试中都会碰到这样的几个问题: 1. 请你说说什么是闭包? 2. 请你说说闭包有哪些应用? 3. 请你说说闭包有哪些优缺点? 大家都是怎么回答这几个问题的呢?在网上的文章中对闭包的概念都有不同的说法,导致很多人对闭包没有一个清晰的概念,接下来就和我一起探究什么是闭包。 首先...

weixin_41531446的博客 346

js 点击闭包_分分钟了解弄懂JavaScript闭包

点击右上方红色按钮关注“web秀”,让你真正秀起来什么是JS闭包?分分钟了解弄懂JavaScript闭包先看一段代码:function a(){ var n = 0; function couter() { n++; console.log(n); } couter(); couter(); } a(); //控制台输出1,再输出2再来看一段代码:function a(){ var...

weixin_42307290的博客 112

从node.js模块化开发来了解javascript闭包

  之前看过很多关于javascript闭包的解释,只有短短几个demo,一大段晦涩难懂的介绍,巴拉巴拉一大段闭包的概念,但是到最后也没看懂闭包到底是什么意思,起什么作用,直到昨晚凌晨,我在学习node.js模块开发的时候,才突然恍然大悟,所以赶紧记下来,免得以后忘记。代码来源:廖雪峰的网站node.js教程。   直接上代码: 我们首先编写了一个hello.js文件,这个hello.js文件...

weixin_30663471的博客 135

真正了解Javascript闭包

这篇文章将以简单的方式说明Javascript闭包的工作方式。 我们将讨论以下主题和常见问题: 什么是Javascript闭包 “关闭”这个名称的背后是什么原因 实际在调试器中查看闭包 编码时如何推理闭包 使用中最常见的陷阱 一个简单的例子(包括错误) 理解闭包的最简单方法是了解它们试图解决的问题。 让我们以一个简单的代码示例为例,其中一个计数器在循环内递增3次...

danpu0978的博客 1911

javascript深入了解闭包

观看者:不能理解闭包的人,需要对javascript作用域链有了解,如果没有,可以看我上一篇文章javascript深入了解(作用域); 目标:深入了解闭包的原理; 实现方式:代码及相关文字解释,请逐步运行代码并理解里面每句的含义。 闭包:原理是指有权访问另一个函数作用域中的变量的函数。如下面的例子: var name = 'fanlizhi'; funct

荔枝小站 858

深入了解JavaScript闭包

什么是JS闭包? JS闭包是一个难点也是JS的特色,是JS的高级特性。首先我们知道JS运行函数的时候会在内存中开辟一个存储空间,会把函数体内的代码当作字符串一摸一样的放在这个空间中, 把这个空间地址赋值给函数名(变量名),当我们调用函数的时候会根据地址找到这个储存空间,然后执行储存空间里的代码会在内存中开辟一个函数执行空间,在这个执行空间里面进行形参赋值,执行完毕以后,因为JS的垃圾回收机制 这个...

Shura0的博客 250

深入了解JavaScript闭包机制

这篇文章主要介绍了JavaScript闭包机制,针对内嵌函数的变量访问等问题分析了JS的闭包,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参考学习下。如有不足之处,欢迎批评指正。 JavaScript 变量可以是局部变量或全局变量。 私有变量可以用到闭包。 全局变量 函数可以访问是有函数内部定义的变量,如: 实例 function myFunction() { var a ...

weixin_44526331的博客 167

了解js中的闭包

今天我们来谈一谈js中的闭包闭包是js的一个难点也是它的一个特色,是我们必须掌握的js高级特性。 1.什么是闭包? 2.闭包的特点是什么? 3.闭包能干什么? 首先来简述一下什么是闭包,请看下面代码 function a(){ var num = 100 return function b(){ console.log(num); } } var...

weixin_46391646的博客 178

了解 JavaScript 中的闭包

了解 JavaScript 中的闭包 写在前面 闭包这个概念最初接触起来是有一定难度的,但是闭包JavaScript 中的一个特色,很多高级应用都要依靠闭包实现。此篇博客就来了解一下闭包的概念。 作用域链 什么是作用域? 所谓作用域,就是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。 在 ECMAScript 5 版本中不存在块级作用域,也就是说在代码块中的定义的变量是存在全局作用域的,示例代码如下: if (true) { var a = 100; } console.log(a);

一碗周 166

深度了解js闭包形成及其原理

闭包 一说到这个问题,很多人第一印象就是函数里返回一个函数,就是闭包闭包也是被大家广泛讨论的一个问题,但是很多人都是只知其然,不知其所以然。 那么闭包到底是什么?我们放到后面来回答。 作用域与作用域链 作用域 // 案例1 // 全局作用域 函数向上找 var name= '帅哥'; function fn1(){ console.log(name...

欧阳的技术博客 8049

关于js中函数的闭包 深入了解

闭包就是能够读取其他函数内部变量的函数。 即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是说,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必需访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数

小亚美美的博客 6921

了解什么是js闭包

了解什么是js闭包

aaaaaaaa_ewf的博客 2127

深入了解js闭包

#js闭包 ##深入了解函数 在讲解闭包之前,我们先来了解函数。 函数在声明定义的时候,将函数的地址和函数名存储在栈中,在堆中开辟一个函数存储空间,将函数体以字符串形式存入 函数在调用阶段:按照函数名或者变量名找到堆中对应的储存空间,在堆中重新开辟一个函数执行空间,在这空间里进行函数赋值,预解析,把函数存储空间的代码复制到执行空间中执行,执行完后销毁 有了上面的知识,现在我们来学习js的闭包,就...

qq_46266925的博客 183

MATLAB有限元分析与应用.pdf

MATLAB有限元分析与应用.pdf

上一篇: 如何在不同项目中共用前端资源,从此不加班
下一篇: Android与IOS的的兼容总结
悲伤日记
博客等级 码龄7年 154粉丝 91原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值