2015-06-26 10 views
8

Chcę wywołać funkcję z niestandardowym thisArg.Czy istnieje bezpieczny sposób wywoływania `call` w celu wywołania funkcji w JavaScript?

To wydaje się banalne, po prostu trzeba zadzwonić call:

func.call(thisArg, arg1, arg2, arg3); 

Ale czekaj! func.call może nie być Function.prototype.call.

Więc pomyślałem o użyciu

Function.prototype.call.call(func, thisArg, arg1, arg2, arg3); 

Ale czekaj! Function.prototype.call.call może nie być Function.prototype.call.

Zatem zakładając, że Function.prototype.call jest rodzimą wersją, ale biorąc pod uwagę możliwość dodania dowolnych nie-wewnętrznych właściwości, czy ECMAScript zapewnia bezpieczny sposób na wykonanie następujących czynności?

func.[[Call]](thisArg, argumentsList) 
+4

Zajęło mi 3 czytania ... ale dobre pytanie! –

Odpowiedz

4

To moc (i ryzyko) kaczki typowania: if typeof func.call === 'function', wtedy powinniśmy traktować go tak, jakby to była normalna, wpłacone funkcji. To zależy od dostawcy usługi func, aby upewnić się, że jego właściwość call jest zgodna z sygnaturą publiczną. Używam tego w kilku miejscach, ponieważ JS nie zapewnia sposobu na przeciążenie operatora () i zapewnienia klasycznego funktora.

Jeśli naprawdę trzeba unikać func.call, pójdę z func() i wymagają func wziąć thisArg jako pierwszy argument. Ponieważ func() nie deleguje do call (tj. f(g, h) nie cofa się do f.call(t, g, h)) i można używać zmiennych po lewej stronie parens, da to przewidywalne wyniki.

Można również buforować odwołanie do Function.prototype.call, gdy biblioteka zostanie załadowana, na wypadek gdyby została później wymieniona, i użyć jej do późniejszego wywołania funkcji. Jest to wzorzec używany przez lokalnie/podkreślenie do przechwytywania natywnych metod tablicowych, ale nie zapewnia żadnej faktycznej gwarancji otrzymania oryginalnej metody wywołania natywnego. Może być bardzo blisko i nie jest okropnie brzydka:

const call = Function.prototype.call; 

export default function invokeFunctor(fn, thisArg, ...args) { 
    return call.call(fn, thisArg, ...args); 
} 

// Later... 
function func(a, b) { 
    console.log(this, a, b); 
} 

invokeFunctor(func, {}, 1, 2); 

Jest to podstawowy problem w każdym języku z polimorfizmem. W pewnym momencie musisz zaufać obiektowi lub bibliotece, aby zachowywać się zgodnie z jej umową. Podobnie jak w każdym innym przypadku, zaufanie, ale zweryfikować:

if (typeof duck.call === 'function') { 
    func.call(thisArg, ...args); 
} 

z rodzajem kontroli, można zrobić kilka błędów obsługi, a także:

try { 
    func.call(thisArg, ...args); 
} catch (e) { 
    if (e instanceof TypeError) { 
    // probably not actually a function 
    } else { 
    throw e; 
    } 
} 

Jeśli możesz poświęcić thisArg (lub zmusić go za rzeczywisty argument), a następnie można wpisać-czek i wywołać z parens:

if (func instanceof Function) { 
    func(...args); 
} 
+0

Ale coś, co wygląda jak urocza kaczka, może być ukrytym zabójczym mantykiem! Zatem próba nakarmienia go chlebem nie byłaby bezpieczną aktywnością. – Oriol

+2

@Oriol To na pewno możliwe, dlatego 'try {duck.feed()} catch (lostFingers) {console.log ('It's a manorore!'); } 'jest częścią języka. Jak każdy język z polimorfizmem, w pewnym momencie musisz zaufać obiektowi, aby działał poprawnie. – ssube

+0

Tak, domyślam się, że jedynym bezpiecznym sposobem jest jak najszybsze uruchomienie kodu i odniesienie do 'Function.prototype.call'. A może "pieczęć" lub "zamrożenie" go. – Oriol

2

w pewnym momencie trzeba zaufać, co jest dostępne w oknie. Oznacza to albo buforowanie funkcji, z których planujesz korzystać, albo próby piaskowania kodu.

„simple” rozwiązanie wywołaniem call jest tymczasowo ustawić właściwość:

