你真的了解JavaScript的作用域与闭包吗

目录
  • 一、作用域
  • 二、闭包
  • 总结

一、作用域

1.作用域总体来说就是根据名称查找变量的一套规则。JS查找变量的方式有两种:LHS和RHS。

LHS(left hand side)大致可以理解为给某个变量赋值,在赋值符(=)的左边;RHS(right hand side)是指找到某个变量的源值,如在赋值符(=)的右边。

举个例子说明,对于 a = 10; a = b; 这两个语句。

var a 这里就是LHS查询,查找当前作用域是否有a这个变量来给a进行赋值,如果有则进行赋值;如果没有,在非严格模式下JS引擎会自动创建一个变量a来进行赋值;在严格模式下JS引擎会抛出ReferenceError错误。

a = b;这个语句,对于b这个变量是进行RHS查询,查找b这个变量的值。如果成功查询,则进行赋值操作;如果查询失败,则抛出ReferenceError错误。如果查询成功,但是对这个变量的操作有问题,就会抛出TypeError错误。如b只是一个普通的变量,却当成函数使用(b())。

2.JS是基于词法作用域的。指在你写代码时将变量和函数写在哪里决定的,而不是调用顺序决定的。

举个例子

var num = 1;
function a() {
    console.log(num); // 1
}
function b() {
    var num = 2;
    a();
}
b();

当按照顺序执行b()时,在b函数作用域中定义了一个值为2的num变量,然后在b函数中调用a函数,来到a函数的作用域进行调用(而不是在b函数作用域),找到全局变量num = 1。

3.不同作用域间是互相不会影响的,重复定义变量是不会发生错误的。

举个例子

var i = 1;
var j = 2;
function a(){
    var i = 3;
    var j = 4;
    console.log(i,j); // 3,4
}
function b(){
    var i = 5;
    var j = 6;
    console.log(i,j); // 5,6
}
console.log(i,j);// 1,2
a();
b();

4.for循环中,var声明的变量在全局作用域能够访问,并且值是退出循环的那个值;let声明的变量会产生块作用域,它将值重新绑定到循环迭代中。

for(var i = 0;i<5;i++){
    console.log(i); // 0 1 2 3 4
}
console.log(i); // 5
 
for(let i = 0;i<5;i++){
    console.log(i); // 0 1 2 3 4
}
console.log(i); // ReferenceError

5.每个作用域都会发生提升操作;函数声明会发生提升,但是函数表达式不会被提升;

b() // TypeErrorn() // ReferenceError,因为不会变量提升// 函数声明function a(){    console.log(num); // undefined    var num = 1;}// 函数表达式var b = function n(){    console.log('b');}

6.函数声明首先被提升,变量声明在后;

函数首先被提升可以认为是函数的优先级高于变量,重复声明的变量会被函数替代。但是其他函数声明可以覆盖之前的函数声明。

b() // TypeError
n() // ReferenceError,因为不会变量提升
// 函数声明
function a(){
    console.log(num); // undefined
    var num = 1;
}
// 函数表达式
var b = function n(){
    console.log('b');
}

二、闭包

1.闭包是基于词法作用域书写代码时的必然结果。

不论函数在哪里执行,函数都会在代码书写的位置(即函数本身在哪)来进行作用域链的查找。当函数进行作用域查找时,就形成了闭包。

也可以说是当函数被当作值进行传递的时候(如参数传递、返回值等),就会形成闭包。

举个例子

function foo() {
    var a = 2;
    function bar() {
        console.log(a);
    }
    return bar;
}
var a = 3;
var baz = foo();
baz(); // 2 

当foo函数执行时,会返回bar函数,当bar函数在全局作用域执行时,会沿着bar函数定义的地方开始进行RHS查找a变量的值。这时就产生了闭包。闭包的产生准确来说应该是函数在函数本身所处作用域之外执行,对于IIFE(立即执行函数)来说虽然创建了闭包,但是却没有真正使用闭包,因为立即执行函数的执行作用域是函数定义的作用域,虽然是闭包,但是没有使用闭包的功能。

闭包的功能:函数在所处作用域之外执行时,会沿着函数定义时的作用域链进行查找。

var a = 1;
// 立即执行函数
(
    function(){
        console.log(a);
     }
)();

2.循环与闭包

观察下面的例子

for(var i = 0;i<3;i++){
    setTimeout(()=>{
        console.log(i);
    },i*500)
}

