JS之作用域与变量提升

作用域与变量提升

作用域(栈内存、执行上下文)

全局作用域(全局栈内存)

  • 浏览器打开一个页面,开始运行时率先形成一个栈内存,这个栈内存又叫全局作用域,为代码提供执行环境,在全局作用域下会生成一个全局的大对象叫window。
  • 浏览器打开,生成的全局作用域一般不销毁,直到页面关闭。

全局变量

  • 在全局作用域下声明的变量就是全局变量
  • 在全局下定义的变量会默认给window新增一个属性名,属性名是变量名,属性值是变量所存储的值;
  • // 在全局作用域下定义的函数,也会给window新增键值对;属性名是函数名,属性值时函数的空间地址;
  • c = 100; //在函数里,也会给window新增键值对
  • 当全局作用域中存在n,window中也存在n,会先找全局,再找window;
// let  : let声明的变量不能给window新增键值对;const声明的常量也不能新增键值对;var声明的变量可以给window新增键值对;
const e=0;
let  d=1;
var f=3;
console.log(window.d,window.e,window.f);//undefined undefined 3
  • 全局变量的区别
    1. 用var和function 声明的变量会在全局作用域下率先创建,而且也会给window增加属性,属性名是变量名,属性值是变量名储存的值(let,const不可以)
      var s = 12;
      function fn(){}
      console.log(window.s)
      console.log(window.fn)
      let a = 12;
      `console.log(window.a) // undefined``

    2. var跟function可以重复创建同一个变量名(let不可以)
      var a = 12;
      var a = 13;
      console.log(a) // 13
      let a = 12; // 报错 SyntaxError(语法错误)
      `let a = 13;``

    3. var和function有变量提升(let没有)
      b = 12 // 等价于window.b = 12因为window.可以省略
      var b = 12(有变量提升)

创建变量和赋值

var a,b,c = 12; //undefined   undefined  12
//创建变量a,b,c,但是只给c赋值
var a = b = c = 12; // 等价于 var a = 12; b = 12; c = 12;
var a = 12,b = 13, c = 14; // 创建三个变量,给每一个变量都进行赋值
       
		var a, b, c = 100; // 代码时被var声明过,只是a,b没有被赋值
        console.log(b); //undefined
        var d = e = f = 200; // e,f没有被var;只有d被var过;但是def都被赋值200;
        console.log(e); // 200

私有作用域(私有栈内存)

  • 全局作用域生成之后才会有私有作用域,私有作用域属于全局作用域
  • 函数执行时会形成一个私有作用域(私有栈内存)为函数内的代码执行提供环境。
  • 函数执行会形成私有的作用域,会保护里面的私有变量不受外界的干扰;

创建函数时

  1. 首先开辟一个堆内存生成一个16进制的空间地址
  2. 把函数体里的代码以字符串的格式存储进去
  3. 把16进制的地址赋值给函数名

执行函数时

  1. 首先开辟出一个私有栈内存(私有作用域)
  2. 形参赋值
  3. 变量提升
  4. 代码从上往下执行
  5. 作用域是否被销毁

私有变量

  • 在私有作用域中定义的变量就是私有变量(var、function、let、const····)
  • 形参也是私有变量
  • 在私有作用域里使用一个变量,如果自己私有作用域里有,直接用自己的,如果没有,就取上一级作用域的
  • 函数外边不能拿到函数里边的变量
  • 在函数私有作用域中定义的变量,不会给window新增键值对;
  • 在函数体中函数不会给window新增键值对,函数外面不能调用此函数

变量提升(声)

  • 变量提升就是浏览器解析代码的一个过程,发生在js代码之前。
  • 变量提升的含义
    • 在当前作用域,代码执行之前。浏览器会从头到尾检测一遍所有变量,给带var和function进行提前声明和定义。
    • 带var的会只声明(创建变量),不定义(赋值)
    • 带function的既声明(创建变量),又定义(赋值)
  • 当函数执行的时候,才会对函数体中的代码发生变量提升;

  • let会形成块级作用域,let和{}结合就会形成块级作用域

{
            let w = 13
        }
        if(true){
            var s = 13;
            let w = 12;
        }
        console.log(w)
        console.log(s)

        console.log(a)
        解决暂时性死区
           console.log(typeof a) 
           let a  = 12;
       
           const w;//const创建常量必须赋值
           console.log(w)

变量提升的特殊情况

  1. 变量提升发生在等号左边
var a = function () {}//此处,浏览器变量提升时,只识别var,不识别function
  1. 不管if条件是否成立,if里的代码都要进行变量提升
    • 在老版本浏览器里,if条件里的function既声明,又定义
    • 在新版本浏览器里,if条件里的函数只声明,不定义
    • 条件一旦成立,马上给函数进行定义,然后再执行代码
