This in Javascript

1 什麼是this?

this是一個keyword, 它的值老是變換,依賴於調用它場景javascript

有6種狀況,this會指向特定的值java

(1) global context (全局)數組

(2) object construction (對象構造函數)瀏覽器

(3) object method (對象方法)app

(4) simple function (簡單函數)函數

(5) arrow function (箭頭函數)post

(6) event listener (事件監聽)this

 

2 詳細說明6中使用狀況

2.1  global context

當在任何函數以外調用this時,即global context, this指向瀏覽器的默認全局對象Windowspa

console.log(this) // Window

 

2.2 object construction

當使用new建立新實例時,this指向建立的實例instancecode

function Human (age) {
  this.age = age
}

let greg = new Human(23)
let thomas = new Human(25)

console.log(greg) // this.age = 23
console.log(thomas) // this.age = 25

new一個函數對象,返回被調用的函數名和建立的對象

function ConstructorExample() {
    console.log(this);
    this.value = 10;
    console.log(this);
}

new ConstructorExample();

// -> ConstructorExample {}
// -> ConstructorExample { value: 10 }

 

2.3 object method

Methods here are defined with ES6 object literal shorthand, 好比下面

let o = {
  aMethod () {}
}

在method裏面的this,都指向該對象自己

let o ={
  sayThis () {
    console.log(this) // o
  }
}

o.sayThis() // o

 

2.4 simple function

最多見的函數形式,相似下面的

function hello () {
  // say hello!
}

this指向Window, 即便 simple function 在object method中,也是指向Window

function simpleFunction () {
  console.log(this)
}

const o = {
  sayThis () {
    simpleFunction()
  }
}

simpleFunction() // Window
o.sayThis() // Window

爲何object method中的 simpleFunction() this沒有指向o, 這也是讓人迷惑的地方啊,或許下面的代碼能解釋一下,只能說object method裏面嵌套的函數的this都從新指向了Window

const o = {
  doSomethingLater () {
    setTimeout(function() {
      this.speakLeet() // Error, this-->Window
    }, 1000)
  },
  speakLeet() {
    console.log(`1337 15 4W350M3`)
  }
}

要解決這個問題,須要在嵌套函數外將this賦值給self

const o = {
  doSomethingLater () {
    const self = this
    setTimeout(function () {
      self.speakLeet()
    }, 1000)
  },

  speakLeet () {
    console.log(`1337 15 4W350M3`)
  }
}

 

2.5 arrow function

在箭頭函數中,this老是指向箭頭函數所在做用域的對象

好比箭頭函數在object method中,那麼箭頭函數中的this就指向object

const o = {
  doSomethingLater () {
    setTimeout(() => this.speakLeet(), 1000)
  },

  speakLeet () {
    console.log(`1337 15 4W350M3`)
  }
}

 

2.6 Event Listener

在事件監聽函數中,this指向觸發事件的元素

let button = document.querySelector('button')

button.addEventListener('click', function() {
  console.log(this) // button
})

 

若是要在監聽函數中調用其餘函數,須要先將this賦值給其餘變量,如self 

function LeetSpeaker (elem) {
  return {
    listenClick () {
      const self = this
      elem.addEventListener('click', function () {
        self.speakLeet()
      })
    },
    speakLeet() { console.log(`1337 15 4W350M3`) }
  }
}

 

固然,使用箭頭函數也能夠直接使用this, 訪問元素可使用e.currentTarget

function LeetSpeaker (elem) {
  return {
    listenClick () {
      elem.addEventListener('click', (e) => {
        console.log(e.currentTarget) // elem
        this.speakLeet()
      })
    },
    speakLeet () {
      console.log(`1337 15 4W350M3`)
    }
  }
}

new LeetSpeaker(document.querySelector('button')).listenClick()

 

若是想要移除監聽事件,則須要在綁定事件時,第二個參數--回調函數要使用命名函數,而非匿名函數

function someFunction () {
  console.log('do something')

  // Removes the event listener.
  document.removeEventListener('click', someFunction)
}

document.addEventListener('click', someFunction)

 

須要注意的是,有些時候,上述的規則會共同做用,這是會有優先級,好比下面的例子,new和對象方法共同做用的狀況,那麼,new 規則主導

