【JS】Javascript中的this到底是什么

        JavaScript中的this是一个对于新手来说特别吓人并且不友好的概念。对于一个前端萌新来说,这个概念既模糊,又看不到它存在的意义。本文将深度解析JavaScript中的this,并且逐一分析他在每个常见场景中的意义和指向。

目录

基本篇: 什么是this?

构造函数中的this

Call, Apply, 和 Bind改变this

箭头函数继承this


基本篇: 什么是this?

        简而言之, this代表存在于函数的一个对象,而这个对象就是调用这个函数的那个对象。

        假设我们有一个对象叫做counter, 其中有两个函数,分别是incrementlog。 当你调用counter中的函数的时候,你就可以获得函数中的this对象。

        首先我们先试用log函数,来看看这里面的this到底是什么。

let counter = {
            count: 0,
            increment() {
                return this.count
            },
            log() {
                console.log(this)
            }
        }

counter.log()//{count: 0, increment: ƒ, log: ƒ}

counter.log()的结果显示,log函数的this就是counter这个object,也就是log函数的调用者。

counter.increment()//0

           以此类推,我们可以得出increment的结果是0,也就是对象中count的值。

         但现在你也许会问,那如果我们在全局环境下不通过一个对象,直接调用函数,他的this回事什么呢?请看这个例子:

function printThis() {
    console.log(this)
}
printThis()//Window

在全局环境下直接调用函数,this的指向将会是Window对象自身。但这并不是一个固定的情况。如果你在函数内或者他的全局环境内写明了 "use strict" 关键词,那么这个函数将在严格模式下执行,而此时,在window环境下直接调用函数将不再返回Window, 而是undefined

function printThis() {
    "use strict"
    console.log(this)
}
printThis()//undefined

你可能会问,什么是严格模式?简而言之,严格模式的存在是为了写更严谨的代码,从而减少bug,并为未来JS的版本做铺垫。 在我们的案例中,严格模式会改变this的指向,将无调用者的函数的this从Window变成undefined

        但是尽管在严格模式下的无调用者的函数中的this不会默认指向Window,如果你直接在全局作用域打印this,this仍然会指向Window

"use strict"
let x = this
console.log(x)//Window

        接着我们之前的话题,我们接着使用之前的counter对象,我们再声明一个新的函数 newLog, 而这个newLog的值就是counter中的log方法:

let counter = {
    count: 0,
    increment() {
        console.log(this.count)
    },
    log() {
        console.log(this)
    }
}

let newLog = counter.log

当我们调用newLog的时候,你也许会认为,调用newLog的结果也会是counter对象,但是:

counter.log()//Window
newLog()//undefined

newLog的this并不是counter对象,而是Window,因为counter并没有亲自调用newLog。即使newLog和counter中的log的内容完全一样,他们指向的this并不一定相同。

        如果你在一个html标签中定义this,那么这个this将会指向这个html标签,你也可以通过getAttribute来获得这个标签上的属性

i am a box

          以上就是JavaScript中的this的基本理解和用法,接下来的内容将一定程度利用其他有可能难以理解的知识。


构造函数中的this

        在构造函数中,this指向的是构造函数的new出来的实例对象。在接下来的例子中,我们有一个构造函数Person,我们在这个构造函数的原型上添加一个函数sayHi,然后我们我们构造一个他的实例对象newPerson,并调用sayHi来看看他的this指向哪里

function Person(name, age) {
    this.name = name,
    this.age = age
}

Person.prototype.sayHi = function() {
    console.log(this, this.name, this.age)
}

let newPerson = new Person('tom', 30)
newPerson.sayHi()// {name: 'tom', age: 30} 'tom' 30

        正如我们刚才所讲,这里newPerson的this并不指向刚刚的构造函数Person,而是实例对象本身。同理,class中的this也将指向实例对象

class User {
    constructor(name){
        this.name = name
    }

    sayName() {
        console.log(this.name)
    }
}

let user = new User('playerone')
user.sayName()//playerone

Call, Apply, 和 Bind改变this

        如果你接触JS的时间不长,你也许对这三个函数很陌生。但他们的基本概念和用法其实特别简单,并且他们三个本质上做同一件事并且用法类似,就是修改一个函数的this指向。他们的语法也不难,都是函数自带的原型方法:

        function.apply(thisArg, argsArray)

        function.call(thisArg, arg1, /* …, */ argN)

        function.bind(thisArg,[, arg1[, arg2[, ...]]])   ==>(这种以[]嵌套的写法经常出现,代表该参数是选填的)

        他们三个第一个参数都是一个对象,而这个对象将变成这个函数的新的this。后面跟着的是该函数的参数。唯一的区别是他们传参数的方式;apply会把所函数的参数作为一个数组传入。call会以逗号隔开,分别作为多个参数传入。bind的情况稍微特殊一些,。最后,callapply的使用会直接调用该函数,而bind不会。

 

