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

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