一文小小总结 作用域 作用域链 词法作用域和欺骗词法

一、作用域

作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合。换句话说,作用域决定了代码区块中变量和其他资源的可见性。

function myFunction() {
    let inVariable = "函数内部变量";
}
myFunction();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined

上述例子中,函数myFunction内部创建一个inVariable变量,当我们在全局访问这个变量的时候,系统会报错。这就说明我们在全局是无法获取到(闭包除外)函数内部的变量。

我们一般将作用域分成:

  • 全局作用域
  • 函数作用域
  • 块级作用域
全局作用域

任何不在函数中或大括号中声明的变量,都在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。

// 全局变量
var greeting = 'Hello World!';
function greet() {
  console.log(greeting);
}
// 打印 'Hello World!'
greet();
函数作用域

函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。这些变量只能在函数内部访问,不能在函数以外去访问

function greet() {
  var greeting = 'Hello World!';
  console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错: Uncaught ReferenceError: greeting is not defined
console.log(greeting);
块级作用域

ES6引入了letconst关键字,和var关键字不同,在大括号中使用letconst声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。

{
  // 块级作用域中的变量
  let greeting = 'Hello World!';
  var lang = 'English';
  console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
Let、const和Var的区别
  • var是全局作用域(对于大括号中来说),let 只在当前代码块内有效。

  • let变量不能重复声明,var可以

  • var有变量提升,let没有变量提升(let必须先声明再使用)

  • 所以在for循环时,用var初始化i只会得到最后一轮循环的值,因为var是全局的只有一个,用let初始化则可以得到每一轮的值,为什么let可以?因为js引擎内部会记住上一轮的值,下一轮在此基础上计算

  • 另外, for 循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

    for (let i = 0; i < 3; i++) {
    let i = 'abc';
    console.log(i);
    }
    // abc abc abc
    
暂时性死区

只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响

var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}

上面代码中,存在全局变量 tmp ,但是块级作用域内 let 又声明了一个局部变量 tmp ,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错。

ES6明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用 let 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。

typeof x; // ReferenceError
let x;
上面代码中,变量 x 使用 let 命令声明,所以在声明之前,都属于 x 的“死区”,只要用到该变量就会报错。因此, typeof 运行时就会抛出一个 ReferenceError 。

作为比较,如果一个变量根本没有被声明,使用 typeof 反而不会报错。
typeof undeclared_variable // "undefined"

块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp = ...;
...
}());
// 块级作用域写法
{
let tmp = ...;
...
}

js立即执行函数主要是用于隔离作用域

img

二、词法作用域

我们先来看下面这个栗子

var a = 2;
function foo(){
    console.log(a)
}
function bar(){
    var a = 3;
    foo();
}
bar()

大家认为应该输出多少呢?答案是2
在这里插入图片描述

词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,JavaScript 遵循的就是词法作用域。相同层级的 foobar 就没有办法访问到彼此块作用域中的变量,所以输出2。

注意:重点是 被创建时就确定好了,而不是执行时。也就是说在创建foo的时候,函数里没有找到a,那么就到外面去找,此时a=2,ok,已经确定好了。如果我们把var a = 2;删除掉,会直接报错a is not defined.看起来foo在bar声明了a=3后可以找到3,但其实在创建时,js怎么知道你有个bar里调用3呢?此时foo里找不到a的定义,外面也没有,当然就未定义咯。

在此强调,词法作用域就是作用域是由书写代码时函数声明的位置来决定的。编译阶段就能够知道全部标识符在哪里以及是如何声明的,所以词法作用域是静态的作用域,也就是词法作用域能够预测在执行代码的过程中如何查找标识符。

eval()和with可以通过其特殊性用来“欺骗”词法作用域,不过正常情况下都不建议使用,会产生性能问题。同时严格模式下也无法生效。了解即可。

首先了解一下:遮蔽效应:在多层的嵌套作用域中可以定义同名的标识符,即内部的标识符“遮蔽”了外部的标识符

eval(…):对一段包含一个或多个声明的“代码”字符串进行演算,并借此来修改已经存在的词法作用域(在运行时)

function foo(str, a) {
  eval( str ); // 欺骗!相当于 var b = 3;
  console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
//严格模式下,eval(..) 在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域

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 块可以将一个对象处理为词法作用域,但是这个块内部正常的 var声明并不会被限制在这个块的作用域中,而是被添加到 with 所处的函数作用域中。
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 被泄漏到全局作用域上了!
//当我们传递 o1 给 with 时,with 所声明的作用域是 o1,而这个作用域中含有一个同 o1.a 属性相符的标识符。但当我们将 o2 作为作用域时,其中并没有 a 标识符,因此进行了正常的 LHS 标识符查找。 o2 的作用域、foo(..) 的作用域和全局作用域中都没有找到标识符 a,因此当 a=2 执行时,自动创建了一个全局变量(因为是非严格模式)

三、作用域链

当在Javascript中使用一个变量的时候,首先Javascript引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。

如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值