PS:文字解释为本人理解,少许内容借鉴参考其他博文,或许有很多不全面的地方,至少执行结果是真的。而至于理论知识部分,推荐阅读相关书籍或文章。
👉👉👉传送门:详解JavaScript作用域和作用域链文章目录
难度:★☆☆☆☆
例1:
function fn() {
console.log(a);
}
fn();
// err: a is not defined
例1中,变量 a 无值且未声明,所以报错。
例2:
var a;
function fn() {
console.log(a);
}
fn();
// undefined
等同
function fn() {
console.log(a);
}
fn();
var a; // 变量被提升
// undefined
例2中,声明了全局变量 a,但并未赋值,所以打印出 ‘undefined’。
关于变量提升:所有的声明都会提升到当前作用域的最顶部,而值必须遵循执行顺序。
例3:
function fn() {
console.log(a);
}
fn();
a = 11;
// err: a is not defined
或者
console.log(a);
a = 55;
// err: a is not defined
例3中,两种情况均报错,因为隐式变量不存在变量声明提升。
例4:
a = 0;
function fn() {
console.log(a);
}
fn();
console.log(a);
// 0
// 0
等同
var a = 0;
function fn() {
console.log(a);
}
fn();
console.log(a);
// 0
// 0
例4中,即使没有使用 var 关键字定义变量 a ,但却被隐式声明为全局变量,而函数内并没有声明同名变量,所以它直接拿全局变量打印。
例5:
function fn() {
a = 11;
console.log(a);
}
// console.log(a); // 直接报错,a is not defined,中断后面代码的执行
fn();
console.log(a);
// 11
// 11
例5中,函数内并未使用 var 关键字声明,那么当函数被调用之后,a 成了全局变量,所以外部可获取,如果未调用函数,a 就还不是全局变量所以报错。
例6:
var a = 0;
function fn() {
a = 11;
console.log(a);
}
console.log(a);
fn();
console.log(a);
// 0
// 11
// 11
例6中,原理和例5一样,函数内并未使用 var 声明变量,所以只要调用了该函数,那么 a 就成了全局变量,并覆盖掉原值。
例7:
function fn() {
console.log(a);
var a = 11;
}
fn();
console.log(a);
// undefined
// err: a is not defined
等同
function fn() {
var a;
console.log(a);
a = 11;
}
fn();
console.log(a);
// undefined
// err: a is not defined
例7中,变量 a 的声明被提升,作用域为当前函数,所以是局部变量,而又因为值是在打印后才有的,所以打印时为 ‘undefined’,外部无法获取局部变量,所以报错。
例8:
function fn() {
var a = 11;
console.log(a);
}
fn();
console.log(a);
// 11
// err: a is not defined
例8中,a 被关键字 var 声明为局部变量,所以外部无法访问获取 a 值,报错。
例9:
function fn() {
a = 11;
console.log(a);
var a = 0;
}
fn();
console.log(a);
// 11
// err: a is not defined
等同
function fn() {
var a; //变量提升
a = 11;
console.log(a);
a = 0;
}
fn();
console.log(a);
// 11
// err: a is not defined
例9中,只要该变量在当前作用域有被声明,无论在当前作用域的哪个地方,都会被提升到顶部,所有当前作用域同名变量,共用一个声明,即全部均为局部变量。【若没有被声明,则所有该变量均为全局变量,可回看 例5、例6 】。而打印值根据执行顺序进行,所以 a = 0;
并未被执行打印。
难度:★★☆☆☆
例10:
function fn(a) {
console.log(a);
}
fn();
// undefined
等同
function fn(a) {
var a; // 形参作为变量被函数隐式声明【局部变量】
console.log(a);
}
fn();
// undefined
例10中,形参(变量) a 在函数内【当前作用域】被隐式声明,所以不会报错,而是打印 ‘undefined’。
例11:
var a = 11;
function fn(a) {
console.log(a);
a = 43;
}
fn();
console.log(a);
// undefined
// 11
例11中,因为带形参 a 声明函数 fn,所以 a 在函数内部被隐式声明,为局部变量,所以第一次打印 ‘undefined’,第二次则打印全局的值,即 ‘11’;
例12:
var a = 11;
function fn(a) {
console.log(a);
a = 43;
}
fn(11);
console.log(a);
// 11
// 11
例12中,形参被隐式声明为局部变量,所以 fn 里的 a = 43;
其实是局部变量,无法影响外部的全局变量值。还有注意细节,调用的函数的时候,传入了值,所以第一次打印传入的值 ‘11’,第二次打印全局变量的值 ‘11’ ,不要被迷惑同时注意细节。
例13:
function fn(a, b) {
console.log(b);
}
fn(11);
// undefined
例13中,函数传入的值,对应定义的形参位置。只传入一个值,则默认为第一个形参的值。所以 ‘11’ 是 形参 a 的值,b 没有值传入。而形参 b,只被函数隐式声明,并没有值,所以打印 ‘undefined’。
例14:
function fn(a, b) {
console.log(b);
var b = a;
console.log(b);
}
fn(4, 55);
// 55
// 4
例14中,第一次打印的是形参 b 的值,第二次因为变量(也是形参) b 值被 a 值赋值(覆盖),所以打印出 a 值。
例15:
var a = 0;
function fn(x) {
console.log(a);
var a = x;
console.log(a);
}
fn(5);
console.log(a);
// undefined
// 5
// 0
例15中,因为函数内部有用 var 声明变量,所以函数内所有 a 变量均为局部变量,所以第一次打印 ‘undefined’,因为只声明而没有值;第二次,因为传入之为 5,即 a = x = 5;
,所以打印出 5;第三次,不用说了,外部无法获取内部值,所以打印的是全局变量的值 0。
例16:
var a = 11;
function fn(a, b) {
b = 44;
console.log(b);
}
fn(0, 97);
console.log(a);
// 44
// 11
例16中,函数体内赋值变量的优先级大于参数值,所以优先打印 ‘44’。
关于作用域中的访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】
👉👉👉详情:js函数作用域中的优先级
例17:
var fn = function() {
console.log(this);
}
fn();
// window
或者
function fn() {
console.log(this);
}
fn();
// window
例17中,由于没有指定对象调用函数,即该函数不属于任何对象,所以默认 this 指向 window。
例18:
function foo() {
var a = 1;
console.log(this);
function bar() {
console.log(this);
}
bar();
}
foo();
// window
// window
或者
var foo = function() {
var a = 1;
console.log(this);
var bar = function() {
console.log(this);
}
bar();
}
foo();
// window
// window
例18中,原理同例17,函数并未被任何对象调用,未指向任何对象,所以默认 this 指向 window。
例19:
var a = 0;
function fn() {
var a = 11;
console.log(this.a);
}
fn();
// 0
例19中,函数并未被任何对象调用,所以默认 this 指向 window,所以 this.a
指向 window.a
,即全局 ‘a = 0’。
例20:
var a = 0;
var fn = function() {
var a = 11;
console.log(this.a);
console.log(a)
}
fn();
// 0
// 11
例20中,与例19同理,函数并未被任何对象调用,所以 this.a 等于 window.a = 0
,第二个不用说了,它打印的是局部变量a。
例21:
var a = 0;
function fn() {
a = 15;
console.log(this.a);
console.log(a);
}
fn();
// 15
// 15
例21中,与例20同理,但是注意,函数体内变量 a 并未被关键字声明,所以它是全局变量,所以两个都是打印 ‘15’。
例22:
var a = 0;
var fn = function(a) {
var a = 15;
console.log(this.a);
console.log(a)
}
fn(6);
// 0
// 15
例22中,第一次打印同上,第二次打印出 ‘15’。【变量提升 < 传参 < 函数体 < 首行赋值】
例23:
var obj = {
fn : function() {
console.log(this);
}
}
var foo = obj.fn();
var bar = obj.fn;
bar();
// obj
// window
例23中,第一次打印,函数被对象 obj 对象调用:obj.fn()
,所以 this 指向当前对象;第二次,var bar = obj.fn;
把函数引用赋值给一个变量,造成 this 丢失。所以 this 指向了全局对象,window。
难度:★★★☆☆
例24:
var a = 0;
var obj = {
a : 15,
b : function() {
console.log(this.a);
var a = 3;
console.log(this);
}
}
var foo = obj.b();
var bar = obj.b;
bar();
// 15
// obj
// 0
// window
例24中,原理同例23,首先看 foo ,即 obj.b();
,由于被当前对象 obj 调用,所以 this 指向 obj,所以打印 ‘15, obj’;再看 bar,例23中说了,var bar = obj.b;
把函数引用赋值给一个变量,造成 this 丢失。所以 this 指向了全局对象,window。而至于函数体内的 var a = 3;
为局部变量,完全是为了混淆视听。
例25:
var a = 0;
var obj = {
a : 14,
b : function() {
var c =() => {
console.log(this);
}
var d = function() {
console.log(this);
}
c();
d();
}
}
var foo = obj.b();
var bar = obj.b;
b();
// obj
// window
// window
// window
例25中,箭头函数 this 会跟随它的父级函数的 this 的指向。即跟随 obj.b() 的指向。第二部分,this 丢失,所以箭头函数也跟着指向 window。
例26:
var x = 12;
function fnA() {
console.log(x);
}
function fnB(f) {
var x = 44;
f();
}
fnB(fnA);
// 12
例26中,【函数的声明决定其作用域,而不是调用决定作用域】,即函数在哪里创建的,就用哪里的变量,找不到才往父级作用域找。
例27:
【写个闭包】
var a = 14;
function fun() {
var a = 22;
return function() {
var a = 36;
console.log(a);
}
}
var fn = fun();
fn();
// 36
例27中,若是当前声明的函数内有声明变量,则用其内部的变量,而不会跑出去使用外部的变量。当没有 var a = 36;
这条变量时,它才会拿父级的 var a = 22;
。这个过程称之为【作用域链】。
例28:
var x = 10;
function fn(){
console.log(x);
}
function show(f){
var x = 20;
(function(){
f();
})();
}
show(fn);
// 10
例28中,与例27、例26是一样的道理。
例29:
var a = 100;
function fn() {
var b = 2*a;
var a = 200;
var c = a/2;
function foo(x) {
console.log(x);
}
foo(b);
foo(c);
}
fn();
// NaN
// 100
例29中,只要见到函数内有 var a
,那想都不用想,把全局变量 无视掉。首先看 b,乘号 var a = 100
*
强制将a的数据类型转换成数值型【隐式转换】,但因 a 只有声明被提升,而没有值,所以 a 为 ‘undefined’,那么此时 var b = 2 * undefined
含非数值类型,直接输出 ‘NaN’。而第二次打印,‘100’ 不用说了,就 var c = 200 / 2
。
例30:
function fun(n, o) {
console.log(o);
return {
fun : function(m) {
return fun(m, n);
}
};
}
// 第一部分
var a = fun(0); // undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
// 第二部分
var b = fun(0).fun(1).fun(2).fun(3);
// undefined, 0, 1, 2
// 第三部分
var c = fun(0).fun(1); // undefined, 0
c.fun(2); // 1
c.fun(3); // 1
第一部分:
第二部分:
第三部分:
例31:
+function() {
console.log(a);
var a = 5;
function a() {};
console.log(a);
function b() {};
b = 6;
console.log(b);
var c = d = b;
}
console.log(d);
console.log(c);
// f a() {}
// 5
// 6
// 6
// err: c is not defined
作用域访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】
第一次:由于函数体优先级大于变量提升,所以不是打印 ‘undefined’,而是打印函数体;
第二次:由于此次是“首行赋值”,即打印前遇到该变量且有值,那么优先获取,而不是函数体;
第三次:同第二次打印一样,赋值大于函数体,最大优先级(别理它是否全局变量);
第四次:d = b;
在函数体内没有被 var 声明,所以是全局变量,外部可取;
第五次:由于变量 c 在函数体内被 var 声明,所以是局部变量,外部不可取,直接报错未找到定义。
难度:★★★★☆
例32:
【360经典试题】
window.val = 1;
var obj = {
val : 2,
dbl : function() {
console.log(this);
this.val *= 2;
console.log(val);
console.log(this.val);
}
}
var ff = obj.dbl();
var fn = obj.dbl;
fn();
// obj
// 1
// 4
// window
// 2
// 2
第一部分:对象 obj 调用函数 dbl(),所以 this 指向 obj,而 val 因为没有在 dbl() 函数体内声明,所以使用的是全局变量的值,this.val 则是 obj.val 的值。
第二部分:var fn = obj.dbl;
把函数引用赋值给一个变量,造成 this 丢失,所以这里的 this 指向 window。所以函数内部 this.val => window.val => val => 1
这三者是相等的。而又 this.val *= 2;
,所以 window.val = window.val * 2 = 1 * 2 = 2;
如何改变 var fn;
中 this 的指向?
可通过call()、apply()等方法来改变 this 指针,当然不同情况不同用法
防坑点总结:
- 函数首先在其内部找目标变量,找不到则往父级作用域找,找不到再往父级的父级作用域找,直到找完,找完还没找到,那就报错处理。
- 隐式变量不存在变量声明提升。
- 在声明函数存在形参时,形参会被隐式声明为局部变量。
- 只要当前作用域存在变量声明,甭管它在当前作用域下方还是上方,是打印前还是打印后,它都会被变量提升至当前函数内顶部。
- 作用域中的访问优先级:【变量提升 < 传参 < 函数体 < 首行赋值】,同样,当变量与函数名同名,亦遵循此优先级顺序。
- 【函数的声明决定其作用域,而不是调用决定作用域】
- this 指向对象,而不是函数名。
- 在非严格模式中,无指向的函数内部的 this 指向 window【箭头函数除外】。
- 内部自执行匿名函数不属于任何对象,所以 this 指向 window。
- 当把函数引用赋值给一个变量时,造成 this 丢失。
- 最后一点,也是最重要的一点,仔细审题!不可粗心大意!个人练习做题大部分错的并不是不会,而是因粗心而导致给出错误答案。