1. this 指向的分析
基本概念
this 是 JavaScript 中的一个关键字,它的指向取决于函数的调用方式,而不是函数定义的位置。
代码示例
javascript
// 定义函数
function foo() {
console.log('foo函数', this);
}
// 方式一:直接使用
// foo();
// 方式二:通过对象调用
const obj = { name: 'meishiduoshuijiao' }
obj.aaa = foo
obj.aaa() // this -> obj
关键点
-
同一个函数在不同的调用方式下,
this的指向是不同的 -
直接调用函数时,
this指向全局对象(浏览器中是window) -
通过对象调用函数时,
this指向该对象
2. this 绑定规则一:默认绑定
什么是默认绑定?
当函数独立调用时(没有任何修饰),会应用默认绑定规则,此时 this 指向全局对象(在浏览器中是 window,在 Node.js 中是 global)。
代码示例
javascript
// 1.普通函数被独立的调用
function foo() {
console.log("foo", this);
}
foo() // this -> window
// 2.函数定义在对象中,但是独立调用
var obj = {
name: "msdsj",
bar: function() {
console.log("bar", this)
}
}
var baz = obj.bar
baz() // this -> window,因为是独立调用
// 3.高阶函数
function test(fn) {
fn() // 独立调用
}
test(obj.bar) // this -> window
严格模式
在严格模式下("use strict"),独立函数调用时,this 指向 undefined,而不是全局对象。
javascript
"use strict"
function foo() {
console.log(this) // undefined
}
foo()
关键点
-
独立函数调用时应用默认绑定
-
即使函数定义在对象中,只要是独立调用,
this仍然指向全局对象 -
通过高阶函数传递的函数,如果在内部独立调用,
this也指向全局对象 -
严格模式下,默认绑定的
this是undefined
3. this 绑定规则二:隐式绑定
什么是隐式绑定?
当函数作为对象的方法被调用时,this 会隐式地绑定到该对象上。
代码示例
javascript
// 隐式绑定
function foo() {
console.log("foo", this)
}
var message = "hello world"
var obj = {
bar: foo
}
obj.bar() // this -> obj
关键点
-
当函数通过对象引用来调用时(如
obj.method()),this绑定到这个对象 -
调用位置使用对象的上下文来引用函数,所以
this就是这个对象 -
隐式绑定可能会丢失(见默认绑定的第二个例子)
4. this 绑定规则三:new 绑定
什么是 new 绑定?
使用 new 关键字调用函数时,会创建一个新对象,并将 this 绑定到这个新对象上。
new 关键字的执行过程
-
创建一个新的空对象
-
将
this指向这个空对象 -
执行函数体中的代码
-
如果函数没有显式返回非空对象,则默认返回这个新对象
代码示例
javascript
function foo() {
this.name = "msdsj"
console.log("foo函数", this)
}
foo() // this -> window
new foo() // this -> 新创建的对象
关键点
-
使用
new调用函数时,会创建一个新对象 -
这个新对象会绑定到函数调用的
this -
如果函数没有返回其他对象,
new表达式会自动返回这个新对象 -
构造函数通常首字母大写(约定俗成)
5. this 绑定规则四:显式绑定
什么是显式绑定?
通过 call()、apply() 或 bind() 方法,可以显式地指定函数调用时 this 的指向。
代码示例
javascript
var obj = {
name: "msdsj"
}
function foo() {
console.log("foo函数", this)
}
// 执行函数,并且函数中的this指向obj对象
// 方式一:通过对象调用(隐式绑定)
// obj.foo = foo
// obj.foo()
// 方式二:通过call显式绑定
foo.call(obj) // this -> obj
foo.call(123) // this -> Number {123}
foo.call("abc") // this -> String {"abc"}
关键点
-
call()和apply()可以在调用函数时显式指定this的值 -
传入基本类型会被包装成对应的包装对象
-
显式绑定比隐式绑定的优先级更高
6. 额外函数补充:apply 和 call
call 和 apply 的区别
两者功能相同,都是显式绑定 this,区别在于传参方式:
-
call(thisArg, arg1, arg2, ...):参数逐个列举 -
apply(thisArg, [argsArray]):参数以数组形式传递
代码示例
javascript
function foo(name, age, height) {
console.log("foo函数被调用", this)
console.log("打印参数", name, age, height)
}
foo("msdsj", 18, 1.88) // 普通调用
// apply:第一个参数绑定this,第二个参数以数组形式传入
foo.apply("apply", ["msdsj", 18, 1.98])
// call:第一个参数绑定this,后续参数逐个传递
foo.call("call", "msdsjsleep", 18, 2.00)
使用场景
-
当参数已经是数组形式时,使用
apply更方便 -
当参数需要逐个传递时,使用
call更直观 -
可以用于借用其他对象的方法
7. 额外函数补充:bind
什么是 bind?
bind() 方法创建一个新函数,这个新函数的 this 被永久绑定到 bind() 的第一个参数。
bind 与 call/apply 的区别
-
call和apply会立即执行函数 -
bind返回一个新函数,不会立即执行
代码示例
javascript
function foo(name, age, height, address) {
console.log("foo", this)
console.log("参数", name, age, height, address)
}
var obj = { name: "msdsj" }
// 1. bind函数的基本使用
var bar = foo.bind(obj)
bar() // this -> obj
// 2. bind函数的其他参数(预设参数/柯里化)
var bar = foo.bind(obj, "msdsj", 18, 1.80)
bar("江西省") // 前三个参数已预设,只需传入第四个参数
关键点
-
bind()返回一个新函数,原函数不受影响 -
可以预设部分参数(函数柯里化)
-
一旦绑定,
this不可再被改变(即使使用call或apply)
8. 内置函数的调用绑定
常见内置函数的 this 绑定
JavaScript 的许多内置函数都有自己的 this 绑定规则。
代码示例
javascript
// 1. setTimeout
setTimeout(function() {
console.log("定时器函数", this) // window
}, 1000)
// 2. DOM 事件监听
var btnEl = document.querySelector("button")
btnEl.onclick = function() {
console.log("btn的点击:", this) // 指向按钮元素
}
// 3. 数组方法(forEach、map 等)
var names = ["arr1", "arr2", "arr3"]
names.forEach(function(item) {
console.log("foreach", this)
}, "aaa") // 第二个参数可以指定this,否则为window/undefined
常见内置函数的 this 规则
| 函数类型 | this 指向 | 说明 |
|---|---|---|
setTimeout/setInterval |
全局对象 | 独立函数调用 |
| DOM 事件监听器 | 触发事件的元素 | 浏览器内部绑定 |
forEach/map/filter |
可选参数指定 | 第二个参数可指定 this |
addEventListener |
事件目标元素 | 浏览器自动绑定 |
关键点
-
了解常用内置函数的
this指向规则 -
某些数组方法提供了可选参数来指定
this -
使用箭头函数可以避免
this绑定的问题
9. this 绑定规则优先级
优先级排序
当多个绑定规则同时出现时,按以下优先级判断(从高到低):
-
new 绑定 > 显式绑定
-
显式绑定(bind) > 显式绑定(call/apply)
-
显式绑定 > 隐式绑定
-
隐式绑定 > 默认绑定
代码示例
1. 显式绑定高于隐式绑定
javascript
function foo() {
console.log("foo", this)
}
var obj = { foo }
obj.foo.apply("abc") // this -> "abc",显式绑定优先
var bar = foo.bind("aaa")
var obj = {
name: "msdsj",
baz: bar
}
obj.baz() // this -> "aaa",bind的优先级更高
2. new 绑定高于隐式绑定'
javascript
var obj = {
name: "msdsj",
foo: function() {
console.log("foo", this)
}
}
new obj.foo() // this -> 新创建的对象,而不是obj
3. new 绑定高于 bind
javascript
function foo() {
console.log("foo", this)
}
var bindFn = foo.bind("aaa")
new bindFn() // this -> 新创建的对象,而不是"aaa"
注意:new 不能和 call/apply 一起使用(语法错误)
4. bind 高于 apply/call
javascript
function foo() {
console.log("foo:", this)
}
var bindFn = foo.bind("aaa")
bindFn.apply("bbb") // this -> "aaa",bind后无法改变
优先级记忆口诀
new 最大,bind 次之,显式再次,隐式最末,独立最小
10. this 绑定之外的情况
特殊情况
有些情况下,this 的绑定会出现意想不到的结果。
代码示例
1. 传入 null 或 undefined
javascript
function foo() {
console.log("foo", this)
}
foo.apply("abc") // this -> String {"abc"}
foo.apply(null) // this -> window (非严格模式)
foo.apply(undefined) // this -> window (非严格模式)
在严格模式下:
javascript
"use strict"
function foo() {
console.log("foo", this)
}
foo.apply(null) // this -> null
foo.apply(undefined) // this -> undefined
2. 间接函数引用
javascript
var obj1 = {
name: "obj1",
foo: function() {
console.log("foo", this)
}
}
var obj2 = {
name: "obj2"
};
obj2.foo = obj1.foo;
obj2.foo() // this -> obj2
(obj2.foo = obj1.foo)() // this -> window
// 赋值表达式的返回值是函数本身,相当于独立函数调用
关键点
-
传入
null或undefined时,非严格模式下会绑定到全局对象 -
赋值表达式
(obj2.foo = obj1.foo)的结果是函数本身,调用时是独立调用 -
这些特殊情况在实际开发中要注意避免
11. 箭头函数中的 this
箭头函数的特性
箭头函数不绑定 this,它会捕获其所在上下文的 this 值,作为自己的 this 值。
核心特点
-
箭头函数没有自己的 this
-
this 由外层作用域决定
-
无法通过 call、apply、bind 改变 this
-
在定义时就确定了 this,而不是调用时
代码示例
1. 箭头函数没有自己的 this
javascript
// 箭头函数会去上层作用域找this,也就是全局对象window
var bar = () => {
console.log("bar", this) // window
}
bar()
2. 无法通过 apply 改变 this
javascript
var bar = () => {
console.log("bar", this) // 仍然是window
}
bar.apply("aaa")
3. this 的查找规则
javascript
var obj = {
name: "aaa",
message: "hello world",
foo: function() {
// 普通函数,this指向obj
var bar = () => {
// 箭头函数,this从外层作用域(foo)继承
console.log("bar", this) // this指向obj对象
}
return bar
}
}
var fn = obj.foo()
fn.apply("bbb") // this仍然指向obj,无法改变
使用场景
✅ 适合使用箭头函数
-
回调函数(如数组方法、定时器)
-
需要保持外层
this的场景 -
简短的函数表达式
javascript
// 示例:数组方法中
const obj = {
name: 'test',
arr: [1, 2, 3],
print: function() {
this.arr.forEach(item => {
console.log(this.name, item) // 可以访问到obj.name
})
}
}
// 示例:定时器中
const person = {
name: 'John',
sayHi: function() {
setTimeout(() => {
console.log('Hi, ' + this.name) // 可以访问到person.name
}, 1000)
}
}
❌ 不适合使用箭头函数
-
对象的方法
-
需要动态
this的场景 -
构造函数
-
需要使用
arguments对象的函数
javascript
// 错误示例:对象方法
const obj = {
name: 'test',
sayHi: () => {
console.log(this.name) // this不指向obj
}
}
// 正确示例:使用普通函数
const obj = {
name: 'test',
sayHi: function() {
console.log(this.name) // this指向obj
}
}
箭头函数总结
| 特性 | 普通函数 | 箭头函数 |
|---|---|---|
| this 绑定 | 动态绑定,取决于调用方式 | 静态绑定,继承外层作用域 |
| 可否用 new | 可以 | 不可以 |
| arguments | 有 | 没有 |
| 可否改变 this | 可以(call/apply/bind) | 不可以 |
| 适用场景 | 对象方法、构造函数 | 回调函数、保持外层this |
总结
this 指向判断流程图
javascript
判断函数调用时的this指向:
1. 是否用 new 调用?
└─ 是:this 指向新创建的对象
2. 是否用 call/apply/bind 调用?
└─ 是:this 指向指定的对象
3. 是否通过对象调用(obj.method())?
└─ 是:this 指向该对象
4. 是否是箭头函数?
└─ 是:this 继承外层作用域的this
5. 以上都不是(独立函数调用)
└─ 非严格模式:this 指向 window
└─ 严格模式:this 是 undefined
核心要点
-
this 是在运行时绑定的,不是编写时绑定
-
this 的绑定只取决于函数的调用方式
-
四大绑定规则:默认绑定、隐式绑定、显式绑定、new 绑定
-
优先级:new > bind > call/apply > 隐式 > 默认
-
箭头函数:不绑定 this,继承外层作用域