你不知道的javascript—作用域、闭包「建议收藏」

你不知道的javascript—作用域、闭包

大家好,又见面了,我是全栈君。

一、作用域

1、 期骗词法

JavaScript 中有两个机制可以“欺骗”词法作用域:eval(..) 和 with。
前者可以对一段包 含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在 运行时)。
后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作 用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。

1.1 eval()

function foo(str, a) {
    eval( str ); // 欺骗!
    console.log( a, b );
} 
var b = 2; 
foo( "var b = 3;", 1 ); // 1, 3
复制代码

在严格模式的程序中,eval(..) 在运行时有其自己的词法作用域,意味着其 中的声明无法修改所在的作用域。

 function foo(str) {
    "use strict";        
     eval( str );         
     console.log( a ); // ReferenceError: a is not defined     
 } 
 foo( "var a = 2" );
 注意:
// "use strict" 的目的是指定代码在严格条件下执行。
// 严格模式下你不能使用未声明的变量。
// 严格模式通过在脚本或函数的头部添加 "use strict";
复制代码

eval()用法

eval函数是用来解析json对象的;它的功能是把对应的字符串解析成JS代码并运行。

语法:

eval("("+jsonObj+")")
复制代码

举个栗子。

function name1(){
   console.log('name1')
 }
 function name2(){
   console.log('name2')
 }
 var m="name1";
 eval(m+'()');//运行name1();
 m='name2';
eval(m+'()');//运行name2();
复制代码

1.2 with()

JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是 with 关键字。
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象 本身。

var obj = {
    a: 1,
    b: 2,
    c: 3 
}; 
// 单调乏味的重复 "obj" 
    obj.a = 2; 
    obj.b = 3; 
    obj.c = 4; 
// 简单的快捷方式 
    with (obj) {     
        a = 3;     
        b = 4;     
        c = 5; 
    }
复制代码

with()用法

with语句用于设置代码在特定对象中的作用域。换句话说with就是为了封装某个对象,减少某个对象的调用。

语法:

var str="hello";
with(str){
    alert(toUpperCase());//输出"HELLO"
}
复制代码

接下来再举个栗子,大家来思考一下结果。

function foo(obj) {     
    with (obj) {         
        a = 2;     
    } 
} 
var o1 = {  a: 3 }; 
var o2 = {  b: 3 }; 
foo( o1 ); 
console.log( o1.a ); // 2 
foo( o2 ); 
console.log( o2.a ); // undefined 
console.log( a ); 
// 2——不好,a 被泄漏到全局作用域上了!
复制代码

到这里大家有什么疑问的吗???


如有不懂请看下面的解释
回顾一下上面的问题,实际上 a = 2 赋值操作创建了一个全局的变量 a。这是怎么回事?
简单来讲,with 可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对 象的属性也会被处理为定义在这个作用域中的词法标识符。
eval(..) 函数如果接受了含有一个或多个声明的代码,就会修改其所处的词法作用域,而 with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。
好了到这里大家明白了吧 嘿嘿!

2、块作用域

说到块作用域大家一定很困扰吧,在es6入门时,var与let带来的作用域让我们的头都大了对吧!!!下面的会很有趣 ^_^

2.1 let和var

ES6 引入了新的 let 关键字,提供了除 var 以外的另一种变量声明方式。 let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。
代码分析:

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
复制代码

var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6
复制代码

以上两段代码区别在于var与let使用的作用域不同。
1.var 变量的作用域是全局,而let 是局部的块作用域即for循环内。
2.明确循环内部的i与console.log(i)中的i是在不同的作用域中,它们有各自单独的作用域。
3.全局变量唯一性,var 声明的变量i在循环中被不断覆盖最终只是唯一的10,因此在外部调用中无论调用a数组的哪一个,最终都是10。
4.局部使用 let 定义时只在该函数作用域内部有效。
举个栗子细磨一下:

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined
复制代码

2.2 const

