摘要:而模擬的方法返回的函數用作構造函數時,生成的對象為。同樣,使用運算符時,綁定構造函數和未綁定構造函數并無兩樣。標準的方法創建一個新函數稱為綁定函數,新函數與被調函數綁定函數的目標函數具有相同的函數體在規范中內置的屬性。
這是一道面試題,題目給出了使用bind方法的樣例,要求用javascript實現這個方法,面試官還很善意的提醒我函數柯里化,然而,我還是不會這道題目,所以回來這會《javacript權威指南》和《javacript 高級教程》開始學習相關知識。
一、javacript實現bind方法bind()是在ECMAScript5中新增的方法,但是在ECMAScript3中可以輕易的模擬bind()。
版本一這部分參考了《javacript權威指南》權威指南的p191,ECMAScript3版本的Function.bind()方法的實現。
if(!Function.prototype.bind){ Function.prototype.bind = function(o){ // 將`this`和`arguments`的值保存在變量中,以便在后面嵌套的函數中可以使用它們 var self = this, boundArgs = arguments; //bind方法的返回值是一個函數 return function(){ var args = [],//創建一個實參列表,將傳入的bind()的第二個及后續的實參都傳入這個函數。 i; for(i=1;i版本一存在的問題 上述ECMAScript3版本的Function.bind()方法和ECMAScript5中定義的bind()有些出入,主要有以下三個方面。
真正的bind()方法(ECMAScript5中定義的bind())返回一個函數對象,這個函數對象的length屬性是綁定函數的形參個數減去綁定實參的個數。而模擬的bind()方法返回的函數對象的length屬性的值為0.
真正的bind()方法可以順帶用作構造函數,此時將忽略傳入bind()的this,原始函數就會以構造函數的形式調用,其實參也已經綁定。而模擬的bind()方法返回的函數用作構造函數時,生成的對象為Object()。
真正的bind()方法所返回的函數并不包含prototype屬性(普通函數固有的prototype屬性是不能刪除的),并且將這些綁定的函數用作構造函數時所創建的對象從原始的未綁定的構造函數中繼承prototype。同樣,使用instanceof運算符時,綁定構造函數和未綁定構造函數并無兩樣。
版本二針對上述ECMAScript3版本的Function.bind()方法存在的問題,《JavaScript Web Application》一書中給出的版本有針對性的修復了這些問題。
Function.prototype.bind = function(context){ var args = Array.prototype.slice.call(arguments,1),//要點3 self = this, F = function(){};//要點1 bound = function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return self.apply((this instanceof F ? this : context),finalArgs);//要點2 }; F.prototype = self.prototype; bound.prototype = new F(); return bound; }要點1,解釋
如下這段代碼,實際上用到了原型式繼承。這跟ECMAscript5中的Object.creat()方法只接受一個參數時是一樣的。
F = function(){};//要點1 ... F.prototype = self.prototype; bound.prototype = new F();要點2,解釋
如下這段代碼,是要判斷通過bind方法綁定得到的函數,是直接調用還是用作構造函數通過new來調用的。
this instanceof F ? this : context為了分析這段代碼的具體含義,需要知道通過構造函數生成對象時,new操作符都干了啥。比如如下代碼:
var a = new B()(1).首先創建一個空對象,var a = {};
(2).將構造函數的作用域賦給新對象(因此,this就指向了這個新對象);
(3).執行構造函數中的代碼(為這個新對象添加屬性), B.call(a);
(4).繼承被構造函數的原型,a._proto_ = B.prototype;
(5).返回這個新對象。標準的bind方法:
創建一個新函數(稱為綁定函數),新函數與被調函數(綁定函數的目標函數)具有相同的函數體(在 ECMAScript 5 規范中內置的call屬性)。當目標函數被調用時this值綁定到bind()的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操作符創建對象:這種行為就像把原函數當成構造器。提供的this值被忽略,同時調用時的參數被提供給模擬函數。
通過原型鏈的繼承可以判斷綁定函數是否用作了構造函數,通過new操作符來調用。假設目標函數為funObj,綁定函數為funBind.即
var funBind = funObj.bind(context); var obj = new funBind();上面代碼具有如下繼承關系(這里畫出繼承關系圖更容易理解):
obj instanceof funBind // true funBind.prototype instanceof F //true F.prototype = self.prototyepa instanceof B原理,是判斷B.prototype是否存在于a的原型鏈中。因此有
obj instanceof F // true此外,要點2這里還用到了借用構造函數來實現繼承,如下代碼
self.apply(this,finalArgs)要點3,解釋
版本二測試這里實際上是將類數組對象轉化為數組,因為類數組對象,比如arguments、nodelist;雖然很像數組,比如具有length屬性,但是不是數組,比如,沒有concat、slice這些方法.
常用的將類數組對象轉為數組的方法有
(1).Array.prototype.slice.call
(2).擴展運算符...,比如[...arguments]
(3). Array.from();測試1
可見,版本二并沒有解決版本一的問題1和3
測試2
版本二的精簡版可見版本二解決了版本一的問題2
版本二中要點1和要點2看著很不爽,于是,我給精簡了一下,測試結果與版本二相同。
Function.prototype.bind = function(context){ var args = Array.prototype.slice.call(arguments,1),//要點3 self = this, //F = function(){};//要點1 bound = function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); //return self.apply((this instanceof F ? this : context),finalArgs);//要點2 return self.apply((this instanceof self ? this : context),finalArgs);//要點2 }; //F.prototype = self.prototype; //bound.prototype = new F(); bound.prototype = self.prototype; return bound; }測試結果如下:
二、bind函數應用關于bind函數的應用這里只提兩點在我使用這個方法的時候,遇到的讓我剛開始比較懵逼仔細一想還真是這么回事的問題。
一段神奇的代碼var unBindSlice = Array.prototype.slice; var bindSlice = Function.prototype.call.bind(unBindSlice); ... bindSlice(arguments);測試一下
這段代碼的作用就是將一個類數組對象轉化為真正的數組,是下面這段代碼的另一種寫法而已
Array.prototype.slice.call(arguments);bind函數只創建一個新函數而不執行將一個函數對象作為bind的context,這種寫法的作用是,為需要特定this值的函數創造捷徑。
私以為這是bind和call與apply方法的一個重要差別,call和apply這兩個方法都會立即執行函數,返回的是函數執行后的結果。而bind函數只創建一個新函數而不執行。
之前看過一段錯誤的代碼,就是用apply改變一個構造函數的this,緊接著又用這個構造函數創建新對象,毫無疑問這是錯誤的,遺憾的是找不到這段錯誤代碼的出處了。
三、函數柯里化函數柯里化是與函數綁定緊密相關的一個主題,它用于創建已經設置好了一個或者多個參數的函數。函數柯里化的基本方法與函數綁定是一樣的:使用一個閉包返回一個函數。
柯里化函數通常創建步驟如下:調用另一個函數并為它傳入要柯里化的函數和必要參數。同樣方式如下:
function curry(fn){ var args = Array.prototype.slice.call(arguments,1); return function(){ var innerArgs = Array.prototype.slice.call(arguments); var finalArgs = args.concat(innerArgs); return fn.apply(null, finalArgs); }; }有沒有感到很熟悉,其實上面bind方法的兩個實現版本都用到了函數柯里化,區別在于,這里的通用函數沒用考慮到執行環境。
曾經看過一段類似函數柯里化的代碼,私以為很巧妙,如下:
假如有一個對象數組,想要根據對象的某個屬性來對其進行排序。而傳遞給sort方法的比較函數只能接受兩個參數,即比較的值,這樣就無法指定排序的對象屬性了。如何將需要三個參數的函數轉化為滿足要求的僅需要兩個參數?要解決這個問題,可以定義一個函數,它接收一個屬性名,然后根據這個屬性名創建并返回一個比較函數,如下:
function createComparisionFunction(property){ return function(obj1,obj2){ return obj1[property]-obj2[property]; }; }四、參考文獻1.Javascript中bind()方法的使用與實現.
2.javascript原生一步步實現bind分析.
3.JS中的bind方法與函數柯里化.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/86648.html
摘要:引言上一節介紹了高階函數的定義,并結合實例說明了使用高階函數和不使用高階函數的情況。我們期望函數輸出,但是實際上調用柯里化函數時,所以調用時就已經執行并輸出了,而不是理想中的返回閉包函數,所以后續調用將會報錯。引言 上一節介紹了高階函數的定義,并結合實例說明了使用高階函數和不使用高階函數的情況。后面幾部分將結合實際應用場景介紹高階函數的應用,本節先來聊聊函數柯里化,通過介紹其定義、比較常見的...
摘要:柯里化通用式上面的柯里化函數沒涉及到高階函數,也不具備通用性,無法轉換形參個數任意或未知的函數,我們接下來封裝一個通用的柯里化轉換函數,可以將任意函數轉換成柯里化。 showImg(https://segmentfault.com/img/remote/1460000018998373); 閱讀原文 前言 在 JavaScript 中,柯里化和反柯里化是高階函數的一種應用,在這之前...
摘要:作為函數式編程語言,帶來了很多語言上的有趣特性,比如柯里化和反柯里化。在一些函數式編程語言中,會定義一個特殊的占位變量。個人理解不知道對不對延遲執行柯里化的另一個應用場景是延遲執行。不斷的柯里化,累積傳入的參數,最后執行。作為函數式編程語言,JS帶來了很多語言上的有趣特性,比如柯里化和反柯里化。 這里可以對照另外一篇介紹 JS 反柯里化 的文章一起看~ 1. 簡介 柯里化(Currying)...
摘要:簡介柯里化,又稱部分求值,是把接收多個參數的函數變成接受一個單一參數最初函數的第一個參數的函數,并且返回接受剩余的參數而且返回結果的新函數的技術。按照作者的說法,所謂柯里化就是使函數理解并處理部分應用。的思想極大地助于提升函數的復用性。 簡介 柯里化(Currying),又稱部分求值(Partial Evaluation),是把接收多個參數的函數變成接受一個單一參數(最初函數的第一個...
摘要:面試題實現結果,題的核心就是問的的柯里化先說說什么是柯里化,看過許多關于柯里化的文章,始終搞不太清楚,例如柯里化是把接受多個參數的函數變換成接受一個單一參數最初函數的第一個參數的函數,并且返回接受余下的參數且返回結果的新函數的技術。 面試題:實現add(1)(2)(3) //結果 = 6,題的核心就是問的js的柯里化 先說說什么是柯里化,看過許多關于柯里化的文章,始終搞不太清楚,例如...
閱讀 676·2021-11-15 11:37
閱讀 4129·2021-09-09 09:34
閱讀 3571·2019-08-30 15:52
閱讀 2611·2019-08-29 14:03
閱讀 2852·2019-08-26 13:36
閱讀 1594·2019-08-26 12:16
閱讀 1601·2019-08-26 11:45
閱讀 3492·2019-08-23 18:41