首页 > 编程语言 >深入理解JavaScript之作用域链与闭包

深入理解JavaScript之作用域链与闭包

时间:2023-05-17 21:03:17浏览次数:38  
标签:闭包 function 函数 作用域 JavaScript var 变量

作用域

作用域是指程序源代码中定义变量的区域。

实际上描述的就是查找变量的范围,作用域必须有的两个功能就是存储变量以及查找变量,作用域就是发挥这两个作用以及更多作用的规则。

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

词法作用域和动态作用域

  • 词法作用域:(静态作用域)函数的作用域在函数定义的时候就决定了。
  • 动态作用域:函数的作用域是在函数调用的时候才决定的。

JavaScript采用的是词法作用域

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域

全局变量:

  1. 生命周期将存在于整个程序之内。
  2. 能被程序中任何函数或者方法访问。
  3. 在 JavaScript 内默认是可以被修改的。

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

显式声明

带有关键字 var 的声明

var winValue = 10;
console.log(window.winValue); // 10
隐式声明

不带有声明关键字的变量,JS 会默认帮你声明一个全局变量

function foo(value) {
    result = value + 1;	 // 没有用 var 声明
    return result;
};
foo(1);	
console.log(window.result);	// 2 <=  挂在了 window全局对象上

函数作用域

函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域。

在函数内部的变量权限称为函数作用域,有以下特点:

  1. 每个函数都有自己的作用域,而且调用一次就会生成新的作用域
  2. 只能在函数内部才能访问,外部是没有权限访问的
  3. 进入函数内部时开启,函数执行完毕后销毁
function bar() {
    var foo = 'test';
}

console.log(foo); // Uncaught ReferenceError: foo is not defined

块级作用域(ES6新增)

凡是由{}符号包裹起来的都是块作用域

for(let i = 0; i < 5; i++) {
    // ...
}
console.log(i); // Uncaught ReferenceError: i is not defined

在 for 循环执行完毕之后 i 变量就被释放了

作用域链

作用域链:当访问一个变量时,解释器会首先在当前作用域查找,如果没有找到,就去父作用域找,直到找到该变量或者不在父作用域中,这就是作用域链。

var a = 1

function foo () {
    var b = 2
    console.log(a)
}

foo() // 1
console.log(b) // Uncaught ReferenceError: b is not defined

从上面代码的执行结果可以看出,foo 函数取到了它外部的变量 a, 而最外层的 console.log(b) 操作并没能取得 foo 函数里面的变量 b。

  • 作用域链和原型继承查找时的区别:

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

闭包

定义

闭包是指有权访问另外一个函数作用域中的变量的函数

在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成"定义在一个函数内部的函数"。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

  • 闭包由两部分构成:
  1. 函数
  2. 能访问另外一个函数作用域中的变量

为什么有闭包?

之所以出现闭包是因为JS的垃圾回收机制,JS本身为了避免解释器过量消耗内存,造成系统崩溃,自带有一套垃圾回收机制,垃圾回收机制能够检测到一个对象是不是无用的。检测到之后,就会把它占用的内存释放掉。但是实际工作中,我们也会需要一些变量不那么及时的被清理,所以就出现了闭包,用来达成这个效果。

闭包的特点

  • 闭包可以访问当前函数以外的变量
  • 即使外部函数已经返回,闭包仍能访问外部函数定义的变量
  • 参数和变量不会被垃圾回收机制收回。
function getOuter(){
    var name = 'jacky';
    function getName(str){
        console.log(str + name);  // 可以访问getName函数外部的name
    }
    return getName('名字是:'); 
}
getOuter(); // 名字是:jacky

再来看下面一段代码:

function bar() {
    var x = 1;
    return function () {
        var y = 2;
        return x + y;
    }
}
var foo = bar(); // 这一句执行完,变量x并没有被回收,因为要内部函数还需要引用
console.log(foo()) // 3  执行内部函数,引用外部变量x

上面代码中,在全局执行上下文中定义了一个函数bar和变量foo,函数bar内部返回一个匿名函数,所以此刻匿名函数的作用域链初始化为包含了全局变量对象和bar中的变量对象。

当执行var foo = bar()时,把函数bar的执行上下文压入栈,当bar执行完后,其执行上下文应该弹出栈,但是因为bar内部的匿名函数作用域链还引用这bar函数内的变量x,所以bar的执行上下文得不到释放,这样就形成了闭包。

闭包的应用

  1. 设计私有的方法和变量(封装,定义模块)。
var counter = (function(){
    var privateCounter = 0; //私有变量
    function change(val){
        privateCounter += val;
    }
    return {
        increment:function(){  
            change(1);
        },
        decrement:function(){
            change(-1);
        },
        value:function(){
            return privateCounter;
        }
    };
})();
  1. 匿名函数最大的用途是创建闭包。减少全局变量的使用。从而使用闭包模块化代码,减少全局变量的污染。
