【JS编程接口】jQuery

203 阅读9分钟

一、手写jQuery

我自己写的部分代码看我的github的jQuery项目

开源项目里有全部代码

(一)准备工作

1. 新建文件夹DOM-1、文件index.html、文件main.js、文件jQuery.js

2. 初始化index.html

  • 语言改成zh
  • title改成“手写jQuery”
  • 写个‘你好’
  • 先引用jQuery.js<script src="jQuery.js"></script>
  • 再引用main.js<script src='main.js'></script>,因为main.js需要使用jQuery.js的代码

3. 初始化jQuery.js

创造jQuery是个全局函数

window.jQuery = function () {  
    console.log('我是jQuery')
}

4. 初始化main.js

  • main.js里面就可以使用jQuery
  • jQuery()

5、用parcel预览效果

  • 新建终端
  • parcel src/index.html
  • 打开网页,看见‘你好’
  • 打开控制台看见'我是jQuery'
  • 成功

(二)重点开始

1、写jQuery函数

(1)第一版代码:(闭包)傻瓜代码,为了让你知道最详细的过程

window.jQuery = function(selector) {
  //1.jQuery是个全局函数,我们要给他一个选择器当参数
  const elements = document.querySelectorAll(selector); //2.他会选出满足条件的节点,组成一个伪数组叫element
  const api = {
    //3.他会生成一个叫api的对象,这个对象里的每一个函数都和elements形成闭包,也就是每个函数都要用到elements
    //用这个api对象里的任意一个函数都可以操作elements
    addClass(className) {
      //给我一个class的名字,我会遍历每个element中的元素,给他们都加上className
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
    }
  };
  return api; //4.最后返回对象api,这个对象里的函数可以对选中的元素(elements)操作
};
//你想选出(操作)哪些元素,你就把对应的选择器给jQuery函数,然后他会给你可以操作这些元素的函数集合(api对象)
//我想对.test的元素操作,那么jQuery(.test)就是一个叫api的对象,他包含了所有可以对.test元素操作的函数
//jQuery(.test).addClass('yy') 这样就对所有.test元素加上个叫yy的class

(2)第二版代码:(链式操作)思考return啥

window.jQuery = function(selector) {
  const elements = document.querySelectorAll(selector); 
  const api = {
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return api  //执行完函数后还是返回api,可以继续对elements操作
    }
  };
  return api; 
};
//jQuery(.test).addClass('yy') 这样就对所有.test元素加上个叫yy的class,但是返回值仍是api,也就是说我们可以继续对.test操作
//jQuery(.test).addClass('yy').addClass('bb')对所有.test元素加上个叫yy的class,因为返回值是api,我们仍可对所有.test元素操作加上bb的class

注意到,jQuery(.test).addClass('yy')的this就是jQuery(.test)也就是api。 jQuery(selector).addClass('yy')的this===jQuery(selector)===api,所以改成return this

window.jQuery = function(selector) {
  const elements = document.querySelectorAll(selector); 
  const api = {
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this  //执行完函数jQuery(selector).addClass(xxx)后还是返回this(=jQuery(selector)),还可以继续对elements操作
    }
  };
  return api; 
};

window.jQuery(selector)的this是window,不要晕倒了

(3)第三版代码:优化,把api弄掉节省内存,因为我们创建了api对象,又返回api对象,直接返回一个对象不就好吗(知道为啥前面要把return从api改成this了吧)

window.jQuery = function(selector) {
  const elements = document.querySelectorAll(selector);

  return {
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    }
  };
};

(4)小总结

jQuery(选择器)用于获取对应的元素

  • 但它却不返回这些元素
  • 相反,它返回一个对象,称为jQuery构造出来的对象(简称jQuery对象)
  • 这个对象可以操作对应的元素
jQuery(.test) 返回 当前的JQuery对象
jQuery(.test).addClass('yy') 操作:给所有.test元素加上classyy
                             返回:this===jQuery(.test) ----当前的JQuery对象
jQuery(.test).addClass('yy').addClass('bb') 操作:给所有.test元素加上classbb
                                            返回:this===jQuery(.test).addClass('yy') ----当前的JQuery对象

jQuery是一个不需要加new的构造函数。

  • jQuery()能构造一个对象,而且不用加new
  • jQuery不是常规意义上的构造函数,这是因为jQuery用了一些技巧(目前没必要知道)

window.$ = window.jQuery以后用$()就行

④用$开头的名字给JQuery对象命名!const $div=jQuery(.test)易于和普通DOM标签区分!

2、写find()函数以及发现链式操作的问题

(1)第一版代码:写find函数

window.jQuery = function(selector) {
  const elements = document.querySelectorAll(selector);

  return {
    //对elements操作
    //一、查
    //find函数用于找某些元素
    find(selector) {
      let array = []; //这个数组将储存结果元素
      for (let i = 0; i < elements.length; i++) {
        //对elements中每一个元素中来找其中的结果元素
        array = array.concat(
          Array.from(elements[i].querySelectorAll(selector))
        );
        //所有结果元素会成为伪数组,把伪数组变成真数组,在连接到array中去,成为新的array
      }
      return array;
    },


  };
};

