Hậu trường: Chuyện về hàm $ trong jQuery

Nhớ lại bài viết trước, hàm $ trong jQuery của chúng ta xuất hiện với dáng vẻ xem ra rất hiền lành đơn giản, một hàm $ có khả năng tạo, truy vấn và tìm kiếm mọi phần tử hiện diện trên trang web dựa trên cấu trúc truy vấn CSS Selector. Tuy nhiên, câu chuyện hậu trường sau đây sẽ hé mở nhiều tình tiết ly kỳ đằng sau cái dáng vẻ đơn giản và hiền lành ấy. Những mẩu chuyện mà tôi đã tận mắt chứng kiến và chưa bao giờ kể!

Hãy xem lại cảnh mở màn của $. Ở cảnh này, hàm $ đến với bạn như một cỗ máy tìm kiếm các phần tử trong DOM. Nhưng hãy thử dừng hình một chút! bạn sẽ thấy có điều gì đó không đúng?! Vâng, đúng vậy, hãy thử xem một đoạn code sau đây:

//the function that creates dream
function dream(){
    //calculating random color of dream
    var color = 'rgb('+Math.floor(Math.random()*255)+','
                      +Math.floor(Math.random()*255)+','
                      +Math.floor(Math.random()*255)+')';

    //calculating random X position
    var x = Math.floor(Math.random()*$(window).width());

    //calculating random Y position
    var y = Math.floor(Math.random()*$(window).height());

    //creating the dream and hide
    drawingpix = $('').attr({class: 'drawingpix'}).hide();

    //appending it to body
    $(document.body).append(drawingpix);

    //styling dream.. filling colors.. positioning.. showing.. growing..fading
    drawingpix.css({
                       'background-color':color,
                       'border-radius':'100px',
                       '-moz-border-radius': '100px',
                       '-webkit-border-radius': '100px',
                       top: y-14, //offsets
                       left: x-14 //offsets
                   }).show().animate({
                                      height:'500px',
                                      width:'500px',
                                      'border-radius':'500px',
                                      '-moz-border-radius': '500px',
                                      '-webkit-border-radius': '500px',
                                      opacity: 0.1,
                                      top: y-250, //offsets
                                      left: x-250
                                     }, 3000).fadeOut(2000);

     //Every dream's end starts a new dream
     window.setTimeout('dream()',200);
}

$(document).ready(function() {
     //calling the first dream
     dream();
});

Xem ngay

Nếu tinh ý các bạn sẽ thấy các elements mà $ tạo ra bỗng dưng không còn là chính mình nữa. Chúng bỗng trở nên linh hoạt khác thường, chúng có thể “nhảy múa”, biến đổi sắc thái, ẩn hiện như có phép màu. Lúc này hẳn bạn nghĩ mình vừa đụng phải “cây đũa phép $” của Harry Potter. Hàm $ → đũa phép $, bạn đang mơ chăng?? Rất có thể! Nếu bạn đã từng xem bộ phim Inception (hoặc Matrix) chắc sẽ vẫn còn nhớ cái ảo giác rằng tất cả chúng ta thật ra đang mơ… Ít phút nữa thôi, các bạn cũng sẽ được chứng kiến cảm giác đó, nhưng may mắn nhân vật chính không phải chúng ta mà là các elements khi bị rơi vào mê trận của hàm $ trong cái thế giới được tạo ra bởi jQuery (bạn có nhớ $=jQuery :D).



Tuy nhiên, trước khi bước vào cuộc hành trình lần theo những giấc mơ này, chúng ta cần trang bị một chút hành trang để không bị môi trường trong jQuery gây sốc:

+ Cơ bản về hàm trong javascript:

Theo đặc tả của ECMA script, có 3 cách tạo và khai báo hàm:

1. Sử dụng hàm tạo của đối tượng Function

var sum = new Function('a','b', 'return a + b;');
alert(sum(10, 20)); //alerts 30

2. Sử dụng cách khai báo hàm.

function sum(a, b)
{
    return a + b;
}
alert(sum(10, 10)); //Alerts 20;

