作用域

作用域可以理解为JS引擎执行代码的时候,查找变量的规则。

确定变量访问范围的阶段的角度,可以分为2类,词法作用域和动态作用域。js是词法作用域

变量查找的范围的角度,可以分为3类,全局作用域、函数作用域和块级作用域

词法作用域

词法作用域是在词法分析阶段就确定的作用域,变量的访问访问仅由声明时候的区域决定。

动态作用域则是在调用的而时候决定,它是基于调用栈的。

  1. var a = 2;
  2. function foo() {
  3. console.log( a );
  4. }
  5. function bar() {
  6. var a = 3;
  7. foo();
  8. }
  9. bar(); // 2

如果处于词法作用域,变量a首先在foo()函数中查找,没有找到。于是顺着作用域链到全局作用域中查找,于是输出2。

如果处于动态作用域,变量a首先在foo()函数中查找,没有找到。于是会顺着调用栈在调用foo()函数的地方,也就是bar()函数中查找,于是输出3。

但上述代码输出2,由此可以证明js是词法作用域。

全局作用域、函数作用域和块级作用域

全局作用域:

直接编写在script标签中的JS代码,或者一个单独的JS文件中的,都是全局作用域。

全局作用域在页面打开时创建,页面关闭时销毁。

在全局作用域中有一个全局对象window,代表一个浏览器的窗口,由浏览器创建,可以直接使用。

函数作用域:

JS函数作用域是指在函数内部声明的变量,在函数内部和函数内部声明的函数中都可以访问到。

访问规则:访问变量时,现在函数内部找,找不到则在外层函数找,直到最外层的全局作用于,这个查找的过程就是‘作用域链’。

块级作用域(es6)

使用let/const关键字创建的变量都具有块级作用域。

块级作用域的变量只有在语句块内可以访问。所谓语句块就是用{ }包起来的区域。

块级作用域有几个特性:不存在变量提升、暂时性死区、不允许重复声明。

不存在变量提升和不允许重复声明很好理解,那什么是暂时性死区呢?

答:只要块级作用域内存在let命令,它所声明的变量就绑定了这个区域,不再受外部影响。在代码块内,使用let命令声明函数之前,该变量都是不可用的,这在语法上称为“暂时性死区”。

  1. var tmp = 123;
  2. if (true) {
  3. tmp = 'abc'; // ReferenceError
  4. let tmp;
  5. }

变量提升

JS在执行之前,会先进行预编译,主要做两个工作:

  • 1、将全局作用域或者函数作用域内的所有函数声明提前。
  • 2、将全局作用域或者函数作用域内的所有var声明的变量提前声明,并且复制undefined

这就是变量提升。

  1. // 将全局作用域或者函数作用域内的所有函数声明提前。
  2. function test() {
  3. exec();
  4. function exec() {
  5. console.log('exec');
  6. }
  7. }
  8. // 等价于
  9. function test() {
  10. function exec() {
  11. console.log('exec');
  12. }
  13. exec();
  14. }
  15. // 将全局作用域或者函数作用域内的所有var声明的变量提前声明,并且复制undefined
  16. function test1() {
  17. console.log(name);
  18. var name = 'test';
  19. }
  20. // 等价于
  21. function test1() {
  22. var name;
  23. console.log(name);
  24. name = 'test';
  25. }

注意:

  • 函数声明可以提升,但是函数表达式不提升,具名的函数表达式的标识符也不会提升。
  • 同名的函数声明,后面的覆盖前面的。
  • 函数声明的提升,不受逻辑判断的控制。
  1. // 函数表达式和具名函数表达式标识符都不会提升
  2. test(); // TypeError test is not a function
  3. log(); // TypeError log is not a function
  4. var test = function log() { console.log('test') };
  5. // 同名函数声明,后面的覆盖前面的
  6. function test() {
  7. console.log(1);
  8. }
  9. function test() {
  10. console.log(2);
  11. }
  12. test(); // 2
  13. // 函数声明的提升,不受逻辑判断的控制
  14. // 注意这是在ES5环境中的规则,在ES6中会报错
  15. function test() {
  16. log();
  17. if (false) {
  18. function log() {
  19. console.log('test');
  20. }
  21. }
  22. }
  23. test(); // 'test'

