原生JavaScript模拟jQuery核心功能实现详解

作者:像素猫

时间:2026-10-21

浏览:75

本文详细解析如何使用原生JavaScript从零构建一个轻量级类库lQuery,模拟jQuery的核心功能。内容涵盖$()选择器对ID、Class及标签名的处理,DOM元素的数组化转换,html()、css()等常用方法的实现,以及基于addEventListener和attachEvent的事件绑定机制。通过具体代码示例,展示如何封装面向对象的结构,实现链式调用、事件冒泡阻止及DOM遍历功能,帮助开发者深入理解前端库的底层原理。

文章目录

本节主要介绍文章背景与目的。

参考妙味课堂的教学内容,尝试使用原生JavaScript代码来复现jQuery库中的部分核心功能。

lQuery库基础架构与选择器实现

开始构建一个名为lQuery的自定义函数库,作为后续功能实现的基础载体。

重点实现$()选择器,这是jQuery中最基础且常用的功能之一。

jQuery采用面向对象的设计模式,因此lQuery也需要遵循这一架构,确保代码结构清晰且具备扩展性。


// 定义lQuery对象
function lQuery(lArg){
}
function lQ(lArg){
 return new lQuery(lArg);
}
// css()方法
lQuery.prototype.css = function(){};
// html()方法
lQuery.prototype.html = function(){};

首先尝试仿写jQuery中传入函数作为参数的$(function)语法,用于页面加载完成后的初始化操作。


// 定义lQuery对象
function lQuery(lArg){
 // 用typeof判断参数的类型是 function 、
 switch( typeof lArg){
 case 'function':
  // 如果采用这种写法,给lQ绑定相同的函数,但是只会执行一次
  // window.onload = lArg;
  // break;
 }
}

如果直接采用简单的函数赋值方式,会导致后续绑定的相同类型函数覆盖前一个,从而引发执行异常。


lQ(function(){
  alert(1);
 });
 lQ(function(){
  alert(2);
 });

上述直接赋值方式存在缺陷,例如只会执行最后一次绑定的alert('2'),而jQuery能依次执行所有绑定函数。为解决此问题,需采用事件绑定的机制来处理。


// 绑定事件函数
function lQbind(obj,eventName,fn){
 // 标准浏览器
 if(obj.addEventListener){
 obj.addEventListener(eventName,fn,false);
 }else{
 // IE浏览器
 obj.attachEvent('on'+eventName,fn);
 }
}

通过特定的调用方式,可以正确触发并执行多个绑定的初始化函数。


switch( typeof lArg){
 case 'function':
  // 如果采用这种写法,给lQ绑定相同的函数,但是只会执行一次
  // window.onload = lArg;
  // break;
  lQbind(window,'load',lArg);
  break;
 }

接下来仿写jQuery中常见的三种选择器语法:$('.div')、$('#div')以及$('div')。

这三种选择器的主要区别在于参数字符串的第一个字符,因此可以通过判断首字符来区分处理逻辑。

首先实现处理类名选择器$('.div')的逻辑。


   // '.div'
  case '.':
  this.elements = getClass(document,lArg.substring(1));
   break;

由于getElementsByClassName()是HTML5标准方法,在IE8及以下版本中不兼容,因此需要手动编写一个兼容性的getClass方法。


 // 获取class属性
function getClass(obj,name){
 var arr = [];
 var elems = obj.getElementsByTagName('*');
 for(var i=0;i<elems.length;i++){
 if(elems[i].className == name){
  arr.push(elems[i]);
 }
 }
 return arr;
}

接着实现处理ID选择器$('#div')的逻辑。


  case '#':
   this.elements.push(document.getElementById(lArg.substring(1)));
   break;
   // '.div'
  case '.':

随后实现处理标签名选择器$('div')的逻辑。


default:
 // getElementsByTagName返回的是一个类数组NodeList,为了防止以后出现麻烦,要把他转为一个
 // 数组
 this.elements = toArray(document.getElementsByTagName(lArg));
 break;

getElementsByTagName()返回的是类数组对象NodeList,为了避免后续操作中出现兼容性问题或方法缺失,需要将其转换为真正的数组,为此自定义了一个toArray辅助方法。


// 将一个类数组转为真正的数组
function toArray(lickArr){
 var arr = [];
 for(var i=0;i<lickArr.length;i++){
 arr.push(lickArr[i]);
 }
 return arr;
}

实现当传入对象(如window或document)时的处理逻辑。

// window document case 'object': this.elements.push(lArg); break;

DOM内容与样式操作方法

实现html()方法,用于获取或设置元素的HTML内容。

html()方法需区分有参(设置内容)和无参(获取内容)两种情况。


// html()方法
lQuery.prototype.html = function(str){
 if(str){ //设置
 for(var i=0;i<this.elements.length;i++){
  this.elements[i].innerHTML = str;
 }
 }else{
 return this.elements[0].innerHTML;
 }
 return this;
};

事件绑定与交互功能实现

