JavaScript的作用域机制

一、什么是作用域

通俗来讲,作用域是变量存储在哪里,以及怎么取变量的一套规则。

 

作用域与编译器、引擎配合,在代码运行过程中发挥了至关重要的作用。

比如以下代码:

var a = 2;

function add(b) {
    console.log(a + b);
}

add(3);

考虑这三者的分工那就是这样的:

编译器:查看全局作用域中是否有a这个变量,结果没有,于是在全局作用域中定义这个变量。查看全局作用域中是否有add这个函数,结果没有,于是在全局作用域中定义这个函数。然后是add函数作用域,查看函数作用域中是否有b变量,结果没有,于是在add的作用域中定义b这个变量。将处理之后的代码给到引擎。

引擎:询问全局作用域中是否存在a,找到了,于是给a赋值。询问全局作用域中是否存在add,存在了,取值并调用,询问add的作用域中是否存在b,存在,于是将3赋值给b。

然后询问add的作用域是否存在b,存在,于是取到b的值。然后询问add的作用域是否存在a,不存在,再问全局作用域,存在,于是取到a的值。打印两者相加的结果。

 

在这里add的作用域就涉及到了一个叫 作用域的嵌套 ,当在一个作用域找不到变量的时候,会一直往上层继续寻找,直到全局作用域,无论找没找到都会停止查找的过程。

 

作用域的嵌套还会引发一种叫“遮蔽效应”,如果在里层作用域里定义了一个变量,那么外层的同名变量就无法访问到。不过全局变量可以通过 window.xx 的方式绕开这种遮蔽

 

二、作用域模型

作用域大体分为两种模型,一种是词法作用域,这种是JavaScript所采用的模型。另一种是动态作用域,也有很多编程语言使用。

 

词法作用域是根据代码书写的位置来决定的。在词法分析阶段,会保持作用域不变。(当然也有一些例外,后面细说)

参考以下代码:

function add(b) {
    console.log(a + b);
}


var a = 1;
add(2);

在这里有2个作用域:

1. 全局作用域,声明变量有add和a

2. add函数作用域,声明变量有b

 

在使用了with和eval之后,会改变原本的词法作用域。总的来说,with创建了一个全新的词法作用域,而eval修改了原有的词法作用域

 

1. with

在这里传入一个对象,然后with会以这个对象创建出一个新的词法作用域,对象的属性就是词法作用域的变量。

with(obj) {
    a = 3;
}

在这里传入obj,如果obj原先有a属性,那么会被赋值为3。如果obj原先没有a属性,那么会按照词法作用域的寻找过程,现在obj的作用域里找,再往上全局作用域,在非严格模式下会声明一个全局变量a并赋值为3。

 

2. eval

eval可以接受字符串代码,然后在运行的时候会生成代码替换原先eval所在的位置,就如同原本代码就写在那里一样

eval("var a = 4");

这里原本词法作用域中没有a变量,用了eval之后词法作用域多了a变量,所以eval修改了词法作用域

 

这两种影响词法作用域的做法并非不好,首先是因为严格模式并不支持,而且还会引发一系列的性能问题。

JavaScript代码在编译的时候,会采取一些优化的策略,会对代码的词法进行静态分析,根据代码和变量的位置做一些优化,而eval和with破坏了这种策略。eval使得引入的变量和代码变得不确定,with使得传入的对象不可预知

 

所以在写代码的时候要尽量减少这两种做法。

 

三、函数作用域

函数作用域有利于避免变量的污染

var a = 3;

function hide() {
    var b = 4;
}

console.log(b);

hide创建了一个函数作用域,里面包含了b变量,这样做避免b污染全局变量。

 

但是通常用一个函数声明来隐藏变量不是很好,因为首先你声明一个函数名,这个函数名就会污染全局,而且你还必须调用函数。所以自然有疑问产生就是,有没有更加方便的,不污染全局又能不必分次调用的方式。

答案是,有的!这就是IIFE(立即执行函数表达式)

 

IIFE

形如下面的结构:

(function() {
    var a = 1;
    var b = 2;
})()

这就是立即执行函数表达式。

给function(){}加上个括号,是将函数声明转为函数表达式,而且这个函数是匿名函数,不会污染全局。

转为表达式之后在后面加上括号实现自动调用

 

四、块级作用域

学习JavaScript,大家应该对JS的块级作用域没什么印象,而实际上有没有这个东西呢?是有的。

 

1. try/catch

平时用的 try...catch(e) {...} 中,

{...}这里面的内容属于块级的作用域

 

2. let 

ES6开始,let声明的变量都会属于块级作用域。const同样也有块级作用域的概念,但是声明的变量不能更改

 

那么块级作用域的作用是啥呢?

块级作用域可以避免一些不必要的变量泄露,比如最典型的for循环

for(let i = 0; i < 12; i++) {
    console.log(i);
}

如果用var定义而不是let的话,那么i会泄露到全局中,i这个变量主要是用在for循环中,带到全局并没有什么意义,所以用let可以让i避免泄露到全局。

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值