11/08/2018, 20:28

Ứng dụng prototype trong JavaScript viết Slideshow

Giới thiệu Chắc mọi người ai cũng đã nắm hoặc nghe qua khái niệm Prototype trong JavaScript rồi phải không? Nếu chưa thì đọc trước vài tài liệu dưới đây: https://toidicodedao.com/2016/02/02/series-javascript-sida-po-ro-to-tai-prototype-la-cai-gi/ https://kipalog.com/posts/prototype-la-khi ...

Giới thiệu

Chắc mọi người ai cũng đã nắm hoặc nghe qua khái niệm Prototype trong JavaScript rồi phải không?
Nếu chưa thì đọc trước vài tài liệu dưới đây:

  • https://toidicodedao.com/2016/02/02/series-javascript-sida-po-ro-to-tai-prototype-la-cai-gi/
  • https://kipalog.com/posts/prototype-la-khi-gi-

Tóm lại, prototype là cách để thực hiện việc kế thừa 1 object trong JavaScript. Chúng ta ai cũng hiểu về OOP nhưng để áp dụng OOP vào JS là 1 chuyện khá lấn cấn, chưa quen, do đặc thù ngôn ngữ JS bị "si-đa" (theo bác Phạm Huy Hoàng - toidicodedao.com), đúng thật là người ta viết JS theo hướng FP (Function Programming) như 1 thói quen từ lâu khó bỏ.
Do vậy, bài viết này giúp các bạn áp dụng kiến thức prototype để tự viết 1 Slideshow hình ảnh đơn giản, nhằm có cái nhìn thực tiễn hơn về OOP trong JS.
bài viết gồm 2 phần:
Phần 1 - viết slider theo hướng function - cách tiếp cận phổ biến của mọi người
Phần 2 - tối ưu hóa slider theo hướng OOP - sử dụng prototype.
Ứng dụng prototype trong JavaScript viết Slideshow

Viết Slider dạng function

Slider của chúng ta là 1 trình chiếu hình ảnh, rất quen thuộc trong các website. Có 2 cái nút để chuyển hình.
Đầu tiên là phần HTML

<div class="slider">
    <ul>
        <li><img src="http://via.placeholder.com/400x200" alt="image"></li>
        <li><img src="http://via.placeholder.com/400x200" alt="image"></li>
        <li><img src="http://via.placeholder.com/400x200" alt="image"></li>
        <li><img src="http://via.placeholder.com/400x200" alt="image"></li>
    </ul>
</div>

<div id="slider-nav">
    <button data-dir="prev">Previous</button>
    <button data-dir="next">Next</button>
</div>

Nhúng thư viện jQuery giúp việc code dễ dàng hơn + Tách code slider của chúng ta ra file riêng.

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="slider.js"></script>

Phần JavaScript/jQuery
http://jsfiddle.net/a0Luphbk/6/

Phân tích đoạn code JS trên
Đầu tiên là bao ngoài phần code của chúng ta là 1 anonymous function có tác dụng tránh đụng độ với các thư viện sử dụng dấu $ khác với jQuery như Mootools hay PrototypeJS
Sau đó là khai báo các biến cần thiết cho slider.
Khi nhấn vào nút Next, ta nhận biết được nhờ vào data-dir="next", ta sẽ tăng biến vị trí hiện tại lên 1
Hàm transition có tác dụng hiệu ứng, dịch chuyển bức hình số 1 sang trái 200px (imgWidth) - sẽ hiển thị tấm hình số 2.

Khi current bằng tấm hình cuối cùng (current - 1 === imgsLen === 4) ta sẽ reset lại current = 1
Tương tự với hiệu ứng dịch chuyển sang phải khi ta ấn nút Previous.

Tối ưu hóa Prototype

bây giờ ta sẽ viết function trên thành class Slider thuần OOP
Constructor
Thay vì select element trong hàm như phần 1, tôi sẽ truyền vào constructor của class Slider:

function Slider(container, nav) {
    this.container = container;
    this.imgs = this.container.find('img');
    this.imgWidth = this.imgs[0].awidth;
    this.imgsLen = this.imgs.length;

    this.current = 0;

    this.events.click.call(this);
}

