在闡述它們如何使用以前,咱們有必要整理清楚this
的用法,簡單的說this
是JavaScript
語言的一個關鍵字,它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。數組
那麼問題又來了,this
的值是什麼呢?bash
由於this
是在函數運行時,函數內部自動生成的一個對象,那麼接下來咱們經過函數來對this
進行分析。 首先JavaScript
中的函數能夠分爲兩類:app
接下來分別分析this
在這些函數中到底是什麼?函數
this
1.純粹的函數調用學習
function test(name) {
console.log(name)
console.log(this)
}
test('Jerry') //調用函數
複製代碼
以上函數調用的方式是很是常見的,然而這只是一種簡寫的形式,完整的寫法應該以下:ui
function test(name) {
console.log(name)
console.log(this)
}
test.call(undefined, 'Tom')
複製代碼
這裏面便出現了咱們將要學習的call
,先不討論它的做用,咱們繼續討論this
的用處,call
方法接受的第一個參數就是this
,可是咱們這裏是undefined
,按照規定,若是你傳的context
是 null
或者 undefined
,那麼 window
對象就是默認的context
(嚴格模式下默認context
是 undefined
)。this
2.對象中函數的調用spa
const obj = {
name: 'Jerry',
greet: function() {
console.log(this.name)
}
}
obj.greet() //第一種調用方法
obj.greet.call(obj) //第二種調用方法
複製代碼
從上面的例子中,咱們發現此次call
方法的第一個參數爲obj
,此時說明函數greet
內部的this
指向了obj
對象,這顯而易見便知call
方法的做用是改變this
的指向,又由於上面兩種調用方式結果同樣可知函數的this
指向能夠理解爲誰調用便指向誰。prototype
3.構造函數中的this
code
每一個構造函數在new
以後都會返回一個對象,這個對象就是this
,也就是context
上下文。
this
在使用箭頭函數的時候,箭頭函數會默認綁定外層的this
值,因此在箭頭函數中this
的值和外層的this
是同樣的。由於箭頭函數沒有this
,因此須要經過查找做用域鏈來肯定this
的值。
這就意味着若是箭頭函數被非箭頭函數包含, this
綁定的就是最近一層非箭頭函數的 this
。
注意:多層對象件套裏面的this
是和最外層保持一致的。
由於今天的重點是講解call
,apply
,bind
的用法及實現,然而箭頭函數是沒有這些方法的,因此箭頭函數的使用僅限於此。
首先說明call
,apply
是ES5中的語法,bind
是ES6新引入的,它們三者的類似之處爲:
this
對象的指向this
要指向的對象不一樣之處使用一個例子進行說明:
const personOne = {
name: "張三",
age: 12,
say: function () {
console.log(this.name + ',' + this.age);
}
}
const personTwo = {
name: "李四",
age: 24
}
personOne.say(); //張三,12
複製代碼
對於以上的結果,咱們應該都很是清楚,那麼問題來了,若是咱們想要知道personTwo
對象的信息如何實現呢?
分別使用call
,apply
以及bind
方法實現,並從中獲得它們三者的區別:
personOne.say.call(personTwo); //李四,24
personOne.say.apply(personTwo); //李四,24
personOne.say.bind(personTwo); //沒有輸出任何東西
複製代碼
修改以上代碼對比可知:call
和apply
都是對函數的直接調用,而bind
方法返回的仍然是一個函數,所以咱們須要執行它纔會有結果。
personOne.say.call(personTwo); //李四,24
personOne.say.apply(personTwo); //李四,24
personOne.say.bind(personTwo)(); //李四,24
複製代碼
接着繼續討論其他參數
const personOne = {
name: "張三",
age: 12,
say: function (gender, phone) {
console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
}
}
const personTwo = {
name: "李四",
age: 24
}
personOne.say("女", "123");
複製代碼
這個例子的區別於上面的即爲say
函數須要傳遞參數,咱們分別使用這三種方法實現傳遞參數:
personOne.say.call(personTwo, "女", "123"); //李四,24,女,123
personOne.say.apply(personTwo, ["女", "123"]); //李四,24,女,123
personOne.say.bind(personTwo, "女", "123")(); //李四,24,女,123
複製代碼
顯而易見的區別call
和bind
除了第一個參數外,以後的參數均爲一一傳遞,而apply
除了第一個參數外,只有一個參數即爲一個數組,數組中的每一項爲函數須要的參數。
說明它們的用法以及區別以後,咱們就要本身嘗試着剖析它的原理,本身書寫這三個方法啦~~~~~
call
實現在知道了它的使用即原理以後,想必直接看實現方法應該也能夠理解的,那麼先上代碼:
Function.prototype.myCall = function (obj) {
const object = obj || window; //若是第一個參數爲空則默認指向window對象
let args = [...arguments].slice(1); //存放參數的數組
object.func = this;
const result = object.func (...args);
delete object.func; //記住最後要刪除掉臨時添加的方法,不然obj就平白無故多了個fn
return result;
}
複製代碼
代碼很是簡短,一步步進行說明解釋:
由於call
方法是每個函數都擁有的,因此咱們須要在Function.prototype
上定義myCall
,傳遞的參數obj
即爲call
方法的第一個參數,說明this
的指向,若是沒有該參數,則指向默認爲window
對象。
args
爲一個存放除第一個參數之外的其他參數的數組(arguments
爲函數中接收到的多有參數,[...arguments]
能夠將arguments
類數組轉換爲真正的數組,詳細講解能夠查看ES6語法)。
解釋object.func=this
以前,咱們先使用示例使用一下本身定義的myCall
函數:
const personOne = {
name: "張三",
age: 12,
say: function (gender, phone) {
console.log(this.name + ',' + this.age + ',' + gender + ',' + phone);
}
}
const personTwo = {
name: "李四",
age: 24
}
Function.prototype.myCall = function (obj) {
const object = obj || window; //若是第一個參數爲空則默認指向window對象
let args = [...arguments].slice(1); //存放參數的數組
object.func = this;
const result = object.func (...args);
delete object.func; //記住最後要刪除掉臨時添加的方法,不然object就平白無故多了個func
return result;
}
personOne.say.myCall(personTwo,"女",18333669807); //李四,24,女,18333669807
複製代碼
根據示例,咱們進行解釋,myCall
裏面的this
指的是personOne.say
這個方法(由於myCall
是一個方法,上面所說的,誰調用它,它的this
便指向誰),object.func=this
至關於給object
這個對象克隆了一個personOne.say
方法,讓object
在調用這個方法,至關於object.personOne.say
,達到了call
的效果。(記住最後要刪除掉臨時添加的方法,不然object
就平白無故多了個func
)
object.func (...args)
裏面的參數即爲傳入的其他參數。
apply
實現經過上面的分析,想必你們應該已經基本明白了它是如何實現的了,那麼接下來實現apply
就很是簡單了,由於二者的區別主要就是參數的傳遞方式不一樣,和上面同樣,先直接看一下代碼:
Function.prototype.myApply = function (obj) {
const object = obj || window; //若是第一個參數爲空則默認指向window對象
if (arguments.length > 1) {
var args = arguments[1]; //存放參數的數組
} else {
var args = []; //存放參數的數組
}
object.func = this;
const result = object.func(...args);
delete object.func; //記住最後要刪除掉臨時添加的方法,不然obj就平白無故多了個fn
return result;
}
personOne.say.myApply(personTwo, ["女", 24]);
複製代碼
主要區別就是獲取參數不一樣,由於apply
的帶二個參數爲數組,數組中包含函數須要的各項參數值,其他內容實現myCall
相同,此處就不在作解釋。
bind
實現話很少說,依舊是先上代碼
Function.prototype.myBind = function (obj) {
const object = obj || window; //若是第一個參數爲空則默認指向window對象
let self = this;
let args = [...arguments].slice(1); //存放參數的數組
return function () {
let newArgs = [...arguments]
return self.apply(object, args.concat(newArgs))
}
}
personOne.say.myBind(personTwo, "女", 24)();
複製代碼
前面的知識不重複說,return function
是由於bind
返回的是一個函數,而且這個函數不會執行,須要咱們再次調用,那麼當咱們調用的時候,咱們依舊能夠對這個函數進行傳遞參數,即爲支持柯里化形式傳參,因此須要在返回的函數中聲明一個空的數組接收調用bind
函數返回的函數時傳遞的參數,以後對兩次的參數使用concat()
方法進行鏈接,調用ES5中的apply
方法。