箭頭函數(shù)和 this指向

箭頭函數(shù)和普通函數(shù)的區(qū)別:

首先補(bǔ)一下 this 是什么?

this:

this 是和執(zhí)行上下文綁定的,也就是說每個(gè)執(zhí)行上下文中都有一個(gè) this。

222.png

上圖中 outer 是在變量環(huán)境里面的,為了方便看

  • 我們都知道 js 在編譯階段 創(chuàng)建執(zhí)行上下文
  • 在每個(gè)執(zhí)行上下文的變量環(huán)境中,都包含了一個(gè)外部引用,用來指向外部的執(zhí)行上下文,我們把這個(gè)外部引用稱為 outer

全局執(zhí)行上下文中的 this:

全局執(zhí)行上下文中的 this 是指向 window 對(duì)象的。這也是 this 和作用域鏈的唯一交點(diǎn),作用域鏈的最底端包含了 window 對(duì)象,全局執(zhí)行上下文中的 this 也是指向 window 對(duì)象

函數(shù)中的this:


function foo(){
  console.log(this)
}
foo()
// 也是指向window

那能不能設(shè)置執(zhí)行上下文中的 this 來指向其他對(duì)象呢? 肯定是可以的

1、通過函數(shù)的 call 方法設(shè)置:


let bar = {
  myName : "極客邦",
  test1 : 1
}
function foo(){
  this.myName = "極客時(shí)間"
}
foo.call(bar)
console.log(bar)   // {myName: "極客時(shí)間", test1: 1}
console.log(myName)   // myName is not defined

你就能發(fā)現(xiàn) foo 函數(shù)內(nèi)部的 this 已經(jīng)指向了 bar 對(duì)象,
因?yàn)橥ㄟ^打印 bar 對(duì)象,可以看出 bar 的 myName 屬性已經(jīng)由“極客邦”變?yōu)椤皹O客時(shí)間”了,
同時(shí)在全局執(zhí)行上下文中打印 myName, myName is not defined

2、通過對(duì)象調(diào)用方法設(shè)置:

要改變函數(shù)執(zhí)行上下文中的 this 指向,除了通過函數(shù)的 call 方法來實(shí)現(xiàn)外,還可以通過對(duì)象調(diào)用的方式


var myObj = {
  name : "極客時(shí)間", 
  showThis: function(){
    console.log(this)
  }
}
myObj.showThis()  // {name: "極客時(shí)間", showThis: ?}

使用對(duì)象來調(diào)用其內(nèi)部的一個(gè)方法,該方法的 this 是指向?qū)ο蟊旧淼摹?/p>


var myObj = {
  name : "極客時(shí)間",
  showThis: function(){
    this.name = "極客邦"
    console.log(this)
  }
}
var foo = myObj.showThis
foo()
//你會(huì)發(fā)現(xiàn) this 又指向了全局 window 對(duì)象。

  • 在全局環(huán)境中調(diào)用一個(gè)函數(shù),函數(shù)內(nèi)部的 this 指向的是全局變量 window。
  • 通過一個(gè)對(duì)象來調(diào)用其內(nèi)部的一個(gè)方法,該方法的執(zhí)行上下文中的 this 指向?qū)ο蟊旧怼?/strong>

3. 通過構(gòu)造函數(shù)中設(shè)置:

function CreateObj(){ 
this.name = "極客時(shí)間"
console.log(this)   // CreateObj {name: "極客時(shí)間"}
}
var myObj = new CreateObj()

new CreateObj() 過程:
1、首先創(chuàng)建了一個(gè)空對(duì)象 tempObj;
2、接著調(diào)用 CreateObj.call 方法,并將 tempObj 作為 call 方法的參數(shù),這樣當(dāng) CreateObj 的執(zhí)行上下文創(chuàng)建時(shí),它的 this 就指向了 tempObj 對(duì)象;
3、然后執(zhí)行 CreateObj 函數(shù),此時(shí)的 CreateObj 函數(shù)執(zhí)行上下文中的 this 指向了 tempObj 對(duì)象;
4、最后返回 tempObj 對(duì)象。

嵌套函數(shù)中的 this 不會(huì)從外層函數(shù)中繼承


