JS中的this(一)
2016-06-02 20:05
573 查看
会用一门语言来写程序,并不代表就能正确地理解和使用该语言。当然, JavaScript 也是如此。尽管JS(JavaScript 的简称)是一门很容易上手的语言, 但里面其实有许多细节, 初学者可能并不了解, 即使是经验丰富的JS高手也偶尔会掉到坑里去。
很多有经验的程序员对
this在 JS 内部是如何运作的也是一头雾水。通俗点讲,
this只是一个引用别名(referencing alias) - 这个别名只知道当前指向的那个对象, 而这也是最棘手的地方。
本文为你理清思路,并介绍
this关键字的内部运作原理。
那么, this
是个什么鬼?
简而言之, this是一个特殊的标识符关键字 —— 在每个 function 中自动根据作用域(scope) 确定, 指向的是此次调用的 “所有者,owner”。但要完全搞定这个问题, 我们需要回答两个关键问题:
this
是如何创建的?
每调用一次 JavaScript 函数时,都会创建一个新的对象, 其中的信息包括: 传入了哪些参数, 函数是如何调用(invoked)的, 函数是在哪里被调用(called)的,等等。该对象中还有一个重要的属性是 this引用, 函数是哪个对象的方法,
this就会自动绑定到该对象。
var car = { brand: "Nissan", getBrand: function(){ console.log(this.brand); } }; car.getBrand(); // output: Nissan
在这个例子中, 使用的是
this.brand, 这是
car对象的引用。所以此时,
this.brand等价于
car.brand。
this
指向的是谁
在所有 function 中, this的值都是根据上下文(context, 函数在调用时刻所处的环境)来决定的。
this的作用域(scope) 与函数定义的位置没有关系, 而是取决于函数在哪里被调用( where they are called from ;i.e. the context)。
每一行JavaScript代码都是在执行上下文(execution context)中运行的。
this指向的对象在每次进入新的执行上下文后是固定的, 直到跳转(shifted)到另一个不同的上下文才发生改变。决定执行上下文(以及
this的绑定)需要我们去找出调用点(call-site), 调用点即函数在代码中调用的位置。
让我们看下面的示例:
var brand = 'Nissan'; var myCar = {brand: 'Honda'}; var getBrand = function() { console.log(this.brand); }; myCar.getBrand = getBrand; myCar.getBrand(); // output: Honda getBrand(); // output: Nissan
虽然
myCar.getBrand()和
getBrand()指向的是同一个函数, 但其中的
this是不同的,因为它取决于
getBrand()在哪个上下文中调用。
我们已经知道, function 是哪个对象的方法, 在函数中,
this就绑定到这个对象。
上面的代码里,第一次函数调用对应的是
myCar对象, 而第二次对应的是
window【 此时
getBrand()等价于
window.getBrand()】。因此,不同的上下文产生的是不同的结果。
调用上下文
下面,让我们看看不同的上下文中this的指向。
全局作用域(Global Scope)
所有的JavaScript 运行时都有唯一的全局对象(global object)。在浏览器中,全局对象是window。而在Node.js里面是
global。
在全局执行上下文中(任何函数之外的代码),
this指向的是全局对象, 不管是否在严格模式下(
strict mode)。
局部作用域(Local Scope)
在函数中,this取决于函数是怎么调用的。分三种情况:
1. 在简单函数调用(Simple Function Call)中使用this
第一种情形是直接调用一个独立的函数。function simpleCall(){ console.log(this); } simpleCall(); // output: the Window object
在这种情况下,
this值没有被 call 设置。因为代码不是运行在严格模式下,
this又必须是一个对象, 所以他的值默认为全局对象。
如果是在严格模式(strict mode)下, 进入执行上下文时设置为什么值那就是什么值。如果没有指定, 那么就一直是
undefined, 如下所示:
function simpleCall(){ "use strict"; console.log(this); } simpleCall(); // output: undefined
2. 在对象的方法(Object’s Method)中使用 this
将函数保存为对象的属性, 这样就转化为一个方法, 可以通过对象调用这个方法。当函数被当成对象的方法来调用时, 里面的 this值就被设置为调用方法的对象。
var message = { content: "I'm a JavaScript Ninja!", showContent: function() { console.log(this.content); } }; message.showContent(); // output: I'm a JavaScript Ninja!
showContent()是
message对象的一个方法, 所以此时
this.content等价于
message.content。
3. 在构造函数(Constructor Function)中使用 this
我们可以通过 new操作符来调用函数。在这种情况下,这个函数就变成了构造函数(也就是一个对象工厂)。与前面讨论的简单函数调用和方法调用不同, 构造函数调用会传入一个全新的对象作为
this的值, 并且隐式地返回新构造的这个对象作为结果【简言之, 新构造对象的内存是 new 操作符分配的, 构造函数只是做了一些初始化工作】。
当一个函数作为构造器使用时(通过
new关键字), 它的
this值绑定到新创建的那个对象。如果没使用
new关键字, 那么他就只是一个普通的函数,
this将指向
window对象。
function Message(content){ this.content = content; this.showContent = function(){ console.log(this.content); }; } var message = new Message("I'm JavaScript Ninja!"); message.showContent(); // output: I'm JavaScript Ninja!
在上面的示例中, 有一个名为
Message()的构造函数。通过使用
new操作符创建了一个全新的对象,名为
message。同时还通传给构造函数一个字符串, 作为新对象的
content属性。通过最后一行代码中可以看到这个字符串成功地打印出来了, 因为
this指向的是新创建的对象, 而不是构造函数本身。
如何正确地使用 this
在本节中,我们将学习一些决定 this行为的内部机制。
在JavaScript中,所有的函数都是对象, 因此函数也可以有自己的方法。所有的函数都有的两个方法, 是 apply()和 call(). 。我们可以通过这两个方法来改变函数的上下文, 在任何时候都有效, 用来显式地设置
this的值。
apply()方法接收两个参数: 第一个是要设置为
this的那个对象, 第二个参数是可选的,如果要传入参数, 则封装为数组作为
apply()的第二个参数即可。
call()方法 和
apply()基本上是一样的, 除了后面的参数不是数组, 而是分散开一个一个地附加在后面。
下面来演练一下:
function warrior(speed, strength){ console.log( "Warrior: " + this.kind + ", weapon: " + this.weapon + ", speed: " + speed + ", strength: " + strength ); } var warrior1 = { kind: "ninja", weapon: "shuriken" }; var warrior2 = { kind: "samurai", weapon: "katana" }; warrior.call(warrior1, 9, 5); // output: Warrior: ninja, weapon: shuriken, speed: 9, strength: 5 warrior.apply(warrior2, [6, 10]); // output: Warrior: samurai, weapon: katana, speed: 6, strength: 10
在这里,我们有一个工厂函数
warrior(), 通过传入不同的战士对象创建不同类型的warrior(战士)。那么,在工厂函数里面,
this将指向我们通过
call()和/或
apply()传入的对象。
在第一个函数调用中,我们使用
call()方法来将
this设置为
warrior1对象, 并传入需要的其他参数, 参数间用逗号分隔。在第二个函数调用中, 其实都差不多, 只是传入的是
warrior2对象, 并将必要参数封装为一个数组。
除了
call()和
apply()以外, ECMAScript 5还增加了 bind() 方法, 在调用一个函数或方法时也可以通过
bind方法来绑定
this对象。让我们看下面的例子:
function warrior(kind){ console.log( "Warrior: " + kind + ". Favorite weapon: " + this.weapon + ". Main mission: " + this.mission ); } var attributes = { weapon: "shuriken", mission: "espionage" }; var ninja = warrior.bind(attributes, "ninja"); ninja(); // output: Warrior: ninja. Favorite weapon: shuriken. Main mission: espionage
在这个例子中,
bind()方法的使用方式还是类似的, 但
warrior.bind()创建了一个新的函数(方法体和作用域跟
warrior()一样),并没有改动原来的
warrior()函数。新函数的功能和老的一样, 只是绑定到了
attributes对象。
【
bind方法和
call,
apply的区别在于,
bind()之后函数并没有执行, 可以传给其他函数, 在某个适当的时机再调用 】
总结
So, 与this有关的知识差不多就是这些。作为初学者,掌握这些你就足够来正确地使用它了, 对自己多点信心! 当然, 在使用的过程中也可能有一些问题会困扰你, 那么, 欢迎阅读下一篇文章 掌握JS中的“
this” (二)。
推荐阅读
阮一峰老师的: ECMAScript 6入门CNBlog : Javascript Context和Scope的一些学习总结
infoq: 深入浅出ES6(1~10)系列
babeljs-ES6: https://babeljs.io/repl/
掌握JS中的“
this” (一)
掌握JS中的“
this” (二)
相关链接
原文链接: Revealing the Inner Workings of JavaScript’s “this” Keyword原文日期: 2015年05月01日
翻译日期: 2015年09月17日
作者: 铁锚 http://blog.csdn.net/renfufei
相关文章推荐
- 在这个看脸的世界,该如何优雅的创建JS对象
- JavaScript 引擎指南1
- javascript:算法之for循环
- JavaScript进阶练习
- POST请求给后台传JSON
- avalon.js 1.4.6简单列表数据绑定ms-repeat ms-click
- 通过日期获取年龄 extjs
- 跟我学《JavaScript高程3》第一讲,视频教程,课程笔记
- 跟我学《JavaScript高程3》视频教程,下载地址
- js、oc、html交互
- Javascript中的ajax
- js的构造函数
- JSP页面乱码全解析
- JSPatch 部署安全策略
- Scrapy集成selenium+PhantomJS+代理IP 解析js动态内容
- Extjs combobox 实现搜索框的效果
- JavaScript到底是不是单线程
- 【JSON】数据格式
- javascript动画框架实现宽高透明度变化
- Extjs 将表格中的行上下移动,把一个表格中的数据移动到另一个表格中