万水千山老是情,看看this行不行

this = ?

在JS中,当一个函数执行时,都会建立一个执行上下文用来确认当前函数的执行环境,执行上下文分为 全局执行上下文函数执行上下文。而 this 就是指向这个执行上下文的对象。因此,this是在运行时决定的,能够简单的理解为 谁调用,this指向谁。javascript

分四种状况来看:java

  • 普通函数调用
  • 对象方法调用
  • 构造函数调用
  • call、apply、bind

普通函数调用

当函数做为函数独立调用的时候,则是在全局环境中运行,this 则指向全局对象 window数据结构

一个简单的例子app

function demo() {
    console.log(this);  // window
}

demo();

demo 函数独立调用,因此 this 指向全局对象 window函数

接着this

function outer() {
    function inner() {
        console.log(this); // window
    }

    inner();
}

outer();

虽然在 outer 函数内部声明了一个 inner 函数,但实际上 inner 函数是独立调用的,因此依然是在全局环境,this仍然是指向了 windowprototype

function demo(func) {
        func();
    }
    demo(function () {
        console.log(this); // window
    });

demo函数传入一个匿名函数,执行匿名函数func的时候,依然是做为函数独立调用,因此this仍然指向window设计

理解一下什么是做为函数独立调用:
当定义一个函数,例如var demo = function () {} 等号右边的函数是独立放在内存中的,而后赋予demo变量的指向为函数所在的内存地址,当直接调用 demo(),至关于直接找到函数自己执行,因此函数内部建立的上下文为全局上下文,this 则指向了全局对象 windowcode

对象方法调用

当调用一个对象方法时,this 表明了对象自己。对象

let obj = {
        name: 'invoker',
        getName: function () {
            console.log(this);   // obj
            console.log(this.name);  // "invoker"
        }
    }

    obj.getName();

定义了一个 obj 对象,调用其内部的getNamethis 则指向了 obj 对象。

稍微修改一下

var name = 'windowName';
    let obj = {
        name: 'invoker',
        getName: function () {
            console.log(this);  // window
            console.log(this.name); // windowName
        }
    }
 
    var getName = obj.getName;
    getName();

当用一个变量 getName 接收 obj 对象的 getName方法, 再执行 getName,发现 this 指向了 window,由于此时变量 getName 直接指向了函数自己,而不是经过 obj 去调用,此时就变成了函数独立调用的状况了。

再看个例子

let obj = {
        test: function() {
            function fn() {
                console.log(this); // window
            }
            fn();
        },
        test1: function (fn) {
            fn()
        }
    }

    obj.test();
    obj.test1(function () {
        console.log(this) // window
    });

虽然在 obj 对象的 test 方法内定义了 fn ,但执行时一样属于函数独立调用,因此 this 指向 window
将函数做为参数传入 objtest1 方法,也属于函数独立调用,this 一样指向 window

构造函数调用

使用 new 关键字调用函数,则是构造函数调用,this 指向了该构造函数新建立的对象。

function person(name) {
        this.name = name
    }

    let p = new person('invoker')
    console.log(p.name) // 'invoker'

回顾一下 new 关键词的过程:

  • 建立一个新的对象 obj
  • 使得 obj__proto__ 指向 构造函数的原型对象
  • 执行构造函数中的 constructor,改变this的指向为 obj
  • 若是结果是对象类型,则返回结果,不然返回obj
function myNew(Fn) {
        let obj = {}
        obj.__proto__ = Fn.prototype

        const res = Fn.prototype.constructor.call(obj)
        if (typeof res === 'object') {
            obj = res
        }

        return obj
    }

call、apply、bind

this 指向的是 callapplybind 调用时传递的第一个参数。

let obj = {
        name: 'invoker'
    }

    function demo() {
        console.log(this.name) // 'invoker'
    }

    demo.call(obj)
    demo.apply(obj) 
    demo.bind(obj)()

箭头函数

箭头函数在执行时并不会建立自身的上下文,它的 this 取决于自身被定义的所在执行上下文。

例子:

let obj = {
        fn: () => {
            console.log(this) // window
        }
    }

    obj.fn()

objfn 指向一个箭头函数,因为只有函数能够建立执行上下文,而箭头函数外部并无包裹函数,因此箭头函数所在的执行上下文为全局的执行上下文,this 指向 window

包裹一个函数看看呗?

let obj = {
        fn: function () {
            console.log('箭头函数所在执行上下文', this) // '箭头函数所在执行上下文' obj
            
            var arrow = () => {
                console.log(this) //obj
            }
            arrow()
        }
    }

    obj.fn()

箭头函数 arrow 被定义在 obj.fn 内,因此 fn 中的 this 就是 arrow 中的 this

箭头函数一次绑定上下文后便不可更改:

let obj = { name: 'invoker' }

    var demo = () => {
        console.log(this) // window
    }

    demo.call(obj)

虽然使用了 call 函数间接修改 this 的指向,但并不起做用。

为何会有this的设计

javascript中存在 this 的设计,跟其内存中的数据结构有关系。

假设定义 let obj = { name: 'invoker' }

  1. 此时会先生成一个对象 { name: 'invoker' } 并放在内存当中
  2. { name: 'invoker } 所在的内存地址赋予 obj

因此 obj 其实就是个指向某个对象的地址,若是要读取 obj.name,则先要找到 obj 所在地址,而后从地址中拿到原始对象,读取 name 属性。

对象中每一个属性都有一个属性描述对象:可经过 Object.getOwnPropertyDescriptor(obj, key) 来读取。
也就是说上面所说的 objname 属性实际是下面这样的

{
  name: {
    [[value]]: 'invoker',
    [[configurable]]: true,
    [[enumerable]]: true,
    [[writable]]: true
  }
}

value 就是得到的值。

如今假设对象的属性是一个函数:

let name = 'windowName'
    let obj = {
        name: 'invoker',
        sayHello: function () {
            console.log('my name is ' + this.name)
        }
    }

    let descriptor = Object.getOwnPropertyDescriptor(obj, 'sayHello')
    console.log(descriptor)
    
    //这个sayHello的属性描述对象为:
      sayHello: {
        [[value]]: ƒ (),
        [[configurable]]: true,
        [[enumerable]]: true,
        [[writable]]: true
      }

sayHellovalue 值是一个函数,这个时候,引擎会单独将这个函数放在 内存 当中,而后将函数的内存地址赋予 value

所以能够得知,这个函数在 内存 中是单独的,并不被谁拥有,因此它能够在不一样的上下文执行。

因为函数能够在不一样上下文执行,因此须要一种机制去获取当前函数内部的执行上下文。因此,就有了 this,它指向了当前函数执行的上下文。

// 接着上面代码

    obj.sayHello() // my name is invoker
    
    let sayHello = obj.sayHello
    sayHello() // my name is windowName

obj.sayHello() 是经过 obj 找到 sayHello,也就是对象方法调用,因此就是在 obj 环境执行。
let sayHello = obj.sayHello,变量 sayHello 就直接指向函数自己,因此 sayHello() 也就是函数独立调用,因此是全局环境执行。

总结

this 的出现,跟JS引擎内存中的数据结构有关系。
当发现一个函数被执行时,经过上面的多种状况。

  • 分析函数怎么调用(单独调用、对象方法、构造方法)
  • 是否有使用 call、apply 等间接调用
  • 是否有箭头函数
  • 甚至还可能分析是否为严格模式

这样就能很好的确认 this 的指向。

相关文章
相关标签/搜索