Rumah > hujung hadapan web > tutorial js > Mari kita bincangkan tentang penutupan JavaScript (perkongsian ringkasan)

Mari kita bincangkan tentang penutupan JavaScript (perkongsian ringkasan)

WBOY
Lepaskan: 2022-02-17 18:38:18
ke hadapan
1877 orang telah melayarinya

Artikel ini membawa anda pengetahuan yang berkaitan tentang penutupan dalam JavaScript, termasuk melihat penutupan dari perspektif timbunan, penutupan isu pembolehubah yang dikongsi dan isu lain yang berkaitan saya harap ia akan membantu semua orang.

Mari kita bincangkan tentang penutupan JavaScript (perkongsian ringkasan)

1 Penutupan penutupan sendiri

Konsep penutupan:

Hasilnya dikembalikan selepas pelaksanaan fungsi ialah An. fungsi dalaman dirujuk oleh pembolehubah luaran Jika fungsi dalaman memegang pembolehubah dalam skop fungsi yang dilaksanakan, penutupan terbentuk. Skop fungsi luaran boleh diakses daripada fungsi dalaman.

Menggunakan penutupan, anda boleh membaca pembolehubah dalam fungsi dan menyimpan pembolehubah dalam fungsi dalam ingatan untuk melindungi pembolehubah daripada tercemar. Oleh kerana penutupan menyimpan nilai pembolehubah dalam fungsi dalam memori dan menggunakan memori, penutupan tidak boleh disalahgunakan, jika tidak, ia akan menjejaskan prestasi web dan menyebabkan kebocoran memori. Apabila tidak ada keperluan untuk menggunakan penutupan, untuk melepaskan memori dalam masa, anda boleh menetapkan null kepada pembolehubah objek fungsi dalaman.

Ciri penutupan: Berbilang ruang memori penutupan yang dijana oleh fungsi luaran adalah bebas antara satu sama lain.

Senario aplikasi penutupan:

  1. Kekalkan pembolehubah dalam ingatan: jika data dicache, kari
  2. Lindungi keselamatan pembolehubah dalam fungsi: seperti iterator, penjana.

Kelemahan: Penutupan akan menyebabkan rantai skop asal tidak dilepaskan, menyebabkan kebocoran memori.

  1. Penggunaan memori mempunyai kesan negatif. Kerana fungsi dalaman menyimpan rujukan kepada pembolehubah luaran, ia tidak boleh dikumpul sampah dan meningkatkan penggunaan memori Oleh itu, penggunaan yang tidak betul akan membawa kepada kebocoran memori
  2. . memberi kesan negatif pada kelajuan pemprosesan. Tahap penutupan menentukan panjang rantai skop yang dilalui oleh pembolehubah luaran yang dirujuk semasa mencari
  3. Anda mungkin mendapat nilai yang tidak dijangka( nilai yang ditangkap)

Kelebihan:

  1. boleh diakses dari skop fungsi luaran daripada fungsi dalaman pembolehubah, dan pembolehubah yang diakses adalah kekal dalam ingatan dan boleh digunakan kemudian
  2. Elakkan pembolehubah mencemarkan dunia global Disimpan dalam skop yang berasingan, wujud sebagai ahli peribadi <. . gabungan adalah penutup. dengan kata lain penutupan membolehkan anda mengakses skop fungsi luar dari dalam dalam. javascript setiap kali dicipta dibuat pada masa yang sama dicipta.>
  3. Skop leksikal
Sila lihat kod berikut:

init() mencipta nama pembolehubah tempatan dan pembolehubah bernama displayName ( ) fungsi. displayName() ialah fungsi dalaman yang ditakrifkan dalam init() dan hanya tersedia dalam badan fungsi init(). Ambil perhatian bahawa displayName() tidak mempunyai pembolehubah setempatnya sendiri. Walau bagaimanapun, kerana ia mempunyai akses kepada pembolehubah fungsi luaran, displayName() boleh menggunakan nama pembolehubah yang diisytiharkan dalam fungsi induk init() .

Selepas menjalankan kod menggunakan pautan JSFiddle ini, kami mendapati pernyataan alert() dalam fungsi displayName() berjaya memaparkan nilai nama pembolehubah (yang diisytiharkan dalam fungsi induknya). Contoh skop leksikal ini menerangkan cara penghurai menyelesaikan nama pembolehubah apabila fungsi bersarang. Istilah leksikal merujuk kepada fakta bahawa skop leksikal menentukan tempat pembolehubah tersedia berdasarkan tempat dalam kod sumber ia diisytiharkan. Fungsi bersarang boleh mengakses pembolehubah yang diisytiharkan dalam skop luarnya.