var obj1 = {
    value: 'hi',
    print: function() {
        console.log(this);
    },
};

new obj1.print(); // -> print {}

 

小結:

某些規則共同做用,優先級以下,從前日後,優先級越低:

(1) new 操做符

(2) bind()

(3) call(), apply()

(4) object method

(5) global object, except in strict mode

 

3 改變this的指向

什麼狀況下須要改變this的指向?好比想要得到類數組對象如{1:'a', 2: 'b'}的數組值,就可使用Array.slice方法,但OOP中,方法都是和對象綁定的,因此須要手動修改this的指向。

Javascript中共提供了三種方法修改this的指向, call,apply,bind

3.1 call

咱們使用func.call(param), 傳遞參數給this, 第一個參數綁定給this,後面的做爲函數的實參

例子1:不帶參數的函數

function logThis() {
    console.log(this);
}

var obj = { val: 'Hello!' };

logThis(); // -> Window {frames: Window, postMessage: ƒ, …}
logThis.call(obj); // -> { val: 'Hello!' };

 

例子2:帶參數的函數

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

 

call + arguments 移花接木

aruguments在Javascript中,是傳遞給函數的參數,一個類數組的對象

function add() {
    console.log(arguments);
}

add(4); // -> { '0': 4 }
add(4, 5); // -> { '0': 4, '1': 5 }
add(4, 5, 6); // -> { '0': 4, '1': 5, '2': 6 }

當有需求要遍歷這些參數,使用Array的map, forEach等,咱們可使用call來說arguments轉變爲數組

Array.slice:經過this引用,返回調用數組的一份拷貝,若是Array.slice傳入arguments, 就會返回一份新的數組,從arguments建立而來的

function add() {
    var args = [].slice.call(arguments);
    console.log(args);
}

add(4, 5, 6); // -> [ 4, 5, 6 ]

 

3.2 apply

apply 運行的機制和call相似,不一樣的是arguments是以數組的形式傳遞的

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

 

3.3 bind

bind和call, apply運行不太同樣,func.bind調用後函數並不當即執行,而是當函數調用時纔會觸發, 傳參的方式和call同樣,一個一個傳

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');

console.log(fnBound);
// -> [Function: bound logThisAndArguments]

fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

 

function sayThis () {
  console.log(this)
}
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc()  // {hippy: "hipster"}

 

遇到箭頭函數,可能狀況就不同了

const sayThis = _ => console.log(this)
const boundFunc = sayThis.bind({hippy: 'hipster'})
boundFunc() // Window

 

傳給bind的其餘參數則做爲實參

const sayParams = (...args) => console.log(...args)
const boundFunc = sayParams.bind(null, 1, 2, 3, 4, 5)
boundFunc() // 1 2 3 4 5

 

事件監聽函數只調用一回的例子

function LeetSpeaker (elem) {
  return {
    listenClick () {
      this.listener = this.speakLeet.bind(this)
      elem.addEventListener('click', this.listener)
    },

    speakLeet(e) {
      const elem = e.currentTarget
      this.addLeetSpeak()
      elem.removeEventListener('click', this.listener)
    },

    addLeetSpeak () {
      const p = document.createElement('p')
      p.innerHTML = '1337 15 4W350M3'
      document.body.append(p)
    }
  }
}

const button = document.body.querySelector('button')
const leetSpeaker = LeetSpeaker(button)
leetSpeaker.listenClick()

 

 

綜合示例:

function logThisAndArguments(arg1, arg2) {
    console.log(this);
    console.log(arg1);
    console.log(arg2);
}

var obj = { val: 'Hello!' };

// NORMAL FUNCTION CALL
logThisAndArguments('First arg', 'Second arg');
// -> Window {frames: Window, postMessage: ƒ, …}
// -> First arg
// -> Second arg

// USING CALL
logThisAndArguments.call(obj, 'First arg', 'Second arg');
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

// USING APPLY
logThisAndArguments.apply(obj, ['First arg', 'Second arg']);
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

// USING BIND
var fnBound = logThisAndArguments.bind(obj, 'First arg', 'Second arg');
fnBound();
// -> { val: 'Hello!' }
// -> First arg
// -> Second arg

 

參考資料:https://zellwk.com/blog/this/

相關文章
相關標籤/搜索