jQuery的本质是什么?

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

jQuery的便利之处

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

实现jQuery的过程

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

首先我们来 封装函数

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

<body>
    <div>111</div>
    <div>222</div>
    <div>333</div>
    <ul>
        <li id="item1">select1</li>
        <li id="item2">select2</li>
        <li id="item3">select3</li>
    </ul>
</body>

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

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

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

//<li id="item3" class="a b">select3</li>
给予它们一个命名空间

上面的函数,虽然功能已经初步实现,但是有一个很大的问题:
我们在处理复杂页面时,为了方便使用和记忆,可能会声明很多名字和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]

03-05 15:36