3. Sử dụng biểu thức hàm

var sum = function(a, b) { return a + b; }
alert(sum(5, 5)); // alerts 10

Với biểu thức hàm ta có thể tạo hàm ở bất cứ đâu chấp nhận biểu thức trong javascript. Ví dụ bạn có thể viết:

//tạo hàm fsum
var fsum=function(a,b){return a+b};
//gọi hàm
sum=fsum(4,5);
//tương đương với
sum=(fsum)(4,5);
//cũng tương đương với.
sum=(function(a,b){return a+b})(4,5);

Chú ý cặp dấu () bao quanh khai báo hàm chỉ đơn giản là để nhóm phần tử hàm bên trong biểu thức.

+ Tính hướng đối tượng trong javascript 

(xem loạt bài JavaScript và lập trình hướng đối tượng)

+ Các kiến thức cơ bản về DOM Elements

(tham khảo trên w3school.com)

OK! Đến đây bạn đã sẵn sàng đi vào bên trong giấc mơ jQuery (=>link mã nguồn). Nhưng đừng vội, việc xông vào mã nguồn của jQuery ngay là việc làm hết sức mạo hiểm. Chính vì vậy tôi đã bỏ chút thời gian tạo ra một môi trường giả lập jQuery tạm gọi là iQuery để giảm sóc cho những nhà thám hiểm trước khi trải nghiệm trong môi trường thật. Và đây là iQuery framework:

(function(window) {
    var iQuery = function(selector) {
                      return new DOMWrapper(selector);
                 };
    DOMWrapper= function(selector) {
                      var elems;
                      if (typeof selector == "string") {
                          elems = document.getElementById(selector);
                      } else {
                          elems = selector;
                      }
                      this.collection = elems[1] ? Array.prototype.slice.call(elems) : [elems];
                      return this;
                };
    iQuery.fn = DOMWrapper.prototype = {
                    aaaaaaa: function() {
                                alert("Aaa!!!");
                                return this;
                             },
                    addStyles: function(styles) {
                                var elems = this.collection;
                                for (var i = 0, l = elems.length; i < l; i++) {
                                   for (var prop in styles) {
                                       elems[i].style[prop] = styles[prop];
                                   }
                                }
                                return this;
                               },
                    click:function(fn) {
                                var elems = this.collection;
                                for (var i = 0, l = elems.length; i < l; i++) {
                                    elems[i].onclick=fn;
                                }
                           }
                         };
     window.$ = iQuery;
     return iQuery;
 })(window);

//Demo sử dụng iQuery
 $('a').addStyles({
      color: 'yellow',
      backgroundColor: 'blue'
 }).aaaaaaa();

$('a').click(function(){alert("Hello iQuery");});

Xem Demo

Hãy quan sát từ ngoài vào trong:
– Đầu tiên là “bức tường” ngăn cách giữa thế giới iQuery và môi trường bên ngoài:

(function(window){
    …
})(window);

Cấu trúc này bản chất là một biểu thức hàm (như trên đã nói), với window là cửa thông giữa môi trường bên ngoài và thế giới iQuery bên trong (hiển nhiên :D).
– Đi qua vách ngăn đó ta bắt gặp một đối tượng mà ở đó mọi giấc mơ bắt đầu:

var iQuery = function(selector) {
                  return new DOMWrapper(selector);
             };

Tại đây, câu “thần chú” selector dùng để triệu hồi các DOM Element được đưa vào và đi qua cỗ máy DOMWrapper, thực chất là nơi mà tất cả các elements được lưu giữ. Lúc này, iQuery chính là một hàm mà giá trị trả về của nó là một đối tượng DOMWrapper.
– Ta hãy xem cấu tạo của DOMWrapper:

 DOMWrapper= function(selector) {
                 var elems;
                 if (typeof selector == "string") {
                        elems = document.getElementById(selector);
                 } else {
                        elems = selector;
                 }
                 this.collection = elems[1] ? Array.prototype.slice.call(elems) : [elems];
                 return this;
             };