输出结果为 3 3 3,并不是想象中的 0 1 2。因为在for循环结束时,setTimeout开始执行,在执行的时候进行变量 i 的RHS查询,但是变量 i 只能在全局作用域中查到,这是变量 i 的值是退出循环的值 i = 3;

可能与你的想象结果有些许不同,如果要输出 0 1 2 该怎么做?

(1).使用IIFE形成闭包是否可行?

for (var i = 0; i < 3; i++) {
    (function () {
        setTimeout(() => {
            console.log(i);
        }, i*500)
    })()
}

结果依然是 3 3 3。因为虽然使用IIFE形成了闭包,但是在闭包中并没有 i 的值,还会沿着作用域链查找到全局 i = 3;

(2).在IIFE使用一个变量临时保存 i 值或传参

for (var i = 0; i < 3; i++) {
    (function () {
        var j = i;
        setTimeout(() => {
            console.log(j);
        }, i*500)
    })()
}

// 或
for (var i = 0; i < 3; i++) {
    (function (i) {
        setTimeout(() => {
            console.log(i);
        }, i*500)
    })(i)
}

结果为 0 1 2,第一个for循环闭包中有变量 j ,第二个for循环有形参 i ,都不会到达全局作用域。

(3).使用 let 生成块作用域

for(let i = 0;i<3;i++){
    setTimeout(()=>{
        console.log(i);
    },i*500)
}

