JavaScript函数中this的指向
[toc]
一、函数的基本用法
1 | // 定义函数 |
二、this绑定规则
1. this绑定规则一:默认绑定
默认绑定规则描述 :
默认绑定是最基本的规则,当函数调用不符合其他绑定规则(如隐式绑定、显式绑定或者new绑定)时应用。在默认绑定下,函数的调用方式决定了this的值:
- 非严格模式下,
this默认绑定到全局对象(浏览器环境中是window对象)。 - 严格模式下,
this将绑定到undefined,防止不小心修改全局对象。
(1)普通的函数被独立调用
1 | // "use strict" |
当函数不在任何对象或类中直接调用时,this的值取决于是否在严格模式下运行。在非严格模式下,this会默认绑定到全局对象(浏览器中是window对象),在严格模式下,this会被绑定到undefined。
(2) 函数定义在对象中,但独立调用
1 | //2. 函数定义在对象中,但是独立调用 |
在这里,bar函数从其属于的对象obj中脱离出来,并被赋值给baz。当通过baz()独立调用时,this遵循默认绑定规则,即指向全局对象或在严格模式下为undefined。
(3)高阶函数
1 | // 3.高阶函数 |
即使bar函数最初是obj对象的方法,当它被作为参数传递并在test函数中调用时,this再次遵循默认绑定,指向全局对象或在严格模式下为undefined。
(4)严格模式下的默认绑定
严格模式下,独立调用的任何函数中的this值将被设置为undefined。这是一种防止函数意外修改全局对象的安全措施。
2. this绑定规则二:隐式绑定
__隐式绑定规则__:
- 调用位置:在确定
this的绑定时,关键在于查找函数被调用的位置。具体来说,是查看函数调用时点符号.前面的对象是什么。 - 调用方式:如果一个函数是作为对象的方法被调用,那么
this就指向这个对象。
在JavaScript中,隐式绑定是指当一个函数作为对象的属性被调用时,this自动指向这个对象。
1 | function foo() { |
在这个例子中,函数foo最初被定义为一个普通的函数。之后,它被赋值给对象obj的bar属性。当通过obj.bar()调用foo时,this关键字自动绑定到obj对象。这是因为foo是作为obj对象的bar属性被调用的。
__隐式绑定的失效情况__:
尽管隐式绑定似乎很直接,但在某些情况下,它可以失效,使this回到默认绑定(指向全局对象或undefined)。失效的常见情况包括:
间接引用:如果将方法赋值给一个变量,并通过这个变量调用方法,隐式绑定将失效。
1
2var anotherFunc = obj.bar;
anotherFunc(); // this不再指向obj,而是全局对象或在严格模式下为undefined传递给高阶函数:如果你将一个对象的方法作为回调函数传递给另一个函数,除非使用绑定工具(如
.bind),否则原有的this绑定通常会丢失。1
setTimeout(obj.bar, 100); // 当bar被调用时,this通常不会指向obj
3. this绑定规则三:new绑定
1 | /* |
在这个示例中:
new foo()创建了一个新对象。this在foo函数中指向了这个新创建的对象。- 在
foo函数内部,这个新对象被赋予了一个名为name的新属性,并设为"why"。 - 函数执行了
console.log("foo函数:", this);语句,打印了这个新对象,显示它现在包含一个name属性。 - 由于函数没有返回任何值,所以
new foo()的结果是新创建的对象。
4. this绑定规则四:显示绑定
在JavaScript中,显式绑定是一种通过特定的函数方法,如 .call(), .apply(), 和 .bind(), 来设定函数执行时this的值的技术。通过显式绑定,我们可以直接指定函数调用的上下文对象,不论函数在何处被声明或被调用。
__显式绑定的机制__:
显式绑定主要通过三种函数方法实现:
- **
.call(thisArg, arg1, arg2, ...)**:调用一个函数,其this值被显式设置为call的第一个参数thisArg,其余参数依次传递给函数。 - **
.apply(thisArg, [argsArray])**:与.call()类似,但接受一个参数数组作为函数执行时的参数。 - **
.bind(thisArg[, arg1[, arg2[, ...]]])**:创建一个新函数,当这个新函数被调用时,其this值永久地被绑定为.bind()的第一个参数thisArg,其余参数将作为新函数的预设参数。
1 | var obj = { |
**
foo.call(obj)**:在这个调用中,this被显式设置为指向obj对象。因此,当foo执行时,它打印出this指向的对象,显示对象中的name属性。foo.call(123)和 **foo.call("abc")**:在这两个调用中,this不指向一个常规的对象。JavaScript将这些原始值(数值和字符串)转换为它们对应的包装对象(Number和String对象)。因此,函数内部的this实际上是一个对象,而这个对象是原始值的一个包装形式。
三、this绑定规则优先级
1. 绑定优先级概述:
JavaScript中this的绑定规则有四种主要形式,且它们具有不同的优先级:
- new绑定
- 显式绑定 (
call,apply,bind) - 隐式绑定 (作为对象的方法调用)
- 默认绑定 (独立函数调用)
2. 显式绑定高于隐式绑定:
1 | function foo() { |
无论是.apply(), .call()还是.bind(),显式绑定的this值都会覆盖隐式绑定。
3. new 绑定高于隐式绑定
1 | function foo() { |
即使foo函数作为obj的方法被定义和调用,new操作符创建了一个新的对象,并且this被绑定到这个新对象上。
4. new绑定高于bind
1 | function foo() { |
尽管foo被.bind()方法绑定到"aaa",但new bindFn()的调用创建了一个新的对象,this指向这个对象,显示new绑定优先级高于.bind()。
5. bind和apply/call的比较
1 | var bindFn = foo.bind("aaa"); |
这个例子中,尽管.call()试图将this绑定到"bbb",bindFn的this已被永久绑定到"aaa",表明.bind()的效果是不可被后续的.call()或.apply()覆盖的。
6. 总结
this绑定的优先级从高到低依次是:new绑定、显式绑定(bind优先于call/apply)、隐式绑定、默认绑定。
四、this绑定之外的情况
1. 显式绑定null或undefined
当使用.call(), .apply(), 或 .bind()进行显式绑定时,如果第一个参数是null或undefined,则这些方法不会将this绑定到任何特定值上,而是采用默认绑定规则(非严格模式下绑定到全局对象,严格模式下绑定到undefined)。
1 | function foo() { |
这里,.apply("abc")将this绑定到字符串”abc”的包装对象。但是,.apply(null)和.apply(undefined)则回落到默认绑定规则。在严格模式下,这些调用将导致this绑定到undefined。
2.情况二:间接函数引用
间接引用发生时,函数虽然是从某个对象上取得,但由于赋值或其它操作,函数的调用已经与原对象分离。
1 | var obj1 = { |
在(obj2.foo = obj1.foo)()的调用中,函数通过赋值操作被取得,并立即调用。这种调用方式使得函数的执行脱离了任何对象的上下文(默认绑定),因此在非严格模式下,this指向全局对象。这是因为赋值表达式(obj2.foo = obj1.foo)返回的是函数本身,并且这个返回值不持有原始的对象引用。
五、箭头函数
箭头函数在ECMAScript 6中引入,提供了一种更简洁的函数写法,主要用于匿名函数表达式。与传统的函数表达式相比,箭头函数具有几个特点,包括更短的语法、没有自己的this、arguments、super或new.target,这些值由外围最近一层非箭头函数决定。
箭头函数的注意事项:
- 不适合用作方法:由于箭头函数没有自己的
this,如果将箭头函数作为对象的方法,this将不会指向该对象。 - 不能用作构造函数:箭头函数不能使用
new关键字,因为它们没有[[Construct]]方法,不具备构造对象的能力。 - 没有
arguments对象:不能直接访问命名参数之外的参数,除非使用剩余参数语法。
1. 箭头函数基本使用
(1) 传统函数和箭头函数的对比:
1 | // 1.函数传统写法 |
(2)forEach中使用箭头函数
1 | // 3.箭头函数的练习 |
- 箭头函数作为回调函数传递给
forEach方法,可以直接访问item,index, 和arr。 - 这种场景下箭头函数非常方便,因为它可以继承外围作用域的
this。
(3)setTimeout中使用箭头函数
1 | // 3.2. setTimeout |
- 在
setTimeout调用中使用箭头函数,无需担心this的指向问题,因为箭头函数不绑定this,可以安全地用在定时器和延时调用中。
2. 箭头函数的简写
1 | var names = ["abc", "cba", "nba"] |
3. 箭头函数中this的使用
1 | // 1.普通函数中是有this的标识符 |
箭头函数bar被定义在全局作用域中,因此其this指向全局对象(在浏览器中是window,在Node.js中可能是global或模块的exports对象),即便使用.apply()或.call()也无法改变this的指向。
4. this查找规则
1 | var obj = { |
在这个例子中,即使foo是作为obj的属性定义的,由于foo是箭头函数,其this并不指向obj,而是继承自其创建时的上下文(全局作用域)。因此,嵌套的箭头函数bar中的this同样指向全局作用域,而非obj或通过.apply()尝试指定的任何对象。