3. Lihat penutupan dari perspektif timbunan

Nilai pembolehubah jenis data asas umumnya wujud dalam jenis data asas: Nombor, Boolean, Tidak ditentukan , Rentetan, Null. ;Nilai pembolehubah jenis objek disimpan dalam memori timbunan, dan memori tindanan menyimpan alamat ruang yang sepadan.

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

Storan memori yang sepadan:


Apabila kita melaksanakan b={m:30}, terdapat objek baharu {m dalam ingatan timbunan : 30}, b dalam ingatan tindanan menghala ke alamat ruang baharu (menunjuk ke {m: 30}), dan {m: 20} asal dalam ingatan timbunan akan menjadi sampah yang dikumpul oleh enjin program, menjimatkan ruang memori. Kita tahu bahawa fungsi js juga merupakan objek, dan ia juga disimpan dalam memori timbunan dan tindanan Mari kita lihat pada penukaran:

var a = 1 
//a是一个基本数据类型 
var b = {m: 20 } 
//b是一个对象
Salin selepas log masuk

<.>Timbunan ialah Struktur data yang pertama-masuk-akhir:

  1. 在执行fn前,此时我们在全局执行环境(浏览器就是window作用域),全局作用域里有个变量a;
  2. 进入fn,此时栈内存就会push一个fn的执行环境,这个环境里有变量b和函数对象fn1,这里可以访问自身执行环境和全局执行环境所定义的变量
  3. 进入fn1,此时栈内存就会push 一个fn1的执行环境,这里面没有定义其他变量,但是我们可以访问到fn和全局执行环境里面的变量,因为程序在访问变量时,是向底层栈一个个找(这就是Javascript语言特有的"链式作用域"结构(chain scope),如果找到全局执行环境里都没有对应变量,则程序抛出underfined的错误。
  4. 随着fn1()执行完毕,fn1的执行环境被杯销毁,接着执行完fn(),fn的执行环境也会被销毁,只剩全局的执行环境下,现在没有b变量,和fn1函数对象了,只有a 和 fn(函数声明作用域是window下)

在函数内访问某个变量是根据函数作用域链来判断变量是否存在的,而函数作用域链是程序根据函数所在的执行环境栈来初始化的,所以上面的例子,我们在fn1里面打印变量b,根据fn1的作用域链的找到对应fn执行环境下的变量b。所以当程序在调用某个函数时,做了一下的工作:准备执行环境,初始函数作用域链和arguments参数对象

我们现在看下闭包例子

function outer() {
     var  a = '变量1'
     var  inner = function () {
            console.info(a)
     }
    return inner    // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var  inner = outer()   // 获得inner闭包函数
inner()   //"变量1"
Salin selepas log masuk

当程序执行完var inner = outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被被inner的函数作用域链所引用,当程序执行完inner(), 这时候,inner和outer的执行环境才会被销毁调;《JavaScript高级编程》书中建议:由于闭包会携带包含它的函数的作用域,因为会比其他函数占用更多内容,过度使用闭包,会导致内存占用过多。

4.闭包的共享变量问题

下面通过outer外函数和inner内函数来讲解闭包的共享变量问题。

同一个外函数生成的多个闭包是独立空间还是共享空间如何判断?请先看实例

//第一种情况 调用时给外函数传入变量值
function outer(name){
    return function(){
        console.log(name)
    }
}

f1 = outer('yang')
f2 = outer('fang')

console.log(f1.toString())
f1() //yang 
f2() //fang
f1() //yang 

//第二种情况:外函数局部变量值为变化
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];  //16
var f2 = results[1];  //16
var f3 = results[2];  //16
console.log(f1 )

//第三种情况:外函数的局部变量值变化。
function test(){
    var i = 0;
    return function(){
       console.log(i++)
    }
}; 
var a = test();
var b = test();
//依次执行a,a,b,控制台会输出什么呢?0 1 0  
//b为什么不是2
a();a();b();
Salin selepas log masuk

同一个外函数生成的多个闭包是独立空间还是共享空间如何判断?

  1. 第一种情况说明多次调用外函数生成的不同闭包函数没有共享name变量
  2. 第二种情况说明外函数内部循环生成的多个内函数共享 i 局部变量
  3. 第三种情况说明a 、b为两个 不同闭包 函数,同一闭包函数 a 多次调用 共享 i 变量,a b之间不共享。

可以总结出记住三个闭包共享变量的原则

  1. 调用外函数,就会生成内函数和外函数的局部变量组成的闭包。每调用一次生成一个闭包函数。不同闭包函数之间内存空间彼此独立。
  2. 调用同一个闭包函数多次,共享内存空间,即外函数的局部变量值。
  3. 第二种情况for循环。没有调用外函数,只是将内函数存到了数组中,故并没有生成3个独立的闭包函数。而是3个内函数共享一个外函数局部变量,即3个内函数和外函数局部变量组成了一个整体的闭包环境。

简记:调用一次外函数,生成一个独立的闭包环境;外函数内部生成多个内函数,那么多个内函数共用一个闭包环境。


5.闭包应用场景

应用场景主要就两个

  • 在内存中维持变量:如果缓存数据、柯里化  
  • 保护函数内的变量安全:如迭代器、生成器。

场景一:保存局部变量在内存中

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

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

在 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;
}
Salin selepas log masuk

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

