1. 程式人生 > 其它 >高階前端進階,為什麼要使用call、apply、bind?

高階前端進階,為什麼要使用call、apply、bind?

技術標籤:高階前端進階

前言:

callapplybind這3個方法的用處都是更改this指向,在學習callapplybind之前,需要先了解this,所以本文會先對this進行講解。

通過本文可以瞭解:

  1. this是什麼

  2. callapplybind是如何實現的

  3. callapplybind的用處

什麼是this

定義:

當前執行上下文(globalfunctioneval)的一個屬性,在非嚴格模式下,總是指向一個物件,在嚴格模式下可以是任意值。

說白了,this就是一個變數,他指向一個物件或者一個引數,我們通常需要使用this來設定或獲取變數、物件、方法,最重要的就是能夠分辨出當前使用的this的指向。接下來我們來看看不同情況下this

的指向。

重點:this的指向是呼叫時決定的。

1. 全域性上下文

  • 在全域性上下文中,this指向的是window
console.log(this==window)
//true

2.函式呼叫

  • 直接呼叫this指向window
function test(){
	console.log(this)
}
test();
//window

function test(){
	test1()
	function test1(){
		console.log(this)
	}
}
test();
//window	

3.通過物件呼叫

  • this指向呼叫方法的物件
const obj = {
	a:1,
	test:function(){
		console.log(this)
	}
}
console.log(obj.test())
//obj

4.箭頭函式

  • this與所在上下文this指向相同
const fun1 = ()=>{
	console.log(this)
}
console.log(fun1())
//window

function fn1(){
	console.log(this)
	return ()=>{
		console.log(this)
	}
}
console.log(fn1()())
//window
//window

	
5.建構函式
  • this指向構造出的物件
function Test(){
	this.name = 'test';
}
let t = new Test();
console.log(t.name)
//test

callapplybind原生實現解析

1.call

function.call(thisArg, arg1, arg2, …)。
第一個引數為要更改的物件,arg1,arg2…為傳入引數

原生實現

//call函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.call = function(context = window,...args){
	//將呼叫call的函式掛在傳入的物件上,起到更改this的目的
	context.fun = this;
	//執行方法,並傳入引數
	const result = context.fun(...args);
	//執行完畢刪除剛建立的自定義方法,防止汙染
	delete context.fun;
	//返回結果
	return result
}
let obj = {
	a:1
}
function test(){
	console.log(this.a)
}
test.call(obj);
//輸出
//1

2.apply

func.apply(thisArg, [argsArray])。 第一個引數為要更改的物件,第二個引數為func執行時傳入的陣列引數

//apply函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.apply = function(content=window,args){
	//將呼叫call的函式掛在傳入的物件上,起到更改this的目的
	content.fn = this;
	//執行方法,並傳入引數
	let result = content.fn(...args);
	//執行完畢刪除剛建立的自定義方法,防止汙染
	delete content.fn;
	//返回結果
	return result;
}
let obj = {
	a:1
}
function test(){
	console.log(this.a)
}
test.apply(obj);

call/apply方法基本一樣,唯一的區別在於call傳入的是多個引數,apply傳入的為陣列

3.bind

function.bind(thisArg[, arg1[, arg2[, …]]])。第一個引數為要更改的物件,arg1, arg2, …為傳入的引數

  • callapply的不同在於,callapply呼叫後會立即執行,返回值依賴於呼叫他們的函式。而bind,呼叫後會返回原函式,擁有指定的this以及初始引數。
