this
虐我千百遍,看完此文效立见!不得不说,这篇文章的总结很地道很全面,适合收藏之用。原文:all this
习惯了高级语言的你或许觉得Javascript中的this
跟Java这些面向对象语言相似,保存了实体属性的一些值。其实不然。将它视作幻影魔神比较恰当,手提一个装满未知符文的灵龛。
以下内容我希望广大同行们能够了解。全是掏箱底的干货,其中大部分占用了我很多时间才掌握。
this
浏览器宿主的全局环境中,this
指的是window
对象。
123 | <script type="text/javascript"> console.log(this === window); //true</script> |
示例
浏览器中在全局环境下,使用var
声明变量其实就是赋值给this
或window
。
12345 | <script type="text/javascript"> var foo = "bar"; console.log(this.foo); //logs "bar" console.log(window.foo); //logs "bar"</script> |
示例
任何情况下,创建变量时没有使用var
或者let
(ECMAScript 6),也是在操作全局this
。
1234567891011 | <script type="text/javascript"> foo = "bar"; function testThis() { foo = "foo"; } console.log(this.foo); //logs "bar" testThis(); console.log(this.foo); //logs "foo"</script> |
示例
Node命令行(REPL)中,this
是全局命名空间。可以通过global
来访问。
1234567 | > this{ ArrayBuffer: [Function: ArrayBuffer], Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1 }, Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1 }, ...> global === thistrue |
在Node环境里执行的JS脚本中,this
其实是个空对象,有别于global
。
test.js
12 | console.log(this);console.log(this === global); |
123 | $ node test.js{}false |
当尝试在Node中执行JS脚本时,脚本中全局作用域中的var
并不会将变量赋值给全局this
,这与在浏览器中是不一样的。
test.js
12 | var foo = "bar";console.log(this.foo); |
12 | $ node test.jsundefined |
…但在命令行里进行求值却会赋值到this
身上。
12345 | > var foo = "bar";> this.foobar> global.foobar |
在Node里执行的脚本中,创建变量时没带var
或let
关键字,会赋值给全局的global
但不是this
(译注:上面已经提到this
和global
不是同一个对象,所以这里就不奇怪了)。
test.js
123 | foo = "bar";console.log(this.foo);console.log(global.foo); |
123 | $ node test.jsundefinedbar |
但在Node命令行里,就会赋值给两者了。
译注:简单来说,Node脚本中
global
和this
是区别对待的,而Node命令行中,两者可等效为同一对象。
this
除了DOM的事件回调或者提供了执行上下文(后面会提到)的情况,函数正常被调用(不带new
)时,里面的this
指向的是全局作用域。
1234567891011 | <script type="text/javascript"> foo = "bar"; function testThis() { this.foo = "foo"; } console.log(this.foo); //logs "bar" testThis(); console.log(this.foo); //logs "foo"</script> |
示例
test.js
123456789 | foo = "bar";function testThis () { this.foo = "foo";}console.log(global.foo);testThis();console.log(global.foo); |
123 | $ node test.jsbarfoo |
还有个例外,就是使用了"use strict";
。此时this
是undefined
。
1234567891011 | <script type="text/javascript"> foo = "bar"; function testThis() { "use strict"; this.foo = "foo"; } console.log(this.foo); //logs "bar" testThis(); //Uncaught TypeError: Cannot set PRoperty 'foo' of undefined </script> |
示例
当用调用函数时使用了new
关键字,此刻this
指代一个新的上下文,不再指向全局this
。
12345678910111213 | <script type="text/javascript"> foo = "bar"; function testThis() { this.foo = "foo"; } console.log(this.foo); //logs "bar" new testThis(); console.log(this.foo); //logs "bar" console.log(new testThis().foo); //logs "foo"</script> |
示例
通常我将这个新的上下文称作实例。
this
函数创建后其实以一个函数对象的形式存在着。既然是对象,则自动获得了一个叫做prototype
的属性,可以自由地对这个属性进行赋值。当配合new
关键字来调用一个函数创建实例后,此刻便能直接访问到原型身上的值。
12345678 | function Thing() { console.log(this.foo);}Thing.prototype.foo = "bar";var thing = new Thing(); //logs "bar"console.log(thing.foo); //logs "bar" |
示例
当通过new
的方式创建了多个实例后,他们会共用一个原型。比如,每个实例的this.foo
都返回相同的值,直到this.foo
被重写。
1234567891011121314151617181920212223 | function Thing() {}Thing.prototype.foo = "bar";Thing.prototype.logFoo = function () { console.log(this.foo);}Thing.prototype.setFoo = function (newFoo) { this.foo = newFoo;}var thing1 = new Thing();var thing2 = new Thing();thing1.logFoo(); //logs "bar"thing2.logFoo(); //logs "bar"thing1.setFoo("foo");thing1.logFoo(); //logs "foo";thing2.logFoo(); //logs "bar";thing2.foo = "foobar";thing1.logFoo(); //logs "foo";thing2.logFoo(); //logs "foobar"; |
示例
在实例中,this
是个特殊的对象,而this
自身其实只是个关键字。你可以把this
想象成在实例中获取原型值的一种途径,同时对this
赋值又会覆盖原型上的值。完全可以将新增的值从原型中删除从而将原型还原为初始状态。
12345678910111213141516171819202122 | function Thing() {}Thing.prototype.foo = "bar";Thing.prototype.logFoo = function () { console.log(this.foo);}Thing.prototype.setFoo = function (newFoo) { this.foo = newFoo;}Thing.prototype.deleteFoo = function () { delete this.foo;}var thing = new Thing();thing.setFoo("foo");thing.logFoo(); //logs "foo";thing.deleteFoo();thing.logFoo(); //logs "bar";thing.foo = "foobar";thing.logFoo(); //logs "foobar";delete thing.foo;thing.logFoo(); //logs "bar"; |
示例
…或者不通过实例,直接操作函数的原型。
12345678910 | function Thing() {}Thing.prototype.foo = "bar";Thing.prototype.logFoo = function () { console.log(this.foo, Thing.prototype.foo);}var thing = new Thing();thing.foo = "foo";thing.logFoo(); //logs "foo bar"; |
示例
同一函数创建的所有实例均共享一个原型。如果你给原型赋值了一个数组,那么所有实例都能获取到这个数组。除非你在某个实例中对其进行了重写,实事上是进行了覆盖。
123456789 | function Thing() {}Thing.prototype.things = [];var thing1 = new Thing();var thing2 = new Thing();thing1.things.push("foo");console.log(thing2.things); //logs ["foo"] |
示例
通常上面的做法是不正确的(译注:改变thing1
的同时也影响了thing2
)。如果你想每个实例互不影响,那么请在函数里创建这些值,而不是在原型上。
12345678910 | function Thing() { this.things = [];}var thing1 = new Thing();var thing2 = new Thing();thing1.things.push("foo");console.log(thing1.things); //logs ["foo"]console.log(thing2.things); //logs [] |
示例
多个函数可以形成原型链,这样this
便会在原型链上逐步往上找直到找到你想引用的值。
1234567891011 | function Thing1() {}Thing1.prototype.foo = "bar";function Thing2() {}Thing2.prototype = new Thing1();var thing = new Thing2();console.log(thing.foo); //logs "bar" |