Câu trả lời:
Tham số ngữ cảnh chỉ đặt giá trị của this
hàm iterator.
var someOtherArray = ["name","patrick","d","w"];
_.each([1, 2, 3], function(num) {
// In here, "this" refers to the same Array as "someOtherArray"
alert( this[num] ); // num is the value from the array being iterated
// so this[num] gets the item at the "num" index of
// someOtherArray.
}, someOtherArray);
Ví dụ hoạt động: http://jsfiddle.net/a6Rx4/
Nó sử dụng số từ mỗi thành viên của Mảng được lặp để lấy mục ở chỉ mục someOtherArray
đó, được biểu thị bởi this
vì chúng tôi đã chuyển nó làm tham số ngữ cảnh.
Nếu bạn không đặt bối cảnh, thì this
sẽ tham chiếu đến window
đối tượng.
context
là nơi this
đề cập đến trong chức năng lặp của bạn. Ví dụ:
var person = {};
person.friends = {
name1: true,
name2: false,
name3: true,
name4: true
};
_.each(['name4', 'name2'], function(name){
// this refers to the friends property of the person object
alert(this[name]);
}, person.friends);
Ngữ cảnh cho phép bạn cung cấp các đối số tại thời điểm cuộc gọi, cho phép dễ dàng tùy chỉnh các hàm trợ giúp được xây dựng trước chung chung.
vài ví dụ:
// stock footage:
function addTo(x){ "use strict"; return x + this; }
function pluck(x){ "use strict"; return x[this]; }
function lt(x){ "use strict"; return x < this; }
// production:
var r = [1,2,3,4,5,6,7,8,9];
var words = "a man a plan a canal panama".split(" ");
// filtering numbers:
_.filter(r, lt, 5); // elements less than 5
_.filter(r, lt, 3); // elements less than 3
// add 100 to the elements:
_.map(r, addTo, 100);
// encode eggy peggy:
_.map(words, addTo, "egg").join(" ");
// get length of words:
_.map(words, pluck, "length");
// find words starting with "e" or sooner:
_.filter(words, lt, "e");
// find all words with 3 or more chars:
_.filter(words, pluck, 2);
Ngay cả từ các ví dụ hạn chế, bạn có thể thấy "đối số bổ sung" có thể mạnh đến mức nào để tạo mã có thể sử dụng lại. Thay vì thực hiện một chức năng gọi lại khác nhau cho từng tình huống, bạn thường có thể điều chỉnh một trình trợ giúp cấp thấp. Mục tiêu là để logic tùy chỉnh của bạn kết hợp một động từ và hai danh từ, với bản tóm tắt tối thiểu.
Phải thừa nhận rằng, các hàm mũi tên đã loại bỏ rất nhiều lợi thế "mã golf" của các hàm thuần túy chung, nhưng các lợi thế về ngữ nghĩa và tính nhất quán vẫn còn.
Tôi luôn thêm "use strict"
vào các trình trợ giúp để cung cấp [].map()
khả năng tương thích riêng khi vượt qua các nguyên thủy. Mặt khác, chúng bị ép buộc vào các đối tượng, thường vẫn hoạt động, nhưng nó nhanh hơn và an toàn hơn để được cụ thể theo kiểu.
_.each(['Hello', 'World!'], function(word){
console.log(word);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Đây là ví dụ đơn giản có thể sử dụng _.each
:
function basket() {
this.items = [];
this.addItem = function(item) {
this.items.push(item);
};
this.show = function() {
console.log('items: ', this.items);
}
}
var x = new basket();
x.addItem('banana');
x.addItem('apple');
x.addItem('kiwi');
x.show();
Đầu ra:
items: [ 'banana', 'apple', 'kiwi' ]
Thay vì gọi addItem
nhiều lần, bạn có thể sử dụng gạch dưới theo cách này:
_.each(['banana', 'apple', 'kiwi'], function(item) { x.addItem(item); });
giống hệt như gọi addItem
ba lần liên tiếp với các mục này. Về cơ bản, nó lặp lại mảng của bạn và cho mỗi mục gọi hàm gọi lại ẩn danh của bạn mà gọi x.addItem(item)
. Hàm gọi lại ẩn danh tương tự như addItem
hàm thành viên (ví dụ: nó lấy một mục) và là loại vô nghĩa. Vì vậy, thay vì đi qua chức năng ẩn danh, tốt hơn hết là _.each
tránh sự gián tiếp này và gọi addItem
trực tiếp:
_.each(['banana', 'apple', 'kiwi'], x.addItem);
nhưng điều này sẽ không hoạt động, vì addItem
chức năng thành viên của giỏ this
sẽ không đề cập đến x
giỏ của bạn mà bạn đã tạo. Đó là lý do tại sao bạn có một tùy chọn để vượt qua giỏ hàng của mình x
để được sử dụng như [context]
:
_.each(['banana', 'apple', 'kiwi'], x.addItem, x);
function basket() {
this.items = [];
this.addItem = function(item) {
this.items.push(item);
};
this.show = function() {
console.log('items: ', this.items);
}
}
var x = new basket();
_.each(['banana', 'apple', 'kiwi'], x.addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Nói tóm lại, nếu chức năng gọi lại mà bạn chuyển đến _.each
bằng bất kỳ cách nào sử dụng this
thì bạn cần chỉ định những gì this
sẽ được đề cập bên trong chức năng gọi lại của bạn. Nó có vẻ như x
là không cần thiết trong ví dụ của tôi, nhưng x.addItem
chỉ là một chức năng và có thể hoàn toàn không liên quan đến x
hoặc basket
hoặc bất kỳ đối tượng khác, ví dụ :
function basket() {
this.items = [];
this.show = function() {
console.log('items: ', this.items);
}
}
function addItem(item) {
this.items.push(item);
};
var x = new basket();
_.each(['banana', 'apple', 'kiwi'], addItem, x);
x.show();
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Nói cách khác, bạn liên kết một số giá trị vào this
bên trong cuộc gọi lại của mình hoặc bạn cũng có thể sử dụng liên kết trực tiếp như thế này:
_.each(['banana', 'apple', 'kiwi'], addItem.bind(x));
Làm thế nào tính năng này có thể hữu ích với một số phương pháp gạch dưới khác nhau?
Nói chung, nếu một underscorejs
phương thức nào đó có chức năng gọi lại và nếu bạn muốn cuộc gọi đó được gọi trên một số chức năng thành viên của một đối tượng nào đó (ví dụ như một hàm sử dụng this
) thì bạn có thể liên kết chức năng đó với một đối tượng hoặc truyền đối tượng đó làm [context]
tham số và đó là ý định chính. Và ở đầu tài liệu underscorejs, đó chính xác là những gì họ nêu: iteratee bị ràng buộc với đối tượng bối cảnh, nếu một được thông qua
Như đã giải thích trong câu trả lời khác, context
là this
bối cảnh được sử dụng bên trong gọi lại truyền choeach
.
Tôi sẽ giải thích điều này với sự trợ giúp của mã nguồn của các phương thức liên quan từ mã nguồn gạch dưới
Định nghĩa của _.each
hoặc _.forEach
như sau:
_.each = _.forEach = function(obj, iteratee, context) {
iteratee = optimizeCb(iteratee, context);
var i, length;
if (isArrayLike(obj)) {
for (i = 0, length = obj.length; i < length; i++) {
iteratee(obj[i], i, obj);
}
} else {
var keys = _.keys(obj);
for (i = 0, length = keys.length; i < length; i++) {
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj;
};
Tuyên bố thứ hai rất quan trọng cần lưu ý ở đây
iteratee = optimizeCb(iteratee, context);
Ở đây, context
được truyền cho một phương thức khác optimizeCb
và hàm trả về từ nó sau đó được gán cho iteratee
cái được gọi sau này.
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1:
return function(value) {
return func.call(context, value);
};
case 2:
return function(value, other) {
return func.call(context, value, other);
};
case 3:
return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4:
return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
Như có thể thấy từ định nghĩa phương thức trên optimizeCb
, nếu context
không được thông qua thì func
được trả về như hiện tại. Nếu context
được thông qua, hàm gọi lại được gọi là
func.call(context, other_parameters);
^^^^^^^
func
được gọi với call()
cái được sử dụng để gọi một phương thức bằng cách đặt this
bối cảnh của nó. Vì vậy, khi this
được sử dụng bên trong func
, nó sẽ đề cập đến context
.
// Without `context`
_.each([1], function() {
console.log(this instanceof Window);
});
// With `context` as `arr`
var arr = [1, 2, 3];
_.each([1], function() {
console.log(this);
}, arr);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
Bạn có thể coi context
là tham số tùy chọn cuối cùng forEach
trong JavaScript.
someOtherArray[num]
chứ không phảithis[num]
?