js中的this(二)

在上一篇文章 掌握JS中的“this” (一) 里面, 我们学会了如何正确使用JavaScript中的 this 关键字及其基本原理。我们也知道决定 this 指向哪个对象的关键因素, 是找出当前的执行上下文(execution context)。但如果执行上下文不按正常的方式进行设置,问题可能就会变得很棘手。在本文中,我会着重提示在哪些地方会发生这种情况, 以及用什么方式可以弥补。

解决常见问题

在本节中,我们将探讨一些使用 this 关键字时最常见的问题, 并了解如何处理这种情况。

1. 在拆出来(Extracted)的方法中使用 this

最常见的错误,就是将对象的方法(method)赋值给一个变量, 并认为 function 中的 this 仍然指向原来那个对象。从下面的例子中我们可以看到, 完全不是这么回事。

var car = {
  brand: "Nissan",
  getBrand: function(){
    console.log(this.brand);
  }
};

var getCarBrand = car.getBrand;

getCarBrand();   // output: undefined

看起来 getCarBrand 似乎引用的是 car.getBrand(), 但实际上他指向的仅仅是 getBrand() 自身。我们已经知道在确定上下文时最重要的是调用点(call-site), 上面的代码中,调用点是 getCarBrand(), 这只是一个普通的函数调用。

证明 getCarBrand 指向一个普通函数(baseless function, 没有绑定到任何特定对象的函数) 的方法, 是使用alert(getCarBrand); 来查看对应的内容, alert 的输出如下所示:

function(){
  console.log(this.brand);
}

getCarBrand 仅仅是一个普通的函数, 它不再是 car 对象的方法。所以,在这种情况下, this.brand 实际上会被转换为 window.brand。 输出的结果当然就是 undefined

如果我们从对象中拆取出(extract)某个方法, 那么这个方法(method)就会变成了一个普通的函数(function)。他和原来对象的联系被切断了(severed), 所以不再按照原来的方式运行。换句话说, 一个拆取出来的函数就不再绑定到原来的对象上了。

如何处理这种情况呢? OK,如果还想指向原来的那个对象的话,就需要在赋值给 getCarBrand 的时候显式地将getBrand() 函数绑定(bind) 到 car 对象, 当然, 我们可以使用 bind()方法

var getCarBrand = car.getBrand.bind(car);
getCarBrand();   // output: Nissan

现在,我们得到了期望的正确输出,因为我们成功地重新定义想要的上下文。

2.在回调函数中使用 this

第二类问题是将某个(使用了 this 的)方法作为回调函数。例如:

<button id="btn" type="button">查看汽车品牌</button>

var car = {
  brand: "Nissan",
  getBrand: function(){
    console.log(this.brand);
  }
};

var el = document.getElementById("btn");
el.addEventListener("click", car.getBrand);

虽然使用的是 car.getBrand, 但实际上只是将 getBrand() 函数关联到 button 对象。

将参数传递给一个函数是一种隐式的赋值, 所以此处和上一个示例基本上是一样的。区别在于此时car.getBrand 没有显式地赋值。结果是一样的,得到的只是一个普通函数,绑定的对象是 button

换句话说,当我们在某个对象上执行一个方法, 虽然这个方法可能是在其他对象里面定义的, 但此时 this 关键字已经不再指向原来的对象了, 而是指向调用此方法的对象。

比如说在上面的示例中: 我们用的是 el(即 button) 来执行 car.getBrand,而没用 car 对象, 尽管最初是定义在 car 对象里面。因此, this指向的是 el,而不是 car

如果确实需要保持对原来对象的引用, 那么需要显式地将 getBrand() 函数绑定到 car 对象, 可以使用每个 function 都有的 bind() 方法。

el.addEventListener("click", car.getBrand.bind(car));

那么现在, 就可以按照想要的方式运行了。

3. 在闭包(Closure)之中使用 this

在闭包之中使用 this 也可能会引起错误。看下面的示例:

var car = {
  brand: "Nissan",
  getBrand: function(){
    var closure = function(){
      console.log(this.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: undefined

在这里,我们得到的输出是 undefined, 因为闭包函数(即内部函数)不能访问到外部函数的 this 变量。所以最终的结果就是 this.brand 等价于 window.brand, 因为内部函数中的 this 绑定的是全局对象。

针对这种问题, 我们可以把 this 绑定给 getBrand() 函数。

var car = {
  brand: "Nissan",
  getBrand: function(){
    var closure = function(){
      console.log(this.brand);
    }.bind(this);// 注意这里
    return closure();
  }
};

car.getBrand();   // output: Nissan

此处的绑定相当于 car.getBrand.bind(car) 。

另一种处理闭包的常用方法是先将 this 赋值给另一个变量, 来避免不必要的改变。

var car = {
  brand: "Nissan",
  getBrand: function(){
    var self = this;
    var closure = function(){
      console.log(self.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: Nissan

这里, 我们可以把_this 值赋给 _thisthatselfmemycontext 之类的变量, 或者是其他有这一类含义的变量名。关键是保留下外层的对象引用。

ECMAScript 6 中的高科技

在上面的示例中, 我们看到了 “语法形式上的 this” 用法,也就是我们可以把 this 赋值给另一个变量。在 ECMAScript 6 中我们可以用更优雅的新技术, 箭头函数(arrow function) 来实现类似的效果。

Arrow-functions 并不是通过 function 关键字创建的, 而是通过所谓的“胖箭头” 运算符(fat arrow, =>)来实现的。 与一般的函数不一样, 箭头函数从其所处的封闭范围(enclosing scope)中取得 this值 。箭头函数的语法绑定不会被覆盖, 即使使用了 new 操作符。

下面我们来看看箭头函数是如何替代 var self = this; 这种语句的:

var car = {
  brand: "Nissan",
  getBrand: function(){
    // 箭头函数保留了语法上的 "this" 作用域.
    var closure = () => {
      console.log(this.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: Nissan

关于 this 的知识点

和其他机制一样, this 关键字也遵循一些简单的规则, 如果你了解了这些规则,那就可以用得顺畅一些。下面快速回顾这两节中所学的知识点:

  • 在下列情况下 this 指向的是全局对象:

    • 在最外层的代码中, 不在任何 function 里面
    • 不是对象方法(method)的函数(method)里面
    • 不是构造函数(constructor)的函数里面
  • 当函数作为父对象的属性被调用时, this 指向的是父对象(parent object)。
  • 当函数通过 call() 、 apply() 或者 bind()调用时, this指向的是传递给这些方法的第一个参数。如果第一个参数是 null
  • 或者不是一个对象, 那么 this 指向的是全局对象。
  • 在使用 new 操作符来调用一个函数时, this 指向的是新创建的这个对象。
  • 在 ECMAScript 6 中使用箭头函数时, this 根据所处的语法作用域指向上级对象(parent object)。

了解了这些简单直接的规则,我们就可以很容易地看出 this 指向的是哪个对象, 如果指向不正确, 那可以用学过的这些黑科技来搞定。

总结

要掌握 JavaScript 中的 this 是不太容易的, 但如果你用得足够多, 你自然就理解了。希望本系列的博文能帮你打好基础,并加深理解,然后下次你碰到相关问题的时候能够心中有数。

推荐阅读

阮一峰老师的: ECMAScript 6入门

CNBlog : Javascript Context和Scope的一些学习总结

infoq: 深入浅出ES6(1~10)系列

babeljs-ES6: https://babeljs.io/repl/

掌握JS中的“this” (一)

掌握JS中的“this” (二)

相关链接

GitHub版本:https://github.com/cncounter/translation/blob/master/tiemao_2015/20_JavaScript_Mastering_this/JavaScript_Mastering_this.md

原文链接: http://www.sitepoint.com/mastering-javascripts-this-keyword/

作者: 铁锚 http://blog.csdn.net/renfufei

日期: 2015年09月22日

时间: 2024-10-19 11:56:06

js中的this(二)的相关文章

掌握JS中的“this” (二)

在上一篇文章 掌握JS中的"this" (一) 里面, 我们学会了如何正确使用JavaScript中的 this 关键字及其基本原理.我们也知道决定 this 指向哪个对象的关键因素, 是找出当前的执行上下文(execution context).但如果执行上下文不按正常的方式进行设置,问题可能就会变得很棘手.在本文中,我会着重提示在哪些地方会发生这种情况, 以及用什么方式可以弥补. 解决常见问题 在本节中,我们将探讨一些使用 this 关键字时最常见的问题, 并了解如何处理这种情况.

php中向前台js中传送一个二维数组

在php中向前台js中传送一个二维数组,并在前台js接收获取其中值的全过程方法: (1),方法说明:现在后台将数组发送到前台 echo json_encode($result); 然后再在js页面中的ajax一部传送的返货函数中接收,最重要的是接受的时候设置一下接收参数的类型: $.post(              "http:"+$('#url').val()+"/search",              {drug_name:drug_name},    

JS中的函数(二):函数参数(你可能不知道的参数传递)

前言: 函数分为有参有返回值,有参无返回值,无参无返回值,无参有返回值:那么对于无参数的函数你想使用函数的调用怎么办呢?如果你想封装一个代码,实现多种功能,但是形参大于实参或者实参大于形参又该如何?本文就发生函数调用过程中,值传递,参数的不对等处理原则等关于参数的一些相关操作进行阐述. 1.实参数大于形参数: function say(name,message){ console.log('Hello' + name + message); } say('World!','ByeBye!','W

掌握JS中的“this” (一)

译者注: 一般来说,function 翻译为函数, method 翻译为方法. 我们一般说, 某某对象的方法, 而不说 "某某对象的函数".原因是在面向对象中, 一个对象就是一个具体额实例,有自己的方法. 而函数,是和对象没关系的. 另外, scope(作用域) 和 context(上下文) 也是一个容易迷糊的地方.请参考: Javascript Context和Scope的一些学习总结 推荐阅读: 深入浅出ES6(1~10)系列 会用一门语言来写程序,并不代表就能正确地理解和使用该语

JS中的this(一)

scope(作用域) 和 context(上下文) 是一个容易迷糊的地方.请参考: Javascript Context和Scope的一些学习总结 推荐阅读: 深入浅出ES6(1~10)系列 会用一门语言来写程序,并不代表就能正确地理解和使用该语言.当然, JavaScript 也是如此.尽管JS(JavaScript 的简称)是一门很容易上手的语言, 但里面其实有许多细节, 初学者可能并不了解, 即使是经验丰富的JS高手也偶尔会掉到坑里去. 很多有经验的程序员对 this 在 JS 内部是如何

在Vue项目中动态生成二维码 阿星小栈

一.使用jquery.qrcode生成二维码 1.首先在页面中加入jquery库文件和qrcode插件 <script type="text/javascript" src="jquery.js"></script> <script type="text/javascript" src="jquery.qrcode.min.js"></script> 2.在页面中需要显示二维码的

从零开始的JS生活(二)——BOM、DOM与JS中的事件

上回书说道,JS中变量.运算符.分支结构.循环和嵌套循环等内容.本回就由本K给大伙唠唠JS中的BOM.DOM和事件. 一."花心大萝卜"--BOM 1.震惊,FFF团为何对BOM举起了火把--BOM简介 BOM(Browser Object Model) 是指浏览器对象模型,在JS中BOM是个不折不扣的花心大萝卜,因为它有很多个对象,其中代表浏览器窗口的Window对象是BOM的"正室".也就是最重要的,其他对象都是正室的下手.或者叫侧室也不足为过. 2.细数BOM

用Netty和Raphael来写塔防online游戏(二) - JS中使用protobuf协议

一. 简单介绍一下protobuf: Protocol Buffers are a language-neutral, platform-neutral, extensible way of serializing structured data for use in communications protocols, data storage, and more, originally designed at Google . 如今,已经有人用JS实现了protobuf协议,就是ProtoBu

Cookie和Session在Node.JS中的实践(二)

Cookie和Session在Node.JS中的实践(二) cookie篇在作者的上一篇文章Cookie和Session在Node.JS中的实践(一)已经是写得算是比较详细了,有兴趣可以翻看,这篇是session篇,重点在讨论seesion的特性.概念,以及session和cookie的区别和联系. 之前有人问我,这个问题有必要长篇大论的探讨吗?其实只要我觉得这个技术点重要而且具有迷惑性,就值得去写.去探讨,否则你很难完全明白.写这种文章有两个好处: 首先,于作者而言,写出来文章让大家一起理解某