this

本文最后更新于:1 年前

this

根据函数的调用方式的不同,this 会指向不同的对象:
这些this的指向,是当我们调用函数的时候确定的。调用方式的不同决定了this的指向不同,一般指向我们的调用者。

调用方式 this指向
普通函数调用 window
定时器函数 window
立即执行函数 window
构造函数调用 实例对象 原型对象里面的方法也指向实例对象
对象方法的调用 该方法所属对象
事件绑定方法 绑定事件对象
  • 全局this -> window
  • 构造函数的this指向实例化对象
  • 预编译函数this -> window
  • bind/call/apply 改变this指向,this 指向指定的那个对象
  • 箭头函数没有 this,它的 this 只取决于包裹箭头函数的第一个普通函数的 this
  • 以函数的形式(包括普通函数、定时器函数、立即执行函数)调用时,this 的指向永远都是 window。
  • 以事件绑定函数的形式调用时,this 指向绑定事件的对象

首先来看几个使用场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var a = 1;
var obj = {
a: 2,
foo: foo
};

function foo() {
console.log(this.a);
}

// 直接调用方式
foo();//1 默认绑定this指向window

// 作为对象属性调用
obj.foo();//2 隐式绑定this指向obj

// 使用 new 关键字
var b = new foo();// undefined 构造函数this指向实例

下面对这几种用法进行分析:

  • 直接调用 foo(称为默认绑定)不管 foo 函数放在哪里,this 都是全局对象(window、global)

  • 对于 obj.foo 这种用法(称为隐式绑定)只需要记住,谁调用了函数,this 就指向谁

  • 对于 new 的方式来说,this 永远被绑定在了实例上,任何方式都无法改变 this

    函数内部的this

    普通函数(未实例化)内部的this默认指向window

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function test(b) {
    this.d = 3 //window.d=3
    let a = 1
    function c() {}
    }
    test(123)
    console.log(d) //3
    console.log(this.d) //3
    console.log(window.d) //3
    // AO = {
    // arguments: [123],
    // this:window
    // b:undefined
    // 123
    // a:undefined
    // c:function c() {}
    // }

    构造函数内部的this

    构造函数实例化之后this指向实例化对象
    对于 new 的方式来说,this 永远被绑定在了实例上,任何方式都无法改变 this

    预编译函数this

    预编译函数this指向window

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function Test() {
    //new的时候构造函数内部产生成this,然后生成__proto__被赋值构造函数的prototype,生成原型链
    // let this = {
    // __proto__: Test.prototype
    // }
    this.name = '123'
    }
    let test = new Test()
    // new 之前this指向window
    // AO = {
    // this: window
    // }

    // new 之后this接收属性和原型对象,生成原型链
    // AO = {
    // this:{
    // name:'123', //接收属性
    // __proto__: Test.prototype //生成原型链
    // }
    // }
    //new的时候产生GO
    // GO = {
    // Test: function test() {...}
    // test:{ //new 之后
    // name:'123', //接收属性
    // __proto__: Test.prototype //生成原型链
    // }
    // }

    bind/call/apply

    bind/call/apply调用时this 指向指定的那个对象

    bind 和 call/apply 的异同如下:

  • 相同:都会修改函数中的 this,并将 this 指向传入的对象

  • 不同

    • bind:修改 this 后,返回一个硬编码的新函数,需要手动执行新函数
    • call/apply:不返回新函数,修改 this 后会立即执行原函数

call和apply传递的参数不一样,call传递参数arg1,arg2…形式,apply必须数组形式[arg]
看明白下面的代码,bind 和 call/apply 的区别就清楚了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function foo() {
console.log(this.a);
}

var obj1 = { a: 1, foo: foo };
var obj2 = { a: 2 };
var obj3 = { a: 3 };
var a = 4;

var bar = obj1.foo;
// call 将 this 指向 obj2,并立即执行原函数
bar.call(obj2); // => 2
obj1.foo(); // => 1

