• this、apply/call、bind、闭包、函数、变量复制


    一、实际场景中抽象出的一个问题

     下面this各指向什么?

              var a = {                    
                 b: function() {
                    console.log(this);
                 },
                
                 f: function() {            
                  var c = this.b;
                  c();
                }
            };
            
            a.b();
            a.f();

     第一个this指向a,第二个this指向window。(做对了吗)

    二、JavaScript中变量复制的问题

           变量拷贝分为值拷贝和引用类型数据拷贝

           一个变量向另一个变量复制基本类型数据值时,另一个变量会在自己所占的内存中保存一份属于自己的数据值。

           一个变量向另一个变量复制引用类型数据值时,实质上复制的是一个指针变量,这个指针指向堆内存中的对象,复制之后这两个变量指向同一个对象。

           Es5中基本数据类型包括string、number、null、undefined、boolean

                     引用类型包括Array、RegExp、Date、Function等

                   (  基本包装类型:String、Boolean、Number; 单体内置对象:Global、Math)

    三、函数

        创建一个函数的方式: 函数声明、函数表达式、通过Function构造函数创建,它接受任意数量的参数,最后一个参数始终被看成是函数体。

       (例如:new Function(“num1”, “num2”, “return num1+num2”))          

        函数声明有声明提升的过程,解析器在向执行环境中加载数据时会优先读取函数声明,保证它在执行任何代码之前都可用,而函数表达式则是在解析器

        执行到它所在的代码行时才被解析执行。

          var condition = false;
          console.log( afun(100) );//undefined
          if (condition) {
    	      function afun(num) {
    	      	return num + 100;
    	      }
          }
          else {
    	      function afun(num) {
    	      	return num + 200;
    	      }
          }	  
    

      上面的代码,函数调用之前还没有做判断,所以还没有afun函数

          var condition = false;    
          if (condition) {
    	      function afun(num) {
    	      	return num + 100;
    	      }
          }
          else {
    	      function afun(num) {
    	      	return num + 200;
    	      }
         }	  
    	  console.log( afun(100) );//300
    	  
    

      js中没有块级作用域的概念,上面的代码可以正常调用

     js中函数也是一个对象,函数名是一个指针,所以在将一个函数的函数名复制给另一个变量时,这个变量也指向了这个函数

        if (condition) {
    	 var bfun = function(num) {
    	      return num + 100;
    	 }
        }
        else {
             var bfun = function(num) {
    	     return num + 200;
             }
        }	
    	
           var cfun = bfun;
           bfun = null;
    //     console.log( bfun(100) );//报错
           console.log( cfun(200) );//400
           
    

    验证函数拷贝之后,被赋值的变量和赋值的变量指向的是同一个函数,函数表达式和函数声明定义的函数在复制时是一样的,复制之后两个变量指向同一函数。

    复制之后,添加新的bfun,这个新的bfun覆盖原来的bfun,cfun同样也改变了,说明被赋值的变量和赋值的变量指向的是同一个函数。

            function bfun(num) {
    	     return num + 200;
            }         
            var cfun = bfun;
    	function bfun(num) {
    	     return num*1000;
    	} ;
           console.log( bfun(100) );//100000
           console.log( cfun(200) );//200000
          
    

      但是当采用函数表达式的形式(在用函数表达式定义的bfun函数,bfun也是一个全局变量,这里没有用var定义变量,规范写法应该加上var) 再次定义一个同名的函数时,

     如下:

           function bfun(num) {
    	  return num + 200;
           }         
            var cfun = bfun;    
            bfun = function(num) {
    	    return num*1000;
            } ;
           console.log( bfun(100) );//100000
           console.log( cfun(200) );//300
          
    

        函数表达式定义的函数没有覆盖函数声明定义的同名函数,在JavaScript中函数也是一个对象,函数名是一个指向函数的的变量指针,函数名也是一个变量,

    在JavaScript中有一条规则是,函数声明会覆盖和其函数名同名的变量的声明,但是不会覆盖同名的变量的赋值,不管它们定义的顺序如何。在用函数表达式定

    义的bfun函数中,bfun也是一个变量,只不过它被赋的值是一个匿名函数,一个变量的值可以是任何类型(string、number、boolean、null、undefined、一个

    函数、一个复杂类型数据(对象)等)。

     验证:函数声明会覆盖和其函数名同名的变量的声明,但是不会覆盖同名变量的赋值

           bfun = function(num) {
    	     return num*1000;
           } ;
    	    
            var cfun = bfun;
            
            function bfun(num) {
    	     return num + 200;
            }
    	   
           console.log( bfun(100) );//100000
           console.log( cfun(200) );//200000
    

    再看一段代码,最后结果为多少?

     bfun = function(num) {
    return num*1000; } ; var cfun = bfun; bfun = function(num) { return num + 200;
    } console.log( bfun(100) ); console.log( cfun(200) );

    最后结果为300、2000,后面的bfun覆盖了前面的bfun,在后面调用bfun时调用的是第二个,而cfun仍然为第一个bfun的值,why?

    不是说函数是一个对象,函数名相当于一个指针,指向的是函数,在复制之后两个变量会指向同一个函数吗?

    当bfun是采用函数声明的形式定义的时候,后面函数声明定义一个相同的同名函数之后,bfun改变,cfun会随之改变。而这里的bfun是采用函数表达式定义的函数,

    又会有什么不同?

    我的理解是,函数表达式定义的函数是没有函数名的,在复制的时候复制的是一个具体值,这里复制的就是一个匿名函数,而不是像函数声明复制一样复制的是一个

    内存地址(指针),

    函数表达式拷贝,就等同于基本数据类型变量拷贝,变量与变量之间各保存了一份属于自己的值,其中一个变量的值改变不会影响另外一个。

    函数的内存分配(无论是函数声明还是函数表达式定义的函数):

     函数表达式与函数声明不同的是,bfun再重新被赋值一个匿名函数之后,此时bfun指向一个新的对象(有点类似于原型上采用字面量的方式定义属性和方法一样,

    此时指向的是一个新对象),cfun还是指向原来的匿名函数。也就是函数声明方式定义的函数,定义同名的函数,同名函数会覆盖原来的函数;而函数表达式定义

    的函数不会被同名函数覆盖,定义同名函数之后原来的函数仍然存在。

     函数是一个对象,所以可以在函数名上定义属性和方法:

            bfun = function(num) {
    	    return num*1000;
            } ;
    	   
            var cfun = bfun;
            bfun.a = 7777;
            console.log(bfun.a);//777
            bfun.b = function(num) {
    	     return num + 200;
    	}
           console.log(typeof bfun);//function
    	   
           console.log(bfun.a);//777
           console.log( bfun(100) );	//10000    
           console.log( bfun.b(100) ); //300
    

     验证:函数表达式定义的函数不会被同名函数覆盖,定义同名函数之后原来的函数仍然存在

           bfun = function(num) {
    	    return num*1000;
           } ;
    	   
            var cfun = bfun;
            bfun.a = 7777;
            console.log(bfun.a);//777
            bfun = function(num) {
    	     return num + 200;
            }
            console.log(typeof bfun);//function
    	   
           console.log(bfun.a);//undefined
           console.log( bfun(100) );	//300   
           console.log( cfun(200) );//200000
           console.log(cfun.a);//777  

    函数是一个对象,所以可以在函数对象上添加属性和方法,这样看似乎对象不一定就是存储在堆内存中了?函数表达式中的匿名函数这样的函数对象就是存在栈中啊 

      函数名是一个指针,指向函数,函数是一个对象,存在堆内存中。

    四、闭包 

            var a = {                    
                 b: function() {
                    console.log(this);
                 },
                
                 f: function() {            
                  var c = this.b;
                  c();
                }
            };      
            a.b();
            a.f();

    通过上面的分析,c这里和b指向的是同一个函数,但是为什么this的指向不同?问题在于调用(直接调用b时this为a,在f中将b复制给c之后,在调用c,this指向window)

    函数的方式不一样,结果执行环境也不同。

             这个例子实质上类似于(this===window):

                 var object = {
    		    _name : "my object",
    		    getNameFunc : function() {
    		        
    		       var that = this;
                           var a = function() {
    		           console.log("value:"+this._name);//undefined		
    		        };		        
    		         a();                
    		    }
    		};
              console.log(object.getNameFunc());
    

      同样也类似于(this===window):

    	     var object = {
    		    _name : "my object",
    		    getNameFunc : function() {
    		         
    		        return function() {
    //		        	this._name = "yyy";
    		        	console.log(this);//window
    		                return this._name;//undefined		
    		        };
                                  
    		   }
    	     };
              console.log(object.getNameFunc()()); 

      上面三个例子都是在函数中创建了一个闭包函数

      闭包: 一个有权访问另一个函数中的变量的函数就是闭包,常见的闭包就是一个函数中包含另一个函数

       函数没有利用对象调用时,this指向window;以上 闭包中的this指向window,这又引申到this指向的问题。

    五、this的指向问题  (见:https://www.zhihu.com/question/19636194)

          调用函数的几种方式以及当前this的指向问题:

          直接通过函数名调用(无论是在哪里调用),此时this指向window

          通过对象点函数名的形式调用,this指向函数的直接调用者

          new关键字调用构造函数,this指向new出来的这个对象

          通过apply或call方法调用,this指向第一个参数中的对象,如果第一个参数为null,this指向window

          还可以通过bind()方法来改变函数的作用域,它返回的是一个函数的实例,最终需要通过这个实例来调用函数(apply和call方法是直接调用函数)

          通过apply和call方法用来改变函数的作用域,这是每个函数中都包含的两个非继承的方法。

     function a(num, num2) {
    
     } 

          当通过函数名直接调用一个函数时a(1,2),这个相当于a.apply(null, arguments),第一个参数是为null,所以this指向window

     var b = {      
           function a(num, num2) {
    
           }
     }

           当通过对象调用一个函数时b.a(1,2),这个相当于a.apply(b, arguments),第一个参数是为b,所以this指向b

           当把调用函数的方法换写成apply或call的形式就很好理解this的指向了

           关于直接调用函数和通过new关键调用构造函数的区别,如下:

         考察不同调用方式this的指向问题和变量提升的问题

                           var a=10;
    			function test(){
    				a=5;
    				alert(a);
    				alert(this.a);
    				var a;
    				alert(this.a);
    				alert(a);
    			}
    			
    			test(); 
    			new test();
    			// 5 10 10 5
    			//5 undefined undefined 5
    

      test()方法,this指向window,test中用var定义了一个a,它将被提升到执行环境的最前端,a=5

           new test(),此时test是个构造函数,this.a没有被定义所以为undefined

    总结:

        上面的问题的实际场景如下:   

        实际项目中抽象出来的一部分,用来统一给dom元素添加事件的写法,大大提升了代码的可维护性

    		var util = {
    		     maps: {
    			  'click #btn': 'clickBtn'
    		     },
    		     
    		     stop: function() {
    		     	console.log("stop....");
    		     },
    		     
    		      clickBtn: function(event) {
    				var e = event;
    				var target = e.target || e.srcElement;
    				console.log(target.id);             
    				this.stop();
    		       },
    			 
    		      _scanEventsMap: function(maps, isOn) {
    			  	
    		        var delegateEventSplitter = /^(S+)s*(.*)$/;
                            var bind = isOn ? this._delegate.bind(this) : this._undelegate.bind(this);
    //	     	        this._delegate('click', "#btn", this["clickBtn"]);
    //	     	        bind('click', "#btn", "clickBtn");                   
    	                for (var keys in maps) {	            	
    	                  if (maps.hasOwnProperty(keys)) {
    	                    var matchs = keys.match(delegateEventSplitter);
    	                    bind(matchs[1], matchs[2], maps[keys]);
    	                  }
    	               }
    		    },  
    		    
    	        _delegate: function(name, selector, func) {
    	            var ele = document.querySelector(selector),
    	                that = this,
    	                func = this[func];
    	                console.log(func);
    		    	ele.addEventListener(name, function(event) {
    		              var e = event || window.event;   				    	
    		    	      func.apply(that, arguments);
    		    	}, false);
    	        },
    	       
    	       _undelegate: function(name, selector, func) {
    	           var ele = $(selector);
    		     ele.removeEventListener(name, func, false);
                   },
              
                 _init: function() {
              	 this._scanEventsMap(this.maps, true);
                 }
           }		
           util._init();
    

      

  • 相关阅读:
    iOS 定制controller过渡动画 ViewController Custom Transition使用体会
    iOS 和 Android 中的Alert
    iOS 中的frame,bounds,center,transform关联
    Android Services重点记录
    iOS 中的字体预览
    iOS 中关于ViewController总结
    iOS7 和 iOS6的页面兼容问题
    docker安装并修改Nginx镜像
    docker环境搭建
    Eclipse中.setting目录下文件介绍
  • 原文地址:https://www.cnblogs.com/yy95/p/7214452.html
Copyright © 2020-2023  润新知