摘要:返回的函數可以作為構造函數使用被用作構造函數時,應指向出來的實例,同時有屬性,其指向實例的原型。判斷當前被調用時,是用于普通的還是用于構造函數從而更改指向。運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的屬性。
寫在最前
最近開始重新學習一波js,框架用久了有些時候覺得這樣子應該可以實現發現就真的實現了,但是為什么這么寫好像又說不太清楚,之前讀了LucasHC以及冴羽的兩篇關于bind的文章感覺自己好像基礎知識都還給體育老師了哈哈哈,所以危機感爆棚,趕緊重頭復習一遍。本次主要圍繞bind是什么;做了什么;自己怎么實現一個bind,這三個部分。其中會包含一些細節代碼的探究,往下看就知道。
所以bind是什么bind()方法創建一個新的函數, 當被調用時,將其this關鍵字設置為提供的值,在調用新函數時,在任何提供之前提供一個給定的參數序列。
var result = fun.bind(thisArg[, arg1[, arg2[, ...]]]) result(newArg1, newArg2...)
沒看懂沒事接著往下看。
bind到底做了什么從上面的介紹中可以看出三點。首先調用bind方法會返回一個新的函數(這個新的函數的函數體應該和fun是一樣的)。同時bind中傳遞兩個參數,第一個是this指向,即傳入了什么this就等于什么。如下代碼所示:
this.value = 2 var foo = { value: 1 } var bar = function() { console.log(this.value) } var result = bar.bind(foo) bar() // 2 result() // 1,即this === foo
第二個參數為一個序列,你可以傳遞任意數量的參數到其中。并且會預置到新函數參數之前。
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { console.log(name) // "An" console.log(age) // 22 console.log(school) // "家里蹲大學" } var result = bar.bind(foo, "An") //預置了部分參數"An" result(22, "家里蹲大學") //這個參數會和預置的參數合并到一起放入bar中
我們可以看出在最后調用 result(22, "家里蹲大學") 的時候,其內部已經包含了在調用bind的時候傳入的 "An"。
一句話總結:調用bind,就會返回一個新的函數。這個函數里面的this就指向bind的第一個參數,同時this后面的參數會提前傳給這個新的函數。調用該新的函數時,再傳遞的參數會放到預置的參數后一起傳遞進新函數。
自己實現一個bind 實現一個bind需要實現以下兩個功能返回一個函數,綁定this,傳遞預置參數
bind返回的函數可以作為構造函數使用。故作為構造函數時應使得this失效,但是傳入的參數依然有效
1、返回一個函數,綁定this,傳遞預置參數this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { console.log(name) // "An" console.log(age) // 22 console.log(school) // "家里蹲大學" console.log(this.value) // 1 } Function.prototype.bind = function(newThis) { var aArgs = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的預置參數序列 var that = this return function() { return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments))) //綁定this同時將調用時傳遞的序列和預置序列進行合并 } } var result = bar.bind(foo, "An") result(22, "家里蹲大學")
這里面有一個細節就是Array.prototype.slice.call(arguments, 1) 這句話,我們知道arguments這個變量可以拿到函數調用時傳遞的參數,但不是一個數組,但是其具有一個length屬性。為什么如此調用就可以將其變為純數組了呢。那么我們就需要回到V8的源碼來進行分析。#這個版本的源碼為早期版本,內容相對少一些。
function ArraySlice(start, end) { var len = ToUint32(this.length); //需要傳遞this指向對象,那么call(arguments), //便可將this綁定到arguments,拿到其length屬性。 var start_i = TO_INTEGER(start); var end_i = len; if (end !== void 0) end_i = TO_INTEGER(end); if (start_i < 0) { start_i += len; if (start_i < 0) start_i = 0; } else { if (start_i > len) start_i = len; } if (end_i < 0) { end_i += len; if (end_i < 0) end_i = 0; } else { if (end_i > len) end_i = len; } var result = []; if (end_i < start_i) return result; if (IS_ARRAY(this)) SmartSlice(this, start_i, end_i - start_i, len, result); else SimpleSlice(this, start_i, end_i - start_i, len, result); result.length = end_i - start_i; return result; };
從源碼中可以看到通過call將arguments下的length屬性賦給slice后,便可通過 start_i & end_i來獲得最后的數組,所以不需要傳遞進slice時就是一個純數組最后也可以得到一個數組變量。
2、bind返回的函數可以作為構造函數使用被用作構造函數時,this應指向new出來的實例,同時有prototype屬性,其指向實例的原型。
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { ... console.log("this.value", this.value) } Function.prototype.bind = function(newThis) { var aArgs = Array.prototype.slice.call(arguments, 1) var that = this //that始終指向bar var NoFunc = function() {} var resultFunc = function() { return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments))) } NoFunc.prototype = that.prototype //that指向bar resultFunc.prototype = new NoFunc() return resultFunc } var result = bar.bind(foo, "An") result.prototype.name = "Lsc" // 有prototype屬性 var person = new result(22, "家里蹲大學") console.log("person", person.name) //"Lsc"上面這段模擬代碼做了兩件重要的事。 1.給返回的函數模擬一個prototype屬性。,因為通過構造函數new出來的實例可以查詢到原型上定義的屬性和方法
var NoFunc = function() {} ... NoFunc.prototype = that.prototype //that指向bar resultFunc.prototype = new NoFunc() return resultFunc
通過上面代碼可以看出,that始終指向bar。同時返回的函數已經繼承了that.prototype即bar.prototype。為什么不直接讓返回的函數的prototype屬性resultFunc.prototype 等于為bar(that).prototype呢,這是因為任何new出來的實例都可以訪問原型鏈。如果直接賦值那么new出來的對象可以直接修改bar函數的原型鏈,這也就是是原型鏈污染。所以我們采用繼承的方式(將構造函數的原型鏈賦值為父級構造函數的實例),讓new出來的對象的原型鏈與bar脫離關系。
2.判斷當前被調用時,this是用于普通的bind還是用于構造函數從而更改this指向。如何判斷當前this指向了哪里呢,通過第一點我們已經知道,通過bind方法返回的新函數已經有了原型鏈,剩下需要我們做的就是改變this的指向就可以模擬完成了。通過什么來判斷當前被調用是以何種姿勢呢。答案是instanceof 。
instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性。
// 定義構造函數 function C(){} function D(){} var o = new C(); // true,因為 Object.getPrototypeOf(o) === C.prototype o instanceof C; // false,因為 D.prototype不在o的原型鏈上 o instanceof D;
從上面可以看出,instanceof可以判斷出一個對象是否是由這個函數new出來的,如果是new出來的,那么這個對象的原型鏈應為該函數的prototype.
所以我們來看這段關鍵的返回的函數結構:
var resultFunc = function() { return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments))) }
在這其中我們要先認清this instanceof that 中的this是bind函數被調用后,返回的新函數中的this。所以這個this可能執行在普通的作用域環境,同時也可能被new一下從而改變自己的指向。再看that,that始終指向了bar,同時其原型鏈that.prototype是一直存在的。所以如果現在這個新函數要做new操作,那么this指向了新函數,那么 this instanceof that === true, 所以在apply中傳入this為指向,即指向新函數。如果是普通調用,那么this不是被new出來的,即新函數不是作為構造函數,this instanceof that === false就很顯而易見了。這個時候是正常的bind調用。將調用的第一個參數作為this的指向即可。
完整代碼(MDN下的實現)if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== "function") { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable"); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; if (this.prototype) { // Function.prototype doesn"t have a prototype property fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }; }
可以看到,其首先做了當前是否支持bind的判定,不支持再實行兼容。同時判斷調用這個方法的對象是否是個函數,如果不是則報錯。
同時這個模擬的方法也有一些缺陷,可關注MDN上的Polyfill部分
小結模擬bind實現最大的一個缺陷是,模擬出來的函數中會一直存在prototype屬性,但是原生的bind作為構造函數是沒有prototype的,這點打印一下即可知。不過這樣子new出來的實例沒有原型鏈,那么它的意義是什么呢。如果哪天作者知道了意義會更新在這里的=。= 如果說錯的地方歡迎指正,一起交流哈哈。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/90678.html
摘要:也就是說當返回的函數作為構造函數的時候,時指定的值會失效,但傳入的參數依然生效。構造函數效果的優化實現但是在這個寫法中,我們直接將,我們直接修改的時候,也會直接修改函數的。 JavaScript深入系列第十一篇,通過bind函數的模擬實現,帶大家真正了解bind的特性 bind 一句話介紹 bind: bind() 方法會創建一個新函數。當這個新函數被調用時,bind() 的第一個參數...
摘要:簡介柯里化,又稱部分求值,是把接收多個參數的函數變成接受一個單一參數最初函數的第一個參數的函數,并且返回接受剩余的參數而且返回結果的新函數的技術。按照作者的說法,所謂柯里化就是使函數理解并處理部分應用。的思想極大地助于提升函數的復用性。 簡介 柯里化(Currying),又稱部分求值(Partial Evaluation),是把接收多個參數的函數變成接受一個單一參數(最初函數的第一個...
摘要:但是三作為構造函數時函數其實還有一個非常重要的特點返回的函數如果作為構造函數,搭配關鍵字出現的話,我們的綁定就需要被忽略。其次,當返回的函數作為構造函數時,之前綁定的會失效。 本文共 1100 字,讀完只需 4 分鐘 概述 前一篇文章我們嘗試模擬實現了 call 和 apply 方法,其實 bind 函數也可以用來改變 this 的指向。bind 和 call和 apply 兩者的區別...
摘要:系統,扎實的語言基礎是一個優秀的前端工程師必須具備的。第一個參數為調用函數時的指向,隨后的參數則作為函數的參數并調用,也就是。和的區別只有一個,就是它只有兩個參數,而且第二個參數為調用函數時的參數構成的數組。 系統,扎實的 javascript 語言基礎是一個優秀的前端工程師必須具備的。在看了一些關于 call,apply,bind 的文章后,我還是打算寫下這篇總結,原因其實有好幾個。...
摘要:那么,它到底是如何工作的呢讓我們從一種更簡單的實現開始實際上這種實現代碼更短,并且更易讀是函數原型中的一個函數,它調用函數,使用第一個參數作為參數,并傳遞剩余參數作為被調用函數的參數。 原文:The Most Clever Line of JavaScript 作者:Seva Zaikov 原文 最近 一個朋友 發給我一段非常有趣的 JavaScript 代碼,是他在某個 開源庫中...
摘要:支持綁定大多數的結構,包括和中引入的。枚舉支持枚舉和枚舉類。雖然還有進一步優化的空間,但到目前為止,它在實際應用程序中的性能已經被證明是完全可以接受的。 翻譯:云荒杯傾 Embind用于綁定C++函數和類到JavaScript,這樣編譯代碼就能在js中以一種很自然的方式來使用。Embind也支持從C++調JavaScript的class。 Embind支持綁定大多數C++的結構,包括C...
閱讀 1300·2021-11-24 09:39
閱讀 2672·2021-09-30 09:47
閱讀 1333·2021-09-22 15:15
閱讀 2419·2021-09-10 10:51
閱讀 1969·2019-08-30 15:55
閱讀 2982·2019-08-30 11:06
閱讀 903·2019-08-30 10:53
閱讀 840·2019-08-29 17:26