关于js作用域

53 阅读6分钟

作用域

全局作用域

最外层函数和最外层函数外面定义的变量拥有全局作用域。

所有未定义直接赋值的变量自动声明为全局作用域,任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。例如:

var hi = "hello js"
function f() {
  console.log(hi) // hello js
}
f()
console.log(hi) 

所有window对象的属性拥有全局作用域

弊端:过多的全局作用域变量会污染全局命名空间,容易发生命名冲突。

函数作用域

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

函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到。

作用域是分层的,内层作用域可以访问到外层作用域,反之不行。

function f() {
  var hi = "hello js"
  console.log(hi); // hello js
}
f();
console.log(hi); //报错

块级作用域

let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码中创建。由{}包裹的代码片段。

let和const声明的变量不会由变量提升,也不可以重复声明。只有var才可以变量提升。看如下例子:

var a = 100;
const f = ()=>{
  console.log(a) //100
}
f();
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
console.log(a) //100
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
console.log(a) //100
var a = 300;
console.log(a) //300

在循环中比较适合绑定块级作用域,这样就可以把声明的计数变量限制在循环内部。

作用域链

在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己的作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。(说白了作用域的集合就是作用域链)

作用域链的作用是保证执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

当在作用域内访问 变量/方法 的时候,会找离自己最近的那个 变量/方法(就近原则)

function f() {
  var a = 10;
  console.log(c) //10
  function f1() {
    var b = 20;
    console.log(a) //10
    function f2() {
      var a = 30;
      console.log(a) //30
      console.log(b) //20
    }
    f2();
  }
  f1();
}
f();
console.log(a); //报错
console.log(b);	//报错

从作用域链的结构可以看出,函数在执行的过程中,先从自己内部寻找变量,如果找不到,再从创建当前函数所在的作用域去找,从此往上,也就是向上一级找,直到找到全局作用域还是没找到。

在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上面案例所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。## 全局作用域

最外层函数和最外层函数外面定义的变量拥有全局作用域

所有未定义直接赋值的变量自动声明为全局作用域

所有window对象的属性拥有全局作用域

弊端:过多的全局作用域变量会污染全局命名空间,容易发生命名冲突。

函数作用域

函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到。

作用域是分层的,内层作用域可以访问到外层作用域,反之不行

块级作用域

let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码中创建。由{}包裹的代码片段。

let和const声明的变量不会由变量提升,也不可以重复声明。只有var才可以变量提升。看如下例子:

var a = 100;
const f = ()=>{
  console.log(a) //100
}
f();
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
console.log(a) //100
var a = 100;
const f = ()=>{
  console.log(a) //undefined
  var a = 200;
  console.log(a) //200
}
f();
console.log(a) //100
var a = 300;
console.log(a) //300

在循环中比较适合绑定块级作用域,这样就可以把声明的计数变量限制在循环内部。

作用域链

在当前作用域中查找所需变量,但是该作用域没有这个变量,那这个变量就是自由变量。如果在自己的作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。(说白了作用域的集合就是作用域链)

作用域链的作用是保证执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

当在作用域内访问 变量/方法 的时候,会找离自己最近的那个 变量/方法(就近原则)

function f() {
  var a = 10;
  console.log(c) //10
  function f1() {
    var b = 20;
    console.log(a) //10
    function f2() {
      var a = 30;
      console.log(a) //30
      console.log(b) //20
    }
    f2();
  }
  f1();
}
f();
console.log(a); //报错
console.log(b);	//报错

从作用域链的结构可以看出,函数在执行的过程中,先从自己内部寻找变量,如果找不到,再从创建当前函数所在的作用域去找,从此往上,也就是向上一级找,直到找到全局作用域还是没找到。

在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上面案例所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。