JavaScript闭包原理及作用详解

目录
  • 简介
  • 闭包的用途
    • 柯里化
    • 实现公有变量
    • 缓存
    • 封装(属性私有化)
  • 闭包的原理
  • 垃圾收集
    • 简介
    • 实际开发中的优化

简介

说明

本文介绍JavaScript的闭包的作用、用途及其原理。

闭包的定义

闭包是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函

数被返回(寿命终结)了之后。

闭包的作用(特点)

1.函数嵌套函数

2.内部函数可以引用外部函数的参数或者变量

3.外部函数的参数和变量不会被垃圾回收,因为被内部函数引用。

闭包与全局变量

闭包的用途

柯里化

可以通过参数来生成不同的函数。

function makeWelcome(x) {
	return function(y) {
		return x + y;
	};
}

let sayHello = makeWelcome("Hello,");
let sayHi = makeWelcome("Hi,");

console.log(sayHello("Tony"));
console.log(sayHi("Tony"));

结果

Hello,Tony

Hi,Tony

实现公有变量

需求:实现一个累加器,每次调用就增加一次。

function makeCounter(){
	let count = 0;
	function innerFunction(){
		return count++;
	}
	return innerFunction;
}
let counter = makeCounter();

console.log(counter());
console.log(counter());
console.log(counter());

结果

0

1

2

缓存

设想有一个处理过程很耗时的函数对象,可以将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找。如果找不到,则进行计算,然后更新缓存并返回值;如果找到了,直接返回查找到的值即可。

闭包可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

本处为了简单,直接写读写缓存的示例。(而不是读不到再计算,然后存到缓存)。

let cache = function () {
	// Map允许键为任意类型。如果这么写:let storage = {},则键只能为字符串
	let storage = new Map();
	return {
		setCache: function (k, v) {
			storage[k] = v;
		},
		getCache: function (k) {
			return storage[k];
		},
		deleteCache: function (k) {
			delete storage[k];
		}
	}
}();

cache.setCache('a', 1);
console.log(cache.getCache('a'))

结果

1

封装(属性私有化)

只能通过提供的闭包的形式来访问内部变量。(此法不好,建议使用原型链)。

let person = function(){
	//变量作用域为函数内部,外部无法访问
	let name = "defaultName";

	return {
		getName: function(){
			return name;
		},
		setName: function(newName){
			name = newName;
		}
	}
}();

console.log(person.name);
console.log(person.getName());
person.setName("Hello");
console.log(person.getName());

结果

undefined

defaultName

Hello

闭包的原理

以计数器为例:

function makeCounter() {
	let count = 0;
	return function() {
		return count++;
	};
}
let counter = makeCounter();
console.log(counter());
console.log(counter());
console.log(counter());

结果

0

1

2

每次 makeCounter() 调用的开始,都会创建一个新的词法环境对象,以存储该makeCounter 运行时的变量。

因此,我们有两层嵌套的词法环境:

在执行 makeCounter() 的过程中创建了一个仅占一行的嵌套函数: return count++ 。我们尚未运行它,仅创建了它。

所有的函数在“诞生”时都会记住创建它们的词法环境。原理:所有函数都有名为 [[Environment]] 的隐藏属性,该属性保存了对创建该函数的词法环境的引用:

因此, counter.[[Environment]] 有对 {count: 0} 词法环境的引用。这就是函数记住它创建于何处的方式,与函数被在哪儿调用无关。 [[Environment]] 引用在函数创建时被设置并永久保存。

稍后,当调用 counter() 时,会为该调用创建一个新的词法环境,并且其外部词法环境引用获取于 counter.[[Environment]] :

现在,当 counter() 中的代码查找 count 变量时,它首先搜索自己的词法环境(为空,因为那里没有局部变量),然后是外部 makeCounter() 的词法环境,并且在哪里找到就在哪里修

改(在变量所在的词法环境中更新变量)。

这是执行后的状态:

如果我们调用 counter() 多次, count 变量将在同一位置增加到 2, 3等。

垃圾收集

简介

通常,函数调用完成后,会将词法环境和其中的所有变量从内存中删除,因为现在没有任何对它们的引用了。

与 JavaScript 中的任何其他对象一样,词法环境仅在可达时才会被保留在内存中。但是,如果有一个嵌套函数在函数结束后仍可达,则它具有引用词法环境的[[Environment]] 属性。

如果在函数执行完成后,词法环境仍然可达,则此嵌套函数仍然有效。例如:

function f() {
    let value = 123;
    return function() {
        alert(value);
    }
}
// g.[[Environment]] 存储了对相应 f() 调用的词法环境的引用
let g = f();

如果多次调用 f() ,并且返回的函数被保存,那么所有相应的词法环境对象也会保留在内存中。例如:

function f() {
    let value = Math.random();
    return function () {
        alert(value);
    };
}

