转载:https://www.jb51.net/javascript/3394889at.htm
前言
this机制,我猜大部分的JavaScript使用者都对它头疼不已,它确实在很多时候能给我们写代码带来便利,但有些时候却让人难以掌控,时常会出现指哪哪不灵的场景。作为受害者之一,出于好奇和对代码的控制欲,我不得不发挥一下强迫症的力量,驱使我弄懂它,现分享一下我对this关键字的理解。
this对象:解析器在每次调用函数时,都会向函数内部转递一个隐含的参数,这个参数就是this,this指向的是一个对象,这个对象我们称为函数执行的上下文对象,根据函数调用方式的不同,this会指向不同的对象。this是当前执行上下文中所指向的对象(实际上就是这个对象的指针或引用),它取决于函数是如何被调用的。也就是说,this是执行上下文的属性。
this关键字是什么?
this
是javascript
中最复杂的机制之一,this
关键字存在于我们声明的函数中,它是我们所期望操作对象的代表。- 当一个函数被调用时,会在编译器中留下一条记录,这个记录包括了函数的调用位置、调用方式、传递的参数等信息,this也作为记录中的一个信息属性在函数执行的时候被使用。
一、将根据一下几个方面介绍this的指向问题
1、全局环境中的this
在全局环境中(不在任何函数或对象内部),this指向全局对象(在浏览器环境中是window,在node.js中是global)
console.log(this === window); // 在浏览器中输出 true
2、函数中的this
当一个函数被直接调用时,this在非严格模式下指向全局对象(window),在严格模式下指向undefined
JS严格模式:JavaScript在语法和行为上存在一些模糊的特性,可能导致一些不易察觉的错误,为提高代码的质量和可维护性,js引入了严格模式,通过启用一些额外的规则,强制执行更严格的语法和行为。在严格模式下代码中的潜在问题将被捕获并抛出错误,有助于提前发现和修复潜在bug。
function regularFunction() { console.log(this); } regularFunction(); // 非严格模式下指向 window,严格模式下为 undefined // 演示严格模式下的情况 (function () { "use strict"; regularFunction(); })();
3、对象方法中的this
当函数作为对象的方法被调用时,this指向调用该方法的对象
var person = { name: "John", sayName: function () { console.log(this.name); } }; person.sayName(); // 输出 "John",这里的 this 指向 person 对象
4、构造函数中的this
使用new关键字(实例化)调用函数时,该函数被当作构造函数(类),this会指向新创建的对象实例
function Person(name) { this.name = name; this.sayHello = function () { console.log("Hello, I'm " + this.name); }; } var john = new Person("John"); john.sayHello(); // 输出 "Hello, I'm John",这里 this 指向 john 实例
构造函数怎么执行创建对象的过程:
- 调用一个构造函数,他会立即创建一个对象
- 将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
在构造函数中,创建对象和返回对象都给我们隐藏了,使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例。
5、事件处理函数中的this
在DOM事件处理函数中,this通常指向触发事件的元素
<button id="myButton">Click me</button> <script> var button = document.getElementById("myButton"); button.onclick = function () { console.log(this); // 点击按钮时,这里的 this 指向按钮元素 //打印 :<button id="myButton">Click me</button> }; </script>
6、箭头函数中的this
箭头函数没有自己的this,它的this继承自外层作用域的this
// 普通函数 function outerFunction() { this.name = "Outer"; var innerFunction = function () { console.log(this.name); }; innerFunction(); } // 箭头函数 function outerFunctionWithArrow() { this.name = "Outer with Arrow"; var innerFunction = () => { console.log(this.name); }; innerFunction(); } new outerFunction(); // 输出 undefined,因为 innerFunction 中的 this 指向全局对象,全局对象没有 name 属性 new outerFunctionWithArrow(); // 输出 "Outer with Arrow",箭头函数的 this 继承自 outerFunctionWithArrow 的 this
二、改变this指向的方法
由于箭头函数的this来自于继承,箭头函数无法使用以下三种方法改变this指向
1、call()方法
call
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, …)
- 使用 call 方法的时候:
1、会立即执行函数
2、第一个参数是你要改变的函数内部的 this 指向
3、第二个参数开始,依次是向函数传递参数
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) fn.call(obj, 1, 2)
fn(1,2)
的时候,函数内部的 this 指向 window(函数被直接调用)fn.call(obj, 1, 2)
的时候,函数内部的 this 就指向了 obj 这个对象
2、apply()方法
apply
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, …])
- 使用
apply
方法的时候:
1、会立即执行函数
2、第一个参数是你要改变的函数内部的 this 指向
3、第二个参数是一个 数组,数组里面的每一项依次是向函数传递的参数(和call方法的主要区别点)
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) fn.apply(obj, [1, 2])
fn(1,2)
的时候,函数内部的 this 指向 window(函数被直接调用)fn.call(obj, 1, 2)
的时候,函数内部的 this 就指向了 obj 这个对象
3、bind()方法
bind
方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向- 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
- 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'Jack' } function fn(a, b) { console.log(this) console.log(a) console.log(b) } fn(1, 2) var newFn = fn.bind(obj) newFn(1, 2)
- bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
- 这个新的函数就是一个改变了 this 指向以后的 fn 函数
fn(1, 2)
的时候 this 指向 windownewFn(1, 2)
的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj
三、回调函数中this指向
这里我们补充一下在回调函数运用中this指向(也是容易混淆的知识点)
1、对象方法作为回调函数
如果回调函数是一个对象的方法,并且是以对象方法的方式传递进去的,那么 this 通常会指向该对象。
var myObject = { value: 10, callbackFunction: function () { console.log(this.value); } }; [1,2,3].forEach(() => { myObject.callbackFunction() }); //输出三个10
在这个例子中,forEach 是数组的方法,myObject.callbackFunction 作为回调函数传递给 forEach。当 forEach 调用这个回调函数时,this 仍然指向 myObject,因为这个函数本质上还是 myObject 的一个方法。
2、箭头函数作为回调函数
箭头函数没有自己的 this,它会继承外层作用域的 this(依据词法作用域规则)。
function outerFunction() { this.name = "Outer"; var array = [1, 2, 3]; array.forEach(() => { console.log(this.name); //打印三次Outer }); } new outerFunction();
根据上面两个例子,这里我们介绍一下普通函数和箭头函数在确定this指向时的一些区别:
1、普通函数
普通函数在函数定义时会确定函数的作用域,但不会明确函数中this的指向。普通函数中this的指向是在函数被调用时被确定(指向调用者或者全局对象)
2、箭头函数
箭头函数由于其本身不会生成this,其this继承自外层作用域。箭头函数在定义时不仅会确定作用域,而且会捕获外层作用域的this作为自身的this,箭头函数的this在定义时就已经确定,在其后的函数调用时,无论调用箭头函数的是谁它的this指向都不会发生改变。
以上面这个例子为例:
箭头函数定义在 outerFunction 这个函数内部,注意不是定义在 forEach 方法内。具体可以了解一下函数传参的步骤,这里箭头函数是先在 outerFunction 这个函数内部定义,之后才作为参数传给 forEach 方法。箭头函数继承了 outerFunction 函数的this,并在之后被 forEach 方法调用时不会发生改变。
3、回调函数指向全局对象的常见情况
当普通函数作为回调函数,并且这个普通函数是被一个全局函数(如 setTimeout、setInterval)或者在全局作用域中独立调用的函数(没有通过对象来调用)调用时,在非严格模式下,this 通常会指向全局对象。
//被一个全局函数调用 setTimeout(function () { console.log(this); }, 1000);
//在全局作用域中独立调用 function outer(){ inner() } function inner(){ console.log(this); } outer()
四、总结与常见错误示例
this指向:
- 1、普通函数:谁调用函数,this就指向谁,没有调用者就指向全局对象Window
- 2、箭头函数:箭头函数不会创建this,它的this继承自上层作用域中的this
1、案例一
//回调函数中错误使用this var person = { name: "Eve", greetLater: function () { setTimeout(function () { console.log(this.name); // 此处的function为普通函数,作为setTimeout的参数this指向全局对象 }, 1000); } }; person.greetLater();
此处的function为普通函数,被一个全局函数调用,其this指向全局对象 Window 。
要想输出 Eve ,将此处的普通函数改为箭头函数即可。
var person = { name: "Eve", greetLater: function () { setTimeout( ()=> { console.log(this.name); // 输出Eve }, 1000); } }; person.greetLater();
2、案例二
//在嵌套函数中混淆 this 指向 var outer = { name: "Outer Object", innerFunction: function () { var inner = { name: "Inner Object", nestedFunction: function () { console.log(this.name); // 这里 this 指向 inner,而不是 outer } }; inner.nestedFunction(); } }; outer.innerFunction();
nestedFunction 是作为 inner 对象的方法被调用,this指向 inner 对象(根据普通函数 this 指向的规则,当函数作为对象的方法被调用时,this 会指向调用该函数的对象。)
五、使用外层作用域this的方法
想要访问到 outer 对象的 name 属性,可以使用以下两种方法:
1、保存外层 this 的引用
var outer = { name: "Outer Object", innerFunction: function () { var self = this; var inner = { name: "Inner Object", nestedFunction: function () { console.log(self.name); // 现在可以访问到 outer 的 name 属性,输出 "Outer Object" } }; inner.nestedFunction(); } }; outer.innerFunction();
在方法的开头先把 this 保存到一个变量中(通常命名为 self 或 that 等),然后在 inner 对象中使用这个保存的变量来访问 outer 对象的属性。
2、使用箭头函数
var outer = { name: "Outer Object", innerFunction: function () { var inner = { name: "Inner Object", nestedFunction: () => { console.log(this.name); // 输出 "Outer Object" } }; inner.nestedFunction(); } }; outer.innerFunction();
将 nestedFunction 改为箭头函数,因为箭头函数会继承外层作用域的 this,在这里外层作用域是 outer.innerFunction(),其 this 指向 outer 对象,所以箭头函数里的 this 也能指向 outer 对象。
六、JavaScript的this 和 Java的 this 有什么不同吗?
JavaScript 和 Java 中的 this
关键字虽然名称相同,但 工作机制截然不同。理解这些差异是避免混淆的关键:
核心区别概览
特性 | JavaScript | Java |
---|---|---|
绑定时机 | 动态绑定(运行时确定) | 静态绑定(编译时确定) |
作用域规则 | 函数作用域(取决于调用方式) | 类作用域(指向当前实例) |
全局环境中的 this | 浏览器中指向 window ,Node.js 中指向 global | 无全局 this ,只能在类方法中使用 |
构造函数中的 this | 创建并返回新对象 | 引用正在创建的实例 |
静态方法中的 this | 指向全局对象或构造函数本身(取决于调用方式) | 无法使用 this (静态方法属于类而非实例) |
箭头函数 | 继承自外层作用域的 this | 无箭头函数概念 |
详细对比分析
1. JavaScript 的 this
- 动态绑定:
this
的值取决于函数的调用方式,而非定义位置。 - 示例:
// 全局作用域 console.log(this === window); // true(浏览器环境) // 函数调用 function showThis() { console.log(this); // window(非严格模式)或 undefined(严格模式) } // 对象方法 const obj = { name: 'Alice', greet() { console.log(this.name); // 'Alice' } }; // 构造函数 function Person(name) { this.name = name; // this 指向新创建的实例 } // 箭头函数 const arrow = () => { console.log(this); // 继承自外层作用域(如全局作用域) };
2. Java 的 this
- 静态绑定:
this
始终指向当前实例,在编译时即可确定。 - 示例:
public class Person { private String name; // 构造函数 public Person(String name) { this.name = name; // this 指向正在创建的实例 } // 实例方法 public void greet() { System.out.println("Hello, " + this.name); // 访问实例属性 } // 静态方法(无法使用 this) public static void staticMethod() { // this.name = "Bob"; // 编译错误:静态方法中不能使用 this } }
常见场景对比
1. 全局环境
- JavaScript:
console.log(this); // 浏览器中是 window 对象
- Java:
// 错误:无法在类外部使用 this System.out.println(this); // 编译错误
2. 对象方法
- JavaScript:
const obj = { value: 100, getValue() { return this.value; // this 指向 obj } };
- Java:
public class MyClass { private int value = 100; public int getValue() { return this.value; // this 指向当前实例 } }
3. 构造函数
- JavaScript:
function Car(make) { this.make = make; // this 指向新创建的对象 } const myCar = new Car("Toyota");
- Java:
public class Car { private String make; public Car(String make) { this.make = make; // this 指向正在创建的实例 } }
4. 静态方法
- JavaScript:
class MyClass { static staticMethod() { console.log(this === MyClass); // true(this 指向类本身) } }
Java:
public class MyClass { public static void staticMethod() { // this 无法使用 } }
5. 内部类
- JavaScript(箭头函数):
class Outer { constructor() { this.value = 100; } method() { const inner = () => { console.log(this.value); // 继承自 Outer 的 this }; inner(); } }
- Java:
public class Outer { private int value = 100; public class Inner { public void printValue() { System.out.println(Outer.this.value); // 显式引用外部类的 this } } }
总结:如何避免混淆?
- 牢记 JavaScript 的动态绑定特性:
- JavaScript 的
this
取决于 调用方式,而非定义位置。 - 使用箭头函数保留上下文,或通过
bind()
、call()
、apply()
显式绑定this
。
- JavaScript 的
- 理解 Java 的静态绑定特性:
- Java 的
this
始终指向 当前实例,不存在动态变化。 - 静态方法中不能使用
this
,因为静态方法属于类而非实例。
- Java 的
- 注意场景差异:
- JavaScript 的全局环境中有
this
(指向window
或global
),而 Java 没有。 - JavaScript 的箭头函数和 Java 的内部类对
this
的处理不同。
- JavaScript 的全局环境中有