深入理解JavaScript設計模式之閉包與高階函數

前言小序

一場失敗面試

2023年的某一天,一場讓我印象深刻的面試:
面試官:

“你了解閉包嗎?請說一下你對閉包的理解。”

我自信滿滿地答道:

“閉包就是函數里面套函數,里面的函數可以訪問外部函數的變量。”

面試官點點頭,繼續問:

“那你知道高階函數嗎?”

我依舊不慌不忙:

“高階函數嘛,就是能接收函數作為參數,或者返回一個函數的函數,在函數里執行回調的那種。”

當時我心里還美滋滋地想:這不都是我看過的八股文嘛,穩了!走出面試室,我仿佛已經收到了 offer,決定先去吃頓大餐犒勞自己。吃完飯回到家,手機一響,是面試來電。
電話那頭傳來溫柔而熟悉的聲音:

“很抱歉,您未能通過本次面試……盡管這次未能成功,我們仍希望您能找到更適合您的工作機會……祝您未來職業道路順利……吧啦吧啦吧啦……”

通話結束,我的心情也隨著“嘟——”的一聲沉入谷底,那一晚,我久久不能平靜,回想起這場看似順利的面試,卻以失敗告終,我突然意識到一個問題:我背得再標準,說得再流利,但那不是我真正理解的東西。于是,我下定決心:“一定要搞懂閉包到底是什么!”

面試后的覺醒

學習,啟動!
我翻開了《JavaScript設計模式與開發實踐》,一頁頁啃,一遍遍讀,反復琢磨,不斷練習。雖然學得不算特別深入,但也算是從“八股文選手”邁向了“理解者”的第一步。現在,我想用我自己的話,重新講一遍我對閉包的理解:

“閉包是一個函數能夠訪問并記住它定義時所處的詞法環境,即使這個函數在其作用域外執行。它不僅能延長變量的生命周期,還能封裝私有狀態,是JavaScript 中非常強大且常用的一種機制。”

至于高階函數,我也有了更深的認識:

“高階函數并不只是‘傳函數進去然后調用’這么簡單。它的核心在于抽象行為 —— 你可以把邏輯像數據一樣傳遞,從而寫出更靈活、可復用、富有表現力的代碼。”

我希望下一次面試,我不再是那個只會背誦八股文的“偽高手”,而是能用自己的語言,準確表達出技術背后思想的那個“真正的我”。一雪前恥,就在下一場面試。加油!

閉包

在前端開發JavaScript這門語言中,JavaScript不僅是前端開發得力助手,還是一個多才多藝的小能手! 為什么這么說呢,因為javaScript不僅能干面向對象編程的活,還掌握了一些函數式編程的小技巧——比如閉包。
閉包這東西,聽起來高深莫測,新手一聽到閉包就懵,面試官一問閉包就巴拉巴拉開始背八股文:“函數里面套函數,訪問外部變量…”,然后被pass掉😅!
經過反復學習研磨,我就對《JavaScript設計模式與開發實踐》第一部分第三章進行個人理解與總結。如果覺得我的總結錯誤請"Coach Me !!" 哇哈哈哈哈。

變量作用域:誰的地盤聽誰的

JavaScript編程中,變量也是有"勢力范圍"的,這個范圍叫做作用域,比如你在函數里定義了一個變量,那它就是一個宅男,只能在這個函數內部活動,外面是看不到他的。

function sayHi() {var name = '小明';console.log(name); // 小明出來打招呼了
}
sayHi();
console.log(name); // Uncaught ReferenceError: name is not defined

看,小明只在函數里活躍,一出函數門,世界就忘了他。但 JavaScript 的函數有個特點:它像個玻璃罩子,從內往外能看到外面的東西,但從外往內是看不到里面的。這就是所謂的作用域鏈。再比如:

<html><body><script>// 全局變量 a:爺爺,在客廳里大聲宣布:"我是1!"var a = 1;// func1 是爸爸,有自己的小秘密 b = 2var func1 = function () {var b = 2;console.log("爸爸說:我有秘密b =", b);// func2 是兒子,偷偷藏了一個寶貝 c = 3var func2 = function () {var c = 3;console.log("兒子說:我能看到爸爸的秘密b =", b); // 看得見爸爸console.log("兒子說:我也能看見爺爺a =", a); // 看得見爺爺};func2(); // 兒子執行完畢,把c藏回房間// 爸爸試圖查看兒子的玩具c...console.log("爸爸說:我想看看兒子的c =", c); // ? 找不到!ReferenceError!};func1(); // 全家開始聚會</script></body>
</html>