//bind函式原生模擬實現
//給context一個預設值,如果沒有傳入,預設指向window,用...來將傳入引數轉為陣列
Function.prototype.bind = function(oThis) {
	//校驗呼叫者是否為方法
	if (typeof this !== "function") {
		throw new TypeError("error");
	}
	//擷取入參內的引數,並轉為陣列
	const aArgs = Array.prototype.slice.call(arguments, 1);
	//臨時儲存呼叫bind的物件
	const fToBind = this;
	//中間方法
	const fNOP = function() {};
	//返回的新函式
	const fBound = function() {
		//這裡判斷this指向
		//如果this指向中間方法,則說明返回的函式fBound通過new呼叫了,則這裡的this不更改指向,否則指向bind時傳入的物件
		//執行方法並將bind時傳入的引數與新函式呼叫時傳入的引數合併,傳入執行方法。
		//例:如果通過let t = new test1()
		//此時 這裡的this指向t,通過t的原型鏈可以找到fNOP的原型物件,這時候不能更改this為其他值
		return fToBind.apply(this instanceof fNOP && oThis ?
			this :
			oThis,
			aArgs.concat(Array.prototype.slice.call(arguments)));
	};
	//中間方法的原型指向呼叫bind的原型物件
	fNOP.prototype = this.prototype;
	//將返回函式的原型物件的隱式原型指向中間方法的原型物件
	fBound.prototype = new fNOP();
	return fBound;
};
let obj = {
	a: 1
}
function test() {
	console.log(this.a)
}
let test1 = test.bind(obj);
test1()
//輸出
//1

到這裡,callapplybind的實現方式已經介紹完畢。

為什麼要更改this指向

接下來用幾個示例說明為什麼要使用callapplybind更改this指向

例1:

var obj = {
	fn1: function() {
	},
	fn2: function() {
	},
	fn3: function(cb) {
	    //這裡呼叫的this.fn3傳入的function,由於是直接呼叫,所以this指向的是window
		cb();
	},
	list: function() {
		this.fn3(function() {
			console.log(this)
			this.fn1();
			this.fn2();
		});
	}
};
obj.list();

執行這段程式碼,控制檯會報個錯誤,this.fn1 is not a function,我們檢視this.fun3中的this,可以知道,這裡的this指向的是window,去window中查詢fn1方法,的確是查不到。

這時候我們就需要更改方法中的this指向了。

1、使用call
var obj = {
	fn1: function() {
		console.log('fn1')
	},
	fn2: function() {
		console.log('fn2')
	},
	fn3: function(cb) {
		cb.call(this);
		console.log('fn3')
	},
	list: function() {
		this.fn3(function() {
			this.fn1();
			this.fn2();
		});
	}
};
obj.list();
//輸出
//fn1
//fn2
//fn3

2、使用bind
var obj = {
	fn1: function() {
		console.log('fn1')
	},
	fn2: function() {
		console.log('fn2')
	},
	fn3: function(cb) {
		cb();
		console.log('fn3')
	},
	list: function() {
		this.fn3(function() {
			this.fn1();
			this.fn2();
		}.bind(this));
	}
};
obj.list();
//輸出
//fn1
//fn2
//fn3

例2:

function test(){
	console.log(typeof arguments)
	//通過輸出可以看到,arguments是類陣列物件
	//如果我們想要把傳入的引數轉為陣列,就需要借用call,將slice方法掛到arguments上,從而實現我們要的功能
	let args = Array.prototype.slice.call(arguments,0)
	console.log(args);
}
test(1,2,3);
//輸出
//object
//[1,2,3]

通過call方法可以將不存在當前物件的方法掛在到自己物件上,從而實現我們需要的功能。

例3:

const arr = [1,2,3,4];
//通過apply方法,將min方法掛到window上,然後將arr轉為字串組傳入min方法
const min = Math.min.apply(null,arr);
console.log(min)
//輸出
//1

通過這3個示例,可以看到callapplybind的使用場景。

結尾:

callapply都是立即執行,call傳入的是多個引數,apply傳入的是陣列。

bind是返回一個新函式,擁有this以及初始引數。

再此,對callapplybind的介紹就結束了,希望本篇文章對大家有所啟示,謝謝觀看~

如果想獲取更多內容,可以掃描下方二維碼,一起學習,一起進步。
左道前端