除了 let和var 以外,ES6 还引入了 const,同样可以用来创建块作用域变量,但其值是固定的 (常量)。换句话说:const声明一个只读的常量。一旦声明,常量的值就不能改变。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
代码分析:

var foo = true; 
if (foo) {     
     var a = 2;     
     const b = 3; // 包含在 if 中的块作用域常量 
     a = 3; // 正常 !     
     b = 4; // 错误 ! 
} 
console.log( a ); // 3 
console.log( b ); // ReferenceError!
复制代码

const foo;
// SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来说,只声明不赋值,就会报错。
复制代码

const 常量为变量时结果。。。。
const foo = {
  x:0,
  y:1
}
foo.x = 2
console.log(foo.x)
复制代码

大家思考一下第三个结果是多少? 打印结果为0?还是2呢?
先总结一下const:
1.只在块级作用域起作用,和let关键字一样
2.不存在变量提升,但必须提前声明,和let一样
3.不可重复声明同一个变量
4.声明后要赋值,没有赋值会报错
5.思考一下const定义的常量是变量呢????


公布一下答案foo.x为2 是不是大家有点蒙了,不是说const定义的常量不能改变吗,而此时却改变且未报错!我解释一下
有一个概念:在赋值过程中,我们可以分为传值赋值和传址赋值。
这里我们用到了传址赋值,什么叫传址赋值?
传址:在赋值过程中,变量实际上存储的是数据的地址(对数据的引用),而不是原始数据或者数据的拷贝
举个栗子

var obj= {

    "name": '张三'
}
var obj1 = obj
obj1.name='李四'
console.log(obj) //李四
console.log(obj1) // 李四
复制代码

上面的obj1,obj2都变为李四就是传址赋值


说到这我想到了一个问题关于const关键字,我们常常说const定义常量。其实在es6中,const代表一个值 的常量索引。换句话说,变量名字在内存中的指针不能够改变,但是指向这个变量的值可以改变。

二、闭包

1、闭包是什么?

闭包就是外层函数的内部函数(不过要注意它的特性)。

1.1特性:

1.它有自己的局部作用域(local scope);

2.它可以访问外部函数的作用域(outer scope),参数(parameters),而不是参数对象;

3.它也可以访问全局的(global scope)

4.参数和变量不会被垃圾回收机制回收(不当的使用闭包可能造成内存泄漏的原因)

2、闭包工作原理

1.闭包存储外部函数变量的引用,因此总是可以访问外部变量的更新值

2.在它的外部函数被执行并返回值后,闭包仍然可以执行(常驻内存)

3、闭包的好处

1.保存状态(使一个变量长期驻扎在内存中)

2.避免全局变量的污染

3.允许私有成员的存在

4、如何使用闭包

大家看一下下面的代码是闭包吗?

function foo() {     
    var a = 2; 
    function bar() {         
        console.log( a ); // 2     
    } 
    bar(); 
} 
foo();
复制代码

上面的代码是闭包吗??函数bar()可以访问外部作用域的变量a 但不是闭包,现在大家是不是有点好奇了
下面我们再看一段代码:

function foo() {     
    var a = 2; 
    function bar() {          
        console.log( a );    
    } 
    return bar; 
} 
var baz = foo(); 
baz(); 
// 2 —— 朋友,这就是闭包的效果。
// 函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作 一个值类型进行传递。在这个例子中,我们将 bar 所引用的函数对象本身当作返回值。
// 在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),实 际上只是通过不同的标识符引用调用了内部的函数 bar()。
// bar() 显然可以被正常执行。但是在这个例子中,它在自己定义的词法作用域以外的地方 执行。
复制代码