輸出結果
深入理解JavaScript設計模式之閉包與高階函數
總結:JavaScript中,函數可以用來創造作用域,在函數里面可以看到外面的變量,在函數外面則無法看到函數里面的變量,搜索過程會隨著代碼執行環境創建的作用域鏈往外層逐層搜索,一直搜索到全局對象為止,變量的搜索是從內到外而不是從外到內的。

變量的生命周期:該走了,不該走的還在

變量不僅有“地盤”,還有“壽命”,全局變量活得久,除非手動刪除,否則它一直活在,而函數里的局部變量呢?通常函數執行完,它們就“退休了”,但是有時候,局部變量就是“賴著不走”,比如:

 var func = function () {var a = 1;return function () {a++;console.log(a);};};var f = func();f(); // 輸出:2f(); // 輸出:3f(); // 輸出:4f(); // 輸出:5

上面代碼中,a明明是在函數里聲明的,按理說函數執完它就該"退休"了,但它偏偏沒有走,為啥? 因為有一個函數(也就是返回的哪個匿名函數)還”愛著它“,它被閉包捕獲了。于是這個變量不再只是一個普通的變量,而是變成了"有故事的變量”。局部變量的生命看起來被延續了!!!
利用閉包,可以完成很多有趣的工作,比如下面的經典案例:“假設頁面上有5個div節點,我們通過循環來給每個div綁定onclick實踐,按照索引順序,點擊第1個div時彈出0,點擊第2個div彈出1,以此類推”:

<html><body><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><script>var nodes = document.getElementsByTagName("div");for (var i = 0, len = nodes.length; i < len; i++) {nodes[i].onclick = function () {alert(i);};}</script></body>
</html>

欻欻欻,這段簡單的事件綁定就寫好了,但是當你運行的時候,你就會發現吳倫點擊哪個div,最后彈出的結果都是5,這是因為div節點的onclick事件被異步出發的,當事件觸發的時候,for循環早就結束了,此時變量i的值已經是5,所以在div的onclick事件函數中順著作用域鏈從內到外查找i的時候,查找到的值總是5。
深入理解JavaScript設計模式之閉包與高階函數
上面的BUG可以通過閉包去解決,加入閉包,把每次循環的i值都封閉起來,當事件函數中順著作用域鏈中從內到外查找i變量的時候,會先找到被封閉在閉包環境中的i,如果有5div,這里的i就分別是0,1,2,3,4

<html><body><div>1</div><div>2</div><div>3</div><div>4</div><div>5</div><script>var nodes = document.getElementsByTagName("div");for (var i = 0, len = nodes.length; i < len; i++) {(function (i) {nodes[i].onclick = function () {alert(i);return;};})(i);}</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數
根據上面了解的閉包機制,可以寫一個通過閉包進行判斷數據類型的案例

<html><body><script>var Type = {};for (var i = 0, type; (type = ["String", "Array", "Number"][i++]); ) {(function (type) {Type["is" + type] = function (obj) {return (Object.prototype.toString.call(obj) === "[object " + type + "]");};})(type);}console.log(Type.isArray([])); // 輸出:trueconsole.log(Type.isString("str")); // 輸出:true</script></body>
</html>

IIFE把當前的 type 值保存下來,這樣每次循環都把當前的 type 傳進一個新的函數作用域中,確保每個生成的方法都能正確記住自己的 type。運行完這段代碼后,Type 對象變成了:

Type = {isString: function(obj) {return Object.prototype.toString.call(obj) === '[object String]';},isArray: function(obj) {return Object.prototype.toString.call(obj) === '[object Array]';},isNumber: function(obj) {return Object.prototype.toString.call(obj) === '[object Number]';}
};

輸出:
深入理解JavaScript設計模式之閉包與高階函數

閉包的更多作用:不只是談戀愛,還能干活!

1、封裝私有變量:你的變量我來守護

如果你想讓某些變量不被外界隨意修改,閉包就能幫你做到這一點,閉包可以把一些不需要暴露在全局的變量封裝成"私有變量"。假設有一個計算乘積的簡單函數:

 var mult = function () {var a = 1;for (var i = 0, l = arguments.length; i < l; i++) {a = a * arguments[i];}return a;};console.log(mult(2, 3, 4, 5));

