作用域是当前的执行上下文,在其中的值和表达式“可见”(可被访问)。作用域也可以堆叠成层次结构(作用域链),子作用域可以访问父作用域,反之不行
在书籍《你不知道的 Javascript》上卷中有这样一句话:作用域是根据名称查找变量的一套规则
所以通俗来讲,作用域在 JavaScript 里可以理解为:作用域是一套规则,可以决定一个变量或函数的有效访问范围
静态词法作用域
解释:
- 静态:代码书写的位置
- 词法:通过关键字声明的语法变量,如:
var
、let
、const
、class
、function
、import
、export
等 - 作用域:代码中定义变量的区域,它决定了变量的可见性和生命周期,作用域层层嵌套是一个树状结构从叶子到根节点依次查询访问
静态词法作用域是 JavaScript 中确定变量访问权限的一种机制。在这种机制下,一个变量的作用域在定义时就已经确定了,而不是在运行时才确定
全局作用域
脚本模式运行所有代码的默认作用域
var a = 'a'
window.b = 'b'
c = 'c' // 不推荐的写法,非严格模式下相当于: globalThis.c = 'c'
function bar() {
// 函数中没用 var 声明的变量,其作用域也是全局的
d = 'd' // 不推荐的写法,和 c 一样,非严格模式下相当于: globalThis.d = 'd'
}
bar()
// --------------------------------------------
delete a // false -> 变量不能删除
// c 不是严格意义的全局变量,而是全局属性
delete c // true -> 相当于 delete globalThis.c
// 如何删除 a?
delete window.a // true -> window 的属性 a 可以被删除
模块作用域
ESModule 模块模式中运行代码的作用域。在一个模块中定义的变量,除非显式地导出(export
),否则其他模块不能访问
函数作用域
由函数创建的作用域,也称局部作用域
function bar() {
var a = 'a'
function foo() {
var b = 'b'
console.log(a) // 'a'
}
console.log(b) // Error: undefined
}
bar()
console.log(a) // Error: undefined
函数内部的变量如何暴露给外界?
- return
- 闭包
块级作用域
用一对花括号(一个代码块)创建出来的作用域
ES6 新增,使用 let
、const
、calss
声明的变量拥有块级作用域,严格模式的 function
也支持块级作用域(详见:变量与函数声明)
ES6 之前没有块级作用域,这是语言设计缺陷
// ES6 之前:没有块级作用域
while (var a = 'a') {
var b = 'b'
}
console.log(a, b) // 'a' 'b'
// ES6 之后:有块级作用域(通过 let 和 const)
while (let c = 'c') {
const d = 'd'
}
console.log(c, d) // Error: undefined undefined
{ // 花括号就是一个块
const e = 'e'
}
this
:动态执行上下文
函数的 this
关键字是在函数被调用时确定的,而不是在函数被定义时确定的。这就是所谓的“运行时绑定”
this
指向问题:
普通函数
普通函数:谁调用,this
指向谁
function
中的this
const fn = function() {
console.log(this)
}
const obj = {
name: 'OBJ',
fn,
subObj: {
name: 'SUB_OBJ',
fn,
},
}
fn() // window,严格模式下是 undefined
// 这里不能理解为 window.fn()
// 因为用 const/let 定义的变量不会被挂载到 window 上
// 即:
window.fn // undefined
obj.fn() // obj {}
obj.subObj.fn() // subObj {}
// this: 执行主体
// 可以理解为谁把函数执行的(谁调用的),函数内部的 this 就指向谁
// 和函数在哪定义、在哪执行、在哪调用都没有关系
- 回调函数中的
this
给当前元素的某个事件行为绑定方法,当事件行为触发,方法中的 this
是当前元素本身(排除 attachEvent
)
document.body.addEventListener('click', function() { console.log(this) }) // body
// 可以理解为 body 调用了回调函数
构造函数/类
构造函数/类中的 this
指向当前类的实例
function Dog(name) {
this.name = name
}
const wangcai = new Dog('旺财')
wangcai.name // '旺财'
箭头函数
箭头函数中没有自己的 this
,所用到的 this
都是其所处上下文(静态词法作用域)中的 this
const demo = {
name: 'DEMO',
fn() {
console.log(this) // demo
// setTimeout 的回调函数是 window 调用的,所以 this 指向 window
setTimeout(function() { console.log(this) }, 0) // window
// 箭头函数的 this 是看在哪定义的
// 这里在 fn 中定义,this 就是 fn 的 this
setTimeout(() => console.log(this), 0) // demo
}
}
demo.fn() // 上面的注释都基于此调用
显式改变 this
指向
通过 Function.prototype.call/apply/bind
显式改变 this
指向
function fn(x, y) {
console.log(this, x, y)
}
fn.call(obj, 10, 20) // obj
fn.apply(obj, [10, 20]) // obj
document.body.addEventListener('click', fn.bind(obj, 10, 20)) // obj
箭头函数无法显式改变
this
指向箭头函数中没有自己的
this
,所以无法通过Function.prototype.call/apply/bind
显式改变其this
指向底层:箭头函数的底层是
bind
,无法改变this
,只能改变参数