5

The docs tell us:

Let's say we want to create a plugin that makes text within a set of retrieved elements green. All we have to do is add a function called greenify to $.fn and it will be available just like any other jQuery object method.

$.fn.greenify = function() {
    this.css( "color", "green" );
};


$( "a" ).greenify(); // Makes all the links green.

Notice that to use .css(), another method, we use this, not $( this ). This is because our greenify function is a part of the same object as .css().

I don't understand that last paragraph. What does the function pass to this? Why not $(this) to refer to the jQuery object? Don't we use $(el).css() to normally set CSS in jQuery? Then why not within a plugin?

Bram Vanroy
  • 27,032
  • 24
  • 137
  • 239
  • 3
    because in this case _this_ refers to jQuery object – Grundy May 27 '15 at 09:39
  • `greenify` is function from jQuery prototype, since `$.prototype === $.fn`, so context in this case - jQuery object. Of course if you do something like: `var f = $( "a" ).greenify; f();` then _this_ not jQuery object, in this case - it global object – Grundy May 27 '15 at 09:42
  • @Grundy, is css itself defined like $.fn.css = function(){...}? – AmmarCSE May 27 '15 at 09:45
  • If that help you to understand it, it could be write as (it can't here, that's just an example of syntax): `greenify($( "a" ))`, so this inside `greenify()` method will be the matched set of element(s), `$( "a" )` – A. Wolff May 27 '15 at 09:47
  • @A.Wolff That, I understand, but not that why then inside `greenify` you don't have reference back to the jQuery object. For example, in an each function, you also always refer back to the jQuery object, right? `$(".els").each(function(){ var $this = $(this);});`? – Bram Vanroy May 27 '15 at 09:51
  • 1
    @BramVanroy you a bit confused with jQuery function, and callback passed to it: for example in each you pass callback that call like this: `callback.call( obj[ i ], i, obj[ i ] )` where `obj[i]` refers to dom element or element in collection – Grundy May 27 '15 at 09:54

2 Answers2

2

Let's try see a bit deeper:

let's try generate a very simplified version lib, like jQuery, and name it for example microM

(function(global) {
  //function analog jQuery
  var microM = function(context) { 
    return new microM.fn.init(context);
  }

  //init prototype
  microM.fn = microM.prototype = {
    version: '0.0.0.1',
    constructor: microM
  };

  //function for initialize context
  var init = microM.fn.init = function(context) {
    if (context instanceof microM) context = microM.extend([], context.context);

    this['context'] = [].concat(context);
    return this;
  };

  init.prototype = microM.fn;

  //add function extend to prototype and as static method
  microM.extend = microM.fn.extend = function() {
    if (arguments.length == 2) {
      var target = arguments[0],
        source = arguments[1];
    } else {
      var target = this,
        source = arguments[0];
    }
    for (var key in source) {
      target[key] = source[key];
    }

    return target;
  }

  //extend microM prototype with a few simple function
  microM.fn.extend({
    min: function() {
      return Math.min.apply(Math, this.context);
    },
    max: function() {
      return Math.max.apply(Math, this.context);
    },
    pow: function(exponent) {
      for (var i = 0, len = this.context.length; i < len; i++) {
        this.context[i] = Math.pow(this.context[i], exponent);
      }
      return this;
    },
    get: function() {
      return microM.extend([], this.context);
    },
    map: function(callback) {//a function that takes a callback
      var result = [];
      for (var i = 0, len = this.context.length; i < len; i++) {
        var callbackResult = callback.call(this.context[i], this.context[i], i);
        if (callbackResult instanceof microM) result = result.concat(callbackResult.get());
        else result = result.concat(callbackResult);
      }
      return microM(result);
    }
  });

  //looks a like jQuery :-)
  global.microM = microM;
})(window);

So we have a simplest lib looks a like jQuery. Now we want add "plugin" to it, for example function square.

As in jQuery we add this to prototype, or fn that same as prototype in our case:

microM.fn.square = function() {
  return this.pow(2);
}

here we can call pow directly from this because in this case this instance of our microM, and all functions from microM.prototype is available directly;

But when we call our map function that takes a callback inside callback this will be concrete element, for example Number primitive, because we call it like

callback.call(this.context[i], this.context[i], i);

where first param in call function - is thisArg.

Possibly code snippet below can make clear my muddled explanation :-)

(function(global) {
  var microM = function(context) {
    return new microM.fn.init(context);
  }

  microM.fn = microM.prototype = {
    version: '0.0.0.1',
    constructor: microM
  };

  var init = microM.fn.init = function(context) {
    if (context instanceof microM) context = microM.extend([], context.context);

    this['context'] = [].concat(context);
    return this;
  };

  init.prototype = microM.fn;

  microM.extend = microM.fn.extend = function() {
    if (arguments.length == 2) {
      var target = arguments[0],
        source = arguments[1];
    } else {
      var target = this,
        source = arguments[0];
    }
    for (var key in source) {
      target[key] = source[key];
    }

    return target;
  }

  microM.fn.extend({
    min: function() {
      return Math.min.apply(Math, this.context);
    },
    max: function() {
      return Math.max.apply(Math, this.context);
    },
    pow: function(exponent) {
      for (var i = 0, len = this.context.length; i < len; i++) {
        this.context[i] = Math.pow(this.context[i], exponent);
      }
      return this;
    },
    get: function() {
      return microM.extend([], this.context);
    },
    map: function(callback) {
      var result = [];
      for (var i = 0, len = this.context.length; i < len; i++) {
        var callbackResult = callback.call(this.context[i], this.context[i], i);
        if (callbackResult instanceof microM) result = result.concat(callbackResult.get());
        else result = result.concat(callbackResult);
      }
      return microM(result);
    }
  });

  global.microM = microM;
})(window);


microM.fn.printTo = function(id, descr) {
  document.getElementById(id).innerHTML += (descr ? descr + ": " : "") + JSON.stringify(this.get()) + '<br/>';
  return this;
}

microM.fn.square = function() {
  return this.pow(2);
}

var t = microM([2, 3, 4]).printTo('res', 'initial');
t.square().printTo('res', 'square')
  .map(function(el) {
    return microM(this + 10).square();
  }).printTo('res', 'mapped')
  .map(function(el) {
    return this instanceof Number;
  }).printTo('res', 'inside map: this instanceof Number');
<div id="res"></div>
Grundy
  • 13,356
  • 3
  • 35
  • 55
1

Don't we use $(el).css() to normally set CSS in jQuery?

Yes, when in the context of an element.

However in

$.fn.greenify = function() {
    // 'this' is a jQuery object at this point - with all the jQuery functions
    this.css( "color", "green" );
};

greenify is part of the same object that has the css function.

Somewhere else, there is a

$.fn.css = function() {
       ...
    };

Both css and greenify are part of the prototype($.fn)

See jQuery: What's the difference between '$(this)' and 'this'? and https://remysharp.com/2007/04/12/jquerys-this-demystified

Community
  • 1
  • 1
AmmarCSE
  • 30,079
  • 5
  • 45
  • 53
  • I'm afraid I still don't understand. Let's say we do `$("div").greenify()`, then greenify is a function that takes the jQuery div object as its only argument. But I don't understand why you don't have to refer back to the jQuery object again inside that function. – Bram Vanroy May 27 '15 at 09:43