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