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

前言

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修改对象中不存在的属性时,会将该属性泄露到全局作用

相关推荐
真的很上进40 分钟前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
噢,我明白了4 小时前
同源策略:为什么XMLHttpRequest不能跨域请求资源?
javascript·跨域
sanguine__4 小时前
APIs-day2
javascript·css·css3
关你西红柿子5 小时前
小程序app封装公用顶部筛选区uv-drop-down
前端·javascript·vue.js·小程序·uv
济南小草根5 小时前
把一个Vue项目的页面打包后再另一个项目中使用
前端·javascript·vue.js
小木_.5 小时前
【python 逆向分析某有道翻译】分析有道翻译公开的密文内容,webpack类型,全程扣代码,最后实现接口调用翻译,仅供学习参考
javascript·python·学习·webpack·分享·逆向分析
Aphasia3115 小时前
一次搞懂 JS 对象转换,从此告别类型错误!
javascript·面试
m0_748256566 小时前
Vue - axios的使用
前端·javascript·vue.js
m0_748256346 小时前
QWebChannel实现与JS的交互
java·javascript·交互
胡西风_foxww6 小时前
【es6复习笔记】函数参数的默认值(6)
javascript·笔记·es6·参数·函数·默认值