var safeCall = (function (call, id) { 
    return function (fn, ctx) { 
     var ret, 
      args, 
      i; 
     args = []; 
     // The temptation is great to use Array.prototype.slice.call here 
     // but we can't rely on call being available 
     for (i = 2; i < arguments.length; i++) { 
      args.push(arguments[i]); 
     } 
     // set the call function on the call function so that it can be...called 
     call[id] = call; 
     // call call 
     ret = call[id](fn, ctx, args); 
     // unset the call function from the call function 
     delete call[id]; 
     return ret; 
    }; 
}(Function.prototype.call, (''+Math.random()).slice(2))); 

ten można wykorzystać jako:

safeCall(fn, ctx, ...params); 

Należy pamiętać, że parametry przekazywane do safeCall będzie skumulowany razem w tablicy. Aby to poprawnie działało, potrzebujesz apply, a ja próbuję tutaj uprościć zależności.


ulepszona wersja safeCall dodając zależność do apply:

var safeCall = (function (call, apply, id) { 
    return function (fn, ctx) { 
     var ret, 
      args, 
      i; 
     args = []; 
     for (i = 2; i < arguments.length; i++) { 
      args.push(arguments[i]); 
     } 
     apply[id] = call; 
     ret = apply[id](fn, ctx, args); 
     delete apply[id]; 
     return ret; 
    }; 
}(Function.prototype.call, Function.prototype.apply, (''+Math.random()).slice(2))); 

ten może być stosowany jako:

safeCall(fn, ctx, ...params); 

Alternatywnym rozwiązaniem do bezpiecznego wywołanie połączenia jest użycie funkcje z innego kontekstu okna.

Można to zrobić po prostu tworząc nowe iframe i funkcje przechwytywania z jego okna. Nadal musisz założyć pewną ilość uzależnienia od manipulacji DOM funkcje są dostępne, ale to się dzieje w kroku konfiguracji, tak że wszelkie przyszłe zmiany nie będą miały wpływu na istniejący skrypt:

var sandboxCall = (function() { 
    var sandbox, 
     call; 
    // create a sandbox to play in 
    sandbox = document.createElement('iframe'); 
    sandbox.src = 'about:blank'; 
    document.body.appendChild(sandbox); 
    // grab the function you need from the sandbox 
    call = sandbox.contentWindow.Function.prototype.call; 
    // dump the sandbox 
    document.body.removeChild(sandbox); 
    return call; 
}()); 

ten może następnie być używany jako:

sandboxCall.call(fn, ctx, ...params); 

Zarówno safeCall i sandboxCall są bezpieczne od przyszłości zmiany Function.prototype.call, ale jak c widzą, że polegają na niektórych istniejących funkcjach globalnych, aby działały w środowisku wykonawczym. Jeśli złośliwy skrypt wykona przed tego kodu, twój kod nadal będzie zagrożony.

1

Jeśli ufasz Function.prototype.call, można zrobić coś takiego:

func.superSecureCallISwear = Function.prototype.call; 
func.superSecureCallISwear(thisArg, arg0, arg1 /*, ... */); 

Jeśli ufasz Function..call ale nie Function..call.call, można to zrobić:

var evilCall = Function.prototype.call.call; 
Function.prototype.call.call = Function.prototype.call; 
Function.prototype.call.call(fun, thisArg, arg0, arg1 /*, ... */); 
Function.prototype.call.call = evilCall; 

A może nawet zawinąć które w sposób pomocnik.

Jeśli funkcje są czyste i obiektami serializable, można utworzyć iframe oraz poprzez przekazywanie komunikatów (window.postMessage), przekaż kod funkcji i argumenty, niech to zrobić call dla Ciebie (ponieważ jest to nowy iframe bez każdy 3-cia kod partia jesteś całkiem bezpieczny), i jesteś złoty, coś jak (nie testowane w ogóle, prawdopodobnie usiane błędami):

// inside iframe 
window.addEventListener('message', (e) => { 
    let { code: funcCode, thisArg, args } = e.data; 
    let res = Function(code).apply(thisArg, args); 

    e.source.postMessage(res, e.origin); 
}, false); 

To samo można zrobić z Web Workers.

Jeśli tak jest, możesz zrobić krok dalej i wysłać go na serwer. Jeśli używasz węzła, możesz bezpiecznie uruchamiać dowolne skrypty za pomocą vm module. W Javie masz projekty takie jak Rhino i Nashorn; Jestem pewien, że .Net ma swoje własne implementacje (może nawet uruchamiać je jako JScript!) I prawdopodobnie w PHP są uszkodzone wirtualne maszyny wirtualne javascript.

Jeśli można zrobić że, dlaczego nie wykorzystać usługę jak Runnable do on-the-fly utworzyć javascript Piaskownice, może nawet ustawić własne środowisko po stronie serwera za to.