前端開發的小夥伴多多少少曾被 this 關鍵字難倒過,由於 JS 的 this 的指向不少時候能夠是「動態變化」的,可是關於 this
關鍵字咱們只須要記住一點:哪一個對象調用函數,函數的this指向哪一個對象。前端
可是這個判斷是誰就是一個不那麼簡單的過程了,接下來咱們就一一舉例說明。面試
像這樣一個直接聲明的函數,它的this
指向誰呢?數組
function outer(){
console.log(this);
function inner(){
console.log(this);
}
inner();
}
outer();
// Window
// Window
複製代碼
上面這個例子說明直接聲明在做用域裏的函數this
都指向Window
對象。 可是真的是這樣嗎?接下來咱們看看這兩個例子。瀏覽器
// 注: 'use strict' 只能聲明在做用域的第一行
'use strict';
function outer0(){
console.log(this);
function inner0(){
console.log(this);
}
inner0();
}
outer0();
// undefined
// undefined
outer0.call(0);
// 這裏會輸出什麼?
複製代碼
function outer1(){
console.log(this);
function inner1(){
'use strict';
console.log(this);
}
inner1();
}
outer1();
// Window
// undefined
outer1.call(0);
// 這裏又會輸出什麼?
複製代碼
因此,咱們得出:直接聲明在做用域的函數,在非嚴格模式下其this
指向Window
對象;在嚴格模式下指向undefined
。 這一點並不會隨父做用域的this
變化而變化。安全
小夥伴們能夠自行到瀏覽器看看經過call(thisValue)
調用輸出內容,關於這點,稍後咱們也會講到。bash
接下來咱們全部代碼都默認在嚴格模式下運行app
'use strict';
var key = 20;
var value = 20;
var obj = {
key: 10,
fn: function(){
console.log(this.key);
console.log(this.value);
}
};
obj.fn();
// 10
// undefined
複製代碼
咱們能夠看到,輸出的this.key
是10
,而不是20
;而對象上沒有value
屬性,輸出的this.value
獲得的是undefined
,並不會由於外面聲明瞭一個value = 20
就輸出20
,故咱們能夠得出,經過對象調用函數,其this
指向當前對象。函數
可是下面這種狀況應該注意:ui
'use strict';
var obj1 = {
key: 10,
fn: function(){
console.log(this.key);
console.log(this.value);
},
};
var obj2 = {
key: 20,
fn: obj1.fn,
}
obj1.fn();
// 10
// undefined
obj2.fn();
// 20
// undefined
複製代碼
雖然obj2.fn
等於obj1.fn
,但調用它的是obj2
,因此函數this
指向是obj2
,哪一個對象調用函數,函數裏面的this指向哪一個對象。this
// 這裏不能用嚴格模式了,有興趣的能夠看看使用嚴格模式會怎樣,以及爲何~~
// 'use strict';
var obj = {
key: 10,
fn: function(){
console.log(this.key);
console.log(this.value);
},
};
var fn = obj.fn;
obj.fn();
// 10
// undefined
fn();
// undefined
// undefined
複製代碼
咱們能夠看見,當咱們拿出來單獨調用時,它輸出了兩個undefined
,這是由於:哪一個對象調用函數,函數裏面的this指向哪一個對象。當咱們拿出來後,其實是由Window
對象調用,進一步證實了:哪一個對象調用函數,函數裏面的this指向哪一個對象。同時也說明了,一個聲明的普通函數調用,都是由全局對象Window
調用,嚴格模式下是由undefined
調用。(undefined
竟然能調用方法?~~~僞裝吧,但結果很重要)
return
語句'use strict';
function Student(name){
this.name = name;
// 當經過 new 調用時,這可認爲有一條隱式的語句
// return this;
}
var student = new Student('lilei');
console.log(student.name);
// lilei
複製代碼
咱們都知道是這樣一個結果,可是爲何呢?這咱們就須要理清當咱們 new
一個對象的時候,js 都幫咱們幹了什麼呢,其實很簡單:
// 建立一個空對象
var obj = {};
// 將新建立的對象 __proto__ 指向 Student 的 prototype
obj.__proto__ = Student.prototype;
// 將新建立對象的指針指向 Student 函數
Student.call(obj);
複製代碼
當通過這樣一個步驟以後,當咱們執行new Student('lilei')
時,咱們就知道它的this
指向爲何是這麼個結果了。
等等,上面代碼提到了經過new
調用時,有一個隱式return this;
語句,那若是咱們顯示的寫出return
語句會如何呢?
return
語句時'use strict';
function Student(name){
this.name = name;
return {
name: 'benshaoye',
}
}
var student = new Student('lilei');
console.log(student.name);
// benshaoye
複製代碼
納尼,這是爲何?當咱們在構造函數裏有return
時,new
出來的對象就是return
的對象,而不是Student
對象,須要注意,不過這種狀況也挺少(我就歷來沒碰見過)。
在上面的例子中咱們已經使用過call
了,apply
也有相似的地方,那麼它們的做用是什麼,區別又是什麼呢?
call
、apply
和bind
的第一個參數始終是函數執行時this
指向的對象。
call
後面其餘的參數是執行函數傳給函數的參數:
'use strict';
function sum(a, b){
return this + a + b;
}
/* * 這裏 this 指向 1,參數 a、b 分別是 二、3 */
console.log(sum.call(1, 2, 3));
// 6
複製代碼
apply
只接受兩個參數,第一個參數是執行時this
指向的對象,第二個參數是一個數組,數組裏的參數就是函數執行時參數,經過例子說明:
'use strict';
function sum(a, b){
return this + a + b;
}
/* * 這裏 this 指向 1,參數 a、b 分別是 二、3 * 經過例子更能理解其中的差別 */
console.log(sum.apply(1, [2, 3]));
// 6
var obj = {
key: 123,
fn: function(){
console.log(this.key);
},
fn0: ()=> {
console.log(this)
}
}
obj.fn();
// 123
// 這裏雖然調用的是 obj.fn 方法
// 可是經過 call 動態改變了它的 this 指向了 target
// 此時至關於 obj.fn 的執行環境是 target
var target = {key: 456}
obj.fn.apply(target);
// 456
// 稍後會講到,箭頭函數不能改變其 this 指向
obj.fn0.apply(target);
// undefined
複製代碼
這裏須要注意,bind
的第一個參數thisValue
是this
指向的對象,後面的參數依次是要傳給函數的參數,而且不可修改,返回的是另一個函數,這個函數接受的參數是剩餘的參數,舉例說明:
'use strict';
function sum(a, b){
return this + a + b;
}
var otherSum = sum.bind(1, 2);
console.log(otherSun(3));
// 6
複製代碼
能夠看出,this
綁定了1
,a
被綁定了2
,執行是3
,就是b
,很高級。
有興趣的小夥伴能夠先了解下 柯里化,之後也會講到。
call
、apply
和bind
?這個問題有時候面試常常會問到,就貼出來一下,供參考。
Function.prototype.call0 = function(){
var args = arguments, argsLen = args.length;
var self = this;
var customKey = 'customKey';
// 或者 customKey = Symbol.for('fn'),這樣更安全
var thisValue = args[0];
thisValue[customKey] = self;
var paramsName = [];
for (var i = 0; i < argsLen; i++){
params.push('args[' + i + ']');
}
// 這裏還有另外的方法,如:
// self.apply(thisValue, params),不過咱們就是來實現這兩個的,是否是有點換湯不換藥的味道
// thisValue[customKey](...params), 解構語法
var returnVal = eval('thisValue[customKey](' + paramsName.join(',') +')');
// 刪除咱們添加的屬性
delete thisValue[customKey];
return returnVal;
}
// 兩者實現很類似
Function.prototype.apply0 = function(){
var args = arguments, argsLen = args.length;
var self = this;
var customKey = 'customKey';
// 或者 customKey = Symbol.for('fn'),這樣更安全
var thisValue = args[0];
var params = args[1];
thisValue[customKey] = self;
var paramsName = [];
for (var i = 0; i < argsLen; i++){
params.push('params[' + i + ']');
}
// 其實這裏最好也用解構語法,這種方式很煩
var returnVal = eval('thisValue[customKey](' + paramsName.join(',') +')');
// 刪除咱們添加的屬性
delete thisValue[customKey];
return returnVal;
}
// 之後直接用解構語法這些新特性了,這纔是明智的選擇!!!
Function.prototype.bind0 = function(thisValue, ...args){
const thisLen = this.length, argsLen = args.length;
const self = this;
if (thisLen < argsLen) {
const params = args.slice(1, thisLen);
return function(){
return self.apply(thisValue, params)
}
} else {
return function(...otherArgs){
return self.apply(thisValue, args.concat(otherArgs));
}
}
}
複製代碼
ES6 提供的箭頭函數,大大增長了咱們的開發效率,可是在箭頭函數裏面,是沒有this
上下文的,箭頭函數裏的this
是繼承外面的環境。
'use strict';
const obj1 = {
value: 123,
fn: function(){
setTimeout(function(){
// 這裏的 function 是直接聲明
// 在調用的時候是由全局對象調用
console.log('this.value', this.value);
});
}
}
const obj2 = {
key: 456,
fn: function(){
// 若是說普通函數式動態綁定 this 上下文
// 箭頭函數的 this 上下文則是靜態綁定
// 始終指向 obj2
const callback = () => {
console.log('this.key', this.key);
}
setTimeout(callback);
return callback;
}
}
obj1.fn();
const obj2Arrows = obj2.fn();
obj2Arrows.call({key: 789});
// 考慮一下事件循環,執行 setTimeout 不會立刻輸出,而是在下一個循環輸出
// 若有不明白的能夠先自行查閱資料,之後也會講到這。
// 順序不必定
// this.value undefined
// this.key 456
// this.key 456
複製代碼
能夠看出,obj2Arrows
經過call
執行,並不能改變其this
指向,而是始終指向obj2
,印證了箭頭函數this
指向是靜態編譯,始終指向與其父做用域的this
相同的論點。看下面例子:
function handler() {
const fn = ()=> {
console.log('this.value0', this.value)
};
fn();
console.log('this.value1', this.value)
}
handler.call({value: 1});
handler.call({value: 2});
// this.value0 1
// this.value1 1
// this.value0 2
// this.value1 2
複製代碼
this
指向全局對象Window
,嚴格模式下指向undefined
;this
始終指向調用它的那個對象;call
、apply
、bind
調用時,this
指向其第一個參數;this
是靜態編譯,始終與其父做用域的this
一致;謝謝你們,喜歡點個贊再走哦!!!