专门针对小白 聊聊关于作用域的那点事儿

前言

JavaScript是一种弱类型,动态语言。

弱类型,动态语言:是运行时才确定数据类型的语言,不用声明变量的类型,比如我们用var声明一个变量时编译器会自动帮我们识别并记录好这个变量的类型,在运行时才确定这个数据的类型。
首先,我们要了解的一个对象就是引擎,在Javascrip里面,这个引擎就像是一个霸道总裁一样,十分简单粗暴,负责代码从头到尾的运行。而一个霸道总裁就必须有一个十分贴心且负责的秘书了,这时候我们又要引出一个对象叫做编译器,负责代码的分析和编译,也可以说是找到当前域的有效标识符。这时候可能有小伙伴问了,什么是域?切莫抓耳挠腮,让我们直接上才艺,加上通俗易懂的代码,且看我们轻松拿捏作用域。

作用域(scope)

定义:作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。

通俗来讲:作用域在一个函数或变量在声明时就已经确定了,就是这个函数或变量作用的范围。

1.作用域的范围

在JS中,一共有两种作用域(ES6之前):

  • 全局作用域:作用于整个全局

  • 函数作用域(局部作用域):作用于函数内的代码环境

在 ES6 版本时Javascript进行了一次大更新,并且新增了一个域:

  • 块级作用域
js 复制代码
var a = 1;
function foo(){
    var b = 2;
    console.log(a);
}
foo();

比如我们在开头声明了一个全局变量a,并且将数字1赋值给了这个全局变量,那么这个变量的作用域就是全局作用域

再往下看,我们在函数foo内部再声明了一个局部变量 b 并且将数字2赋值给了这个局部变量,那么这个变量的作用域就是局部作用域

2.作用域的访问关系

在内部作用域中是可以访问到外部作用域的变量的,但是在外部作用域中无法访问到内部作用域的变量。

JavaScript复制代码 复制代码
var a = 1;
function foo(){
    var b = 2;
    console.log(a);
}
console.log(b); // ReferenceError: b is not defined
foo(); // 1

接下来我们来执行上面这个代码,运行后发现代码报错:ReferenceError: b is not defined

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

而如果我们直接执行以上代码 将会打印出 1
这就说明外层作用域无法访问内层作用域里的变量 而内层作用域既可以访问内层作用域里的变量也可以访问外层作用域的变量。
代码的编译总是发生在运行之前的 而编译器(秘书)要干的事情就是到当前域中的有效标识符

3.声明提升

var 声明的变量存在声明提升

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

这里我们发现在函数foo里面 var a = 2 放在了输出语句的后面 如果我们执行代码会输出什么呢?答案是 undefined 因为在JS里var 声明变量存在声明提升,最后代码就相当于是

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

所以这是var声明变量一个违反我们基本常识的缺点

这时候我们需要用ES6版本更新的let声明来声明变量

因为当我们用let声明变量时 如果声明语句在执行语句之后编译器会直接给我们报错

js 复制代码
function foo() {
    console.log(a);
    let a = 2 ; //Cannot access 'a' before initialization
}
foo()

但是let声明也会有一个缺点 我们称为暂时性死区

js 复制代码
let a = 2;
function foo() {
    console.log(a);
    let a = 2 ; //暂时性死区
}
foo()

我们明明在全局声明了变量a并且赋予值 但是最终输出依然是报错

这还是因为let声明的特点

编译器在识别有效标识符的时候读到了a变量被声明了两次,违背了let规则所以直接报错。

4.{let}形成的块级作用域

js 复制代码
if(true){
   let a = 1;
}
console.log(a);

这种情况我们依然无法打印出来,为什么呢我们来分析一下

我们开头提到在ES6之前JS只有两种域:全局作用域和函数作用域

但是在ES6版本时 JS又增加了一种域:块级作用域

我们这里let所在的域就是块级作用域

然后又回到变量的查找是可以从内部作用域往外部作用域查找的,不能由外到内查找

所以我们这里是输出 ReferenceError:a is not defined

5. eval & 欺骗词法作用域

js 复制代码
function foo(str,a){
 eval(str)//var b = 3 欺骗词法作用域
 console.log(a,b);
}
foo('var b = 3',1)

欺骗相信大家都很了解是什么意思吧 just tell a lie

但是欺骗词法作用域是什么意思呢?

我们在上述代码可以看到在函数foo里面我们传入了两个形参str和a

在全局我们调用函数的时候传入了两个实参 第一个是一个字符串 第二个是一个number 1

我们按照常识来想如果执行肯定报错 b is not defined

但是这里我们在函数里面还加了一个eval函数并传入了形参str

eval函数的作用是把对应的字符串解析成js代码并运行

所以最后我们输出的结果是 1 3

6. with 修改对象导致属性泄露

js 复制代码
let obj = {
    a : 1,
    b : 2,
    c : 3
}
console.log(obj);

这段代码运行的结果显然是{a: 1, b: 2, c: 3}

js 复制代码
let obj = {
    a : 1,
    b : 2,
    c : 3
}

with(obj){
    a = 2,
    b = 3,
    c = 4
}
console.log(obj);

我们可以用with方法对obj对象进行键值的修改

输出的结果是 {a: 2, b: 3, c: 4}

js 复制代码
function foo(obj){
    with(obj){
      c = 1
    }
  }
  let obj = {
    a : 1
  }
  foo(obj)
  console.log(c);

而当我们将with方法放进一个函数作用域中会发生什么 能打印出什么呢?

运行结果为 1

是不是觉得有点出乎意料 这不是违背了变量的查找是可以从内部作用域往外部作用域查找的,但是不能由外到内查找这条规则吗

答案是当然没由违背 因为当with修改对象中不存在的属性时,会将该属性泄露到全局作用

最终编译器从全局中读取到了c的值 最终由我们的v8引擎执行并且成功

综上而言

  • js 是弱类型,动态语言

  • 代码编译总是发生在执行前的

  • 变量的查找是可以从内部作用域往外部作用域查找的,不能由外到内查找

  • 编译要干的事,是找到当前域中的有效标识符

  • var 声明的变量存在声明提升

  • eval() 将原本不属于此处的代码搬到此处,让代码执行的就像天生就定义在此处一样

  • with() {} :当with修改对象中不存在的属性时,会将该属性泄露到全局作用

相关推荐
四喜花露水10 分钟前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
前端Hardy19 分钟前
HTML&CSS: 实现可爱的冰墩墩
前端·javascript·css·html·css3
web Rookie1 小时前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
工业甲酰苯胺1 小时前
C# 单例模式的多种实现
javascript·单例模式·c#
程序员爱技术5 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
悦涵仙子6 小时前
CSS中的变量应用——:root,Sass变量,JavaScript中使用Sass变量
javascript·css·sass
兔老大的胡萝卜6 小时前
ppk谈JavaScript,悟透JavaScript,精通CSS高级Web,JavaScript DOM编程艺术,高性能JavaScript pdf
前端·javascript
cs_dn_Jie9 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic10 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿10 小时前
webWorker基本用法
前端·javascript·vue.js