zoukankan      html  css  js  c++  java
  • javascript 误用this指针 的情况

    理解了this指针后,我们再来看看一些很容易误用this指针的情况。

    示例1——内联式绑定Dom元素的事件处理函数

    <script type="text/javascript">
      function sayHi(){
        alert("当前点击的元素是" + this.tagName);
      }  
    script>
    <input id="btnTest" type="button" value="点击我" onclick="sayHi()">

    在此例代码中,我们绑定了button的点击事件,期望在弹出的对话框中打印出点击元素的标签名。但运行结果却是:

    也就是this指针并不是指向input元素。这是因为当使用内联式绑定Dom元素的事件处理函数时,实际上相当于执行了以下代码:

    <script type="text/javascript">  
      document.getElementById("btnTest").onclick = function(){
        sayHi();
      }
    script>

    在这种情况下sayHi函数对象的所有权并没有发生转移,还是属于window所有。用上面的指导原则一套我们就很好理解为什么this.tagName是undefined了。

    那么如果我们要引用元素本身怎么办呢?

    我们知道,onclick函数是属于btnTest元素的,那么在此函数内部,this指针正是指向此Dom对象,于是我们只需要把this作为参数传入sayHi即可。

    那么如果我们要引用元素本身怎么办呢?

    我们知道,onclick函数是属于btnTest元素的,那么在此函数内部,this指针正是指向此Dom对象,于是我们只需要把this作为参数传入sayHi即可。

    <script type="text/javascript">
      function sayHi(el){
        alert("当前点击的元素是" + el.tagName);
      }
    script>
    <input id="btnTest" type="button" value="点击我" onclick="sayHi(this)">

    等价代码如下:

    <script type="text/javascript"> 
      document.getElementById("btnTest").onclick = function(){
        sayHi(this);
      }
    script>

    示例2——临时变量导致的this指针丢失

    <script type="text/javascript">
      var Utility = {
        decode:function(str){
          return unescape(str);
        },
        getCookie:function(key){
          // ... 省略提取cookie字符串的代码
          var value = "i%27m%20a%20cookie";
          return this.decode(value);
        }
      };
      alert(Utility.getCookie("identity"))
    script>

    我们在写稍微有点规模的Js库的时候,一般都会自己封装一个Utility的类,然后将一些常用的函数作为Utility类的属性,如客户端经常会用到的getCookie函数和解码函数。如果每个函数都是彼此独立的,那么还好办,问题是,函数之间有时候会相互引用。例如上面的getCookie函数,会对从document.cookie中提取到的字符串进行decode之后再返回。如果我们通过Utility.getCookie去调用的话,那么没有问题,我们知道,getCookie内部的this指针指向的还是Utility对象,而Utility对象时包含decode属性的。代码可以成功执行。

    但是有个人不小心这样使用Utility对象呢?

    <script type="text/javascript">
      function showUserIdentity(){
        // 保存getCookie函数到一个局部变量,因为下面会经常用到
        var getCookie = Utility.getCookie;
        alert(getCookie("identity"));
      }
      showUserIdentity();
    script>

    这个时候运行代码会抛出异常“this.decode is not a function”。(我允许报错:global object 没有decode方法)运用上面我们讲到的指导原则,很好理解,因为此时Utility.getCookie对象被赋给了临时变量getCookie,而临时变量是属于window对象的——只不过外界不能直接引用,只对Javascript引擎可见——于是在getCookie函数内部的this指针指向的就是window对象了,而window对象没有定义一个decode的函数对象,因此就会抛出这样的异常来。

    这个问题是由于引入了临时变量导致的this指针的转移。解决此问题的办法有几个:

    • 不引入临时变量,每次使用均使用Utility.getCookie进行调用
    • getCookie函数内部使用Utility.decode显式引用decode对象而不通过this指针隐式引用(如果Utility是一个实例化的对象,也即是通过new生成的,那么此法不可用)
    • 使用Funtion.apply或者Function.call函数指定this指针

    前面两种都比较好理解,第三种需要提一下。正是因为this指针的指向很容易被转移丢失,因此Javascript提供了两个类似的函数apply和call来允许函数在调用时重新显式的指定this指针。

    修正代码如下:

    script type="text/javascript">
      function showUserIdentity(){
        // 保存getCookie函数到一个局部变量,因为下面会经常用到
        var getCookie = Utility.getCookie;
        alert(getCookie.call(Utility,"identity"));
        alert(getCookie.apply(Utility,["identity"]));
      }
      showUserIdentity();
    script>

    call和apply只有语法上的差异,没有功能上的差别。

    示例3——函数传参时导致的this指针丢失

    我们先来看一段问题代码:

    script type="text/javascript">
      var person = {
        name:"Kevin Yang",
        sayHi:function(){
          alert("你好,我是"+this.name);
        }
      }
      setTimeout(person.sayHi,5000);
    script>

    这段代码期望在访客进入页面5秒钟之后向访客打声招呼。setTimeout函数接收一个函数作为参数,并在指定的触发时刻执行这个函数。可是,当我们等了5秒钟之后,弹出的对话框显示的this.name却是undefined。

    其实这个问题和上一个示例中的问题是类似的,都是因为临时变量而导致的问题。当我们执行函数的时候,如果函数带有参数,那么这个时候Javascript引擎会创建一个临时变量,并将传入的参数复制(注意,Javascript里面都是值传递的,没有引用传递的概念,这句话好像有问题,在JavaScript中,给函数传递参数是按照上述默认约定——即对不可变类型,传值;对可变类型,传址——进行的。如: 

    function example(str, obj){ 
    …… 

    example(stringValue,objectValue); 

    调用example函数时,第一个参数传递的是实际的字符串值,第二参数传递的是对象的引用(内存地址
    给此临时变量。也就是说,整个过程就跟上面我们定义了一个getCookie的临时变量,再将Utility.getCookie赋值给这个临时变量一样。只不过在这个示例中,容易忽视临时变量导致的bug。

    更多参考:http://www.cnblogs.com/youxin/archive/2012/09/21/2697526.html

    函数对象传参

    对于函数作为参数传递导致的this指针丢失的问题,目前很多框架都已经有方法解决了。

    Prototype的解决方案——传参之前使用bind方法将函数封装起来,并返回封装后的对象

    <script type="text/javascript">
      var person = {
        name:"Kevin Yang",
        sayHi:function(){
          alert("你好,我是"+this.name);
        }
      }
      var boundFunc = person.sayHi.bind(person,person.sayHi);
      setTimeout(boundFunc,5000);
    script>

    上面的bind后面person.sayHi作为参数好像没有意义,写作:

    var boundFunc = person.sayHi.bind(person) 也可以。
    因为bind的第一个参数是this绑定的对象,后面的都是这个运行bound函数时默认的参数,可以省略。
    加person.sayHi没有意义。
    bind与call,apply区别,call,apply是直接调用来的,而bind返回的是一个函数。

    bind方法的实现其实是用到了Javascript又一个高级特性——闭包。我们来看一下源代码:

    function bind(){
      if (arguments.length < 2 && arguments[0] === undefined) 
        return this;
      var __method = this, args = $A(arguments), object = args.shift();
      return function(){
        return __method.apply(object, args.concat($A(arguments)));
      }
    }

    首先将this指针存入函数内部临时变量,然后在返回的函数对象中引用此临时变量从而形成闭包。

    微软的Ajax库提供的方案——构建委托对象

    <script type="text/javascript">
      var person = {
        name:"Kevin Yang",
        sayHi:function(){
          alert("你好,我是"+this.name);
        }
      }  
      var boundFunc = Function.createDelegate(person,person.sayHi);
      setTimeout(boundFunc,5000);
    script>
    其实本质上和prototype的方式是一样的。

    著名的Extjs库的解决方案采用的手法和微软是一样的。extjs2 createDelegate源码:http://www.blogjava.net/zhangchao/archive/2008/04/30/197562.html

    http://msdn.microsoft.com/en-us/library/dd393582.aspx

    转自:http://www.jb51.net/article/19434.htm

  • 相关阅读:
    ffmpeg rtmp推流 视频转码
    java日志发展史 log4j slf4j log4j2 jul jcl 日志和各种桥接包的关系
    nginx stream 流转发,可以转发rtmp、mysql访问流,转发rtmp、jdbc请求
    java web http 转https 通过nginx代理访问
    linux 服务器磁盘挂载
    novnc 通过websockify代理 配置多点访问
    linux 文件服务 minio 安装部署配置
    AOP实现原理,手写aop
    java 泛型
    JAVA反射getGenericSuperclass()用法
  • 原文地址:https://www.cnblogs.com/youxin/p/3354836.html
Copyright © 2011-2022 走看看