最近被一个题目给弄懵了,然后知道了考点,就一直找资料补补,后来发现,特么是那么的基础
下面就是那道题:你先试着想想会打印什么,后面我会讲解为什么
var x = 11;
var obj = {
x: 22,
methods: {
x: 33,
say: function () {
console.log(this.x)
console.log(this)
},
say2: () => {
console.log(this.x)
console.log(this)
}
}
}
obj.methods.say();
obj.methods.say2();
一:在JavaScript中变量的作用域分为: 全局作用域 和 局部作用域(函数作用域)
1:所有window的属性和方法都属于全局作用域
2:只有函数有局部作用域,对象等其他是没有作用域的
例如:
var x = 11;
var obj = {
x: 22,
methods: {
x: 33,
say: function () {
console.log(this.x)
console.log(this)
},
say2: () => {
console.log(this.x)
console.log(this)
}
}
}
obj.methods.say();
//第一种 很简单就是 methods调用了say() this指向了methods
obj.methods.say2();
//第二种 箭头函数没有自己的this,就只能从自己的作用域往上一层找,就到了obj这一层,然后obj这一层的父执行上下文是window,对象没有作用域(只有函数有局部作用域),所以obj,methods都没有作用域,this就直接指向了window
二:作用域链需要了解下面3个知识点
- 变量和函数的声明
- 函数的生命周期函数的生命周期
- Activetion Object(AO)、Variable Object(VO)Activetion Object(AO)、Variable Object(VO)
注释:先了解下父亲上下文:这个函数在哪个上下文中被声明,那个上下文就是此函数的父亲上下文,这个函数的声明会被保存到父亲上下文的变量对象中。上层上下文(也就是所有前辈上下文)对于作用域链的创建至关重要,我们会在文章后面讲。
1:变量和函数的声明
- var、function声明的变量依附最近的函数作用域或全局作用域;let、const声明的变量依附于最近的块级作用域、函数作用域或全局作用域,即所有作用域气泡
- function,var声明的变量在编译阶段产生变量提升,且函数优先提升。let,const不会产生变量提升
- fucntion,var在同一作用域重复声明变量,后者会覆盖前者(前者与后者的关系要看编译环节过后的代码);而let,const 会直接抛出语法错误
- const 声明变量的同时需要赋值,否则抛出语法错误,且变量的指向不能变(但是变量指向的内容可以变)
注意:同一个标识符的情况下,变量声明与函数声明都会提升;函数声明会覆盖变量声明,但不会覆盖变量赋值,即:如果声明变量的同时初始化或赋值那么变量优先级高于函数。
2: 函数的生命周期
函数的的生命周期分为创建和执行两个阶段。
在函数创建阶段,JS解析引擎进行预解析,会将函数声明提前,同时将该函数放到全局作用域中或当前函数的上一级函数的局部作用域中。
在函数执行阶段,JS引擎会将当前函数的局部变量和内部函数进行声明提前,然后再执行业务代码,当函数执行完退出时,释放该函数的执行上下文,并注销该函数的局部变量。
3:变量对象(OV)
当我们执行一个函数之前,执行引擎会创建一个上下文对象,随之创建了一个重要的属性,就是变量对象,创建变量对象的过程:
- 建立argument对象,属性名为0,1,2,3···,属性值就没传入的参数
- 获取所有该函数内的所有函数声明,属性名为函数名,属性值为函数的引用(地址)
- 获取所有改函数内的所有变量声明,属性名为变量名,属性值为初始化undefined
4:活动对象(AO)
活动对象与变量对象是同一个东西在不同阶段的不同叫法,没开始执行前,扫描一遍代码,将变量,函数声明全部存入变量对象,开始执行函数的时候,会建立一个执行上下文,该执行上下文包含了函数所需的所有变量,,执行就开始去活动对象中寻找变量对象中属性名对应的属性值了。
该对象包含了:
- 函数的所有局部变量
- 函数的所有命名参数
- 函数的参数集合
- 函数的this指向
例如:
function fun1(argu) {
console.log(a); // undefinded,因为活动对象中键值对:a:undefinded。
var a = 111;
console.log(a); // 111,因为活动对象中键值对:a:111
fun2(); // test2! 因为活动对象中键值对:fun2:某个内存地址
return; // 即使是在return之后的声明,也会被放入变量对象!
function fun2() {
console.log('test2');
}
}
// 创建变量对象:{
// argu:'test', // argumetn
// a:undefinded // 变量声明
// fun2:fun2的地址, // 函数声明
// }
fun1('test');
// 输入1:undefined
// 输入2:111
// 输入3:test2
从这打印a的时候,可以看到我们常听到的声明提升,当你看完上面的作用域链大概就知道,是变量对象让变量,函数有了声明提升的特性。
三:作用域链
虽然说作用域链是在函数执行之前,与执行上下文一起创建的,但是实际上其中很大一部分内容在定义的时候就已经确定了。
作用域链这个数组可以分成两部分:
- 第一个元素就是本函数的变量对象后面的所有元素都是上层上下文的变量对象(按照由近到远的顺序排列)。在网上的一些资料中,所有上层上下文的链叫做[[Scope]]。
- 第一部分是在函数执行之前创建的。第二部分其实在函数声明的时候就已经确定了,[[Scope]]是函数的一个属性,从函数声明到函数销毁一直存在,且不会改变。我们在文章开头也说过,决定上层上下文的是函数在哪里声明,而不是函数在哪里调用。因此,本函数的变量对象一旦创建好,执行引擎就可以合并[[Scope]],创建出一个作用域链了。
function inner() {
// 创建作用域链:[inner的变量对象,全局上下文的变量对象],其中没有outter的变量对象!怪不得找到不到outter_var!
console.log("in inner, outter_var = "+outter_var);
}
// inner声明完毕,[[scope]]已经确定
// 因为此时的上下文是全局上下文,所以[[scope]]就是全局上下文的作用域链,也就是全局变量对象
function outter() {
var outter_var = 111;
inner();
}
outter(); // 报错:outter_var is not defined
讲inner包含在outter中就不会报错
function outter() {
var outter_var = 111;
function inner() {
// 创建作用域链:[inner的变量对象,outter的变量对象,全局上下文的变量对象]
console.log("in inner, outter_var = " + outter_var);
}
// 声明完毕,[[scope]]已经确定
// 因为此时的上下文是:outter上下文
// outter上下文的作用域链是:[outter上下文的变量对象,全局上下文的变量对象]
// 所以[[scope]]是:[outter上下文的变量对象,全局上下文的变量对象]
inner();
}
outter();
// 输出:in inner, outter_var = 111
提示:建议看完这些东西后,找一些js作用域的面试题做做