Khâu đầu tiên trong DOMWrapper là thực thi câu thần chú selector để thu về các elements. (Việc làm này giống như hàm $ nguyên bản trong phần trước). Sau đó cất giữ toàn bộ chúng vào trong chiếc hòm collection của DOMWrapper.

Tôi khuyên các bạn hãy dừng lại ở đây nếu yếu tim hoặc không đủ can đảm đi tiếp, bởi chỉ một bước nữa thôi là chúng ta sẽ nhìn thấy những “điều khủng khiếp” sẽ xảy đến với các DOM element tội nghiệp đang bị nhốt trong DOMWrapper.collection:

   iQuery.fn = DOMWrapper.prototype = {
                    aaaaaaa: function() {
                                alert("Aaa!!!");
                                return this;
                             },
                    addStyles: function(styles) {
                                var elems = this.collection;
                                for (var i = 0, l = elems.length; i < l; i++) {
                                   for (var prop in styles) {
                                       elems[i].style[prop] = styles[prop];
                                   }
                                }
                                return this;
                               },
                    click:function(fn) {
                                var elems = this.collection;
                                for (var i = 0, l = elems.length; i < l; i++) {
                                    elems[i].onclick=fn;
                                }
                           }
                         };

Vâng cái đoạn này: iQuery.fn = DOMWrapper.prototype thực sự là “thủ đoạn” của iQuery. Nó khai báo một thuộc tính có tên fn của iQuery, nhưng thuộc tính này lại được gán là nguyên mẫu của DOMWrapper. Nhắc lại một chút: trước đó iQuery là hàm triệu hồi DOMWrapper và giờ iQuery.fn là nguyên mẫu của DOMWrapper. Kết quả của mẫu kết cấu này là iQuery có đầy đủ những đặc tính mà DOMWrapper nắm giữ. Chính tại đây “phép thuật” bắt đầu xuất hiện:

– Đầu tiên là tiếng kêu la aaaaaaa, báo hiệu điềm chẳng lành cho các elements trong DOMWrapper.collection.

– Tiếp theo là addStyles, khi phép này được gọi, các element sẽ bị lôi ra và được tô son, chát phấn theo kiểu nghệ thuật “body painting” :D.

– Cuối cùng là click, điều này thật sự gây khủng hoảng cho các element bởi giờ đây chúng có thể bị sai khiến và thực hiện mọi hành động mà các lập trình viên tạo ra. Bởi tham số truyền vào cho click chính là một hàm bất kỳ và nó được gán cho sự kiện onclick của các element.

Vẫn còn một chiến thuật nguy hiểm nữa mà DOMWrapper cài vào, nếu tinh ý bạn sẽ nhận ra ở hai phương thức aaaaaaa và addStyles có đoạn: return this. Đây chính điểm sẽ tạo ra cơn ác mộng liên hồi và dường như không có hồi kết trong môi trường của iQuery:

$('a').addStyles({
     color: 'yellow',
     backgroundColor: 'blue'
 }).aaaaaaa().aaaaaaa().aaaaaaa(); //Aaa!!........

Như bạn thấy, DOMWrapper có thể được coi là “Kẻ đánh cắp giấc mơ” nguy hiểm nhất trong iQuery, và không may nó cũng chính là iQuery!!!

Có lẽ tôi nên dừng lại ở đây, bởi như đã nói, iQuery chỉ là một mô hình thu nhỏ của jQuery, mà nó đã ẩn chứa rất nhiều cạm bẫy và ma thuật. Vậy nên các bạn hãy khởi động thật kỹ trước khi theo đuổi hành trình đến thế giới rộng lớn “jQuery Pandora” – thế giới của những giấc mơ.

Tham khảo:

Mã nguồn jQuery: http://code.jquery.com/jquery-1.7.2.js

http://djdesignerlab.com/2011/05/21/20-jquery-animate-resources-for-developers/

http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf

One comment on “Hậu trường: Chuyện về hàm $ trong jQuery

Leave a Reply

Your email address will not be published. Required fields are marked *