摘要:每個函數表達式包括函數對象括號和傳入的實參組成。和作用都是動態改變函數體內指向,只是接受參數形式不太一樣。在定義函數時,形參指定為一個對象調用函數時,將整個對象傳入函數,無需關心每個屬性的順序。
函數
JavaScript中,函數指只定義一次,但可以多次被多次執行或調用的一段JavaScript代碼。與數組類似,JavaScript中函數是特殊的對象,擁有自身屬性和方法
每個函數對象都有prototype和length屬性,bind、apply()、call()方法。函數的特殊性在于:可以通過函數調用執行函數體中的語句。
函數是對象,所以可以賦值給變量、作為參數傳遞進其他函數、掛載到對象上作為方法
1 函數定義函數定義總共有三種方法:函數定義表達式、函數聲明語句和new Function()。
但是new Function()使用很少,因為通過它創建的函數不使用詞法作用域,創建的函數都在全局作用域被調用。
函數定義表達式和函數聲明語句都利用關鍵字function來定義函數
// 函數聲明語句 function funcName([arg1 [, arg2] [..., argn]]) { statements } //函數定義表達式 var funcName = function([arg1 [, arg2] [..., argn]]) { statements }
函數名標識符funcName:引用新定義的函數對象
參數列表:函數中的參數與函數體中的局部變量相同,function(x)相當于在函數體中var x;
{ statments }:構成函數體的語句,調用函數后執行的語句
1.1 變量提升JavaScript中由var關鍵字聲明的變量存在變量提升:將變量聲明提升到作用域的頂部,但賦值仍保留在原處。所以函數聲明語句和函數定義表達式有本質的區別
函數聲明語句:將函數聲明和函數的賦值都提升到作用域的頂部,在同一個作用域中可以出現調用在函數定義之前;
ECMAScript允許函數聲明語句作為頂級語句,可以出現在全局作用域中、也可以出現在嵌套函數中,但不能出現在循環、判斷、try-catch-finally和with語句中。函數定義表達式沒有限制
函數定義表達式:與var聲明的普通變量相同,只是將變量聲明提升到作用域頂部,但賦值仍然保留在原處,不能在定義前使用
//沒有顯式指明返回值的函數,默認返回undefined //輸出對象o的每個屬性的名稱 function printPrps(o) { for(var prop in o) { console.log(prop + ": " + o[prop] + " "); } } //計算笛卡爾坐標系中兩點間的距離 function distance(x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; return Math.sqrt(dx * dx + dy * dy); } // 計算階乘的遞歸函數,x!是x到1間(步長1)的累乘 function factorial(x) { //遞歸結束標志 if(x <= 1) { return 1; } return x * factorial(x - 1); } //將函數表達式賦值給變量 var square = function (x) {return x * x;}; // 函數表達式可以包含函數名,在遞歸時很有用 // var f = function fact(x) { if(x <= 1) {return 1;} return x * fact(x -1); }; // 函數表達式可以作為參數傳遞給其他函數 data.sort(function(a, b) {return a - b;}); //定義后立即調用函數表達式 var tensquare = (function(x) {return x * x;})(10);1.2 嵌套函數
JavaScript中,函數可以嵌套在其他函數中。內部函數可以訪問外部函數的局部變量和參數。
// 內部函數square可以訪問到外部函數的參數a、b和局部變量c function hypotenuse(a, b) { var c = 10; function square(x) {return x * x;} return Math.sqrt(square(a) + square(b) + square(c)); }2 函數的調用
在定義函數時,函數體中的代碼不會執行,只有在調用函數時,才執行函數體中的語句。有四種方式可以調用函數:
作為普通函數
作為對象的方法
作為構造器函數
使用函數的call()和apply()方法間接調用
2.1 調用函數使用調用表達式來調用普通函數,每個調用表達式由多個函數表達式組成。每個函數表達式包括函數對象、括號和傳入的實參組成。
每次調用會擁有本次調用的上下文this;在ES5非嚴格模式下,普通函數的this值是全局對象;在嚴格模式下是undefined
以函數形式調用的函數通常不使用this關鍵字
如果函數沒有顯式return語句返回一個值,默認返回undefined
傳入的實參是由逗號分隔的0個或多個函數表達式
// 調用printProps()函數,傳入對象作為實參即可 printPrps({x: 1}); // 調用distance()函數 var total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5); // 調用factorial()函數 var probability = factorial(5) / factorial(13);2.2 方法調用
方法是保存在JavaScript對象屬性中的函數。
對方法調用的參數和返回值處理與函數調用相同
方法調用由兩個部分組成:對象.屬性名(),其中屬性名是值為函數的屬性
方法調用中:調用上下文指調用方法的對象,使用this關鍵字引用
printProps({x: 1}); var total = distance(0, 0, 2, 1) + distance(2, 1, 3, 5); var probability = factorial(5) / factorial(13); var calculator = { //對象字面量 operand1: 1, operand2: 2, add: function() { //用this關鍵字指代當前對象calculator this.result = this.operand1 + this.operand2; } }; calculator.add(); //調用其add方法,使calculator對象獲得result屬性 calculator.result; // ==> 3
方法和this關鍵字是面向對象的核心,任何函數作為方法調用時會傳入一個隱式實參(指代調用方法的對象this),基于this的方法可以執行多種操作。
this是一個關鍵字,不是變量名、屬性名,JavaScript不允許為this賦值,但是可以將其賦值給其他變量
this沒有作用域限制,但是嵌套的函數不會從調用它的函數中繼承this
嵌套函數如果作為方法調用,this的值指向調用它的對象;
嵌套函數如果作為函數調用,this不是全局變量(ES5非嚴格模式),就是undefined(ES5嚴格模式)
嵌套函數的this并不指向調用它的外層函數的上下文
在外層函數中使用變量將外層函數的this對象及arguments屬性保存下來,在嵌套函數中便可以訪問
var o = { m: function() { var self = this; // 保存this(指向o對象)在變量self中 console.log(this === o); // ==> true,this指向o對象 f(); //將f()作為函數調用 function f() { console.log(this); // ==> window嚴格模式下,嵌套函數作為函數來調用,其this是undefined;非嚴格模式下是全局對象 console.log(this === o); //false,此處的this指向全局對象或undefined console.log(self === o); //true,self指向外部函數的this值 } } }; o.m(); //調用對象o的方法m()2.3 構造函數調用
如果函數或方法調用前有關鍵字new,函數或者方法便作為構造函數來調用。構造函數會創建一個新對象,新對象繼承構造函數的prototype屬性。
作為構造器函數的調用,會將新創建的對象作為其調用上下文(this指向新創建的對象),在構造器函數中使用this引用新創建的對象。
2.4 間接調用call()和apply()函數是對象,每個函數都有call()和apply()兩個方法,作用是改變函數運行時的上下文context--改變函數體內部this的指向
,因為JavaScript中有函數定義時上下文、函數運行時上下文和函數中上下文可以改變的概念。
call()和apply()作用都是動態改變函數體內this指向,只是接受參數形式不太一樣。
call()需要將參數按順序傳遞進函數,并且知道參數的數量(參數數量確定時使用)
apply()將參數放在數組中傳進函數(參數數量不確定時使用)
call()和apply()存在的意義在JavaScriptOOP中,使用原型實現繼承,call()和apply()是用于不同對象間的方法復用。當一個object沒有某個方法,但是另一個objAnother對象有,可以借助call()和apply()使object可以操作objAnother對象的方法。
function Cat() {} function Dog() {} Cat.prototype = { food: "fish", say: function () { console.log("I love " + this.food); } }; Dog.prototype = {food: "bone"}; var bCat = new Cat(); var bDog = new Dog(); bCat.say(); // ==> "I love fish" bCat.say.call(bDog); //==>"I love bone",bDog對象使用bCat對象的say方法,輸出自身的`this.food`屬性3 函數的實參和形參
實參和形參是相對的概念,在函數定義時指定的參數叫做形參;在函數調用時傳入的參數叫做實參。對于需要省略的實參,可以使用null或undefined`作為占位符。
3.1 參數默認值如果調用函數時,傳入的實參個數arguments.length小于定義時形參的個數arguments.callee.length,剩余的形參都被設置為undefined。對可以省略的值應該賦一個合理的默認值。
// 將對象obj中可枚舉的自身屬性追加到數組a中,并返回數組a // 如果省略a,則創建一個新數組,并返回這個新數組 function getPropertyNames(obj, /*optional*/ a) { if(!a) { a = []; } //如果未傳入a,則使用新數組。 // a = a || [];代替寫法更有語義 for(var prop in obj) { if(!obj.hasOwnProperty(prop)) {continue;} a.push(prop); } return a; } // 調用,出入一個參數或兩個參數 var a = getPropertyNames(obj); //將obj的屬性存儲到一個新數組中 getPropertyNames(obj, arr); //將obj的屬性追加到arr數組中
函數中的參數等同于函數體內的局部變量,具有函數的作用域。
3.2 參數對象函數體內,標識符arguments指向實參對象的引用,實參對象是一個類數組對象,可以通過下標訪問每個傳入的參數。
arguments僅是一個標識符,嚴格模式下不能賦值;
應用場景:函數包含固定個數的必須參數,隨后包含不定數量的可選參數
// 可以接收任意個數的實參, // 接收任意數量的實參,返回傳入實參的最大值,內置的Math.max()方法功能類似 function max(/*...optional*/) { //實參個數不能為0 var maxNum = Number.NEGATIVE_INFINITY; //將保存最大值的變量初始化 for(var i in arguments) { maxNum = (arguments[i] > maxNum) ? arguments[i] : maxNum; } return maxNum; }3.3 callee和caller屬性
callee是ECMAScript規范中arguments對象的屬性:代表當前正在執行的函數。
caller是非標準的,只是瀏覽器基本都實現了這個屬性:帶表調用當前函數的函數。
在嚴格模式中,對這兩個屬性讀寫都會產生錯誤
// arguments的callee屬性用在匿名函數的遞歸實現 var factorial = function(x) { if(x <= 1) {return 1;} return x * arguments.callee(x - 1); }3.4 將對象屬性作為參數
在定義一個函數時,如果傳入的參數多于3個,在調用時按順序傳入會變得很麻煩。一種解決方式是傳入key/value形式的參數,無需關注參數的順序。
在定義函數時,形參指定為一個對象;
調用函數時,將整個對象傳入函數,無需關心每個屬性的順序。(性能會差,參數需要在對象中去查找值)
// 將原始數組的length復制到目標數組 // 開始復制原始數組的from_start元素 // 并且將其復制至目標數組的to_start中 // 參數復雜,調用時順序難以控制 function arrayCopy(array, from_start, target_arr, to_start, length) { // (原始數組, index, 目標數組, index, length) { // 實現邏輯 } } // 無需關心參數順序的版本,效率略低 // from_start和to_start默認為0 function easyCopy(args) { arrayCopy(args.array, args.from_start || 0, args.target_arr, args.to_start || 0, args.length); } // easyCopy()的調用 var a = [1, 2, 3, 4]; var b = []; easyCopy({array: a, target_arr: b, length: 4});3.5 實參類型
JavaScript在定義函數時并未聲明形參類型,形參整體傳入函數體前不會做類型檢查,如果對傳入的實參有某種限制,最好在函數體內增加類型檢查的代碼。
// 返回數組或類數組a中元素的累加和 // 數組a中的元素必須是數字,null和undefined被忽略 // 類型檢查嚴格,但是靈活性很差 function sum(a) { if(isArrayLike(a)) { // a是數組或類數組 var result = 0; for(var i in a) { var element = a[i]; if(element == null) {continue;} //跳過null和undefined if(isFinite(element)) { result += element; } else { throw new Error("sum(): elements must be finite number"); } } return result; } else { throw new Error("sum(): arguments must be array-like"); } }4 函數作為值
函數定義及調用是JavaScript中的詞法特性;同時JavaScript中函數是一個對象:
可以賦值給變量
存儲在對象的屬性中或者數組的元素中
作為參數傳入另一個函數:例如Array.sort()方法,用來對數組元素進行排序。但是排序的規則有很多中,將具體規則封裝在函數中,傳入sort()。函數實現對任意兩個值都返回一個值,指定它們在排序好數組中的先后順序
// 簡單函數 function add(x, y) {return x + y;} function subtract(x, y) {return x - y;} function mutiply(x, y) {return x * y;} function divide(x, y) {return x / y;} // 這個函數以上面一個函數作為參數,并傳入兩個操作數,使用傳入的函數來調用 // 過程抽象:兩個數可以執行加、減、乘、除四個操作,將四個運算抽象為操作符,根據操作符不同,執行不同的函數 function operate(operator, operand1, operand2) { return operator(operand1, operand2); } // 執行(2 + 3) + (4 * 5) var i = operate(add, 2, 3) + operate(mutiply, 4, 5); // ==>25 // 另外一種實現 var operators = { add: function(x, y) {return x + y;}, subtrack: function(x, y) {return x + y;}, mutiply: function(x, y) {return x + y;}, divide: function(x, y) {return x + y;}, pow: Math.pow }; function operate2(operator, operand1, operand2) { if(typeof operators[operator] === "function") { return operators[operator](operand1, operand2); } else { throw "unknown operator"; } } // 計算("hello" + " " + "world")的值 operate2("add", "hello", operate2("add", " ", "world")); // ==> "hello world" operate2("pow", 10, 2); // ==> 100自定義屬性
函數是對象,可以擁有屬性。對于函數中的靜態變量,可以直接存入函數的屬性中。
// 初始化函數對象的計數器屬性,函數聲明會被提前,所以可以先給他的屬性賦值 uniqueInteger.counter = 0; // 每次調用這個函數,都會返回一個不同的整數,使用counter屬性保存下次要返回的值 function uniqueInteger() { return uniqueInteger.counter++; // 先返回計數器的值,再自增1 }5 函數作為命名空間
JavaScript中只存在函數作用域和全局作用域,沒有塊級作用域。可以使用自執行函數用作臨時命名空間,這樣不會污染全局變量。
(function() {/* 模塊代碼 */})(); //注意調用括號的位置,兩種寫法均可 (function() {/* 模塊代碼 */} ());6 閉包
編程界崇尚優雅簡潔唯美,很多時候如果你覺得一個概念很復雜,那么可能是你理錯了
閉包在JavaScript中,指內部函數總是可以訪問其所在的外部函數中聲明的變量和參數,即使外部函數被返回(調用結束)。
Closure使JavaScript使當前作用域能夠訪問到外部作用域中的變量;
函數是JavaScript中唯一擁有自身作用域的結構,所以Closure的創建依賴于函數
6.1 如何理解var scope = "global scope"; function checkScope() { var scope = "local scope"; function f() {return scope;} return f; //將函數對象返回 } checkScope()(); // ==> "local scope"
在JavaScript中,每個函數在定義時會創建一個與之相關的作用域鏈,并且在程序執行期間一直存在
外部函數checkScope有自身的作用域鏈,內部函數f有自身多帶帶的的作用域鏈)
每次調用函數會創建一個新對象來保存參數和局部變量,并將其添加到作用域鏈。
當函數返回時,將綁定的新對象從作用域鏈上刪除。如果沒有其他變量引用該對象、或該對象沒有保存在某個對象的屬性中,它會被當做垃圾回收。
如果沒有外部變量引用checkScope調用函數時創建的臨時對象,函數return后便被垃圾回收
如果checkScope定義有嵌套函數f,并將f作為返回值或保存在某個對象的屬性中。相當于有一個外部引用指向嵌套函數。
f有自身的作用域鏈和保存參數與局部變量的對象
f在checkScope函數體內,可以訪問外部函數中所有的變量和參數
綜上所述:JavaScript中的函數,通過作用域鏈和詞法作用域兩者的特性,將該函數定義時的所處的作用域中的相關函數進行捕獲和保存,從而可以在完全不同的上下文中進行引用
6.2 注意點每個函數調用都有一個this值和arguments對象,需要在外部函數中用變量保存this值和arguments對象,Closure才可以訪問到外部函數的這兩個值。that = this,outerArguments = arguments
Closure是通過調用外部函數返回內部嵌套函數創建的,每次調用外部函數都會創建一個Closure。但是每個Closure共享外部函數聲明的變量,不會為每個Closure多帶帶創建一份外部作用域的副本
// 函數返回一個返回v的函數 function constFunc(v) { return function() {return v;}; } //創建一個數組用來保存常數 var funcs = []; for(var i=0; i<10; i++) { funcs[i] = constFunc(i); // 創建了10個Closure,每個Closure的值不同,因為每次傳入外層函數constFunc的值不同 } console.log(funcs[6]()); // ==> 6 function constFuncs() { var funcs = []; for(var i=0; i<10; i++) { funcs[i] = function() {return i;}; // 創建10個Closure,但10個Closure在同一個外層函數constFuncs內,共享它的局部變量。 } // 10個Closure創建完畢后,i的值變為0,所以每個Closure返回的值都是0 return funcs; } var foo = constFuncs(); console.log(foo[4]()); // ==> 10
CLosure中部分資源不能自動釋放,容易造成內存泄漏
7 函數的屬性、方法和構造函數內存泄漏指由于疏忽或錯誤造成程序未能釋放已經不再使用的內存(即不再利用的值或對象依然占據內存空間)
JavaScript中函數是對象,每個函數都有lenght和prototype屬性;每個函數都有call()、apply()、bind()方法,并且可以利用函數的構造函數Function()來創建函對象。
7.1 length屬性函數對象的length屬性是只讀的,用于獲取定義函數時指定的形參個數。可以用來檢驗定義的參數與傳入的參數是否相同。
// arguments.callee不能在嚴格模式下工作 function check(args) { var actual = args.length; var expected = args.callee.length; // arguments.callee指代函數本身 if(expected !== actual) { throw Error("Expected:" + expected + " args; got " + actual + "args;"); } } // 測試函數,只有傳入三個函數才不會報錯 function f(x, y, z) { check(arguments); return x + y + z; }7.2 prototype屬性
每個函數都有一個prototype屬性,指向一個原型對象的引用,每個函數的原型對象都不同。
將函數用作創建對象的構造器函數使用時,新創建的對象會從函數的原型對象上繼承屬性
7.3 call()和apply()call()和apply()用于動態改變this的指向,使對象的方法可以借用給別的對象使用。
7.4 bind()bind()方法的作用是將函數綁定至某個對象,bind()方法的返回值是一個新的函數對象
將f()函數調用bind()方法綁定至對象o,用變量g來接收bind()返回的函數,(以函數調用形式)調用g時,會將原始函數f當做對象o的方法來使用。
var f = function(y) {return this.x + y;}; var o = {x: 2}; var g = f.bind(o); // 將f()綁定到o對象上 console.log(g(6)); // ==> 以函數調用的方式調用g(x),相當于調用o.f(x) // 實現bind()綁定 function bind(f, o) { if(f.bind) { //如果bind()方法存在,使用bind()方法 return f.bind(o); } else { return function() { //利用apply()使o對象來調用f()方法,并且傳入類數組對象參數arguments return f.apply(o, arguments); //arguments是調用綁定函數時傳入的參數 }; } }
bind()第一個實參是要綁定方法的對象(本質是將函數的this指向改為傳入的對象),同時后面的實參也會綁定至this,函數式編程中的currying 柯里化。
var sum = function(x, y) {return x + y;}; // 創建一個類似sum的新函數,但是this綁定到null // 并且第一個參數綁定為1,新的函數只期望傳入一個參數 var g = sum.bind(null, 1); // 將sum的第一個參數x綁定為1 console.log(g(3)); // ==> 4,因為x綁定為1,將3作為參數傳入y function f(y, z) {return this.x + y + z;} var g = f.bind({x: 2}, 3); // 將f函數綁定到對象{x: 2},將3綁定到函數的第一個參數y,新創建的函數傳入一個參數 console.log(g(1)); // ==>6
模擬實現bind()方法:bind()方法返回的是一個Closure
if(!Function.prototype.bind) { //不支持bind方法 Function.prototype.bind = function (o) { var self = this; // 保存bind()中的this與arguments,便于在嵌套函數中使用 var boundArgs = arguments; // bind()方法返回一個函數對象 return function() { // 創建一個實參列表,將傳入bind()的第二個及以后的實參都傳入這個函數 var args = []; // 傳入bind()函數的參數處理,從第二位開始 for(var i=1; i注意點 bind()方法的某些特性是上述模擬方法不能替代的。
bind()方法返回一個真正的函數對象,函數對象的length屬性是綁定函數的形參個數減去綁定的實參個數(length的值不能小于0)
function f(y, z) {return this.x + y + z;} // 綁定函數f的形參個數時2 var g = f.bind({x: 2}, 3); // 綁定的實參個數是1(從第二位開始是傳入綁定函數的實參),即將3傳遞給f的第一個參數y g(1); // ==> 6,繼續將1傳遞給函數f的形參zES5的bind()方法可以順帶做構造函數,此時將會忽略傳入bind()方法的this,原始函數以構造函數的形式調用,其實參已經綁定。
bind()方法返回的函數并不包含prototype屬性(普通函數的固有prototype屬性是不能刪除的);并且將綁定的函數用作構造器函數時所創建的對象,從原始為綁定的構造器函數中繼承prototype
如果將g()作為構造函數,其創建的對象與直接利用f當做構造函數創建的對象原型是同一個prototype
7.5 toString()方法根據ECMAScript規范,函數的toString()方法返回一個字符串,字符串與函數聲明語句的語法有關。
大多數函數的toString()方法都返回函數的完整源碼
內置函數的toString()方法返回一個類似"[native code]"的字符串作為函數體
7.6 Function()構造函數Function()構造函數運行JavaScript在運行時動態創建并編譯函數
每次調用Function()構造函數都會解析函數體,并創建新的函數對象。如果在循環中執行Function(),會影響效率;
Function()創建的函數不使用詞法作用域,函數體的代碼編譯總在全局作用域執行
Function()在實際編程中使用很少。
8 函數式編程JavaScript并非函數式編程語言,但JavaScript中函數是對象,可以像對象一樣操控,所以可以應用函數式編程技術
8.1 使用函數處理數組假設有一個數組,元素都是數字,要計算所有元素的平均值與標準差。
非函數式編程風格
var data = [1, 1, 3, 5, 5]; var total = 0; //平均數是所有元素的和除以元素的個數 data.forEach(function(value) { total += value; }); var mean = total / data.length; //標準差:先計算每個元素與平均值的差的平方的和 var sum = 0; data.forEach(function(value) { var tmp = value - mean; sum += tmp * tmp; }); //標準差stddev var stddev = Math.sqrt(sum / data.length-1);
函數式編程風格,利用map()和reduce()來實現,抽象出兩個過程:
求平均值和標準差會用到求一個數組中所有元素的和:使用reduce()
求數組中每個元素的平方:使用map()
// 定義求和、求積兩個過程 var add = function(x, y) {return x + y;}; var square = function(x) {return x * x;}; var data = [1, 1, 3, 5, 5]; // reduc()實現數組求和 var avg = data.reduce(add) / data.length; // map()實現差的平方,返回操作后的數組,再調用reduce() var sum = data.map(function(value) {return value - avg;}); var stddev = Math.sqrt(sum.map(square).reduce(add) / (data.length - 1));8.2 高階函數高階函數higher-order function指操作函數的函數,接收一個或多個函數作為參數,并返回一個新函數。
// 高階函數not()返回一個新函數,新函數將它的實參傳入f() function not(f) { return function() { // 返回一個新函數 var result = f.apply(this, arguments); // 調用f() return !result; // 對結果求反 }; } var even = function (x) { //判斷一個數是否是偶數 return x % 2 === 0; }; var odd = not(even); // 一個新函數,所做的事情與even()相反 [1, 1, 3, 5, 5].every(odd); // ==> true每個元素都是奇數 // mapper()返回的函數的參數是數組,對每個元素執行函數f() // 返回所有計算結果組成的數組 function mapper(f) { return function(a) { return map(a, f); }; } var increment = function(x) {return x + 1;}; var incrementer = mapper(increment); incrementer([1, 2, 3]);8.3 不完全函數將一次完整的函數調用拆分為多次函數調用,每次傳入的實參都是完整實參的一部分,每個拆分開的函數叫做不完全函數partial function,每次函數調用叫做不完全函數調用partial application。特點是每次調用都返回一個函數,知道得到最終運行結果為止。
if(!Function.prototype.bind) { //不支持bind方法 Function.prototype.bind = function (o) { var self = this; // 保存bind()中的this與arguments,便于在嵌套函數中使用 var boundArgs = arguments; // bind()方法返回一個函數對象 return function() { // 創建一個實參列表,將傳入bind()的第二個及以后的實參都傳入這個函數 var args = []; // 傳入bind()函數的參數處理,從第二位開始 for(var i=1; i函數f()的bind()方法返回一個新函數,給新函數傳入特定的上下文和一組指定的參數,然后調用函數f()。傳入bind()的實參都是放在傳入原始參數的實參列表開始的位置。
但有時希望將傳入bind()的實參放在完整實參列表的右側:// 實現一個工具函數,將類數組對或對象轉化為真正的數組 // 將arguments對象轉化為真正的數組 function array(a, n) {return Array.prototype.slice.call(a, n || 0);} // 這個函數的實參傳遞至左側 function partialLeft(f) { var args = arguments; // 保存外部的實參數組 return function() { // 返回一個函數 var a = array(args, 1); // 開始處理外部的第一個args a = a.concat(array(arguments)); //然后增加所有的內部實參 return f.apply(this, a); // 基于這個實參列表調用f() }; } // 這個函數的實參傳遞至右側 function partialRight(f) { var args = arguments; // 保存外部的實參數組 return function() { // 返回一個函數 var a = array(arguments); // 從內部參數開始 a = a.concat(array(args, 1)); //然后從外部第一個args開始添加 return f.apply(this, a); // 基于這個實參列表調用f() }; } // 這個函數的實參被用作模板,實參列表中的undefined值都被填充 function partial(f) { var args = arguments; return function() { var a = array(args, 1); var i = 0, j = 0; // 遍歷args,從內部實參填充undefined值 for(; i-2:綁定第一個實參 2*(3-4) partialRight(f, 2)(3, 4); // ==> 6:綁定最后一個實參 3*(4-2) partial(f, undefined, 2)(3, 4); // ==> -6:綁定中間的實參 3*(2-4) 利用不完全函數的編程技巧,可以利用已有的函數來定義新的函數
8.4 記憶在函數式編程中,把將上次計算記過緩存的技術叫做記憶memerization。
本質上是犧牲算法的空間復雜度以換取更優的時間復雜度。因為在客戶端中JavaScript代碼的執行速度往往成為瓶頸。
// 返回f()的帶有記憶功能的版本(緩存上次計算結果) // 只有在f()的實參字符串表示都不相同時才工作 function memorize(f) { var cache = {}; //將值保存在閉包內 return function() { // 將實參轉為字符串形式,并將其用作緩存的鍵 var key = arguments.length + Array.prototype.join.call(arguments, ","); if(key in cache) { return cache[key]; } else { return cache[key] = f.apply(this, arguments); } }; } // memorize()創建新對象cache并將其保存在局部變量中,對于返回的函數來說它是私有的(在閉包中)。 // 返回的函數將它的實參數組轉化為字符串,并將字符串用作緩存對象的屬性名。如果在緩存中有這個值,則直接返回 // 如果沒有,調用既定函數對實參進行計算,將計算結果緩存并返回 // 返回兩個整數的最大公約數 function gcd(a, b) { var t; if(a < b) { t= b; b = a; a = t; } while(b !== 0) { t = b; b = a % b; a = t; } return a; } var gcdmemo = memorize(gcd); gcdmemo(85, 187); // ==> 17 //注意寫一個遞歸函數時,往往需要記憶功能 // 調用實現了記憶功能的遞歸函數 var factorial = memorize(function(n) { return (n <= 1)? 1 : n * factorial(n - 1); }); factorial(5); // ==> 120,同時緩存了1~4的值。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81883.html
摘要:專題系列共計篇,主要研究日常開發中一些功能點的實現,比如防抖節流去重類型判斷拷貝最值扁平柯里遞歸亂序排序等,特點是研究專題之函數組合專題系列第十六篇,講解函數組合,并且使用柯里化和函數組合實現模式需求我們需要寫一個函數,輸入,返回。 JavaScript 專題之從零實現 jQuery 的 extend JavaScritp 專題系列第七篇,講解如何從零實現一個 jQuery 的 ext...
摘要:設計模式是以面向對象編程為基礎的,的面向對象編程和傳統的的面向對象編程有些差別,這讓我一開始接觸的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續了解設計模式必須要先搞懂面向對象編程,否則只會讓你自己更痛苦。 JavaScript 中的構造函數 學習總結。知識只有分享才有存在的意義。 是時候替換你的 for 循環大法了~ 《小分享》JavaScript中數組的那些迭代方法~ ...
摘要:理解的函數基礎要搞好深入淺出原型使用原型模型,雖然這經常被當作缺點提及,但是只要善于運用,其實基于原型的繼承模型比傳統的類繼承還要強大。中文指南基本操作指南二繼續熟悉的幾對方法,包括,,。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。 怎樣使用 this 因為本人屬于偽前端,因此文中只看懂了 8 成左右,希望能夠給大家帶來幫助....(據說是阿里的前端妹子寫的) this 的值到底...
摘要:然后將構造函數的原型設為,便實現了對象繼承。首先,我們定義一個構造函數,并在其中定義一個局部變量。這里的是局部變量,其作用域仍然存在是閉包現象,而非對象屬性。 Javascript是動態的,弱類型的,解釋執行的程序設計語言。 Javascript極其靈活,支持多種程序設計范式:面向對象、指令式、函數式。JavaSCript最初被用于瀏覽器腳本,現在已經是所有主流瀏覽器的默認腳本語言。瀏...
摘要:和類在開始時遇到類組件,只是需要有關類的基礎。畢竟,中的條件呈現僅再次顯示大多數是而不是特定的任何內容。 在我的研討會期間,更多的材料是關于JavaScript而不是React。其中大部分歸結為JavaScript ES6以及功能和語法,但也包括三元運算符,語言中的簡寫版本,此對象,JavaScript內置函數(map,reduce,filter)或更常識性的概念,如:可組合性,可重用...
閱讀 1446·2021-11-24 09:39
閱讀 3626·2021-09-29 09:47
閱讀 1571·2021-09-29 09:34
閱讀 3067·2021-09-10 10:51
閱讀 2536·2019-08-30 15:54
閱讀 3216·2019-08-30 15:54
閱讀 869·2019-08-30 11:07
閱讀 1004·2019-08-29 18:36