// 数组中的 3 个函数,每个都与来自对应的 f() 的词法环境相关联
let arr = [f(), f(), f()];

当词法环境对象变得不可达时,它就会死去(就像其他任何对象一样)。换句话说,它仅在至少有一个嵌套函数引用它时才存在。

在下面的代码中,嵌套函数被删除后,其封闭的词法环境(以及其中的 value )也会被从内存中删除:

function f() {
    let value = 123;
    return function() {
        alert(value);
    }
}
let g = f(); // 当 g 函数存在时,该值会被保留在内存中
g = null;    // 现在内存被清理了

实际开发中的优化

正如我们所看到的,理论上当函数可达时,它外部的所有变量也都将存在。但在实际中,JavaScript 引擎会试图优化它。它们会分析变量的使用情况,如果从代码中可以明显看出有未使用的外部变量,那么就会将其删除。

V8(Chrome,Opera)的一个重要的副作用是,此类变量在调试中将不可用。

打开 Chrome 浏览器的开发者工具,并尝试运行下面的代码。

    function f() {
        let value = Math.random();
        function g() {
            debugger;
        }
        return g;
    }
    let g = f();
    g();

当代码执行到“debugger;”这个地方时会暂停,此时在控制台中输入 console.log(value);。

结果:报错:VM146:1 Uncaught ReferenceError: value is not defined

这可能会导致有趣的调试问题。比如:我们可以看到的是一个同名的外部变量,而不是预期的变量:

let value = "Surprise!";
function f() {
    let value = "the closest value";
    function g() {
        debugger;
    }
    return g;
}
let g = f();
g();

当代码执行到“debugger;”这个地方时会暂停,此时在控制台中输入 console.log(value);。

结果:输出:Surprise。 