Với container là $('.slider').children('ul')
Và nav là $('#slider-nav').find('button')
Vẫn là biến imgs và kích thước của nó.
Current lúc này ta chọn gốc = 0
this.events.click.call(this) - ta dùng call tác dụng đảm bảo luôn truyền vào class Slider là this trong method events.click
Xong phần constructor, ta sẽ viết tiếp phần event.click

Slider.prototype.events = {
    click: function() {
        var self = this;

        self.nav.find('button').on('click', function(){
            self.setCurrent( $(this).data('dir') );
            self.transition();
        });
    }
};

Ở đây ta đã áp dụng kiến thức prototype được học, định nghĩa thêm 1 method vào class có sẵn Slider
events ở đây thực chất là 1 object với method là click (nếu khi gọi hàm click trong constructor, ta không dùng call, thì this sẽ hiểu là function click)
Event click này làm 2 việc là setCurrent và hiệu ứng transition. Tính ra thì công việc cũng y hệt với phần click khi viết bằng function, tuy nhiên nhìn sáng sủa, rõ ràng hơn rất nhiều.

Tiếp theo ta định nghĩa method setCurrent

Slider.prototype.setCurrent = function( dir ) {
  var pos = this.current;
  // if direction == next then pos++ else pos--
  pos += (dir === 'next' ? 1 : -1);
  // reset current if pos < 0, 0 is start position
  this.current = (pos < 0) ? this.imgsLen - 1 : pos % this.imgsLen;

  return pos;
}

Dòng code reset lại this.current nếu nó < 0 ngược lại thì current sẽ là modulo cho 4

Tiếp theo ta định nghĩa method transition

Slider.prototype.transition = function() {
    this.container.animate({
        'margin-left': -(this.current * this.imgWidth)
    });
}

Method này cũng ngắn hơn phiên bản đầu tiên.
Ứng với bức hình thứ 3, current sẽ bằng 2 (bắt đầu từ 0), 2 * 400 = 800 => margin-left: -800px

|400|400|400|400|
        here



Khác với phương pháp function, phương pháp OOP chúng ta phải khởi tạo instance từ object Slider và truyền vào 2 tham số

var slider = new Slider( $('.slider').find('ul'), $('#slider-nav') );

Sau đây là toàn bộ code cho phần tối ưu hóa slider bằng prototype này:
https://jsfiddle.net/r6x3sLb1/2/


Vậy là ta đã viết được 1 Slideshow trình chiếu hình ảnh đơn giản, áp dụng kiến thức prototype được học.
Phải công nhận rằng, code js viết theo OOP nhìn sáng sủa và dễ hiểu hơn Function rất nhiều, nhất là các chức năng phức tạp.
Học đi đôi với hành, hy vọng bạn sẽ cải tiến object Slider này để nó càng linh hoạt hơn, càng nhiều option hơn nữa, cũng như tự viết được nhiều object js theo hướng OOP.

Câu hỏi nhỏ xíu

Qua bài prototype này, có 1 câu hỏi giành cho các bạn: tại sao Slider lại phải viết method transition, setCurrent, events... bên ngoài thông qua prototype? Sao không viết ngay bên trong Object như:

function Slider(container, nav) {
    // constructor
    this.transition = function() {}
    this.setCurrent = function() {}
    this.events = function() {}
}

Why?

Trả lời:
Nếu ta viết các method đó vào bên trong object Slider, thì những method này sẽ luôn luôn được tạo ra mỗi khi ta khởi tạo 1 instance mới var slider = new Slider()
Thứ 2, các method này được hiểu là dành riêng cho instance.

Còn khi ta dùng prototype, thì những method này không tạo ra khi khởi tạo instance, nó tồn tại sẵn trong prototype, tức là Slider.prototype. Và chỉ khi instance gọi tới this.events.click.call(this) thì method này mới được sử dụng, this tương ứng với var slider
Thứ 2, các method này chia sẻ chung cho toàn bộ các instance khởi tạo từ object Slider


Ở đây đang nói tới điểm lợi dùng prototype chính là bộ nhớ sử dụng, performance. Chúng ta chỉ viết method bên trong Function constructor, khi và chỉ khi method đó truy xuất tới các biến cục bộ (private), và dành riêng cho mỗi instance.
Một câu hỏi hay khi phỏng vấn

Bài viết và hình ảnh copy từ blog của mình

0