let obj = {
    username: 'lucy',
    age: 30,
    hobby: 'running'
}

function logAge() {
    console.log(`${this.username} is ${this.age} years old`)
}

function logInfo(a, b) {
    console.log(a, b)
}

function addArgs(a, b, c) {
    console.log("a: " + a)
    console.log("b: " + b)
    console.log("c: " + c)
    return a + b + c
}

// logAge() // undefined is undefined years old

// logAge.call(obj) // lucy is 30 years old

// logAge.apply(obj) // lucy is 30 years old

// logAge.bind(obj)() // lucy is 30 years old
// 后面的小括号()用来调用函数,因为bind并不会立刻调用函数

logInfo.apply(null, [20, 30]) // 20 30

logInfo.call(null, 20, 30) // 20 30

let newAddArgs = addArgs.bind(null, 30, 'haha', [1,2,3])

newAddArgs() // a: 30, b: 'haha', c: [1,2,3]

//因为bind中的参数会为该函数设置预设参数,即使你自己再传参也不会有任何效果
newAddArgs('bb', 'cc') // a: 30, b: 'haha', c: [1,2,3] 

箭头函数继承this

        箭头函数是一个ES6的主要特性之一,虽然我们在代码中经常遇到他,但你知道很多时候他存在的真正意义并不是为了更加简洁的语法,而是因为他独特的this指向。箭头函数自身并没有this对象。我们在开头讲过,函数中this一般指向他的调用者,而箭头函数的this, 它只会从自己作用域上一层继承this。也就是说,它的this和定义自己的“主人”的this相同。如果上一侧也没有,就继续往上一层去找,直到找到Windowcallapply也对箭头函数无效,声明严格模式也没有影响。

        在下面这段代码中,箭头函数的this和普通函数的指向并不相同

let obj = {
    name: 'kevin',
    printA: function () {
        console.log(this)
    },
    printB: () => {
        console.log(this)
    }
}

obj.printA()//{name: 'kevin', printA: ƒ, printB: ƒ}
obj.printB()//Window

在第二种情况中,箭头函数定义在另一个函数内,那么他将继承定义他的这个函数的this,也就是obj2这个对象

let obj2 = {
    name: 'john',
    printThis() {
        console.log(this) 
    },
    parentFunc() {
        const arrowPrint = () => [
            console.log(this) 
        ]
        printThis() // Window
        arrowPrint() // {name: 'john', printThis: ƒ, parentFunc: ƒ}
        console.log(this) // {name: 'john', printThis: ƒ, parentFunc: ƒ}
    }
}


obj2.parentFunc()

         第三种情况,如果我们分别把两个匿名的普通函数和箭头函数绑定给两个按钮,并且在全局作用域下定义另外一个箭头函数并在回调函数中调用它。那么, 不论如何,全局下的箭头函数的this都会指向window,因为箭头函数是在Window下定义的:定义它的对象。并且,你可以发现,普通函数如果直接作为元素的回调函数,那么他的this会指向该元素。

    
    
 
    

        但是,当你在普通函数中加入严格模式,那么他的this指向将在匿名回调中变成undefined,正如我们之前所说,普通函数的this指向他的调用者。

        function logThis() {
            "use strict"
            console.log(this)
        }
        
        btn1.addEventListener('click', function () {
            logThis() // undefined
        })
        
        btn2.addEventListener('click', logThis) // 

        以上就是JavaScript中的this在各种常见情况下的行为表现。下面这段W3Schools的话可以比较好地总结我们所讲的内容:

In an object method, this refers to the object. 在对象方式中,this指向该对象
Alone, this refers to the global object. 自己被调用的话,指向全局对象
In a function, this refers to the global object. 在函数中,this也指向全局对象
In a function, in strict mode, this is undefined. 在函数中的严格模式下,指向undefined
In an event, this refers to the element that received the event. 在事件中,指向触发事件的元素

Methods like call()apply(), and bind() can refer this to any object.

call(), apply(), bind()可以把this重定向为任何对象

你可能感兴趣的:(javascript,前端,开发语言)