this关键字与箭头函数

178 阅读5分钟

一.this的值

this的值表示当前代码所在的执行环境

二.this的本质

  • 当一进入函数上下文中,但是还没有开始执行函数体内的代码时,此时js引擎会找到该上文中所有的function declarations(FD)、variableDeclaration、arguments、thisValue、scopeChain等进行初始化,在这个阶段(也就是说在函数调用阶段)this的值才确定。注意不是函数定义的时候知道的。

  • 事实上函数调用时的第一个参数就是this,只不过js将它隐藏了,我们看不到,所以说this的值在调用阶段才能确定,因为不知道你会以什么方式调用函数或者说不确定你每次调用函数时传入的参数值是什么。

看下列代码:
function fn() {
    console.log(this)
}
fn()    //window

1.以上代码在函数被调用时并没有传入相应的参数那么this值是怎么获取到的?
之前已经说过js将它隐藏了而已。 
2.那为什么this的值是window呢?
当你在全局环境中或者在全局环境下调用函数时,this的值默认为window(在非严格模式下)
3.call、apply、bind可以修改this的值

将上述代码看成下面这种形式就好理解了:
function fn() {
    console.log(this)   //window
}
fn.call(window)  //实际上js给你传了一个参数window进去,故你打出的this为window
fn.apply(window)

好,到这里你又有疑问了,那我通过fn.call(window)将window传进去了,那么在函数定义 的时候并没有一个变量接收这个window参数啊,那this怎么还是能够打出来呢?

可以参考arguments对象。

function fn() {
    console.log(arguments)   //[5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
fn.call(window, 5, 10)

在这里函数fn定义的时候也没有形式参数,那么实参仍然可以通过arguments获取到,
那么this的道理也是一样的,this实际上是函数定义时候的第一个参数,js将它隐藏了。

你可以将上面代码看成下面这种形式:
function fn(/*this这是隐藏的变量*/) {
    console.log(arguments)   //[5, 10, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
fn.call(window, 5, 10)

通过以上可以知道,在函数内部,this的值是取决于函数被调用的方式而不是定义的方式。

三.在function(运行内)环境中this的值

3.1 简单调用

function f() {
    return this        
}
f() //在浏览器中为window,在node中为global,相当于下面这种调用方式
f.call(window/global)   //这里相当于将this的值指定为window或global

在严格模式下,this将保持它进入执行环境时的值。如果this没有被执行环境定义,那它的默认值就为undefined。

function f() {
    "use strict"
    return this        
}
f() //undefined,相当于下面这样
f.call(undefined)

3.2 作为对象的方法

当函数作为对象里的方法被调用时,它们的this指向的是调用该函数的那个对象。

let obj = {
    age: 18,
    getAge: function () {
        return this.age
    }
}
obj.getAge()   //18,这里的this指向obj;相当于下面这种调用方式
obj.getAge.call(obj)  //18

3.3 作为构造函数

当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象(实例)。

function Fn() {
    this.age = 23
}
let fn = new Fn()
console.log(fn.age)  //23

3.4 bind方法

ECMAScript5 引入了Function.prototype.bind。调用fn.bind(obj)会创建一个与fn具有相同函数体和作用域的函数,但是在这个新函数中,this将永久地被绑定到了bind的第一个参数,无论这个函数是如何被调用的。

let obj = {
        name: 'jack',
        address: 'china'
}
let name = 'tom'
function fn () {
    console.log(this.name)
}
let f = fn.bind(obj)
f() //jack
f.call()    //"jack"
f.call(obj) //"jack"
以上可以看到无论函数f被如何调用,this都是指向obj

四.全局环境中this的值

  • 无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this都指向全局对象
//在浏览器中window对象同时也是全局对象
this === window //true
a = 'l'
this.a === window.a //true

五.箭头函数

箭头函数事实上根本没有this,其this值是箭头函数所在的那个最近环境中this的值。在全局环境中,它将被设置为全局对象:

let arrow = () => this
console.log(arrow())  //window
var name = 'tony'
let obj = {
    name: 'jack',
    age: 25,
    getName () {
        return () => this.name
    }
}
console.log(obj.getName()())    //'jack'

以上getName函数中返回的那个箭头函数中的this值是该箭头函数所在的那个最近环境中的this值,离该箭头函数最近的一个环境是函数getName,而getName中的this为obj,故箭头函数中的this也为obj,故打印出jack。

注意:在箭头函数中,如果将this传递给call、bind、或者apply,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数应该设置为null。看下列例子:

var name = 'tony'
let obj = {
    name: 'jack',
    age: 25,
    getName() {
        return () => this.name
    }
}
let obj2 = {
    name: 'ma'
}
console.log(obj.getName()())    //'jack'
console.log(obj.getName().call())   //'jack'
console.log(obj.getName().call(obj2))   //'jack'

注意以下一点要进行区分:

var name = 'tony'
let obj = {
    name: 'jack',
    age: 25,
    getName() {
        return () => this.name
    }
}
let obj2 = {
    name: 'ma'
}
let temp = obj.getName()
console.log(temp())  //'jack' 注意temp()的结果是'jack',不是'tony'
        
//但是如果你只是引用了obj的方法,而没有调用它,那么调用箭头函数后,this
//指向window

let temp2 = obj.getName
console.log(temp2()())  //此时为'tony'
//以上那句相当于,故结果为tony。因为在调用temp2()的时候,内部箭头函数的this
就被永久的绑定成了temp2函数环境内部的this值,也即window。
console.log(temp2.call(window)())   //'tony'
    
当obj.getName()进行调用时,此时getName函数环境内的this值为obj。由于内部返回了一个箭头函
数,而箭头函数本身并没有this,它取得实际上是离它最近的那个环境中this的值,this值被永久的置
为了obj.getName函数的this,即为obj。当将obj.getName()的返回值赋值给了temp,所以即使被调用
的方式通常将其设置为undefined或全局对象,它的this也仍然是 obj。

综上:
   箭头函数本身没有this,它的this值实际上是离它最近的那个环境中this的值,由于没有this值
   故无法作为构造函数而使用。