js中this究竟指向哪里?现在终于搞定了!

thsi据说是js中最复杂的机制,不过相信我,看完这篇文章,小白也能跨过js中this这座大山
this是个代词,代指某个东西,在我们js中,他是某作用域的代名词

this的绑定规则有六种方式,分别为默认绑定、隐式绑定、隐式丢失、显示绑定、new绑定、箭头函数,下面就开始一一对其进行介绍

一、默认绑定

函数在哪个词法作用域中生效,this就指向哪里,你可以理解为this最终指向了全局

在下面引例之前我需要先解释下,函数本身是没有词法作用域([[scope]])的,它的词法作用域是由函数声明的位置确定的 或者this坚决不能访问词法作用域内部

比如我们看下面这个例子

javascript 复制代码
function foo(){
     var a = 1
     console.log(this.a);
}
foo() // undefined

foo中的this指向了foo的词法作用域,但是由于函数是没有词法作用域的,它的词法作用域就是函数声明的位置---全局确定的,因此this代表了全局,全局中没有a这个属性,因此输出undefined(非报错是因为这是对象,找不到也是undefined)

再来一个

javascript 复制代码
function foo() {
    var a = 2
    this.bar()
}
function bar() {
    var b = 3
    console.log(this.b)
}
foo() // undefined

foo声明在全局中,因此this指向了全局,去全局寻找bar这个属性,找到后执行bar这个函数,bar声明在全局中,因此this指向了全局,全局中没有b这个值,找不到后输出undefined

再来看个正常的

javascript 复制代码
var b = 1
function bar() {
    console.log(this.b)
}
bar() // 1

bar声明在全局中,因此bar中的this指向了全局,全局中恰好有b这个值,打印出1

再来看个误导性较强的

scss 复制代码
function foo(){
    var b = 1
    bar()
}
function bar(){
    console.log(this.b);
}   
foo() // undefined

调用foo也就是调用bar,bar中this因为bar是声明在全局中,所以指向了全局,全局中没有b这个值所以输出undefined,如果大家认为是1,你就是把bar的最终词法作用域搞错了,并不是看他的调用环境

我们改一下上面的代码

scss 复制代码
var b = 2
function foo(){
    var b = 1
    bar()
}
function bar(){
    baz()
} 
function baz(){
    console.log(this.b)
}
foo() // 2

baz中this指向了全局,因为他就是声明在全局中,因此直接去了全局找b,输出2,与foo没有关系

我再改下

scss 复制代码
var b = 2
function foo(){
    var b = 1
    bar()
    function bar(){
        baz()
    } 
    function baz(){
        console.log(this.b)
    }
}
foo() // 2

baz中this指向了foo,因为他在foo中声明了,但是foo并没有自身的词法作用域,它的词法指向的是全局(因为他声明在全局中),因此最终打印出2,而非1

这里还有个小知识:全局定义变量相当于是往window这个全局对象挂属性,当然得是在浏览器环境中,node中全局对象是global,你可以试试var a = 1然后在浏览器输出window.a

二、隐式绑定

当一个函数被一个对象所引用(非调用),再次调用时,函数中的this会指向对象

直接看下面这个例子

javascript 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
    // 相当于下面这样
    // foo: function foo(){
    //	console.log(this.a)
    // }
}
obj.foo() // 2

先解释下这段代码:obj中有个foo属性,然后这个属性的值又是一个函数体,就相当于foo这个函数体写在了foo这个key里面的value中,最后obj.foo()中obj.foo就是相当于foo这个函数体,然后()就是一个调用的意思

根据隐式绑定的规则,foo这个函数被obj这个函数引用,等你obj再次调用这个函数时,函数中的this最终指向了obj这个对象,而非全局,这就是隐式绑定,因此最终打印2

来个误导性强的

javascript 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 3,
    foo: foo()
}
obj.foo // undefined

这里对象中并非是引用这个foo函数,因为有个括号,这是调用,所以不符合隐式绑定规则,只能是默认绑定,this还是指向了全局,找不到a的值,输出undefined

三、隐式丢失

隐式丢失是隐式绑定的一种,当一个函数被多个函数链式调用时,函数中的this最终指向引用函数的对象

直接看下面这个例子

css 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2,
    foo: foo
}
var obj2 = {
    a: 3,
    obj: obj
}
obj2.obj.foo()

其实这个绑定规则就是在隐式绑定的规则上了多了对象进行了一个链式的调用

这段代码如何理解呢?我们有两种理解关于最后一行代码,一种是obj2.obj就是obj这个对象,然后obj这个对象去obj.foo(),就是成了一个隐式绑定,this会指向obj。另一种是obj2.(obj.foo()),obj.foo()就是foo(),然后obj2.foo(),相当于obj2调用了这个函数,this会指向obj2。两种理解好像都没问题,其实大部分人应该就是第一种理解,其实最终确实是第一种理解,换句话说,就近原则,最终输出2

来个误导性强的

javascript 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo() // undefined

如果你理得清关系,就清楚这里并没有引用这个函数foo,因此只能是默认绑定规则,this指向了全局,全局没有a这个变量因此输出undefined

如果我就是想要把这个this指向obj,但是又不能引用这个函数,我们如何去改变这个this的指向呢,那就是下面显示绑定的作用了

四、显示绑定

显示绑定就是使用call、apply或bind方法人为干预他,让他代指谁

call

javascript 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo.call(obj) // 2

这个call的用法就是把foo的this指向了obj

当foo中有参数时如何使用呢

