2012-01-15 30 views
7

Trong thư viện Underscore.js tuyệt vời của Jeremy Ashkenas, tôi đã cố gắng hiểu một điều về tệp nguồn. Tôi không hiểu điều này:Cố gắng hiểu nguồn gọi underscore.js và áp dụng được sử dụng trong thư viện

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

Vì vậy mà:

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

.call hoặc .apply là các phương pháp của các chức năng. Nhưng ở đây, chức năng nào làm .call tham khảo? Tham số đầu tiên phải là ngữ cảnh, nhưng arguments là ngữ cảnh? Tham số thứ hai nên là các tham số để truyền trong các hàm. Đây là số 2. Điều đó có nghĩa là gì? Đôi khi trong thư viện, nó sử dụng 1 hoặc 0. Họ có phải là số lượng các tham số để vượt qua trong các chức năng không?

_.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; 
    }; 
    }; 

Câu hỏi 2: Tôi không hiểu rõ logic của hàm này. Cần giúp đỡ để hiểu. Một ví dụ nên rất hữu ích.

// 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); 
    }); 
    }; 

Cảm ơn bạn đã trợ giúp.

Trả lời

7

Chức năng "lát" trên mẫu thử Array dự kiến ​​rằng this sẽ tham chiếu đến mảng mà trên đó nó phải hoạt động. Nói cách khác, nếu bạn có một mảng thực:

var myArray = [1, 2, 3]; 

và bạn gọi slice():

var sliced = myArray.slice(1); 

Sau đó, trong đó kêu gọi slice(), this đề cập đến mảng "myArray". Như Raynos ghi nhận trong một chú thích:

myArray.slice(1) 

cũng giống như

myArray.slice.call(myArray, 1); 

Như vậy khi bạn sử dụng call() để gọi chức năng, và vượt qua nó arguments như đối tượng bối cảnh, mã slice() hoạt động trên arguments . Các tham số khác được truyền qua .call() chỉ đơn giản là tham số hoặc tham số cho chính số slice(). Trong ví dụ trên, lưu ý rằng tôi đã chuyển 1 cho hàm.

Bây giờ là câu hỏi thứ hai của bạn, hàm .invoke() đầu tiên cô lập các đối số được truyền sau sau hai đối số đầu tiên. Điều đó có nghĩa là khi bạn sử dụng _.invoke(), bạn vượt qua hai đối số hoặc nhiều hơn: thứ nhất là danh sách để hoạt động, thứ hai là phương thức và các đối số tiếp theo (tùy chọn) được chuyển đến phương pháp cho từng phần tử trong danh sách .

Cuộc gọi đó tới _.map() rất phức tạp (và thực tế tôi nghĩ nó có một chút vô nghĩa trong đó). Những gì nó đang làm là lặp qua danh sách, gọi một hàm cho mỗi giá trị trong danh sách. Chức năng đó thực hiện trước tiên để xác định xem tham số "method" có thực sự là một hàm hay không. Nếu có, thì nó gọi hàm đó qua .apply() với phần tử của danh sách dưới dạng ngữ cảnh. Nếu "phương thức" là không phải một hàm, thì nó giả định đó là tên của thuộc tính của từng phần tử danh sách và thuộc tính là các hàm.

Vì vậy, ví dụ, với một danh sách đơn giản nó khá đơn giản:

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

Điều đó sẽ cho kết quả [2, 4, 6] vì chức năng tôi đã thông qua sẽ nhân vật bối cảnh của nó (this) bởi tham số trôi qua, và tôi đã thông qua 2 trong cuộc gọi đến _.invoke().

Với một danh sách phức tạp hơn, tôi có thể sử dụng hương thứ hai của _.invoke() và gọi một phương thức trên từng đối tượng trong danh sách:

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"); 

Điều đó sẽ gọi là "getName" chức năng trên từng đối tượng trong danh sách và trả về một danh sách được tạo từ các kết quả. Hiệu ứng sẽ là danh sách ["Congressman Bob", "Congressman Sam", "Congressman Lou"].

Bây giờ về điều đó vô nghĩa. Trong mã cho _.invoke():

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

Đó subexpresion method || value sẽ luôn trả về giá trị của "phương pháp", hoặc ít nhất là hầu như luôn luôn chặn một số mẹo kỳ lạ. Nếu method.call là sự thật, thì tham chiếu đến method cũng phải đúng sự thật. Ngoài ra, nếu đó là mã của tôi, tôi sẽ kiểm tra methodbên ngoài gọi lại _.map() để quyết định không phải được thực hiện lặp đi lặp lại. Có lẽ cái gì đó như:

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

Có đó là một cách tuyệt vời đơn giản để hiển thị các mối quan hệ. – Pointy

+2

@Raynos, nó thực sự là sai. 'slice' tạo hai đối tượng mảng khác nhau, không giống nhau. – katspaugh

Các vấn đề liên quan