JavaScript權威指南(8)——函式

夕陽下的奔跑發表於2019-08-07

函式

函式定義

  1. 函式語句和表示式兩種方式定義
   //函式語句——宣告
   function printprops(o) {
   for(var p in o)
   console.log(p + ": " + o[p] + "\n");
   }
   //函式表示式
   var square = function(x) { return x*x; }
複製程式碼
  1. 表示式定義的函式,函式的名稱是可選的
  2. 函式宣告定義的函式可以在定義之前出現的程式碼呼叫
  3. 以表示式定義的函式在定義之前無法呼叫(變數的宣告提前,但是賦值不會提前)
  4. 函式宣告語句不能出現在迴圈,條件判斷,或者try/catch/finally以及with語句中

函式呼叫

  1. 4種方式呼叫:作為函式,作為方法,作為建構函式,通過它們的call()和apply()方法間接呼叫
  2. ES 3和非嚴格的ES 5,呼叫上下文(this)是全域性物件,嚴格模式下,則是undefined
  3. 函式表示式作為物件的屬性訪問表示式,此時是一個方法,物件成為函式的呼叫上下文
   var calculator = { // An object literal
       operand1: 1,
       operand2: 1,
       add: function() {
           // Note the use of the this keyword to refer to this object.
           this.result = this.operand1 + this.operand2;
       }
   };
   calculator.add(); // A method invocation to compute 1+1.
   calculator.result // => 2
複製程式碼
  1. 任何函式的方法呼叫會傳入一個隱式的實參——這個實參是物件,方法呼叫的母體。
  2. 方法鏈——方法的返回值是一個物件,這個物件還可以再呼叫它的方法
  3. this是關鍵字,非變數,所以this沒有作用域的限制——巢狀的函式不會從呼叫它的函式中繼承this,它的this指向呼叫它的物件
   var o = { // An object o.
       m: function() { // Method m of the object.
           var self = this; // Save the this value in a variable.
           console.log(this === o); // Prints "true": this is the object o.
           f(); // Now call the helper function f().
           function f() { // A nested function f
               console.log(this === o); // "false": this is global or undefined
               console.log(self === o); // "true": self is the outer this value.
           }
       }
   };
   o.m(); // Invoke the method m on the object o.
複製程式碼
  1. 如果函式或者方法呼叫之前帶有關鍵字new,就構成建構函式呼叫
  2. 建構函式中的this指向這個新建立的物件
  3. call和apply可以顯示地指定呼叫所需的this值,即使該函式不是那個物件的方法

函式的實參和形參

  1. 呼叫函式時傳入的實參比函式宣告時指定的形參個數要少,則剩下的形參設定為undefined,所以需要給省略的引數賦一個預設值
   // Append the names of the enumerable properties of object o to the
   // array a, and return a. If a is omitted, create and return a new array.
   function getPropertyNames(o, /* optional */ a) {
       if (a === undefined) a = []; // If undefined, use a new array
       for(var property in o) a.push(property);
       return a;
   }
   // This function can be invoked with 1 or 2 arguments:
   var a = getPropertyNames(o); // Get o's properties into a new array
   getPropertyNames(p,a); // append p's properties to that array
   
   a = a || [];
複製程式碼
  1. 呼叫函式時傳入的實參個數超過形參個數時,不能直接獲得未命名值的引用——在函式體內,識別符號arguments是指向實參物件的引用,是一個類陣列物件
   function f(x, y, z)
   {
       // First, verify that the right number of arguments was passed
       if (arguments.length != 3) {
       	throw new Error("function f called with " + arguments.length +
       	"arguments, but it expects 3 arguments.");
   	}
   // Now do the actual function...
   }
複製程式碼
  1. 不定實參函式——接受任意個數的實參
   function max(/* ... */) {
       var max = Number.NEGATIVE_INFINITY;
       // Loop through the arguments, looking for, and remembering, the biggest.
       for(var i = 0; i < arguments.length; i++)
           if (arguments[i] > max) max = arguments[i];
           // Return the biggest
       return max;
   }
   var largest = max(1, 10, 100, 2, 3, 1000, 4, 5, 10000, 6); // => 10000
