【jQuery】浅析jQuery设计原理

26 篇文章 0 订阅
1 篇文章 0 订阅

jQuery封装原理技巧,你真的了解吗?

目录

  • jQuery有多牛X
  • jQuery设计模式
  • 术语+命名风格
  • 链式风格
  • 浅析jQuery设计封装原理
  • 总结+github仓库代码

一、jQuery有多牛X

它是目前最长寿的库,2006年发布。

它是世界上使用最广泛的库,全球80%的网站在用


二、jQuery设计模式

  • jQuery用到了那些设计模式?
    1. 不用new的构造函数,这个模式没有专门的名字
    2. $(支持多个参数),这个模式叫做重载
    3. 用闭包隐藏细节,这个模式没有专门的名字
    4. $.text()即可读也可写,getter/setter
    5. $.fn是$.prototypr的别名,这叫别名
    6. jQuery针对不同的浏览器使用不同的代码,这叫适配器

三、术语+命名风格

  • 举例

    1. Object对象表示Object构造出来的对象
    2. jQuery对象表示jQuery构造出来的对象
  • 命名风格

    const div=$('div');
    
    1. 上面这个代码会产生某种歧义,让人误以为是原生DOM的。怎么避免这种误解呢?
    2. 改成这样
    const $div=$('div');
    
    1. 因为他是jQuery对象,所以在前缀加上$以区分。

四、链式风格

  • 也叫jQuery风格

    1. window.jQuery()是我们提供的全局函数
    2. 代码示例
    <!DOCTYPE html>
    <html lang="en">
    <head>
    	<meta charset="UTF-8">
    	<meta name="viewport" content="width=device-width, initial-scale=1.0">
    	<title>Document</title>
    </head>
    <body>
    	<div class="demo">demo</div>
    </body>
    <script>
    	import $ form 'jquery';	//假设导入了jQuery包
    
    	$('demo').addClass('demo2').css("width":"100px","height":"100px","border":"1px solid red","font-size":"150%");
    
    	/* 能够一直使用"."语法来操作这个选中的div元素就是链式风格了 */
    </script>
    </html>
    
  • 特殊函数jQuery

    1. jQuery(选择器) 用于获取对应的元素
    2. 但是jQuery不返回这些元素
    3. 相反,它返回一个可以操作的api对象,称为jQuery构造出来的对象
    4. 这个api对象可以直接操作对于的元素

五、浅析jQuer设计封装原理

在前面,我实现了自己手动封装一个DOM库,其实功能十分有限,API也仅仅是简单的增删改查。