var baz = bar.bind(obj3);
// bind 修改 this 后,返回硬编码的新函数,需要手动执行
baz(); // => 3
// 调用 bar 并不等价于调用 obj1.foo,而是等价于直接调用 foo
// 因此 this 丢失(隐式绑定 this 丢失),应用默认绑定规则
bar(); // => 4

apply 方法的第一个参数是作为函数上下文的对象,此时函数里的 this 便指向了构造函数Person。
所以实例对象p可以借用构造函数Person里边的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Person() {
this.name = '张三'
this.age = 18
}
function Programmer() {
Person.apply(this)
this.work = 'Programmer'
}
let p = new Programmer()
console.log(p) //Programmer {name: '张三', age: 18, work: 'Programmer'}

// function Person(name, age) {
// this.name = name
// this.age = age
// }
// function Programmer(name, age) {
// Person.apply(this, [name, age])
// this.work = 'Programmer'
// }
// let p = new Programmer('张三', 18)
// console.log(p) //Programmer {name: '张三', age: 18, work: 'Programmer'}
1
2
3
4
5
6
7
8
9
10
function foo() {
bar.apply(null, arguments) //相当于bar.call() -> bar(arguments)
// bar.call()
}
function bar() {
console.log(arguments)
}
foo(1, 2, 3, 4, 5)
//Arguments(5) [1, 2, 3, 4, 5, callee: ƒ, Symbol(Symbol.iterator): ƒ]

对于 bind / call / apply,如果第一个参数为 null、undefined 或 不传,那么就会应用默认绑定规则,也就是函数中的 this 会泄漏到全局对象(window, global)上。
为了防止这个副作用,可以通过传一个空对象 ∅ = Object.create(null) 来保护全局对象。

箭头函数的 this

首先要知道,箭头函数没有 this,它的 this 只取决于包裹箭头函数的第一个普通函数的 this。举个例子:

1
2
3
4
5
6
7
8
9
function foo() {
return () => {
return () => {
console.log(this);
};
};
}

foo()(); // => window

这个例子中,由于包裹箭头函数的第一个普通函数是 foo,所以很显然结果为 window。
另外,对箭头函数使用 call、apply、bind 这类函数是无效的(箭头函数没有 this 嘛,因此绑定上下文的函数无法生效)。

箭头函数中,this是静态的,this始终指向函数声明时所在作用于下的this的值

其他方面的 this

隐式绑定中 this 丢失

给隐式绑定起别名后,直接调用别名就会出现 this 丢失的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
console.log(this.a);
}

var obj = { a: 1, foo: foo };
var a = 2;
var bar = obj.foo;

// 隐式绑定
obj.foo(); // => 1

// 给隐式绑定起别名后,直接调用别名就会出现 this 丢失的问题
bar(); // => 2

对象内部函数嵌套函数 this 丢失

1
2
3
4
5
6
7
8
9
10
const team = {
members:["Henry","Elyse"],
teamName:"es6",
teamSummary:function(){
return this.members.map(function(member){
return `${member}隶属于${this.teamName}小组`; // this不知道该指向谁了
})
}
}
console.log(team.teamSummary());//["Henry隶属于undefined小组", "Elyse隶属于undefined小组"]

teamSummary函数里面又嵌了个函数,这导致内部的this的指向发生了错乱。
那如何修改:

方法一、let self = this

