Decorators and forwarding, call/apply

Transparent caching

// 有一個很吃資源的函式,可以用一個包裝函式把函式丟進去,同樣參數跑出同樣結果不用重複運算。
function slow(x) {
  // there can be a heavy CPU-intensive job here
  alert(`Called with ${x}`);
  return x;
}

function cachingDecorator(func) {
  let cache = new Map();

  return function(x) {
    if (cache.has(x)) { // if the result is in the map
      return cache.get(x); // return it
    }

    let result = func(x); // otherwise call func

    cache.set(x, result); // and cache (remember) the result
    return result;
  };
}

slow = cachingDecorator(slow);

alert( slow(1) ); // slow(1) is cached
alert( "Again: " + slow(1) ); // the same

alert( slow(2) ); // slow(2) is cached
alert( "Again: " + slow(2) ); // the same as the previous line

// 包裝函數可以重複使用
// 包裝函數是獨立的不影響函數
// 可以使用多個包裝函數

Using “func.call” for the context

// this = endefined 出現錯誤
// we'll make worker.slow caching
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    // actually, there can be a scary CPU-heavy task here
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

// same code as before
function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func(x); // (**)
    cache.set(x, result);
    return result;
  };
}

alert( worker.slow(1) ); // the original method works

worker.slow = cachingDecorator(worker.slow); // now make it caching

alert( worker.slow(2) ); // Whoops! Error: Cannot read property 'someMethod' of undefined

// 內建的 func.call() 可以解決
func.call(context, arg1, arg2, ...)

// two calls same
func(1, 2, 3);
func.call(obj, 1, 2, 3);

// sample
function sayHi() {
  alert(this.name);
}

let user = { name: "John" };
let admin = { name: "Admin" };

// use call to pass different objects as "this"
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin

// 帶入參數
function say(phrase) {
  alert(this.name + ': ' + phrase);
}

let user = { name: "John" };

// user becomes this, and "Hello" becomes the first argument
say.call( user, "Hello" ); // John: Hello

// 改寫
let worker = {
  someMethod() {
    return 1;
  },

  slow(x) {
    alert("Called with " + x);
    return x * this.someMethod(); // (*)
  }
};

function cachingDecorator(func) {
  let cache = new Map();
  return function(x) {
    if (cache.has(x)) {
      return cache.get(x);
    }
    let result = func.call(this, x); // "this" is passed correctly now
    cache.set(x, result);
    return result;
  };
}

worker.slow = cachingDecorator(worker.slow); // now make it caching

alert( worker.slow(2) ); // works
alert( worker.slow(2) ); // works, doesn't call the original (cached)

Going multi-argument with “func.apply”

// 如何儲存物件的方法有 2 個以上的參數?
let worker = {
  slow(min, max) {
    return min + max; // scary CPU-hogger is assumed
  }
};

// should remember same-argument calls
worker.slow = cachingDecorator(worker.slow);

// 原生的 map 只能儲存單一值作為 key,但現在有 2 個參數
// 有多種方法可以解決
// 1.使用新的或第 3 方的資料結構,允許儲存多個參數
// 2.使用巢狀 map,cache.set(min) 儲存 (max, result) 的 map。
// 3.將 2 個參數合為 1 個,"min,max" 作為 key。將使用這個方法解決

// this = content args = array
func.apply(context, args)

// same
func(1, 2, 3);
func.apply(context, [1, 2, 3])

// sample
function say(time, phrase) {
  alert(`[${time}] ${this.name}: ${phrase}`);
}

let user = { name: "John" };

let messageData = ['10:00', 'Hello']; // become time and phrase

// user becomes this, messageData is passed as a list of arguments (time, phrase)
say.apply(user, messageData); // [10:00] John: Hello (this=user)

// call 跟 apply 差別在於一個傳入參數列表 iterable,一個傳入類似 array 的參數 array-like
let args = [1, 2, 3];

func.call(context, ...args); // pass an array as list with spread operator
func.apply(context, args);   // is same as using apply

// 改寫
let worker = {
  slow(min, max) {
    alert(`Called with ${min},${max}`);
    return min + max;
  }
};

function cachingDecorator(func, hash) {
  let cache = new Map();
  return function() {
    let key = hash(arguments); // (*)
    if (cache.has(key)) {
      return cache.get(key);
    }

    let result = func.apply(this, arguments); // (**)

    cache.set(key, result);
    return result;
  };
}

function hash(args) {
  return args[0] + ',' + args[1];
}

worker.slow = cachingDecorator(worker.slow, hash);

alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)

// * 將參數 3. 5 轉為 "3, 5"
// ** 用 func.apply() 傳遞參數

Borrowing a method

function hash(args) {
  return args[0] + ',' + args[1];
}

// 改為可以使用多個參數
function hash(args) {
  return args.join();
}

// 但 arguments 並不是真正的 array 所以會出錯誤
function hash() {
  alert( arguments.join() ); // 报错:arguments.join 不是函数
}

hash(1, 2);

// 有一個簡單的方法可以解決,叫做 method borrowing 
function hash() {
  alert( [].join.call(arguments) ); // 1,2
}

hash(1, 2);

Last updated