admin管理员组

文章数量:1334887

Element.prototype.each = function(fn) {
  for(var i = 0; i < this.length; i++) {
    fn(i);
  } 
};

var li = document.getElementsByTagName('li');

li.each(function(i) {
  this.style.borderBottom = '1px solid red';
});

I'm trying to make an each method like in jQuery. I tried many things in the for loop and in the callback but I got errors. I'm sure this is something to do with the 'this' context.

Element.prototype.each = function(fn) {
  for(var i = 0; i < this.length; i++) {
    fn(i);
  } 
};

var li = document.getElementsByTagName('li');

li.each(function(i) {
  this.style.borderBottom = '1px solid red';
});

I'm trying to make an each method like in jQuery. I tried many things in the for loop and in the callback but I got errors. I'm sure this is something to do with the 'this' context.

Share Improve this question asked Dec 18, 2014 at 10:39 Victor RicoVictor Rico 834 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 8

You can use call to set context

EDIT : Element was not the right class, it should be NodeList and HTMLCollection

NodeList.prototype.each = HTMLCollection.prototype.each = function(fn) {
  for(var i = 0; i < this.length; i++) {
    fn.call(this, i);
  } 
};

When you use Function.prototype.call it allows you to bind a context AKA this to a function

There are 3 ways AFAIK to do this:

  • call (as said above)
  • apply (which works like call except it takes only two arguments, the second being an array)
  • bind (which is used for currying)

Also note that as of DOM level 4 (ES6 harmony) there is a new class called Elements which extends Array and is meant to replace NodeList/HTMLCollection, so you wouldn't need to actually extend it in ES6 to add an each method and use Array.prototype.forEach instead (though you won't be able to use this in your callback.

document.getElementsByTagName('li') returns HTML Collection object, not Element.

It's quite easy to duplicate jQuery each (the only difference between native forEach and $.each is order of parameters - $.each uses (i,el) tuple, [].forEach uses (el,i) tuple). In modern browsers (all except IE8) you can just use:

document.getElementsByTagName('li').constructor.prototype.each = function(fn,thisArg) {
    [].forEach.call(this, function(el,i,array) {
        fn.call(thisArg, i,el,array);
    });
};

I don't remend to extend native prototypes, you should use [].forEach.call(yourNodeList, func) instead. It's possible to do yourNodeList = [].slice.call(yourNodeList); to convert your DOM collection to plain array.

I recently had cause to solve a particular problem in a similar manner and was interested to see how others had approached it. Bearing in mind the caveats posted about extending native prototypes, I'm just logging my solution, implemented in a browser environment only, for posterity.

/* For Arrays, enumerable Objects,
   DOMTokenLists and HTMLCollections etc */
Object.prototype.each = function(fn) {
    (Array.isArray(this) ? this : (this.constructor.name === 'Object' ? Object.keys(this) : [].slice.call(this))).forEach(fn);
};

:)

本文标签: