说起变量提升前端的小伙伴肯定都不陌生,写这篇文章的目的也是为了让自己巩固一下自己学到的知识,也送给对这些知识不是那么熟悉的同学,与之共勉~
首先我们先来看一道题热热身
var a = 12,
b = 13,
c = 14;
function fn(a) {
console.log(a, b, c);
var b = c = a = 20;
console.log(a, b, b)
}
fn(a);
console.log(a, b, c)
答案是什么先不公布,首先我们要知道在JS代码从上到下执行之前做了什么;
JS代码执行的之前,会有一个预编译的过程,预编译有两个任务
- 对关键字带有var的进行提前声明,但是不定义,值为undefied;
- 对于带有function的进行声明 + 定义;
那么我们看看这道题是如何执行的
代码执行首先js预编译
var a = undefined, b = undefined, c = undefined;
var fn = function () {
console.log(a, b, c);
var b = c = a = 20;
console.log(a, b, b)
}
这个时候 函数function作为字符串存储在开辟的堆内存之中,并没有执行,并且将这个堆内存的地址赋值给了fn,预编译完成;
2.js代码开始从上往下执行;
a = 12, b = 13, c = 14;
fn(a) 函数执行;
将预编译阶段的堆内存中的代码从上到下的执行;
fn (a) {
函数执行分为两步;
1.先检验是否有参数传递,如果有先执行形参赋值;
2.如果没有,就检查是否有带var的关键字,如果有就进行变量提升,如果都没有,代码从上往下继续执行;
函数fn 有形参a 所以先声明a = 12;
var b = undefined;
然后代码从上往下执行;
console.log(a, b, c)
a = 12;b = undefined; c不是fn函数中的私有变量,所以去上级作用域中找,找到window,发现了c = 14;
所以第一次console的结果是12 undefined 14;
b = c = a = 20; b和a都是fn中的私有变量,c是window的全局变量,所以c = 20 = window.c = 20;
console.log(a, b, c)
此时fn中的私有变量a, b, 以及window下的c都已经修改为20; 所以结果是20 20 20 ;
}
console.log(a, b, c)
a, b, c是window下的全局变量, 12,13,20 ;
这个分析不知道大家有没有对变量提升有了一个清晰的理解? 那我们接下来看下一道题
let i = 2;
let fn = function (n) {
i*= 2;
return function (m) {
i-= (n--) + (++m);
console.log(i)
}
};
let f = fn(1);
f(2);
fn(3)(4);
f(5);
console.log(i);
这道题和上一道题差不多,就是var 变成了 let;那么同学们也知道,只有带var关键字的会进行变量提升,带let的并不会;那这道题是不是就简单很多了呢? 还是老规矩,你们先自己尝试把答案写出来,然后看看分析过程,答案是否一致;
代码执行首先js预编译
开辟一个堆内存,把匿名函数当作字符串存入一个地址A中;
function (n) {
i*= 2;
return function (m) {
i-= (n--) + (++m);
console.log(i)
}
}
代码从上往下执行; let i = 2; let fn = 堆内存地址; let f = fn(1)执行的返回结果;
fn(1)执行;
fn(n) {
n = 1; 形参赋值;
i*= 2; (i是上局作用域下的变量2*=2 = 4; 此时全局下的i = 4);
return function (m) { (此时继续声明一个堆内存,把这个匿名函数当作字符串存入这个地址,并且返回地址B,也就是f = B)
i-= (n--) + (++m);
console.log(i)
}
};
f(2)继续执行; f(2)执行就相当于B(2)执行
f(2) {
m = 2; (形参赋值);
i-= (n--) + (++m); (i去上级作用域找,找到全局下的i = 4, n也是去上级作用域A下面找 n = 1;全局i = 4-= 1+3 = 0;此时上级作用域A中的n = 0; 当前m = 3);
console.log(i = 0);
};
fn(3)(4)执行;开辟一个新的堆内存AA执行并且将AA中的返回值BB执行
fn(3) {
n = 3; 形参赋值;
i*= 2; 0*=2 = 0;
return BB 并且将BB执行
BB(4){
m = 4;
i-= (n--) + (++m); (0-= 3 + 5) = -8;
console.log(i = -8)
}
};
f(5)执行 也就是B执行
f(5) {
m = 5;
i-= (n--) + (++m); (-8 -= 0 + 6 = -14)
console.log(i = -14)
};
console.log(i = -14)
怎么样,是不是很简单?那么我们趁热打铁,再来一道;
let n = 10, obj = {n: 20};
let fn = obj.fn = (function() {
this.n++;
n++;
return function(m) {
n+=10+ (++m);
this.n += n;
// console.log(this.n)
console.log(n);
}
})(obj.fn);
fn(10);
obj.fn(10);
console.log(n, obj.n)
这道题就说一下思路,这里就有一点不一样就是多了this;那么我们来看一下;
let n = 10; obj = {n: 20} )这里的obj等于一个堆内存;
fn = obj.fn = 一个自执行函数的返回值 = 一个堆内存地址A,参数是obj.fn;
自执行函数执行
没有形参接受,所以没有形参赋值;
this.n++; 自执行函数中的this默认是window; window下没有n = NAN;
n++; 去上级作用域找n = 10 ++ = 11; 全局n = 11;
return A
fn(10) {
m = 10;
n+= 10 + (++m); 11+= 10 + 11= 32; m =11;
this.n += n; = NAN += 32 = NAN;
console.log(n = 32)
};
obj.fn(10) {
m = 10;
n+= 10 + (++m); 32+= 10 + 11= 53; m =11;
this.n += n; 20 += 53 = 73;
console.log(n = 53)
};
console.log(n = 53, obj,n = 73; window.n = NAN)
怎么样?是不是很简单?let 和 var的区别就是带var的会变量提升,let 不会,并且var的变量会添加到window上,let 也不会; 我也不知道我说的详细不详细,反正我把我知道的都描述出来了,有什么不对的希望大家可以指出