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

40次阅读

共计 3752 个字符,预计需要花费 10 分钟才能阅读完成。

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: singlesaulwork@gmail.com

正文完
 0