这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
JS作用域是一套变量查找规则,用于确定在代码执行的时候在能够被访问的范围内如何查找变量,任何语言都有作用域的概念,在JS的世界中,作用域分为全局作用域、函数作用域和块级作用域
全局作用域:在全局作用域中的对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期。
函数作用域:在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问。函数执行结束之后,函数内部定义的变量会被销毁。
块级作用域:使用一对大括号包裹的一段代码,比如函数、判断语句、循环语句,甚至单独的一个{}都可以被看作是一个块级作用域。
函数作用域
函数中定义的变量叫作函数变量,这个时候只能在函数内部才能访问到它,所以它的作用域也就是函数的内部,称为函数作用域
const getName = () => {
var name = 'nordon';
console.log(name)
}
const fn = () => {
console.log(name)
}
getName();
fn();
变量name是定义在getName函数中,其作为函数作用域中的变量存在,在执行函数getName时,可以获取到name的值
函数fn执行的时候,其对应的函数作用域中并没有定义name变量,会依次沿着作用域链查找,最终返回undefined
全局作用域
在全局环境下定义的变量会被默认挂在到window对象下,在代码中任意位置都可以访问
var name = 'nordon';
const fn = () => {
console.log(name) // 等价 window.name
}
fn();
当函数fn执行的时候,会在在fn的函数作用域中查找,没有找到便会到全局作用域中查找,若是找到便会返回数据,找不到返回undefined
块级作用域
ES6 增加了 let
和 const
声明变量的块级作用域,使得 JavaScript 中作用域范围更加丰富。块级作用域,顾名思义,作用域范围限制在代码块中,有暂时性死区的特点,也就是说这个变量在定义之前是不能被使用的。
const fn = () => {
console.log(name)
var name = 'norodn'
}
fn()
在ES6之前,会出现变量提升,会输出:undefined,原因是变量 name 在函数内进行了提升。相当于:
const fn = () => {
var name = undefined
console.log(name)
name = 'norodn'
}
fn()
若是使用 let
或 const
声明变量,会针对这个变量形成一个封闭的块级作用域,在这个块级作用域当中,如果在声明变量前访问该变量,就会报 referenceError
错误
const fn = () => {
console.log(name)
let name = 'norodn'
}
fn()
执行代码会报错:Uncaught ReferenceError: Cannot access 'name' before initialization
如果在声明变量后访问,则可以正常获取变量值
const fn = () => {
let name = 'norodn'
console.log(name)
}
fn()
那么什么是块级作用域呢
只要在一对大括号{}中定义的变量,就会单独产生一个独立的作用域
const fn = () => {
console.log(name) // Uncaught ReferenceError: name is not defined
if(true) {
let name = '1'
}
}
fn()
变量name只作用于块级作用域if判断中,对域函数作用域fn内部其他地方是不可用的
一道关于块级作用域的经典面试题:
function fn(){
for(var i = 1; i <= 10; i ++){
setTimeout(function() {
console.log(i)
}, 0)
}
}
fn()
因为在函数作用域fn中存在变量i,当定时器执行的时候会放问函数作用域中的变量i,此时函数作用域中的i已经是11,所以此时的控制台会输出10个11
若是想要按照顺序输出1,2,3...,10, 可以将var改为let,将其变为块级作用域,那么每一次定时器执行的回调函数时都会在其执行的块级作用域中查找
for(let i = 1; i <= 10; i ++){ // var 修为为 let 即可
}