zoukankan      html  css  js  c++  java
  • JS作用域、执行上下文、递归与闭包

    目录

     

    作用域

    全局作用域

    函数作用域

    执行上下文

    函数执行上下文

    执行上下文栈

    作用域与执行上下文的区别

    递归

    闭包

    产生闭包的条件

    闭包的作用

    使用注意 

    内存泄漏

    内存溢出(一种程序运行出现的错误)


    作用域

    作用域指一个变量的作用范围。它是静态的(相对于上下文对象), 在编写代码时就确定了。

    作用:隔离变量,不同作用域下同名变量不会有冲突。

    全局作用域

    直接编写在script标签中的JS代码,都在全局作用域。

    在全局作用域中:

    • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。

    • 创建的变量都会作为window对象的属性保存。

    <script>
        var a = 10;
    </script>
    • 创建的函数都会作为window对象的方法保存。

    <script>
        function say(){
        }
    </script>

    全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问到。

    函数作用域

    调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁。每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的。

    在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量。简单讲就是里面可以访问外面,但是外面不能访问里面。

    在函数中要访问全局变量可以使用window对象。

    <script>
        var a = 10;
        function say(){
            console.log(window.a);
        }
    </script>

    提醒1:

    在函数作用域也有声明提前的特性:

    • 使用var关键字声明的变量,会在函数中所有的代码执行之前被声明

    • 函数声明也会在函数中所有的代码执行之前执行

    因此,在函数中,没有var声明的变量都会成为全局变量,而且并不会提前声明。

    <script>
        var a = 10;//全局
        function say(){
            var a = 5;//局部变量
            b = 20;//全局
            console.log(window.a);
        }
    </script>

    提醒2:定义形参就相当于在函数作用域中声明了变量。

    <script>
        var a = 10;//全局
        function say(b){
            //b为局部
        }
    </script>

    执行上下文

    函数执行上下文

    在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象(虚拟的, 存在于栈中)。

    (1)对局部数据进行预处理:

    • 形参变量==>赋值(实参)==>添加为执行上下文的属性

    • arguments==>赋值(实参列表), 添加为执行上下文的属性

    • var定义的局部变量==>undefined, 添加为执行上下文的属性

    • function声明的函数 ==>赋值(fun), 添加为执行上下文的方法

    • this==>赋值(调用函数的对象)

    (2)开始执行函数体代码

    执行上下文栈

    • 1.在全局代码执行前, JS引擎就会创建一个栈来存储管理所有的执行上下文对象

    • 2.在全局执行上下文(window)确定后, 将其添加到栈中(压栈)

    • 3.在函数执行上下文创建后, 将其添加到栈中(压栈)

    • 4.在当前函数执行完后,将栈顶的对象移除(出栈)

    • 5.当所有的代码执行完后, 栈中只剩下window

    作用域与执行上下文的区别

    区别1:

    • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时

    • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建

    • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

    区别2:

    • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化

    • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

    联系:

    • 执行上下文(对象)是从属于所在的作用域

    • 全局上下文环境==>全局作用域

    • 函数上下文环境==>对应的函数使用域

    递归

    一个函数通过名字调用自身的情况,好处是代码简洁。在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多。

    注意:

    • 找规律,很容易找到规律的使用递归特别方便

    • 出口,必须得有个已知的,没有出口就会一直循环

    缺点:

    • 函数调用导致的时间和空间消耗大

    • 存在重复计算,效率低

    • 调用栈可能会溢出

    <script>
    //递归实现阶乘
        function sayhi(num) {
            if (num <= 1){
                return 1;
            }else {
                return num * sayhi(num-1);
                }
            }
            console.log(sayhi(5));
    </script>
    <script>
    	//递归:一个函数可不可以自己调用自己
    	 function f1() {
    	 	console.log("hello");
    	 	f1();
    
    	 }
    	 
    	//递归就是老和尚讲故事,死循环了,必须要给一个结束的条件才有意义
    	 var i = 0;
    	 function f1() {
    		
    	 	console.log("hello");
    	 	i++;
    	 	if (i < 10) {
    	 		f1();
    	 	}
    		
    
    	// }
    	// f1();
    	// 
    	//求n个数的累加,我们可以采取递归来替代循环
    	 function getSum(n) {
    	 	if (n === 1) {
    	 		return 1;
    	 	}
    
    	 	return n + getSum(n-1);
    
    	 }
    	 console.log (getSum(100));
    	 	
    	//递归调用比较占用资源,循环次数太多不适合使用
    	//
    	//输入一个数,求这个数的各位数字之和
    	//先取余再相除在取整
    	function getSum(n) {
    		//结束条件
    		if (n < 10) {
    			return n;
    		}
    		
    		return n % 10 + getSum(parseInt(n/10));
    	}
    	console.log(getSum(140));
    		 
    </script>

    闭包

    有权访问另外一个函数作用域中的变量的函数。当内部函数被保存在外部时,将会生成闭包。

    产生闭包的条件

    • 1.函数嵌套

    • 2.内部函数引用了外部函数的数据(变量/函数)。

    缺点:闭包导致原有作用链不释放,导致内存泄露(漏得越多,等价占用的内存越多,剩下的可用内存就少了)。

    全局变量和局部变量,该特殊之处在于函数内部可以读取全局变量,但是函数外部是不能读取局部变量的。函数内部声明变量的时候,一定要使用var命令。这就导致一个问题,如果我们需要从外部读取函数内的局部变量的时候,就必须采用一种特殊的方式,在函数内部再定义一个函数。

    <script>
     function f1(){
        var n=22;
        function f2(){
          alert(n); // 22
        }
    
      }
    </script>

    我们只需要将f2作为返回值,就可以读取f1的内部变量了。

    上述f2就是闭包,也就是说闭包是一个可以读取其他函数内部变量的函数;即一个函数内部的函数。

    闭包的作用

    • 读取函数内部的变量,实现公有变量;
    • 可以做缓存,使得变量的值始终保持在内存中;
    • 可以实现封装,属性私有化;
    • 模块化开发,防止污染全局变量

    举例1:

        <script>
            function f1() {
                var a = 1;
                function f2() {
                    a ++;
                    console.log(a);
                }
                return f2;
    
            }
            var f = f1();
            f();//2
            f();//3
            f();//4
            f();//5
        </script>

    ps:外部函数f1执行完毕后,变量a并没有消失,而是保存在了内存中。

    内部采用函数表达式,没有产生闭包的原因如下:

        <script>
            function f1() {
                var re = [];
                for (var i=0; i < 10; i++){
                    re[i] = function () {
                        console.log(i);
                    };
                }
                return re;
            }
            var f = f1();
            for (var j=0; j < 10; j++){
                f[j]();
            }
            // 输出为10个10
            
        </script>

    ps:f1执行完了之后外部i值已经是10了,函数内部只是引用 ,只有外部调用的时候才会运行函数,那时候i就已经是10了,而且10个函数都指向同一个AO。

    举例2:将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数。

    <script>        
        function myModule() {
            //私有数据
            var msg = 'hello'
    
            //操作私有数据的函数
            function doSomething() {
                console.log('doSomething() '); 
            }
    
            function doOtherthing() {
                console.log('doOtherthing() ')) 
            }
    
            //通过【对象字面量】的形式进行包裹,向外暴露多个函数
            return {
                doSomething1: doSomething,
                doOtherthing2: doOtherthing
            }
        }
    </script>

    使用注意 

    • 内存消耗大,理性使用
    • 不要随便改变父函数内部变量的值

    内存泄漏

    内存泄漏:占用的内存没有及时释放。内存泄露积累多了就容易导致内存溢出。

    常见的内存泄露:

    • 1.意外的全局变量

    • 2.没有及时清理的计时器或回调函数

    • 3.闭包

    内存溢出(一种程序运行出现的错误)

    内存溢出:当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误。

  • 相关阅读:
    Swift之类型安全和类型推断
    Swift之浮点数
    Swift之整数
    泛互联网产品技术支持划分
    WPF WebBrowser 不可见问题的解析[转]
    form WebBrowser自动点击弹出提示框alert、弹出对话框confirm、屏蔽弹出框、屏蔽弹出脚本错误的解决办法
    Mvvm绑定datagrid或listview的selectItems的方法[转]
    [C#]获取最近在Windows上所使用的文件
    拷贝构造函数和赋值运算符重载的区别
    Why Doesn’t Drag-and-Drop work when my Application is Running Elevated? – A: Mandatory Integrity Control and UIPI(转载)
  • 原文地址:https://www.cnblogs.com/markniefeng/p/10561859.html
Copyright © 2011-2022 走看看