JavaScript 中“this”可以指向的 8 个不同的地方

456199229c19a825394b3573079935c4.png

英文 | https://blog.bitsrc.io/where-exactly-does-this-point-to-in-javascript-2be1e3fad1fd

函数中的 this 在调用时是绑定的,完全取决于函数的调用位置(即函数的调用方式)。要知道 this 指向什么,你必须知道相关函数是如何被调用的。

1、Global context

在非严格模式和严格模式下,this 指的是顶级对象(浏览器中的窗口)。

this === window // true
'use strict'
this === window;
this.name = 'dog';
console.log(this.name); // dog

2、函数上下文

var name = 'window';
var doSth = function(){
    console.log(this.name);
}
doSth(); // 'window'

您可能会错误地认为 window.doSth() 被调用并因此指向 window.doSth()。尽管在这种情况下 window.doSth 确实等于 doSth。名称等于 window.name。这是因为在 ES5 中,全局变量挂载在顶层对象(浏览器为 Window)中。

事实上,事实并非如此。

let name2 = 'window2';
let doSth2 = function(){
    console.log(this === window);
    console.log(this.name2);
}
doSth2() // true, undefined

本例中,let 不给顶层对象添加属性(浏览器为Window);window.name2 和 window.doSth 都是未定义的。

在严格模式下,正常函数中的 this 行为不同,如未定义。

'use strict'
var name = 'window';
var doSth = function(){
    console.log(typeof this === 'undefined');
    console.log(this.name);
}
doSth(); // true,// Cannot read properties of undefined (reading 'name')

这称为默认绑定。熟悉 call 和 apply 的读者会使用这个类比:

doSth.call(undefined);
doSth.apply(undefined);

效果是一样的。调用 apply 所做的一件事是更改 this 所指的函数中的第一个参数。第一个参数是 undefined 或 null,在非严格模式下指向 window。

在严格模式下,它指的是第一个参数。

经常有这样的代码(回调函数),其实是正常的函数调用模式。

var name = 'dog';
setTimeout(function(){
    console.log(this.name);
}, 0);


setTimeout(fn | code, 0, arg1, arg2, ...)


fn.call(undefined, arg1, arg2, ...);

3、对象函数(方法)调用模式

var name = 'window';
var doSth = function(){
    console.log(this.name);
}
var student = {
    name: 'dog',
    doSth: doSth,
    other: {
        name: 'other',
        doSth: doSth,
    }
}


student.doSth(); // 'dog'
// call like this
student.doSth.call(student);


student.other.doSth(); // 'other'
// call like this
student.other.doSth.call(student.other);

但是,经常会出现将对象中的函数分配给变量的情况。这实际上又是一个普通函数,所以使用普通函数的规则(默认绑定)。

var studentDoSth = student.doSth;
studentDoSth(); // 'window'
// call like this :
studentDoSth.call(undefined);

4、call(), apply(), bind()

上面提到了call、apply,这里有一个详细的解释。Function.prototype.call()。

fun.call(thisArg, arg1, arg2, ...)

根据参数thisArg的描述可知,call就是将函数中的this改为指向thisArg并执行该函数,这让JS灵活了很多。

在严格模式下,thisArg 是一个原始值,它是一个值类型,即原始值。不会被包裹到一个对象中。例如:

var doSth = function(name){
    console.log(this);
    console.log(name);
}
doSth.call(2, 'dog'); // Number{2}, 'dog'
var doSth2 = function(name){
    'use strict';
    console.log(this);
    console.log(name);
}
doSth2.call(2, 'dog'); // 2, 'dog'

5、构造函数调用模式

function Student(name){
    this.name = name;
    console.log(this); // {name: 'dog'}


    // return this;
}
var result = new Student('dog');

由此可知,调用new操作符时,this指向的是新生成的对象。

6、原型链中的调用模式

function Student(name){
    this.name = name;
}
var s1 = new Student('dog');
Student.prototype.doSth = function(){
    console.log(this.name);
}
s1.doSth(); // 'dog'

这是对象的方法调用模式。自然,它指向生成的新对象。如果对象继承自其他对象。它还将通过原型链进行查找。

7、箭头函数调用模式

我们先来看看箭头函数和普通函数的重要区别:

它没有自己的 this、super、arguments 和 new.target 绑定。

你不能使用 new 来调用。

没有原型对象。

this 的绑定不能更改。

形参名称不能重复。

箭头函数中没有 this 绑定,它的值必须通过查找作用域链来确定。如果箭头函数包含在非箭头函数中,则 this 绑定到最近的非箭头函数的 this 上,否则 this 的值设置为全局对象。例如:

var name = 'window';
var student = {
    name: 'dog',
    doSth: function(){
        // var self = this;
        var arrowDoSth = () => {
            // console.log(self.name);
            console.log(this.name);
        }
        arrowDoSth();
    },
    arrowDoSth2: () => {
        console.log(this.name);
    }
}
student.doSth(); // 'dog'
student.arrowDoSth2(); // 'window'

其实,等价于箭头函数外的this,也就是普通函数在箭头函数之上缓存的this。如果没有正常功能,则为全局对象(浏览器中的窗口)。

也就是说,不可能通过call、apply、bind来绑定箭头函数的this(它本身没有this)。而call、apply、bind可以绑定缓存箭头函数上面的普通函数的this。

例如:

var student = {
    name: 'dog',
    doSth: function(){
        console.log(this.name);
        return () => {
            console.log('arrowFn:', this.name);
        }
    }
}
var person = {
    name: 'person',
}
student.doSth().call(person); // 'dog'  'arrowFn:' 'dog'
student.doSth.call(person)(); // 'person' 'arrowFn:' 'person'

8、DOM事件处理函数调用

<button class="button">onclick</button>
<ul class="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
    var button = document.querySelector('button');
    button.onclick = function(ev){
        console.log(this);
        console.log(this === ev.currentTarget); // true
    }
    var list = document.querySelector('.list');
    list.addEventListener('click', function(ev){
        console.log(this === list); // true
        console.log(this === ev.currentTarget); // true
        console.log(this);
        console.log(ev.target);
    }, false);
</script>

onclick 和 addEventListener 是指向绑定事件的元素。

总结

如果要确定一个正在运行的函数的 this 绑定,则需要找到该函数的直接调用位置。找到它之后,我们可以应用以下四个规则来确定 this 的绑定对象。

  • new call:绑定新创建的对象,注意:显示返回函数或对象,返回值不是新创建的对象,而是显式返回的函数或对象。

  • call或apply(或bind)call:在严格模式下,绑定到指定的第一个参数。在非严格模式下,null 和 undefined 指向全局对象(浏览器中的窗口),其余值指向由 new Object() 包裹的对象。

  • 对对象的函数调用:绑定到该对象。

  • 普通函数调用:在严格模式下绑定到 undefined,否则绑定到全局对象。

ES6 中的箭头函数:以上四个标准绑定规则将不会用到,但这会根据当前词法范围来确定。

具体来说,箭头函数会继承外层函数并调用 this 绑定(不管 this 绑定什么),如果没有外层函数,则绑定到全局对象(浏览器中的窗口)。

DOM事件函数:一般指向事件绑定的DOM元素,但在某些情况下是绑定到全局对象。

今天的文章内容就到这里节省了,感谢您的阅读,希望你能学到一些新东西,祝编程愉快!

学习更多技能

请点击下方公众号

80bbbd23fca2826d68cb5df388960066.gif

550873b8de8743d5743fb1f30307c035.png

f4d4c4e8905d5d6e687fb18fd2b34f3b.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值