【JavaScript】this 指向问题

this 指向

在全局环境中,this 指向顶层对象;在函数中,this 的指向取决于函数的调用方式

  1. 直接调用:this 指向顶层对象。
var name = 'superman';

function showName() {
    console.log(this); // Window {window: Window, self: Window, document: document, …}
    console.log(this.name); // superman
}

showName(); // 直接调用, this 指向顶层对象; 严格模式下, this 为 undefiend
  1. 通过指定对象调用:this 指向该对象。
const obj = {
    name: 'superman',
    objFun() {
        console.log(this); // {name: 'superman', objFun: ƒ}
        console.log(this.name); // superman
    },
};

obj.objFun(); // 通过 obj 调用, this 指向 obj
function fun() {
    console.log(this); // {name: 'superman', objFun: ƒ}
    console.log(this.name); // superman
}

const obj = { name: 'superman' };
obj.objFun = fun;

obj.objFun(); // 通过 obj 调用, this 指向 obj
  1. 通过 new 调用:this 指向新创建的实例对象:
function Person(name) {
    this.name = name;
}

const person = new Person('superman'); // new 调用, this 指向实例对象

console.log(person); // Person { name: 'superman' }
console.log(person.name); // superman



修改 this 指向

call & apply

Function.prototype.call 和 Function. prototype.apply 用于改变函数内部的 this 指向,并立即调用该函数。

window.name = 'window';
const obj1 = { name: 'sven' };
const obj2 = { name: 'anne' };

const getName = function () {
    console.log(this.name);
};

getName(); // window
getName.call(obj1); // sven
getName.call(obj2); // anne

call 和 apply 的区别在于传入参数的形式不同:

apply 接受两个参数,第一个参数指定了函数体内 this 的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。

call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。

function sum(a, b) {
    console.log(this);
    return a + b;
}
sum(10, 20); // [object Window]

const obj = { name: 'obj' };
sum.call(obj, 10, 20); // { name: 'obj' }
sum.apply(obj, [10, 20]); // { name: 'obj' }

对于 call 和 apply 方法的第 1 个参数:
如果是 NumberStringBoolean,包装类会将其转为对象。
如果是 undefinednull,它们没有包装类,无法转成对象,this 指向 window

function fun() {
    console.log(this);
}

fun.call(true); // Boolean { true }
fun.call(null); // [object Window]

当使用 call 或者 apply 的时候,如果我们传入的第一个参数为 null,函数体内的 this 会指向顶层对象:

var func = function (a, b, c) {
    alert(this === window); // true
};

func.apply(null, [1, 2, 3]);

但如果是在严格模式下,函数体内的 this 会为 null

var func = function (a, b, c) {
    'use strict';
    alert(this === null); // true
};

func.apply(null, [1, 2, 3]);

有时候我们使用 call 或者 apply 的目的不在于指定 this 指向,而是另有用途,比如借用其他对象的方法。那么我们可以传入 null 来代替某个具体的对象:

Math.max.apply(null, [1, 2, 5, 3, 4]); // 5

bind

Function.prototype.bind 用于硬性绑定函数的 this 指向,且不可更改。

bind 不会执行函数,而是修改 this 指向后,返回一个新的函数。

const newFun = fun.bind(obj);

通过 bind 方法获取的新函数,无法修改 this 指向:

function fun() {
    console.log('this', this);
}

const obj1 = { name: 'obj1' };
const biFun = fun.bind(obj1); // 使用 bind 硬绑定
biFun(); // this { name: 'obj1' }

const obj2 = { name: 'obj2' };
biFun.call(obj2); // this { name: 'obj1' }

此时就算使用 callapply 方法也修改不了函数 biFun 的 this 指向。



this 指向的优先级

this 指向的优先级:① 通过 new 调用 > ② 显示绑定 (bind > call、apply) > ③ 通过指定函数调用 > ④ 直接调用

function Person(name) {
    this.name = name;
    console.log(this);
}

const obj = {};

const biPerson = Person.bind(obj);

biPerson('obj'); // { name: 'obj' } -- 硬绑定
const newObj = new biPerson('newObj'); // Person {name: 'newObj'} -- 构造函数



特殊函数内的 this 指向

立即执行函数 IIFE

立即执行函数 IIFE 说白了就是直接调用一个函数,this 指向顶层对象。

const obj = {
    outer() {
        console.log('outer', this); // outer { outer: [Function: outer] }

        // 立即执行函数 -- 直接调用, this 指向顶层对象
        (function () {
            console.log('inner', this); // inner [object Window]
        })();
    },
};

obj.outer();

闭包

其实就是把内部函数赋值到外部了,说白了也是直接调用一个函数,this 指向顶层对象。

const obj = {
    outer() {
        console.log('outer', this); // outer { outer: [Function: outer] }

        return function () {
            console.log('inner', this); // inner [object Window]
        };
    },
};

// 闭包 -- 直接调用, this 指向顶层对象
obj.outer()();

箭头函数

箭头函数会使用上一级的 this。

const obj = {
    // 箭头函数 -- 使用上一级的 this
    objFun: () => {
        console.log('this', this); // this [object Window]
    },
};

obj.objFun();

因为箭头函数没有自己的 this,所以 call & apply、bind 方法对箭头函数都不管用。⭐

// 创建箭头函数
const arrFun = () => {
    console.log('this', this);
};

const obj = {};
const newArrFun = arrFun.bind(obj); // 使用 bind 硬绑定

newArrFun(); // this [object Window]



面试题

var name = 'window';

var obj1 = {
    name: 'obj1',

    fn1() {
        console.log(this.name);
    },

    // 箭头函数, 使用上一级的 this
    fn2: () => console.log(this.name),

    fn3() {
        // 闭包
        return function () {
            console.log(this.name);
        };
    },

    fn4() {
        // 箭头函数, 使用上一级的 this
        return () => console.log(this.name);
    },
};

var obj2 = { name: 'obj2' };

obj1.fn1(); // 通过 obj1 调用, this 指向 obj1
obj1.fn1.call(obj2); // 通过 call 使 this 指向 obj2

obj1.fn2(); // 箭头函数, 此时上一级的 this 指向 window
obj1.fn2.call(obj2); // call 对箭头函数无效, this 仍然指向 window

obj1.fn3()(); // 直接调用闭包传递出来的函数, this 指向 window
obj1.fn3().call(obj2); // 通过 call 使 this 指向 obj2
obj1.fn3.call(obj2)(); // 直接调用闭包传递出来的函数, this 指向 window

obj1.fn4()(); // 箭头函数, 此时上一级的 this 指向 obj1
obj1.fn4().call(obj2); // call 对箭头函数无效, this 仍然指向 obj1
obj1.fn4.call(obj2)(); // 使用 call 使箭头函数上一级的 this 指向 obj2

function Foo() {
    getName = function () {
        console.log(1);
    };
    return this;
}

Foo.getName = function () {
    console.log(2);
};

Foo.prototype.getName = function () {
    console.log(3);
};

var getName = function () {
    console.log(4);
};

function getName() {
    console.log(5);
}

Foo.getName(); //  2
getName(); // 4  --  变量 getName 覆盖了函数 getName

Foo().getName(); // 1 -- 这里重写了 window.getName 方法
getName(); // 1

new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JS.Huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值