var myObj = {
  name : "極客時(shí)間", 
  showThis: function(){
    console.log(this) 
    function bar(){console.log(this)}
    bar()
  }
}
myObj.showThis()
//函數(shù) bar 中的 this 指向的是全局 window 對(duì)象,
// 而函數(shù) showThis 中的 this 指向的是 myObj 對(duì)象

那怎么實(shí)現(xiàn) bar 的this指向外層 myObj 呢?


var myObj = {
  name : "極客時(shí)間", 
  showThis: function(){
    console.log(this)
    var self = this
    function bar(){
      self.name = "極客邦"
    }
    bar()
  }
}
myObj.showThis()
console.log(myObj.name)
console.log(window.name)

本質(zhì)是把 this 體系轉(zhuǎn)換為了作用域的體系 。也可以使用 ES6 中的箭頭函數(shù)來解決這個(gè)問題


var myObj = {
  name : "極客時(shí)間", 
  showThis: function(){
    console.log(this) // {name: "極客時(shí)間", showThis: ?}
    var bar = ()=>{
      this.name = "極客邦"
      console.log(this)  //  {name: "極客邦", showThis: ?}
    }
    bar()
  }
}
myObj.showThis()  
console.log(myObj.name)   // 極客邦
console.log(window.name)  //  ''

因?yàn)?ES6 中的箭頭函數(shù)并不會(huì)創(chuàng)建其自身的執(zhí)行上下文,所以箭頭函數(shù)中的 this 取決于它的外部函數(shù)

  • 當(dāng)函數(shù)作為對(duì)象的方法調(diào)用時(shí),函數(shù)中的 this 就是該對(duì)象;
  • 當(dāng)函數(shù)被正常調(diào)用時(shí),在嚴(yán)格模式下,this 值是 undefined,非嚴(yán)格模式下 this 指向的是全局對(duì)象 window;
  • 嵌套函數(shù)中的 this 不會(huì)繼承外層函數(shù)的 this 值

下面說下 普通函數(shù)和箭頭函數(shù)的區(qū)別:

箭頭函數(shù)和普通函數(shù)的this:

1、this 指向不同:

  • 普通函數(shù)this 指向 為方法調(diào)用的對(duì)象,可以通過bind,call,apply,改變this指向
  • 箭頭函數(shù)比函數(shù)表達(dá)式更簡(jiǎn)潔,箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this。bind,call,apply只能調(diào)用傳遞參數(shù),不可修改this指向
var obj = {
  a: 10,
  b: () => {
    console.log(this.a); // undefined
    console.log(this); // Window {postMessage: ?, blur: ?, focus: ?, close: ?, frames: Window, …}
  },
  c: function() {
    console.log(this.a); // 10
    console.log(this); // {a: 10, b: ?, c: ?}
  }
}
obj.b(); 
obj.c();

箭頭函數(shù)不綁定this,會(huì)捕獲其所在的上下文的this值,作為自己的this值。
任何方法都改變不了其指向

var obj = {
  a: 10,
  b: function(){
    console.log(this.a); //10
  },
  c: function() {
     return ()=>{
           console.log(this.a); //10
     }
  }
}
obj.b(); 
obj.c()();

箭頭函數(shù)通過 call() 或 apply() 方法調(diào)用一個(gè)函數(shù)時(shí),只傳入了一個(gè)參數(shù),對(duì) this 并沒有影響。

補(bǔ)充:call,aplly,bind:
它們?cè)诠δ苌鲜菦]有區(qū)別的,都是改變this的指向,它們的區(qū)別主要是在于方法的實(shí)現(xiàn)形式和參數(shù)傳遞上的不同。call和apply方法都是在調(diào)用之后立即執(zhí)行的。而bind調(diào)用之后是一個(gè)函數(shù),需要再調(diào)用一次才行,

①:函數(shù).call(對(duì)象,arg1,arg2....)
②:函數(shù).apply(對(duì)象,[arg1,arg2,...])
③:var ss=函數(shù).bind(對(duì)象,arg1,arg2,....)

let obj2 = {
    a: 10,
    b: function(n) {
        let f = (n) => n + this.a;
        return f(n);
    },
    c: function(n) {
        let f = (n) => n + this.a;
        let m = {
            a: 20
        };
        return f.call(m,n);
    }
};
console.log(obj2.b(1));  // 11
console.log(obj2.c(1)); // 11

