this关键字

334 阅读5分钟

前言

jsthis是比较重要的概念,this在不同地方表示意思可能不同,一个简单的操作就可能会改变this的意思,所以怎么识别当前this是非常重要的

执行上下文

this执行上下文的一个特殊的属性,所以要了解this就必须了解执行上下文执行上下文简而言之就是当前代码的执行环境,首次运行js代码时会创建一个全局执行上下文并压入执行栈(后进先出)中,当遇到调用函数时会为该函数创建一个新的执行上下文,函数执行完毕对应的执行上下文后从执行栈中移除,即:该执行环境销毁,环境内保存的变量和函数销毁

至此我们已经知道执行上下文分为两种(eval这里不做讨论):

  • 全局执行上下文:这里的this浏览器中通常指window

  • 函数执行上下文:函数被调用的时候才会创建执行上下文而非定义时,this作为执行上下文的一个属性也只能在函数被调用的时候才能确定,所以通常会说:谁调用this就是谁

接下来我们看几个demo

// 此demo了解执行栈后进先出
function test() {
    console.log('I am test')
    test1()
    console.log('I am test2')
}
function test1() {
    console.log('I am test1')
}
test()

// 执行过程:
// 1. 创建全局执行上下文推入执行栈
// 2. 遇到test创建test执行上下文推入执行栈
// 3. 遇到test1创建test1执行上下文推入执行栈
// 4. test1执行完毕,test1执行上下文出栈,销毁
// 5. test执行完毕,test执行上下文出栈,销毁
// 6. 关闭浏览器,全局执行上下文出栈,销毁

var a = 1
var obj = {
    a: 2,
    fn: function () { // 函数被创建,this不明确,具体看被谁调用
        console.log('a--', this, this.a)
        return function() {
            console.log('c--', this, this.a)
        }
    }
}
// 调用一
obj.fn() // 此时函数被obj调用
// 执行结果: a-- {a: 2, fn: ƒ} 2

// 调用二
var fn = obj.fn
fn()// 相当于window.fn(),此时this指向window
// 执行结果:a-- Window {window: Window, self: Window, document: document, name: "", location: Location, …} 1

// 调用三
obj.fn()()
// 执行结果: 
//  a-- {a: 2, fn: ƒ} 2
//  c-- Window {window: Window, self: Window, document: document, name: "", location: Location, …} 1
// fn()被obj调用所以this指向obj,a是2,第二个函数实则是被window调用,所以this是window,a是1
// 函数所谓的被谁调用就是看who.function中的who,谁是who,this就是谁,没有who.,this就是window
总结:this是执行上下文属性,新的执行上下文才有可能存在新的this指向,可以创建执行上下文的有全局和函数,除函数内this,其余this均为window,函数this被调用时确认:who?.function,who是谁函数中this就是谁,没有who时函数this为window

js中会有一些特殊的存在,接下来我们一起了解一下

New

new做了以下事情:

  1. 创建新对象
  2. 新对象__proto__指向函数的prototype
  3. this指向该对象
  4. 返回新对象

new的实现:

function Animal(type) {
    this.type = type
    return
}
Animal.prototype.say = function() {
    console.log('say')
}
// 自定义new
function myNew(...values) {
    let [ fn, ...others ] = values
    let obj = {}
    obj.__proto__ = fn.prototype
    const result = fn.apply(obj, others)
    // fn返回的若为对象则返回改对象,否则返回obj
    return Object.prototype.toString.call(result) == '[object Object]' ? result : obj
}
const animal = myNew(Animal, 'v')
console.log('animal', animal)
// animal Animal {type: "v"}

new的实现可以看出改变Animalthis的指向了新对象obj

Call、Apply和Bind

callapplybind都可以改变this的指向,它们的实现如下:

// call
Function.prototype.myCall = function(...values) {
    let [ obj, ...others ] = values
    obj = obj ? Object(obj) : window
    obj.fn = this
    obj.fn(...others)
    delete obj.fn
    return
}
//  apply
Function.prototype.myApply = function(...values) {
    let [ obj, others ] = values
    obj = obj ? Object(obj) : window
    obj.fn = this
    obj.fn(others)
    delete obj.fn
    return
}
// bind
Function.prototype.myBind = function(...values) {
    let [ obj, others ] = values
    let _self = this
    let bind = function(vl) {
        return _self.apply(obj, [...others, ...vl])
    }
    bind.prototype = Object.create(this.prototype)
    return bind
}

ok以上是它们的实现,可以看出它们三个都有改变this的作用,还是以demo为例:

var a = 1
var obj = {
    a: 2,
    fn: function () {
        console.log('a--', this, this.a)
        return function() {
            console.log('c--', this, this.a)
        }
    }
}
// 调用一
var fn = obj.fn
fn.call(obj)
// 执行结果: a-- {a: 2, fn: ƒ} 2,不同于之前的结果这里的this指向了obj

// 调用二
obj.fn().call(obj)
// 执行结果
// 1. a-- {a: 2, fn: ƒ} 2
// 2. c-- {a: 2, fn: ƒ} 2
// 由于我们使用call改变第二个函数的this指向,所以此时的this为obj,再次证明函数被调用的时候才能确定this指向

箭头函数

箭头函数是一个特殊的存在,没有自己this,没有prototype,没有arguments,不能作为构造函数,它继承的是上层作用域链的this,箭头函数this的指向创建便确定,一旦确定即使callapplybind也无法改变,一起看几个demo

var obj = {
    fn: () => {
        console.log('this', this)
    }
}
// 调用一
obj.fn()
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}

// 调用二
obj.fn.call(obj)
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}
// 执行结果都是window,因为箭头函数被创建时this的指向已经确定为window

再看一个

var obj = {
    fn: function() {
        return () => {
            console.log('this---', this)
        }
    }
}
// 调用一
obj.fn()()
// 执行结果:this--- {fn: ƒ}

// 调用二
const fn = obj.fn()
fn()
// 执行结果:this--- {fn: ƒ}

// 调用三
obj.fn.call(window)()
// 执行结果: this Window {window: Window, self: Window, document: document, name: "", location: Location, …}

// 第三次结果不同,是因为通过call将fn的this指向了window,也就是说箭头函数依赖的this指向了window,所以箭头函数的this指向window

结语

执行上下文还有一些概念,如:变量提升、函数提升、作用域链等,不做过多的解释了,不了解了小伙伴自行查阅一下