複製程式碼
  1. 通過實參名字來修改實參值的話,通過arguments[]陣列可以獲取到更改後的值
   function f(x) {
       console.log(x); // Displays the initial value of the argument
       arguments[0] = null; // Changing the array element also changes x!
       console.log(x); // Now displays "null"
   }
   x和arguments[0]指代同一個值
複製程式碼
  1. ES 5中移除了上述的特殊特性
  2. ES 5的嚴格模式下,讀寫callee和caller屬性會產生型別錯誤。callee指代當前正在執行的函式。caller指代呼叫當前正在執行的函式的函式。
   var factorial = function(x) {
       if (x <= 1) return 1;
   	return x * arguments.callee(x-1);
   };
複製程式碼
  1. 形參過多時,可以將傳入的實參寫入一個單獨的物件
   // Copy length elements of the array from to the array to.
   // Begin copying with element from_start in the from array
   // and copy that element to to_start in the to array.
   // It is hard to remember the order of the arguments.
   function arraycopy(/* array */ from, /* index */ from_start,
                   /* array */ to, /* index */ to_start,
                   /* integer */ length)
   {
   	// code goes here
   }
   // This version is a little less efficient, but you don't have to
   // remember the order of the arguments, and from_start and to_start
   // default to 0.
   function easycopy(args) {
       arraycopy(args.from,
       args.from_start || 0, // Note default value provided
       args.to,
       args.to_start || 0,
       args.length);
   }
   // Here is how you might invoke easycopy():
   var a = [1,2,3,4], b = [];
   easycopy({from: a, to: b, length: 4});
   
複製程式碼
  1. 實參型別做型別檢查
   function sum(a) {
   	if (isArrayLike(a)) {
           var total = 0;
           for(var i = 0; i < a.length; i++) { // Loop though all elements
               var element = a[i];
               if (element == null) continue; // Skip null and undefined
               if (isFinite(element)) total += element;
               else throw new Error("sum(): elements must be finite numbers");
   		}
   		return total;
   	}
   	else throw new Error("sum(): argument must be array-like");
   }
  
複製程式碼

作為值的函式

  1. 函式可以賦值給變數
   function square(x) { 
   	return x*x; 
   }
   
複製程式碼
  1. 函式除了賦值給變數,還可以賦值給物件的屬性,此時函式就稱為方法
   // We define some simple functions here
   function add(x,y) { return x + y; }
   function subtract(x,y) { return x - y; }
   function multiply(x,y) { return x * y; }
   function divide(x,y) { return x / y; }
   // Here's a function that takes one of the above functions
   // as an argument and invokes it on two operands
   function operate(operator, operand1, operand2) {
   	return operator(operand1, operand2);
   }
   // We could invoke this function like this to compute the value (2+3) + (4*5):
   var i = operate(add, operate(add, 2, 3), operate(multiply, 4, 5));
   // For the sake of the example, we implement the simple functions again,
   // this time using function literals within an object literal;
   var operators = {
       add: function(x,y) { return x+y; },
       subtract: function(x,y) { return x-y; },
       multiply: function(x,y) { return x*y; },
       divide: function(x,y) { return x/y; },
       pow: Math.pow // Works for predefined functions too
   };
   // This function takes the name of an operator, looks up that operator
   // in the object, and then invokes it on the supplied operands. Note
   // the syntax used to invoke the operator function.
   function operate2(operation, operand1, operand2) {
       if (typeof operators[operation] === "function")
       	return operators[operation](operand1, operand2);
       else throw "unknown operator";
   }
   // Compute the value ("hello" + " " + "world") like this:
   var j = operate2("add", "hello", operate2("add", " ", "world"));
   // Using the predefined Math.pow() function:
   var k = operate2("pow", 10, 2);
   