2、箭頭函數(shù)沒有原型,

var a = ()=>{
  return 1;
}

function b(){
  return 2;
}

console.log(a.prototype);  // undefined
console.log(b.prototype);   // {constructor: ?}

3、箭頭函數(shù)不能綁定arguments,取而代之用rest參數(shù)...解決

function A(a){
  console.log(arguments);
}
A(1,2,3,4,5,8);
// [1, 2, 3, 4, 5, 8, callee: ?, Symbol(Symbol.iterator): ?]
let C = (...c) => {
  console.log(c);
}
C(3,82,32,11323);
// [3, 82, 32, 11323]

4、箭頭函數(shù)是匿名函數(shù),不能作為構(gòu)造函數(shù),不能使用new

無法實(shí)例化的原因:
沒有自己的this,無法調(diào)用call,apply
沒有prototype屬性,而new命令執(zhí)行的時(shí)候需要將構(gòu)造函數(shù)的prototype賦值給新的對(duì)象的_proto

5、箭頭函數(shù)不可以使用 yield 命令,因此箭頭函數(shù)不能用作 Generator 函數(shù)。

6、 函數(shù)體內(nèi)的this對(duì)象(繼承的),就是定義時(shí)所在的對(duì)象,而不是使用時(shí)所在的對(duì)象。

如何實(shí)現(xiàn) call 和apply?

1、改變this指向:可以將目標(biāo)函數(shù)作為這個(gè)對(duì)象的屬性
2、利用arguments類數(shù)組對(duì)象實(shí)現(xiàn)參數(shù)不定長(zhǎng)
3、不能增加對(duì)象的屬性,所以在結(jié)尾需要delete

var doThu = function(a, b) {
    console.log(this)
    console.log(this.name)
    console.log([a, b])
}
var stu = {
    name: 'xiaoming',
    doThu: doThu,
}
stu.doThu(1, 2) // stu對(duì)象 xiaoming [1, 2]
doThu.call(stu, 1, 2) // stu對(duì)象 xiaoming [1, 2]

call的作用就與此相當(dāng),只不過call為stu添加了doThu方法后,執(zhí)行了doThu,然后再將doThu這個(gè)方法從stu中刪除。

//在函數(shù)原型上增加call1方法
Function.prototype.call1 = function(context, ...rest) {
let newContext = context || window
  newContext.fn = this  // 將調(diào)用call函數(shù)的對(duì)象添加到context的屬性中
  
  let result = newContext.fn(...rest)  //  // 執(zhí)行該屬性

  delete newContext.fn  // 刪除該屬性

  return result
}
//在函數(shù)原型上增加apply1方法
Function.prototype.apply = function(thisArg, args) {
    if (typeof this !== 'function') { 
        throw new TypeError('Error')
    }
    thisArg = thisArg || window
    thisArg.fn = this
    let result
    if(args) {
        result = thisArg.fn(...args)
    } else {
        result = thisArg.fn()
    }
    delete thisArg.fn
    return result
}

bind的實(shí)現(xiàn)原理比call和apply要復(fù)雜一些,bind中需要考慮一些復(fù)雜的邊界條件。bind后的函數(shù)會(huì)返回一個(gè)函數(shù),而這個(gè)函數(shù)也可能被用來實(shí)例化:

Function.prototype.bind = function(thisArg) {
    if(typeof this !== 'function'){
        throw new TypeError(this + 'must be a function');
    }
    // 存儲(chǔ)函數(shù)本身
    const _this  = this;
    // 去除thisArg的其他參數(shù) 轉(zhuǎn)成數(shù)組
    const args = [...arguments].slice(1)
    // 返回一個(gè)函數(shù)
    const bound = function() {
        // 可能返回了一個(gè)構(gòu)造函數(shù),我們可以 new F(),所以需要判斷
        if (this instanceof bound) {
            return new _this(...args, ...arguments)
        }
        // apply修改this指向,把兩個(gè)函數(shù)的參數(shù)合并傳給thisArg函數(shù),并執(zhí)行thisArg函數(shù),返回執(zhí)行結(jié)果
        return _this.apply(thisArg, args.concat(...arguments))
    }
    return bound
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容