作用域、作用域链、变量提升

132 阅读7分钟

一、作用域

  • 全局作用域
  • 私有作用域
  • ES6的块级作用域

1、全局作用域

当打开页面的时候,会提供一个給js代码执行的环境全局作用域,默认提供一个最大的window对象

【全局变量】:在全局作用域中保存的变量

全局变量与window的关系

判断一个对象有没有某个属性:属性名 in 对象
var a = 10; 
undefined
"a" in window; // 返回值为布尔类型
true
  • 在全局作用域下声明的变量,相当于給window添加的一个属性(属性名就是变量名,属性值就是变量值)
  • window上的方法,可以省略window。(eg:window.alert("1"可以写成alert("1")))
  • ES6中let、const声明的变量,阻断了与window的关系

在全局变量中带var 和 不带var

  • 相同点:都是給window添加属性
  • 不同点:
    1. 带var 有变量提升,不带var 没有变量提升;
    2. 带var 通过delete window.属性名 删除不掉;不带var的可以删除掉(也就说明隐含全局变量严格来说不是真正的变量,而是全局对象的属性,因为属性可以通过delete删除,而变量不可以。)
 var a = 10;
 "a" in window;
 true
 b = 20;
 delete window.a;
 false
 delete window.b;
 true

2、私有作用域

函数执行的时候,形成的作用域就是私有的,保存里面的变量不受干扰

【私有变量】:

  • 函数里边声明的变量
  • 形参

二、作用域链

查找变量的时候,先看自己私有作用域中有没有,没有的话,就向上一级作用域继续查找

获取

  • 未定义也未声明的变量,console的时候, 会报错:n is not defined;
function fn() {

    a = 10;
    console.log(n);

}
undefined
fn(); // Uncaught ReferenceError: n is not defined

赋值

  • 赋值但未声明的变量,会被默认定义为全局变量
function fn() {

    a = 10;
    n = 20;
    console.log(n); // 20

}
fn(); 
console.log(n); // 20

三、堆栈内存

栈内存

  1. 供js代码运行的环境(window全局作用域、函数执行时的局部作用域)
  2. 存储基本数据类型

堆内存

  • 存储引用数据类型
    • 对象:键值对形式存储
    • 函数:字符串形式存储

四、变量提升

  • 代码执行的时候,首先会形成一个供js执行的环境,接下来在代码自上而下执行之前有一步操作:“变量提升”(会把带var 和带function的变量找出来)
    • var:只声明
    • function:声明并且定义(赋值)变量
  • 变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的变量进行提升,因为此时函数中存储的都是字符串),栈内存形成之后,代码执行之前。
  • 当代码执行遇到创建函数(除匿名函数(函数表达式和立即执行函数)外)这部分代码后,会直接跳过,因为在提升阶段就已经完成函数的赋值操作;函数执行前 首先 生成私有作用域,私有作用域形成之后,先形参赋值,再进行变量提升。

变量提升的特殊性!!

1、判断语句:不论条件是否成立,都会进行变量提升

  • VAR 还是只声明,不定义
  • FUNCTION
    • 在IE10以及其以下老版本的浏览器中:声明+定义
    • 在最新版本的浏览器中:只声明
// 例题1
console.log(a); 
if(1==2){
  console.log(a);
  var a=12; 
  console.log(a)
}
console.log(a);
(1) 自执行函数在全局作用域下不进行变量提升
  • 最新版本浏览器之条件语句中的变量提升(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()); 

// 题目解析:(最新版本浏览器中)
1、在全局作用域中,变量提升:无
2、代码开始自上往下执行 f 就是window.f=function(){}, g就是window.g=function(){}

     自执行函数,形成私用作用域,变量提升:不管条件是否成立,判断语句里面的代码都会进行变量提升,function g 在新版本浏览器中只声明未定义。 接着走到if语句中,g(),此时的g只声明未定义,相当于undefined(),所以g( )会报类型错误,下面的代码都不会执行Uncaught TypeError: g is not a function
  • IE10以及其以下老版本浏览器之 条件语句中的变量提升(FUNCTION变量提升时 声明+定义)

(2)条件判断下的变量提升到底有多坑!
在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升
// 例题3
console.log(fn); 
if(1==1){
   console.log(fn); 
   function fn(){

       console.log("ok");

   }
}
console.log(fn)

解析:
console.log(fn); // undefined 在新版本浏览器中,不管条件是否成功,都会进行变量提升,function 只声明,
if(1==1){

    console.log(fn);// fn 函数:在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升   
                    // 再从上往下执行代码,此时fn 定义完成。

   function fn(){

       console.log("ok");

   }
}
console.log(fn) // 条件成立,给fn进行了赋值,打印出fn函数
在条件判断下,如果有function定义的变量,在这个function这个函数后面的更改变量的值,更改的都是私有变量。
// 例题4
 var a=0; 

    if(true){
       a=1;
       function a(){}
       a=21;
       console.log(a);
    }
    console.log(a);

2、只对等号左边的做变量提升

console.log(fn); 
console.log(fn(1, 2)); 
var fn=function (n, m){

    return n+m;

 }