1
2
3
4
5
6
7
8
9
10
11
const team = {
members:["Henry","Elyse"],
teamName:"es6",
teamSummary:function(){
let self = this;
return this.members.map(function(member){
return `${member}隶属于${self.teamName}小组`;
})
}
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

方法二、bind函数

1
2
3
4
5
6
7
8
9
10
11
const team = {
members:["Henry","Elyse"],
teamName:"es6",
teamSummary:function(){
return this.members.map(function(member){
// this不知道该指向谁了
return `${member}隶属于${this.teamName}小组`;
}.bind(this))
}
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

方法三、 箭头函数

1
2
3
4
5
6
7
8
9
10
11
const team = {
members:["Henry","Elyse"],
teamName:"es6",
teamSummary:function(){
return this.members.map((member) => {
// this指向的就是team对象
return `${member}隶属于${this.teamName}小组`;
})
}
}
console.log(team.teamSummary());//["Henry隶属于es6小组", "Elyse隶属于es6小组"]

隐式绑定中多次调用

隐式绑定多次调用时,this 的指向只取决于最后一次

1
2
3
4
5
6
7
8
9
10
function foo() {
console.log(this.a);
}

var obj3 = { a: 3, foo: foo };
var obj2 = { a: 2, obj3: obj3 };
var obj1 = { a: 1, obj2: obj2 };

// 多次调用时,this 的指向只取决于最后一次
obj1.obj2.obj3.foo(); // => 3

严格模式下的 this

严格模式主要影响 this 的默认绑定规则,但是不影响函数调用中的 this。来看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
var a = 1;

function foo() {
'use strict';

console.log(this.a);
}

foo(); // TypeError: Cannot read property 'a' of undefined

// 由于严格模式的限制,foo 函数中 this 的默认绑定规则失效了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = 1;

function foo() {
console.log(this.a);
}

(function () {
'use strict';

foo(); // => 1
})();

// foo 函数中不是严格模式,其中 this 的默认绑定规则仍有效
// 并且调用时,其 this 不受外界严格模式的影响

通常来说,不应该在代码中混用 strict 模式和非 strict 模式。上述情况主要出现在调用第三方库时,其严格程度和你的代码可能有所不同,因此需要注意此类兼容细节。

this 总结

  • 当多个规则同时出现,就需要根据优先级来决定 this 最终指向哪里。优先级情况如下:new 关键字 > bind 这类函数(显示绑定) > obj.foo() 这种方式(隐式绑定) > foo() 直接调用
  • 箭头函数没有自己的 this,仅继承外层函数的,因此无法通过 bind/call/apply 修改。
  • 隐式绑定中,要注意 this 丢失的问题
  • 隐式绑定中,多次调用时 this 只取决于最后一次
  • 严格模式会使 this 的默认绑定失效,但不影响函数调用中的 this
1
2
3
4
5
6
7
8
9
10
   let f =
(function f() {
return '1'
},
function g() {
return 2
})
//f=( f(), g() ) -> f=g()
console.log(f) //ƒ g() {return 2}
console.log(typeof f) //function
1
2
3
4
5
6
7
8
9
let f = (function f() {
return '1'
},
function g() {
return 2
})()
//f=( f(), g() )() -> f=g()() -> f()=g()
console.log(f) // 2
console.log(typeof f) //number
1
2
3
4
5
6
7
8
9
var a = '1'
function test() {
var a = '2'
this.a = '3' //这里this默认是全局window
console.log(a)
}
test() //2
new test() //2
console.log(a) //3 打印的是全局的a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a = 5
function test() {
a = 0
console.log(a)
console.log(this.a)
var a
console.log(a)
}
test() // 0 5 0
//0和0是自己有的,this.a是window上的
new test() //0 undefined 0
//0和0是自己有的,实例化之后内部产生的this,但是没有给this加上a这个属性

// var b = 5
// function test2() {
// b = 0
// console.log(this)
// var b
// }
// new test2() //test2 {} //没有任何属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Test(a, b, c) {
let d = 0
this.a = a
this.b = b
this.c = c
function e() {
d++
console.log(d)
}
this.f = e
//new之后
// let this={
// f:function () {d++;console.log(d)}
// }
// return this //隐式return this形成闭包
}
// AO = {
// d: undefined, //1
// 0 //4
// }
let test1 = new Test()
test1.f() //1
test1.f() //2
let test2 = new Test()
test2.f() //1

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!