当前位置:Gxlcms > 前端框架 > 浅谈JavaScript变量的作用域及闭包

浅谈JavaScript变量的作用域及闭包

时间:2021-07-01 10:21:17 帮助过:84人阅读

与闭包有关的概念:变量的作用域和变量的生存周期。下面本篇文章就来给大家介绍一下JavaScript中变量的作用域及闭包,有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

一、变量的作用域

1、变量的作用域指变量有效的范围,与变量定义的位置密切相关,作用域是从空间的角度来描述变量的,也可以理解为变量的可见性。在某个范围内变量是可见的,也就是说,变量是可用的。【相关课程推荐:JavaScript视频教程】

2、按照作用域的不同,变量可分为全局变量和局部变量。

● 全局变量:在全局环境中声明的变量

● 局部变量:在函数中声明的变量

● 当函数在执行时,会创建一个封闭的执行期上下文环境,函数内部声明的变量仅可在函数内部使用,外部无法访问,而全局变量则在任何地方都可以使用

3、在函数中使用var关键字显示声明的变量是局部变量;而没有用var关键字,用直接赋值的方式声明的变量是全局变量

  1. var m=8;
  2. function f1(){
  3. var a1=10;
  4. console.log(m); //8
  5. }
  6. function f2(){
  7. var a2=20;
  8. console.log(m); //8
  9. }
  10. f1();
  11. f2();

4、依赖变量作用域实现封装特性

(1)使用ES6提供的let
(2)通过函数来创建作用域:

  1. var myObject=(function(){
  2. var _name="yian"; //私有变量
  3. return {
  4. getName:function(){ //公有方法
  5. return _name;
  6. }
  7. }
  8. })();
  9. console.log(myObject._name); //undefined
  10. console.log(myObject.getName()); //yian

二、变量的生存周期

1、对于全局变量来说,其生命周期是永久的,除非主动销毁此全局变量;

2、对于在函数内用var关键字声明的局部变量来说,当退出函数时,这些局部变量即失去它们的价值,它们会随着函数调用的结束而被销毁

3、模仿块级作用域

(1)用作块级作用域的匿名函数:将函数声明包含在一对圆括号中,表示它实际上是一个函数表达式,而紧随其后的另一对圆括号会立即调用这个函数。

  1. (function(){
  2. //这里是块级作用域
  3. })();

在匿名函数中定义的任何变量,都会在执行结束时被销毁

(2)先定义一个函数,然后调用它。定义函数的方式是创建一个匿名函数,并把这个匿名函数赋值给变量;而调用函数的方式是在函数的名称后添加一对圆括号。

  1. var someFunction=function(){
  2. //这里是块级作用域
  3. };
  4. someFunction();

经典问题:

  1. var nodes=document.getElementsByTagName("div");
  2. for(var i= 0,len=nodes.length;i<len;i++){
  3. nodes[i].onclick=function(){
  4. console.log(i); //无论点击哪个div,最后弹出的结果都是5
  5. }
  6. }

解释: div节点的onclick事件是被异步触发的,当事件被触发时,for循环早已结束,此时i 已经是5

解决办法:

法一:使用ES6中的let

法二:在闭包的帮助下,把每次循环的i值都封存起来

  1. var nodes=document.getElementsByTagName("div");
  2. for(var i= 0,len=nodes.length;i<len;i++){
  3. (function(x){
  4. nodes[i].onclick=function(){
  5. console.log(x);
  6. }
  7. })(i);
  8. }

4、作用:读取函数内部的变量,并将这些变量的值始终保存在内存中

(1)封装变量:闭包可以把一些不需要暴露在全局的变量封装为“私有变量”,私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

例:mult函数接收number类型的参数,并返回这些参数的乘积、

初始代码:

  1. var cache={ };
  2. var mult=function(){
  3. var args=Array.prototype.join.call(arguments,",");
  4. if(cache[args]){
  5. return cache[args];
  6. }
  7. var a=1;
  8. for(var i=0,len=arguments.length;i<len;i++){
  9. a=a*arguments[i];
  10. }
  11. return cache[args]=a;
  12. };
  13. console.log(mult(1,2,3)); //6
  14. console.log(mult(3,4,5)); //60

使用闭包后:

  1. var mult=(function(){
  2. var cache={ }; //加入缓存机制,避免相同参数的计算
  3. var calculate=function(){
  4. var a=1;
  5. for(var i= 0,len=arguments.length;i<len;i++){
  6. a=a*arguments[i];
  7. }
  8. return a;
  9. };
  10. return function(){
  11. var args=Array.prototype.join.call(arguments,",");
  12. if(args in cache){
  13. return cache[args];
  14. }
  15. return cache[args]=calculate.apply(null,arguments);
  16. }
  17. })();

补充:in判断属性属于对象

  1. var mycar = {make: "Honda", model: "Accord", year: 1998};
  2. if ( "make" in mycar ){ //属性名必须是字符串形式,因为make不是一个变量
  3. document.write('true'); // 显示true
  4. }
  5. else{
  6. document.write('false');
  7. }

(2)延续局部变量的寿命