複製程式碼
  1. 函式可以擁有屬性,使資訊在函式呼叫過程中持久化——不需要放到全域性變數中
   // Initialize the counter property of the function object.
   // Function declarations are hoisted so we really can
   // do this assignment before the function declaration.
   uniqueInteger.counter = 0;
   // This function returns a different integer each time it is called.
   // It uses a property of itself to remember the next value to be returned.
   function uniqueInteger() {
   	return uniqueInteger.counter++; // Increment and return counter property
   }
   
複製程式碼
  1. 函式使用自身的屬性快取上一次的計算結果
   // Compute factorials and cache results as properties of the function itself.
   function factorial(n) {
       if (isFinite(n) && n>0 && n==Math.round(n)) { // Finite, positive ints only
       if (!(n in factorial)) // If no cached result
           factorial[n] = n * factorial(n-1); // Compute and cache it
           return factorial[n]; // Return the cached result
       }
       else return NaN; // If input was bad
   }
   factorial[1] = 1; // Initialize the cache to hold this base case.
   
複製程式碼

作為名稱空間的函式

  1. 在函式中宣告的變數在整個函式體內是可見的,在函式外是不可見的。不在任何函式內宣告的變數是全域性變數。
  2. 通過將程式碼放入一個函式內,然後呼叫該函式,使全域性變數變成函式內的區域性變數。
   function mymodule() {
       // Module code goes here.
       // Any variables used by the module are local to this function
       // instead of cluttering up the global namespace.
   }
   mymodule(); // But don't forget to invoke the function!
   
   (function() { // mymodule function rewritten as an unnamed expression
   	// Module code goes here.
   }()); // end the function literal and invoke it now.
   
複製程式碼
  1. 使用圓括號,JavaScript直譯器將其解析為函式定義表示式,否則解析為函式宣告語句
  2. 特定場景下返回帶補丁的extend()版本——for/in迴圈是否會列舉測試物件的toString屬性
   // Define an extend function that copies the properties of its second and
   // subsequent arguments onto its first argument.
   // We work around an IE bug here: in many versions of IE, the for/in loop
   // won't enumerate an enumerable property of o if the prototype of o has
   // a nonenumerable property by the same name. This means that properties
   // like toString are not handled correctly unless we explicitly check for them.
   var extend = (function() { // Assign the return value of this function
       // First check for the presence of the bug before patching it.
       for(var p in {toString:null}) {
           // If we get here, then the for/in loop works correctly and we return
           // a simple version of the extend() function
           return function extend(o) {
               for(var i = 1; i < arguments.length; i++) {
               	var source = arguments[i];
               	for(var prop in source) o[prop] = source[prop];
               }
           	return o;
           };
       }
       // If we get here, it means that the for/in loop did not enumerate
       // the toString property of the test object. So return a version
       // of the extend() function that explicitly tests for the nonenumerable
       // properties of Object.prototype.
   	return function patched_extend(o) {
       for(var i = 1; i < arguments.length; i++) {
           var source = arguments[i];
           // Copy all the enumerable properties
           for(var prop in source) o[prop] = source[prop];
               // And now check the special-case properties
               for(var j = 0; j < protoprops.length; j++) {
               	prop = protoprops[j];
              		if (source.hasOwnProperty(prop)) o[prop] = source[prop];
               }
           }
       	return o;
       };
       // This is the list of special-case properties we check for
       var protoprops = ["toString", "valueOf", "constructor", "hasOwnProperty",
       "isPrototypeOf", "propertyIsEnumerable","toLocaleString"];
   }());
   
複製程式碼

閉包

  1. JavaScript採用詞法作用域,函式的執行依賴於變數作用域,是在函式定義時決定,而不是呼叫時決定的
  2. 閉包——函式物件可以通過作用域鏈相互關聯起來,函式體內部的變數都可以儲存在函式作用域內
  3. 詞法作用域規則——作用域鏈是函式定義的時候建立的
   var scope = "global scope"; // A global variable
   function checkscope() {
       var scope = "local scope"; // A local variable
       function f() { return scope; } // Return the value in scope here
       return f();
   }
   checkscope() // => "local scope"
   
   var scope = "global scope"; // A global variable
   function checkscope() {
       var scope = "local scope"; // A local variable
       function f() { return scope; } // Return the value in scope here
       return f;
   }
   checkscope()() // What does this return?
   
