关于作用域和作用域链的总结,以及总结过程中联想到的 this 的指向问题
目录
- 作用域
- 作用域链
- this的指向
- 总结
1. 作用域
1.1 什么是作用域?
// 汉语释义
作用:对人或事物产生的影响、效果
域:泛指某种范围
作用域:简而言之,就是控制变量与函数的使用范围(能被访问 / 可见性)的区域或集合;
详细来说,作用域------对变量和函数起隔离保护作用的一块区域,是一个标识符(变量与函数)在代码中的可访问范围,作用域控制着变量与函数的可见性和生命周期。
1.2 作用域的分类
在 js 中,一般将作用域分为:
- 全局作用域
特性:其变量和对象在程序的任何地方都能被访问。
包括:
1)最外层的函数和定义在最外层的变量
2)所有末定义直接赋值的变量
3)window 对象的内置属性 - 函数作用域(局部作用域 / 私有作用域)
顾名思义,即函数内部才能访问。
特性:变量在声明它的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
包括:
声明在函数内部的变量 - 块级作用域(es6 语法,适用 let 或 const 关键字声明的变量)
特性:ES6 引入了 let 和 const 关键字,和 var 关键字不同,在函数体内使用 let 和 const 声明的变量仅存在于块级作用域中。在函数体外不能访问这些变量。
tips:需要注意的是:
同一变量只能使用一种方式声明,不然会报错
let 和 const 的使用,都有其特性
let:用来定义变量,只能在块级作用域里访问,不能跨块访问,也不能跨函数访问,且不能重复声明。
我们在用 let 声明了一个变量之后,在其自身作用域及其所有子作用域里,都不能再用 let 声明其同名变量(var 可以)
const:用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,且不能修改。
1.3 作用域详解
先看一段代码:
例一:
var aa = "global";
function examOne(){
console.log('aa1', aa); // 输出 aa1 undefined
console.log('bb1', bb); // 输出 bb1 undefined
var aa = 'local'
console.log(aa); // 输出 local
// 块级作用域中的变量
let bb = 'bb 仅在函数体内部可以访问';
console.log(bb); // 输出 bb 仅在函数体内部可以访问
var cc = 'cc'
console.log(cc); // 输出 cc
}
examOne();
console.log(aa); // 输出 global
// 输出 [Vue warn]: Error in created hook: "ReferenceError: bb is not defined"
console.log(bb);
// 输出 [Vue warn]: Error in created hook: "ReferenceError: cc is not defined"
console.log(cc);
你可能会感觉很奇怪,认为变量 aa 的第一个输出是"aa1 global",因为代码还没有执行到var aa=“local”。
但前文看的仔细的朋友可能注意到了,函数作用域特性------变量在声明它的函数体以及这个函数体嵌套的任意函数体内都是有定义的。
所以例一的代码,由于这一特性,我们可以将其做一些调整,将变量声明”提前到函数体顶部,同时变量初始化还在原来位置。它们是完全等价的
var aa ="global";
function examOne(){
var aa
let bb
var cc
console.log('aa1', aa); // 输出 aa1 undefined
console.log('bb1', bb); // 输出 bb1 undefined
aa = 'local'
console.log(aa); // 输出 local
// 块级作用域中的变量
bb = 'bb 仅在函数体内部可以访问';
console.log(bb); // 输出 bb 仅在函数体内部可以访问
cc = 'cc'
console.log(cc); // 输出 cc
}
examOne()
console.log(aa); // 输出 global
// 输出 [Vue warn]: Error in created hook: "ReferenceError: bb is not defined"
console.log(bb);
// 输出 [Vue warn]: Error in created hook: "ReferenceError: cc is not defined"
console.log(cc);
2. 作用域链
作用域之前的嵌套关系组成了一条作用域链。
作用域链主要是为了标识符(变量与函数)的查询,先到创建这个变量的函数的作用域中取值,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这个查找过程形成的一层一层的关系链就叫做作用域链。
先看一段代码:
例二:
var aa = "global";
var bb = "global bb"
function examTwo(){
var aa = "local";
function childOne(){
var aa = "localChild";
console.log('aa1', aa); // 输出 localChild
}
function childTwo(){
console.log(aa); // 输出 local
console.log(bb); // 输出 global bb
}
childOne();
childTwo();
}
examTwo();
变量 aa 的第一次输出,在函数 childOne 中直接有其定义。故输出 localChild;
aa 的第二次输出,在函数 childTwo 中没有其定义,故顺着作用域链向上找,在其父 examTwo 函数中,有其定义。故输出 local;
同样,bb 的第一次输出,在函数 childTwo 中没有其定义,故沿着作用域链向上找,在其父 examTwo 函数中,仍然没有其定义,再向上找,找到。故输出 global bb。
3. this 的指向
本来这篇文章里,是没有这部分内容的,但是在写总结的时候,突然就想到了这个,this 的指向是哪个对象调用函数,函数里的 this 就指向哪个对象。换句话来说,this 这个特殊变量所在的作用域不就是调用函数的那个对象本身吗?把这两个知识点一关联,瞬间我就膨胀了,感觉我对 this 的指向问题的理解升华了!汗(-_-||)!
废话不多说,this 的指向
如果没有特殊情况,this 指向全局对象 window
目前来说,我记得的改变 this 指向的情况包括:
- call(), apply(), bind() 三种函数
- 对象函数调用,指向调用它的对象
- 还有一种特殊情况------ES6 提供的箭头函数。在箭头函数里没有 this,故沿着作用域链向上级作用域去查,故 this 是继承外面的环境。
上代码
例三:
var bb = 'global'
console.log(this.bb); // 输出 global
var obj = {
aa: 'local',
fn: function() {
console.log(this); // 输出结果是 obj 对象
console.log(this.aa); // 输出 local
var fnchilda = function() {
console.log(this.bb); // 普通函数调用,指向 window,输出 global
};
// 普通函数调用,指向 window
fnchilda();
setTimeout(function(){
/**
* 虽然 fn() 里面的 this 是指向 obj,
* 但是,setTimeout 里的是普通函数,
* this 指向是 window , window 下面没有 aa,
* 所以这里输出 undefined 。
*/
console.log(this.aa)
})
setTimeout(()=>{
/**
* setTimeout 里的是箭头函数,然后箭头函数里面没有 this,
* 所以要向上层作用域查找,setTimeout 的上层作用域是 fn()。
* 而 fn() 里面的 this 指向 obj,
* 所以 setTimeout 里面的箭头函数的 this ,指向 obj,
* 故输出 local 。
*/
console.log(this.aa)
});
}
}
obj.fn(); // 对象函数调用,指向调用它的对象,即 obj
4. 变量的定义与变量的使用总结
1)变量的定义规则:
一个变量定义之后,
该变量定义在哪个作用域,在这个作用域及其子作用域的任意位置都可以访问,而其所有父级作用域均不能访问。
2)变量的使用规则:
使用一个变量的,
首先, 在自己的作用域内部查找, 如果有, 就直接使用, 停止查找;
如果没有, 就去上一级作用域查找, 有就使用, 停止查找;
如果还没有, 就再去上一级作用域查找, 有就使用, 停止查找;
直到最上级,即全局作用域,如果还没有, 那么就报错 “变量 is not defined”