1 this的五种绑定方式
1.1 默认绑定
默认绑定是指当函数调用时,没有为其指定对象上下文,此时会将该函数的this绑定到全局对象(window对象)。自ES5有了严格模式之后,默认绑定方式又分为非严格模式的默认绑定和严格模式的默认绑定。
1.1.1 非严格模式
在非严格模式下,函数的默认绑定只能绑定到全局对象window,见下面例子:
var myName = 'syzdev' // 相当于 window.myName = 'syzdev'
function sayName() {
// 这里的 this === window
console.log(this === window); // true
console.log(this.myName); // syzdev
}
sayName()
在这个例子中,首先声明了一个全局变量myName:
var声明的变量为全局变量,并且会将该变量添加为全局对象window的属性,需要注意的是,例子中的var不可修改为ES6中的let或const,不然结果大有不同,有兴趣的读者可以查阅资料“var、let、const的区别”。
再定义了一个sayName()函数,在函数中输出this.myName,最后直接调用sayName()函数,再调用时,由于没有指定其对象上下文,所以触发了默认绑定,即将函数中的this绑定到全局对象window上,因此在函数内this === window,最后输出的this.myName为 syzdev。
1.1.2 严格模式
关于严格模式,这里不做介绍,若不了解的读者可以阅读MDN-严格模式。
在严格模式下,有一项规定为“禁止this关键字指向全局对象window”,此时this会绑定到undefined,见下面例子:
// 开启严格模式
'use strict'
var myName = 'syzdev'
function sayName() {
// 这里的 this !== window
console.log(this === window); // false
console.log(this); // undefined
console.log(this.myName); // 报错:Uncaught TypeError: Cannot read properties of undefined (reading 'myName')
}
sayName()
在这个例子中,在代码顶部使用'use strict'声明了严格模式,由于在严格模式下禁止this绑定到全局对象window,所以函数中的this !== window,输出this为undefined,因此输出this.myName时会直接抛出错误“Uncaught TypeError: Cannot read properties of undefined (reading ‘name’)”。
由于严格模式可以声明为全局或个别函数内,上面的例子中就是声明为全局严格模式,若声明为函数内严格模式,伪代码如下:
function sayName() {
'use strict'
...
}
需要注意的是,严格模式并不会影响到调用函数内的默认绑定,见下面例子:
var myName = 'syzdev'
function sayName() {
// 这里的 this === window
console.log(this === window); // true
console.log(this.myName); // syzdev
}
function strictSayName() {
// 函数内的严格模式
'use strict'
sayName()
}
strictSayName()
在这个例子中,定义函数strictSayName()并在函数内部声明严格模式,再调用sayName()函数,此时sayName()函数内部依然能够将this绑定到全局对象window上。
1.2 隐式绑定
隐式绑定是日常开发中最为常见的绑定方式,即调用函数时为函数指明其对象上下文,此时函数中的this就为对象本身,见下面例子:
var personObj = {
myName: 'syzdev',
sayName: function() {
console.log(this === personObj) // true
console.log(this.myName); // syzdev
}
}
personObj.sayName()
在这个例子中,定义一个personObj对象,包含myName属性和sayName()方法,通过对象personObj.sayName()调用函数,此时就会触发this隐式绑定,函数中的this就是personObj本身。
但隐式绑定还可能会出现绑定丢失的情况,此时隐式绑定就会变成了默认绑定,见下面例子:
var personObj = {
myName: '123',
sayName: function() {
console.log(this === window); // true
console.log(this.myName); // undefined
}
}
var anotherSayName = personObj.sayName
anotherSayName()
在这个例子中,把personObj.sayName()函数赋值给了变量anotherSayName,再直接使用anotherSayName()调用,此时虽然调用的还是sayName()函数,但是却丢失了对象上下文,此时隐式绑定变成了默认绑定,anotherSayName()函数内的this绑定到全局对象window上,由于window对象上没有myName属性,所以输出undefined。
1.3 显式绑定
显示绑定顾名思义,通过调用call/apply/bind方法,强制将函数中的this绑定到指定的对象,三者区别如下:
call()接受的是多个对象作为参数。apply()接受的是多个对象组成的数组作为参数。bind()返回的是一个函数,除此之外使用方法与call()一样。
使用call/apply/bind实现显示绑定的方法如下:
- 不含参数的使用方法:
function foo() {
console.log(this.myName);
}
var myName = 'window myName'
var personObj = {
myName: 'personObj myName'
}
foo() // window myName
foo.call(personObj) // personObj myName
foo.apply(personObj) // personObj myName
foo.bind(personObj)() // personObj myName
在这个例子中,直接调用sayName()函数会触发默认绑定,函数内的this绑定到全局对象window,所以输出window myName。由于该例子中函数不需要传参,所以在使用call/apply/bind方法时,不需要为其传递除this对象外的其他参数,其效果都是一致的,都是将sayNname()函数内的this绑定到personObj对象,最后输出的myName为personObj对象中的personObj myName。
- 含参数的使用方法:
function foo(name, age) {
console.log(name, age)
}
foo('syzdev', 18) // 直接调用函数传递参数
foo.call(this, 'syzdev', 18) // call执行函数,第一个参数指定this对象,后续参数依次为函数的传参
foo.apply(this, ['syzdev', 18]) // apply执行函数,第一个参数指定this对象,第二个参数为一个数组,数组中为函数的传参
foo.bind(this, 'syzdev', 18)() // bind执行函数,返回的是一个函数,除此之外用法与call相同
// 输出结果
// syzdev 18
// syzdev 18
// syzdev 18
// syzdev 18
代码详解见注释。
1.4 new绑定
在ES6以前,生成一个实例对象的方法是使用构造函数,构造函数只是一个普通的函数,当使用new操作符调用构造函数时,JavaScript解释器便会在底层创建一个新对象,构造函数内的this绑定的就是这个新对象,见下面例子:
function Person(name, age) {
this.name = name
this.age = age
}
var person = new Person('syzdev', 18)
在这个例子中,使用new操作符执行一个构造函数,创建一个person对象,在构造函数内会创建一个新的对象,将函数内的this绑定到这个新对象上,如果函数内部没有返回其他对象,则构造函数会默认返回这个新对象,流程如下图所示:
1.5 箭头函数绑定
箭头函数是ES6中的一个重要特性,在箭头函数中没有自己的this,其this是根据外层的作用域来决定的,箭头函数内的this指的是定义时所在的对象,是在函数定义时已经决定了,而不是像普通函数一样在调用时绑定this对象,见下面例子:
var foo = () => {
console.log(this.myName);
}
var myName = 'window myName'
var personObj = {
myName: 'personObj myName'
}
foo() // window myName
foo.call(personObj) // window myName
foo.apply(personObj) // window myName
foo.bind(personObj)() // window myName
在这个例子中,函数foo定义为箭头函数,由于箭头函数没有自己的this,其this值在定义时已经决定,所以无法被call/apply/bind方法所改变,在该例子中其this值为全局对象window,所以四种方法调用foo()函数的结果都为window myName。
2 this绑定的优先级
所谓this绑定的优先级,指的是当函数执行时若同时指定了多个this对象时的绑定顺序。
- 隐式绑定 > 默认绑定
在第2章的例子中已经证明了这个结论:
var personObj = {
myName: 'syzdev',
sayName: function() {
console.log(this === personObj) // true
console.log(this.myName); // syzdev
}
}
// 相当于 window.personObj.sayName()
personObj.sayName()
在这个例子中,personObj.sayName()为隐式绑定,而personObj.sayName()相当于window.personObj.sayName()可见window对象的默认绑定失效了,最终执行的this依旧是personObj,所以隐式绑定 > 默认绑定。
- 显示绑定 > 隐式绑定
修改第3章的例子如下:
function foo() {
console.log(this.myName);
}
var myName = 'window myName'
var personObj = {
myName: 'personObj myName',
foo: foo // 修改部分
}
personObj.foo.call(window) // window myName
在上面例子中的personObj.foo为隐式绑定,再通过call将foo()函数中的this显示绑定到全局对象window上,最终输出的结果还是window myName,可见显示绑定 > 隐式绑定。
new绑定 > 显示绑定
function foo(name) {
this.name = name
}
// 创建对象obj
var obj = {}
// 将obj绑定为函数foo中的this
var bindFoo = foo.bind(obj)
bindFoo('syzdev')
console.log(obj.name) // syzdev
var bindObj = new bindFoo('bind syzdev')
console.log(bindObj.name) // bind syzdev
在这个例子中,使用bind方法将obj绑定到foo()函数中并返回一个新的函数bindFoo,在使用new操作符创建一个新的对象bindObj时,函数bindFoo()的this指向发生了改变,原本函数bindFoo()中的this指向的是obj,在使用new操作符后指向了一个新的对象并返回赋值给了bindObj。
2.2 总结
在讨论this绑定优先级时之所以不讨论箭头函数,是因为箭头函数的this在其函数定义时就已经确定,不存在优先级一说。其他四种绑定方式的优先级如下,由高到低:
new绑定- 显示绑定
- 隐式绑定
- 默认绑定

本文详细解析JavaScript中this的五种绑定方式,包括默认绑定(严格模式与非严格模式)、隐式绑定、显式绑定(call/apply/bind)、new绑定以及箭头函数的独特行为。理解这些绑定规则有助于提升函数式编程的灵活性。
309

被折叠的 条评论
为什么被折叠?