console.log(num)//undefined
if(false){
   // 只要进入到块级作用域,立即开辟堆内存;对函数赋值
  var num = 12;
}
console.log(num)//undefined



console.log(fn); // undefined
    // 在老版本浏览器里,if条件里的function既声明又定义,
    // 在新版本浏览器里,if条件里的函数只声明不定义
if(false){
     // 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
     fn()
     function fn(){}
 }
 console.log(fn) // undefined



console.log(fn); // undefined
    // 在老版本浏览器里,if条件里的function既声明又定义,
    // 在新版本浏览器里,if条件里的函数只声明不定义
if(true){
     // 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
     fn()
     function fn(){}
 }
 console.log(fn) // fn(){}
  1. 在函数里,虽然return下面的代码不执行,但是要进行变量提升,return 后面的代码不进行变量提升的;
function fn() {
     console.log(ss); // undefined
     return function f(){
              };//return返回值没有变量提升,中断下面代码执行
     var ss = 34;//此处的ss仍然要变量提升。永远是undefined
 }
 fn()

  1. 自执行函数、匿名函数不进行变量提升
 // 当代码执行到这一行时,先开辟一个空间地址,然后再执行
(function(){
            
})()
var  fn = function(){};
//
        var obj = {
            fn:(function(){
                console.log(100);
                // 如果fn的属性值时一个自执行函数,那么当代码以键值对存储的时候
              //(当代码执行到这一行时,(会先开辟堆内存空间,然后再立即形成栈内存,
        代码执行)自执行函数就会运行),并且把自执行函数的执行结果赋值给属性名fn;
                return function(){

                }
            })()
        };
        console.log(200)
        obj.fn();
        obj.fn();// 执行函数中返回的小函数执行

5.如果变量名重名,不再进行重复声明,但是要重新赋值;


        var num = 1;
        console.log(num);// 1
        var num = 2;
        console.log(2);
        fn();// 4
        function fn(){
            console.log(1);
        }
        function fn(){
            console.log(2);   
        }
        fn();// 4
        function fn(){
            console.log(3);
        }
        fn=100;
        function fn(){
            console.log(4);   
        }
        fn();// 报错

6.函数里的 let ,词法解析的时候定义了一下,let没有变量提升


//1.let 声明的变量不进行变量提升;
        
				var a = 1;
        function bar() {
            console.log(b)//Cannot access 'a' before initialization//初始化前无法访问“a”
            let b = 10;//词法解析的时候定义了一下,let没有变量提升
        }
        bar();
//2.let声明的变量在同一个作用域下不允许重名; 
// 在代码运行之前,会对当前作用域下带let进行解析,判断是否有重名的变量;有的话,就直接报错;
        // let a=1;
        // let a=2;    
  1. 给window新增键值对是发生在变量提升的阶段;
//console.log(window);
//var a=100;

微信图片_20191119124659.png

对象里的in属性

in属性,检测一个对象中有没有某个属性名,如果有就返回true,反之返回false

let obj = {
	name:1
}
console.log('name' in obj)//true
console.log('d' in obj)//false


console.log(fn); // undefined
// var和function给window增加属性的过程是变量提升的时候发生的
if ('fn' in window) {
	// 如果条件成立,进来的后的第一件事就是给fn赋值,然后在执行代码
	fn(); // 'erYa'
	function fn() {
	    console.log('erYa');
	}
}
fn();

函数的作用域查找

函数的上一级作用域是谁,在函数定义的时候就已经确定了,函数在哪创建的,他的上一级作用域就是谁,跟函数在哪执行没有关系。

作用域链查找机制

在私有作用域中,函数执行,如果要使用一个变量,获取变量所对应的值时,自己作用域要是有,就使用自己的,要是没有,就向上一级作用域查找,上一级还没有,在向上一级查找,直到找到全局作用域,如果还没有就报错—>这种一级一级向上查找的机制就是【作用域链查找机制】
TIM截图20191119163857.png

        /* 
        n = 1
        fn:fn
        x = f
        */
        var n = 1;
        function fn() {
            /* 
            n = 2 1 0
            f:f
            */
            var n = 2;
            function f() {
                /* 
                
                */
                n--;
                console.log(n); // 1 0 
            }
            f();
            return f;
        }
        var x = fn();
        x();
        console.log(n); //1

