2012-01-15 5 views
7

Nella fantastica libreria Underscore.js di Jeremy Ashkenas, ho cercato di capire una cosa sul file sorgente. Non capisco questo:Cercando di capire sorgente underscore.js - chiama e applica usato nella libreria

var slice = Array.prototype.slice; 
args = slice.call(arguments, 2); 

In modo che:

args = Array.prototype.slice.call(arguments, 2); 

.call o .apply sono i metodi delle funzioni. Ma qui, a quali funzioni si riferisce .call? Il primo parametro dovrebbe essere il contesto, ma arguments è contesto? Il secondo parametro dovrebbe essere il parametro da passare nelle funzioni. Qui sono il numero 2. Cosa significa questo? A volte nella libreria, utilizza 1 o 0. Sono il numero dei parametri da passare nelle funzioni?

_.bind = function bind(func, context) { 
    var bound, args; 
    if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 
    if (!_.isFunction(func)) throw new TypeError; 
    args = slice.call(arguments, 2); 
    return bound = function() { 
     if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 
     ctor.prototype = func.prototype; 
     var self = new ctor; 
     var result = func.apply(self, args.concat(slice.call(arguments))); 
     if (Object(result) === result) return result; 
     return self; 
    }; 
    }; 

Domanda 2: Io non capisco la logica di questa funzione. Hai bisogno di aiuto per capire. Un esempio dovrebbe essere molto utile.

// Invoke a method (with arguments) on every item in a collection. 
    _.invoke = function(obj, method) { 
    var args = slice.call(arguments, 2); 
    return _.map(obj, function(value) { 
     return (method.call ? method || value : value[method]).apply(value, args); 
    }); 
    }; 

Grazie per l'aiuto.

risposta

7

La funzione "slice" sul prototipo di array prevede che this farà riferimento alla matrice su cui deve funzionare. In altre parole, se si dispone di una vera e propria matrice:

var myArray = [1, 2, 3]; 

e si chiama slice():

var sliced = myArray.slice(1); 

Poi in quella chiamata a slice(), this si riferisce alla matrice "myArray". Come Raynos osserva in un commento:

myArray.slice(1) 

è lo stesso di

myArray.slice.call(myArray, 1); 

Così, quando si utilizza call() per richiamare la funzione, e passarlo arguments come l'oggetto di contesto, il codice slice() opera su arguments . Gli altri parametri passati tramite .call() sono semplicemente il parametro oi parametri per slice() stesso. Nel mio esempio sopra, nota che ho passato 1 alla funzione.

Ora, quanto alla seconda domanda, la funzione .invoke() innanzitutto isola gli argomenti passati dopo i primi due. Ciò significa che quando si utilizza _.invoke() si passa a due o più argomenti: il primo è l'elenco su cui operare, il secondo è il metodo e gli argomenti successivi (facoltativi) vengono passati al metodo per ciascun elemento dell'elenco .

Questa chiamata a _.map() è complicata (e in effetti penso che abbia un po 'di assurdità). Quello che sta facendo è scorrere l'elenco, chiamando una funzione per ogni valore nella lista. Quale funzione fa in primo luogo determinare se il parametro "metodo" sia realmente una funzione. Se lo è, chiama questa funzione tramite .apply() con l'elemento della lista come contesto. Se "metodo" è non una funzione, quindi assume che sia il nome di una proprietà di ciascun elemento di elenco e che le proprietà siano funzioni.

Così, per esempio, con un semplice elenco è abbastanza semplice:

var myList = [1, 2, 3]; 
var result = _.invoke(myList, function(n) { return this * n; }, 2); 

che darà il risultato [2, 4, 6] perché la funzione passai moltiplica il suo oggetto contesto (this) per il parametro passato, e ho passato 2 nella chiamata a _.invoke().

Con un elenco più complicato, posso utilizzare il secondo sapore di _.invoke() e chiamare un metodo su ciascun oggetto dell'elenco:

var getName = function(prefix) { return prefix + " " + this.name; }; 
var list = [ 
    { name: "Bob", getName: getName }, 
    { name: "Sam", getName: getName }, 
    { name: "Lou", getName: getName } 
]; 

var result = _.invoke(list, "getName", "Congressman"); 

che chiamerà la funzione "getName" a ciascun oggetto dell'elenco e restituire una lista fatta dai risultati. L'effetto sarà la lista ["Congressman Bob", "Congressman Sam", "Congressman Lou"].

Ora che assurdità. Nel codice per _.invoke():

return _.map(obj, function(value) { 
    return (method.call ? method || value : value[method]).apply(value, args); 
}); 

Quello subexpresion method || value sarà sempre restituire il valore di "metodo", o almeno quasi sempre salvo qualche trucco esotico. Se method.call è sincero, anche un riferimento a method deve essere veritiero. Inoltre, se fosse il mio codice, ispezionerei methodfuori il callback _.map() in modo che la decisione non debba essere ripetuta più e più volte. Forse qualcosa di simile:

return _.map(obj, method.call ? 
    function(value) { method.apply(value, args); } : 
    function(value) { value[method].apply(value, args); } 
); 
+2

'myArray.slice (1) === slice.call (myArray, 1)' – Raynos

+0

Sì che è un grande, modo semplice per mostrare la relazione. – Pointy

+2

@Raynos, in realtà è falso. 'slice' crea due diversi oggetti array, che non sono identici. – katspaugh