标签:
当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程
、事件驱动
、面向对象
等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清楚。有句话这么说:如果你不能向一个6岁小孩解释清楚一个东西,那么你自己也不懂这个东西。这句话或许有点夸张,但是极其有道理。个人觉得,如果需要掌握一门语言,掌握它的API只是学了皮毛,理解这门语言的精髓才是重点。提及JavaScript的精髓,this
、闭包
、作用域链
、函数
是当之无愧的。这门语言正式因为这几个东西而变得魅力无穷。
博客的标题是《JavaScript中的this陷阱的最全收集--没有之一》,很显然这篇博客阐述的是this。相信做过JavaScript开发的人都遇到过不少this的陷阱,我自己本身也遇到过不少坑,但是如果非要给出一个系统的总结的话,还没有足够的底蕴。非常幸运的是,今天早上起来看《Hacker News》的时候,恰巧看到了一篇有关于JavaScript this的解析:all this。于是,本着学习和共享的精神,决定将它翻译成中文。翻译的目的绝对不是为了当大自然的搬运工,在这个过程中会完全弄明白别人的著作,加深认识,同时将好东西分享给别人,才能让更多的学习者站在巨人的肩膀上前进。按照我自己的习惯,会翻译的过程中加上一些自己解释(引用部分),毕竟中西方人的思考方式是有差异的。当然文章标题所述的最全也不是吹的,文章非常长。
JavaScript来自一门健全的语言,所以你可能觉得JavaScript中的this和其他面向对象的语言如java的this一样,是指存储在实例属性中的值。事实并非如此,在JavaScript中,最好把this当成哈利波特中的博格特的背包,有着深不可测的魔力。
下面的部分是我希望我的同事在使用JavaScript的this的时候应当知道的。内容很多,是我学习好几年总结出来的。
JavaScript中很多时候会用到this,下面详细介绍每一种情况。在这里我想首先介绍一下宿主环境这个概念。一门语言在运行的时候,需要一个
环境
,叫做宿主环境
。对于JavaScript,宿主环境最常见的是web浏览器
,浏览器提供了一个JavaScript运行的环境,这个环境里面,需要提供一些接口
,好让JavaScript引擎
能够和宿主环境
对接。JavaScript引擎才是真正执行JavaScript代码的地方,常见的引擎有V8
(目前最快JavaScript引擎、Google生产)、JavaScript core
。JavaScript引擎主要做了下面几件事情:
- 一套与宿主环境相联系的规则;
- JavaScript引擎内核(基本语法规范、逻辑、命令和算法);
- 一组内置对象和API;
- 其他约定。
但是环境不是唯一的,也就是JavaScript不仅仅能够在浏览器里面跑,也能在其他提供了宿主环境的程序里面跑,最常见的就是nodejs。同样作为一个宿主环境,
nodejs
也有自己的JavaScript引擎--V8。根据官方的定义:
Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications
1 <script type="text/javascript">
2 console.log(this === window); //true
3 </script>
1 <script type="text/javascript">
2 var foo = "bar";
3 console.log(this.foo); //logs "bar"
4 console.log(window.foo); //logs "bar"
5 </script>
1 <script type="text/javascript">
2 foo = "bar";
3
4 function testThis() {
5 foo = "foo";
6 }
7
8 console.log(this.foo); //logs "bar"
9 testThis();
10 console.log(this.foo); //logs "foo"
11 </script>
> this
{ ArrayBuffer: [Function: ArrayBuffer],
Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 },
Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 },
...
> global === this
true
1 test.js脚本内容:
2
3 console.log(this);
4 console.log(this === global);
5
6 REPL运行脚本:
7
8 $ node test.js
9 {}
10 false
1 test.js:
2
3 var foo = "bar";
4 console.log(this.foo);
5
6 $ node test.js
7 undefined
1 > var foo = "bar";
2 > this.foo
3 bar
4 > global.foo
5 bar
1 test.js
2
3 foo = "bar";
4 console.log(this.foo);
5 console.log(global.foo);
6
7 $ node test.js
8 undefined
9 bar
上面的八种情况可能大家已经绕晕了,总结起来就是:在
浏览器
里面this是老大,它等价于window对象,如果你声明一些全局变量(不管在任何地方),这些变量都会作为this的属性。在node里面,有两种
执行JavaScript代码的方式,一种是直接执行写好的JavaScript文件
,另外一种是直接在里面执行一行行代码
。对于直接运行一行行JavaScript代码的方式,global才是老大,this和它是等价的。在这种情况下,和浏览器比较相似,也就是声明一些全局变量会自动添加给老大global,顺带也会添加给this。但是在node里面直接脚本文件就不一样了,你声明的全局变量不会自动添加到this,但是会添加到global对象。所以相同点是,在全局范围内,全局变量终究是属于老大的。
1 <script type="text/javascript">
2 foo = "bar";
3
4 function testThis() {
5 this.foo = "foo";
6 }
7
8 console.log(this.foo); //logs "bar"
9 testThis();
10 console.log(this.foo); //logs "foo"
11 </script>
test.js
foo = "bar";
function testThis () {
this.foo = "foo";
}
console.log(global.foo);
testThis();
console.log(global.foo);
$ node test.js
bar
foo
1 <script type="text/javascript">
2 foo = "bar";
3
4 function testThis() {
5 "use strict";
6 this.foo = "foo";
7 }
8
9 console.log(this.foo); //logs "bar"
10 testThis(); //Uncaught TypeError: Cannot set property ‘foo‘ of undefined
11 </script>
1 <script type="text/javascript">
2 foo = "bar";
3
4 function testThis() {
5 this.foo = "foo";
6 }
7
8 console.log(this.foo); //logs "bar"
9 new testThis();
10 console.log(this.foo); //logs "bar"
11
12 console.log(new testThis().foo); //logs "foo"
13 </script>
我更喜欢把新的值称作一个实例。
函数里面的this其实相对比较好理解,如果我们在一个函数里面使用this,需要注意的就是我们调用函数的方式,如果是正常的方式调用函数,this指代全局的this,如果我们加一个new,这个函数就变成了一个构造函数,我们就创建了一个实例,this指代这个实例,这个和其他面向对象的语言很像。另外,写JavaScript很常做的一件事就是绑定事件处理程序,也就是诸如button.addEventListener(‘click’, fn, false)之类的,如果在fn里面需要使用this,this指代事件处理程序对应的对象,也就是button。
1 function Thing() {
2 console.log(this.foo);
3 }
4
5 Thing.prototype.foo = "bar";
6
7 var thing = new Thing(); //logs "bar"
8 console.log(thing.foo); //logs "bar"
复制代码
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 console.log(this.foo);
6 }
7 Thing.prototype.setFoo = function (newFoo) {
8 this.foo = newFoo;
9 }
10
11 var thing1 = new Thing();
12 var thing2 = new Thing();
13
14 thing1.logFoo(); //logs "bar"
15 thing2.logFoo(); //logs "bar"
16
17 thing1.setFoo("foo");
18 thing1.logFoo(); //logs "foo";
19 thing2.logFoo(); //logs "bar";
20
21 thing2.foo = "foobar";
22 thing1.logFoo(); //logs "foo";
23 thing2.logFoo(); //logs "foobar";
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 console.log(this.foo);
6 }
7 Thing.prototype.setFoo = function (newFoo) {
8 this.foo = newFoo;
9 }
10 Thing.prototype.deleteFoo = function () {
11 delete this.foo;
12 }
13 var thing = new Thing();
14 thing.setFoo("foo");
15 thing.logFoo(); //logs "foo";
16 thing.deleteFoo();
17 thing.logFoo(); //logs "bar";
18 thing.foo = "foobar";
19 thing.logFoo(); //logs "foobar";
20 delete thing.foo;
21 thing.logFoo(); //logs "bar";
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 console.log(this.foo, Thing.prototype.foo);
6 }
7
8 var thing = new Thing();
9 thing.foo = "foo";
10 thing.logFoo(); //logs "foo bar";
1 function Thing() {
2 }
3 Thing.prototype.things = [];
4
5
6 var thing1 = new Thing();
7 var thing2 = new Thing();
8 thing1.things.push("foo");
9 console.log(thing2.things); //logs ["foo"]
1 function Thing() {
2 this.things = [];
3 }
4
5
6 var thing1 = new Thing();
7 var thing2 = new Thing();
8 thing1.things.push("foo");
9 console.log(thing1.things); //logs ["foo"]
10 console.log(thing2.things); //logs []
1 function Thing1() {
2 }
3 Thing1.prototype.foo = "bar";
4
5 function Thing2() {
6 }
7 Thing2.prototype = new Thing1();
8
9
10 var thing = new Thing2();
11 console.log(thing.foo); //logs "bar"
1 function Thing1() {
2 }
3 Thing1.prototype.foo = "bar";
4
5 function Thing2() {
6 this.foo = "foo";
7 }
8 Thing2.prototype = new Thing1();
9
10 function Thing3() {
11 }
12 Thing3.prototype = new Thing2();
13
14
15 var thing = new Thing3();
16 console.log(thing.foo); //logs "foo"
1 function Thing1() {
2 }
3 Thing1.prototype.foo = "bar";
4 Thing1.prototype.logFoo = function () {
5 console.log(this.foo);
6 }
7
8 function Thing2() {
9 this.foo = "foo";
10 }
11 Thing2.prototype = new Thing1();
12
13
14 var thing = new Thing2();
15 thing.logFoo(); //logs "foo";
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 var info = "attempting to log this.foo:";
6 function doIt() {
7 console.log(info, this.foo);
8 }
9 doIt();
10 }
11
12
13 var thing = new Thing();
14 thing.logFoo(); //logs "attempting to log this.foo: undefined"
在doIt里面的this是global对象或者在严格模式下面是undefined。这是造成很多不熟悉JavaScript的人深陷 this陷阱的根源。在这种情况下事情变得非常糟糕,就像你把一个实例的方法当作一个值,把这个值当作函数参数传递给另外一个函数但是却不把这个实例传递给这个函数一样。在这种情况下,一个方法里面的环境变成了全局范围,或者在严格模式下面的undefined。
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 console.log(this.foo);
6 }
7
8 function doIt(method) {
9 method();
10 }
11
12
13 var thing = new Thing();
14 thing.logFoo(); //logs "bar"
15 doIt(thing.logFoo); //logs undefined
博主非常喜欢用这种方式
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 var self = this;
6 var info = "attempting to log this.foo:";
7 function doIt() {
8 console.log(info, self.foo);
9 }
10 doIt();
11 }
12
13
14 var thing = new Thing();
15 thing.logFoo(); //logs "attempting to log this.foo: bar"
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 var self = this;
6 function doIt() {
7 console.log(self.foo);
8 }
9 doIt();
10 }
11
12 function doItIndirectly(method) {
13 method();
14 }
15
16
17 var thing = new Thing();
18 thing.logFoo(); //logs "bar"
19 doItIndirectly(thing.logFoo); //logs undefined
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 console.log(this.foo);
6 }
7
8 function doIt(method) {
9 method();
10 }
11
12
13 var thing = new Thing();
14 doIt(thing.logFoo.bind(thing)); //logs bar
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 function doIt() {
6 console.log(this.foo);
7 }
8 doIt.apply(this);
9 }
10
11 function doItIndirectly(method) {
12 method();
13 }
14
15
16 var thing = new Thing();
17 doItIndirectly(thing.logFoo.bind(thing)); //logs bar
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4
5
6 function logFoo(aStr) {
7 console.log(aStr, this.foo);
8 }
9
10
11 var thing = new Thing();
12 logFoo.bind(thing)("using bind"); //logs "using bind bar"
13 logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
14 logFoo.call(thing, "using call"); //logs "using call bar"
15 logFoo("using nothing"); //logs "using nothing undefined"
1 function Thing() {
2 return {};
3 }
4 Thing.prototype.foo = "bar";
5
6
7 Thing.prototype.logFoo = function () {
8 console.log(this.foo);
9 }
10
11
12 var thing = new Thing();
13 thing.logFoo(); //Uncaught TypeError: undefined is not a function
奇怪的是,如果你在构造函数里面返回了一个原始值,上面所述的情况并不会发生并且返回语句被忽略了。最好不要在你将通过new调用的构造函数里面返回任何类型的数据,即便你知道自己正在做什么。如果你想创建一个工厂模式,通过一个函数来创建一个实例,这个时候不要使用new来调用函数。当然这个建议是可选的。
1 function Thing() {
2 }
3 Thing.prototype.foo = "bar";
4
5
6 Thing.prototype.logFoo = function () {
7 console.log(this.foo);
8 }
9
10
11 var thing = Object.create(Thing.prototype);
12 thing.logFoo(); //logs "bar"
1 function Thing() {
2 this.foo = "foo";
3 }
4 Thing.prototype.foo = "bar";
5
6
7 Thing.prototype.logFoo = function () {
8 console.log(this.foo);
9 }
10
11
12 var thing = Object.create(Thing.prototype);
13 thing.logFoo(); //logs "bar"
1 function Thing1() {
2 this.foo = "foo";
3 }
4 Thing1.prototype.foo = "bar";
5
6 function Thing2() {
7 this.logFoo(); //logs "bar"
8 Thing1.apply(this);
9 this.logFoo(); //logs "foo"
10 }
11 Thing2.prototype = Object.create(Thing1.prototype);
12 Thing2.prototype.logFoo = function () {
13 console.log(this.foo);
14 }
15
16 var thing = new Thing2();
1 var obj = {
2 foo: "bar",
3 logFoo: function () {
4 console.log(this.foo);
5 }
6 };
7
8 obj.logFoo(); //logs "bar"
1 var obj = {
2 foo: "bar"
3 };
4
5 function logFoo() {
6 console.log(this.foo);
7 }
8
9 logFoo.apply(obj); //logs "bar"
1 var obj = {
2 foo: "bar",
3 deeper: {
4 logFoo: function () {
5 console.log(this.foo);
6 }
7 }
8 };
9
10 obj.deeper.logFoo(); //logs undefined
var obj = {
foo: "bar",
deeper: {
logFoo: function () {
console.log(obj.foo);
}
}
};
obj.deeper.logFoo(); //logs "bar"
1 function Listener() {
2 document.getElementById("foo").addEventListener("click",
3 this.handleClick);
4 }
5 Listener.prototype.handleClick = function (event) {
6 console.log(this); //logs "<div id="foo"></div>"
7 }
8
9 var listener = new Listener();
10 document.getElementById("foo").click();
1 function Listener() {
2 document.getElementById("foo").addEventListener("click",
3 this.handleClick.bind(this));
4 }
5 Listener.prototype.handleClick = function (event) {
6 console.log(this); //logs Listener {handleClick: function}
7 }
8
9 var listener = new Listener();
10 document.getElementById("foo").click();
1 <div id="foo" onclick="console.log(this);"></div>
2 <script type="text/javascript">
3 document.getElementById("foo").click(); //logs <div id="foo"...
4 </script>
1 function test () {
2 var this = {}; // Uncaught SyntaxError: Unexpected token this
3 }
eval this
function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = function () {
eval("console.log(this.foo)"); //logs "bar"
}
var thing = new Thing();
thing.logFoo();
这会造成一个安全问题,除非不用eval,没有其他方式来避免这个问题。
function Thing () {
}
Thing.prototype.foo = "bar";
Thing.prototype.logFoo = new Function("console.log(this.foo);");
var thing = new Thing();
thing.logFoo(); //logs "bar"
1 function Thing () {
2 }
3 Thing.prototype.foo = "bar";
4 Thing.prototype.logFoo = function () {
5 with (this) {
6 console.log(foo);
7 foo = "foo";
8 }
9 }
10
11 var thing = new Thing();
12 thing.logFoo(); // logs "bar"
13 console.log(thing.foo); // logs "foo"
许多人认为这样使用是不好的因为with本身就饱受争议。
1 <div class="foo bar1"></div>
2 <div class="foo bar2"></div>
3 <script type="text/javascript">
4 $(".foo").each(function () {
5 console.log(this); //logs <div class="foo... 6 });
7 $(".foo").on("click", function () {
8 console.log(this); //logs <div class="foo... 9 });
10 $(".foo").each(function () {
11 this.click();
12 });
13 </script>
如果你用过underscore.js 或者 lo-dash 你可能知道许多类库的方法可以通过一个叫做thisArg 的函数参数来传递实例,这个函数参数会作为this的上下文。举个例子,这适用于_.each。原生的JavaScript在ECMAScript 5的时候也允许函数传递一个thisArg参数了,比如forEach。事实上,之前阐述的bind,apply和call的使用已经给你创造了传递thisArg参数给函数的机会。这个参数将this绑定为你所传递的对象。
1 function Thing(type) {
2 this.type = type;
3 }
4 Thing.prototype.log = function (thing) {
5 console.log(this.type, thing);
6 }
7 Thing.prototype.logThings = function (arr) {
8 arr.forEach(this.log, this); // logs "fruit apples..."
9 _.each(arr, this.log, this); //logs "fruit apples..."
10 }
11
12 var thing = new Thing("fruit");
13 thing.logThings(["apples", "oranges", "strawberries", "bananas"]);
这使得代码变得更加简介,因为避免了一大堆bind语句、函数嵌套和this暂存的使用。
标签:
原文地址:http://www.cnblogs.com/koleyang/p/4877641.html