2016-07-14 27 views
6

我有一個實現XMLHttpRequest接口的類。根據傳遞給open()的URL,我可以確定是使用默認的XMLHttpRequest還是使用我的自定義實現。我的想法是使用代理來做到這一點:是否可以更改代理的目標?

let xhr = new XHRProxy(); 
xhr.open('GET', 'http://blah'); // Decide here depending on URL 

我沒有使用ES6代理,這似乎是有前途的一些測試,但不幸的是,代理目標無法構建代理後可以修改:

var foo = { 
    name() { 
     return "foo"; 
    } 
}; 
var bar = { 
    name() { 
     return "bar"; 
    } 
} 
var handler = { 
    get(target, property, receiver) { 
     if (property === "switchToBar") { 
      // FIXME: This doesn't work because a Proxy's target is not exposed AFAIK 
      receiver.target = bar; 
      return function() {}; 
     } else { 
      return target[property]; 
     } 
    } 
} 
var proxy = new Proxy(foo, handler); 
console.log(proxy.name()); // foo 
proxy.switchToBar(); 
console.log(proxy.name()); // foo :(

我想我可以通過完全不設置目標來完成我想要的任務 - 而是將所有陷阱定義爲委託給所需的對象 - 但我希望獲得更簡單的解決方案。

+0

請注意,您正在嘗試更改已獲取'.switchToBar'屬性的目標,而不是調用'.switchToBar()'方法 – Bergi

+0

是的,但我不認爲這個例子很重要。爲了在XHR.open中使用它,實際上我想在調用XHR.open實現之前設置委託。 –

回答

6

這裏的「定義的所有陷阱委託給所需的對象」

(function() { 
    let mutableTarget; 
    let mutableHandler; 

    function setTarget(target) { 
    if (!(target instanceof Object)) { 
     throw new Error(`Target "${target}" is not an object`); 
    } 
    mutableTarget = target; 
    } 

    function setHandler(handler) { 
    Object.keys(handler).forEach(key => { 
     const value = handler[key]; 

     if (typeof value !== 'function') { 
     throw new Error(`Trap "${key}: ${value}" is not a function`); 
     } 

     if (!Reflect[key]) { 
     throw new Error(`Trap "${key}: ${value}" is not a valid trap`); 
     } 
    }); 
    mutableHandler = handler; 
    } 

    function mutableProxyFactory() { 
    setTarget(() => {}); 
    setHandler(Reflect); 

    // Dynamically forward all the traps to the associated methods on the mutable handler 
    const handler = new Proxy({}, { 
     get(target, property) { 
     return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]); 
     } 
    }); 

    return { 
     setTarget, 
     setHandler, 
     getTarget() { 
     return mutableTarget; 
     }, 
     getHandler() { 
     return mutableHandler; 
     }, 
     proxy: new Proxy(mutableTarget, handler) 
    }; 
    } 

    window.mutableProxyFactory = mutableProxyFactory; 
})(); 

const { 
    proxy, 
    setTarget 
} = mutableProxyFactory(); 

setTarget(() => 0); 
console.log(`returns: ${proxy()}`); 

setTarget({ val: 1 }); 
console.log(`val is: ${proxy.val}`); 

setTarget({ val: 2 }); 
console.log(`val is: ${proxy.val}`); 

setTarget(() => 3); 
console.log(`returns: ${proxy()}`); 

我覺得必須有某種原因,這是不支持開箱即用的去,但我沒有足夠的信息進一步評論。

經過一段時間的黑客攻擊後,我觀察了一些事情。看起來代理構造函數被調用的原始目標被視爲代理身份的一部分,無論如何。將原始目標設置爲普通對象並稍後將目標交換到函數時,會在調用代理時引發錯誤。肯定有一些粗糙的邊緣,所以謹慎使用。

+0

哦,非常聰明!使用代理處理器的好主意,它允許更改目標(和處理程序),代碼非常少。你說得對,這種黑客有一些問題。我遇到了一些測試我的(不太優雅的)實現。例如,Object.preventExtensions(proxy);會拋出一個錯誤,因爲Proxy使用它構建的目標代替可變目標來執行一些驗證。我認爲沒有制定目標/處理程序的安裝人員是一種疏忽 - 即使有充分的理由,仍然很難過。這至少起作用。謝謝! –

2

是否可以更改代理的目標?

不,這是不可能的。代理處理程序已經是一個非常通用的接口,並且通過定義所有陷阱將操作轉發給不同的處理程序,這很容易實現。這就是爲什麼沒有額外的方法來改變目標,接口保持最小。通過不使目標可變,還保留了代理的形狀(例如,它是可調用的還是數組)。

1

這個怎麼樣?我們不直接製作foobar目標,而是使用目標框,並將foobar放入框中。

var foo = { 
    name() { 
     return "foo"; 
    } 
}; 
var bar = { 
    name() { 
     return "bar"; 
    } 
}; 
var handler = { 
    get(target, property, receiver) { 
     if (property === "switchToBar") { 
      target.content = bar; 
      return function() {}; 
     } else { 
      return target.content[property]; 
     } 
    } 
}; 

var box = {content: foo}; 
var proxy = new Proxy(box, handler); 

console.log(proxy.name()); // foo 
// Switch over to bar by calling the function 
proxy.switchToBar(); 
// Or, we could do the switch from out here 
box.content = bar; 
// Either way, we get the same result 
console.log(proxy.name()); // bar 

在這種情況下,我們的箱子是屬性爲content的物體。但是,您也可以使用帶有索引爲0的項目的數組。

相關問題