在块级作用域中声明函数会是什么效果呢?

ES6环境中,如果在语句块中声明函数,按照正常的规范,函数声明应该被封闭在语句块里面,但是为了兼容老代码,因此语法标准允许其他的实现:

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。
  1. function f() { console.log('I am outside!'); }
  2. (function () {
  3. if (false) {
  4. // 重复声明一次函数f
  5. function f() { console.log('I am inside!'); }
  6. }
  7. f(); // Uncaught TypeError: f is not a function
  8. }());
  9. // 等价于
  10. function f() { console.log('I am outside!'); }
  11. (function () {
  12. var f = undefined;
  13. if (false) {
  14. function f() { console.log('I am inside!'); }
  15. }
  16. f(); // Uncaught TypeError: f is not a function
  17. }());

闭包

函数和函数内部能访问到的变量的总和,就是一个闭包。

如何生成闭包?函数内嵌套函数,并且函数执行完后,内部函数会被引用,这样内部函数可以访问外部函数中定义的变量,于是就生成了一个闭包。(函数嵌套 + 内部函数被引用)

闭包的作用是什么?可以让内部的函数访问到外部函数的变量,避免变量在全局作用域中存在被修改的风险。

注意事项:不用的时候解除引用,避免不必要的内存占用。

缺点:使用时候不注意的话,容易产生内存泄漏。

闭包实现一个计数器

  1. var count = 0;
  2. function createCounter() {
  3. function increase() {
  4. count++;
  5. }
  6. function getCount() {
  7. return count;
  8. }
  9. return {
  10. increase: increase,
  11. getCount: getCount
  12. };
  13. }
  14. var counter = createCounter();
  15. counter.increase();
  16. console.log(counter.getCount());
  17. console.log(count);

上述实现方法,变量count放在全局,很容易被其他模块修改从而导致不可预知的问题。因此我们希望count变量不会被其他模块访问到,于是需要把count放在函数作用域中:

  1. function createCounter() {
  2. var count = 0;
  3. function increase() {
  4. count++;
  5. }
  6. function getCount() {
  7. return count;
  8. }
  9. return {
  10. increase: increase,
  11. getCount: getCount
  12. };
  13. }
  14. var counter = createCounter();
  15. counter.increase();
  16. console.log(counter.getCount());
  17. console.log(count);

这样函数createCounter中的increate和getCount两个函数可以访问到createCounter内部定义的count,这样就形成了闭包。而count只能被createCounter内部定义的函数访问到,因此不会有被随意修改的风险。

通常情况下函数中定义的变量在函数执行完成后会被销毁,例如:

  1. function createCounter() {
  2. var count = 0;
  3. function increase() {
  4. count++;
  5. }
  6. function getCount() {
  7. return count;
  8. }
  9. return {
  10. increase: increase,
  11. getCount: getCount
  12. };
  13. }
  14. createCounter();

通常执行完createCounter()方法之后,内部的所有变量都被从内存中销毁(因为没有其他地方使用了)。但是如果生成了闭包(即有对内部嵌套函数的引用),则内部变量不会被销毁(因为还有其他地方在用,嵌套的内部函数还在使用)。

还是以上面createCounter闭包为例,由于createCounter返回的方法们被引用,因此形成闭包,所以内部变量count不会被销毁,而是会继续被increase和getCount使用。

生成闭包之后,如果我们不再需要使用counter可以执行counter = null;这样失去了对内部嵌套函数的引用,浏览器就会将方法内资源都销毁调了。因此当我们使用完闭包之后如果后续不再需要使用,最好通过取消引用来释放闭包的资源。

学习参考:https://www.yuque.com/baiyueguang-rfnbu/tr4d0i/gu0blp#WW3FR

