从不断“进化”的源代码来碰面jQuery

jQuery的本质是什么?

一个JavaScript库。
接受一个旧的节点,返回一个新的对象(哈希),这个对象就是jQuery对象。
jQuery的特点就是它拥有自己的API。
jQuery的源码调用DOM API,但是jQuery对象无法使用DOM API。

jQuery的便利之处

jQuery极大地简化了JS编程,也容易上手和学习。
尽可能的避免了你使用原生DOM那些很“烂、复杂”的API。
jQuery的兼容性极好。
但是极多的API和不断更新的时效性,想要透彻了解还是离不开:
熟能生巧。

实现jQuery的过程

就是编写源码,反复优化实现需求的过程。

首先我们来 封装函数

这里只举一个“添加类”的函数为例。在实际开发中,我们要声明很多的函数来实现方法的需求。
我们使用简单的html结构


    
111
222
333
  • select1
  • select2
  • select3

函数的功能:
为指定节点(node)添加类。

function addClass(node,classes) { //给传入的元素节点添加一个或多个类
  classes.forEach((value)=>node.classList.add(value))
}

addClass(item3,['a','b']) //id为"item3"的节点

//
  • select3
  • 给予它们一个命名空间

    上面的函数,虽然功能已经初步实现,但是有一个很大的问题:
    我们在处理复杂页面时,为了方便使用和记忆,可能会声明很多名字和DOM API极为“类似”的函数;那么就会在不知不觉中,覆盖掉一些API固有的函数,也可能会覆盖一些全局变量。

    DOM API将众多方法放在document对象中(window.document.xxx)。我们也需要给它们一个命名空间,即将其写入一个库中,来暂时性的解决这个问题。也同时直接的告诉开发者:我们声明的这些函数,是有内在的关联性的,因为它们都是在操纵节点。

    命名空间:名字空间,也称命名空间、名称空间等,它表示着一个标识符的可见范围。一个标识符可在多个名字空间中定义,它在不同名字空间中的含义是互不相干的。命名空间就是一种设计模式。(详见维基百科)

    比如笔者的 name = 'sgs'

    window.sgs = {} //声明一个空的库
    
    function addClass(){如上}
    sgs.addClass = addClass
    
    sgs.addClass(item2,['a','c'])
    //实现同样的效果

    这样我们就可以调用sgs库的方法来达到同样的目的,而避免了全局变量的覆盖;同时,这些方法都在我们归纳的同一个库中。

    这个问题解决以后,我们又发现:我们其实并不想这样使用一个API。
    我们是否可以直接的对节点调用一个功能函数:item3.addClass()

    如何实现?将node放在“前面”
    node.fn(x,y)

    我们来“篡改”Node的原型,给Node的公有属性添加一个属性。
    把函数中的node全部替换为this,调用函数时默认把this当做第一个参数传入进来。

    Node.prototype.addClass = function(classes) { 
      classes.forEach((value)=>this.classList.add(value))
    }

    我们可以在Node.prototype中找到我们自己添加的属性。
    使用方法时:

    item3.addClass(['a','b','c'])
    //=== item3.addClass.call(item3,['a','b','c'])

    但是,我们这样操作是在改Node的公有属性。这样就有很大的麻烦:
    如果众多开发者都在改公有属性,那么既会相互覆盖,同时公有属性也失去了本身最为一个标准的意义。

    如何再进一步的改进?

    我们自己声明一个类似的"Node",名为:"jQuery"


    window.jQuery = function(node){ //传入node节点
      return {
                //key: value
                
                node.APIOne: function(){}
                node.APITwo: function(){}
                ...
                node.addClass: function(classes){
                   classes.forEach((value)=>node.classList.add(value))
                }
             }
    }

    我们现在是这样使用的

    var nodex = jQuery(item3) //首先根据节点获取一个新的对象
    nodex.addClass(['a','b']) //使用jQuery提供的新API来操作

    首先我们把参数item3传给了jQuery,然后jQuery将其存在了函数的node里面;这样我们使用其他方法时,可以直接通过操作nodex这个对象来操作节点。

    如果你传入的参数不是一个节点,而是代表CSS选择器的字符串呢?

    当你传入的不是一个id对应的节点,而是表示一个节点的选择器:

    var nodex = jQuery('#item3')

    让jQuery源码进行传参类型的判断:

    window.jQuery = function(nodeOrSelector){
      let node
      if(typeof nodeOrSelector === 'string'){
        node = document.querySelector(nodeOrSelector)
        //如果jQuery发现你输入了字符串,那么就回去寻找这个字符串对应的节点
      }else{
        node = nodeOrSelector
      }
      return
      ...
    }
    如果传入了多个元素节点

    当你传入一个选择多个元素节点的选择器时

    var nodex = jQuery('ul>li')
    var nodey = jQuery('ul li:nth-child(2)')

    将1~n个节点全部放在伪数组中。

    window.jQuery = function (nodeOrSelector) {
        //--------------------判断部分--------------------
        let nodes = {}
        if (typeof nodeOrSelector === 'string') {
            let temp = document.querySelectorAll(nodeOrSelector) //伪数组,但它的原型链还覆盖有很多层
            for (let i = 0; i < temp.length; i++) { //获取纯净的原型链,__proto__===Object.prototype
                nodes[i] = temp[i]
            }
            nodes.length = temp.length
        } else if (nodeOrSelector instanceof Node) { //说明传入的是一个节点,但是保持一致性也要变成伪数组
            nodes = {
                0: nodeOrSelector,
                length: 1
            }
        }
        
        //--------------------添加类方法--------------------
        nodes.addClass = function () { 
            let array = [] //传入多个字符串代表类,不用传入数组,内部将字符串组合为数组
            for (let k = 0; k < arguments.length; k++){
                array.push(arguments[k])
            }
            array.forEach((value) => {
                for (let i = 0; i < nodes.length; i++) { //nodes并不是元素,而是伪数组
                    nodes[i].classList.add(value)
                }
            })
        }
        
        //------------------设置节点文本方法------------------
        nodes.setText = function (text) { //设置传入参数的文本
            for (let i = 0; i < nodes.length; i++) {
                nodes[i].textContent = text
            }
        }
        
        //-------------------------------------------------
        return nodes
    }
    
    //** 我们同时引入$,通过$来声明jQuery对象:**
    
    window.$ = jQuery

    使用jQuery的两个API:

    var $div = $('div')
    var $li = $('ul>li')
    $div.addClass('active')
    $li.setText('We are the same')

    至此,可以看出,jQuery的本质就是一个JavaScript库。通过一个“进化”的过程来达到一个使用的标准。jQuery的API就是源代码中的属性对应的函数,而且jQuery的源码远比此复杂和繁琐,才得以实现如此强大的功能。

    我们可以允许在不完全透彻理解的情况下使用一个原理,就像我们引入外部的库一样。

    我们在此只是形象化的了解jQuery实现的基本原理。知根知底,才可以更透彻的学习和使用jQuery。


    Edit By: Eden Sheng
    EMail: [email protected]

    你可能感兴趣的:(命名空间,jquery,javascript)