javascript | 作用域和闭包

一般理解为一个变量的作用范围。

🌞全局作用域

  • 全局作用域在页面打开时被创建,页面关闭时被销毁。
  • 编写在<script>标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到。
  • 全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用。
  • 全局作用域中声明的变量和函数会作为window对象的属性和方法保存。

🌞函数作用域

  • 调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁。
  • 每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的。
  • 在函数作用域中可以访问的到全局作用域的变量,在函数外无法访问到函数作用域内的变量。
  • 在函数作用域中访问变量、函数时,会优先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直找到全局作用域。

🌞预编译

❄执行期的上下文
  • 当函数代码执行的前期,会创建一个执行期上下文的内部对象AO(作用域)

  • 这个内部的对象是预编译的时候创建出来的,因为当函数被调用的时候,会先进行预编译

  • 在全局代码执行的前期会创建一个执行期的上下文对象AO

  • 创建阶段(用函数来举例就是说,函数定义但是还没有被调用的阶段)

    • 创建作用域链(当前变量对象+所有父级变量对象)
    • 创建变量对象(参数、变量、函数声明)
    • 决定this指向
  • 执行阶段

    • 变量赋值、函数引用等
❄预编译
  • 函数作用域预编译

    💬【例题】

     function fn(a, c) {
         console.log(a);   //function a() {}
         var a = 123
         console.log(a);   //123
         console.log(c);   //function c() {}
         function a() {}
         if (false) {
             // false 不会进来
             var d = 678
         }
         console.log(d);   //undefined
         console.log(b);   //undefined
         var b = function() {}
         console.log(b);   //function() {}
         function c() {}
         console.log(c);  //function c() {}
     }
     fn(1, 2)
    

    1.会创建一个AO对象AO{}

    2.找形参和变量声明,将变量和形参名当做AO对象的属性名,值为undefined

    	AO{
    		a:undefined
    		c:undefined
    		d:undefined
    		b:undefined
    	}
    

    3.实参和形参相统一

    ​ 即fn(1,2)fn(a,c)

    	AO{
    		a:undefined  1
    		c:undefined  2
    		d:undefined
    		b:undefined
    	}
    

    4.在函数体里面找函数声明,值赋予函数体

    	AO{
    		a:undefined  1  function a() {}
    		c:undefined  2  function c() {}
    		d:undefined
    		b:undefined
    	}
    

    ​ ⚠var b = function() {}不是函数的声明,是函数的表达式

    5.然后再一行行解释执行。

  • 全局作用域的预编译

    1.创建GO对象

    2.找变量声明,将变量名作为GO对象的属性名,值是undefined

    3.找函数声明,值赋予函数体

🌞作用域链

  • 会被保存到一个隐式的属性scope中去,这个属性是我们用户访问不到的,但是的的确确是存在的、让JS引擎来访问的,里面存储的就是作用域链AO和GO的集合
  • 栈stack
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    var global
    function a() {
        function b() {
            var bb = 123
            aa = 0
        }
        var aa = 123
        b()
    }
    a()

🌞闭包

❄什么是闭包?

闭包就是可以在作用域外部访问作用域内部的变量

function books(){
    var book="书包里的书本"
  }

console.log(book)  //book is not defined

​ 执行任何代码前,先进入全局执行上下文

​ ❗ 函数只有执行才会有环境,这里只有函数声明,没有执行函数,所以没有进入函数执行上下文。

​ 所以此刻只是在全局环境中要求输出book的值

​ 然而全局环境中并没有book这个变量

❄作用域链

function books(){
	var book="书包里的书本"
	return function(){
		console.log(book)
	}
}

var bag=books()
bag()

​ 1.创建全局执行上下文={作用域链:{全局变量对象},{变量对象:books,bag}}

​ 2.因为bag指向的是books函数,bag()要进入到books函数的执行上下文

​ 3.books执行上下文={作用域链:{books变量对象(顶部)+全局变量对象},{变量对象:book}}

​ 4.返回匿名函数,创建匿名函数执行上下文={作用域链:{匿名函数变量对象(顶部)+books变量对象+全局变量对象},{变量对象:}}

​ 5.现在要输出book,在作用域链中逐层寻找,匿名函数变量对象中没有,就找上一层的变量对象,找到books函数中的book

❄面试题

for(var i=0;i<5;i++){
	setTimeout(function(){
		console.log(i++)
	},4000)
}
console.log(i)  

setTimeout 里面的函数不会马上放到执行栈里面,而是先放在任务队列中
在这里插入图片描述

并未实际执行,这里还没有计算结果

最外面的console.log(i),因为for里面没有设置块级作用域,所以这里的i属于全局,循环结束这里的i就是循环结束的值5

现在,全局执行上下文已经没有需要执行的代码了,执行栈的东西已经执行完了,就可以执行任务队列的内容了。

在这里插入图片描述

现在,i的值是5,因此setTimeout里面的匿名函数就会依次输出56789

最后的结果是:5(4秒后)56789

这里的四秒指的是执行栈执行完所有的任务后,间隔四秒再执行任务队列的内容。

如果我们想要输出5,再输出01234,要怎么做呢?

修改回调函数的作用域,让回调函数的作用域链指向每一轮i的值→立即执行函数(function(x){}(i))

for(var i=0;i<5;i++){
    (function(x){
      	setTimeout(function(){
		console.log(x)
	},4000)  
    })(i)
}
console.log(i)  

全局执行上下文={作用域链:{全局变量对象},{变量对象:i}

立即执行函数执行上下文={作用域链:{立即执行函数变量对象+全局变量对象},{变量对象:x(即实参i,此刻为0)}

立即执行函数执行上下文从栈中弹出,回到全局执行上下文

进入第二次for循环……
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值