javascript 复制代码
function foo(n){
    console.log(this.a,n);
}
var obj = {
    a: 2
}
foo.call(obj,100) // 2 100

可以看出call与foo共用了这个括号

apply

javascript 复制代码
function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo.apply(obj)

无参数时用法与call相同

传参

javascript 复制代码
function foo(n,m){
    console.log(this.a,n,m);
}
var obj = {
    a: 2
}
foo.apply(obj,[100,200]) // 2 100 200

apply传参可以看出是以数组的形式

bind

javascript 复制代码
function foo(n,m){
    console.log(this.a,n,m);
}
var obj = {
    a: 2
}
var bar = foo.bind(obj,100,200)
bar() // 2 100 200

bind会返回一个新的函数体,这个函数体拥有了obj的词法作用域,并且foo中this会指向obj。

bind传参有下面三种方式

  1. 就是上面的例子,全部放进bind()中

  2. var bar = foo.bind(obj)

    bar(100,200) foo的参数可以全部写在返回的新函数中

  3. var bar = foo.bind(obj,100)

    bar(200) bind和返回的函数体都可以进行传参

    如果是下面这种情况

    var bar = foo.bind(obj,100,100)

    bar(200) // 2 100 100 这种情况并不覆盖

我们来看一个误导性极强的例子

scss 复制代码
function foo(){
    var a = 1
    function bar(){
        console.log(this.a);
    }
    var baz = bar.bind(foo)
    baz()
}
foo() // undefined

这里使用bind就是让bar中的this指向了foo,但是foo是没有词法作用域,foo有个[[scope]]指向了全局,因为在全局中声明,所以this最终指向了全局,而非foo,因此输出undefined。这里其实就是一个默认绑定规则,恰好应证了this不能引用一个词法作用域内部的内容,其实就是说的函数

三者区别

call和apply

call和apply非常类似,只是apply传入的参数是数组

bind和call,apply

bind返回一个新的函数,并且需要再进行调用

五、new绑定

当使用new关键字进行实例化一个构造函数时,this最终指向该实例化对象

引用上次讲原型的时候的例子

javascript 复制代码
Person.prototype.say = function(){
    console.log("hello " + this.name);
}
function Person(name){
    this.name = name;
    // 相当于下面
    // var this = {
    // 	name: name,
    // 	__proto__:Person.prototype
    // }
    // return this
}
var person1 = new Person("Dolphin")
person1.say()

new的作用其实就是在构造函数中创建一个this对象,然后构造函数中的内容就相当于往this里面挂属性,另外还会放一个实例对象的隐式原型,其值就是构造函数的显示原型,最终return出这个this对象

六、箭头函数

箭头函数没有this这个概念,在箭头函数中的this指向了外层第一个非箭头函数
这里我们不对箭头函数用法做仔细介绍,总之,箭头函数是es6新引进的函数,代码较普通函数更为简洁

我们来看个例子

javascript 复制代码
var a = 1
var bar = () => {
    console.log(this.a)
} 
bar() // 1

这个很好理解,箭头函数中的this就是指向了外层第一个非箭头函数,外层是全局,直接输出1

再来个误导性强的

javascript 复制代码
var obj = {
    name: 'Tom',
    show: function(){
        var bar = () => {
            console.log(this.name)
        }
        bar()
    }
}
obj.show() // Tom

bar中的this指向了外面第一个非箭头函数,也就是function这个函数,也就是说function中有个this,这不就是一个对象引用了一个函数吗,然后这个函数中的this就是指向了该对象,所以最终输出Tom

再来一个

scss 复制代码
a = 5
var obj = function(){
    a = 10
    var foo = ()=>{
        console.log(this.b)
    }
    foo()
}
obj() // 10

这里输出10是因为函数中a = 10 相当于跑到全局中去了,因为a前面没有用声明。如果这里var b = 10 打印this.b也是10 ,同样的道理,函数中没有赋值操作没有声明就会跑到全局中去

既然箭头函数不承认this这个东西,那我使用new给他一个this能否成功

javascript 复制代码
var Foo = () => {

}
console.log(new Foo()); // 报错

好吧,这样是不行的,箭头函数不能当作构造函数来用

总结

this的绑定规则其实就是可以分成默认和隐式,隐式丢失是隐式一种,显示三种函数以及new另外处理。默认永远指向全局,隐式注意一定是对象体中引用函数而非调用函数。有人觉得this很难,我们直接不用不就可以了吗,this的作用太多了以至于你无法避免使用,就拿传参来说,this隐式传参,不需要显示地传参。另外this也是面试热点问题,面试官会让你聊聊this的指向问题,call,apply,bind三者的区别。


如果觉得本文对你有帮助的话,可以给个免费的赞吗[doge] 还有可以给我的gitee链接codeSpace: 记录coding中的点点滴滴 (gitee.com)点一个免费的star吗[星星眼]

相关推荐
10年前端老司机1 小时前
什么!纯前端也能识别图片中的文案、还支持100多个国家的语言
前端·javascript·vue.js
摸鱼仙人~1 小时前
React 性能优化实战指南:从理论到实践的完整攻略
前端·react.js·性能优化
程序员阿超的博客2 小时前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 2452 小时前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇6 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖7 小时前
http的缓存问题
前端·javascript·http
小小小小宇7 小时前
请求竞态问题统一封装
前端
loriloy7 小时前
前端资源帖
前端
源码超级联盟7 小时前
display的block和inline-block有什么区别
前端
GISer_Jing7 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js