而闭包的“神奇”之处正是可以阻止foo()执行后被销毁的发生。事实上内部作用域依然存在,没有被回收。是 bar() 本身在使用这个内部作用域。
拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。
bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。
闭包比较关键的地方在于函数A执行完毕后,函数里的变量或参数并没有被回收而被其他函数B(常见的情况就是B在A内声明或定义)引用着。
第一个:foo执行完后,它没有返回函数,它的外面也没有其他函数引用着它的变量,它的变量被回收,所以不是闭包。
简单来讲, foo执行过程中,bar被执行,虽然它引用了 foo 中的 a,但在 foo 执行完之前, bar也已经执行完了,所以整个过程执行完以后,所有局部变量都沒有被当前存在的其它变量(对象)引用,已经被系统销毁了。
第二个:foo执行完后,它返回的函数(也就是bar)还引用着它的变量a,所以是闭包。

结语

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时 就产生了闭包。闭包内容还有很多,下次再做分享。


今天先到这,有疑问可以留言我会一一解答。
以上如有不足,请大家多多指点!!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/107679.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 最佳路径分析_最佳路径选择

    最佳路径分析_最佳路径选择最佳路径分析(作者:吴东梅,撰写时间:2019年1月17日)学习了GIS技术,让我们对地理信息系统有了一定的了解,也让我们知道了我们平时常用的导航系统里面的导航最佳路径里面的一些奥妙。现在就让我们了解一下如何制出最佳路径的方法并应用在我们自己的项目中吧!1、 先在SuperMapiDesktop9D把你的地图打开,然后点击上方导航条上面的交通分析,里面有一个拓扑构网,鼠标移到拓扑构网上…

    2022年8月24日
    8
  • 为什么香港服务器可以免备案

    为什么香港服务器可以免备案呢?这可能是许多朋友都回答不上来的话题,即使是经常使用香港服务器的用户也有部分人不清楚,那么今天我们就聊聊为什么香港服务器可以免备案:众所周知,我们国家实行的一国两制,所以香港地区在管理制度方面会和我们国内的不太一样,不仅是管理制度,包括法律,互联网使用规则也不一样。在国内,根据我国的互联网规定,网站所有者必须向国家有关部门申请实名备案,甚至部分网站还需要完成公安部备案,才能正常使用域名访问网站,且国内为净化互联网,也对许多内容进行限制;而香港则没有必须申请备案的要求,只要网站

    2022年4月8日
    66
  • 代码触发,手动触发touchstart事件,touch事件,click事件,自定义事件

    代码触发,手动触发touchstart事件,touch事件,click事件,自定义事件代码触发,手动触发touchstart事件,touch事件,click事件,自定义事件

    2022年6月19日
    23
  • linux怎样重启命令,Linux重启命令介绍

    linux怎样重启命令,Linux重启命令介绍下面介绍在Linux操作系统中重启和关闭相关的命令:shutdown、reboot、init、halt、poweroff、systemctl,你可以根据需要来选择适合的Linux命令关闭或重新启动系统。其中shutdown、halt、poweroff、reboot命令是用来停机、重启或切断电源,systemctl命令管理systemd,是Linux系统和服务器的管理程序。使用…

    2022年10月17日
    0
  • 二进制如何转十进制?_二进制转换为十进制的算法

    二进制如何转十进制?_二进制转换为十进制的算法1、计算机的数制介绍数制:计数的方法,指用一组固定的符号和统一的规则来表示数值的方法数位:指数字符号在一个数中所处的位置基数:指在某种进位计数制中,数位上所能使用的数字符号的个数位权:指在某种

    2022年8月5日
    2
  • 使用CityScapes数据集训练实例分割网络YOLACT

    使用CityScapes数据集训练实例分割网络YOLACT#2020开年第一篇,谁能预料新年伊始的世界如此脆弱,中国疫情肆虐,美伊箭拔弩张,英国愤懑脱欧,儿时的偶像科比和女儿也不幸离世,生命之渺小,生活之曲折,兄弟们,要充满阳光地活着啊,人间,值得。:-)今天还是鄙人生日,愿世间多点爱,与和平。上一篇介绍了博主用CityScapes数据集提取了五类实例,并转换成了COCO数据集的标注格式(将CityScapes数据集转换为COCO格式的实例分割数据集…

    2022年8月23日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号