# Decorators and forwarding, call/apply

## Transparent caching

```javascript
// 有一個很吃資源的函式，可以用一個包裝函式把函式丟進去，同樣參數跑出同樣結果不用重複運算。
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

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

![](/files/-LhinQ-HHuKBXDeR--ww)

## Using “func.call” for the context

```javascript
// 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”

```javascript
// 如何儲存物件的方法有 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

```javascript
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);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mistborn.gitbook.io/til-coding/javascript/decorators-and-forwarding-call-apply.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