複製程式碼
  1. 改造uniqueInteger()函式,使區域性變數變成私有狀態
   var uniqueInteger = (function() { // Define and invoke
           var counter = 0; // Private state of function below
           return function() { return counter++; };
   	}());
   
複製程式碼
  1. 類似counter的私有變數可以被多個巢狀函式訪問
   function counter() {
       var n = 0;
       return {
           count: function() { return n++; },
           reset: function() { n = 0; }
       };
   }
   var c = counter(), d = counter(); // Create two counters
   c.count() // => 0
   d.count() // => 0: they count independently
   c.reset() // reset() and count() methods share state
   c.count() // => 0: because we reset c
   d.count() // => 1: d was not reset
   
複製程式碼
  1. 將閉包合併為存取器方法getter和setter
   function counter(n) { // Function argument n is the private variable
       return {
           // Property getter method returns and increments private counter var.
           get count() { return n++; },
           // Property setter doesn't allow the value of n to decrease
           set count(m) {
           	if (m >= n) n = m;
           	else throw Error("count can only be set to a larger value");
           }
       };
   }
   var c = counter(1000);
   c.count // => 1000
   c.count // => 1001
   c.count = 2000
   c.count // => 2000
   c.count = 2000 // => Error!
   
複製程式碼
  1. 使用閉包技術共享私有狀態
   // This function adds property accessor methods for a property with
   // the specified name to the object o. The methods are named get<name>
   // and set<name>. If a predicate function is supplied, the setter
   // method uses it to test its argument for validity before storing it.
   // If the predicate returns false, the setter method throws an exception.
   //
   // The unusual thing about this function is that the property value
   // that is manipulated by the getter and setter methods is not stored in
   // the object o. Instead, the value is stored only in a local variable
   // in this function. The getter and setter methods are also defined
   // locally to this function and therefore have access to this local variable.
   // This means that the value is private to the two accessor methods, and it
   // cannot be set or modified except through the setter method.
   function addPrivateProperty(o, name, predicate) {
       var value; // This is the property value
       
       // The getter method simply returns the value.
       o["get" + name] = function() { return value; };
       
       // The setter method stores the value or throws an exception if
       // the predicate rejects the value.
       o["set" + name] = function(v) {
           if (predicate && !predicate(v))
               throw Error("set" + name + ": invalid value " + v);
           else
               value = v;
       };
   }
   
   // The following code demonstrates the addPrivateProperty() method.
   var o = {}; // Here is an empty object
   // Add property accessor methods getName and setName()
   // Ensure that only string values are allowed
   addPrivateProperty(o, "Name", function(x) { return typeof x == "string"; });
   
   o.setName("Frank"); // Set the property value
   console.log(o.getName()); // Get the property value
   o.setName(0); // Try to set a value of the wrong type
   
複製程式碼
  1. 閉包使用時,注意將不希望共享的變數共享給其他的閉包
   // This function returns a function that always returns v
   function constfunc(v) { return function() { return v; }; }
   // Create an array of constant functions:
   var funcs = [];
   for(var i = 0; i < 10; i++) funcs[i] = constfunc(i);
   // The function at array element 5 returns the value 5.
   funcs[5]() // => 5
   
   // Return an array of functions that return the values 0-9
   function constfuncs() {
       var funcs = [];
       for(var i = 0; i < 10; i++)
       	funcs[i] = function() { return i; };
       return funcs;
   }
   var funcs = constfuncs();
   funcs[5]() // What does this return? -— 10
   
複製程式碼
  1. 閉包在外部函式內是無法訪問this的,除非外部函式將this轉存為一個變數