闭包


  • 函数执行形成的私有作用域就是闭包,他可以保护里边的私有变量不受外界干扰,
  • 还可以保存变量
  • 利用了函数执行会形成不销毁的作用域,可以保存变量的特点=>这个机制也叫闭包
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        #box {
            width: 500px;
            margin: 10px auto;
        }

        ul {
            list-style: none;
            position: relative;
            top: 1px;
            text-align: center;
            display: flex;

        }

        li {
            border: 1px solid black;
            line-height: 50px;
            height: 50px;
            font-size: 30px;
            margin-right: 20px;
            padding: 0 10px;
        }

        #box div {
            width: 500px;
            text-align: center;
            height: 300px;
            line-height: 300px;
            font-size: 50px;
            color: orangered;
            border: 1px solid black;
            display: none;
        }

        #navList li.active {
            border-bottom-color: white;
        }

        #box div.active {
            display: block;
        }
    </style>
</head>

<body>
    <div id="box">
        <ul id="navList">
            <li class="active">erYa</li>
            <li>jinYu</li>
            <li>xiaoHua</li>
        </ul>
        <div class="active">长的这俊哩</div>
        <div>你好帅啊</div>
        <div>哈哈</div>
    </div>
    <script>
        let navList = document.querySelectorAll('#navList li');
        let tabList = document.querySelectorAll('#box div');
        console.log(navList, tabList);

        // for (var i = 0; i < navList.length; i++) {
        //     // 把当前的i保存到元素结构上
        //     navList[i].setAttribute('myIndex', i);
        //     // 给每一个li绑定点击事件
        //     navList[i].onclick = function () {
        //         // this:点击谁,this就是谁
        //         //点击相应的li,获取到li结构上的myIndex属性,当做实参传递给fn方法
        //         fn(this.getAttribute('myIndex'))
        //     }
        // }

        function fn(index) {
            // 清除每一个li和每一个div的样式名
            for (var i = 0; i < tabList.length; i++) {
                navList[i].className = ''
                tabList[i].className = ''
            }
            // 给相应的li和div加上样式名
            navList[index].className = 'active'
            tabList[index].className = 'active'
        }




        // for (var i = 0; i < navList.length; i++) {
        //         // 当往元素的堆内存中存储键值对的时候,自执行函数就已经运行了
        //     navList[i].onclick = (function(index){
        //         // 函数return了一个引用数据类型值,而且return的值被外界所接收,所以这个作用域不销毁
        //         // 把return后面这个小函数赋值给onclick
        //         // 其实就是利用了函数执行会形成不销毁的作用域,可以保存变量的特点=>这个机制也叫闭包
        //             return function(){
        //                 fn(index)
        //             }
        //     })(i)
        // }

        /* 
        利用了闭包可以保存私有变量的特点,
        而且这个闭包是不销毁的作用域
        i=0
        navList[0].onclick = (function(index){ // 0
                    return function(){
                        fn(index)
                    }
            })(i) // 0

        i=1
        navList[1].onclick = (function(index){ // 1
                    return function(){
                        fn(index)
                    }
            })(i) // 1

        i=2
        navList[0].onclick = (function(index){ // 2
                    return function(){
                        fn(index)
                    }
            })(i) // 2
         */


        // for (let i = 0; i < navList.length; i++) {
        //     // ES6中let在for循环的大括号会形成块级作用域
        //     navList[i].onclick = function () {
        //         fn(i)
        //     }
        // }

        /* 
        {i=0
             navList[0].onclick = function () {fn(i)}
        }

        {i=1
             navList[2].onclick = function () {fn(i)}
        }

        {i=2
             navList[2].onclick = function () {fn(i)}
        }

         */
    </script>
</body>

</html>

块级作用域

//  
		console.log(num);// undefined
        console.log(fn);// undefined
        if([]){
            // 只要进到当前if条件中,会立即对fn进行赋值;
            // 支持es6的浏览器,会把这个if的大阔号解析成一个块级作用域;
            fn()
            var num=100;
            function fn(){console.log("a")}
        }
        console.log(fn);//fn(){console.log("a")}
//
        console.log(fn);// undefined
        for(var i=0;i<2;i++){
            function fn(){}
        }
        console.log(fn);//function fn(){}
//
        console.log(f); //undefined
        let i = 0;
        while (i < 1) {
            i++
            function f() {

            }
        }
        console.log(f); //function fn(){}
  • 块级作用域,和形参重名,函数赋值不能影响外面
         var b = {
            a: "hello"
        };
        function fn(b) {
            b.a = "world";
            if (true) {
                function b(){}//块级作用域,和形参重名,函数赋值不能影响外面
            }
            console.log(b);//{a: "world"}

        }
        fn(b)
  • 在块级作用域下var和function是不可以重名的
       //  在块级作用域下var和function是不可以重名的
        if (!("aa" in window)) {
            var aa = 1;

            function aa() {
                console.log(aa)
            }
        }
        console.log(aa);//报错Identifier 'aa' has already been declared  已声明标识符“aa”//

猜你喜欢

转载自blog.csdn.net/Sheng_zhenzhen/article/details/106797808