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
global this
- <script type="text/javascript">
- console.log(this === window); //true
- </script>
- <script type="text/javascript">
- var foo = "bar";
- console.log(this.foo); //logs "bar"
- console.log(window.foo); //logs "bar"
- </script>
- <script type="text/javascript">
- foo = "bar";
- function testThis() {
- foo = "foo";
- }
- console.log(this.foo); //logs "bar"
- testThis();
- console.log(this.foo); //logs "foo"
- </script>
- > this {
- ArrayBuffer: [Function: ArrayBuffer],
- Int8Array: { [Function: Int8Array] BYTES_PER_ELEMENT: 1
- },
- Uint8Array: { [Function: Uint8Array] BYTES_PER_ELEMENT: 1
- },
- ... > global === this true
- test.js脚本内容:
- console.log(this);
- console.log(this === global);
- REPL运行脚本:
- $ node test.js
- {}
- false
- test.js:
- var foo = "bar";
- console.log(this.foo);
- $ node test.js
- undefined
- > var foo = "bar";
- > this.foo
- bar
- > global.foo
- bar
- test.js
- foo = "bar";
- console.log(this.foo);
- console.log(global.foo);
- $ node test.js
- undefined
- bar
上面的八种情况可能大家已经绕晕了,总结起来就是:在浏览器里面 this 是老大,它等价于 window 对象,如果你声明一些全局变量 (不管在任何地方),这些变量都会作为 this 的属性。在 node 里面,有两种执行 JavaScript 代码的方式,一种是直接执行写好的 JavaScript 文件,另外一种是直接在里面执行一行行代码。对于直接运行一行行 JavaScript 代码的方式,global 才是老大,this 和它是等价的。在这种情况下,和浏览器比较相似,也就是声明一些全局变量会自动添加给老大 global,顺带也会添加给 this。但是在 node 里面直接脚本文件就不一样了,你声明的全局变量不会自动添加到 this,但是会添加到 global 对象。所以相同点是,在全局范围内,全局变量终究是属于老大的。
function this
- <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
- foo = "bar";
- function testThis () {
- this.foo = "foo";
- }
- console.log(global.foo);
- testThis();
- console.log(global.foo);
- $ node test.js
- bar
- foo
- <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>
- <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 其实相对比较好理解,如果我们在一个函数里面使用 this,需要注意的就是我们调用函数的方式,如果是正常的方式调用函数,this 指代全局的 this,如果我们加一个 new,这个函数就变成了一个构造函数,我们就创建了一个实例,this 指代这个实例,这个和其他面向对象的语言很像。另外,写 JavaScript 很常做的一件事就是绑定事件处理程序,也就是诸如
('click', fn,false) 之类的,如果在 fn 里面需要使用 this,this 指代事件处理程序对应的对象,也就是 button。
- button.addEventListener
prototype this
- function Thing() {
- console.log(this.foo);
- }
- Thing.prototype.foo = "bar";
- var thing = new Thing(); //logs "bar"
- console.log(thing.foo); //logs "bar"
复制代码
- 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";
- 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";
- 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";
通过一个函数创建的实例会共享这个函数的 prototype 属性的值,如果你给这个函数的 prototype 赋值一个 Array,那么所有的实例都会共享这个 Array,除非你在实例里面重写了这个 Array,这种情况下,函数的 prototype 的 Array 就会被隐藏掉。
- function Thing() {
- }
- Thing.prototype.things = [];
- var thing1 = new Thing();
- var thing2 = new Thing();
- thing1.things.push("foo");
- console.log(thing2.things); //logs ["foo"]
- 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 []
- function Thing1() {
- }
- Thing1.prototype.foo = "bar";
- function Thing2() {
- }
- Thing2.prototype = new Thing1();
- var thing = new Thing2();
- console.log(thing.foo); //logs "bar"
- function Thing1() {
- }
- Thing1.prototype.foo = "bar";
- function Thing2() {
- this.foo = "foo";
- }
- Thing2.prototype = new Thing1();
- function Thing3() {
- }
- Thing3.prototype = new Thing2();
- var thing = new Thing3();
- console.log(thing.foo); //logs "foo"
- function Thing1() {}
- Thing1.prototype.foo = "bar";
- Thing1.prototype.logFoo = function() {
- console.log(this.foo);
- }
- function Thing2() {
- this.foo = "foo";
- }
- Thing2.prototype = new Thing1();
- var thing = new Thing2();
- thing.logFoo(); //logs "foo";
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- var info = "attempting to log this.foo:";
- function doIt() {
- console.log(info, this.foo);
- }
- doIt();
- }
- var thing = new Thing();
- thing.logFoo(); //logs "attempting to log this.foo: undefined"
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- console.log(this.foo);
- }
- function doIt(method) {
- method();
- }
- var thing = new Thing();
- thing.logFoo(); //logs "bar"
- doIt(thing.logFoo); //logs undefined
博主非常喜欢用这种方式
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- var self = this;
- var info = "attempting to log this.foo:";
- function doIt() {
- console.log(info, self.foo);
- }
- doIt();
- }
- var thing = new Thing();
- thing.logFoo(); //logs "attempting to log this.foo: bar"
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- var self = this;
- function doIt() {
- console.log(self.foo);
- }
- doIt();
- }
- function doItIndirectly(method) {
- method();
- }
- var thing = new Thing();
- thing.logFoo(); //logs "bar"
- doItIndirectly(thing.logFoo); //logs undefined
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- console.log(this.foo);
- }
- function doIt(method) {
- method();
- }
- var thing = new Thing();
- doIt(thing.logFoo.bind(thing)); //logs bar
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- function doIt() {
- console.log(this.foo);
- }
- doIt.apply(this);
- }
- function doItIndirectly(method) {
- method();
- }
- var thing = new Thing();
- doItIndirectly(thing.logFoo.bind(thing)); //logs bar
- function Thing() {
- }
- Thing.prototype.foo = "bar";
- function logFoo(aStr) {
- console.log(aStr, this.foo);
- }
- var thing = new Thing();
- logFoo.bind(thing)("using bind"); //logs "using bind bar"
- logFoo.apply(thing, ["using apply"]); //logs "using apply bar"
- logFoo.call(thing, "using call"); //logs "using call bar"
- logFoo("using nothing"); //logs "using nothing undefined"
- function Thing() {
- return {};
- }
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- console.log(this.foo);
- }
- var thing = new Thing();
- thing.logFoo(); //Uncaught TypeError: undefined is not a function
奇怪的是,如果你在构造函数里面返回了一个原始值,上面所述的情况并不会发生并且返回语句被忽略了。最好不要在你将通过 new 调用的构造函数里面返回任何类型的数据,即便你知道自己正在做什么。如果你想创建一个工厂模式,通过一个函数来创建一个实例,这个时候不要使用 new 来调用函数。当然这个建议是可选的。
- function Thing() {
- }
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function () {
- console.log(this.foo);
- }
- var thing = Object.create(Thing.prototype);
- thing.logFoo(); //logs "bar"
- function Thing() {
- this.foo = "foo";
- }
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- console.log(this.foo);
- }
- var thing = Object.create(Thing.prototype);
- thing.logFoo(); //logs "bar"
因为 Object.create 不会调用构造函数的特性在你继承模式下你想通过原型链重写构造函数的时候非常有用。
- function Thing1() {
- this.foo = "foo";
- }
- Thing1.prototype.foo = "bar";
- function Thing2() {
- this.logFoo(); //logs "bar"
- Thing1.apply(this);
- this.logFoo(); //logs "foo"
- }
- Thing2.prototype = Object.create(Thing1.prototype);
- Thing2.prototype.logFoo = function() {
- console.log(this.foo);
- }
- var thing = new Thing2();
object this
- var obj = {
- foo: "bar",
- logFoo: function () {
- console.log(this.foo);
- }
- };
- obj.logFoo(); //logs "bar"
- var obj = {
- foo: "bar"
- };
- function logFoo() {
- console.log(this.foo);
- }
- logFoo.apply(obj); //logs "bar"
当你用这种方式使用 this 的时候,并不会越出当前的对象。只有有相同直接父元素的属性才能通过 this 共享变量。
- var obj = {
- foo: "bar",
- deeper: {
- logFoo: function() {
- console.log(this.foo);
- }
- }
- };
- obj.deeper.logFoo(); //logs undefined
- var obj = {
- foo: "bar",
- deeper: {
- logFoo: function () {
- console.log(obj.foo);
- }
- }
- };
- obj.deeper.logFoo(); //logs "bar"
DOM event this
- function Listener() {
- document.getElementById("foo").addEventListener("click",
- this.handleClick);
- }
- Listener.prototype.handleClick = function (event) {
- console.log(this); //logs "<div id="foo"></div>"
- }
- var listener = new Listener();
- document.getElementById("foo").click();
- function Listener() {
- document.getElementById("foo").addEventListener("click",
- this.handleClick.bind(this));
- }
- Listener.prototype.handleClick = function (event) {
- console.log(this); //logs Listener {handleClick: function}
- }
- var listener = new Listener();
- document.getElementById("foo").click();
HTML this
- <div id="foo" onclick="console.log(this);"></div>
- <script type="text/javascript">
- document.getElementById("foo").click(); //logs <div id="foo"...
- </script>
override this
- 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"
with this
- function Thing() {}
- Thing.prototype.foo = "bar";
- Thing.prototype.logFoo = function() {
- with(this) {
- console.log(foo);
- foo = "foo";
- }
- }
- var thing = new Thing();
- thing.logFoo(); // logs "bar"
- console.log(thing.foo); // logs "foo"
许多人认为这样使用是不好的因为 with 本身就饱受争议。
jQuery this
- <div class="foo bar1"></div>
- <div class="foo bar2"></div>
- <script type="text/javascript">
- $(".foo").each(function () {
- console.log(this); //logs <div class="foo...
- });
- $(".foo").on("click", function () {
- console.log(this); //logs <div class="foo...
- });
- $(".foo").each(function () {
- this.click();
- });
- </script>
thisArg this
如果你用过 underscore.js 或者 lo-dash 你可能知道许多类库的方法可以通过一个叫做 thisArg 的函数参数来传递实例,这个函数参数会作为 this 的上下文。举个例子,这适用于_.each。原生的 JavaScript 在 ECMAScript 5 的时候也允许函数传递一个 thisArg 参数了,比如 forEach。事实上,之前阐述的 bind,apply 和 call 的使用已经给你创造了传递 thisArg 参数给函数的机会。这个参数将 this 绑定为你所传递的对象。
- function Thing(type) {
- this.type = type;
- }
- Thing.prototype.log = function(thing) {
- console.log(this.type, thing);
- }
- Thing.prototype.logThings = function(arr) {
- arr.forEach(this.log, this); // logs "fruit apples..."
- _.each(arr, this.log, this); //logs "fruit apples..."
- }
- var thing = new Thing("fruit");
- thing.logThings(["apples", "oranges", "strawberries", "bananas"]);
这使得代码变得更加简介,因为避免了一大堆 bind 语句、函数嵌套和 this 暂存的使用。
来源: http://www.bubuko.com/infodetail-2446087.html