برنامه نویسی

جی کوئری خود را از ابتدا بسازید

jQuery یکی از محبوب ترین کتابخانه های جاوا اسکریپت است. برنامه نویسی جاوا اسکریپت را بسیار ساده می کند. ممکن است در مورد نحوه ساخت jQuery کنجکاو باشید. اگر به کد منبع هر کتابخانه معروف نگاه کنید، به احتمال زیاد چیزی متوجه نخواهید شد یا ممکن است احساس کنید که برای ساختن آن باید نابغه باشید. با این حال، آنقدرها هم که در واقعیت به نظر می رسد دشوار نیست. در این مقاله به شما نشان خواهم داد که چگونه می توانید کتابخانه خود را مانند jQuery از ابتدا بسازید.

بیا شروع کنیم!

ایجاد عملکرد انتخابگر

const $ = function(el) {
    return new jQuery(el);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

همانطور که می بینید تابع Selector کلاسی به نام jQuery را برمی گرداند. هر بار که از انتخابگر استفاده می کنید یک نمونه جدید از jQuery ایجاد می شود.

حالا بیایید کلاس jQuery خود را ایجاد کنیم

شی jQuery

 class jQuery extends Array{
    constructor(el) {
        super();
        if (el == document || el instanceof HTMLElement) {
            this.push(el);
        } else {
            let elements=document.querySelectorAll(el);
            for(let i=0;i<elements.length;i++){
                this.push(elements[i]);
            }
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کلاس jQuery ما کلاس آرایه داخلی را گسترش می دهد تا هر نمونه به طور همزمان مانند یک آرایه و یک شی رفتار کند. اگر نه، یک querySelectorAll برای دریافت تمام عناصر منطبق انجام می دهد. سپس هر عنصر را به سمت خود هل می دهد. حالا می توانیم روش های خود را به آن اضافه کنیم.

زنجیر زدن

بهترین چیزی که در مورد jQuery دوست دارم این است که می‌توانید متدها را زنجیره‌ای کنید

$('div').addClass('red').html('hello');
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای اعمال Chaining به کلاس jQuery ما فقط باید نمونه فعلی را برگردانیم، یعنی اضافه کنیم
return this بعد از هر روش

آماده()

ready(callback){
    this[0].addEventListener('readystatechange', e => {
        if(this[0].readyState === "complete"){
            callback();
            return true;
        }
      });
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$(document).ready(function(){
 //codes
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در صورتی که readyState کامل باشد از زمانی که می دانیم عنصر کاملاً بارگذاری شده و آماده استفاده است، readyStates یک عنصر را بررسی می کند.

هر یک()

    each(callback) {
        if (callback && typeof(callback) == 'function') {
            for (let i = 0; i < this.length; i++) {
                callback(this[i], i);
            }
            return this;
        } 
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').each(function(el){
    $(el).html('text')
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش بر روی همه عناصر تکرار می شود و برای هر عنصر یک callback می دهد. ما از این برای ایجاد روش های دیگر استفاده خواهیم کرد.

خواهر و برادر ()

siblings(){
    return  [...this[0].parentNode.children].filter(c=>c!=this[0])
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

console.log($('script').siblings())
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یک آستر بسیار ساده است. ابتدا همه عناصر فرزند عنصر والد را انتخاب می‌کنیم و سپس فقط عنصر فعلی را از لیست حذف می‌کنیم و آرایه‌ای از عناصر خواهر و برادر را به‌دست می‌آوریم.

addClass()

addClass(className) {
    this.each(function(el) {
        el.classList.add(className);
    })
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').addClass('pink')
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما از classList.add() برای اضافه کردن className به هر آیتم با کمک متد this.each استفاده می کنیم.

removeClass()

removeClass(className) {
    this.each(function(el) {
        el.classList.remove(className);
    })
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').removeClass('pink')
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مشابه addClass ما از classList.remove() برای حذف className استفاده می کنیم.

hasClass()


hasClass((className) {
    return this[0].classList.contains(className);
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

console.log($('div').hasClass('pink')) //returns true or false
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

hasClass با کمک classList.contains() بررسی کنید که آیا اولین عنصر انتخاب شده دارای کلاس خاصی است یا خیر و بر اساس آن true یا false را برگردانید.

css()

 css(propertyObject) {
       this.each(function(el) {
           Object.assign(el.style,propertyObject);
        })
        return this;
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').css({
    color:'red'
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این روش پارامتر یک نوع شی است. هر ویژگی این شی با استفاده از تابع () Object.assign در ساخت به شی element.style اضافه می شود.

attr()

attr(attr, value = null) {
    let getattr = undefined;
    if (value) {
        this.each(function(el) {
            el.setAttribute(attr, value);
            getattr = this;
        });
    } else {
            getattr = this[0].getAttribute(attr);
    }
    return getattr;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

console.log($('div').attr('class')) //gets the attribute value
$('div').attr('name','div') //sets the name attribute with value
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اگر آرگومان مقدار وجود نداشته باشد، به سادگی مقدار مشخصه را برمی گردانیم، در غیر این صورت ویژگی را برای هر عنصر تنظیم می کنیم.

html()

html(data) {
    if (data) {
        this.each(function(el) {
            el.innerHTML = data;
        })
    } else {
        return this[0].innerHTML;
    }
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

console.log($('div').html()) //gets the innerHTML 
$('div').html('x') //add the innerHTML
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مشابه متد attr() فقط از innerHTML به جای ویژگی استفاده می کند

prepend()

prepend(el){
    this[0].prepend(el);
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

let h1=document.createElement('h1');
h1.innerHTML='this is a heading';
$('div').prepend(h1) //prepend the node
$('div').prepend('hello') //prepend the string 
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با استفاده از () node.prepend یک گره یا رشته را به عناصر انتخاب شده اضافه کنید.

ضمیمه ()

append(el){
    this[0].append(el);
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

let h1=document.createElement('h1');
h1.innerHTML='this is a heading';
$('div').append(h1) //append the node
$('div').append('hello') //append the string
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

با استفاده از () node.append یک گره یا رشته را به عناصر انتخاب شده اضافه کنید.

پنهان شدن()

hide() {
    this.each(function(el) {
        el.style.display = "none";
    });
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').hide()
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ویژگی style.display را به none تغییر دهید

نمایش ()

show() {
    this.each(function(el) {
        el.style.display = "block";
    });
    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').show()
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ویژگی style.display را به block تغییر دهید

بر()

on(event, child, callback = null) {
    if (callback != null) {
        let selector = child;
        this.each(function(element) {
            element.addEventListener(event, function(event) {
                if (event.target.matches(selector + ', ' + selector + ' *')) {
                    callback.apply(event.target.closest(selector), arguments);
                }
            })
        })
    } else {
    //if the callback argument is not present then assume  the child argument is being use as callback
        callback = child;
        this.each(function(element) {
        element.addEventListener(event, callback);
        })
    }

    return this;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$('div').on('click',function(){
    alert('x')
})
$(document).on('click','div',function(){
    alert('x')
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این یکی کمی پیچیده است
اگر هر سه آرگومان را به متد ارسال کنیم، یک EventListener به عنصر والد اضافه می‌کند و تنها در صورتی که هدف با عنصر فرزند مطابقت داشته باشد، یک تماس پاسخ خواهد داد.

اما اگر فقط دو آرگومان ارسال کنیم، به سادگی یک EventListener با یک callback به عنصر اضافه می کند.

برای مورد اول می توانیم این را به عنوان مثال در نظر بگیریم

$(document).on('click','div',function(){
    alert('x')
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و برای مورد دوم

$('div').on('click',function(){
    alert('x')
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

هر دو در خدمت اهداف متفاوت خود هستند. اولی برای اتصال رویداد به عنصر پویا بسیار مفید است.
مورد دوم برای اتصال رویداد عمومی مفید است.

$.ajax()

$.ajax=function(args) {
        let url = args["url"];
        let type = "get";
        let success =function(){};
        let fail = function(){};
        if(args['success']){
            success=args['success'];
        }
        if(args['fail']){
            fail=args['fail'];
        }
        let xhttp = new XMLHttpRequest();
        xhttp.onerror = function(error){
        //return fial callback with error 
            return fail(error);
        }
        xhttp.onload = function() {
            let response;
            if (this.readyState == 4 && this.status == 200) {
                let response="";
                try {
                //check if the response in json 
                //if json the parse it
                    response=JSON.parse(this.responseText)
                } catch (e) {
                //if not json the simple reurn the response
                    response = this.responseText;
                    }
                 // give a success callback
                return success(response);
            } else {
            //give a fail callback with the error status 
                return fail(this.status);
            }
        };
        let parameters="";
        if (args) {
            type = args["type"];
            if ('data' in args) {
            //converting object to url URLSearchParams
                parameters = new URLSearchParams(args['data']).toString();
            }

        }
        if (type && type.toUpperCase()=='POST') {
            xhttp.open("POST", url, true);
            xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhttp.send(parameters);
        } else if (!type || type.toUpperCase()=='GET'){
            xhttp.open("GET", url + "?" + parameters, true);
            xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            xhttp.send();
        }
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

$.ajax({
    url:'post.php',
    type:'post',
    data:{
        name:'hello'
    },
    success: function(res){
        console.log(res)
    }
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این تابع ajax یک متد نیست اما به عنوان یک ویژگی شی $ کار می کند.
این تابع مانند jQuery اصلی آرگومان های url، type، داده، موفقیت و شکست را می گیرد.
کاری که ما انجام می‌دهیم این است که هر بار که تابع فراخوانی می‌شود، یک XMLHttpRequest() جدید ایجاد می‌کنیم و پست یا درخواست دریافت می‌کنیم. اکنون اگر پاسخ را دریافت کنیم، بررسی می‌کنیم که آیا پاسخ JSON است یا خیر. برای پاسخ JSON، آن را تجزیه می‌کنیم، در غیر این صورت فقط کافی است یک پاسخ تماس موفقیت‌آمیز را با پاسخ برگردانیم. اما اگر درخواست خطا بدهد، پاسخ به تماس ناموفق با کد وضعیت برگردانده می‌شود.

همه اش را بگذار کنار هم

class jQuery extends Array{
    constructor(el) {
        super();
        if (el == document || el instanceof HTMLElement) {
            this.push(el);
        } else {
            let elements=document.querySelectorAll(el);
            for(let i=0;i<elements.length;i++){
                this.push(elements[i]);
            }
        }
    }
    ready(callback){
        this[0].addEventListener('readystatechange', e => {
            if(this[0].readyState === "complete"){
                callback();
                return true;
            }
          });
    }
    each(callback) {
        if (callback && typeof(callback) == 'function') {
            for (let i = 0; i < this.length; i++) {
                callback(this[i], i);
            }
            return this;
        } 
    }
    siblings(){
        return  [...this[0].parentNode.children].filter(c=>c!=this[0])
    }
    addClass(className) {
        this.each(function(el) {
            el.classList.add(className);
        })
        return this;
    }

    removeClass(className) {
        this.each(function(el) {
            el.classList.remove(className);
        })
        return this;
    }

    hasClass(className) {
        return this[0].classList.contains(className);
    }

    css(propertyObject) {
        this.each(function(el) {
            Object.assign(el.style,propertyObject);
        })
        return this;
    }

    attr(attr, value = null) {
        let getattr = undefined;
        if (value) {
            this.each(function(el) {
                el.setAttribute(attr, value);
                getattr = this;
            });
        } else {
            getattr = this[0].getAttribute(attr);
        }
        return getattr;
    }

    html(data) {
        if (data) {
            this.each(function(el) {
                el.innerHTML = data;
            })
        } else {
            return this[0].innerHTML;
        }
        return this;
    }
    append(el){
        this[0].append(el);
        return this;
    }
    prepend(el){
        this[0].prepend(el);
        return this;
    }
    hide() {
        this.each(function(el) {
            el.style.display = "none";
        });
        return this;
    }
    show() {
        this.each(function(el) {
            el.style.display = "block";
        });
        return this;
    }
    on(event, child, callback = null, state = null) {
        if (callback != null) {
            let selector = child;
            this.each(function(element) {
                element.addEventListener(event, function(event) {
                    if (event.target.matches(selector + ', ' + selector + ' *')) {
                        callback.apply(event.target.closest(selector), arguments);
                    }
                }, false)
            })
        } else {
            callback = child;
            this.each(function(element) {
                if (state) {
                    element.addEventListener(event, callback, state);
                } else {
                    element.addEventListener(event, callback, false);
                }
            })
        }

        return this;
    }
}
const $ = function(el) {
    return new jQuery(el);
}
$.ajax=function(args) {
        let url = args["url"];
        let type = "get";
        let success =function(){};
        let fail = function(){};
        if(args['success']){
            success=args['success'];
        }
        if(args['fail']){
            fail=args['fail'];
        }
        let xhttp = new XMLHttpRequest();
        xhttp.onerror = function(error){
            return fail(error);
        }
        xhttp.onload = function() {
            let response;
            if (this.readyState == 4 && this.status == 200) {
                let response="";
                try {
                    response=JSON.parse(this.responseText)
                } catch (e) {
                    response = this.responseText;
                    }
                return success(response);
            } else {
                return fail(this.status);
            }
        };
        let parameters="";
        if (args) {
            type = args["type"];
            if ('data' in args) {
                parameters = new URLSearchParams(args['data']).toString();
            }

        }
        if (type && type.toUpperCase()=='POST') {
            xhttp.open("POST", url, true);
            xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhttp.send(parameters);
        } else if (!type || type.toUpperCase()=='GET'){
            xhttp.open("GET", url + "?" + parameters, true);
            xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            xhttp.send();
        }
    }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون شما کتابخانه جی کوئری خود را دارید. البته jQuery اورجینال روش های بیشتری دارد و بهینه سازی خوبی دارد اما این برای کارهای معمولی کافی است.

اگر به این نوع پست ها علاقه دارید، لطفاً به من مراجعه کنید
وبلاگ

در ادامه بخوانید
پیاده سازی الگوریتم Diffing Dom مجازی در جاوا اسکریپت Vanilla

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا