前端百題斬——快速手撕Call、Apply、Bind
在百題斬js中的這些“this”指向都值得了解中已經簡要概述了call、apply、bind三個方法,這三者作用是相同的,均可以改變this指向,從而讓某對象可以調用自身不具備的方法,本節將深入理解這三者的實現原理。
15.1 call()
15.1.1 基礎
call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。其返回值是使用調用者提供的this值和參數調用該函數的返回值,若該方法沒有返回值,則返回undefined。
基本用法:
- function.call(thisArg, arg1, arg2, ...)
小試牛刀
- function method(val1, val2) {
- return this.a + this.b + val1 + val2;
- }
- const obj = {
- a: 1,
- b: 2
- };
- console.log(method.call(obj, 3, 4)); // 10
15.1.2 實現
實現一個call函數,將通過以下幾個步驟:
- 獲取第一個參數(注意第一個參數為null或undefined時,this指向window),構建對象
- 將對應函數傳入該對象中
- 獲取參數并執行相應函數
- 刪除該對象中函數,消除副作用
- 返回結果
- Function.prototype.myCall = function (context, ...args) {
- // 獲取第一個參數(注意第一個參數為null或undefined時,this指向window),構建對象
- context = context ? Object(context) : window;
- // 將對應函數傳入該對象中
- context.fn = this;
- // 獲取參數并執行相應函數
- let result = context.fn(...args);
- // 消除副作用
- delete context.fn;
- // 返回結果
- return result;
- }
- // ……
- console.log(method.myCall(obj, 3, 4)); // 10
15.2 apply()
15.2.1 基礎
apply() 方法調用一個具有給定this值的函數,以及以一個數組(或類數組對象)的形式提供的參數。其返回值是指定this值和參數的函數的結果。call() 和 apply()的區別是call()方法接受的是參數列表,而apply()方法接受的是一個參數數組;
基本用法
- func.apply(thisArg, [argsArray])
小試牛刀
- function method(val1, val2) {
- return this.a + this.b + val1 + val2;
- }
- const obj = {
- a: 1,
- b: 2
- };
- console.log(method.apply(obj, [3, 4])); // 10
15.2.2 實現
apply和call的區別主要是參數的不同,所以其實現步驟的call大體類似,如下所示:
- Function.prototype.myApply = function (context, arr) {
- context = context ? Object(context) : window;
- context.fn = this;
- let result = arr ? context.fn(...arr) : context.fun();
- delete context.fn;
- return result;
- }
- // ……
- console.log(method.myApply(obj, [3, 4])); // 10
15.3 bind()
15.3.1 基礎
bind() 方法創建一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定為 bind() 的第一個參數,而其余參數將作為新函數的參數,供調用時使用。該函數的返回值是一個原函數的拷貝,并擁有指定的this值和初始參數。
基本用法
- function.bind(thisArg[, arg1[, arg2[, ...]]])
小試牛刀
- function method(val1, val2) {
- return this.a + this.b + val1 + val2;
- }
- const obj = {
- a: 1,
- b: 2
- };
- const bindMethod = method.bind(obj, 3, 4);
- console.log(bindMethod()); // 10
15.3.2 實現
實現一個bind函數相對較復雜一些,應該注意以下幾點:
- 能夠改變this指向;
- 返回的是一個函數;
- 能夠接受多個參數;
- 支持柯里化形式傳參 fun(arg1)(arg2);
- 獲取到調用bind()返回值后,若使用new調用(當做構造函數),bind()傳入的上下文context失效。
- Function.prototype.myBind = function (context, ...args) {
- if (typeof(this) !== 'function') {
- throw new TypeError('The bound object needs to be a function');
- }
- const self = this;
- // 定義一個中裝函數
- const fNOP = function() {};
- const fBound = function(...fBoundArgs) {
- // 利用apply改變this指向
- // 接受多個參數+支持柯里化形式傳參
- // 當返回值通過new調用時,this指向當前實例 (因為this是當前實例,實例的隱士原型上有fNOP的實例(fnop);fnop instanceof fNOP為true)
- return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]);
- }
- // 將調用函數的原型賦值到中轉函數的原型上
- if (this.prototype) {
- fNOP.prototype = this.prototype;
- }
- // 通過原型的方式繼承調用函數的原型
- fBound.prototype = new fNOP();
- return fBound;
- }
本文轉載自微信公眾號「執鳶者」,可以通過以下二維碼關注。轉載本文請聯系執鳶者公眾號。