函式屬性、方法和建構函式

  1. 函式的length代表形參的個數
   // This function uses arguments.callee, so it won't work in strict mode.
   function check(args) {
       var actual = args.length; // The actual number of arguments
       var expected = args.callee.length; // The expected number of arguments
       if (actual !== expected) // Throw an exception if they differ.
      		throw Error("Expected " + expected + "args; got " + actual);
   }
   function f(x, y, z) {
       check(arguments); // Check that the actual # of args matches expected #.
       return x + y + z; // Now do the rest of the function normally.
   }
   
複製程式碼
  1. prototype屬性
  2. call()和apply()
   f.call(o, 1, 2);
   f.apply(o, [1,2]);
   
複製程式碼

ES 5嚴格模式中,call和apply的第一個實參都會變為this的值(即使是null和undefined),在ES 3和非嚴格模式中,傳入的null和undefined會被全域性變數代替 4. 動態修改已有方法-monkey-patching

   function trace(o, m) {
       var original = o[m]; // Remember original method in the closure.
       o[m] = function() { // Now define the new method.
           console.log(new Date(), "Entering:", m); // Log message
           var result = original.apply(this, arguments); // Invoke original.
           console.log(new Date(), "Exiting:", m); // Log message.
           return result; // Return result.
       };
   }
   
複製程式碼
  1. bind()方法——將函式繫結到某個物件 實現bind方法
   function bind(f, o) {
       if (f.bind) return f.bind(o); // Use the bind method, if there is one
       else return function() { // Otherwise, bind it like this
       	return f.apply(o, arguments);
       };
   }
   
複製程式碼
  1. 柯里化
   var sum = function(x,y) { return x + y }; // Return the sum of 2 args
   // Create a new function like sum, but with the this value bound to null
   // and the 1st argument bound to 1. This new function expects just one arg.
   var succ = sum.bind(null, 1);
   succ(2) // => 3: x is bound to 1, and we pass 2 for the y argument
   function f(y,z) { return this.x + y + z }; // Another function that adds
   var g = f.bind({x:1}, 2); // Bind this and y
   g(3) // => 6: this.x is bound to 1, y is bound to 2 and z is 3
   
   ``

7. ES 3Function.bind方法
```JavaScript
   if (!Function.prototype.bind) {
       Function.prototype.bind = function(o /*, args */) {
           // Save the this and arguments values into variables so we can
           // use them in the nested function below.
           var self = this, boundArgs = arguments;
           // The return value of the bind() method is a function
           return function() {
               // Build up an argument list, starting with any args passed
               // to bind after the first one, and follow those with all args
               // passed to this function.
               var args = [], i;
               for(i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
               for(i = 0; i < arguments.length; i++) args.push(arguments[i]);
               // Now invoke self as a method of o, with those arguments
               return self.apply(o, args);
           };
       };
   }
   
複製程式碼
  1. toString()方法——返回原始碼,內建函式返回[native code]
  2. Function()建構函式
   var f = new Function("x", "y", "return x*y;");
   
複製程式碼
  • Function()建構函式允許JavaScript在執行時動態地建立並編譯函式
  • 每次呼叫Function()建構函式都會解析函式體,並建立新的函式物件
  • 它所建立的函式並不是用詞法作用域,總是在頂層函式執行——無法捕獲區域性作用域
     var scope = "global";
     function constructFunction() {
     	var scope = "local";
     	return new Function("return scope"); // Does not capture the local scope!
     }
     // This line returns "global" because the function returned by the
     // Function() constructor does not use the local scope.
     constructFunction()(); // => "global
     
複製程式碼
  1. 可呼叫的物件——callable object
    檢測一個物件是否是真正的函式物件
    function isFunction(x) {
    	return Object.prototype.toString.call(x) === "[object Function]";
    }
    
    ```
