阅读 799

浅析jQuery

用jQuery风格重新封装DOM

为什么学习jQuery

jQuery 设计模式

理解jQuery闭包和链式风格

来自基础之问:jQuery是构造函数吗?

jQuery 一些函数的实现

jQuery 的增删改查

要说的话

   本文主要用来理解jQuery的闭包和链式操作核心思想,以及一些函数的实现。要知道是怎么来的。--资料来源于饥人谷

推荐阅读文章

阮一峰jQuery设计思想。本文源代码

为什么学习jQuery

jQuery有多牛X

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

   它是世界上使用最广泛的库,到2020年11月也有全球80%的网站在用

我应该学习设计模式吗?

  • 设计模式不是用来学的

   你看了这些代码,但你并不知道这代码用来解决什么问题等于看了白看

  • 设计模式是用来总结的

   你只管去写代码,把你的代码尽量写好,不断重写。

   总结你的代码,把写得好的地方抽象出来,看看符合哪个设计模式,你就可以告诉别人你用到了这几个设计模式,显得你特别高端

有人说不用学jQuery

  • 真相

   jQuery这么简单、经典的库为什么不学?

   通过 jQuery可以学会很多封装技巧,为什么不学?

   连jQuery都理解不了, Vue / React肯定学不好

   推荐文章 《jQuery都过时了,那我还学它干嘛?》

  • 学习路线

   理解 jQuery原理

   使用jQuery做一两个项目

   学Vue / React,找工作要紧

设计模式?

  • jQuery 用到了哪些设计模式

   不用new的构造函数,这个模式没有专门的名字

   $(支持多种参数),这个模式叫做重载

   用闭包隐藏细节,这个模式没有专门的名字

   $div.text()即可读也可写,getter / setter

   $.fn是$.prototype 的别名,这叫别名

   jQuery针对不同浏览器使用不同代码,这叫适配器

  • 设计模式是啥

   设计模式就是对通用代码取个名字

理解jQuery闭包和链式风格

   链式风格也叫 jQuery风格

   window.jQuery()是我们提供的全局函数

  • 特殊函数jQuery

   jQuery(选择器)用于获取对应的元素,但它却不返回这些元素,相反,它返回一个对象,称为jQuery构造出来的对象简称jQuery对象

(jQuery对象,不是说「jQuery 这个对象」,一定要记清楚,jQuery对象代指jQuery 函数构造出来的对象,口头约定)

   这个对象可以操作对应的元素

   听不懂?直接写代码!

上代码理解链式

在html里

 <div class="test">
        你好1
    </div>
    <div class="test">
        你好2
    </div>
    <div class="test">
        你好3
    </div>
<script src="jQuery.js"></script>
<script src="main.js"></script>
复制代码

jQuery.js

//核心思想:jQuery接受一个选择器,然后根据选择器得到一些元素,然后返回一个对象,这个对象有个方法去操作这个元素
window.jQuery = function (selector) {
  //jquery获取elements后,声明个api,这个api可以操作这个elements
  const elements = document.querySelectorAll(selector);
  //elements就是选择器对应的元素
  //---api 可以操作elements---
  const api = {
    //---闭包函数访问外部的变量---。(addClass访问elements,elements是外部的变量)
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return api;//返回api对象方便继续调用---链式---
    },
  };
  return api; //jQuery返回一个可以操作elements的api对象。
};

复制代码

main.js

const api = jQuery('.test')//不返回元素们,返回api对象
api.addClass('red')//遍历所有刚才获取的元素,添加 .red
api.addClass('red').addClass('blue')//链式操作
//用api调了addClass函数,这个函数返回了前面的api,于是可以继续调用addClass。只需要return 那个对象   
复制代码

深入链式

   先来看一下this

obj.fn(p1) //函数里的this就是obj
obj.fn.call(obj,p1)
复制代码

   main.js里意思是api就是this。那可以直接ruturn this。

jQuery里代码简化

window.jQuery = function (selector) {
  //jquery获取elements后,声明个api,这个api可以操作这个elements
  const elements = document.querySelectorAll(selector);
  //elements就是选择器对应的元素
  //---api 可以操作elements---
  return {
    //---闭包:函数访问外部的变量---。(addClass访问elements,elements是外部的变量)
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    },
  };
};

复制代码

main.js代码简化

//可以不声明api。简写成
jQuery(".test")
  .addClass('red')
  .addClass('blue')
  .addClass('green')
复制代码

示例闭包和链式风格核心思想:

  jQuery提供一个函数,这个函数接受一个css选择器,这个选择器会获取到这些元素,但不会反回elements这些元素,会返回一个对象,对象里可能有一些方法(函数),这些方法会来操作这些元素(用闭包维持elements)。这个函数能猜到你在调用addClass的时候,肯定是通过得到的api的,所以会return this,希望把api.addClass中.前面的东西作为addClass的返回值(传的什么就是this),这样的话就相当于api从前面传递到了后面,传到了后面又可以.addClass....(链式操作))


jQuery是构造函数吗?

   因为jQuery函数确实构造出了一个对象

  • 不是

   因为不需要写new jQuery()就能构造一个对象,以前讲的构造函数都要结合new才行

  • 结论

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

   jQuery不是常规意义上的构造函数

   这是因为jQuery 用了一些技巧

术语

  • 举例

   Object是个函数,

   Object对象表示 Object构造出的对象。

   Array是个函数,

   Array对象/数组对象表示Array构造出来的对象。

   Function是个函数,

   Function对象/函数对象表示Function构造出来的对象。


一些实现

实现find和返回新的api

   实现find

jQuery.js

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

  //---api 可以操作elements---
  return {
    //---闭包:函数访问外部的变量---。(addClass访问elements,elements是外部的变量)
    addClass(className) {
      for (let i = 0; i < elements.length; i++) {
        elements[i].classList.add(className);
      }
      return this;
    },

    find(selector) {
      //找到当前元素所有匹配选择器的元素,然后把元素放到数组里,在返回这个数组
      let array = []; //思路:我要在test1里找child,可以通过elements去找,但elements是多个元素,可以认为是一个数组,数组是不能querySelectorAll的。只能先声明一个临时的数组,用这个数组去储存新的child元素

      for (let i = 0; i < elements.length; i++) {
        const elements2 = Array.from(elements[i].querySelectorAll(selector)); //concat是个伪数组,要变成数组。用之前的空数组连接上新的元素,然后把新的元素的到的新数组在放回array。
        array = array.concat(elements2);
      }
      return array;
    },
复制代码

main.js

const x1 = jQuery('.test').find('.child')//获取.test1,查找类为child的元素
console.log(x1)//x1是个数组,不是一个纯函数,没有链式。
复制代码
  • 遇到的问题:能不能写成链式。

   按照理解链式来看,就算 return this 也是return{}对象,这个对象操作的是elements,就不能操作array。如jQuery('.test').find('.child').addClass('red')现在return this,加到的是test上,find前面的api是test。

  写成链式,返回新的api对象。

   第一步,find函数里return一个新的api,靠jQuery构造出来

      const newApi = jQuery(array);
      //如果直接用同一个api,那每次得到新的元素都会污染之前的api,所以要得到一个新的对象
      return newApi;
      //return jQuery(array)//两行代码简写
复制代码

   第二步,jQuery不能只接受一个选择器,还得接受一个数组:把selector改成selectorOrArray

   第三步,声明elements值为空,然后根据选择器是string还是Array分别赋予不同的值,然后返回一个api去操作它(重载)

window.jQuery = function (selectorOrArray) {
  //重载
  let elements
  if (typeof selectorOrArray === 'string') {
    elements = document.querySelectorAll(selectorOrArray);
  } else if (selectorOrArray instanceof Array) {
    //对象用instanceof
    elements = selectorOrArray;
  }

复制代码

在main.js里试试

//去变量
jQuery('.test')
  .find('.child')
  .addClass('red')
  .addClass('blue')
  .addClass('green')
复制代码

最后结果,链式成功

   那想返回test再操作呢

   要用到end返回

实现end

jQuery.js

    oldApi: selectorOrArray.oldApi,
    
    ···
      
    find(selecor) {
    ...
    array.oldApi = this; //这里this就是旧api
    //把oldApi放到了数组身上并没有放到api身上,插入最上面的代码这样api也有oldApi
    return jQuery(array);
  },
    
    end() {
    return this.oldApi// 这里this就是当前的新的api 
  },
复制代码

main.js

//命个名方便理解
const api1 = jQuery('.test')
const api2 = api1.find('.child').addClass('red').addClass('blue').addClass('green')
const oldApi = api2.end().addClass('yellow')

复制代码

end()实现

实现each

jQuery.js

    each(fn) {
      //遍历当前所有元素
      for (let i = 0; i < elements.length; i++) {
        fn.call(null, elements[i], i);
      }
      return this//this就是当前api
    },
复制代码

main.js

const x = jQuery('.test')
    .find('.child')

x.each((div) => console.log(div))

复制代码

实现each()

实现爸爸

jQuery.js

        //实现parent
        //parent不需要参数,直接什么什么.parent
        parent(){
            //获取对应元素的爸爸
            const array = []
            this.each((node)=>{//每一个元素我们要得到一个节点
                if(array.indexOf(node.parentNode) === -1){//push的时候判断一下,不在里面就是等于-1
                    array.push(node.parentNode)//把这个节点的爸爸放到数组里 
                }
            })
            return jQuery(array)
            //array没有什么可操作性,所以要封装一个操纵数组的对象,jQuery会返回一个对象,这个对象会操作这些爸爸
        },
        
        print(){//实现print方法把当前elements元素打印出来
            console.log(elements)
        },
复制代码

main.js

//爸爸
const x =jQuery('.test')
x.parent().print()
//直接用获取到的api去print一下,它就会操作这些爸爸
复制代码

实现儿子

jQuery.js


        children(){
            const array = []//准备好一个数组
            this.each((node)=>{
                array.push(...node.children)//...是把里面的东西拆开,第一个元素当做第一个参数,第二个元素当做第二个参数。
                                              //等价于(node.children[0], node.children[1],node.children[2]...等等)
            })//遍历刚才的元素,
            return jQuery(array)
        },
复制代码

main.js

const x =jQuery('.test')

x.children().print()
//获取children并打印出来
复制代码


jQuery的增删改查...

链式风格-查

  • jQuery('#xxx')返回值并不是元素,而是一个api对象

  • jQuery('#xxx').find ('.red')查找#xxx里的.red元素

  • jQuery('#xxx').parent()获取爸爸

  • jQuery('#xxx').children()获取儿子

  • jQuery('#xxx').siblings()获取兄弟

  • jQuery('#xxx').index()获取排行老几(从O开始)

  • jQuery('#xxx').next()获取弟弟

  • jQuery('#xxx').prev()获取哥哥

  • jQuery('.red').each(fn)遍历并对每个元素执行fn

命名风格

   嫌弃jQuery太长了?

  • 用$替代jQuery

   代码中所有$开头的变量,都是jQuery 对象

   这是约定,除非特殊说明

window.$ = window.jQuery = function(selectorOrArray){
//等号赋值从右忘左执行,也可以在最后写成window.$ = window.jQuery
...
}
复制代码
  • 下面的代码令人误解

   const div = $('div#test')

   我们会误以为div是一个DOM

   实际上div是 jQuery构造的api对象

   怎么避免这种误解呢?

  • 改成这样

   const $div = $('div#test')

   $div.appendChild不存在,因为它不是DOM对象

   $div.find存在,因为它是jQuery对象

链式风格·删

  1. $div.remove()

  2. $div.empty()

链式风格·增

  1. $ ('body')获取document.body

  2. $('body' ).append($ ('<div>1</div>'))添加小儿子

  3. $('body').append('<div>1</div>')更方便

  4. $('body').prepend(div或$div)添加大儿子

  5. $('#test').after(div或 $div)添个弟弟

  6. $ ('#test').before(div或 $div)添个哥哥

$ ('<div><span>1</span></div>')

   返回值并不是新增的元素,而是api对象

$ ('<div><span>1</span></div>').appendTo(...)

   appendTo可以把新增的元素放到另一个元素里

  • 这是一种什么感觉

   就感觉DOM是不可见的,你不需要知道DOM的任何细节,只需要使用简洁的API即可

   一个好的封装,能让使用者完全不知道内部细节

   这是通过闭包实现的

我就是想知道细节咋办
  • 举例1

   const $div = $('div#test')

   $div并不是 DOM对象,而是jQuery构造的api对象

   我现在就是想从 $div得到div元素,行不行?

  • 满足你

   $div.get(0)获取第0个元素//div

   $div.get(1)获取第1个元素// undefined

   $div.get(2)获取第2个元素// undefined

   $div.size()得到元素的个数

   $div.length也可以得到元素的个数

   $div.get()获取所有元素组成的数组// [div]

  • 举例2

   const $div= $('.red') //假设有3个div.red

   $div不是DOM对象们,而是jQuery构造的api对象

   我现在就是想从$div得到3个div元素,行不行?

  • 满足你

   $div.get(0)获取第0个元素//div

   $div.get(1)获取第1个元素// div

   $div.get(2)获取第2个元素//div

   $div.size得到元素的个数

   $div.length 也可以得到元素的个数

   $div.get()获取所有元素组成的数组//[div,div,div]

链式风格·改

  1. $div.text(?)读写文本内容

  2. $div.html(?)读写HTML内容

  3. $div.attr('title',?)读写属性

  4. Sdiv.css({color: 'red"})读写style // $div.style更好

  5. $div.addClass('blue') / removeClass / hasClass

  6. $div.on('click', fn)

  7. $div.off('click', fn)

  • 注意

   $div可能对应了多个div元素

使用原型

   把共用属性(函数)全都放到$.prototype

   $.fn = $.prototype//名字太长不爽

   然后让api.__proto__指向$.fn

   api一开始就把原型链链上,共有属性不用操心了,特有属性就放到自己身上。节约了内存。

   注意的问题:变量访问不到了,用一个api作为桥梁,用this去访问api。constructor记得补上





--continue
文章分类
前端
文章标签