你不知道的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)


相关推荐

  • 安卓抓包神器_安卓抓包工具哪个好

    安卓抓包神器_安卓抓包工具哪个好好用的Android抓包神器VNET

    2022年10月29日
  • 培训java机构排行榜_北京java培训班哪家好

    培训java机构排行榜_北京java培训班哪家好要说国内的就业市场中,薪资高待遇好的当属Java软件开发岗位了,因Java软件开发的职业发展稳定获得了不少从业者的追捧。对于想要学习Java的同学来说,培训就成了进入这个行业的敲门砖,很多零基础不太懂这个行业的同学,想要找一家比较好的培训机构,都会在网络上搜索一些像“北京Java培训机构排名”这样的词,能够在其中得到一些参考,下面是通过行业口碑,Java就业率,诚信度,课程体系,Java师资,教学质量,授课方式等多方面得出的北京Java培训机构排名,参考意义很强。就算我们有了排名上的参考,也需.

  • 华为云服务器手机密码找回,忘记华为账号密码怎么办?两招就能帮你解决

    华为云服务器手机密码找回,忘记华为账号密码怎么办?两招就能帮你解决每天跟我们生活息息相关的账号密码,实在是太多太多了。银行卡密码、支付密码、游戏账号密码、各种APP账号密码等等……账号密码太多,也导致了我们有时候会忘记某些账号密码。那么问题来了,如果忘记了华为账号密码,怎么破?别担心,官维君教大家两招如何找回密码,一起来看看吧!第一招:登录华为云服务官网找回密码登录华为云服务官网☛https://cloud.huawei.com/,点击“忘记密码”,然后根据提示…

  • 光棍节程序员闯关秀-解密

    光棍节程序员闯关秀-解密前言最近看到的了一个比较有意思的解密游戏,这解密的过程中确实花了不少的功夫,后来通过搜索才发现这是好几年前的题目,但是题目虽然是老的,但技术是没有过时的,不得不承认其中有些问题我确实解答不上来,不过解密的过程还是很有意思的,在此记录一下,游戏地址为光棍节程序员闯关秀第1关(总共10关)有兴趣的可以自己玩一下,有些题目还是很需要专业知识的,具体的解题步骤网络上一大堆,不过我发现一个问题,你们为什么不把

  • 怎么理解泊松分布_泊松分布公式

    怎么理解泊松分布_泊松分布公式1甜在心馒头店公司楼下有家馒头店:每天早上六点到十点营业,生意挺好,就是发愁一个事情,应该准备多少个馒头才能既不浪费又能充分供应?老板统计了一周每日卖出的馒头(为了方便计算和讲解,缩小了数据):均值为:按道理讲均值是不错的选择(参见如何理解最小二乘法?),但是如果每天准备5个馒头的话,从统计表来看,至少有两天不够卖,的时间不够卖:你“甜在心馒头店”又不是…

  • 线程间通信的几种方法_c语言线程函数

    线程间通信的几种方法_c语言线程函数线程间如何通信/同步?此前小编给大家介绍了进程间通信的方法,于是一些伙伴又好奇线程间的通信及同步方法,没关系,下面小编就继续给大家科普下线程间通信及同步的方法。线程间通信及同步方法介绍:一、线程间的通信方式1、使用全局变量主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile。2、使用消息实现通信在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息…

发表回复

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

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