this到底指向谁
首先先记住以下特点,带你一起了解this的指向问题:
- 在函数体中,非显式或隐式地简单调用函数时,严格模式下,函数内的this 会被绑定到undefined上,在非严格模式下则会被绑定到全局对象window上。
- 一般使用new方法调用构造函数时,构造函数内的this会被绑定到新创建的对象上。
- 一般通过call/apply/bind方法显式调用函数时,函数体内的 this会被绑定到指定参数的对象上。
- 一般通过上下文对象调用函数时,函数体内的 this 会被绑定到该对象上。
- 在箭头函数中,this 的指向是由外层(函数或全局)作用域来决定的。
全局环境中的this指向
function f1() {
console.log(this);
}
function f2() {
'use strict'
console.log(this);
}
f1() // window
f2() // undefined
这到基础的题,可以理解我们的第一个this的特点,那么 我们再来看下面这两个:
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
}
}
let fn1 = foo.fn
fn1() // Window 和 undefined
其实这里也很好理解,函数在调用时才能确定它的this指向,fn赋值给了fn1,所以fn1调用时相当于window环境下全局执行,所以this指向还是window。
那么把上边的代码换成下边:
const foo = {
bar: 10,
fn: function () {
console.log(this);
console.log(this.bar);
}
}
foo.fn()
输出结果:
{bar: 10, fn: ƒ}
10
这时,this指向最后调用它的对象,如果函数中的this,是被上一级所调用的,那么this就是指向它的上一级调用者。
上下文调用中的this
参考上边的代码,我们知道下边的代码输出为true
const student = {
name: 'Lucas',
fn: function () {
return this
}
}
console.log(student.fn()===student); // true
当存在更复杂的关系时,this会指向它最后的调用者,最后的调用者为brother,因此输出为brother。
const person = {
name: 'person',
brother:{
name: 'brother',
fn: function () {
return this.name
}
}
}
console.log(person.brother.fn()); // brother
至此,this上下文对象的调用介绍的比较清楚了,我们再来看一个高阶题目:
const o1 = {
text:'o1',
fn: function(){
return this.text
}
}
const o2 = {
text:'o2',
fn: function(){
return o1.fn()
}
}
const o3 = {
text:'o3',
fn: function(){
let fn = o1.fn
return fn()
}
}
console.log(o1.fn()); // o1
console.log(o2.fn()); // o1
console.log(o3.fn()); // undefined
- 第一个输出不难理解,o1调用的函数fn所以返回的是o1;
- 第二个o2.fn函数最终调用的还是o1.fn,所以运行的结果仍然是o1;
- 第三个 let fn = o1.fn 进行了赋值操作,它的返回值为fn()相当于全局调用,所以o3为undefined
通过bind、call、apply改变this指向
bind、call、apply都是用来改变this指向的,但是call和apply是直接进行函数调用的,bind不会立即执行相关函数,其次就是参数设定上的不同,简单的来说就是以下代码是等价的。
// call
const target = {}
fn.call(target,'arg1','arg2')
// apply
const target = {}
fn.apply(target,['arg1','arg2'])
// bind
const target = {}
fn.bind(target,'arg1','arg2')()
下面我们来看一道例题
const foo = {
name: 'Lucas',
logName:function(){
console.log(this.name);
}
}
const bar = {
name: 'mike'
}
foo.logName.call(bar) // mike
以上代码执行的结果为mike,通过call改变了this的指向,如果有不明白bind、call、apply的可以参考这篇文章 从原生的角度理解call、apply、bind的实现。
构造函数中的this
function Foo() {
this.bar = 'Lucas'
}
const instance = new Foo()
console.log(instance.bar); // Lucas
执行以上代码输出为Lucas,但是这样的场景往往伴随着另一个问题,new操作符在调用构造函数时具体做了什么?简单的来说可以分为以下四个步骤:
- 创建一个新的对象;
- 将构造函数的this指向这个新对象;
- 为这个对象添加新的属性和方法等;
- 最终返回新的对象
// 场景一
function Foo() {
this.user = 'Lucas'
const o = {}
return o
}
const instance = new Foo()
console.log(instance.user) // undefined
// 场景二
function Foo() {
this.user = 'Lucas'
return 1
}
const instance = new Foo()
console.log(instance.user) // Lucas
// 场景三
function Foo() {
this.user = 'Lucas'
return this
}
const instance = new Foo()
console.log(instance.user) // Lucas
以上三种情况可以看出,如果构造函数中显示的返回一个值,且返回的还是一个对象(复杂数据类型),那么this指向这个返回的对象;如果返回的是一个不是一个对象(基本类型),那么this仍然指向实例。
箭头函数中的this
在箭头函数中,this指向都是有外层作用域来决定的,指向它最近一级的作用域。
const foo = {
fn:function(){
setTimeout(function(){
console.log(this);
})
}
}
foo.fn() // Window
上面代码,setTimeout在匿名函数中,所以this指向是Window;如果我们像改变这个this指向,可以巧妙的运用箭头函数。
const foo = {
fn:function(){
setTimeout(()=>{
console.log(this);
})
}
}
foo.fn() //{fn: ƒ}
this的优先级
我们常常把通过call、apply bind、new对this进行绑定的情况称为显式绑定,而把根据调用关系确定this指向的情况称为隐式绑定。那么易式绑定和隐式绑定谁的优先缓更高呢?
function foo(a) {
console.log(this.a);
}
const obj1 = {
a: '1',
foo:foo
}
const obj2 = {
a: '2',
foo:foo
}
obj1.foo.call(obj2) // 2
obj2.foo.call(obj1) // 1
输出分别为2、1,也就是说,call、apply的显式绑定一般来说优先级更高。
function foo(a){
this.a = a
}
const obj1 = {}
let bar = foo.bind(obj1)
bar(2)
console.log(obj1.a); // 2
上述代码通过 bind 将 bar 函数中的this 绑定为obj1对象。执行bar(2)后,obj1.a值为2,即执行 bar(2)后,obj1 对象为{a: 2}。 当再使用bar作为构造函数时,例如执行以下代码,则会输出3。
let bar2 = new foo(3)
console.log(bar2.a); // 3
bar 函数本身是通过 bind 方法构造的函数,其内部已经将 this 绑定为 obj1,当它再次作为构造函数通过 new 被调用时,返回的实例就已经与 obj1 解绑了。也就是说,new 绑定修改了 bind 绑定中的 this 指向,因此new 绑定的优先级比显式 bind 绑定的更高。
再来看以下示例:
function foo() {
return a=>{
console.log(this.a);
}
}
const obj1 = {
a: 1
}
const obj2 = {
a: 2
}
const bar = foo.call(obj1)
console.log(bar.call(obj2));
以上代码输出为1。由于foo中的this绑定到了obj1上,所以bar中的this也会绑定到obj1上,箭头函数中的this无法修改。
var a =123
const foo = ()=>(a)=>{
console.log(this.a);
}
const obj1 ={
a:1
}
const obj2 ={
a:2
}
let bar = foo.call(obj1) // 123
console.log(bar.call(obj2));
上边代码输出的就是123。
const a =123
const foo = ()=>(a)=>{
console.log(this.a);
}
const obj1 ={
a:1
}
const obj2 ={
a:2
}
let bar = foo.call(obj1) // undefined
console.log(bar.call(obj2));
把var换成了const输出就变成了undefined,因为const声明的变量不会挂着到window全局对象上,因此this指向window上时也就找不到哦啊变量a了。
以上就是this指向的问题,有不明白的欢迎提问。