让前端飞Web前端之路互联网科技

理解JavaScript的作用域

2019-06-11  本文已影响6人  子瑜说IT

作用域

作用域嵌套与作用域链

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。作用域嵌套的查询规则:

这样由多个执行上下文的变量对象构成的链表就叫做作用域链。

function foo(b) {
 // 变量a在全局作用域下查询得到的
 console.log(a + b) // 4
}
var a = 2
foo(2)

image

查询异常

var a = 10
function sum() {
 // b 是没有显式声明的,但被隐式创建为全局变量
 b = 20
 return a + b
}
console.log(sum()) // 30
console.log(c) // ReferenceError: c is not defined
// 因为c是未定义的变量,无法使用

词法作用域(静态作用域)

而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。

image

词法作用域的经典例子

请思考以下2个例子输出的结果

// 例子1
var scope = "global scope";
function checkscope() {
 var scope = "local scope";
 function f() {
 return scope;
 }
 return f();
}
checkscope();
// 例子2
var scope = "global scope";
function checkscope() {
 var scope = "local scope";
 function f() {
 return scope;
 }
 return f;
}
checkscope()();

上面两段代码的结果都输出:local scope,因为 JS 采用的是词法作用域,函数的作用域基于函数创建的位置。

欺骗词法作用域

eval() 函数可以接受一个字符串,并执行其中的的 JS 代码。

function foo(str, a) {
 eval(str) // 欺骗作用域,引擎在此声明了:var b = 2,所以能计算以下结果

 console.log(a + b) // 3
}
var b = 1
foo('var b = 2', 1)

with() 语句通常被当作重复引用同一个对象中多个属性的快捷方式,可以不需要重复引用对象本身。

function foo(obj) {
 with (obj) {
 b = 4
 a = 2
 }
}
var obj = {
 b: 3
}
foo(obj)
console.log(obj.a) // undefined
console.log(obj.b) // 4
console.log(a) // 2,a被当前全局变量泄露到全局作用域上了

image

为什么最后能输出a的值为2?

原因是把 obj 对象传入函数内,obj 对象没有 a 属性,所以 obj.a 的值是 undefined,却在 with()语句中的 a 被当作全局变量隐式声明了,而且进行了赋值为2。

函数作用域

函数作用域内的变量或者内部函数,对外都是封闭的,从外层的作用域无法直接访问函数内部的作用域,否则会报引用错误异常。解决方法:闭包。

function f1() {
 var a = 1;
 var b = 2;
 var c = 3;
}
console.log(a, b, c) // ReferenceError: a, b, c is not defined
// 原因变量a,b,c是定义在函数内部的变量,外部作用域是无法访问的。

全局作用域

最外层的全局作用域,任何地方都可以访问得到。在最外层作用域下使用 var 关键字会定义全局变量,也就是说会挂载在 window 对象上,或者不使用关键字 var、let、const 直接对变量名字进行赋值,JS也会自动为其创建为全局变量。

var a = 10;
function f1() {
 b = 20
 function f2() {
 c = 30
 console.log(a) // 10
 }
 f2()
}
f1()
// b和c变量被隐式声明到全局变量了,所以能访问到,这也叫变量提升机制
console.log(b) // 20
console.log(c) // 30
// 但a,b,c也被挂载在window对象(全局作用域)上面了
console.log(window.a) // 10
console.log(window.b) // 20
console.log(window.c) // 30

image

块级作用域

块级作用域指在代码块 {} 里面定义的变量,只会在当前代码块有效,如果外层作用域下想访问该变量,会报引用错误异常。

使用关键字 let 或 const 定义块级作用域的变量。

for (let i = 0; i < 10; i++) {
}
console.log(i) // ReferenceError: i is not defined
// 因为i只能在for循环内部有效,外部作用域是访问不到的。

变量提升机制

先声明,后赋值

JS变量的声明和赋值是2个不同的步骤,比如:

a = 10
var a
console.log(a) // 10

JS引擎会将 var a 和 a = 10 当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务:

// 编译阶段的任务
var a
// 执行阶段的任务
a = 10
console.log(a) // 10

变量提升

function getValue(condition) {
 console.log(value) // 不会报错
 if (condition) {
 var value = "blue";
 return value;
 } else {
 return null;
 }
}
getValue(undefined == null)

实际上以上代码的 value 已经被变量提升了:

function getValue(condition) {

 // 编译阶段的任务
 var value;

 console.log(value) // undefined
 if (condition) {
 // 执行阶段的任务
 value = "blue";
 return value;
 } else {
 return null;
 }
}
getValue(undefined == null)

image

函数优先

foo() // 3
function foo() {
 console.log(1)
}
var foo = function () {
 console.log(2)
}
function foo() {
 console.log(3)
}
foo() // 2 函数表达式的提升情况

为什么呢?原因:

  1. 函数声明和变量声明都会被提升,但是出现在有多个“重复”声明的代码中,函数会首先被提升,然后才是变量。
  2. 相同函数名字,后面函数覆盖前面的函数。

实际上以上代码可以看成这样:

function foo() {
 console.log(3)
}
var foo // 被忽略了
foo()
console.log(foo) // [Function: foo]

image
最后,给大家推荐一个前端学习进阶内推交流群685910553前端资料分享),不管你在地球哪个方位,
不管你参加工作几年都欢迎你的入驻!(群内会定期免费提供一些群主收藏的免费学习书籍资料以及整理好的面试题和答案文档!)

如果您对这个文章有任何异议,那么请在文章评论处写上你的评论。

如果您觉得这个文章有意思,那么请分享并转发,或者也可以关注一下表示您对我们文章的认可与鼓励。

愿大家都能在编程这条路,越走越远。

上一篇 下一篇

猜你喜欢

热点阅读