JavaScript中this详解
都说 JavaScript 是⼀种很灵活的语⾔,这其实也可以说它是⼀个混乱的语⾔。它把函数式编程和⾯向对象编程糅合⼀起,再加上动态语⾔特性,简直强⼤⽆⽐(其实是不能和C++⽐的,^_^ )。
这⾥的主题是 this ,不扯远了。this 本⾝原本很简单,总是指向类的当前实例,this 不能赋值。这前提是说 this 不能脱离类/对象来说,也就是说 this 是⾯向对象语⾔⾥常见的⼀个关键字。说的极端点,如果你编写的 JS 采⽤函数式写法,⽽不是⾯向对象式,你所有的代码⾥ this 会少很多,甚⾄没有。记住这⼀点,当你使⽤ this 时,你应该是在使⽤对象/类⽅式开发,否则this 只是函数调⽤时的副作⽤。
JS ⾥的 this
在 function 内部被创建
指向调⽤时所在函数所绑定的对象(拗⼝)
this 不能被赋值,但可以被 call/apply  改变
以前⽤ this 时经常担⼼,不踏实,你不知道它到底指向谁?这⾥把它所有⽤到的地⽅列出
this 和构造器
this 和对象
this 和函数
全局环境的 this
this 和 DOM/事件
this 可以被 call/apply 改变
ES5 中新增的 bind 和 this
ES6 箭头函数(arrow function) 和 this
1. this 和构造器
this 本⾝就是类定义时构造器⾥需要⽤到的,和构造器在⼀起再⾃然不过。
/**
* 页签
*
* @class Tab
* @param nav {string} 页签标题的class
* @param content {string} 页⾯内容的class
*
*/
function Tab(nav, content) {
this.nav = nav
}
Nav = function() {
return this.nav;
};
Tab.prototype.setNav = function(nav) {
this.nav = nav;
};
Tab.prototype.add = function() {
};
按照 JavaScript 的习惯, this 应该挂属性/字段,⽅法都应该放在原型上。
2. this 和对象
JS 中的对象不⽤类也可以创建,有⼈可能奇怪,类是对象的模板,对象都是从模板⾥ copy 出来的,没有类怎么创建对象?JS 的确可以,并且你完全可以写上万⾏功能代码⽽不⽤写⼀个类。话说 OOP ⾥说的是⾯向对象编程,也没说⾯向类编程,是吧 ^_^ 。
var tab = {
nav: '',
content: '',
getNav: function() {
return this.nav;
},
setNav: function(n) {
this.nav = n;
}
}
3. this 和函数
⾸先,this 和独⽴的函数放在⼀起是没有意义的,前⾯也提到过 this 应该是和⾯向对象相关的。纯粹的函数只是⼀个低级别的抽象,封装和复⽤。如下
function showMsg() {
ssage)
}
showMsg() // undefined
定义 showMsg,然后以函数⽅式调⽤,ssage 是 undefined。因此坚决杜绝在纯函数内使⽤ this,但有时候会这么写,调⽤⽅式使⽤ call/apply
function showMsg() {
ssage)
}
var m1 = {
message: '输⼊的电话号码不正确'
}
var m2 = {
message: '输⼊的⾝份证号不正确'
}
showMsg.call(m1) // '输⼊的电话号码不正确'
showMsg.call(m2) // '输⼊的⾝份证号不正确'
⽤这种⽅式可以节省⼀些代码量,⽐如当两个类/对象有⼀共相似的⽅法时,不必写两份,只要定义⼀个,然后将其绑定在各⾃的原型和对象上。这时候其实你还是在使⽤对象或类(⽅式1/2),只是间接使⽤罢了。
4. 全局环境的 this
jquery是什么有什么作用前⾯提到 this 是 “指向调⽤时所在函数所绑定的对象”,这句话拗⼝但绝对正确,没有⼀个多余的字。
全局环境中有不同的宿主对象,浏览器环境中是 window, node 环境中是 global。这⾥重点说下浏览器环境中的 this。
浏览器环境中⾮函数内 this 指向 window
alert(window=== this) // true
因此你会看很很多开源 JS lib 这么写
(function() {
// ...
})(this);
或这样写
(function() {
// ...
}).call(this);
⽐如 underscore 和 requirejs,⼤意是把全局变量 window 传⼊匿名函数内缓存起来,避免直接访问。⾄于为啥要缓存,这跟JS 作⽤域链有关系,读取越外层的标识符性能会越差。请⾃⾏查阅相关知识,再说就扯远了。
浏览器中⽐较坑⼈,⾮函数内直接使⽤ var 声明的变量默认为全局变量,且默认挂在 window 上作为属性。
var andy = '刘德华'
alert(andy === window.andy) // true
alert(andy === this.andy) // true
alert(window.andy === this.andy) // true
因为这个特性,有些笔试题如
var x = 10;
function func() {
alert(this.x)
}
var obj = {
x: 20,
fn: function() {
alert(this.x)
}
}
var fn = obj.fn
func() // 10
fn() // 10
没错,最终输出的都是全局的 10。永远记住这⼀点:判断 this 指向谁,看执⾏时⽽⾮定义时,只要函数(function)没有绑定在对象上调⽤,它的 this 就是 window。
5. this 和 DOM/事件
W3C 把 DOM 实现成了各种节点,节点嵌套⼀起形成 DOM tree。节点有不同类型,如⽂本节点,元素节点等10多种。元素节点⼜分成了很多,对写HTML的⼈来说便是很熟悉的标签(Tag),如 div,ul,label 等。看 W3C 的 API ⽂档,会发现它完全是按照⾯向对象⽅式实现的各种 API,有 interface,extends 等。如
看到了吧,这是⽤ Java 写的,既然是⽤⾯向对象⽅式实现的API,⼀定有类/对象(废话^_^),有类/对象,则⼀定有 this (别忘了这篇⽂章的中⼼主题)。所有的 HTML tag 类命名如 HTMLXXXElement,如
HTMLDivElement
HTMLLabelElement
HTMLInputElement
...
前⾯说过 this 是指向当前类的实例对象,对于这些 tag 类来说,不看其源码也知它们的很多⽅法内部⽤到的 this 是指向⾃⼰的。有了这个结论,写HTML和JS时, this 就清晰了很多。
⽰例A
<!-- this 指向 div -->
<div onclick="alert(this)"></div>
⽰例B
<div id="nav"></div>
<script>
alert(this) // 指向div#nav
}
</script>
⽰例C
$('#nav').on('click', function() {
alert(this) // 指向 nav
})
以上三个⽰例可以看到,在给元素节点添加事件的时候,其响应函数(handler)执⾏时的 this 都指向 Element 节点⾃⾝。jQuery 也保持了和标准⼀致,但却让⼈迷惑,按 “this 指向调⽤时所在函数所绑定的对象” 这个定义,jQuery 事件 handler ⾥的 this,应该指向 jQuery 对象,⽽⾮ DOM 节点。因此你会发现在⽤ jQuery 时,经常需要把事件 handler ⾥的 element 在⽤$ 包裹下变成 jQuery 对象后再去操作。⽐如
$('#nav').on('click', function() {
var $el = $(this) // 再次转为 jQuery 对象,如果 this 直接为 jQuery 对象更好
$el.attr('data-x', x)
$el.attr('data-x', x)
})
有⼈可能有如下的疑问
<div id="nav" onclick="getId()">ddd</div>
<script>
function getId() {
alert(this.id)
}
</script>
点击 div 后,为什么 id 是 undefined,不说是指向的当前元素 div 吗?如果记住了前⾯提到的⼀句话,就很清楚为啥是undefined,把这句话再贴出来。
判断 this 指向谁,看执⾏时⽽⾮定义时,只要函数(function)没有绑定在对象上调⽤,它的 this 就是 window
这⾥函数 getId 调⽤时没有绑定在任何对象上,可以理解成这种结构
getId()
}
getId 所处匿名函数⾥的 this 是 div,但 getId ⾃⾝内的 this 则不是了。当然 ES5 严格模式下还是有个坑。
6. this 可以被 call/apply 改变
call/apply 是函数调⽤的另外两种⽅式,两者的第⼀个参数都可以改变函数的上下⽂ this。call/apply 是 JS ⾥动态语⾔特性的表征。动态语⾔通俗的定义
程序在运⾏时可以改变其结构,新的函数可以被引进,已有的函数可以被删除,即程序在运⾏时可以发⽣结构上的变化
通常有以下⼏点特征表⽰它为动态语⾔
动态的数据类型
动态的函数执⾏
动态的⽅法重写
动态语⾔多从世界第⼆门语⾔ LISP 发展⽽来,如死去的 SmallTalk/VB,⽬前还活着的 Perl/Python,以及还流⾏的
Ruby/JavaScript。JS ⾥动态数据类型的体现便是弱类型,执⾏的时候才去分析标识符的类型。函数动态执⾏体现为
eval,call/aply。⽅法重写则体现在原型重写。不扯远,这⾥重点说下 call/apply 对 this 的影响。
var m1 = {
message: 'This is A'
}
var m2 = {
message: 'This is B'
}
function showMsg() {
ssage)
}
showMsg() // undefined
showMsg.call(m1) // 'This is A'
showMsg.call(m2) // 'This is B'
可以看到单独调⽤ showMsg 返回的是 undefined,只有将它绑定到具有 message 属性的对象上执⾏时才有意义。发挥想象⼒延伸下,如果把⼀些通⽤函数写好,可以任意绑定在多个类的原型上,这样动态的给类添加了⼀些⽅法,还节省了代码。这是⼀种强⼤的功能,也是动态语⾔的强表现⼒的体现。
经常会听到转向 Ruby 或 Python 的⼈提到“编程的乐趣”,这种乐趣是源⾃动态语⾔更接近⼈的思维(⽽不是机器思维),更符合业务流程⽽不是项⽬实现流程。同样⼀个功能,动态语⾔可以⽤更⼩的代码量来实现。动态语⾔对程序员⽣产⼒的提⾼,是其⼤⾏其道的主要原因。
性能⽅⾯,动态语⾔没有太⼤的优势,但动态语⾔的理念是:优化⼈的时间⽽不是机器的时间。提⾼开发者的⽣产⼒,宁肯牺牲部分的程序性能或者购买更⾼配置的硬件。随着IT业的不断发展和摩尔定律的作⽤,硬件相对于⼈件⼀直在贬值,这个理念便有了合理的现实基础。
JS ⾥的 call/apply 在任何⼀个流⾏的 lib ⾥都会⽤到,但⼏乎就是两个作⽤
配合写类⼯具实现OOP,如 mootools, ClassJS, class.js,
修复DOM事件⾥的 this,如 jQuery, events.js
关于 call 和 apply 复⽤:利⽤apply和arguments复⽤⽅法
关于 call 和 apply 的性能问题参考:冗余换性能-从Backbone的triggerEvents说开了去
7. ES5 中新增的 bind 和 this
上⾯ 6 ⾥提到 call/apply 在 JS ⾥体现动态语⾔特性及动态语⾔的流⾏原因,其在 JS ⽤途如此⼴泛。ES5发布时将其采纳,提了⼀个更⾼级的⽅法 bind。
var modal = {
message: 'This is A'
}
function showMsg() {
ssage)
}
var otherShowMsg = showMsg.bind(modal)
otherShowMsg() // 'This is A'
因为是ES5才加的,低版本的IE不⽀持,可以修复下Function.prototype。bind 只是 call/apply 的⾼级版,其它没什么特殊的。
8. ES6 箭头函数(arrow function) 和 this
ES6 在今年的 6⽉18⽇正式发布(恰京东店庆⽇同⼀天,^_^),它带来的另⼀种类型的函数 - 箭头函数。箭头函数的⼀个重要特征就是颠覆了上⾯的⼀句话,再贴⼀次
判断 this 指向谁,看执⾏时⽽⾮定义时,只要函数(function)没有绑定在对象上调⽤,它的 this 就是 window
是的,前⾯⼀直⽤这句话来判断 this 的指向,在箭头函数⾥前⾯半句就失效了。箭头函数的特征就是,定义在哪,this 就指向那。即箭头函数定义在⼀个对象⾥,那箭头函数⾥的 this 就指向该对象。如下
var book = {
author: 'John Resig',
init: function() {
alert(this.author) ; // 这⾥的 this 不是 document 了
}
}
};
book.init()
对象 book ⾥有⼀个属性 author,有⼀个 init ⽅法,给 document 添加了⼀个点击事件,如果是传统的函数,我们知道 this 指向应该是 document,但箭头函数会指向当前对象 book。
箭头函数让 JS 回归⾃然和简单,函数定义在哪它 this 就指向哪,定义在对象⾥它指向该对象,定义在类的原型上,指向该类的实例,这样更容易理解。
总结:
函数的上下⽂ this 是 JS ⾥不太好理解的,在于 JS 函数⾃⾝有多种⽤途。⽬的是实现各种语⾔范型(⾯向对象,函数式,动态)。this 本质是和⾯向对象联系的,和写类,对象关联⼀起的,和“函数式”没有关系的。如果你采⽤过程式函数式开发,完全不会⽤到⼀个 this。但在浏览器端开发时却⽆可避免的会⽤到 this,这是因为浏览器对象模型(DOM)本⾝采⽤⾯向对象⽅式开发,Tag 实现为⼀个
个的类,类的⽅法⾃然会引⽤类的其它⽅法,引⽤⽅式必然是⽤ this。当你给DOM对象添加事件时,回调函数⾥引⽤该对象就只能⽤ this 了。
明⽩了么?
相信看完全⽂以后,this不再是坑~

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。