你不知道的js—this全面解析

644 阅读4分钟

this不容易理解,但是非常非常非常重要。有问题请评论,作者必回。

概述

每个函数的this是在调用时被绑定的,完全取决于函数的调用位置(也就是函数的调用方法)。

函数被调用的几种场景

  1. 函数正常被调用;
  2. 函数作为方法被调用;
  3. 函数使用call/apply强行调用;
  4. 函数作为构造函数被调用; 我们会结合这四种使用场景去分析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个关键过程:

  1. 创建一个全新的对象。
  2. 新对象会被执行[[原型]]连接。
  3. 新对象会绑定到函数调用的this。
  4. 如果函数没有返回其他对象,那么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)

相关文章

上一篇你不知道的js—关于this