开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
一、概述
在js中,this可以帮我们提供很多的便利。但也是js中比较难掌握的知识,下面我们一起来看了解一下this
二、this在不同环境中的指向
我们都知道js的运行环境大多情况下可以分为两种:
- 浏览器
- node
👶 浏览器环境中
我们在浏览器的控制台打印一下当前的this,可以发现this指向window对象
🤵在node环境中
同样,我们在node环境下打印一下this
发现,在node环境中this指向全局对象global Object
三、同一个函数this不同
我们来看一下下面的代码
function foo (){
console.log(this)
}
// 1. 直接调用
foo()
// 2. 间接调用
var obj = {
message: 'wkb',
foo: foo
}
obj.foo()
// 3.call,apply调用
foo.call({message: 'obj'})
我们可以发现,三种情况调用的同一个函数,但是打印出的结果确实不同的。第一种打印window,第二种打印obj对象,第三种情况打印出传入的对象。
更据情况我们可以总结出以下规律:
- this的指向与函数定义的位置没有关系
- this的指向与调用对象有关系
- this是在运行时被绑定的
四、四种this绑定规则
🙍默认绑定
首先什么是默认绑定呢?
函数在调用时,js会默认给this绑定一个值,例如
function foo (){
console.log(this)
}
foo() // window
在全局状态下执行foo函数会被默认绑定window对象
难度升级
function fn1() {
console.log(this)
fn2()
}
function fn2() {
console.log(this)
fn3()
}
function fn3() {
console.log(this)
}
fn1()
大家猜一猜最终打印的三个this是啥。。。
都是window,其实我们只要看是谁调用的函数就行了。在这个代码中,fn1,2,3前面都没有调用的对象,则会默认绑定window对象
👮隐式绑定
隐式绑定比较难理解一点,先来看一段代码吧
function foo (){
console.log(this)
}
var obj = {
message: 'wkb',
foo: foo
}
obj.foo()
同样也是执行的foo函数,但是打印出的结果却不相同,这就是隐式绑定规则。
与默认绑定不同的是,foo有调用对象,谁调用this就指向谁。
我们再看一个案例
function foo (){
console.log(this)
}
var obj1 = {
name:'obj1',
foo: foo
}
var obj2 = {
name:'obj2',
obj1: obj1
}
obj2.obj1.foo()
很绕哈,但是用上面的结论很容易就能得到答案,结果就是this指向obj1对象。
我们只需要看最后的调用对象就行,最后的调用对象是obj1。
💆显示绑定
在js中有三个方法可以显示的改变this,这三个函数分别是call,apply,bind
我们来看一下call,apply,bind的区别
function foo(name, age) {
console.log(this, name, age)
}
foo.call({ message: 'call' }, 'wkb', 18)
foo.apply({ message: 'apply' }, ['wkb', 18])
var fn = foo.bind({ message: 'bind' })
fn('wkb', 18)
- call,apply,bind的第一个参数都是要指向的this对象。
- call和apply会立即执行,bind会返回一个函数,这个函数的this指向是调用bind时传入的第一个参数
- call之后的传参和bind返回函数的传参方式一致,以剩余参数的方式传递
- apply之后的传参则是数组类型
如果call,apply,bind传入的this是null或者undefined,则会使用默认绑定规则
👳new绑定
在了解new绑定之前,我们先了解一下,new 一个构造函数的时候,都做了什么?
- 创建一个空的简单 JavaScript 对象(即 {});
- 为步骤 1 新创建的对象添加属性 proto,将该属性链接至构造函数的原型对象;
- 将步骤 1 新创建的对象作为 this 的上下文;
- 如果该函数没有返回对象,则返回 this。
上代码
function person(name, age) {
this.name = name
this.age = age
console.log(this)
}
var p = new person('wkb', 18)
console.log(p)
根据打印结果,我们可以发现this的指向不再是window,而是指向了一个新对象
五、绑定规则的优先级
好,我们已经了解了四种this的绑定规则,但是有想过如果不同的规则一起使用的时候会发生什么呢?下面介绍一下绑定规则的优先级
1. 默认规则优先级最低
毫无疑问,其他三种规则都能改变this指向,则默认规则优先级最低
2. 显示绑定 > 隐式绑定
举个🌰,上代码
function foo() {
console.log(this)
}
var obj = {
message: 'obj',
foo: foo,
}
obj.foo.call({ message: 'call' })
打印出{message: 'call'},虽然是obj调用的foo,但最终还是被call改变了this指向
3. new绑定 > 隐式绑定
举个🌰,我们稍微把案例2的代码改亿点点
function foo() {
console.log(this)
}
var obj = {
message: 'obj',
foo: foo,
}
let p = new obj.foo()
结果是this指向了一个空对象,并且空对象的类型是构造函数foo,显然new绑定的优先级高于隐式绑定
4. new绑定 > 显示绑定
因为call和apply都是立即执行的,而new需要一个构造函数则new不能与call,apply一起使用。别担心,还记得bind吗。bind不会立即执行函数,我们可以用bind返回后的函数与new来比较。
废话不多说,福利来一波。。。。
错了,错了,上代码
function foo() {
console.log(this)
}
var obj = {
message: 'obj',
foo: foo,
}
var fn = obj.foo.bind({ message: 'bind' })
let p = new fn()
最终this还是指向空对象
六、箭头函数不绑定this
在es6中引出了非常好的函数简写方法,箭头函数。在使用箭头函数的时候,我们要注意箭头函数是不绑定this的,this会指向上一层作用域,例如
var obj = {
data: [],
foo: () => {
console.log(this)
},
}
obj.foo()
如果是foo定义的是普通的函数,则obj.foo()打印出的应该是obj对象,但foo定义的是箭头函数,箭头函数不绑定this,则往上层找,打印出window
七、面试题
我们来看一道tihs面试题
var name = 'window'
function Person (name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()()
person1.obj.foo1.call(person2)()
person1.obj.foo1().call(person2)
person1.obj.foo2()()
person1.obj.foo2.call(person2)()
person1.obj.foo2().call(person2)
首先来看
- person1.obj.foo1()(),先是执行foo1函数,foo1返回一个普通函数,然后全局调用,则打印window
- person1.obj.foo1.call(person2)(),改变了foo1的this执像,但没有改变返回函数的this执行,则打印window
- person1.obj.foo1().call(person2),改变foo1返回函数的this指向,call绑定大于默认绑定,则打印person2
- person1.obj.foo2()(),执行foo2函数,隐式绑定,当前foo2函数中的this为obj对象。然后foo2返回箭头函数,我们知道箭头函数不绑定this,会从上级作用域中找this,foo1的上级作用域是foo2,则打印obj
- person1.obj.foo2.call(person2)(),同样,改变foo2中的this,foo2返回的箭头函数不绑定this,打印person2
- person1.obj.foo2().call(person2),foo2中this为obj,foo2返回箭头函数不绑定this,则打印obj