1、JS中的作用域

在 es6 出现之前JS中只有全局作用域函数作用域没有块级作用域。即在函数体内就可以有自己的作用域,但是如果不是在函数体的话就全部都是全局作用域。比如在 if、for 等有 {} 的结构体,就不会具备自己的作用域,在这些结构体内声明的变量将会是全局变量。由此可能导致一些问题,下面代码示例:

var tmp = new Date();
function f() {console.log(tmp);if (false) {  //即使没有运行到下面的代码,该变量声明也被提升了。因为变量声明是在编译阶段就被运行的,而不是代码运行阶段var tmp = 'hello world';}
}
f(); // undefined  由于变量提升,导致内层的tmp变量覆盖了外层的tmp变量

var s = 'hello';
for (var i = 0; i < s.length; i++) {console.log(s[i]);
}
console.log(i); // 5  用来计数的循环变量泄露为全局变量。

在 es6 中引入的块级作用域就解决了这些问题。通过 let、const 引入块级作用域后:

function f1() {let n = 5;if (true) {let n = 10;}console.log(n); // 5   无法访问到块级作用域里的变量
}

1.1、调用函数时,函数内部的变量的作用域

编程语言的作用域规则有动态作用域和词法作用域,词法作用域的指的是函数和变量的作用域由声明时所处的位置决定,JS使用的就是词法作用域。

在JS中,调用函数时,函数内部的变量的作用域由函数声明时所处的位置决定,而不是调用的位置。

var a = 'window'
var f = function (){console.log(a);
}
var b = function (){var a = 'else'f();
}
b();    //输出 'window'

但是 JS 中的 this 指针并不遵守词法作用域,而是取决于函数的调用方式。

2、全局变量和局部变量

JS中的变量只有两种:全局变量和局部变量。函数体外声明的变量,称为全局变量。 函数内部使用 var 声明的变量,称为局部变量。

(网上都说在函数体内声明的变量称为局部变量,那块级作用域中的变量是什么变量?我觉得应该是所有在 {} 结构体内声明的变量都称之为局部变量)

//函数内部可以直接读取全局变量。
var n=999;function f1(){alert(n);}
f1(); // 999//但是在函数外部无法读取函数内的局部变量,因为函数内部的变量在函数执行完毕以后就会被释放掉
function f1(){var n=999;
}
alert(n); // error

注意:在函数内部声明变量的时候,一定要使用 var、let、const 命令。否则的话实际上是声明了一个全局变量!

function f1(){n=999;
}
f1();
alert(n); // 999

而要想访问到局部变量,那么就可以使用闭包。

3、闭包的概念

闭包就是能够读取其他函数内部变量的函数。

在 es6之前,JavaScript中的变量的作用域只有两种:全局作用域和函数作用域。函数内部可以直接读取全局变量,但是在函数外部就无法读取函数内的局部变量。要想从外部读取函数内的局部变量就要用到闭包。

function f1() {n = 999;function f2() {alert(n);}return f2;
}
var result = f1();
result(); // 999

4、闭包的作用

闭包可以用在许多地方。它的最大用处有两个:可以读取函数内部的变量、让这些变量的值始终保持在内存中。

4.1、让这些变量的值始终保持在内存中。

function f1() {var n = 999;nAdd = function () {n += 1}function f2() {alert(n);}return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000

在上面的代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。(如果n是一个需要很费时的操作才能得到的值的话就会作用明显,而且可以把局部变量驻留在内存中,避免使用全局变量)

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,nAdd是一个全局变量,这个函数本身也是一个闭包,因为该函数的存在我们可以在函数外部对函数内部的局部变量进行操作。

4.2、减少全局变量的污染(匿名自执行函数)

所有的变量声明时如果不加上var等关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量、造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,比如UI的初始化等,此时我们可以使用闭包。

(function(){var foo = function(){alert("执行完这个闭包后,立即销毁");};foo();
})();

上面创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在函数执行完后会立刻释放资源,关键是不污染全局对象。

4.3、实现封装

var person = function () {//变量作用域为函数内部,外部无法访问    var name = "default";return {getName: function () {return name;},setName: function (newName) {name = newName;}}
}();
console.log(person.name); //直接访问,结果为undefined
console.log(person.getName());
person.setName("abruzzi");
console.log(person.getName());

4.4、实现继承

下面定义了Person,它就像一个类,我们new一个 Person 对象,访问它的方法。下面我们定义了Jack,继承自 Person,并添加自己的方法,Jack 继承了 Person。

function Person2() {var name = "default";this.age = 12;return {getName: function () {return name;},setName: function (newName) {name = newName;}}
};var p = new Person2();
console.log(p.age);
p.setName("Tom");
console.log(p.getName());var Jack = function () {};
//继承自Person
Jack.prototype = new Person2();
//添加私有方法
Jack.prototype.Say = function () {console.log("Hello,my name is Jack");
};
var j = new Jack();
j.setName("Jack");
j.Say();
console.log(j.getName());

5、闭包的危害

5.1、可能导致内存泄露

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露(即己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,将导致程序运行速度减慢)。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

5.2、可能不小心修改掉私有属性

闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象使用,把闭包当作它的公用方法,把内部变量当作它的私有属性,这时一定要小心,闭包可能会修改掉父函数的私有属性。

6、垃圾回收机制

不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收。

(局部变量应该是只有在函数执行时才会被创建然后占用内存,当函数执行结束之后如果没有闭包引用它的话就会被回收。当函数再次执行时又再次创建然后占用内存)

可以参考:https://blog.csdn.net/zxd10001/article/details/81038533

6.1、JavaScript的内存生命周期

  • 分配所需要的内存
  • 使用分配到的内存(读、写)
  • 不需要时将其释放

垃圾回收机制的原理其实很简单:确定变量中哪些还在继续使用的,哪些已经不用的,然后垃圾收集器每隔固定的时间就会清理一下,释放内存。

局部变量在程序执行过程中,会为局部变量分配相应的空间,然后在函数中使用这些变量,如果函数运行结束了,而且在函数之外没有再引用这个变量了,局部变量就没有存在的价值了,因此会被垃圾回收机制回收。在这种情况下,浏览器很容易辨别哪些变量该回收,但是并非所有情况下都这么容易。比如说全局变量。在现代浏览器中,通常使用标记清除策略来辨别及实现垃圾回收。

  • 标记清除

标记清除会给内存中所有的变量都加上标记,然后去掉环境中的变量以及不在环境中但是被环境中变量引用的变量(闭包)的标记。剩下的被标记的就是等待被删除的变量,原因是环境中的变量已经不会再访问到这些变量了。最后垃圾回收器会完成内存清理,销毁那些被标记的值释放内存空间。

参考:https://www.cnblogs.com/yunfeifei/p/4019504.html、  https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures

转载于:https://www.cnblogs.com/wenxuehai/p/10317437.html

JS中的作用域及闭包相关推荐

  1. 了解js基础知识中的作用域和闭包以及闭包的一些应用场景,浅析函数柯里化

    js基础知识中的作用域和闭包 一.作用域 1.作用域.自由变量简介 (1)作用域定义 (2)作用域实例演示 (3)自由变量定义 (4)自由变量实例演示 2.作用域链简介 (1)作用域链定义 (2)作用 ...

  2. js执行环境作用域和闭包_JavaScript中执行上下文,提升,作用域和闭包的终极指南

    js执行环境作用域和闭包 It may seem surprising, but in my opinion the most important and fundamental concept to ...

  3. javascript中关于作用域和闭包

    列表项目 前言 学习了javascript已经很久了,关于这个语言中的这两个特性也是早已耳熟能详,但是在实际的使用的过程中或者是遇到相关的问题的时候,还是不能很好的解决. 因此我觉得很有必要深入的学习 ...

  4. Js中的作用域和作用域链

    Js中的作用域和作用域链 前言 阅读本文,请先阅读:Js中的函数相关:创建函数的三种方式.函数的形参和实参.返回值.return.break.continue的区别.重载和arguments.匿名函数 ...

  5. JS中的作用域(一)-详谈

    本篇文章在于详细解读JavaScript的作用域,从底层原理来解释一些常见的问题,例如变量提升.隐式创建变量等问题,在和大家一起交流进步的同时,也算对自己知识掌握的记录,方便以后复习 首先,直接捡干的 ...

  6. 你不懂的JS学习笔记(作用域和闭包)

    You don't KnowJS 引语:你不懂的JS这本书github上已经有了7w的star最近也是张野大大给我推荐了一波,阅读过之后感觉对js的基础又有了更好的理解.本来我是从来不这种读书笔记的, ...

  7. js变量提升_学习笔记:JS中的作用域和预解析

    知识总结:谢静贤.汤昊 在javascript中作用域是非常重要的,本文将会说明作用域以及我们在工作,以及面试中的一些面试题,如果有不足的地方希望大家可以评论指出来,自己一定会及时的改正错误,避免大家 ...

  8. js中变量作用域的小理解

    一:变量作用域 在js代码中每个变量都是有自己的作用域的,js中不像C语言有块级作用域的概念,取而代之的是函数作用域,看如下代码: var scope="global"; func ...

  9. javascript --- js中的作用域 变量提升

    1 求以下函数的输出 1.1 考察点: 变量提升.this.作用域 // 考察点 作用域.this.变量提升 var a = 10 function test() {a = 100console.lo ...

  10. js函数、作用域和闭包

    JavaScript-作用域.块级作用域.上下文.执行上下文.作用域链 一.函数 1.函数定义 函数是一段可以反复调用的代码块.函数可以接收输入的参数,不同的参数会返回不同的值 2.函数的声明方式 主 ...

最新文章

  1. 基于视觉Transformer的目标检测
  2. 人工智能将再创新高,清华发布人工智能白皮书
  3. java怎吗从磁盘读文件_编写一个Java应用程序,该程序使用FileInputStream类,实现从磁盘读取本应用程序源代码文件,并将文件内容显示在屏幕上。...
  4. 新来的运维这样用HDFS,CIO都懵了···
  5. EnvironmentShare
  6. 代写python作业费用标准_代做159.272作业、代写Programming Paradigms作业、代做Python实验作业、代写Java/c++编程作业代写Database|代做R...
  7. 【数据结构笔记02】什么是算法
  8. 将list集合中按照某个字段排序(从大到小),然后将list中的对象倒序
  9. 网易云音乐获取音频链接(爬虫)破解params参数
  10. MFC 在Edit Control输入完成后按下回车键希望直接执行某个函数
  11. ios label内字体置顶_IOS_Vertically align UILabel文本置顶 | 学步园
  12. 张飞实战电子1-31部 和硬件工程师90天学习资料及笔记汇总
  13. 给IT新人的15点建议:苦逼程序员的辛酸反省与总结
  14. 5个免费翻译网页的工具,快速 、高效、准确率高!
  15. 隐私计算之全同态加密
  16. 关闭笔记本电脑触控板的方法
  17. 百度搜索指数的数据获取和excel导出
  18. 第一章 Linux及Linux Shell简介
  19. ESP8266呼吸灯亮度调节并且实时显示PWM数值
  20. 如何关闭qq空间以及微信朋友圈广告

热门文章

  1. Qt-textEdit 滚顶条设置只读模式
  2. mysql教研室_MySQL数据库 范式
  3. 2021-08-01创建查询关键字及数据类型
  4. 树分解 tree decomposition
  5. mysql获取上周时间戳_php、mysql查询当天,查询本周,查询本月的数据实例(字段是时间戳)...
  6. 作用域和请求参数传递
  7. mysql+instr查询,mysql中使用instr进行模糊查询方法介绍
  8. php 面向对象编程(class)之从入门到崩溃 高级篇
  9. php 面向对象编程(class)之从入门到崩溃 基础篇
  10. 计算学生成绩 c语言,c语言项目实战2学生成绩的输入与计算.ppt