1. 变量的作用域(变量+函数)
作用域是变量的可作用范围,变量只有在自己的作用域下才会生效。
函数会产生作用域,在函数内定义的变量只能在函数内使用。
2. 作用域分类
局部作用域: 函数内定义的变量和形参的作用域就是局部作用域;这样的变量称之为局部变量。
全局作用域: 在函数外面定义的变量的作用域是全局作用域;这样的变量称之为全局变量。
块级作用域(es6新增): 在代码块中定义的变量的作用域是块级作用域;这样的变量称之为块级变量。 ES6才支持,必须使用es6的声明方式。
局部变量 只能在定义变量的函数内使用; 全局变量 在任意地方都可以使用。
注意:函数内的形参也是局部变量,作用域范围就是所在函数。
了解:函数内不使用关键字 var 声明的变量,因为会被当做全局变量,但不建议这么做,在严格模式下,不使用关键字 var 就声明变量,会报错!
3. 哪些关键字声明会产生作用域?
3.1 es5的关键字
var
function
3.2 es6的关键字
let
const
class
4. 作用域的案例
1. a、b 都是在全局下定义的,所以他们的作用域就是全局,n是在fn中定义的,它的作用域就是fn函数中。
var a=3;
var b=6;
function fn(){
var n=3;
console.log(n)
};
console.log(a,b);//3 6
2. 在全局下定义的变量,在哪都可以访问到,在私有作用域中定义的变量,在外面是访问不到的。
var a=3;
function fn(){
var b=6;
console.log(a); //3
}
fn();
console.log(b);//报错
3. 作用:隔离变量,不同作用域下同名变量不会有冲突。
var a = 10,
b = 20;
function fn(x) {//x = 10
var a = 100,
c = 300;
console.log('fn()', a, b, c, x)//"fn()" 100 20 300 10
function bar(x) {//x = 100 200
var a = 1000,
d = 400
console.log('bar()', a, b, c, d, x)//"bar()" 1000 20 300 400 100
//"bar()" 1000 20 300 400 200
}
bar(100)
bar(200)
}
fn(10)
全局变量和私有变量
全局变量(VO):在全局作用域中定义的变量就是全局变量。
私有变量(AO):在私有作用域中定义的变量就是私有变量。例如函数体内声明的形参和在函数私有作用域中定义的变量。
5. 浏览器底层运行机制
1. ECStack(Execution context Stack):执行环境栈(栈内存,从内存中分配出来的一块内存)。
2. EC(Execution Context):执行上下文(在编程语言中,代码执行,为了区分全局作用域和函数执行所处不同的作用域,目的是为了区分每个词法作用域下代码的独立性)EC 就是代码执行所处的范围。
- EC(g):全局执行上下文。
- EC(function):函数执行的上下文。
3. VO 和AO:在每一个执行上下文中,代码执行的时候,都会存贮一些变量:
- 全局上下文中的变量存储在VO。
- 私有上下文中的变量存储在AO。
4. GO:浏览器把一些内置的属性和方法放到了GO中,并且在全局执行上下文(EC(g))中创建了一个window变量对象,并且让其指向GO。
代码案例的执行流程:
6. 作用域链
6.1 什么是作用域链
- 函数会限制变量的作用域范围,而函数内是可以再嵌套函数的,函数的层层嵌套,就形成了一个作用域链。
- 作用域链描述的是程序在执行过程当中寻找变量的过程。
6.2 作用域链寻找变量的过程
当函数内使用某个变量的时候,会按照如下过程找到该变量:
- 先从自身所在作用域去查找,如果没有再从上级作用域当中去查找,直到找到全局作用域当中。
- 如果其中有找到,就不会再往上查找,直接使用。
- 如果都没有找到,那么就会报引用错误提示变量没有定义。
6.3 注意
一个变量的作用域只与函数声明的位置有关,与函数调用的位置无关!
function fn(){
// 自己私有作用域没有这个变量,向上级进行查找,上级window 也没有,就报错
console.log(n);//报错
}
fn();
只有赋值,没有声明关键字的案例:
function fn(){
n=3; // 自己私有作用域中没有,向上级进行查找,上级作用域也没有,就相当于给window.n=3;
}
fn();
console.log(n);
function fn(){
n=3; // 自己私有作用域中没有,向上级进行查找,上级作用域中有,就是上级作用域的n的值重新改了3
}
var n=2;
fn();
console.log(n);
function fn(){
console.log(n);// 2
}
var n=2;
fn();
console.log(n);
7. 变量提升
7.1 什么是变量提升?
当浏览器开辟出供js执行的栈内存之后,代码并不是立即自上而下执行,而是需要先做一些事情:把当前作用域中带var 和function 的关键字进行提前的声明和定义,这叫做变量提升机制。es5的关键字才有变量提示,es6的关键字没有有变量提示。
7.2 var 和 function 在变量提升阶段区别
- var 在变量提升阶段是只声明,未定义(不赋值),赋值留在原地。
- function 在此阶段是声明和定义(赋值)都完成(声明+赋值)。
注意特殊情况!!!
1.var 如果遇到 if,不论判断条件是否成立,都会不影响变量提升。
2.function如果遇到 if,第一步先声明提前不赋值,第二步再看 if 判断是否成立,成立进入到if 中,以要变量提升的函数为界,分为上级作用域和私有作用域(块级作用域)。第三步进入if(上级作用域)立刻重新变量提升声明+赋值,然后其它代码自上而下的执行;进入到私有块级作用域立刻重新变量提升声明+赋值,然后其它代码自上而下的执行。
3.function如果遇到 if,判断条件不成立,直接进行变量提升只声明,不赋值。
4.像【f = function(){}】的函数和自执行函数没有变量提升。如果自执行函数有中有像【function 函数名(){}】这样的函数还是要声明提前+赋值。
5.像【声明关键字 f = function(){}】这样使用表达式定义的函数,function只对等号左边的做变量提升。
6.return 下面的代码进行变量提升,return 后面的代码不进行变量提升。
7.如果变量名字重复,var不会进行重复声明,但是会重新赋值,在变量提升阶段,看到第一行var num ,会声明一个变量num,此时看到第二行还有一个就不用再声明了。function的在变量提升阶段是声明和定义一起完成的,如果遇到重复声明定义的,会进行重新赋值。
7.3 var 变量提升原理
7.3.1 var 变量提升后的代码执行流程
var变量提升,声明提前,赋值还在原来的位置。
7.3.2 var 变量提升案例
案例1:
<body>
<script>
console.log(a);
var a = 10;
console.log(a);
</script>
</body>
--------------------
变量提升
<body>
<script>
var a;
console.log(a); //undefined
a = 10;
console.log(a);//10
</script>
</body>
案例2:
<body>
<script>
console.log(a);
if (1 == "2") {
var a = 12;
}
console.log(a);
</script>
</body>
-----------------
变量提升
<body>
<script>
var a;
console.log(a);//undefined
// if判断不成立,无法进入
if (1 == "2") {
a = 12;
}
console.log(a);//undefined
</script>
</body>
案例3:
<body>
<script>
console.log(a);
if (a in window) {
var a = 100;
}
console.log(a);
</script>
</body>
---------------------
变量提升
<body>
<script>
var a;
console.log(a);//undefined
// if判断成立,进入执行if中的代码
if (a in window) {
a = 100;
}
console.log(a);//100
</script>
</body>
7.4 function 变量提升原理
7.4.1 function 变量提升后的代码执行流程
function变量提升,声明和赋值都提前。
7.4.2 function 变量提升案例
1. function 普通的变量提升
案例:
<body>
<script>
let a;
function fn() {
a = 100;
}
fn();
console.log(a);//100
</script>
</body>
2. 遇到if不成立的情况,function只声明不赋值。
案例1:
<body>
<script>
//遇到if不成立的情况,function只声明不赋值
console.log(fn);//undefined
if (1 == "2") {
function fn() { }
}
console.log(fn);//undefined
</script>
</body>
-----------------------------
变量提升后
1.遇到if先提前声明。
function fn;
console.log(fn);
2.if不成立,if中的语句不执行。
if (1 == "2") {
function fn() { }
}
3.最后只声明了函数没有赋值,所以是undefined
console.log(fn);
案例2:
<body>
<script>
//遇到if不成立的情况,function只声明
// 1.先声明提前
console.log(fn);//undefined
if (1 == "2") {
console.log(fn);
function fn() { }
console.log(fn);
}
console.log(fn);//undefined
</script>
</body>
----------------------
变量提升后
1.遇到if先提前声明。
function fn;
console.log(fn);//undefined
2.if不成立,if中的语句不执行。
if (1 == "2") {
console.log(fn);
function fn() { }
console.log(fn);
}
3.最后只声明了函数没有赋值,所以是undefined
console.log(fn);
3. 遇到 if 成立的情况,先声明提前,赋值还在原来位置,后面是重新function声明+赋值。
案例1:
<body>
<script>
//遇到if成立的情况,function声明+赋值
// 1.先声明提前
console.log(fn);//undefined
if (1 == "1") {
console.log(fn);//函数体ƒ fn() { }
function fn() { }
console.log(fn);//函数体ƒ fn() { }
}
console.log(fn);//函数体ƒ fn() { }
</script>
</body>
分析:
4. 像【f = function(){}】的函数和自执行函数没有变量提升。如果自执行函数有中有像【function 函数名(){}】这样的函数还是要声明提前+赋值。
f=function(){
return true;
};
g=function(){
return false;
};
~function(){
if(g()&&[]==![]){
f=function(){return false;};
function g(){
return true;
}
}
}();
console.log(f());//报错
console.log(g());
-------------------------
变量提升
f=function(){
return true;
};
g=function(){
return false;
};
~function(){
if(g()&&[]==![]){
function g(){
return true;
}
f=function(){return false;};
}
}();
console.log(f());//报错
console.log(g());
5. 有声明关键字 f = function(){},function只对等号左边的做变量提升。
案例1:
console.log(fn);//undefined
console.log(fn(1,2));//报错
var fn=function (n,m){
return n+m;
}
console.log(fn(3,4));
----------------------------
变量提升
var fn;
console.log(fn);//已声明,没有赋值undefined
console.log(fn(1,2))//undefined(1,2)报错
fn = function (n,m){
return n+m;
}
console.log(fn(3,4))
案例2:
sum();//2
fn();//报错
var fn=function(){
console.log(1);
};
function sum(){
console.log(2);
}
fn();
sum();
---------------------------
变量提升
var fn;
function sum(){
console.log(2);
}
sum();//2
fn();//报错信息:fn is not a function 现在的fn是普通变量不是函数
fn = function(){
console.log(1);
};
fn();
sum();
案例3:
console.log(obj.f1);//报错TypeError: Cannot read properties of undefined (reading 'f1')
var obj={
f1:function(){
console.log(1)
}
---------------------------
变量提升
var obj;
console.log(obj.f1);//只声明了变量名,还没有创建出对象,现在是无法访问对象属性的
obj={
f1:function(){
console.log(1)
}
}
5. return 下面的代码进行变量提升,return 后面的代码不进行变量提升。
案例1:
function fn(){
console.log(a);
return function f1(){
}
var a=3;
}
fn();
---------------------------
变量提升
var a;
function fn(){
console.log(a);//undefined
return function f1(){
}
a=3;
}
fn();
案例2:
function fn(){
console.log(f2);
return function f1(){
}
function f2(){
console.log("f2")
}
}
fn();
------------------
变量提升
function fn() {
function f2() {//声明并赋值
console.log("f2")
}
console.log(f2);//函数体
/*ƒ f2() {
console.log("f2")
}*/
return function f1() {}
}
fn();
7.5 综合练习
<body>
<script>
//遇到if成立的情况,function声明+赋值
// 第一轮:先声明提前
var a = 0;
// if成立,以函数为界限,分为上级作用域和块级私有域
if (true) {
a = 1;
function a() { }
a = 21;
console.log(a);//21
}
console.log(a);//1
</script>
</body>
分析:
第一轮:变量提升
var a;
a = 0;
if成立,以函数为界限,分为上级作用域和块级私有域
第二轮:变量提升
声明函数function a
var a 与 function a()重名,所以变量a变成了函数a。
进入if立刻重新变量提升声明函数+赋值
函数a = 1;
--------------------以函数为界限---------------------
第二轮:变量提升
函数a = 21;