## 函數語言程式設計
1. 使用函式處理陣列——計算平均值和標準差
```JavaScript
   // First, define two simple functions
   var sum = function(x,y) { return x+y; };
   var square = function(x) { return x*x; };
   // Then use those functions with Array methods to compute mean and stddev
   var data = [1,1,3,5,5];
   var mean = data.reduce(sum)/data.length;
   var deviations = data.map(function(x) {return x-mean;});
   var stddev = Math.sqrt(deviations.map(square).reduce(sum)/(data.length-1));
   
複製程式碼
   //ES 3 實現自定義的map()和reduce()函式
   // Call the function f for each element of array a and return
   // an array of the results. Use Array.prototype.map if it is defined.
   var map = Array.prototype.map
   	? function(a, f) { return a.map(f); } // Use map method if it exists
   	: function(a, f) { // Otherwise, implement our own
           var results = [];
           for(var i = 0, len = a.length; i < len; i++) {
               if (i in a) results[i] = f.call(null, a[i], i, a);
           }
           return results;
   	};
   // Reduce the array a to a single value using the function f and
   // optional initial value. Use Array.prototype.reduce if it is defined.
   var reduce = Array.prototype.reduce
   	? function(a, f, initial) { // If the reduce() method exists.
           if (arguments.length > 2)
           	return a.reduce(f, initial); // If an initial value was passed.
           else 
               return a.reduce(f); // Otherwise, no initial value.
       }
       : function(a, f, initial) { // This algorithm from the ES5 specification
           var i = 0, len = a.length, accumulator;
           // Start with the specified initial value, or the first value in a
           if (arguments.length > 2) accumulator = initial;
           else { // Find the first defined index in the array
               if (len == 0) throw TypeError();
               while(i < len) {
                   if (i in a) {
                       accumulator = a[i++];
                       break;
                   }
                   else i++;
               }
               if (i == len) throw TypeError();
           }
           // Now call f for each remaining element in the array
           while(i < len) {
               if (i in a)
               	accumulator = f.call(undefined, accumulator, a[i], i, a);
               i++;
           }
           return accumulator;
       }
   
複製程式碼
   var data = [1,1,3,5,5];
   var sum = function(x,y) { return x+y; };
   var square = function(x) { return x*x; };
   var mean = reduce(data, sum)/data.length;
   var deviations = map(data, function(x) {return x-mean;});
   var stddev = Math.sqrt(reduce(map(deviations, square), sum)/(data.length-1));
   
複製程式碼
  1. 高階函式——操作函式的函式,接收一個或多個函式作為引數,返回一個新的函式
   //not()函式時一個高階函式
   function not(f) {
       return function() { // Return a new function
           var result = f.apply(this, arguments); // that calls f
           return !result; // and negates its result.
       };
   }
   var even = function(x) { // A function to determine if a number is even
   	return x % 2 === 0;
   };
   var odd = not(even); // A new function that does the opposite
   [1,1,3,5,5].every(odd); // => true: every element of the array is odd
   
複製程式碼
   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]) // => [2,3,4]
   
複製程式碼
   function compose(f,g) {
       return function() {
           // We use call for f because we're passing a single value and
           // apply for g because we're passing an array of values.
       	return f.call(this, g.apply(this, arguments));
       };
   }
   var square = function(x) { return x*x; };
   var sum = function(x,y) { return x+y; };
   var squareofsum = compose(square, sum);
   squareofsum(2,3) // => 25
   
複製程式碼
  1. 不完全函式——把一次完整的函式呼叫拆成多次函式呼叫,每次傳入的實參都是完整實參的一部分,每次拆分開的函式叫做不完全函式,每次函式呼叫叫做不完全呼叫
   //將類陣列物件轉換為真正的陣列
   function array(a, n) { return Array.prototype.slice.call(a, n || 0); }
   
   //這個函式的實參傳遞至左側
   function partialLeft(f /*, ...*/) {
   	var args = arguments; // Save the outer arguments array
   	return function() { // And return this function
           var a = array(args, 1); // Start with the outer args from 1 on.
           a = a.concat(array(arguments)); // Then add all the inner arguments.
           return f.apply(this, a); // Then invoke f on that argument list.
       };
   }
   
   //這個函式的實參傳遞至右側
   function partialRight(f /*, ...*/) {
       var args = arguments; // Save the outer arguments array
       return function() { // And return this function
           var a = array(arguments); // Start with the inner arguments.
           a = a.concat(array(args,1)); // Then add the outer args from 1 on.
           return f.apply(this, a); // Then invoke f on that argument list.
   	};
   }
   
   //這個函式的實參被用作模板
   //實參列表中的undefined值都被填充
   function partial(f /*, ... */) {
       var args = arguments; // Save the outer arguments array
       return function() {
           var a = array(args, 1); // Start with an array of outer args
           var i=0, j=0;
           // Loop through those args, filling in undefined values from inner
           for(; i < a.length; i++)
               if (a[i] === undefined) 
                   a[i] = arguments[j++];
               // Now append any remaining inner arguments
               a = a.concat(array(arguments, j))
           return f.apply(this, a);
       };
   }
複製程式碼
   var f = function(x,y,z) { return x * (y - z); };
   // Notice how these three partial applications differ
   partialLeft(f, 2)(3,4) // => -2: Bind first argument: 2 * (3 - 4)
   partialRight(f, 2)(3,4) // => 6: Bind last argument: 3 * (4 - 2)
   partial(f, undefined, 2)(3,4) // => -6: Bind middle argument: 3 * (2 - 4)
複製程式碼

利用已有函式來定義新的函式:

var increment = partialLeft(sum, 1);
var cuberoot = partialRight(Math.pow, 1/3);
String.prototype.first = partial(String.prototype.charAt, 0);
String.prototype.last = partial(String.prototype.substr, -1, 1)
複製程式碼

當將不完全呼叫和其他高階函式整合在一起

var not = partialLeft(compose, function(x) { return !x; });
var even = function(x) { return x % 2 === 0; };
var odd = not(even);
var isNumber = not(isNaN)
複製程式碼

使用不完全呼叫的組合來重新組織求平均數和標準差的程式碼——函數語言程式設計!!

var data = [1,1,3,5,5]; // Our data
var sum = function(x,y) { return x+y; }; // Two elementary functions
var product = function(x,y) { return x*y; };
var neg = partial(product, -1); // Define some others
var square = partial(Math.pow, undefined, 2);
var sqrt = partial(Math.pow, undefined, .5);
var reciprocal = partial(Math.pow, undefined, -1);
// Now compute the mean and standard deviation. This is all function
// invocations with no operators, and it starts to look like Lisp code!
var mean = product(reduce(data, sum), reciprocal(data.length));
var stddev = sqrt(product(reduce(map(data,
                                        compose(square,
                                        		partial(sum, neg(mean)))),
                                  sum),
                           reciprocal(sum(data.length,-1))));
複製程式碼
  1. 記憶函式——將上次的計算結果快取起來,在函數語言程式設計中,這種快取技巧叫做“記憶”。

    memorize快取資料到區域性變數

   // Return a memoized version of f.
   // It only works if arguments to f all have distinct string representations.
   function memoize(f) {
       var cache = {}; // Value cache stored in the closure.
       return function() {
           // Create a string version of the arguments to use as a cache key.
           var key = arguments.length + Array.prototype.join.call(arguments,",");
           if (key in cache) return cache[key];
           else return cache[key] = f.apply(this, arguments);
       };
   }
複製程式碼
   //歐幾里得演算法-求兩個整數的最大公約數
   function gcd(a,b) { // Type checking for a and b has been omitted
       var t; // Temporary variable for swapping values
       if (a < b) t=b, b=a, a=t; // Ensure that a >= b
       while(b != 0) t=b, b = a%b, a=t; // This is Euclid's algorithm for GCD
       return a;
   }
   var gcdmemo = memoize(gcd);
   gcdmemo(85, 187) // => 17
   // Note that when we write a recursive function that we will be memoizing,
   // we typically want to recurse to the memoized version, not the original.
   var factorial = memoize(function(n) {
   	return (n <= 1) ? 1 : n * factorial(n-1);
   });
   factorial(5) // => 120. Also caches values for 4, 3, 2 and 1.
複製程式碼

相關文章