console.log(fn(3, 4));

3、函数里return 下面的代码本身是不执行的,但是可以进行变量提升;而return后边的代码不进行变量提升

function fn(){
  console.log(f2); 
  return function f1(){
  }// return后面的代码 不会进行变量提升
  // return下面的代码可以进行变量提升
  function f2(){

    console.log("f2")

  }
}
fn();

4、如果变量名字重复该如何?(重复变量只会声明一次,但可以多次赋值)

对于VAR的不会进行重复声明,但会重新赋值
var num=2; 
var num=3; 
console.log(num);
对于FUNCTION 在变量提升阶段,遇到重复声明定义的,会进行重新赋值
fn(); 
function fn(){

    console.log(1); 

}
function fn(){

    console.log(2);   

}
fn(); 
function fn(){

    console.log(3);

}
fn=100; 
function fn(){

    console.log(4);   

}
fn();

5、自执行函数 在当前所在的作用域(包含自执行函数的区域)中,不进行变量提升(自执行函数自己所形成的私有作用域照常进行)

function f2(){
   console.log("f2"); 
}

 console.log(f1); // 报错 f1 is not defined; 

(function f1(){
   console.log(a); // undefined, 照常进行变量提升
   var a=3; 
})();

五、掌握程度测试

- 1、  !!!
```
    var ary = [12, 23]; 
    var s = 100;
    function fn(ary,s) {
    		// ary的形参赋值为空间地址(全局ary的空间地址)
        s = 200;
        ary[0] = 100;
        console.log(ary); 
        console.log(s);
        ary = [100];  // ary赋值  重新开启一个空间地址
        console.log(ary); 
    }
    fn(ary,s); // ary = 空间地址传入函数
    console.log(ary);
    console.log(s)
```
- 2、 !!
```
    console.log(a); 
    console.log(b);
    console.log(c);
    var a, b, c = 10;
    console.log(a);
    console.log(b);
    console.log(c);
```

- 3、
```
    console.log(a, b);
    var a = 12,
        b = 12;
    function fn() {
        console.log(a, b);
        var a = b = 13;
        console.log(a, b);
    }
    fn();
    console.log(a, b);
```


- 4、
```
    var a = 4;
    var f = 3;
    function b(x,y,a) {
        alert(a); //4
        // 形参和arguments存在映射关系;
        arguments[2]=10;
        alert(a); 10
    }
    a=b(1,2,f);
    alert(a);
    console.log(f); //10
```
- 5、
```
    fn();
    function fn() {
        console.log(1);
    };
    fn();
    function fn() {
        console.log(2);
    };
    fn();
    var fn = function () {
        console.log(3);
    };
    fn();
    function fn() {
        console.log(4);
    };
    fn();
    function fn() {
        console.log(5);
    };
```

- 6、
```

    var foo='hello';
    (function(foo){
      
        console.log(foo); 
        var foo=foo||'world'; 
        console.log(foo); 
    })(foo);
    console.log(foo); 
```

- 7、(自执行函数没有变量提升) !!
```
    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())
```

* 8、

``` 

    var foo = 1;
    function bar() {
	if (!foo) {
		var foo = 10;
	}
	console.log(foo); // 
    }
    bar();
```

* 9、

``` 

var a = 10;
(function () {
    console.log(a); // undefined
    a = 5;
    console.log(window.a); // 10
    var a = 20;
    console.log(a);  // 20
})()
```

* 10、

``` 

console.log(a);  
console.log(b);  
var a = 1;
function a(){}
var b= function(){}; 
console.log(a); 
```

* 11、 !!!!

``` 
// 函数提升优先级高于变量提升,且不会被同名变量声明时覆盖,但是会被变量赋值后覆盖
fun();
function fun() {
    console.log(1);
}
var fun = function () {
    console.log(2);
}
function fun() {
    console.log(3);
}
fun();
function fun() {
    console.log(4);
}
fun();
var fun = function () {
    console.log(5);
}
fun();
```

* 12、

``` 

console.log(fn);
if (1 == 1) {
    console.log(fn);

    function fn() {
        console.log("ok");
    }
}
console.log(fn);
```

-13、
let a=10,
b=10;
fn=function(){
    console.log(a);
    let a=b=20;
    console.log(a,b);
};
fn();
console.log(a,b)

-14、
function forEach() {
    var i = 0;
    for (i = 0; i <= 5; i++) {
        console.log(i);
    }
    console.log(i);
}

forEach();

-15、  !!!!!!
var a=9;
function fn(){
  a=0;
  return function(b){
     return b+a++;  // 先+  再 ++ (先return b+a,再a++)
  }
}
var f=fn();  // 把函数function(b) { ... }的空间地址赋值给f
console.log(f(5));
console.log(fn()(5));  // fn() 重新执行一次,a又赋值为0
console.log(f(5));
console.log(a);

-16、
var i=10;
function fn(){
   return function(n){
      console.log(n+(++i));
   }
}
var f=fn();
f(20);
fn()(20);
fn()(30);
f(30);