以上就是JavaScript闭包原理及作用详解的详细内容,更多关于JavaScript闭包的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS闭包原理及其使用场景解析

    闭包定义 可以通过内层函数访问外层函数的作用域的组合叫做闭包. 闭包使用场景 使用闭包来实现防抖 function debounce(callback, time) { var timer; return function () { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { callback() }, time) } }<br data-filtered="filtered"><b

  • js闭包和垃圾回收机制示例详解

    前言 闭包和垃圾回收机制常常作为前端学习开发中的难点,也经常在面试中遇到这样的问题,本文记录一下在学习工作中关于这方面的笔记. 正文 1.闭包 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.作为一个JavaScript开发者,理解闭包十分重要. 1.1闭包是什么? 闭包就是一个函数引用另一个函数的变量,内部函数被返回到外部并保存时产生,(内部函数的作用域链AO使用了外层函数的AO) 因为变量被引用着所以不会被回收,因此可以用来封装一个私有

  • 关于Javascript闭包与应用的详解

    前言 Javascript闭包在学习过程中一般较难理解,本文从什么是闭包,常见闭包示例,闭包作用,闭包应用及闭包问题等方面来介绍闭包,希望能给大家带来更深层次的认识,有不恰当之处请指出,谢谢. 一.什么是闭包? 闭包是指一个嵌套的内部(子)函数引用了父函数作用域中数据的函数,这就产生了闭包. 关键理解: 1. 产生闭包必须要有嵌套函数 2. 闭包是函数,并是嵌套的内部函数 3. 闭包内部函数必须要引用父函数作用域中数据 如果不满足以上条件,则不能产生闭包,接下来示例说明. 1.1闭包满足条件代码

  • JavaScript高级之闭包详解

    目录 1. 闭包的概念 知识点的补充: 2. 闭包的作用: 3. 闭包示例 3.1 点击li,输出当前li的索引号 总结 1. 闭包的概念 来看一般函数的执行和启发: function stop() { var num = 0; console.log(num); } stop(); // 打印出来num是0 console.log(num); // 报错 函数未定义 1. 此时,函数的外部无法访问函数内部的变量 2. 函数内部定义的变量不会一直存在,随着函数的运行结束而消失 闭包的概念: 1.

  • 详解JavaScript闭包问题

    闭包是纯函数式编程语言的传统特性之一.通过将闭包视为核心语言构件的组成部分,JavaScript语言展示了其与函数式编程语言的紧密联系.由于能够简化复杂的操作,闭包在主流JavaScript库以及高水平产品代码中日益流行起来. 一.变量的作用域 在介绍闭包之前,我们先理解JavaScript的变量作用域.变量的作用域分为两种:全局变量和局部变量. 1.全局变量 var n = 999; //全局变量 function f1() { a = 100; //在这里a也是全局变量 alert(n);

  • Javascript作用域与闭包详情

    目录 1.作用域 2.作用域链 3.词法作用域 5.闭包的应用 6.闭包的缺陷 7.高频闭包面试题 1.作用域 简单来说,作用域是指程序中定义变量的区域,它决定了当前执行代码对变量的访问权限 在ES5中,一般只有两种作用域类型: 全局作用域:全局作用域作为程序的最外层作用域,一直存在 函数作用域:函数作用域只有在函数被定义时才会被创建,包含在父级函数作用域或全局作用域中 说完概念,我们来看下面这段代码: var a = 100 function test(){ var b = a * 2 var

  • JavaScript闭包原理及作用详解

    目录 简介 闭包的用途 柯里化 实现公有变量 缓存 封装(属性私有化) 闭包的原理 垃圾收集 简介 实际开发中的优化 简介 说明 本文介绍JavaScript的闭包的作用.用途及其原理. 闭包的定义 闭包是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函 数被返回(寿命终结)了之后. 闭包的作用(特点) 1.函数嵌套函数 2.内部函数可以引用外部函数的参数或者变量 3.外部函数的参数和变量不会被垃圾回收,因为被内部函数引用. 闭包与全局变量 闭包的用途 柯里化 可以通过参

  • JavaScript进阶(三)闭包原理与用法详解

    本文实例讲述了JavaScript闭包原理与用法.分享给大家供大家参考,具体如下: 为了更好的理解,在阅读此文之前建议先阅读上一篇<JavaScript词法作用域与作用域链> 1.什么是闭包 闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构.所谓的闭包就是一个具有封闭的对外不公开的,包裹结构,或空间. 在JS中函数构成闭包.一般函数是一个代码结构的封闭结构,即包裹的特性,同时根据作用域规则只允许函数访问外部的数据,外部无法访问函数内部的数据,即封闭的对外不公开的特性

  • Vue 列表渲染 key的原理和作用详解

    目录 列表渲染 key 的原理和作用 key的原理分析 key的作用 总结 列表渲染 key 的原理和作用 key就是为该节点做身份标识,如果对key绑定index的值,那么很容易出现问题: 1.若对数据进行:逆序添加.逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新--页面效果没有问题,但是效率有2问题 2.如果解构中还包含输入类的DOM,会产生错误DOM更新--界面有问题 问题案例分析: 点击按钮,在列表的前面添加一个对象 <div id="root"> <

  • JavaScript闭包和范围实例详解

    本文实例分析了JavaScript闭包和范围.分享给大家供大家参考,具体如下: 1. if (!("a" in window)) { var a = 1; } alert(a); [分析]代码含义:如果window不包含属性a,就声明一个变量a并赋值为1 ①JS引擎会先扫描所有的变量声明 ②所有的全局变量都是window的属性 ③变量声明和赋值一起用时,Js引擎会自动将它分成两部分:变量声明提前,变量赋值没有(不将赋值提前是因为他有可能影响代码执行出不可预期的结果) 所以代码执行顺序等

  • Javascript闭包(Closure)详解

    下面就是我的学习笔记,对于Javascript初学者应该是很有用的. 一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量. var n=999; function f1(){ alert(n); } f1(); // 999 另一方面,在函数外部自然无法读取函数内的局部变量. function f1(){ var n=999; } alert(n)

  • JavaScript运动原理基础知识详解

    在这篇文章里,我将把JS的运动由简如深的进行分析: 运动基础 主要步骤为: 清除定时器,保证运动过程中只有一个定时器 开启定时器 开始运动,同时加入判断以便在需要时停止运动.将移动函数进行了简单的封装. <script type='text/x-handlebars-template' id='list-item'> {{#if items}} <ul id='mylist'> {{#each items}} <li><a href='{{url}}'>{{

  • JavaScript中闭包的写法和作用详解

    1.什么是闭包 闭包是有权访问另一个函数作用域的变量的函数. 简单的说,Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内.而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量.参数和声明的其他内部函数.当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包. 2.变量的作用域 要理解闭包,首先要理解变量的作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变

  • JavaScript注入漏洞的原理及防范(详解)

    初次接触: 初次接触JavaScript注入漏洞后,如果不对这种漏洞的作用机理仔细分析并提取出其发生的某种模式,你就不能做到快速的发现项目中可能存在的所有注入风险并在代码中防范. 发生模式: JavaScript注入漏洞能发生作用主要依赖两个关键的动作,一个是用户要能从界面中注入JavaScript到系统的内存或者后台存储系统中:二是系统中存在一些UI会展示用户注入的数据. 比如注入漏洞最常见的就是发生在各种类型的名字中,比如系统中的人名等等,因为名字往往会在各种系统上显示,如果在某个用户输入名

  • JavaScript this的原理以及指向详解

    怎么判断this指向? ①在全局环境中调用就指向window. ②作为对象的方法调用就指向该对象. ③作为构造函数调用就指向这个新创建的对象. ④可以使用apply,call,bind改变this指向. ⑤箭头函数中的this与定义时所处的上下文绑定,且不能被改变, 箭头函数this指向取决于它外层找到的离它最近的第一个非箭头函数的this. 怎么理解this原理? JavaScript语言学懂需要理解下面两种写法 var obj = { foo: function () {} }; var f

随机推荐