作用域&&变量提升&&变量声明区别

139 阅读5分钟

作用域

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

分两种:

-- 静态作用域(词法作用域),函数的作用域在函数定义的时候就决定了

-- 动态作用域,函数的作用域是在函数调用的时候才决定的

JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

JavaScript作用域

JavaScript 中主要有三种作用域:全局作用域、函数作用域和块作用域。查找变量时,会从当前作用域开始,逐级向上查找,直到找到为止。

  • 全局作用域

全局作用域是最外层的作用域,存在于整个程序中。在全局作用域中声明的变量和函数可以在程序的任何地方访问。

  • 函数作用域

函数作用域是指在函数中声明的变量和函数,只能在该函数内部访问。函数作用域是JavaScript中最常见的作用域。

  • 块作用域

ES6引入了 let 和 const 关键字,它允许开发者创建块级作用域。

块级作用域是在代码块( if 语句, for 循环等)内部定义的作用域。

let 和 const 生命的变量只能在他们被定义的块级作用域内访问。

var scope = "global scope"
function checkscope(){
  var scope = "local scope"
  function f(){
    return scope
  }
  return f()
}
checkscope ()   // local scope

var scope = "global scope"
function checkscope(){
  var scope = "local scope"
  function f(){
    return scope
  }
  return f
}
checkscope()()    // local scope

JavaScript 采用词法作用域,函数的作用域在函数定义时候就决定了,所以结果都是“local scope”。

变量提升(Hoisting)

变量提升是指JavaScript引擎在编译阶段将变量和函数的声明提升到其所在作用域的顶部。注意: 只有声明会被提升,而赋值不会被提升。

  • var 声明的变量提升
console.log(str)   // undefined
var str = "hello world"
console.log(str)   // "hello world"

解读:在这个例子中, str 的赋值语句在 console.log 之后,但由于变量提升, str 实际上在编译阶段已经被提升到全局作用域顶部。因此,第一次 console.log(str) 输出的是 undefined 。

  • 函数提升

函数提升不仅包括声明的提升,还包括函数体的提升。这意味着可以在函数声明之前就调用该函数。

console.log(add(1,2))
function add(a,b){
  return a+b
}
// 3

解读:add 函数的定义在调用之后,但由于函数提升,add 函数在编译阶段已经被升到了顶部,因此可以正常使用。

  • let const 不会提升

与 var 声明不同, let 和 const 声明的变量不会被提升。如果在声明之前就使用这些变量,会抛出错误。

console.log(add(1,2))
let add = function (a,b){
  return a+b
}
// 会报错

var、let、const区别?

  1. 作用域
  • var是函数作用域或者全局作用域

在函数内部声明的var变量仅在该函数内有效;若在块级(如iffor)中声明,变量会泄漏到外层作用域。

 if (true) {
    var a = 10;
  }
  console.log(a); // 10(泄漏到全局)
  • let / const 块级作用域

仅在大括号{}内有效,例如在for循环中声明的变量不会污染外层作用域。

if (true) {
  let b = 20;
}
console.log(b); // ReferenceError: b未定义
  1. 重复声明
  • var 允许在同一作用域内重复声明,后声明的变量会覆盖前者。
var c = 1;
var c = 2; // 合法
  • let / const:同一作用域内不可重复声明
let d = 3;
let d = 4; // SyntaxError: 标识符已声明
  1. 变量提升
  • var声明会被提升到作用域顶部,但初始值为undefined
 console.log(e); // undefined
  var e = 5;
  • let / const:存在 暂时性死区(TDZ) ,声明前访问会报错
console.log(f); // ReferenceError: f未初始化
let f = 6;
  1. 重新赋值
  • var / let:可重新赋值
let g = 7;
g = 8; // 合法
  • const:声明时必须初始化,且不可重新赋值(但对象属性可修改)
const h = { name: "Alice" };
h.name = "Bob"; // 合法(修改属性)
h = {}; // TypeError: 常量不可重新赋值
  1. 初始化要求
  • var/let可以只声明,而后赋值
var a;
let b;
console.log(a,b)   // undefined undefined
a = 'a';
b = 'b';
console.log(a,b)   // a b
  • const必须声明同时赋值
const a;
console.log(a)   // 报错

cons b = 'b'
console.log(b)   // 'b'
  1. 全局作用域下行为
  • var:在全局作用域声明的变量会成为window对象的属性
var i = 9;
console.log(window.i); // 9
  • let / const:不会绑定到window对象
let j = 10;
console.log(window.j); // undefined

总结:

特性varletconst
作用域函数/全局作用域块级作用域块级作用域
重复声明允许不允许不允许
变量提升提升(初始undefined暂时性死区(报错)暂时性死区(报错)
重新赋值允许允许不允许(对象属性可修改)
初始化要求可选可选必须初始化
全局绑定

为什么const可以修改对象属性值?

这里需要了解‘基本数据类型’和‘引用数据类型’

  1. 基本数据类型

基本数据类型包括:NumberStringBooleanNullUndefinedSymbol(ES6)、BigInt(ES11)。

基本数据类型的变量保存在栈区中,基本数据类型的值直接在栈内存中存储,值与值之间是独立存在的,修改一个变量不会影响其他的变量。

  1. 引用数据类型

引用数据类型包括:Object(含 ArrayFunctionDateRegExp 等)。

引用数据类型的值是同时保存在栈内存和堆内存的对象,栈区保存了对象在堆区的地址,如下:

const person1 = {
  id:1,
  name:'student1',
};
const person2 = {
  id:2,
  name:'student2',
}
const arr = [
  person1,
  person2,
]
console.log(arr)    // [{id:1,name:'student1'},{id:2,name:'student2'}]

person1.name = 'student3'
console.log(arr)    // [{id:1,name:'student3'},{id:2,name:'student2'}]
栈区堆区
person1堆区地址---------->object1---{}
person2堆区地址---------->object2---{}
arr堆区地址---------->object3---[]

const 声明的只是栈区内内容不变,基本数据类型保存在栈区中不可改变;引用数据类型在栈区内保存的地址不可改变,堆区内实际属性可以改变。

所以const声明的基本数据类型不允许重新赋值;引用数据类型不可以修改栈区内保存的堆区地址,可以修改堆区内的数据。

注意: 数组允许修改堆内数据(增删),禁止改变堆区地址(重新赋值)。