var objEvent = objEvent || {};
(function() {
    var addEvent = function() {
        // some code
    }
    function removeEvent() {
        // some code
    }
    objEvent.addEvent = addEvent
    objEvent.removeEvent = removeEvent
})()

addEvent 和 removeEvent 都是局部变量,但我们可以通过全局变量 objEvent 使用它

如何销毁闭包

javascript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

即释放对闭包的引用,使引用变量为null。

闭包的优缺点

优点:

  1. 闭包里的变量不会污染全局,因为变量被封在闭包里;
  2. 所有变量都在闭包里保证了隐私性和私有性;
  3. 可以让这些局部变量保存在内存中,实现变量数据共享。

缺点:

形成闭包即要把一个函数当成值传递,而且该函数还引用这另一个函数的作用域链使得被引用的函数不能被回收,使用不当容易造成内存泄漏;

标签:闭包,function,函数,作用域,JavaScript,var,变量
From: https://blog.51cto.com/u_16056437/6293730

相关文章

  • JavaScript 使用一个数组对另一个对象数组进行过滤
    JavaScript使用一个数组对另一个对象数组进行过滤假设我们有一个对象数组objs,其中每个对象都有一个name属性,我们希望使用一个数组names对objs数组进行过滤,只保留那些name属性在names数组中的对象。我们可以使用filter()方法来实现这个功能。constobjs=[{id......
  • 学习日记——初识JavaScript
    1.JS的组成和基本结构①JavaScript定义:(1)脚本语言(2)有一定的安全性(3)一种基于对象的一种语言(4)可以定义一堆的事件(方法/函数)并进行调用②组成部分: (1)EcmaScript:核心语法。Js前身(2)Dom(文档对象模型)(3)Bom(浏览器对象模型)③JavaScript基本结构<scripttype="text/javascript">alert();......
  • Javascript执行原理 网页引入javascript的三种方式* javascript核心语法 数据类型 Typ
    Javascript执行原理:用户端发送请求到服务器端将js解析出来的数据(用户身份表示)绑定在请求路径中服务器端获取到参数后会响应客户端客户端通过浏览器解析响应的数据并将数据展现在浏览器上网页引入javascript的三种方式*:使用script标签<scripttype=“text/javascript”>aler......
  • 浅谈Javascript 中几种克隆(clone)方式
    一:在Javascript里,如果克隆对象是基本类型,我们直接赋值就可以了:Js代码varsStr="kingwell";varcStr=sStr;alert(cStr);//输出kingwellsStr="abc";alert(cStr);//输出kingwell; 把一个值赋给另一个变量时,当那个变量的值改变的时候,另一个值不会受到影响。 ......
  • JavaScript——数字超过精度导致数据有误
    前言接口返回的number类型的数据,超过了JavaScript中Number类型的限制,浏览器自动进行了转换;console.log(7232167009634730040)内容以下内容来自ClaudeJavaScript的Number类型可以安全表示的最大整数是2^53-1,也就是9007199254740991。大于这个值的整数在JavaScript......
  • 3:闭包,装饰器,生成器,迭代器
    一:什么是闭包1:必须有一个内部函数2:外部函数返回值内部函数3:内部函数一定要调用外部函数的变量 二:什么是装饰器1:装饰器和闭包的区别闭包传递的是变量,装饰器传递的是函数,可以说装饰器是闭包的一种,它只是传递函数的闭包装饰器本质是一种函数,在原函数上增加新的功能。比......
  • 《JavaScript权威指南第七版》13.3.4实现细节,关于“ES2017解释器可以把函数体分割成一
    读到“ES2017解释器可以把函数体分割成一系列独立的子函数,每个子函数都被传给位于他前面以await标记的那个期约的then方法”这一部分是比较困惑,也没有代码示例,很抽象,不易理解。自己写了个例子来复述一下这段话:functiongetPosts(){returnnewPromise(function(resolve,......
  • Python 的闭包
    闭包是一种特殊的函数,它能够实现类似于函数模板和面向对象的功能.可以实现代码复用:通过函数模板可以实现一类相似功能的函数,在不同的场景中只需要传入不同的参数即可。可以用闭包实现装饰器.defouter_func(x):definner_func(y):returnx+yreturninne......
  • 汉字转换为拼音的JavaScript库的比较
    JSPinyin有提供了两个方法:<依赖mootools>1)一个是将汉字翻译为拼音,其中每一个字的首字母大写;1pinyin.getFullChars(this.value);2)一个是可以将每一个字的拼音的首字母提取出来,是大写的形式。1pinyin.getCamelChars(this.value);还可以设置是否判断多音字。只是功能比较简单,如......
  • 21、闭包
    1.闭包的概念Go语言中支持将一个函数作为另一个函数的返回值,这样就形成了闭包的结构闭包(closure)一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数或者外层函数中直接定义的变量),并且该外层函数的返回值就是这个内层函数这个内层函数和外城函......