深入理解JavaScript設計模式之閉包與高階函數
上面實現了Mult的乘積函數,你會發現,每一次都會清空a,重新計算,每一次計算都是浪費,但是我們可以借用閉包的機制實現一個通過閉包實現的緩存機機制提高這個函數的性能:

<html><body><script>var mult = (function () {var cache = {};return function () {var args = Array.prototype.join.call(arguments, ",");if (args in cache) {return cache[args];}console.log("我被執行了");var a = 1;for (var i = 0, l = arguments.length; i < l; i++) {a = a * arguments[i];}return (cache[args] = a);};})();console.log(mult(1, 2, 3, 4, 5));console.log(mult(1, 2, 3, 4));console.log(mult(1, 2, 3, 4, 5));console.log(mult(1, 2, 3, 4, 6));console.log(mult(1, 2, 3, 4, 6));console.log(mult(1, 2, 3, 4, 6));</script></body>
</html>

效果:
深入理解JavaScript設計模式之閉包與高階函數
不難發現,我調用了六次mult,但是乘積邏輯只運行了3次,將每次的乘積結果都緩存到cache中,每次先判斷是否有,如果有運行返回結果,如果沒有運行乘積邏輯。上面的乘積代碼還可以進行優化,將乘積邏輯獨立出來。

<html><body><script>var mult = (function () {var cache = {};var calculate = function () {console.log("我被執行了");// 封閉 calculate 函數var a = 1;for (var i = 0, l = arguments.length; i < l; i++) {a = a * arguments[i];}return a;};return function () {var args = Array.prototype.join.call(arguments, ",");if (args in cache) {return cache[args];}return (cache[args] = calculate.apply(null, arguments));};})();console.log(mult(1, 2, 3, 4, 5));console.log(mult(1, 2, 3, 4));console.log(mult(1, 2, 3, 4, 5));console.log(mult(1, 2, 3, 4, 6));console.log(mult(1, 2, 3, 4, 6));console.log(mult(1, 2, 3, 4, 6));</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數

2、延長變量的壽命:別著急銷毀,我還想用

在數據上報業務中,如果你以為直接在函數里面創建了一個img對象就完事了,瀏覽器可能還沒有把請求發送出去就把這個變量回收了,這個時候,我們可以用閉包吧img給鎖住,確保請求一定能發出去。有關前端埋點上報內容直通車>>>>

var report = function( src ){ var img = new Image(); img.src = src; 
}; 
report( 'http://xxx.com/getUserInfo' );

上面代碼中實現了原始圖片上報的功能,但是在低版本的瀏覽器下使用report函數進行數據上報會數據丟失,也就是說,report函數并不是每次都發起了http請求,丟失數據原因是img是report函數中的局部變量,當report函數調用結束后,img局部變量就會被立即銷毀,而此時還沒來得及發出http請求就會導致數據丟失,接下來通過閉包將img變量封閉起來,就能解決了數據上報丟失的問題了:

var report = (function () {var imgs = [];return function (src) {var img = new Image();imgs.push(img);img.src = src;};
})();
report( 'http://xxx.com/getUserInfo' );

閉包就像是一個“拖延癥患者”,不讓變量輕易被銷毀。

閉包的面向對象設計:換湯不換藥

你說閉包厲害吧,其實它也能實現面向對象的一些功能,比如下面寫一個計數器:

<html><body><script>function createCounter() {var count = 0;return {increment: function () {count++;},get: function () {return count;},};}var counter = createCounter();counter.increment();console.log(counter.get()); // 1</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數
是不是很像一個簡單的類?只不過它是用閉包實現的,看到這里是不是覺得非常眼熟? 你想想,仔細想想哪里出現過這種結構的使用!我想你已經想到了,vuedata就是使用了類似于閉包的概念來封裝和保護組件內的數據,但是區別在于vuedata更多的結合了閉包,響應式系統以及其他高級特性的綜合方案。

用閉包實現命令模式:命令也能談戀愛

我雖然學到第一部分,還沒學到命令模式,但是經過大量的網頁搜索得出:是一種行為型設計模式,主要用于將請求封裝成對象,從而實現調用者和執行者之間的解耦,方便擴展和修改。命令模式的核心思想是將請求封裝成一個對象,使命令的發起者和執行者分離。
命令模式本來是面向對象中的一種經典設計模式,在js中可以通過閉包來實現他,比如書中封裝了一個打開電視關閉電視的命令模式:

<html><body><script>var tv = {open: function () {console.log("打開電視機");},close: function () {console.log("關上電視機");},};function createCommand(receiver) {return {execute: function () {receiver.open();},undo: function () {receiver.close();},};}var command = createCommand(tv);command.execute(); // 打開電視機command.undo(); // 關上電視機</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數
閉包在這里的作用就是吧接收者tv悄悄地保存起來,等需要的時候在進行調用。

閉包與內存管理:不是泄露,是你太執著

每次面試,問道閉包必會與“內存泄漏”拿出來一塊說事,一聽到閉包就會想:“哎呀,會內存泄漏啊!” 其實太冤枉閉包了,閉包之所以讓變量“不死”,是因為你還需要他,這不是內存泄漏,而是你主動選擇了保留變量。
但是如果你使用不恰當,比如你在DOM元素上面綁定了一個閉包,而這個閉包又引起了這個DOM元素本身,那這就形成了循環調用,這時候才可能造成內存泄漏的問題。
如果想解決內存泄漏問題其實非常的簡單:只需要即使斷開引用,讓垃圾回收機制正常工作【myObject = null;】
一句話總結:閉包不是洪水猛獸,它只是一個有感情,有記憶,有責任的函數。

高階函數

什么是高階函數:你以為的函數 VS 真相

在 JavaScript 的世界里,函數不僅僅是個執行器,它是超級英雄。為什么?因為它可以:
上得了參數舞臺(函數當參數),下得了返回結果(函數當返回值)這類能文能武、能攻能守的函數,被稱為——高階函數。

高階函數作為參數傳遞:屬實打工人

我們可以把函數當作參數傳遞,抽離出一部分容易變化的業務邏輯,把這部分業務邏輯放在函數參數中,這樣一來分離業務代碼種變化與不變的部分。其中非常常見和重要的應用場景就是常見的回調函數。“函數也能做回調?這不是打工人嗎!”

回調函數

在以往Ajax請求的使用中,回調非常非常頻繁,大名鼎鼎的“回調地獄”就處至ajax應用出現的問題,當我們想在ajax請求返回之后做一些事情,但是又不知道請求返回的時間,最常見的方案就是把callBack函數當作參數傳入發起ajax請求的方法中,等待請求完成后執行callBack的函數

 var getUserInfo = function (userId, callback) {$.ajax("http://xxx.com/getUserInfo?" + userId, function (data) {if (typeof callback === "function") {callback(data);}});};getUserInfo(13157, function (data) {alert(data.userName);});

看著像正常 AJAX,其實背地里 Callback 像極了“我寫好了函數,結果還得把邏輯交給別人決定”,屬實打工人無疑了。

典型應用:創建 100div,然后把這些div設置成隱藏,正常想法是將創建div與隱藏div的代碼硬編碼到appendDiv的創建函數中,這顯然不合理更不夠靈活,更更難以服用,于是可以將隱藏代碼抽離出來,用回調的形式傳入appendDiv方法:"哦~ 雅,實在太雅!!真的不要太雅!!!"

<html><body><script>var appendDiv = function (callback) {for (var i = 0; i < 100; i++) {var div = document.createElement("div");div.innerHTML = i;document.body.appendChild(div);if (typeof callback === "function") {callback(div);}}};appendDiv(function (node) {console.log("回調隱藏");node.style.display = "none";});</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數

Array.prototype.sort

Array.prototype.sort接收一個函數當作參數,這個函數里面封裝了數組元素的排序規則,從Array.prototype.sort這個API可以看淡,這個API不變的是對數組進行排序,而需要變是使用什么規則排序是可變的,把可變的部分封裝在函數里面,動態傳入,使Array.prototype.sort方法成為了一個非常靈活好用的方法。

//從小到大排列
[ 1, 4, 3 ].sort( function( a, b ){ return a - b; 
}); // 輸出: [ 1, 3, 4 ] //從大到小排列
[ 1, 4, 3 ].sort( function( a, b ){ return b - a; 
}); // 輸出: [ 4, 3, 1 ]

函數作為返回值輸出:什么函數還能生孩子?

啥玩意? 函數還能生孩子? 哦哦哦,看錯了,原來是返回函數呀!相比把函數當作參數傳遞,函數當作返回值輸出的應用場景那可更多了,讓函數繼續生孩子傳宗接代,哦,不對,是讓函數繼續返回一個可執行函數,那不就代表著運算過程可以一直延續了嘛!!!

判斷數據類型

小白寫法:

<html><body><script>var isString = function (obj) {return Object.prototype.toString.call(obj) === "[object String]";};var isArray = function (obj) {return Object.prototype.toString.call(obj) === "[object Array]";};var isNumber = function (obj) {return Object.prototype.toString.call(obj) === "[object Number]";};console.log(isArray([])); // 輸出:trueconsole.log(isString("str")); // 輸出:trueconsole.log(isNumber(1)); // 輸出:true</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數
這種雖然也可以進行類型的判斷,但是不難發現,這些函數大部分實現都是相同的,不同的只是 Object.prototype.toString.call( obj )返回的字符串。為了避免多余的代碼,我們嘗試把這些字符串作為參數提前值入 isType,通過循環批量注冊isType函數,大佬寫法:

<html><body><script>var Type = {};for (var i = 0, type; (type = ["String", "Array", "Number"][i++]); ) {(function (type) {Type["is" + type] = function (obj) {return (Object.prototype.toString.call(obj) === "[object " + type + "]");};})(type);}Type.isArray([]); // 輸出:trueType.isString("str"); // 輸出:true</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數

單例模式getSingle

javaScript中的單例模式大概意思是一種確保類只有一個實例,并提供全局訪問點的方式。由于 JavaScript 的語言特性(如對象字面量、模塊系統等),實現單例有多種方式,接下來通過高階函數實現一個getSingle的單例模式:

<html><body><script>var getSingle = function (fn) {var ret;return function () {return ret || (ret = fn.apply(this, arguments));};};var getScript = getSingle(function () {return document.createElement("script");});var script1 = getScript();var script2 = getScript();console.log(script1 === script2); // 輸出:true</script></body>
</html>

上面代碼是通過閉包+高階函數實現了一個惰性單例模式,確保某個程序運行過程中最多執行一次,并且始終返回相同的實例。媽的,真特娘優雅!!!
深入理解JavaScript設計模式之閉包與高階函數

高階函數AOP(面向切面編程):前后偷摸加戲AOP

所謂的AOP就是面向切面編程,就是原本安安靜靜寫個函數,突然前后各插一腳:

<html><body><script>Function.prototype.before = function (beforefn) {var __self = this; // 保存原函數的引用return function () {// 返回包含了原函數和新函數的"代理"函數beforefn.apply(this, arguments); // 執行新函數,修正 thisreturn __self.apply(this, arguments); // 執行原函數};};Function.prototype.after = function (afterfn) {var __self = this; // 保存原函數的引用return function () {var ret = __self.apply(this, arguments);afterfn.apply(this, arguments);return ret;};};var func = function () {console.log(2);};func = func.before(function () {console.log(1);}).after(function () {console.log(3);});func();</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數
上面函數把一個函數“動態織入”到另外一個函數中。

高階函數的其他應用

函數柯里化:我先不執行,等等再說

面試中,如雷貫耳的curring【函數柯里化】又稱部分求值,一個curring的函數首先會接收一些參數,接收這些參數之后,該函數并不會立即執行,而是繼續返回另外一個函數,剛傳入的參數在函數形成的閉包中被保存起來,等到函數被真正需要求值得時候,之前傳入的所有參數都會被一次性求值。
案例一、編寫一個計算每月開銷得函數,在每天結束之前,都要記錄今天花了多少錢;
沒接觸過函數柯里化之前:

 var monthlyCost = 0;var cost = function (money) {monthlyCost += money;};cost(100); // 第 1 天開銷cost(200); // 第 2 天開銷cost(300); // 第 3 天開銷//cost( 700 ); // 第 30 天開銷console.log(monthlyCost); // 輸出:600

深入理解JavaScript設計模式之閉包與高階函數
雖然通過上面代碼也能實現,但是我不想關心每天花多少錢,我只想在月底統一計算一次,如果每月的前29天,我們都只是保存好當天的開銷,直到30天才會進行求值計算,這樣就達到了要求,函數柯里化寫法如下:

<html><body><script>var cost = (function () {var args = [];return function () {if (arguments.length === 0) {var money = 0;for (var i = 0, l = args.length; i < l; i++) {money += args[i];}return money;} else {[].push.apply(args, arguments);}};})();cost(100); // 未真正求值cost(200); // 未真正求值cost(300); // 未真正求值cost(300); // 未真正求值console.log(cost()); // 求值并輸出:600</script></body>
</html>

上面還是不是一個標準的柯里化函數,接下來我們實現一個任意參數鏈式調用的標磚柯里化函數,這個函數將傳入的函數 fn 轉換為一個“延遲執行”的柯里化版本。它會收集所有傳入的參數,直到你調用時不帶參數,才真正執行原函數。
解析:當調用cost()的時候,如果明確帶上了參數,表示此時并不是真的在計算結果,而是將參數保存起來,此時讓const函數返回另外一個函數,只有當不帶參數的形式執行cost()時,才會計算結果,通過調用return arguments.callee;,每調用一次 cost(...),它都會返回自己(即 arguments.callee),所以可以繼續鏈式調用【cost(300, 300)(300)()】

<html><body><script>var currying = function (fn) {console.log("我被執行了");var args = [];return function () {if (arguments.length === 0) {return fn.apply(this, args);} else {[].push.apply(args, arguments);return arguments.callee;}};};var cost = (function () {var money = 0;return function () {console.log("我cost執行了", arguments);for (var i = 0, l = arguments.length; i < l; i++) {money += arguments[i];}return money;};})();var cost = currying(cost); // 轉化成 currying 函數cost(100); // 未真正求值cost(200); // 未真正求值cost(300)(50); // 未真正求值console.log(cost(300, 300)(300)()); // 未真正求值</script></body>
</html>

深入理解JavaScript設計模式之閉包與高階函數

非函數柯里化:函數世界的開放平臺

uncurring不像curring那樣拆分參數,而是相反把一個原來只能為某個對象服務的方法變成都能使用的通用函數,比如數組對象得push函數,其實push不止只能為數組服務,也可以為其他對象服務只要你有和數組一樣的特性即可使用,你可以理解為“去標簽化”,js本來就是鴨子語言,只要你會“嘎嘎叫”,你就是只鴨子,所以,不止數組才能調用push,如:

(function() {Array.prototype.push.call(arguments, 4);console.log(arguments); // [1, 2, 3, 4]
})(1, 2, 3);

上面代碼中就是一個類數組對象,調用了專屬于push得方法,但是上面每次都要寫.call(obj,...)實在是太麻煩,就像你每次吃火鍋都要帶上鍋和調料,于是uncurring就誕生了,類似于函數世界里的開放平臺一樣。
uncurring把一個方法從它的原生對象中解放出來,讓他為所有人都能使用,比如Array.prototype.push(obj,value),你會發現每次讓 obj 借用 Array.prototype.push 方法的功能都比較麻煩,但是你使用uncurring后會變得非常清爽,直接push(obj,value)即可,原先push為個人服務,經過uncurring的解放后,push可以為人民服務

其實實現這個`uncurring也不復雜,看代碼:

Function.prototype.uncurrying = function () {var self = this;return function () {var obj = Array.prototype.shift.call(arguments); // 拿第一個參數當 thisreturn self.apply(obj, arguments); // 然后執行原方法};
};

在所有函數原型后面添加一個uncurring方法,先記錄下該方法的this,然后返回一個匿名函數,拿到第一個參數當作this,然后執行原方法,即可解放數組中所有的方法。

<html><body><script>Function.prototype.uncurrying = function () {var self = this;return function () {var obj = Array.prototype.shift.call(arguments); // 拿第一個參數當 thisreturn self.apply(obj, arguments); // 然后執行原方法};};var push = Array.prototype.push.uncurrying();var obj = { length: 0 };push(obj, "hello");push(obj, "hello1");push(obj, "hello2");push(obj, "hello3");push(obj, "hello4");console.log(obj); // { '0': 'hello', length: 1 }</script></body>
</html>

你想用我的方法?沒問題,請把你要代表的對象第一個傳進來,我來假裝它是 this,如下運行結果
深入理解JavaScript設計模式之閉包與高階函數

致敬—— 《JavaScript設計模式》· 曾探

完~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/908232.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/908232.shtml
英文地址,請注明出處:http://en.pswp.cn/news/908232.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用 Spring Boot 3.3 和 JdbcTemplate 操作 MySQL 數據庫

在現代的 Java 應用開發中&#xff0c;Spring Boot 提供了強大的工具來簡化數據庫操作。JdbcTemplate 是 Spring 提供的一個核心類&#xff0c;用于簡化 JDBC 操作&#xff0c;減少樣板代碼。本文將介紹如何在 Spring Boot 3.3 項目中使用 JdbcTemplate 來操作 MySQL 數據庫&am…

如何做好一份技術文檔?(下篇)

如何做好一份技術文檔&#xff1f;&#xff08;下篇&#xff09; 下篇&#xff1a;文檔體驗的極致優化 ——從可用性到愉悅性的跨越 文檔用戶體驗地圖 新手路徑 專家路徑 [安裝] → [配置] → [示例] [API] → [參數] → [源碼] │ ▲ …

Windows 12確認沒了,Win11 重心偏移修Bug

微軟悄然擱置了傳說中的Windows 12開發計劃&#xff0c;轉身將精力投入到Windows 11的持續進化中。今年秋季的主角已經確定——Windows 11 25H2&#xff0c;它將于9月或10月間與我們正式見面。 與去年24H2的大規模更新不同&#xff0c;25H2更像是場精心策劃的“功能解鎖”。微軟…

JavaScript中的正則表達式:文本處理的瑞士軍刀

JavaScript中的正則表達式&#xff1a;文本處理的瑞士軍刀 在編程世界中&#xff0c;正則表達式&#xff08;Regular Expression&#xff0c;簡稱RegExp&#xff09;被譽為“文本處理的瑞士軍刀”。它能夠高效地完成字符串匹配、替換、提取和驗證等任務。無論是前端開發中的表…

基于LEAP模型在能源環境發展、碳排放建模預測及分析中實踐應用

在國家“3060”碳達峰碳中和的政策背景下&#xff0c;如何尋求經濟-能源-環境的平衡有效發展是國家、省份、城市及園區等不同級別經濟體的重要課題。根據國家政策、當地能源結構、能源技術發展水平以及相關碳排放指標制定合理有效的低碳能源發展規劃需要以科學準確的能源環境發…

Python爬蟲實戰:研究RoboBrowser庫相關技術

1. 引言 1.1 研究背景與意義 隨著電子商務的快速發展,商品信息呈現爆炸式增長。據 Statista 數據顯示,2025 年全球電子商務銷售額預計將達到 7.4 萬億美元,海量的商品數據蘊含著巨大的商業價值。對于電商企業而言,及時獲取競爭對手的產品信息、價格動態和用戶評價,能夠幫…

JVM垃圾回收器-ZGC

一、概述 ZGC&#xff08;Z Garbage Collector&#xff09;是一種高效且可擴展的低延遲垃圾回收器。在垃圾回收過程中&#xff0c;ZGC通過優化算法和硬件支持&#xff0c;將Stop-The-World&#xff08;STW&#xff09;時間控制在一毫秒以內&#xff0c;使其成為追求低延遲應用…

區間動態規劃

線性 DP 的一種&#xff0c;簡稱為「區間 DP」。以「區間長度」劃分階段&#xff0c;以兩個坐標&#xff08;區間的左、右端點&#xff09;作為狀態的維度。一個狀態通常由被它包含且比它更小的區間狀態轉移而來。 一、概念 間 DP 的主要思想就是&#xff1a;先在小區間內得到…

4. 數據類型

4.1 數據類型分類 分類 數據類型 說明 數值類型 BIT(M) 位類型。M指定位數&#xff0c;默認值1&#xff0c;范圍1 - 64 TINYINT [UNSIGNED] 帶符號的范圍 -128 ~ 127&#xff0c;無符號范圍0 ~ 255&#xff0c;默認有符號 BOOL 使用0和1表示真和假 SMALLINT [UNSIGNED] 帶符號是…

設計模式-2 結構型模式

一、代理模式 1、舉例 海外代購 2、代理基本結構圖 3、靜態代理 1、真實類實現一個接口&#xff0c;代理類也實現這個接口。 2、代理類通過真實對象調用真實類的方法。 4、靜態代理和動態代理的區別 1、靜態代理在編譯時就已經實現了&#xff0c;編譯完成后代理類是一個實際…

vue+element-ui一個頁面有多個子組件組成。子組件里面有各種表單,實現點擊enter實現跳轉到下一個表單元素的功能。

一個父組件里面是有各個子組件的form表單組成的。 我想實現點擊enter。焦點直接跳轉到下一個表單元素。 父組件就是由各個子組件構成 子組件就像下圖一樣的都有個el-form的表單。 enterToTab.js let enterToTab {}; (function() {// 返回隨機數enterToTab.addEnterListener …

Open SSL 3.0相關知識以及源碼流程分析

Open SSL 3.0相關知識以及源碼流程分析 編譯 windows環境編譯1、工具安裝 安裝安裝perl腳本解釋器、安裝nasm匯編器(添加到環境變量)、Visual Studio編譯工具 安裝dmake ppm install dmake # 需要過墻2、開始編譯 # 1、找到Visual Studio命令行編譯工具目錄 或者菜單欄直接…

【Redis】筆記|第5節|Redisson實現高并發分布式鎖核心源碼

一、加鎖流程 1. 核心方法調用鏈 RLock lock redisson.getLock("resource"); lock.lock(); // 阻塞式加鎖? lockInterruptibly()? tryAcquire(-1, leaseTime, unit) // leaseTime-1表示啟用看門狗? tryAcquireAsync()? tryLockInnerAsync() // 執行Lua腳本 2…

基于React + TypeScript構建高度可定制的QR碼生成器

前言 在現代Web應用中&#xff0c;QR碼已成為連接線上線下的重要橋梁。本文將詳細介紹如何使用React TypeScript Vite構建一個功能強大、高度可定制的QR碼生成器&#xff0c;支持背景圖片、文本疊加、HTML模塊、圓角導出等高級功能。 前往試試 項目概述 技術棧 前端框架:…

【MATLAB代碼】制導——三點法,二維平面下的例程|運動目標制導,附完整源代碼

三點法制導是一種導彈制導策略,主要用于確保導彈能夠準確追蹤并擊中移動目標。該方法通過計算導彈、目標和制導站之間的相對位置關系,實現對目標的有效制導。 本文給出MATLAB下的三點法例程,模擬平面上捕獲運動目標的情況訂閱專欄后可直接查看源代碼,粘貼到MATLAB空腳本中即…

Ubuntu22.04 安裝 IsaacSim 4.2.0

1. 從官網下載 IsaacSim 4.2.0 安裝包 https://download.isaacsim.omniverse.nvidia.com/isaac-sim-standalone%404.2.0-rc.18%2Brelease.16044.3b2ed111.gl.linux-x86_64.release.zip 2. 查閱 Workstation Installation 安裝方式 Workstation Installation — Isaac Sim Do…

開源量子模擬引擎:Quantum ESPRESSO本地部署教程,第一性原理計算輕松入門!

一、介紹 Quantum ESPRESSO 是一個用于電子結構計算和納米尺度材料建模的開源計算機代碼集成套件&#xff0c;專門用于進行第一性原理&#xff08;第一性原理&#xff09;計算&#xff0c;涵蓋了電子結構、晶體學和材料性能的模擬。 Quantum ESPRESSO GPU 版本支持GPU加速&am…

PVE 虛擬機安裝 Ubuntu Server V24 系統 —— 一步一步安裝配置基于 Ubuntu Server 的 NodeJS 服務器詳細實錄1

前言 最近在基于 NodeJS V22 寫一個全棧的項目&#xff0c;寫好了&#xff0c;當然需要配置服務器部署啦。這個過程對于熟手來說&#xff0c;還是不復雜的&#xff0c;但是對于很多新手來說&#xff0c;可能稍微有點困難。所以&#xff0c;我把整個過程全部記錄一下。 熟悉我…

【JUC】深入解析 JUC 并發編程:單例模式、懶漢模式、餓漢模式、及懶漢模式線程安全問題解析和使用 volatile 解決內存可見性問題與指令重排序問題

單例模式 單例模式確保某個類在程序中只有一個實例&#xff0c;避免多次創建實例&#xff08;禁止多次使用new&#xff09;。 要實現這一點&#xff0c;關鍵在于將類的所有構造方法聲明為private。 這樣&#xff0c;在類外部無法直接訪問構造方法&#xff0c;new操作會在編譯…

2. 庫的操作

2.1 創建數據庫 語法&#xff1a; CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [, create_specification] ...] create_specification: [DEFAULT] CHARACTER SET charset_name # 字符集: 存儲編碼 [DEFAULT] COLLATE collation_name # 校驗集: 比較/選擇/讀…