摘要:點擊那么面試官可能會問是否想過到底做了什么,怎么模擬實現呢。另外前不久寫過一篇文章面試官問能否模擬實現的操作符。所以相當于調用時,的返回值函數內部要模擬實現實現的操作。文章中的例子和測試代碼放在中模擬實現。
前言
用過React的同學都知道,經常會使用bind來綁定this。
import React, { Component } from "react"; class TodoItem extends Component{ constructor(props){ super(props); this.handleClick = this.handleClick.bind(this); } handleClick(){ console.log("handleClick"); } render(){ return (點擊); }; } export default TodoItem;
那么面試官可能會問是否想過bind到底做了什么,怎么模擬實現呢。
附上之前寫文章寫過的一段話:已經有很多模擬實現bind的文章,為什么自己還要寫一遍呢。學習就好比是座大山,人們沿著不同的路登山,分享著自己看到的風景。你不一定能看到別人看到的風景,體會到別人的心情。只有自己去登山,才能看到不一樣的風景,體會才更加深刻。
先看一下bind是什么。從上面的React代碼中,可以看出bind執行后是函數,并且每個函數都可以執行調用它。
眼見為實,耳聽為虛。讀者可以在控制臺一步步點開例子1中的obj:
var obj = {}; console.log(obj); console.log(typeof Function.prototype.bind); // function console.log(typeof Function.prototype.bind()); // function console.log(Function.prototype.bind.name); // bind console.log(Function.prototype.bind().name); // bound因此可以得出結論1:
1、bind是Functoin原型鏈中Function.prototype的一個屬性,每個函數都可以調用它。
2、bind本身是一個函數名為bind的函數,返回值也是函數,函數名是bound 。(打出來就是bound加上一個空格)。
知道了bind是函數,就可以傳參,而且返回值"bound "也是函數,也可以傳參,就很容易寫出例子2:
后文統一 bound 指原函數original bind之后返回的函數,便于說明。
var obj = { name: "軒轅Rowboat", }; function original(a, b){ console.log(this.name); console.log([a, b]); return false; } var bound = original.bind(obj, 1); var boundResult = bound(2); // "軒轅Rowboat", [1, 2] console.log(boundResult); // false console.log(original.bind.name); // "bind" console.log(original.bind.length); // 1 console.log(original.bind().length); // 2 返回original函數的形參個數 console.log(bound.name); // "bound original" console.log((function(){}).bind().name); // "bound " console.log((function(){}).bind().length); // 0由此可以得出結論2:
1、調用bind的函數中的this指向bind()函數的第一個參數。
2、傳給bind()的其他參數接收處理了,bind()之后返回的函數的參數也接收處理了,也就是說合并處理了。
3、并且bind()后的name為bound + 空格 + 調用bind的函數名。如果是匿名函數則是bound + 空格。
4、bind后的返回值函數,執行后返回值是原函數(original)的返回值。
5、bind函數形參(即函數的length)是1。bind后返回的bound函數形參不定,根據綁定的函數原函數(original)形參個數確定。
根據結論2:我們就可以簡單模擬實現一個簡版bindFn
// 第一版 修改this指向,合并參數 Function.prototype.bindFn = function bind(thisArg){ if(typeof this !== "function"){ throw new TypeError(this + "must be a function"); } // 存儲函數本身 var self = this; // 去除thisArg的其他參數 轉成數組 var args = [].slice.call(arguments, 1); var bound = function(){ // bind返回的函數 的參數轉成數組 var boundArgs = [].slice.call(arguments); // apply修改this指向,把兩個函數的參數合并傳給self函數,并執行self函數,返回執行結果 return self.apply(thisArg, args.concat(boundArgs)); } return bound; } // 測試 var obj = { name: "軒轅Rowboat", }; function original(a, b){ console.log(this.name); console.log([a, b]); } var bound = original.bindFn(obj, 1); bound(2); // "軒轅Rowboat", [1, 2]
如果面試官看到你答到這里,估計對你的印象60、70分應該是會有的。
但我們知道函數是可以用new來實例化的。那么bind()返回值函數會是什么表現呢。
接下來看例子3:
var obj = { name: "軒轅Rowboat", }; function original(a, b){ console.log("this", this); // original?{} console.log("typeof this", typeof this); // object this.name = b; console.log("name", this.name); // 2 console.log("this", this); // original?{name: 2} console.log([a, b]); // 1, 2 } var bound = original.bind(obj, 1); var newBoundResult = new bound(2); console.log(newBoundResult, "newBoundResult"); // original?{name: 2}
從例子3種可以看出this指向了new bound()生成的新對象。
可以分析得出結論3:1、bind原先指向obj的失效了,其他參數有效。
2、new bound的返回值是以original原函數構造器生成的新對象。original原函數的this指向的就是這個新對象。
另外前不久寫過一篇文章:面試官問:能否模擬實現JS的new操作符。簡單摘要:
new做了什么:
1.創建了一個全新的對象。
2.這個對象會被執行[[Prototype]](也就是__proto__)鏈接。
3.生成的新對象會綁定到函數調用的this。
4.通過new創建的每個對象將最終被[[Prototype]]鏈接到這個函數的prototype對象上。
5.如果函數沒有返回對象類型Object(包含Functoin, Array, Date, RegExg, Error),那么new表達式中的函數調用會自動返回這個新的對象。
所以相當于new調用時,bind的返回值函數bound內部要模擬實現new實現的操作。
話不多說,直接上代碼。
// 第三版 實現new調用 Function.prototype.bindFn = function bind(thisArg){ if(typeof this !== "function"){ throw new TypeError(this + " must be a function"); } // 存儲調用bind的函數本身 var self = this; // 去除thisArg的其他參數 轉成數組 var args = [].slice.call(arguments, 1); var bound = function(){ // bind返回的函數 的參數轉成數組 var boundArgs = [].slice.call(arguments); var finalArgs = args.concat(boundArgs); // new 調用時,其實this instanceof bound判斷也不是很準確。es6 new.target就是解決這一問題的。 if(this instanceof bound){ // 這里是實現上文描述的 new 的第 1, 2, 4 步 // 1.創建一個全新的對象 // 2.并且執行[[Prototype]]鏈接 // 4.通過`new`創建的每個對象將最終被`[[Prototype]]`鏈接到這個函數的`prototype`對象上。 // self可能是ES6的箭頭函數,沒有prototype,所以就沒必要再指向做prototype操作。 if(self.prototype){ // ES5 提供的方案 Object.create() // bound.prototype = Object.create(self.prototype); // 但 既然是模擬ES5的bind,那瀏覽器也基本沒有實現Object.create() // 所以采用 MDN ployfill方案 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/create function Empty(){} Empty.prototype = self.prototype; bound.prototype = new Empty(); } // 這里是實現上文描述的 new 的第 3 步 // 3.生成的新對象會綁定到函數調用的`this`。 var result = self.apply(this, finalArgs); // 這里是實現上文描述的 new 的第 5 步 // 5.如果函數沒有返回對象類型`Object`(包含`Functoin`, `Array`, `Date`, `RegExg`, `Error`), // 那么`new`表達式中的函數調用會自動返回這個新的對象。 var isObject = typeof result === "object" && result !== null; var isFunction = typeof result === "function"; if(isObject || isFunction){ return result; } return this; } else{ // apply修改this指向,把兩個函數的參數合并傳給self函數,并執行self函數,返回執行結果 return self.apply(thisArg, finalArgs); } }; return bound; }
面試官看到這樣的實現代碼,基本就是滿分了,心里獨白:這小伙子/小姑娘不錯啊。不過可能還會問this instanceof bound不準確問題。
上文注釋中提到this instanceof bound也不是很準確,ES6 new.target很好的解決這一問題,我們舉個例子4:
function Student(name){ if(this instanceof Student){ this.name = name; console.log("name", name); } else{ throw new Error("必須通過new關鍵字來調用Student。"); } } var student = new Student("軒轅"); var notAStudent = Student.call(student, "Rowboat"); // 不拋出錯誤,且執行了。 console.log(student, "student", notAStudent, "notAStudent"); function Student2(name){ if(typeof new.target !== "undefined"){ this.name = name; console.log("name", name); } else{ throw new Error("必須通過new關鍵字來調用Student2。"); } } var student2 = new Student2("軒轅"); var notAStudent2 = Student2.call(student2, "Rowboat"); console.log(student2, "student2", notAStudent2, "notAStudent2"); // 拋出錯誤
細心的同學可能會發現了這版本的代碼沒有實現bind后的bound函數的nameMDN Function.name和lengthMDN Function.length。面試官可能也發現了這一點繼續追問,如何實現,或者問是否看過es5-shim的源碼實現L201-L335。如果不限ES版本。其實可以用ES5的Object.defineProperties來實現。
Object.defineProperties(bound, { "length": { value: self.length, }, "name": { value: "bound " + self.name, } });es5-shim的源碼實現bind
直接附上源碼(有刪減注釋和部分修改等)
var $Array = Array; var ArrayPrototype = $Array.prototype; var $Object = Object; var array_push = ArrayPrototype.push; var array_slice = ArrayPrototype.slice; var array_join = ArrayPrototype.join; var array_concat = ArrayPrototype.concat; var $Function = Function; var FunctionPrototype = $Function.prototype; var apply = FunctionPrototype.apply; var max = Math.max; // 簡版 源碼更復雜些。 var isCallable = function isCallable(value){ if(typeof value !== "function"){ return false; } return true; }; var Empty = function Empty() {}; // 源碼是 defineProperties // 源碼是bind筆者改成bindFn便于測試 FunctionPrototype.bindFn = function bind(that) { var target = this; if (!isCallable(target)) { throw new TypeError("Function.prototype.bind called on incompatible " + target); } var args = array_slice.call(arguments, 1); var bound; var binder = function () { if (this instanceof bound) { var result = apply.call( target, this, array_concat.call(args, array_slice.call(arguments)) ); if ($Object(result) === result) { return result; } return this; } else { return apply.call( target, that, array_concat.call(args, array_slice.call(arguments)) ); } }; var boundLength = max(0, target.length - args.length); var boundArgs = []; for (var i = 0; i < boundLength; i++) { array_push.call(boundArgs, "$" + i); } // 這里是Function構造方式生成形參length $1, $2, $3... bound = $Function("binder", "return function (" + array_join.call(boundArgs, ",") + "){ return binder.apply(this, arguments); }")(binder); if (target.prototype) { Empty.prototype = target.prototype; bound.prototype = new Empty(); Empty.prototype = null; } return bound; };
你說出es5-shim源碼bind實現,感慨這代碼真是高效、嚴謹。面試官心里獨白可能是:你就是我要找的人,薪酬福利你可以和HR去談下。
最后總結一下1、bind是Function原型鏈中的Function.prototype的一個屬性,它是一個函數,修改this指向,合并參數傳遞給原函數,返回值是一個新的函數。
2、bind返回的函數可以通過new調用,這時提供的this的參數被忽略,指向了new生成的全新對象。內部模擬實現了new操作符。
3、es5-shim源碼模擬實現bind時用Function實現了length。
事實上,平時其實很少需要使用自己實現的投入到生成環境中。但面試官通過這個面試題能考察很多知識。比如this指向,原型鏈,閉包,函數等知識,可以擴展很多。
讀者發現有不妥或可改善之處,歡迎指出。另外覺得寫得不錯,可以點個贊,也是對筆者的一種支持。
文章中的例子和測試代碼放在github中bind模擬實現 github。bind模擬實現 預覽地址 F12看控制臺輸出,結合source面板查看效果更佳。
// 最終版 刪除注釋 詳細注釋版請看上文 Function.prototype.bind = Function.prototype.bind || function bind(thisArg){ if(typeof this !== "function"){ throw new TypeError(this + " must be a function"); } var self = this; var args = [].slice.call(arguments, 1); var bound = function(){ var boundArgs = [].slice.call(arguments); var finalArgs = args.concat(boundArgs); if(this instanceof bound){ if(self.prototype){ function Empty(){} Empty.prototype = self.prototype; bound.prototype = new Empty(); } var result = self.apply(this, finalArgs); var isObject = typeof result === "object" && result !== null; var isFunction = typeof result === "function"; if(isObject || isFunction){ return result; } return this; } else{ return self.apply(thisArg, finalArgs); } }; return bound; }參考
OshotOkill翻譯的 深入理解ES6 簡體中文版 - 第三章 函數(雖然我是看的紙質書籍,但推薦下這本在線的書)
MDN Function.prototype.bind
冴羽: JavaScript深入之bind的模擬實現
《react狀態管理與同構實戰》侯策:從一道面試題,到“我可能看了假源碼”
作者:常以軒轅Rowboat若川為名混跡于江湖。前端路上 | PPT愛好者 | 所知甚少,唯善學。
個人博客
segmentfault前端視野專欄,開通了前端視野專欄,歡迎關注
掘金專欄,歡迎關注
知乎前端視野專欄,開通了前端視野專欄,歡迎關注
github,歡迎follow~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/108792.html
摘要:之前寫過一篇文章面試官問能否模擬實現的和方法就是利用對象上的函數指向這個對象,來模擬實現和的。雖然實際使用時不會顯示返回,但面試官會問到。非嚴格模式下,和,指向全局對象 前言 面試官出很多考題,基本都會變著方式來考察this指向,看候選人對JS基礎知識是否扎實。讀者可以先拉到底部看總結,再谷歌(或各技術平臺)搜索幾篇類似文章,看筆者寫的文章和別人有什么不同(歡迎在評論區評論不同之處),...
摘要:之前寫過兩篇面試官問能否模擬實現的操作符和面試官問能否模擬實現的方法其中模擬方法時是使用的和修改指向。但面試官可能問能否不用和來實現呢。使用模擬實現的瀏覽器環境非嚴格模式方法的屬性是。 之前寫過兩篇《面試官問:能否模擬實現JS的new操作符》和《面試官問:能否模擬實現JS的bind方法》 其中模擬bind方法時是使用的call和apply修改this指向。但面試官可能問:能否不用cal...
摘要:譯立即執行函數表達式處理支持瀏覽器環境微信小程序。學習整體架構,利于打造屬于自己的函數式編程類庫。下一篇文章可能是學習的源碼整體架構。也可以加微信,注明來源,拉您進前端視野交流群。 前言 上一篇文章寫了jQuery整體架構,學習 jQuery 源碼整體架構,打造屬于自己的 js 類庫 雖然看過挺多underscore.js分析類的文章,但總感覺少點什么。這也許就是紙上得來終覺淺,絕知此...
摘要:接下來繼續看升級版例子例子軒轅軒轅軒轅是瀏覽器實現的查看原型方案。模擬實現知道了這些現象,我們就可以模擬實現操作符。 前言 用過Vuejs的同學都知道,需要用new操作符來實例化。 new Vue({ el: #app, mounted(){}, }); 那么面試官可能會問是否想過new到底做了什么,怎么模擬實現呢。 附上之前寫文章寫過的一段話:已經有很多模擬實現new...
摘要:用過的讀者知道,經常用繼承。部分源碼使用點擊這里查看源碼面試官可以順著這個問繼承的相關問題,比如的繼承用如何實現。主要就是三點子類構造函數的指向父類構造器,繼承父類的靜態方法子類構造函數的的指向父類構造器的,繼承父類的方法。 用過React的讀者知道,經常用extends繼承React.Component。 // 部分源碼 function Component(props, conte...
閱讀 1357·2021-09-02 10:19
閱讀 1101·2019-08-26 13:25
閱讀 2108·2019-08-26 11:37
閱讀 2413·2019-08-26 10:18
閱讀 2676·2019-08-23 16:43
閱讀 2989·2019-08-23 16:25
閱讀 775·2019-08-23 15:53
閱讀 3297·2019-08-23 15:11