以下是 JavaScript:

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + &#39;px&#39;;
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
Salin selepas log masuk

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

document.getElementById(&#39;size-12&#39;).onclick = size12;
document.getElementById(&#39;size-14&#39;).onclick = size14;
document.getElementById(&#39;size-16&#39;).onclick = size16;
<a href="#" id="size-12">12</a>
<a href="#" id="size-14">14</a>
<a href="#" id="size-16">16</a>
Salin selepas log masuk

场景二:用闭包模拟私有方法,保护局部变量

编程语言中,比如 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 */
Salin selepas log masuk

可以将上面的代码拆分成两部分:(function(){}) 和 () 。第1个() 是一个表达式,而这个表达式本身是一个匿名函数,所以在这个表达式后面加 () 就表示执行这个匿名函数。

在之前的示例中,每个闭包都有它自己的词法环境;而这次我们只创建了一个词法环境,为三个函数所共享:Counter.increment,Counter.decrement 和 Counter.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 */
Salin selepas log masuk

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

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

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


6.循环中创建闭包的一个常见错误

在 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();
Salin selepas log masuk
//一、将function直接返回,会发生闭包
 //二、将函数赋值给一个变量,此变量函数外部使用,此时也是闭包。比如,数组、多个变量等。 举例下面也是闭包情况。
 var arr = []
 for (var i = 0; i < 10; i++) {
    arr[i] = function(){console.log(i)}
  }
  arr[6]()此时也是闭包,将十个匿名函数+i组成了一个闭包返回。
Salin selepas log masuk

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

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

原因是赋值给 onfocus 的是闭包。这些闭包是由他们的函数定义和在 setupHelp 作用域中捕获的环境所组成的。这三个闭包在循环中被创建,但他们共享了同一个词法作用域,在这个作用域中存在一个变量item。这是因为变量item使用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();
Salin selepas log masuk

这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境, 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();
Salin selepas log masuk

如果不想使用过多的闭包,你可以用ES2015引入的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();
Salin selepas log masuk

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

另一个可选方案是使用 forEach()来遍历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)'}
    ];

  helpText.forEach(function(text) {
    document.getElementById(text.id).onfocus = function() {
      showHelp(text.help);
    }
  });
}

setupHelp();
Salin selepas log masuk

7.性能考量

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

但是如果某个函数需要不停新建,那么使用闭包保存到内存中对性能有好处。

释放闭包只需要将引用闭包的函数置为null即可。


8.闭包注意事项

第一:多个内函数引用同一局部变量

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function () {
            console.info(i)
        }
     }
     return result
}
Salin selepas log masuk

看样子result每个闭包函数对打印对应数字,1,2,3,4,...,10, 实际不是,因为每个闭包函数访问变量i是outer执行环境下的变量i,随着循环的结束,i已经变成10了,所以执行每个闭包函数,结果打印10, 10, ..., 10
怎么解决这个问题呢?

function outer() {
      var result = [];
      for (var i = 0; i<10; i++){
        result.[i] = function (num) {
             return function() {
                   console.info(num);    // 此时访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样
             }
        }(i)
     }
     return result
}
Salin selepas log masuk

第二: this指向问题

var object = {
     name: ''object",
     getName: function() {
        return function() {
             console.info(this.name)
        }
    }
}
object.getName()()    // underfined
// 因为里面的闭包函数是在window作用域下执行的,也就是说,this指向windows
Salin selepas log masuk

第三:内存泄露问题

function  showId() {
    var el = document.getElementById("app")
    el.onclick = function(){
      aler(el.id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
}

// 改成下面
function  showId() {
    var el = document.getElementById("app")
    var id  = el.id
    el.onclick = function(){
      aler(id)   // 这样会导致闭包引用外层的el,当执行完showId后,el无法释放
    }
    el = null    // 主动释放el
}
Salin selepas log masuk

相关推荐:javascript学习教程

Atas ialah kandungan terperinci Mari kita bincangkan tentang penutupan JavaScript (perkongsian ringkasan). Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Label berkaitan:
sumber:csdn.net
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan