遇到了个小问题,问大家都说是作用域的问题.我觉得不对啊.于是做个笔记记录下来.
本文涉及到 JavaScript执行机制 / 作用域 / 作用域链 / Js预解析 / this指向 / 改变this指向 / var重复声明.菜比较多.
首先先牵扯到的是JavaScript是单线程语言.代码的执行顺序是自上而下的执行代码.
我的理解就像是高速公路收费站一样,一次只能过一辆车.但是如果真的完全是这样的话,就会出问题.好比说:如果调用函数在函数声明前执行,那么就会报错.
以上执行机制内容来自JavaScript闭包 - Web前端工程师面试题讲解
下图来自掘金:
两个内容的差异是名词叫法的不同.
为了简短文字.下文描述JavaScript预解析阶段,只描述创建步骤只描述创建变量对象这一步.
// console.log(a); 这个时候b是等于undefined的.并没有进行赋值.
var a = 66;
function test1(a) {
console.log(a); // function a(){};
function a() { };
var a = 5;
}
test1(a);
这段代码的console.log(a)的结果是 function a(){};
var b = 66;
function test2(b) {
console.log(b); // 输出 66
var b = 5;
}
test2(b);
这段代码的console.log(b)的结果是 66.
而 :
var b = 66;
function test2(b) {
var b = 5;
console.log(b); // 输出 5
}
test2(b);
输出的结果就会是5.因为 var b = 5 把 局部变量给覆盖掉了.进行了一次重新赋值
重复声明时:首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了。如果不存在,则招呼作用域声明一个新的变量a;若已经存在,则忽略 var 继续向下编译,这时 a = 2被编译成可执行的代码供引擎使用。
参考博文 : 为什么var可以重复声明
function test3(b) {
console.log(b); // 输出 undefined
var b = 5;
}
test3();
这段代码的console.log(a)的结果是 undefined
var a = 100;
function fns() {
console.log(a); //undefined
var a = 200;
console.log(a); //200
}
fns();
console.log(a); //100
var a;
console.log(a); //100
var a = 300;
console.log(a); //300
输出结果见上面的注释.
var a = 12;
var a; // 因为值没有覆盖
console.log(a); // 12
赋值时:引擎遇见a=2时同样会询问在当前的作用域下是否有变量a。若存在,则将a赋值为2(由于第一步编译器忽略了重复声明的var,且作用域中已经有a,所以重复声明会发生值的覆盖而不会报错);若不存在,则顺着作用域链向上查找,若最终找到了变量a则将其赋值2,若没有找到,则招呼作用域声明一个变量a并赋值为2(这就是为什么第二段代码可以正确执行且a变量为全局变量的原因,当然,在严格模式下JS会直接抛出异常:a is not defined)。
重复声明时:首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了。如果不存在,则招呼作用域声明一个新的变量a;若已经存在,则忽略 var 继续向下编译,这时 a = 2被编译成可执行的代码供引擎使用。
来自 https://blog.csdn.net/DurianPudding/article/details/87953939
var num1 = 55;
var num2 = 66;
function fn_2(num, num2) {
console.log(num);// 66
num = 100; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num.num在此作用域中存在(函数的参数).所以将100赋值给局部作用域中的num变量.
num1 = 100; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num1.num1在此作用域中不存在.顺着作用域链在全局作用域中找到了num1.所以将100赋值给全局作用域中的num变量.
num2 = 100;// 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num2.num2在此作用域中存在.所以将100赋值给局部作用域中的num2变量.
numall = 888; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在numall.numall在此作用域中不存在.顺着作用域链在全局作用域中寻找是否存在numall.全局作用域中也不存在numall.所以会在全局作用域中创建一个属性.也就是window.numall.
console.log(num);// 100
console.log(num1); //100
console.log(num2); // 100
}
fn_2(num1, num2);
console.log(num1); // 100
console.log(num2); // 66
//console.log(num); // 报错 这行代码会报错.因为全局作用域中不存在num
console.log(numall); // 100
console.log(window)
首先执行全局作用域的预解析. var num1 ; var num2 ; function fn_2; 此刻函数表达式是可以立即执行的,但是变量是等于undefined.
读取到fn_2(num1, num2); 函数fn_2()需要两个局部函数 num和num2. 在调用fn_2的时候(fn_2(num1,num2)). 传递进去(赋值)的参数实际上是全局变量的num1与num2.
而 num = 1;
事实上是对属性赋值操作。首先,它会尝试在当前作用域链(如在方法中声明,则当前作用域链代表全局作用域和方法局部作用域etc。。。)中解析 num; 如果在任何当前作用域链中找到num,则会执行对num属性赋值; 如果没有找到num,它才会在全局对象(即当前作用域链的最顶层对象,如window对象)中创造num属性并赋值。
[]()
fn3();
console.log(c); // 9
console.log(b); // 9
console.log(a); // 报错
function fn3() {
var a = b = c = 9;
/*
以上代码相当于
c = 9;
b = c;
var a = b;
*/
console.log(a);//9
console.log(b);//9
console.log(c);//9
}
js赋值语句执行顺序 // js赋值语句执行:自右向左(仅指简单的赋值.如上).
var n = 0;
function a() {
var n = 10;
function b() {
n++;
console.log(n);
return n;
}
b();
return b;
}
var c = a(); // 11
var now = c(); //12
console.log(n); // n == 0 上图是一个闭包
console.log(now) // 12
这是一个闭包. var c 在引用 function b(){...}.
概念性的东西写起来太长.推荐看书去理解.
var x = 10;
function fn() {
console.log(x); // 答案是10
console.log(this); //window
}
function show(f) {
var obj = { x: 20 };
f();
}
show(fn);
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
作用域与作用域链
var x1 = 10;
function fn1() {
console.log(this.x1);
}
function show1(f) {
let obj = { x1: 66 };
var x1 = 20;
f();//10
f.call(obj); // 使用call改变了this指向 =>66
}
show1(fn1);
var fn9 = function () {
console.log(fn9);
}
var objs = {
fnn: function () {
console.log(fn9);
console.log(this.fn9);
console.log(this); // this指向obj.
}
}
objs.fnn();
console.log(test_haha); // 函数声明的优先级是高于var的
function test_haha() { };
var test_haha = 66;
经过测试,在预解析阶段,函数声明的优先级是高于var变量的. console.log(test_haha);输出的是函数体,尽管var在最下面.
var objs = {
fn9: function () {
console.log(fn9); // 输出 var fn9 = function () {console.log(fn9);}
console.log(this.fn9); // 输出 objs.fn9
console.log(this); // this指向obj.
},
}
objs.fn9();
谁调用this,this指向谁.
js里只有全局作用域与函数作用域.在ES6中出现了块级作用域.还没有学到.
深入理解JavaScript作用域和作用域链
var a;
if (true) {
a = 5;
function a() {
console.log(‘我没有被楼下的骚a给霸占了‘);
};
a = 0
console.log(a);
}
console.log(a)
这段代码在chrome与ie下的输出结果是不一致的.
不要在判断语句里定义函数.
原文:https://www.cnblogs.com/gtscool/p/12636616.html