而今天我将要简单分析jQuery的设计封装原理,它之所以这么长寿,使用这么广泛,因为它是真的非常经典!下面我们通过自己尝试封装jQuery库来一步一步的感受其中的奥妙!

  • 我们不妨先自己尝试下封装jQuery库,从起点出发,探究它的深层代码底蕴。用过jQuery的都知道,jQuery接受两个参数,一个是选择器,另外一个是字符串内容。但是它不返回选择的元素,而是返回一个API对象
    1. 我们先实现返回一个API对象
 /* 在window上挂载一个全局函数jQuery */
 window.jQuery=function (selector) {
	let elements=document.queryselectorAll(selector);

	/* 既然jQuery是返回的一个API对象,那我们也定义一个API对象然后再返回它,注意这个API可以操作elements,这里就是用到了闭包 */

	const api = {
		addClass(className){
			for(let i=0;i<elements.length;i++){
				elements[i].classList.add(className);
			}
			return ???;
		}
				.
				.
				.(省略的一些函数)
				.
				.
	}
	return api;
 }

  1. 每个函数也返回api对象,这样就实现了链式操作!
 window.jQuery=function (selector) {
	let elements=document.queryselectorAll(selector);

	const api = {
		addClass(className){
			for(let i=0;i<elements.length;i++){
				elements[i].classList.add(className);
			}
			return api; //每个函数操作完后也同样返回api;
		}
				.
				.
				.(省略的一些函数)
				.
				.
	}
	return api;
 }

  1. 再次优化返回的API对象--使用this
 window.jQuery=function (selector) {
	let elements=document.queryselectorAll(selector);

	const api = {
		addClass(className){
			for(let i=0;i<elements.length;i++){
				elements[i].classList.add(className);
			}
			return this; //使用this优化
		}
				.
				.
				.(省略的一些函数)
				.
				.
	}
	return api;
 }

  1. 有没有觉得定义了一个api然后再返回api有点多余??那就对了,接下来继续改进
 window.jQuery=function (selector) {
	let elements=document.queryselectorAll(selector);

	/* 直接return 其实return里面这一坨就是api */

	return {
		addClass(className){
			for(let i=0;i<elements.length;i++){
				elements[i].classList.add(className);
			}
			return this; //使用this优化
		}
				.
				.
				.(省略的一些函数)
				.
				.
	}
 }

  1. 实现find()功能查找出响应选择器对应的元素,其实jQuery就是返回使用一次后,就会返回新的api对象,很显然我们在实现find()功能时会出现很多意想不到的错误,那要怎么办呢? 其实答案很明显了,jQuery本来就是重新封装元素对应的api,我们只要在里面添加封装一个函数来返回一个新的api对象即可。那这样刚开始接受的参数selector就有了变化,可能也是数组类型,我们只需要在开头就判断好参数对应的类型就好了。这就是属于重载了。当然了,后面有更多的函数如果也这样,也只需要判断selector类型,重新使用jQuery再次封装一个新的api即可。
 window.jQuery=function (selector) {
	let elements;	//将elements变成jQuery里面的全局变量,不然let只有在块作用域里面才有用
	if(typeof selector==='string'){
		elements=document.queryselectorAll('selector')
	}
	else if(selector instanceof Array){
		elements=selector;
	}

	return {
		addClass(className){
			for(let i=0;i<elements.length;i++){
				elements[i].classList.add(className);
			}
			return this; //使用this优化
		},
		find(selector){
			let arr=[];
			for(let i=0;i<elements.length;i++){
				const elements2=Array.from(elements[i].queryselectorAll(selector))
				arr=arr.concat(elements2);
			}
			/*  return this 注意这里不能返回this,否则会出错,原因如开头的解释 */

			const newApi=jQuery(arr);	//重新使用jQuery封装新的api
			return newApi;		
		}
				.
				.
				.(省略的一些函数)
				.
				.
	}
 }

  1. 实现end()或者叫做return()函数,就是用户想要返回上一次想要操作的api元素来进行操作。我们其实可以在find()函数身上下手,因为只有find()后才是找到或者保留某个对应元素对应的api,这样以来,我直接在find()函数里面保留上一次的oldApi不就ok了!
 window.jQuery=function (selector) {
   let elements;
   if(typeof selector==='string'){
   	elements=document.queryselectorAll('selector')
   }
   else if(selector instanceof Array){
   	elements=selector;
   }

   return {
   	addClass(className){
   		for(let i=0;i<elements.length;i++){
   			elements[i].classList.add(className);
   		}
   		return this; //使用this优化
   	},
   	find(selector){
   		let arr=[];
   		for(let i=0;i<elements.length;i++){
   			const elements2=Array.from(elements[i].queryselectorAll(selector))
   			arr=arr.concat(elements2);
   		}
   		/*  return this 注意这里不能返回this,否则会出错,原因如开头的解释 */

   		arr.oldApi=this;	//保存旧的api
   		const newApi=jQuery(arr);	//重新使用jQuery封装新的api
   		return newApi;		
   	},
   	end(){
   		return this.oldApi;
   	}
   	oldApi:selector.oldApi,
   			.
   			.
   			.(省略的一些函数)
   			.
   			.
   }
 }

  1. 实现createElement()函数创建单个元素或者多个元素。注意了,能够容纳所有标签的元素为[<template></template>标签](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template)
 window.jQuery=function (selector) {
   let elements;	
   if(typeof selector==='string'){
   	if(selector[0]==='<'){
   		elements=createElement(selector);
   	}
   	else{
   		elements=document.queryselectorAll('selector')
   	}
   }
   else if(selector instanceof Array){
   	elements=selector;
   }
   function createElement(string) {
   	const container=document.createElement('template');
   	container.innerHTML=string.trim();	//去除空格
   	return container.content.firstChild;
   }

   return {
   	addClass(className){
   		for(let i=0;i<elements.length;i++){
   			elements[i].classList.add(className);
   		}
   		return this; //使用this优化
   	},
   	find(selector){
   		let arr=[];
   		for(let i=0;i<elements.length;i++){
   			const elements2=Array.from(elements[i].queryselectorAll(selector))
   			arr=arr.concat(elements2);
   		}
   		/*  return this 注意这里不能返回this,否则会出错,原因如开头的解释 */

   		arr.oldApi=this;	//保存旧的api
   		const newApi=jQuery(arr);	//重新使用jQuery封装新的api
   		return newApi;		
   	},
   	end(){
   		return this.oldApi;
   	}
   	oldApi:selector.oldApi,
   			.
   			.
   			.(省略的一些函数)
   			.
   			.
   }
 }

  1. 命名风格上的提升,我觉得jQuery也挺难拼写的,直接就用$代替吧!
winodw.$=window.jQuery=function(selector){
    .
    .
    .(省略的函数)
    .
    .
}

  1. jQuery.prototype共有属性。其实封装好了之后还是有缺陷的,因为这些api函数都是共有属性,若不将这些共有属性抽出来使用,则会浪费非常多的内存空间,每次调用这个jQuery API就会开辟一个新的空间。这样肯定是得不偿失的。我们只需要在原型链上加上这个jQuery的共有属性即可。
window.$=window.jQuery=function(){
    let elements;	//将elements变成jQuery里面的全局变量,不然let只有在块作用域里面才有用
  if(typeof selector==='string'){
  	if(selector[0]==='<'){
  		elements=createElement(selector);
  	}
  	else{
  		elements=document.queryselectorAll('selector')
  	}
  }
  else if(selector instanceof Array){
  	elements=selector;
  }
  function createElement(string) {
  	const container=document.createElement('template');
  	container.innerHTML=string.trim();	//去除空格
  	return container.content.firstChild;
  }

  const api=Object.create(jQuery.prototype);	// 规范里的书写使用create来更改原型链的指向 等价于 api.__proto__=jQuery.prototype。

  Object.assign(api,{
  	elements:elements,
  	oldApi:selector.oldApi
  });	//批量赋值

  return api;
}

<!-- 加上jQuery.fn。这是jQuery源码的一句,我其实没有理解到,可能是为了某些理解或好写避免prototype字太长了 --> 
jQuery.fn=jQuery.prototype={
    constructor:jQuery,
    jQuery:true;
    .
    .
    .(省略的共有函数属性)
    .
    .
}

六、总结

通过上面的例子,我从中收获并且了解了jQuery的经典代码和封装技巧,以及如何使用原型链来节约内存,放置共有属性等等。站在巨人的肩膀上眺望远方,我们不仅能收获很多,更能给我们跟多的思考和启迪,希望以上对jQuery的一些理解也能帮助到你们!

这次浅析jQuery的gitbuh源代码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值