结果为 0 1 2,let声明的变量会包含在块作用域中,并且作为循环变量时,每一次的迭代都会声明一次变量并将变量结果保存,当访问变量时,由于闭包的作用,会得到每次迭代的值。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • JavaScript闭包与作用域链实例分析

    本文实例讲述了JavaScript闭包与作用域链.分享给大家供大家参考,具体如下: 闭包定义 闭包指的是有权访问另一个函数作用域中的变量的函数.创建闭包的常见方式,就是在一个函数A内部创建另一个函数B,那么函数B就是一个闭包,可以访问函数A作用域中的所有变量. JavaScript的闭包与作用域链密不可分,因此本文可以和JavaScript的作用域链相对照分析,一定可以对JavaScript的闭包和作用域链有更深的理解. 下面我们仍然以createComparisonFunction为例进行闭包

  • 浅谈JavaScript作用域和闭包

    作用域和闭包在JavaScript里非常重要.但是在我最初学习JavaScript的时候,却很难理解.这篇文章会用一些例子帮你理解它们. 我们先从作用域开始. 作用域 JavaScript的作用域限定了你可以访问哪些变量.有两种作用域:全局作用域,局部作用域. 全局作用域 在所有函数声明或者大括号之外定义的变量,都在全局作用域里. 不过这个规则只在浏览器中运行的JavaScript里有效.如果你在Node.js里,那么全局作用域里的变量就不一样了,不过这篇文章不讨论Node.js. `const

  • 详解JavaScript作用域 闭包

    JavaScript闭包,是JS开发工程师必须深入了解的知识.3月份自己曾撰写博客<JavaScript闭包>,博客中只是简单阐述了闭包的工作过程和列举了几个示例,并没有去刨根问底,将其弄明白! 现在随着对JavaScript更深入的了解,也刚读完<你不知道的JavaScript(上卷)>这本书,所以乘机整理下,从底层和原理上去刨一下. JavaScript并不具有动态作用域,它只有词法作用域.词法作用域是在写代码或者说定义时确定的,而动态作用域是在运行时确定的.了解闭包前,首先我

  • Javascript作用域与闭包详情

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

  • 深入了解JS之作用域和闭包

    作用域和闭包 ECMAScript5: JS 的代码没有代码块:使用函数运行的机制进行创建闭包:闭包就是作用域的意思: ES5中,JS中只有函数才可以创建能操作的作用域: JavaScript中的内存也分为栈内存和堆内存.一般来说,栈内存中存放的是存储对象的地址,而堆内存中存放的是存储对象的具体内容.对于原始类型的值而言,其地址和具体内容都存在与栈内存中:而基于引用类型的值,其地址存在栈内存,其具体内容存在堆内存中.堆内存与栈内存是有区别的,栈内存运行效率比堆内存高,空间相对推内存来说较小,反之

  • 详解JavaScript作用域、作用域链和闭包的用法

    1. 作用域 作用域是指可访问的变量和函数的集合. 作用域可分为全局作用域和局部作用域. 1.1 全局作用域 全局作用域是指最外层函数外面定义的变量和函数的集合. 换言之,这些最外层函数外面定义的变量和函数在任何地方都能访问. 举个例子: // 最外层定义变量 var a = 1; console.log(a); // 最外层可以访问 function fnOne() { // 最外层函数 console.log(a); // 函数内可以访问 function fnTwo() { // 子函数

  • 你真的了解JavaScript的作用域与闭包吗

    目录 一.作用域 二.闭包 总结 一.作用域 1.作用域总体来说就是根据名称查找变量的一套规则.JS查找变量的方式有两种:LHS和RHS. LHS(left hand side)大致可以理解为给某个变量赋值,在赋值符(=)的左边:RHS(right hand side)是指找到某个变量的源值,如在赋值符(=)的右边. 举个例子说明,对于 a = 10; a = b; 这两个语句. var a 这里就是LHS查询,查找当前作用域是否有a这个变量来给a进行赋值,如果有则进行赋值:如果没有,在非严格模

  • javascript 词法作用域和闭包分析说明

    复制代码 代码如下: var classA = function(){ this.prop1 = 1; } classA.prototype.func1 = function(){ var that = this, var1 = 2; function a(){ return function(){ alert(var1); alert(this.prop1); }.apply(that); }; a(); } var objA = new ClassA(); objA.func1(); 大家应

  • JavaScript 变量作用域及闭包第1/2页

    实例一: 复制代码 代码如下: <script type="text/javascript"> var i = 1; // 弹出内容为 1 true 的提示框 alert(window.i + ' ' + (window.i == i)); </script> 分析: 在全局定义的变量其实就是 window 对象的属性. 上面的例子可以看到,我们定义全局变量的同时,window 对象会产生一个相应的属性,如何让我们的代码避免产生这个属性呢,看下面的例子. 实例二

  • javascript变量提升和闭包理解

    我们先来看一个题目: <script> console.log(typeof a)//undefined var a='littlebear'; console.log(a)//littlebear </script> <script> console.log(typeof a)//string var a=1; console.log(a)//1 </script> 第一个script里可以看出var a 被提升到顶部,a = 'littlebear'被保

  • 浅谈JavaScript中的作用域和闭包问题

    JavaScript的作用域以函数为界,不同的函数拥有相对独立的作用域.函数内部可以声明和访问全局变量,也可以声明局部变量(使用var关键字,函数的参数也是局部变量),但函数外部无法访问内部的局部变量: function test() { var a = 0; // 局部变量 b = 1; // 全局变量 } a = ?, b = ? // a为undefined,b为1 同名的局部变量会覆盖全局变量,但本质上它们是两个独立的变量,一方发生变化不会影响另一方: a = 5; // 函数外a的值为

  • 深入理解javascript作用域和闭包

    作用域 作用域是一个变量和函数的作用范围,javascript中函数内声明的所有变量在函数体内始终是可见的,在javascript中有全局作用域和局部作用域,但是没有块级作用域,局部变量的优先级高于全局变量,通过几个示例来了解下javascript中作用域的那些"潜规则"(这些也是在前端面试中经常问到的问题). 1. 变量声明提前 示例1: var scope="global"; function scopeTest(){ console.log(scope); v

  • JavaScript作用域、闭包、对象与原型链概念及用法实例总结

    本文实例讲述了JavaScript作用域.闭包.对象与原型链概念及用法.分享给大家供大家参考,具体如下: 1 JavaScript变量作用域 1.1 函数作用域 没有块作用域:即作用域不是以{}包围的,其作用域完成由函数来决定,因而if /for等语句中的花括号不是独立的作用域. 如前述,JS的在函数中定义的局部变量只对这个函数内部可见,称之谓函数作用域. 嵌套作用域变量搜索规则:当在函数中引用一个变量时,JS会搜索当前函数作用域,如果没有找到则搜索其上层作用域,一直到全局作用域. var va

  • JavaScript 中的作用域与闭包

    目录 一.JavaScript 是一门编译语言 1.1 传统编译语言的编译步骤 1.2 JavaScript 与传统编译语言的区别 二.作用域(Scope) 2.1 LHS查询 和 RHS查询 2.2 作用域嵌套 2.3 ReferenceError 和 TypeError (1)ReferenceError (2)TypeError (3)ReferenceError 和 TypeError 的区别 小结 三.词法作用域 3.1 词法阶段 3.2 词法作用域 查找规则 3.3 欺骗词法 ——

随机推荐