你不知道的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,所以是闭包。

结语

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


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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 时滞电力系统matlab,时滞电力系统稳定性分析

    时滞电力系统matlab,时滞电力系统稳定性分析工程中许多动力系统可由状态变量随时间演化的微分方程来描述。其中相当一部分动力系统的状态变量之间存在时间滞后的现象,即系统的演化趋势不仅依赖于系统当前的状态,也依赖于系统过去某一时刻或若干时刻的状态,我们将这类动力系统称为时滞动力系统。近年来,时滞动力系统已成为许多领域的重要研究对象。在电路、光学、神经网络、生物环境与医学、建筑结构、机械等领域,人们对时滞动力系统作了大量的研究,取得了许多重要成果,…

  • Linux基础命令—gunzip「建议收藏」

    Linux基础命令—gunzip「建议收藏」Linux基础命令—gunzip

  • 宽字节注入是什么_sqlmap宽字节注入

    宽字节注入是什么_sqlmap宽字节注入在一个CTF练习的网站,看到了一个宽字节注入的题目,我是一个web萌新,没什么经验,一开始也没有想到是宽字节,还是一位朋友给我提到的,让我猛然大悟,咳咳。。。做一些总结。练习题目网站地址:http://ctf.bugku.com。一、了解一下宽字节注入原理前提:1、我们都知道,在防御SQL注入的时候,大多说都是使用的过滤特殊字符,或者使用函数将特殊字符转化为实体,就是说在字符转义,添加‘\’。这里…

    2022年10月14日
  • 超简单的windows发包工具—小兵以太网测试仪

    超简单的windows发包工具—小兵以太网测试仪小兵以太网测试仪是一款windows平台下的发包工具。该软件小巧、易用、开源、免费。该软件的功能有:各种常见报文(包括arpipicmpudptcp等)的编辑与发送发包速率控制抓包对抓到的包进行修改编辑及发送将报文导出为tcpdump/ethereal/wireshark存档(pcap格式)从tcpdump/ethereal/wireshark存档导入报文发送巨帧(j

  • pycharm社区版与专业版区别_vs社区版和企业版区别

    pycharm社区版与专业版区别_vs社区版和企业版区别【时间】2018.09.22【题目】pyCharm专业版和社区版的区别以及如何查看其版本【参考链接】https://zhidao.baidu.com/question/584331885111670725.html一、pyCharm专业版和社区版的区别pycharm产品主页:https://www.jetbrains.com/pycharm/有说明1、专业版是收…

  • 对ajax的理解面试题_javascript面试题大全

    对ajax的理解面试题_javascript面试题大全前两天面试的时候,面试官问我,你掌握的技能是Ajax,那你给我讲一下它的基本原理吧!妈呀,瞬间脑子空白。当时在门口背了好久的网络知识点,一时竟然说不吃话,只记得什么异步通信,同步数据,面试官的笑让我不寒而栗…………今天整体的整理一遍Ajax的知识点吧。…

发表回复

您的电子邮箱地址不会被公开。

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