那完了,链式结构中断了。jQuery('.test').find('.children')函数返回的是数组array,不是this了!如果返回this,那就是那个jQuery对象了,之后还是对.test对象操作,那你找.children干啥??

(2)第二版代码:修改jQuery函数

  • 我们发现jQuery函数不能只接受选择器,必须要接受个数组成为elements
  • 第一步:重载:当参数为字符串时为选择器,elements是选出的那些元素组成的伪数组;当参数为数组时,elements就是数组本身
  • 第二步:需要数组成为新的jQuery对象时,就要重新用jQuery(数组)成为新的jQuery对象,然后return他;否则直接return this返回当前的JQuery对象就行
window.jQuery = function (selectorOrArray) {
  let elements //const声明时必须赋值而且还不能改,所以只能用let了//不可以在下面elements前声明,不然就只在那个块级作用域里有用了
  if (typeof selectorOrArray === 'string') {  //如果是字符串,那就是选择器,选出对应元素
    elements = document.querySelectorAll(selectorOrArray)
  } else if (selectorOrArray instanceof Array) { //如果是数组,那就是它本身
    elements =selectorOrArray
  }

  return {
    //对elements操作
    find(selector) {
      let array = []; 
      for (let i = 0; i < elements.length; i++) {
        array = array.concat(
          Array.from(elements[i].querySelectorAll(selector))
        );
      }
      const newApi = jQuery(array) //我们不可以return之前的api了,不然你find出这些元素干啥?肯定要对find出的元素搞事情了啊。//所以发现jQuery不能只接受选择器,还要有数组
                                   //生成全新的jQuery对象了(专属这个数组里元素的函数集合)
      return newApi;
      //前两句请合并为 return jQuery(array)
    },

    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    }
  };
};

3、end函数:链式问题还没解决:我还想回到之前的旧的jQuery对象咋办?

  • 在新建数组的JQuery对象jQuery(数组)前,要先把当前的JQuery对象加进数组里,成为一个新属性oldApi
  • 给JQuery对象加一个新属性oldApi:selectorOrArray.oldApi
  • end函数:返回当前jQuery对象的oldApi
window.jQuery = function(selectorOrArray) {
  let elements;
  if (typeof selectorOrArray === "string") {
    elements = document.querySelectorAll(selectorOrArray);
  } else if (selectorOrArray instanceof Array) {
    elements = selectorOrArray;
  }

  return {
    oldApi: selectorOrArray.oldApi, //给JQuery对象加一个属性,值为selectorOrArray的oldApi属性

    find(selector) {
      let array = [];
      for (let i = 0; i < elements.length; i++) {
        array = array.concat(
          Array.from(elements[i].querySelectorAll(selector))
        );
      }
      array.oldApi = this; //先把当前的jQuery对象放到即将要成为新的elements的数组中去,成为一个oldApi属性
      return jQuery(array); //在返回 新建的 数组的jQuery对象
    },
    end() {
      return this.oldApi;
    }, //this为数组的JQuery对象了,他有个oldApi属性,属性值为selectorOrArray(也就是数组)的属性oldApi

    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    }
  };
};

api1=jQuery('.test')
api2=jQuery('.test').find('.children')
api2.end()//返回this的oldApi。
          //this===api2===当前的JQuery对象。
          //oldApi:selectorOrArray.oldApi=array.oldApi=(当时的this)=(当时的JQuery对象api2)

二、总结

1、jQuery(选择器)用于获取对应的元素,但它却不返回这些元素

  • 相反,它返回一个对象,称为jQuery构造出来的对象(简称jQuery对象)
  • 这个对象可以操作对应的元素
jQuery(.test) 返回 当前的JQuery对象
jQuery(.test).addClass('yy') 操作:给所有.test元素加上class:yy
                             返回:this===jQuery(.test) ----当前的JQuery对象
jQuery(.test).addClass('yy').addClass('bb') 操作:给所有.test元素加上class:bb
                                            返回:this===jQuery(.test).addClass('yy') ----当前的JQuery对象

2、jQuery是一个不需要加new的构造函数。

  • jQuery()能构造一个对象,而且不用加new
  • jQuery不是常规意义上的构造函数,这是因为jQuery用了一些技巧(目前没必要知道)

3、window.$ = window.jQuery以后用$()就行

4、用$开头的名字给JQuery对象命名!

const $div=jQuery(.test)易于和普通DOM标签区分!

三、常用API