JS作用域、变量提升和闭包的更多相关文章

  1. 深入理解js的变量提升和函数提升

    一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分.上个简历的例子如: ...

  2. js中变量提升(一个是变量,一个是函数表达式都会存在变量提升,函数声明不存在)

    一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分.上个简历的例子如: ...

  3. 深入理解js的变量提升和函数提升(转)

    一.变量提升 在ES6之前,JavaScript没有块级作用域(一对花括号{}即为一个块级作用域),只有全局作用域和函数作用域.变量提升即将变量声明提升到它所在作用域的最开始的部分.上个简历的例子如: ...

  4. 2 —— js语法 —— 对象和方法的声明 。变量提升。闭包

    一,声明对象 var obj1 = {}; var obj2 = {name:'kk',age:18,fun:function{          // name,age,fun为对象的属性,只是属性 ...

  5. js变量作用域--变量提升

    1.JS作用域 在ES5中,js只有两种形式的作用域:全局作用域和函数作用域,在ES6中,新增了一个块级作用域(最近的大括号涵盖的范围),但是仅限于let方式申明的变量. 2.变量声明 var x; ...

  6. 关于JS中变量提升的规则和原理的一点理解

        关于变量提升,以前在一些教程和书籍上都听到过,平时开发中也知道有这个规律,但是今天突然在一个公开课中听到时,第一反应时一脸懵逼,然后一百度,瞬间觉得好熟悉啊,差点被这个概念给唬住了,不信我给你 ...

  7. js 1.变量提升 2.条件语句 3.循环语句 4.加号+的使用

    1.变量提升 变量提升是浏览器的一个功能,在运行js 代码执行前,浏览器会给js一个全局作用域叫 window,window 分两个模块,一个叫运营模块,内存模块找到当前作用域下的所有带var和fun ...

  8. JS高级——变量提升

    JS执行过程 1.首先是预解析:预解析过程最重要的是提升,在JavaScript代码在预解析阶段,会对以var声明的变量名,和function开头的语句块,进行提升操作 2.执行操作 全局中解析和执行 ...

  9. 关于JS中变量提升的规则和原理的一点理解(二)

    上篇文章中讲到变量提升和函数提升的先后顺序时蒙了,后来去查了一下资料,特别整理一下. 在<你不知道的JavaScript(上卷)>一书的第40页中写到:函数会首先被提升,然后才是变量. 书 ...

随机推荐

  1. 【MAUI】为 Label、Image 等控件添加点击事件

    一.前言 已经习惯了 WPF.WinForm 中"万物皆可点击"的方式. 但是在 MAUI 中却不行了. 在 MAUI 中,点击.双击的效果,是需要通过"手势识别器&qu ...

  2. NC14247 Xorto

    NC14247 Xorto 题目 题目描述 给定一个长度为 \(n\) 的整数数组,问有多少对互不重叠的非空区间,使得两个区间内的数的异或和为 \(0\) . 输入描述 第一行一个数 \(n\) 表示 ...

  3. STM32液晶显示HT1621驱动原理及程序代码

    1.HT1621电路分析 HT1621为32×4即128点内存映像LCD驱动器,包含内嵌的32×4位显示RAM内存和时基发生器以及WDT看门狗定时器. HT1621驱动电路如下图所示: 图1 与单片机 ...

  4. 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  5. JS基础小练习

    入职薪水10K,每年涨幅入职薪水的5%,50年后工资多少? var sum = 10000; console.log(sum * (1 + 0.05 * 50)); 为抵抗洪水,战士连续作战89小时, ...

  6. css基础05

    无关浏览器,只想对于原来的位置.而且下面的盒子也不会升上去. 没有父亲的时候就是以浏览器为标准的. 父亲没定位,爷爷有定位,就按照爷爷的,不管父亲了. 绝对定位飘起来比浮动还要高.飘起来了它的位置就会 ...

  7. YII学习总结4(cookie操作)

    cookie操作 <?php namespace app\controllers; use yii\web\Controller; use yii\web\Cookie; class Hello ...

  8. wamp升级php

    1.  停止WAMP服务器. 2.  去网站windows.php.net 下载php-5.4.31-nts-Win32-VC9-x86.zip(32位的). 不要下载THE INSTALLER. 3 ...

  9. odoo 14 一些常见问题集

    1 # 当你往tree或者form视图中增加action的时候 2 # 记住!千万别重名 3 # 一旦重名,Export.Delete.Archive.Unarchive都会消失不见 4 # tree ...

  10. 使用.NET简单实现一个Redis的高性能克隆版(三)

    译者注 该原文是Ayende Rahien大佬业余自己在使用C# 和 .NET构建一个简单.高性能兼容Redis协议的数据库的经历. 首先这个"Redis"是非常简单的实现,但是他 ...