this不容易理解,但是非常非常非常重要。有问题请评论,作者必回。
概述
每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。
函数被调用的几种场景
- 函数正常被调用;
- 函数作为方法被调用;
- 函数使用call/apply强行调用;
- 函数作为构造函数被调用; 我们会结合这四种使用场景去分析this指向。
默认绑定(函数正常被调用)
打印2。证明此时this指向window。
function foo() {
console.log(this.a); // 2
}
var a = 2;
foo()
严格模式呢?
严格模式下,this指向undefined。es6中的class默认是严格模式。
function foo() {
'use strict'
console.log(this); // undefined
}
var a = 2;
foo()
间接调用呢?
打印2。严格模式只会影响直接调用时的this指向。
function foo() {
console.log(this.a) // 2
}
var a = 2;
(function (){
'use strict'
foo()
})()
结论
函数正常被调用时,this指向window;严格模式下指向undefined。
隐式绑定(函数作为方法被调用)
打印1。说明this指向obj。
function foo() {
console.log(this.a)
}
const obj = {
a: 1,
foo: foo,
}
obj.foo();
多层调用 打印1。多层调用,this指向最近的一层
function foo() {
console.log(this.a)
}
const obj = {
a: 1,
foo: foo,
}
const obj2 = {
a: 2,
obj: obj,
}
obj2.obj.foo();
方法赋值后,被调用 打印2。这样间接赋值,fn就是foo本身,和默认绑定一样。
function foo() {
console.log(this.a)
}
const obj = {
a: 1,
foo: foo,
}
var a = 2;
var fn = obj.foo;
fn()
作为函数被调用 打印2。作为参数fn就是foo本身,和默认绑定一样。
function foo() {
console.log(this.a)
}
function bar(fn) {
fn()
}
var a = 2;
bar(foo)
结论
函数作为方法被调用,指向距离调用函数最近的对象。
显式绑定
使用apply、call、bind强行绑定this指向,同时给原函数传递参数。
apply、call
使用call打印3,使用apply打印4。证明this分别指向了obj1、obj2。
apply、call区别是在参数上:apply第二个参数是数组、call所有参数直接排列。
function foo(a, b) {
console.log(this.c + a + b)
}
var obj1 = {
c: 1,
}
var obj2 = {
c: 2,
}
foo.call(obj1, 1, 1)
foo.apply(obj2, [1, 1])
bind
打印3。显然bind和apply、call有明显区别:bind是预绑定,绑定完后并没有指向,而是返回一个function,等待再次被调用。不仅改变了this指向,还能把两次调用的参数整合起来。 大家可以去了解一下函数柯里化。
function foo(a, b) {
console.log(this.c + a + b)
}
var obj = {
c: 1,
}
var bindFoo = foo.bind(obj, 1);
bindFoo(1)
第一个参数不是对象呢?
第一个参数是数字、字符串、布尔时,this会分别指向包装类Number、String、Boolean。参数是undefined、null时,指向window。
foo.call(1, 1, 1)
foo.call(true, 1, 1)
foo.call('', 1, 1)
foo.call(undefined, 1, 1)
foo.call(null, 1, 1)
结论
apply、call、bind都能改变this指向,不过使用方法不同。
new绑定
打印1。this指向后new完生成的新对象。
function foo() {
this.a = 1;
}
const obj = new foo();
console.log(obj.a)
new的4个关键过程:
- 创建一个全新的对象。
- 新对象会被执行[[原型]]连接。
- 新对象会绑定到函数调用的this。
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
目前只需要搞懂1、3、4步。第2步后续会单独写一篇文章,是js非常核心的内容。
结论
函数被new调用时,this指向后new完生成的新对象。
优先级
下面我们讨论一下,如果同时出现两种情况,优先级是怎样的? 首先默认绑定优先级最低,不需要再对比了。
隐式和显式比较
打印2。证明显式绑定 > 隐式绑定。
function foo() {
console.log(this.a)
}
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 2
}
obj.foo.call(obj2)
new和隐式比较
观察打印结果。证明new绑定 > 隐式绑定。
function foo() {
console.log(this.a); // undefined
this.a = 2;
}
var obj = {
a: 1,
foo: foo
}
const newObj = new obj.foo();
console.log(newObj) // { a: 2 }
new和显式比较
new和apply、call没办法一起用。我们用bind做实验: 观察打印结果。证明new绑定 > 显式绑定。
function foo() {
console.log(this.a); // undefined
this.a = 2;
}
var obj = {
a: 1,
}
const bindFoo = foo.bind(obj)
const newObj = new bindFoo();
console.log(newObj) // { a: 2 }
优先级总结
new绑定 > 显式绑定 > 隐式绑定 > 默认绑定
特殊情况-箭头函数
上面介绍函数中的this是在执行时确定的。箭头函数相反,是在定义时就确定了this,且不被显式绑定影响,不能使用new绑定。 打印3。说明bar中this指向了zar中的this。
function foo() {
function zar() {
const bar = () => {
console.log(this.a);
}
bar()
}
zar.call({a: 3})
}
var a = 1;
foo.call({a: 2})
我们把zar也改成箭头函数呢? 打印2。证明:箭头函数中this会,一层一层的向外找,直到找到最近的this。
function foo() {
const zar = () => {
const bar = () => {
console.log(this.a);
}
bar()
}
zar.call({a: 3})
}
var a = 1;
foo.call({a: 2})
都打印true。因为new foo()时,箭头函数创建,this确定。之后即使obj使用隐式/显式修改,this依旧指向newFoo,不会变。本例取自js忍者秘籍第二版
function foo() {
this.a = 1;
this.getThis = () => this;
}
const newFoo = new foo();
const obj = {
foo: newFoo.getThis
}
console.log(obj.foo() === newFoo)
console.log(obj.foo.call({}) === newFoo)