題外篇git
如何改變this
得指向,常見的四種操做以下es6
call、apply、bind
let that = this
es6
中使用箭頭函數new
操做關於this
的指向github
在一個函數上下文中,this
由調用者提供,由調用函數的方式來決定。若是調用者函數,被某一個對象所擁有,那麼該函數在調用時,內部的this
指向該對象。若是函數獨立調用,那麼該函數內部的this
,則指向undefined
。可是在非嚴格模式中,當this
指向undefined
時,它會被自動指向全局對象。數組
參考來自 波波老師bash
call()
方法調用一個函數, 其具備一個指定的this值和分別地提供的參數(參數的列表)。閉包
語法:app
fun.call(thisArg, arg1, arg2, ...)
複製代碼
thisArg:fun
函數運行時指定得this
值函數
this
得值可能有以下幾種可能:測試
null,undefined
or 不傳,this
默認指向window
對象fn,this
指向fn
函數this
指向這個對象看個例子ui
let obj = {
val:'call'
}
function fn () {
console.log(this.val,'testCall');
}
fn.call(obj) //'call','testCall'
複製代碼
先考慮能夠正常執行,後面的傳參暫時不考慮
琢磨一下上面例子的代碼執行過程
call()
在執行過程當中,咱們想象一下它大概會經歷一些幾個階段(真實原理不作介紹)
fn
方法複製到obj
對象中fn
函數的this
指向fn
函數執行fn
從obj
對象刪除分析: 那麼咱們在模擬代碼的場景下 fn.call(obj)
的執行過程能夠想象成以下步驟:
一、將fn複製到obj對象中,那麼以下也就修改了fn中this的指向
obj = {
val:'call',
fn:function(){
console.log(this.val,'testCall')
}
二、 執行fn()
obj.fn()
三、刪除fn這個key
delete obj.fn
複製代碼
Function.prototype.call2 = function(args){
//此時的args就是 上面的obj
//1,此時使用this來獲取調用call的方法
args.fn = this;
//第二步 調用執行fn()
args.fn();
//第三步 刪除方法
delete args.fn
}
//測試下
let obj = {
val:'call2'
}
function fn () {
console.log(this.val,'testCall2');
}
fn.call2(obj) //'call2','testCall2'
複製代碼
MDN
文檔上介紹過,call
能夠接受多個參數,那麼在第二版的時候咱們加上入參這個功能
栗子
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
}
fn.call(obj,'alan') //'call','alan'
複製代碼
分析:
Arguments
中獲取第二個開始到最後結束的參數就好了// 第二版
Function.prototype.call2 = function(...args) {
//利用es6的 rest 來獲取函數的傳參,以及傳入thisArg;
let [thisArg,...arr] = args ;
// 獲取調用的函數方法
thisArg.fn = this;
// 用解構執行函數
thisArg.fn(...arr)
//刪除
delete thisArg.fn
}
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
}
fn.call2(obj,'alan') //'call','alan'
複製代碼
解釋:
es6
的 rest (形式爲...變量名),這樣能夠獲得一個數組,即args
此時爲數組,那麼上文中的thisArg
就是傳遞的第一個參數。fn(..arr)
使用了es6
spread ,他就比如是reset
的逆運算,這樣操做之後無論傳遞了幾個參數均可以正常處理文章開頭介紹過,若是在嚴格模式下,傳
null,undefined
or 不傳,thisArg
默認指向window
對象,還有一種場景若是fn
方法有返回值的狀況。
栗子 1
var val = 'call'
function fn () {
console.log(this.val);
}
fn.call() //'call'
fn.call(null);//'call'
fn.call(undefind);//'call'
複製代碼
分析: 若是不傳值或傳null
等值,處理起來不算麻煩,稍微在咱們原來的版本上作一些修改就好,看以下代碼
// 3.1
Function.prototype.call2 = function(...args) {
let thisArg,arr = [];
if(args.length === 0 || !args[0]){
thisArg = window;
} else{
//利用es6的解構來獲取函數的傳參,以及傳入thisArg;
[thisArg,...arr] = args ;
}
// 獲取調用的函數方法
thisArg.fn = this;
// 用解構執行函數
thisArg.fn(...arr)
//刪除
delete thisArg.fn
}
fn.call2() //'call'
fn.call2(null);//'call'
fn.call2(undefind);//'call'
複製代碼
栗子2
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.call(obj,'alan') //'call','alan'
//
{
val:'call',
name:'alan'
}
複製代碼
終極版本
// 3.2
Function.prototype.call2 = function(...args) {
let thisArg,arr = [];
if(args.length === 0 || !args[0]){
thisArg = window;
} else{
//利用es6的解構來獲取函數的傳參,以及傳入thisArg;
[thisArg,...arr] = args ;
}
// 獲取調用的函數方法
thisArg.fn = this;
// 用解構執行函數
let result = thisArg.fn(...arr)
//刪除
delete thisArg.fn
return result
}
let obj = {
val:'call'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.call2(obj,'alan') //'call','alan'
//
{
val:'call',
name:'alan'
}
複製代碼
apply的實現方式跟call基本類似,就是在傳參上,apply接受的是數組,直接就貼一下代碼
Function.prototype.apply2 = function(thisArg,arr) {
if(!thisArg){
thisArg = window;
}
// 獲取調用的函數方法
thisArg.fn = this;
// 用解構執行函數
let result = thisArg.fn(...arr)
//刪除
delete thisArg.fn
return result
}
let obj = {
val:'apply'
}
function fn (name) {
console.log(this.val,name);
return {
val:this.val,
name:name
}
}
fn.apply2(obj,['alan']) //'apply','alan'
複製代碼
bind()
方法建立一個新的函數,在調用時設置this
關鍵字爲提供的值。將給定參數列表做爲原函數的參數序列的前若干項。
語法:
fun.bind(thisArg,arg1,arg2......)
複製代碼
bind()
方法會建立一個新的函數,通常叫綁定函數
能夠接受參數,這個地方注意,它能夠在bind
的時候接受參數,同時bind()
返回的新函數也能夠接受參數
栗子
var obj = {
val: 'bind'
};
function fn() {
console.log(this.val);
}
// 返回了一個函數
var bindObj = fn.bind(obj);
bindObj(); // bind
複製代碼
照舊,暫時不考慮傳參
分析:
bindObj()
的執行結果跟使用call
同樣的,不一樣的是它須要調用返回的方法bindObj
琢磨上述代碼執行過程,這個時候咱們對比一下call
的模擬來看
bindObj
像是call
模擬過程當中的fn
,然後bindObj()
就像是fn()
bind
返回的函數,咱們能夠想象成call()
調用只有返回的函數而不會執行,只是apply(),call()
是當即執行,而bind
須要再次調用執行Function.prototype.bind2 = function (args) {
//經過this拿到調用方法
let that = this;
//使用一個閉包來存儲call方法的結果
return function () {
return that.call(args);
}
}
var obj = {
val: 'bind'
};
function fn() {
console.log(this.val);
}
// 返回了一個函數
var bindObj = fn.bind2(obj);
bindObj(); // bind
複製代碼
考慮下傳參的場景,開頭介紹過,傳參有兩種場景
栗子
let obj = {
val:'bind'
};
function fn(name,sex){
let o = {
val:this.val,
name:name,
sex:sex
}
console.log(o)
}
let bindObj = fn.bind(obj,'alan');
bindObj('man'); //{ val: 'bind', name: 'alan', sex: 'man' }
複製代碼
栗子分析:
bind
的時候接受了一個參數name
,同時返回了一個函數sex
模擬分析:
bind
方法傳參的場景,咱們能夠借用以前在call
函數中的方法,使用es6 rest
的方法。獲取從第二個開始到結束的全部參數bind
返回的函數傳參,能夠在寫的時候,將bind
傳參跟後續的傳參合並模擬開發
// 2.1
Function.prototype.bind2 = function (args) {
//經過this拿到調用方法
let that = this;
// 獲取bind2函數從第二個參數到最後一個參數
let allArgs = Array.prototype.slice.call(arguments, 1);
return function () {
// 這個時候的arguments是指bind返回的函數傳入的參數
var bindArgs = Array.prototype.slice.call(arguments);
return that.apply(args, allArgs.concat(bindArgs));
}
}
//2.2 es6實現
Function.prototype.bind2 = function (...args) {
//利用es6的 rest 來獲取函數的傳參,以及傳入thisArg;allArgs就是第二個參數到最後一個參數的數組
let [thisArg,...allArgs] = args ;
let that = this;
return function (...bindArgs) {
return that.apply(thisArg, allArgs.concat(bindArgs));
}
}
let obj = {
val:'bind'
};
function fn(name,sex){
let o = {
val:this.val,
name:name,
sex:sex
}
console.log(o)
}
let bindObj = fn.bind2(obj,'alan');
bindObj('man'); //{ val: 'bind', name: 'alan', sex: 'man' }
複製代碼
說明
Array.prototype.slice.call(arguments)
是如何將arguments
轉換成數組的,首先調用call
以後,this
就指向了arguments
,或許咱們能夠假象一下slice
的內部實現是:建立一個新的數組,而後循環遍歷this
,將this
的沒一個值賦值給新的數組而後返回新數組。大佬若是看到文中若有錯誤的地方歡迎指出,我及時修正。