实现on()方法,用于元素事件的绑定。

利用前文实现的底层绑定函数,可以便捷地封装出通用的on()方法。

lQuery.prototype.on = function(eventName,fn){ for(var i=0;i<this.elements.length;i++){ lQbind(this.elements[i],eventName,fn); } }

实现click()和mouseover()等具体事件快捷方法。

这些具体事件方法可以直接复用on()方法,简化代码实现。


// click()方法
lQuery.prototype.click = function(fn){
 this.on('click',fn);
 return this;
}
// mouseover()方法
lQuery.prototype.mouseover = function(fn){
 this.on('mouseover',fn);
 return this;
}

实现hide()和show()方法,用于控制元素的显示与隐藏。


// hide()方法
lQuery.prototype.hide = function(){
 for(var i=0;i<this.elements.length;i++){
 this.elements[i].style.display = 'none';
 }
 return this;
}
// show()方法
lQuery.prototype.show = function(){
 for(var i=0;i<this.elements.length;i++){
 this.elements[i].style.display = 'block';
 }
 return this;
}

实现hover()方法,处理鼠标悬停时的进入与离开事件。


 // hover()方法
 lQuery.prototype.hover = function(fnover,fnout){
 this.on('mouseover',fnover);
 this.on('mouseout',fnout);
 return this;
 }

实现css()方法,用于获取或设置元素的CSS样式。

支持$('div').css('width')获取样式值,以及$('div').css('width','200px')设置样式值两种调用形式。


lQuery.prototype.css = function(attr,value){
 if(arguments.length == 2){
 for(var i=0;i<this.elements.length;i++){
  this.elements[i].attr = value;
 }
 }
 if(arguments.length == 1){
 return getStyle(this.elements[0],attr);
 }
}

定义getStyle()辅助方法,旨在获取包括行内样式在内的完整计算样式。


// 获取属性
function getStyle(obj,attr){
 if(obj.currentStyle[attr]){
 obj.currentStyle[attr];
 }else{
 obj.getComputedStyle(obj,false)[attr];
 }
}

实现attr()方法,用于获取或设置元素的属性值。

attr()的实现逻辑与css()有所不同,需针对属性操作进行专门处理。


// attr()方法
lquery.prototype.attr = function(attr,value){
 if(arguments.length == 2){ //设置
 for(var i=0;i<this.elements.length;i++){
  this.elements[i].setAttribute(attr,value);
 }
 }
 else if(arguments.length == 1){ //获取
 return this.elements[0].getAttribute(attr);
 }
 return this;
};

元素筛选与DOM遍历功能

实现eq()方法,用于从当前匹配的元素集合中选取指定索引的元素。

支持$('div').eq(1)这样的调用,返回索引为1的元素对应的lQuery对象。

由于eq()返回的对象需要继续调用其他lQuery方法,因此必须返回一个新的lQuery实例以保持链式调用。


 lQuery.prototype.eq = function(num){
  return lQ(this.elements[num]);
 };

实现index()方法,用于获取元素在同辈节点中的索引位置。

支持$('div').index()调用,返回该元素在其父节点下的兄弟元素中的位置索引。


lQuery.prototype.index = function(){
 var elems = this.elements[0].parentNode.children;
 for(var i=0;i<elems.length;i++){
  if( elems[i] == this.elements[0] ){
  return i;
  }
 }
 };

处理阻止默认行为和阻止事件冒泡的功能。

在jQuery中,返回false可同时阻止默认事件和冒泡。因此需修改lQbind函数,通过检测绑定函数的返回值是否为false,来决定是否执行preventDefault()和stopPropagation()。


function lQbind(obj,events,fn){
  if(obj.addEventListener){
  obj.addEventListener(events,function(ev){ 
   if( fn() == false ){
   ev.preventDefault();
   ev.cancelBubble = true;
   }
  },false);
  }
  else{
  obj.attachEvent('on'+events,function(){
   if( fn() == false ){
   window.event.cancelBubble = true;
   return false;
   }
  });
  }
 }

实现find()方法,用于在当前匹配元素的后代中查找特定元素。

仿写$('div').find('.box')和$('div').find('#box')等常见用法。

find()的实现逻辑与$()类似,通过判断参数首字符决定处理方式。在循环处理过程中,需使用concat()方法合并数组,最终返回一个包含所有匹配结果的lQuery对象。


lQuery.prototype.find = function(sel){
  var arr = [];
  if( sel.charAt(0) == '.' ){ 
  for(var i=0;i<this.elements.length;i++){ 
   arr = arr.concat(getClass( this.elements[i] , sel.substring(1) ));
  }
  }
  else{ 
  for(var i=0;i<this.elements.length;i++){ 
   arr = arr.concat(toArray(this.elements[i].getElementsByTagName(sel)));
  }
  }
  return lQ(arr); 
 };

以上即为本文介绍的全部内容,希望这些实现思路能对读者学习JavaScript或jQuery底层原理有所帮助,欢迎继续支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章