例:使用report数据上报时会丢失30%左右的数据,原因是img时report中的局部变量,当report函数调用结束后,img局部变量随即被销毁

初始代码:

  1. var report=function(src){
  2. var image=new Image();
  3. image.src=src;
  4. };

使用闭包后(把img变量用闭包封装起来):

  1. var report=(function(){
  2. var imgs=[ ];
  3. return function(){
  4. var image=new Image();
  5. imgs.push(image);
  6. image.src=src;
  7. }
  8. })();

5、闭包和面向对象设计

闭包写法:

  1. var extent=function(){
  2. var value=0;
  3. return {
  4. call:function(){
  5. value++;
  6. console.log(value);
  7. }
  8. }
  9. }
  10. var extent=extent();
  11. extent.call(); //1
  12. extent.call(); //2

面向对象写法一:

  1. var extend={
  2. value:0,
  3. call:function(){
  4. this.value++;
  5. console.log(this.value);
  6. }
  7. };
  8. extend.call(); //1
  9. extend.call(); //2

面向对象写法二:

  1. var Extend=function(){
  2. this.value=0;
  3. };
  4. Extend.prototype.call=function(){
  5. this.value++;
  6. console.log(this.value);
  7. };
  8. var extend=new Extend();
  9. extend.call(); //1
  10. extend.call(); //2

6、闭包与内存管理

● 局部变量变量应该在函数退出时被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么局部变量就会一直生存下去,即它会常驻内存。

● 使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,这就有可能造成内存泄漏。

● 解决循环引用带来的内存泄漏问题:把循环引用中的变量设为null。(将变量设置为null以为着切断变量与它之前引用的值之间的连接,当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存)

7、特点:

● 函数嵌套函数;

● 在函数内部可引用外部的参数和变量;

● 参数和变量不会以垃圾回收机制回收。

8、优点:避免全局变量的污染

9、缺点:会常驻内存,增加内存的使用量,使用不当会造成内存泄漏;闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存

10、创建闭包

写法一:

  1. function a() {
  2. var b=123;
  3. function c(){
  4. console.log(b+=1);
  5. }
  6. return c;
  7. }
  8. var d=a();
  9. d();

方式二:

  1. function f1(){
  2. var num=10; //函数执行完毕,变量仍然存在
  3. var f2=function(){
  4. num++;
  5. console.log(num); //11
  6. };
  7. return f2;
  8. }
  9. var res=f1();
  10. res();

● 解释:执行f1()后,f1()闭包内部的变量会存在,而闭包内部函数的内部变量不会存在,使得JavaScript的垃圾回收机制不会收回f1()占用的资源,因为f1()中内部函数的执行需要依赖f1()中的变量。

方式三:

  1. function foo(x) {
  2. var tmp = 3;
  3. return function f2(y) {
  4. alert(x + y + (++tmp)); //17
  5. };
  6. }
  7. var bar = foo(3); //bar现在是一个闭包
  8. bar(10);

练习题:

  1. function f1(){
  2. var a=1;
  3. t=function(){
  4. a++;
  5. }
  6. return function(){
  7. console.log(a);
  8. }
  9. }
  10. var b=f1(); //返回值为一个匿名函数
  11. b(); //1
  12. t();
  13. b(); //2

声明变量,若变量名称相同,就近原则:

  1. var name="g";
  2. function out(){
  3. var name="loc";
  4. function foo(){
  5. console.log(name);
  6. }
  7. foo();
  8. }
  9. out(); //name=loc

补充知识点:

1、JS中有哪些垃圾回收机制?

(1)引用计数:跟踪记录每个值被使用的次数。

● 当声明一个变量并将一个引用类型赋值给该变量时,该值的引用次数加1;

● 若该变量的值变为另一个,则该值引用次数减1;

● 若该值引用次数为0时,说明变量没有在使用,此值无法访问;

● 因此,可以将它占用的空间回收,垃圾回收机制会在运行时清理引用次数为0 的值所占用的空间。

● 在低版的IE中会发生内存泄漏,很多时候就是因为它采用引用计数得到方式进行垃圾回收(如果两个对象之间形成了循环引用,那么这两个对象都无法被回收)。

(2)标记清除:最常见的垃圾回收方式

● 当变量进入执行环境时,垃圾回收器将其标为“进入环境”,离开时标记为“离开环境”;

● 垃圾回收机制在运行时给存储在内存中的所有变量加上标记;

● 去掉环境中的变量及被环境中变量所引用的变量(闭包)的标记;

● 完成这些后仍存在的标记就是要删除的变量。

2、哪些操作会造成内存泄漏?

● 内存泄漏:指不再拥有或需要任何对象(数据)之后,它们仍然存在于内存中。

● 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为0(没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象占用的内存立即被回收。

● 如果setTimeout的第一个参数使用字符串而非函数,会造成内存泄漏。

● 闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)等会造成内存泄漏。

本文来自 js教程 栏目,欢迎学习!

以上就是浅谈JavaScript变量的作用域及闭包的详细内容,更多请关注Gxlcms其它相关文章!

人气教程排行