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
當在任何函數以外調用this時,即global context, this指向瀏覽器的默認全局對象Windowspa
console.log(this) // Window
當使用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 }
Methods here are defined with ES6 object literal shorthand, 好比下面
let o = { aMethod () {} }
在method裏面的this,都指向該對象自己
let o ={ sayThis () { console.log(this) // o } } o.sayThis() // o
最多見的函數形式,相似下面的
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`) } }
在箭頭函數中,this老是指向箭頭函數所在做用域的對象
好比箭頭函數在object method中,那麼箭頭函數中的this就指向object
const o = { doSomethingLater () { setTimeout(() => this.speakLeet(), 1000) }, speakLeet () { console.log(`1337 15 4W350M3`) } }
在事件監聽函數中,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
什麼狀況下須要改變this的指向?好比想要得到類數組對象如{1:'a', 2: 'b'}的數組值,就可使用Array.slice方法,但OOP中,方法都是和對象綁定的,因此須要手動修改this的指向。
Javascript中共提供了三種方法修改this的指向, call,apply,bind
咱們使用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
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 ]
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
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/