(具体代码不用管了,只需知道大体思路:调用DOM的API再加上些逻辑语句)

  • jQuery('#xxx)返回值并不是元素,而是一个api对象
  • jQuery(" #xxx').find('.red')查找#xxx里的.red元素
  • jQuery('#xxx').parent()获取爸爸
  • jQuery('#xxx").children()获取儿子
  • jQuery(' #xxx' ).siblings()获取兄弟
  • jQuery(' #xxx ).index()获取排行老几(从0开始)
  • jQuery(" #xxx' ).next()获取弟弟
  • jQuery(' #xxx ).prev()获取哥哥
  • jQuery(.red').each(fn)遍历并对每个元素执行fn
  • jQuery('#xxx).get(index)获取elements的下标为index的元素

  • $('<div><span> 1</span></div> ).appendTo(...)把这串HTML加入...中
  • $('body')获取document.body
  • $('body').append(div>l</div>) 添加小儿子
  • $('body ).append('<div>1</div>)更方便
  • $(' body ).prepend(div或$div)添加大儿子
  • $('#test').after(div或$div)添个弟弟
  • $('#test').before(div或$div)添个哥哥

  • $div.remove()
  • $div.empty()删子元素

  • $div.text(?)读写文本内容
  • $div.html(?)读写HTML内容
  • $div.attr('title',?)读写属性
  • $div.css({color: 'red'})读写style // $div.style更好
  • $div.addClass('blue') / removeClass / hasClass
  • $div.on('click', fn)
  • $div.off('click, fn)

四、使用原型优化代码,节约内存

  • 新建一个对象#619,把JQuery对象的所有共有函数放入这个新对象----原型
  • 原来的JQuery对象里面的__proto__保存原型的地址#619
  • JQuery函数的prototype保存他要生成的对象——JQuery对象的原型的地址#619
  • 也就是,把共有属性(函数)放到JQuery函数的prototype,然后jQuery.fn = jQuery.prototype,然后让api.__proto__指向$.fn
  • 注意,此时在JQuery函数里定义的变量elements的作用域只在JQuery函数里,所以jQuery.prototype里定义的函数用不到elements,必须全改为this.elements
window.$=window.jQuery = function(selectorOrArrayOrtemplat) {  //jQuery太长,用$代替
  let elements;
  if (typeof selectorOrArray === "string") {
    elements = document.querySelectorAll(selectorOrArray);
  } else if (selectorOrArray instanceof Array) {
    elements = selectorOrArray;
  }

  //加入共有属性
  const api = Object.create(jQuery.prototype); //创建个名为api的对象,这个对象的__proto__为jQuery.prototype
                                               //相当于const api = {__proto__:jQuery.prototype}

  //加入自身属性
  /*小白写法
  api.elements = elements;
  api.oldApi = selectorOrArray.oldApi;
  */
  //大师写法:Object.assign()表示把后面这些属性一个一个的加入api对象
  Object.assign(api, {
    elements = elements,
    oldApi = selectorOrArray.oldApi
  })

  return api;
};



jQuery.fn = jQuery.prototype = {  //prototype太长,用fn代替
  //共有属性
  constructor: jQuery, //必须得要,这是规定
  oldApi: selectorOrArray.oldApi,
  jquery: true,

  get(index){
      return this.elements[index];
  }
  find(selector) {},
  end() {},
  addClass(className) {},
  each(fn) {},
  parent() {},
  children() {},
  siblings() {},
  index() {},
  next() {},
  prev() {}
};

五、设计模式

(一)jQuery用到了哪些设计模式

  • 不用new的构造函数,这个模式没有专门的名字
  • $(支持多种参数),这个模式叫做重载(一个函数支持的参数多种多样)
  • 用闭包隐藏细节,这个模式没有专门的名字
  • $div.text()即可读也可写,getter / setter
  • $.fn``是$.prototype的别名,这叫别名
  • jQuery针对不同浏览器使用不同代码,这叫适配器模式

(二)设计模式是啥

  • 老子这个代码写得太漂亮了,别人肯定也用得到
  • 那就给这种写法取个名字吧,
  • 比如适配器模式
  • 设计模式就是对通用代码取个名字而已

(三)我应该学习设计模式吗?

  1. 设计模式不是用来学的
  • 你看了这些代码,但你并不知道这代码用来解决什么问题,看了白看
  1. 设计模式是用来总结的
  • 你直管去写代码
  • 把你的代码尽量写好,不断重写
  • 总结你的代码,把写得好的地方抽象出来
  • 看看符合哪个设计模式
  • 你就可以告诉别人你用到了这几个设计模式
  • 显得你特别高端

(三)需要学jQuery!

  1. 真相
  • jQuery这么简单、经典的库为什么不学?
  • 通过jQuery可以学会很多封装(把一个变量放到函数里,然后暴露出一个API,这个API可以操作这个变量)技巧,为什么不学?
  • 连jQuery都理解不了,Vue / React肯定学不好
  1. 推荐文章 《jQuery都过时了,那我还学它干嘛?》
  2. 学习路线
  • 理解jQuery原理(本篇博客就是)
  • 使用jQuery做一两个项目
  • 再总结篇博客(本篇博客+参考阮一峰+参考jQuery API 中文文档),然后再也不碰jQuery了
  • 滚去学Vue / React,找工作要紧