前言:面試的一個重要考察點——this 對於一些人來講一直是個老大難。雖然面試題目變化萬千,但只要真正掌握了原理,不少問題就能迎刃而解的。node
下面我整理了一下判斷this指向的3個步驟:es6
第一準則是:this永遠指向函數運行時所在的對象,而不是函數被建立時所在的對象(箭頭函數則相反)。面試
既然this指向取決於函數調用時的位置,那咱們先看看如何確認調用位置。瀏覽器
首先介紹一下什麼是調用棧和調用位置。bash
function a() {
// 調用棧: c->b->a
// 當前調用位置在b中
console.log(a);
}
function b() {
// 調用棧: c->b
// 當前調用位置在c中
a()
}
function c() {
// 調用棧: c
// 當前調用位置是全局做用域
b();
}
c(); // c的調用位置
複製代碼
完成了第一步:肯定了函數的調用位置,下面看看第二步 this的四個綁定規則。app
能夠這樣理解:沒法應用其餘綁定規則時爲默認規則,獨立函數調用時通常是默認綁定。函數
function test(){
console.log(this.a);
}
var a = 'aaa'; // 或者window.a='aaa'
test(); // 輸出aaa
複製代碼
上例中,test()不在任何對象內,是獨立調用,屬於默認綁定。ui
(ps:默認綁定this指向的全局對象,在瀏覽器裏面是window,在node中是global, 在嚴格模式下,若是 this 沒有被執行上下文定義,this指向爲 undefined。)this
隱式綁定存在於在調用位置有上下文對象或者說調用時被對象包含或擁有。es5
var obj1 = {
name: 'kelvin',
say: function(){
console.log(this.name);
}
}
obj1.say();// kelvin
// 或者
function say() {
console.log(this.name);
}
var obj2 = {
name: 'kelvin',
say: say
}
obj2.say(); // kelvin
複製代碼
此時能夠用一句簡單的話歸納:誰去調用this就指向誰。不過有時這個規則會在不經意間失效,這種現象就叫隱式丟失。
// 第一種
function fun() {
console.log(this.a)
}
function doFun(fn) {
// 參數傳遞其實就是一個隱式的賦值
fn();
}
var obj = {
a: 'obj a',
fun: fun
}
var a = 'global a';
doFun(obj.fun); // 'global a'
複製代碼
顯式綁定裏面有硬綁定和某些API調用的"上下文"控制this
1)硬綁定
硬綁定就是咱們常常看到的call、apply、bind(es5中)三個方法,此處省略實例。
這裏我擴展一下三個方法的實現原理
1. call()
Function.prototype.myCall = function (context) {
var context = context || window
// 給 context 添加一個屬性
// getValue.call(a, 'yck', '24') => a.fn = getValue
// this 指代調用call方法的當前函數
context.fn = this
// 將 context 後面的參數取出來
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 刪除 fn
delete context.fn
return result
}
2. apply()
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 須要判斷是否存儲第二個參數
// 若是存在,就將第二個參數展開
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
3. bind()
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一個函數
return function F() {
// 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
結論:能夠看出call、apply是利用「隱式綁定規則」,經過將當前函數this放置到context對象中,爲context所包含或擁有,從而達到this指向context的目的。bind的不一樣之處是它返回一個新函數,且內部使用apply實現。
複製代碼
2)API調用的"上下文"
JS中有幾個內置函數,filter、forEach等都有一個可選參數,在執行 callback 時的用於指定 this 值。以forEach爲例子:
var person = {
name: '小張'
}
function say(item) {
console.log(this.name + ' ' + item)
}
[1,2,3,4].forEach(say, person)
//小張 1
//小張 2
//小張 3
//小張 4
複製代碼
首先看new作的4件事:
又new所作的第三件事情可知:this指向新對象
PS: 四種規則的優先級以下: new 綁定>顯式綁定>隱式綁定>默認綁定。
凡事有例外,es6中引入的箭頭函數卻不適用於上面的四個規則。看代碼以下:
function a(){
return ()=>{
console.log(this.name);
}
}
const obj1={
name: 「張」,
}
const obj2={
name: 「劉」,
}
var test = a.call(obj1);
test.call(obj2); // 張
複製代碼
由輸出結果:「張」,能夠看出箭頭函數是不適用於上面的四規則的。箭頭函數的具體規則是:箭頭函數this是在聲明時就肯定了,其this就是聲明時所在做用域的this肯定的。好比上面的例子,箭頭函數是在a函數中聲明的,因此箭頭函數中所用的this就是a的this,而a中的this是根據調用位置和規則肯定是obj1,因此箭頭函數中this也是指向obj1。
到此對this的掌握程度應該足以應對不少面試題了,謝謝你們的閱讀!
Over,thanks!