JS 流行库(一):jQuery

JS 流行库(一):jQuery


jQuery 是一个简单、高效的 JavaScript 库,在此篇笔记中,原本记录了所有常用的 API 使用方法及其注意点,但由于一次误操作导致内容全部丢失,不过,我决定将计就计,不再记录常用 API 的使用方法(遗忘时可以查询文档),因此,此篇笔记的内容将包含如下内容:

  • jQuery 原理
  • Ajax
  • Cookie

学习任何一个库(API)最好的笔记必须是官方文档,以下是 API 速查表、官方文档的链接:


原理

此原理介绍参考的版本为 jQuery-1.10.1.js,虽然目前的最新版本为 jquery-3.6.1.js,但是 jquery 框架的架构的核心思想是相同的,并不会影响 jQuery 原理的学习,如果彻底理解 jquery-1.10.1 版本的思想,那么在看 jquery-3.6.1 版本时,不会懵逼,此外,在介绍原理的同时,也可以学习 jQuery API

基本结构

(function( window, undefined ) {
    var jQuery = function( ) {
        return new jQuery.prototype.init( );
    }
    jQuery.prototype = {
        constructor: jQuery,
        init: function( ) { }
    }
    jQuery.prototype.init.prototype = jQuery.prototype;
    window.jQuery = window.$ = jQuery;
})( window );

在上述示例中不难发现,jQuery 的本质是一个立即执行的匿名函数,使用立即执行的匿名函数可以避免同时引入若干框架时产生的冲突,在此匿名函数中,将 jQuery($) 绑定到了 window 上,从而实现外界访问函数内部定义

  • window 参数
    • 压缩代码
  • undefined 参数
    • 兼容性

在此匿名函数中,jQuery 是一个类,在此类的构造函数中调用了 jQuery 原型对象中的 init 方法,init 方法也是一个构造函数,此外,init 的原型对象即为 jQuery 类的原型对象

虽然 jQuery 是一个类,不过它的构造函数返回的实例对象本质上是由 init 构造函数 new 出来的实例对象,为了使此实例对象可以使用 jQuery 类原型中的方法,也就是看起来像一个 jQuery 类的实例对象,因此将 init 类的原型绑定为 jQuery 的原型对象

在调试 jQuery 代码时,我们经常会看到 jQuery.fn.init(在 jQuery 中 fn 为 prototype 的另外一个名称),也可以说明 jQuery 构造函数返回的其实是 jQuery 原型对象的 init 类的实例对象,而由于 init 的原型被绑定为 jQuery 的原型对象,所以 init 可以使用 jQuery 类原型的方法

工具方法

在 jQuery 中实现了某些工具方法,不仅可以提高代码的可读性,也可以弥补原生 JS 方法的不足,将工具方法写为 jQuery 的静态方法

isString
xQuery.isString = function (arg) {
    return typeof arg === "string";
}
isHTML
xQuery.isHTML = function (arg) {
    return arg.charAt(0) === "<" &&
        arg.charAt(arg.length - 1) === ">" &&
        arg.length >= 3;    // 标签以 `<` 开头、`>` 结尾,且长度至少为 3
}
trim
xQuery.trim = function (arg) {
    if (!isString(arg)) {   // 不是字符串
        return arg;
    }
    if (arg.trim) { // 浏览器支持 trim 方法
        return arg.trim();
    } else {
        return arg.replace(/^\s+|\s+$/, "");
    }
}
isObject
xQuery.isObject = function (arg) {
    return typeof arg === "object";
}
isWindow
xQuery.isWindow = function (arg) {
    return arg === window;
}
isArray
xQuery.isArray = function (arg) {
    return xQuery.isObject(arg) &&
        "length" in arg &&
        !xQuery.isWindow(arg);    // (伪)数组是一个对象、保存 length 属性,且不是 window 对象
}
isFunction
xQuery.isFunction = function (arg) {
    return typeof arg === "function";
}
isReady
xQuery.ready = function (arg) {
    if (document.readyState === "complete") {   // 页面加载完毕
        arg();
    } else if (document.addEventListener) {
        document.addEventListener("DOMContentLoaded", function () { // 监听页面加载完毕事件
            arg();
        })
    } else {
        document.attachEvent("onreadystatechange", function () {    // 监听页面加载状态事件
            if (document.readyState === "complete") {
                arg();
            }
        })
    }
}

在上述示例中,使用 DOMContentLoaded 事件监听页面加载完毕的事件,与 onload 事件的不同之处在于,DOMContentLoaded 事件在当前页面的所有 DOM 元素加载完毕后即可执行,而 onload 事件不仅等待 DOM 元素的加载,也必须等待所有资源下载完毕后才会执行,此外,使用 attachEvent 方法和 onreadystatechange 事件的目的在于兼容性

each
xQuery.each = function (arg, fn) {
    /* 数组 */
    if (xQuery.isArray(arg)) {
        for (var i = 0; i < arg.length; i++) {
            var res = fn.call(arg[i], i, arg[i]);

            if (res === true) {
                continue;
            } else if (res === false) {
                break;
            }
        }
    }

    /* 实例 */
    else if (xQuery.isObject(arg)) {
        for (var key in arg) {
            var res = fn.call(arg[key], key, arg[key]);

            if (res === true) {
                continue;
            } else if (res === false) {
                break;
            }
        }
    }

    return arg;    
}

在 jQuery 的 each 静态方法中,传入一个数组、伪数组或实例对象以及一个回调函数,在回调函数中可以传入 key 和 value,在回调函数中的 this 属性为数组、伪数组或实例对象的 value,在回调函数中返回 true 表示在循环中执行 continue 语句,返回 false 表示在循环中执行 break 语句,此外,each 静态方法的返回值即为传入的数组、伪数组或实例对象

map
xQuery.map = function (arg, fn) {
    var rtn = new Array();

    /* 数组 */
    if (xQuery.isArray(arg)) {
        for (var i = 0; i < arg.length; i++) {
            var res = fn(arg[i], i);

            if (res) {
                rtn.push(res);
            }
        }
    }

    /* 实例 */
    else if (xQuery.isObject(arg)) {
        for (var key in arg) {
            var res = fn(arg[key], key);

            if (res) {
                rtn.push(res);
            }
        }
    }

    return rtn;    
}

在 jQuery 的 map 静态方法中,传入一个数组、伪数组或实例对象以及一个回调函数,在回调函数中可以传入 value 和 key,在回调函数中如果存在返回值,那么将返回由若干回调函数返回值构成的数组,如果不存在返回值,即默认情况下,将返回一个空数组

getStyle
xQuery.getSyle = function (arg, styleName) {
    if (window.getComputedStyle) {
        return window.getComputedStyle(arg)[styleName];
    } else {
        return arg.currentStyle[styleName];
    }
}

####registerEvent

xQuery.registerEvent = function (arg, eventName, fn) {
    if (arg.addEventListener) {
        arg.addEventListener(eventName, fn);
    } else {
        arg.attachEvent("on" + eventName, fn);
    }
}

在 jQuery 中,使用如下所示的形式管理方法:

xQuery.extend = xQuery.prototype.extend = function (obj) {
    for (var key in obj) {
        this[key] = obj[key];
    }
}

xQuery.extend({
    isString: function (arg) {
        return typeof arg === "string";
    },

    isHTML: function (arg) {
        return arg.charAt(0) === "<" &&
            arg.charAt(arg.length - 1) === ">" &&
            arg.length >= 3;    // 标签以 `<` 开头,以 `>` 结尾,且长度至少为 3
    },

    trim: function (arg) {
        if (!xQuery.isString(arg)) {    // 不是字符串
            return arg;
        }
        if (arg.trim) { // 浏览器不支持 trim 方法
            return arg.trim();
        } else {
            return arg.replace(/^\s+|\s+$/, "");
        }
    },

    isObject: function (arg) {
        return typeof arg === "object";
    },

    isWindow: function (arg) {
        return arg === window;
    },

    isArray: function (arg) {
        return xQuery.isObject(arg) &&
            "length" in arg &&
            !xQuery.isWindow(arg);    // (伪)数组是一个对象、保存 length 属性,且不是 window 对象
    },

    isFunction: function (arg) {
        return typeof arg === "function";
    },

    ready: function (arg) {
        if (document.readyState === "complete") {   // 页面加载完毕
            arg();
        } else if (document.addEventListener) {
            document.addEventListener("DOMContentLoaded", function () { // 监听页面加载完毕事件
                arg();
            })
        } else {
            document.attachEvent("onreadystatechange", function () {    // 监听页面加载状态事件
                if (document.readyState === "complete") {
                    arg();
                }
            })
        }
    },

    each: function (arg, fn) {
        /* 数组 */
        if (xQuery.isArray(arg)) {
            for (var i = 0; i < arg.length; i++) {
                var res = fn.call(arg[i], i, arg[i]);

                if (res === true) {
                    continue;
                } else if (res === false) {
                    break;
                }
            }
        }

        /* 实例 */
        else if (xQuery.isObject(arg)) {
            for (var key in arg) {
                var res = fn.call(arg[key], key, arg[key]);

                if (res === true) {
                    continue;
                } else if (res === false) {
                    break;
                }
            }
        }

        return arg;
    },

    map: function (arg, fn) {
        var rtn = new Array();

        /* 数组 */
        if (xQuery.isArray(arg)) {
            for (var i = 0; i < arg.length; i++) {
                var res = fn(arg[i], i);

                if (res) {
                    rtn.push(res);
                }
            }
        }

        /* 实例 */
        else if (xQuery.isObject(arg)) {
            for (var key in arg) {
                var res = fn(arg[key], key);

                if (res) {
                    rtn.push(res);
                }
            }
        }

        return rtn;
    },

    getStyle: function (arg, styleName) {
        if (window.getComputedStyle) {
            return window.getComputedStyle(arg)[styleName];
        } else {
            return arg.currentStyle[styleName];
        }
    },

    registerEvent: function (arg, eventName, fn) {
        if (arg.addEventListener) {
            arg.addEventListener(eventName, fn);
        } else {
            arg.attachEvent("on" + eventName, fn);
        }
    }
})

在上述示例中,关注 extend 方法的声明与调用,在定义 extend 方法时,将 extend 同时定义为 xQuery 的静态方法与 xQuery 原型对象的方法,此外,在 extend 函数体中使用 for…in 循环遍历了参数 objs 的 key,并将 objs 的 value 保存到 this 相应的 key 中,在使用 xQuery 类调用此方法时,为 extend 传入的参数为若干个函数,函数名作为 key,函数体作为 value,而 extend 方法中的 this 为 xQuery,即类自身,如此一来,将参数 objs 保存的所有函数都作为 xQuery 类的静态方法,另外,当我们使用 xQuery 类的实例对象调用此 extend 方法时,使用的是 xQuery 类原型对象中的方法,此方法中的 this 为调用此方法实例对象,从而可以将 objs 中的函数都作为此实例对象的实例方法

入口函数

在 jQuery 的入口函数中,不同的数据有不同的数据处理方式:

  • ‘’、null、undefined、NaN、0、false
    • 空 jQuery 实例对象
  • 函数
    • 当页面内容加载完毕时,调用传入的回调
  • 字符串
    • 代码片段
      • 创建相应的 DOM 元素、存入 jQuery 实例对象中
    • 选择器
      • 查找所有相应的 DOM 元素、存入 jQuery 实例对象中
  • (伪)数组
    • 将数组中的元素依次存储到 jQuery 实例对象中
  • 任何对象、DOM 元素、基本数据类型
    • 将传入的数据存储到 jQuery 实例对象中

为了方便测试和避免冲突,将自己的 jQuery 命名为 xQuery

(function (window, undefined) {
    var xQuery = function (selector) {
        return new xQuery.prototype.init(selector);
    }
    xQuery.prototype = {
        constructor: xQuery,
        init: function (selector) { }
    }
    xQuery.prototype.init.prototype = xQuery.prototype;
    window.xQuery = window.$ = xQuery;
})(window);

在上述示例中,为入口函数添加了 selector 参数,用于传入数据,之后的代码都在 init 构造函数的函数体中

selector = xQuery.trim(selector);
!true
/* !true */
if (!selector) {
    return this;
}
函数
/* 函数 */
else if (xQuery.isFunction(selector)) {
    xQuery.ready(selector); // 当页面加载完毕后,在 ready 静态方法中调用 selector 回调函数
}
字符串
/* 字符串 */
else if (xQuery.isString(selector)) {
    /* 代码片段 */
    if (xQuery.isHTML(selector)) {
        var tmp = document.createElement("div");    // 创建一个容器
        tmp.innerHTML = selector;   // 将代码片段保存到一个容器中
        for (var i = 0; i < tmp.children.length; i++) { // 遍历容器中的子元素
            this[i] = tmp.children[i];  // 将容器中的每一个子元素保存到 jQuery 实例对象中
        }
        this.length = tmp.children.length;  // jQuery length
        return this;
    }
    /* 选择器 */
    else {
        var res = document.querySelectorAll(selector);
        for (var i = 0; i < res.length; i++) {
            this[i] = res[i];
        }
        this.length = res.length;
        return this;
    }
}

优化后的代码如下:

else if (xQuery.isString(selector)) {
    /* 代码片段 */
    if (xQuery.isHTML(selector)) {
        var tmp = document.createElement("div");    // 创建一个容器
        tmp.innerHTML = selector;   // 将代码片段保存到一个容器中
        [].push.apply(this, tmp.children);  // 将一个数组转换为一个伪数组
        return this;
    }
    /* 选择器 */
    else {
        var res = document.querySelectorAll(selector);
        [].push.apply(this, res);   // 将伪数组中的元素与 length 属性保存到 jQuery 实例对象中
        return this;
    }
}

在上述示例中,[].push.apply(this, tmp.children) 的含义是通过 apply 调用数组的 push 方法,将 push 方法中的 this 属性(原本 [])替换为 init 构造方法中的 this 属性(object/jQuery 实例对象),不过 init 构造函数中的 this 属性此时为 {}(通过 new Object() 创建),当 push 方法的 this 属性是一个对象时,将自动添加 length 属性,示例如下:

var obj = new Object();
[].push.apply(obj, [1, 3, 5]);
console.log(obj);   // { "0":1, "1":3, "2":5, "length":3 }
(伪)数组
/* 数组 */
else if (xQuery.isArray(selector)) {
    /* 数组 */
    if (({}).toArray.apply(selector) === "[object Array]") {
        [].push.apply(this, selector);
    }

    /* 伪数组 */
    else {
        var arr = [].slice.call(selector);
        [].push.apply(this, selector);
    }

    return this;
}

优化后的代码如下:

else if (xQuery.isArray(selector)) {
    var arr = [].slice.call(selector);  // 不论是数组,亦或是伪数组,均转换为数组
    [].push.apply(this, selector);  // 将数组转换为伪数组
    return this;
}
任何对象、DOM 元素、基本数据类型
/* 任何对象、DOM 元素、基本数据类型 */
else {
    this[0] = selector;
    this.length = 1;
    return this;
}

构造函数 init 的所有代码如下:

init: function (selector) {
    selector = xQuery.trim(selector);

    /* !true */
    if (!selector) {
        return this;
    }

    /* 字符串 */
    else if (xQuery.isString(selector)) {
        /* 代码片段 */
        if (xQuery.isHTML(selector)) { // 如果字符串以 `<` 开始,以 `>` 结尾且长度大于等于 3(最短长度)
            var tmp = document.createElement("div");    // 创建一个容器
            tmp.innerHTML = selector;   // 将代码片段保存到一个容器中

            // for (var i = 0; i < tmp.children.length; i++) { // 遍历容器中的子元素
            //     this[i] = tmp.children[i];  // 将容器中的每一个子元素保存到 jQuery 实例对象中
            // }
            // this.length = tmp.children.length;  // jQuery length

            [].push.apply(this, tmp.children);
        }

        /* 选择器 */
        else {
            var res = document.querySelectorAll(selector);

            // for (var i = 0; i < res.length; i++) {
            //     this[i] = res[i];
            // }
            // this.length = res.length;

            [].push.apply(this, res);
        }
    }

    /* (伪)数组 */
    else if (xQuery.isArray(selector)) {
        /* 数组 */
        // if (({}).toString.apply(selector) === "[object Array]") {
        //     [].push.apply(this, selector);
        // }

        /* 伪数组 */
        // else {
        //     var arr = [].slice.call(selector);
        //     [].push.apply(this, selector);
        // }

        var arr = [].slice.call(selector);
        [].push.apply(this, selector);
    }

    /* 任何对象、DOM 元素、基本数据类型 */
    else {
        this[0] = selector;
        this.length = 1;
    }

    return this;
}

原型

此小节说明 jQuery 中原型上的属性与方法

属性

以下所有的内容均写在 jQuery.prototype 原型对象中

xQuery.prototype = {
    // ...

    xquery: "1.1.0"
    selector: "",
    length: 0,
    push: [].push,
    sort: [].sort,
    splice: [].splice
}

说明如下:

  • jquery
    • 保存 jQuery 的版本
  • selector
    • jQuery 实例对象默认的选择器
  • length
    • jQuery 实例对象默认的长度
  • push
    • jQuery 实例对象的 push 方法
  • sort
    • jQuery 实例对象的 sort 方法
  • splice
    • jQuery 实例对象的 splice 方法

jQuery 实例对象的 push 方法的本质类似于 [].push.apply(this),即将数组 push 方法中的 this 属性替换为了 jQuery 实例对象,上述写法相当于将数组的 push 方法拷贝使用,在 jQuery 实例对象调用此 push 方法时,此 push 方法中的 this 属性必定为 jQuery 实例对象,此外 sort 方法和 splice 方法类似,不再赘述

方法
xQuery.prototype = {
    // ...

    toArray: function () {
        return [].slice.call(this); // 将伪数组转换为数组返回
    },

    get: function (index) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            return this.toArray();
        }

        /* 参数 >= 0 */
        else if (index >= 0) {
            return this[index];
        }

        /* 参数 < 0 */

        else {
            return this[this.length + index];
        }
    },

    eq: function (index) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            return new xQuery();
        }

        /* 传入参数 */
        else {
            return new xQuery(this.get(index));
        }
    },

    first: function () {
        return this.eq(0);
    },

    last: function () {
        return this.eq(-1);
    },

    each: function (fn) {
        return xQuery.each(this, fn);
    },

    map: function (fn) {
        return xQuery.map(this, fn);
    }
}

说明如下:

  • toArray
    • 将 jQuery 实例对象转换为一个数组返回
  • get
    • 不传入参数时,类似于 toArray
    • 传入大于零的参数时,将相应下标的取值以原生 DOM 的形式返回
    • 传入小于零的参数时,将数组长度与参数相加后得到的相应下标的取值以原生 DOM 的形式返回
  • eq
    • 不传入参数时,将一个空 jQuery 对象返回
    • 传入大于零的参数时,将相应下标的取值以 jQuery 实例的形式返回
    • 传入小于零的参数时,将数组长度与参数相加后得到的相应下标的取值以 jQuery 实例的形式返回
  • first
    • 将 jQuery 实例对象的第一个元素以 jQuery 实例的形式返回
  • last
    • 将 jQuery 实例对象的最后一个元素以 jQuery 实例的形式返回
  • each
    • 遍历 jQuery 实例对象中的所有元素(详情参考工具方法)
  • map
    • 遍历 jQuery 实例对象中的所有元素(详情参考工具方法)

DOM

此小结说明 jQuery 中 DOM 相关的方法

xQuery.prototype.extend({   // 由原型对象调用 extend 方法,将参数中所有的方法保存到实例中
    empty: function () {
        this.each(function (ci, cv) {   // 遍历 xQuery 实例中的所有元素
            cv.innerHTML = "";  // 删除所有内容
        })
        return this;    // 返回调用此方法的 xQuery 实例
    },

    remove: function (arg) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            this.each(function (ci, cv) {   // 遍历 xQuery 实例中的所有元素
                var pnt = cv.parentNode;    // 保存元素的父节点
                pnt.removeChild(cv);    // 通过父节点的 removeChild 方法删除子元素
            });
        }

        /* 传入一个参数 */
        else {
            this.each(function (ci, cv) {   // 遍历 xQuery 实例中的所有元素
                var targetObj = cv; // 保存元素
                $(arg).each(function (ci, cv) { // 遍历 xQuery 实例中的所有元素(参数)
                    if (cv === targetObj) { // 如果此元素与调用此方法的实例中的元素相同
                        cv.remove();    // 移除此元素
                    }
                });
            });
        }

        return this;    // 返回调用此方法的 xQuery 实例
    },

    html: function (arg) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            return this.get(0).innerHTML;
        }

        /* 传入参数 */
        else {
            this.each(function (ci, cv) {
                cv.innerHTML = arg;
            });
        }
    },

    text: function (arg) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            var rtnStr = "";    // 空字符串

            this.each(function (ci, cv) {   // 遍历 xQuery 实例的所有元素
                rtnStr += cv.innerText; // 将每一个元素的 innerText 保存到 rtnStr 中
            })

            return rtnStr;
        }

        /* 传入参数 */
        else {
            this.each(function (ci, cv) {   // 遍历 xQuery 实例的所有元素
                cv.innerText = arg; // 将每一个元素的 innerText 更改为传入的参数
            });
        }
    },

    appendTo: function (arg) {
        var xQueryObjs = $(arg);    // 将所有类型的参数都转换为 xQuery 实例
        var rtn = new Array();
        xQueryObjs.each((outerCI, outerCV) => { // 遍历 xQuery 实例(参数)中保存的所有元素
            this.each((innerCI, innerCV) => {   // 遍历调用此方法的 xQuery 实例中保存的所有元素
                if (outerCI === 0) {    // 如果是第一个元素
                    outerCV.appendChild(innerCV);   // 将调用此方法的 xQuery 实例中保存的元素添加到此元素的末尾
                    rtn.push(innerCV);  // 保存元素
                } else {    // 如果不是第一个元素
                    var tmp = innerCV.cloneNode(true);  // 克隆调用此方法的 xQuery 实例中保存的元素
                    outerCV.appendChild(tmp);   // 将副本添加到此元素的末尾
                    rtn.push(tmp);  // 保存元素副本
                }
            });
        });
        return rtn;
    },

    prependTo: function (arg) {
        var xQueryObjs = $(arg);    // 将所有类型的参数都转换为 xQuery 实例
        var rtn = new Array();
        xQueryObjs.each((outerCI, outerCV) => { // 遍历 xQuery 实例(参数)中保存的所有元素
            this.each((innerCI, innerCV) => {   // 遍历调用此方法的 xQuery 实例中保存的所有元素
                if (outerCI === 0) {    // 如果是第一个元素
                    outerCV.insertBefore(innerCV, outerCV.firstChild);  // 将调用此方法的 xQuery 实例中保存的元素添加到此元素的开头
                    rtn.push(innerCV);  // 保存元素
                } else {    // 如果不是第一个元素
                    var tmp = innerCV.cloneNode(true);  // 克隆调用此方法的 xQuery 实例中保存的元素
                    outerCV.insertBefore(tmp, outerCV.firstChild);  // 将副本添加到此元素的开头
                    rtn.push(tmp);  // 保存元素副本
                }
            });
        });
        return rtn;
    },

    append: function (arg) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            this.get(0).innerHTML += arg;
        }

        /* 非字符串 */
        else {
            $(arg).appendTo(this);
        }

        return this;
    },

    prepend: function (arg) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            this.get(0).innerHTML = arg + this.get(0).innerHTML;
        }

        /* 非字符串 */
        else {
            $(arg).prependTo(this);
        }

        return this;
    },

    insertAfter: function (arg) {
        var xQueryObjs = $(arg);    // 将所有类型的参数都转换为 xQuery 实例
        var rtn = new Array();
        xQueryObjs.each((outerCI, outerCV) => { // 遍历 xQuery 实例(参数)中保存的所有元素
            this.each((innerCI, innerCV) => {   // 遍历调用此方法的 xQuery 实例中保存的所有元素
                if (outerCI === 0) {    // 如果是第一个元素
                    if (outerCV.nextElementSibling) {  // 如果元素存在后一个相邻的节点
                        outerCV.parentNode.insertBefore(innerCV, outerCV.nextElementSibling);  // 将元素添加到此相邻节点之前
                    } else {    // 如果元素是最后一个节点
                        outerCV.parentNode.appendChild(innerCV);    // 将元素添加到此节点之后
                    }
                    rtn.push(innerCV);  // 保存元素
                } else {    // 如果不是第一个元素
                    var tmp = innerCV.cloneNode(true);  // 克隆调用此方法的 xQuery 实例中保存的元素
                    if (outerCV.nextElementSibling) {
                        outerCV.parentNode.insertBefore(tmp, outerCV.nextElementSibling);
                    } else {
                        outerCV.parentNode.appendChild(tmp);
                    }
                    rtn.push(tmp);  // 保存元素副本
                }
            });
        });
        return rtn;
    },

    after: function (arg) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            /* 代码片段 */
            if (xQuery.isHTML(arg)) {
                $(arg).insertAfter(this);
            }

            /* 字符串 */
            else {
                this.get(0).nextSibling.nodeValue = arg;
            }
        }

        /* 非字符串 */
        else {
            $(arg).insertAfter(this);
        }

        return this;
    },

    insertBefore: function (arg) {
        var xQueryObjs = $(arg);    // 将所有类型的参数都转换为 xQuery 实例
        var rtn = new Array();
        xQueryObjs.each((outerCI, outerCV) => { // 遍历 xQuery 实例(参数)中保存的所有元素
            this.each((innerCI, innerCV) => {   // 遍历调用此方法的 xQuery 实例中保存的所有元素
                if (outerCI === 0) {    // 如果是第一个元素
                    outerCV.parentNode.insertBefore(innerCV, outerCV);  // 将元素添加到此元素之前
                    rtn.push(innerCV);  // 保存元素
                } else {    // 如果不是第一个元素
                    var tmp = innerCV.cloneNode(true);  // 克隆调用此方法的 xQuery 实例中保存的元素
                    outerCV.parentNode.insertBefore(tmp, outerCV);
                    rtn.push(tmp);  // 保存元素副本
                }
            });
        });
        return rtn;
    },

    before: function (arg) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            /* 代码片段 */
            if (xQuery.isHTML(arg)) {
                $(arg).insertBefore(this);
            }

            /* 字符串 */
            else {
                this.get(0).previousSibling.nodeValue = arg;
            }
        }

        /* 非字符串 */
        else {
            $(arg).insertBefore(this);
        }

        return this;
    },

    next: function (arg) {
        var rtnArr = new Array();

        /* 没有传入参数 */
        if (arguments.length === 0) {
            this.each(function (ci, cv) {   // 遍历调用此方法的 xQuery 实例保存的所有元素
                if (cv.nextElementSibling) {    // 如果元素相邻的后一个同级元素存在
                    rtnArr.push(cv.nextElementSibling); // 保存元素
                }
            });
        }

        /* 传入一个参数 */
        else {
            this.each(function (outerCI, outerCV) { // 遍历调用此方法的 xQuery 实例保存的所有元素
                var targetObj = outerCV.nextElementSibling; // 保存元素相邻的前一个同级元素
                $(arg).each(function (innerCI, innerCV) {   // 遍历参数(选择器)选中的所有元素
                    if (innerCV === targetObj) {    // 如果元素相邻的前一个同级元素存在,且可以被选中
                        rtnArr.push(targetObj); // 保存元素
                    }
                });
            });
        }

        return rtnArr;
    },

    prev: function (arg) {
        var rtnArr = new Array();

        /* 没有传入参数 */
        if (arguments.length === 0) {
            this.each(function (ci, cv) {   // 遍历调用此方法的 xQuery 实例保存的所有元素
                if (cv.previousElementSibling) {    // 如果元素相邻的前一个同级元素存在
                    rtnArr.push(cv.previousElementSibling); // // 保存元素
                }
            });
        }

        /* 传入一个参数 */
        else {
            this.each(function (outerCI, outerCV) { // 遍历调用此方法的 xQuery 实例保存的所有元素
                var targetObj = outerCV.previousElementSibling; // 保存元素相邻的前一个同级元素
                $(arg).each(function (innerCI, innerCV) {   // 遍历参数(选择器)选中的所有元素
                    if (innerCV === targetObj) {    // 如果元素相邻的前一个同级元素存在,且可以被选中
                        rtnArr.push(targetObj); // 保存元素
                    }
                });
            });
        }

        return rtnArr;
    },

    replaceAll: function (arg) {
        var rtnArr = new Array();

        $(arg).each((outerCI, outerCV) => { // 遍历参数(选择器)选中的所有将被替换的 DOM 元素
            var outerCVParent = outerCV.parentNode;
            this.each((innerCI, innerCV) => {
                if (outerCI === 0) { // 如果是第一个元素
                    outerCVParent.insertBefore(innerCV, outerCV);   // 在将被替换的 DOM 元素之前添加替换元素
                    outerCV.remove();   // 移除将被替换的 DOM 元素
                    rtnArr.push(innerCV);   // 保存元素
                } else {    // 如果不是第一个元素
                    var tmp = innerCV.cloneNode(true);  // 克隆元素
                    outerCVParent.insertBefore(tmp, outerCV);   // 在将被替换的 DOM 元素之前添加替换元素的副本
                    outerCV.remove();   // 移除将被替换的 DOM 元素
                    rtnArr.push(tmp);   // 保存元素
                }
            })
        });

        return rtnArr;
    },

    replaceWith: function (arg) {
        $(arg).replaceAll(this);

        return this;
    },

    clone: function (arg = false) {
        var rtnArr = new Array();

        if (arg) {
            this.each(function (ci, cv) {
                var tmp = cv.cloneNode(true);
                xQuery.each(cv.eventCache, function (eventName, eventArray) {
                    xQuery.each(eventArray, function (i, fn) {
                        $(tmp).on(eventName, fn);
                    })
                })
                rtnArr.push(tmp);
            })

        } else {
            this.each(function (ci, cv) {
                var tmp = cv.cloneNode(true);
                rtnArr.push(tmp);
            })
        }

        return $(rtnArr);
    }
})

说明如下:

  • empty
    • 删除特定元素的所有内容,返回调用此方法的实例(链式编程)
  • remove
    • 如果没有传入参数,则删除调用此方法的 jQuery 实例中保存的元素
    • 如果传入一个参数(选择器),则删除调用此方法的 jQuery 实例中保存的元素,此元素可以被参数(选择器)选中
  • html
    • 如果没有传入参数,将返回调用此方法的 jQuery 实例中第一个元素的内容
    • 如果传入参数,则将调用此方法的 jQuery 实例中所有的元素的内容更改为参数内容(解析标签)
  • text
    • 如果没有传入参数,将返回调用此方法的 jQuery 实例中所有元素的内容
    • 如果传入参数,则将调用此方法的 jQuery 实例中的所有元素的内容更改为参数内容(不解析标签)
  • appendTo
    • 将调用此方法的 jQuery 实例中保存的所有元素添加到参数选中的所有元素的内容的末尾
    • 如果参数选中的所有元素存在若干个,将保存元素调用此方法的 jQuery 实例中所保存的元素的拷贝
    • 参数类型如下:
      • 选择器
      • jQuery 实例
      • DOM 元素
    • 返回由所有被添加的元素所构成一个数组
  • prependTo
    • 将调用此方法的 jQuery 实例中保存的所有元素添加到参数选中的所有元素的内容的开头
    • 如果参数选中的所有元素存在若干个,将保存元素调用此方法的 jQuery 实例中所保存的元素的拷贝
    • 参数类型如下:
      • 选择器
      • jQuery 实例
      • DOM 元素
    • 返回由所有被添加的元素所构成一个数组
  • append
    • 将参数选中的所有元素添加到调用此方法的 jQuery 实例中保存的所有元素的内容的末尾
    • 参数类型:
      • 字符串(解析标签)
      • jQuery 实例
      • DOM 元素
    • 返回调用此方法的 jQuery 实例
  • prepend
    • 将参数选中的所有元素添加到调用此方法的 jQuery 实例中保存的所有元素的内容的开头
    • 参数类型:
      • 字符串(解析标签)
      • jQuery 实例
      • DOM 元素
    • 返回调用此方法的 jQuery 实例
  • insertAfter
    • 将调用此方法的 jQuery 实例中保存的所有元素添加到参数选中的所有元素的末尾
    • 如果参数选中的所有元素存在若干个,将保存元素调用此方法的 jQuery 实例中所保存的元素的拷贝
    • 参数类型如下:
      • 选择器
      • jQuery 实例
      • DOM 元素
    • 返回由所有被添加的元素所构成一个数组
  • after
    • 将参数选中的所有元素添加到调用此方法的 jQuery 实例中保存的所有元素的末尾
    • 参数类型:
      • 字符串(解析标签)
      • jQuery 实例
      • DOM 元素
    • 返回调用此方法的 jQuery 实例
  • insertBefore
    • 将调用此方法的 jQuery 实例中保存的所有元素添加到参数选中的所有元素的开头
    • 如果参数选中的所有元素存在若干个,将保存元素调用此方法的 jQuery 实例中所保存的元素的拷贝
    • 参数类型如下:
      • 选择器
      • jQuery 实例
      • DOM 元素
    • 返回由所有被添加的元素所构成一个数组
  • before
    • 将参数选中的所有元素添加到调用此方法的 jQuery 实例中保存的所有元素的开头
    • 参数类型:
      • 字符串(解析标签)
      • jQuery 实例
      • DOM 元素
    • 返回调用此方法的 jQuery 实例
  • next
    • 没有传入参数,将调用此方法的 jQuery 实例中保存的所有 DOM 元素的后一个的相邻的同级元素以数组的形式返回
    • 传入一个参数(选择器),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的后一个的相邻的同级元素以数组的形式返回,元素必须可以被参数(选择器)选中(数组)
  • prev
    • 没有传入参数,将调用此方法的 jQuery 实例中保存的所有 DOM 元素的前一个的相邻的同级元素以数组的形式返回
    • 传入一个参数(选择器),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的前一个的相邻的同级元素以数组的形式返回,元素必须可以被参数(选择器)选中(数组)
  • replaceAll
    • 将调用此方法的 jQuery 实例中保存的所有 DOM 元素替换由参数选中的所有元素
  • replaceWith
    • 将调用此方法的 jQuery 实例中保存的所有 DOM 元素替换为由参数选中的所有元素
  • clone
    • 将调用此方法的 jQuery 实例中保存的所有 DOM 元素拷贝,将所有拷贝元素的副本以 jQuery 实例的形式返回,此方法可以传入一个布尔值,如果为 true,则不仅拷贝元素,也拷贝元素事件,如果为 false,则仅拷贝元素,不拷贝元素事件,如果不传入参数,那么默认值为 false

属性

xQuery.prototype.extend({
    attr: function (arg, value) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            /* 一个参数 */
            if (arguments.length === 1) {
                return this.get(0).getAttribute(arg);
            }

            /* 两个参数 */
            else {
                this.each(function (ci, cv) {
                    cv.setAttribute(arg, value);
                });
            }
        }

        /* 实例 */
        else if (xQuery.isObject(arg)) {
            this.each((outerCI, outerCV) => {
                xQuery.each(arg, (innerCI, innerCV) => {
                    outerCV.setAttribute(innerCI, innerCV);
                });
            });
        }

        return this;
    },

    prop: function (arg, value) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            /* 一个参数 */
            if (arguments.length === 1) {
                return this.get(0)[arg];
            }

            /* 两个参数 */
            else {
                this.each(function (ci, cv) {
                    cv[arg] = value;
                });
            }
        }

        /* 实例 */
        else if (xQuery.isObject(arg)) {
            this.each((outerCI, outerCV) => {
                xQuery.each(arg, (innerCI, innerCV) => {
                    outerCV[innerCI] = innerCV;
                });
            });
        }

        return this;
    },

    css: function (arg, value) {
        /* 字符串 */
        if (xQuery.isString(arg)) {
            /* 一个参数 */
            if (arguments.length === 1) {
                return xQuery.getStyle(this.get(0), arg);
            }

            /* 两个参数 */
            else {
                this.each(function (ci, cv) {
                    cv.style[arg] = value;
                });
            }
        }

        /* 实例 */
        else if (xQuery.isObject(arg)) {
            this.each((outerCI, outerCV) => {
                xQuery.each(arg, (innerCI, innerCV) => {
                    outerCV.style[innerCI] = innerCV;
                });
            });
        }

        return this;
    },

    val: function (arg) {
        /* 未传入参数 */
        if (arguments.length == 0) {
            return this.get(0).value;
        }
        /* 传入一个参数 */
        else {
            this.each(function (ci, cv) {
                cv.value = arg;
            });
            return this;
        }
    },

    hasClass: function (arg) {
        var isExist = false;
        /* 未传入参数 */
        if (arguments.length === 0) {
            return isExist;
        }
        /* 传入一个参数 */
        else {
            this.each(function (ci, cv) {
                var elementClassName = " " + cv.className + " ";    // 为调用此方法的 jQuery 实例中保存的 DOM 元素的类名添加空格
                var targetClassName = " " + arg + " ";  // 为待检测的类名添加空格
                if (elementClassName.indexOf(targetClassName) !== -1) { // 如果调用此方法的 jQuery 实例中保存的 DOM 元素的类名包含待检测的类名
                    isExist = true;
                    return false;   // break
                }
            });
            return isExist;
        }
    },

    addClass: function (arg) {
        if (arguments.length !== 0) {
            var classNames = arg.split(" ");    // 将类名以空格为分隔符拆分为若干个字符串
            xQuery.each(classNames, (outerCI, outerCV) => {
                this.each((innerCI, innerCV) => {
                    if (!$(innerCV).hasClass(outerCV)) {    // 如果不包含待检测的类名
                        innerCV.className = innerCV.className + " " + outerCV;
                        innerCV.className = xQuery.trim(innerCV.className);
                    }
                });
            });
        }

        return this;
    },

    removeClass: function (arg) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            this.each(function (ci, cv) {
                cv.className = "";
            });
        }

        /* 传入参数 */
        else {
            var classNames = arg.split(" ");    // 将类名以空格为分隔符拆分为若干个字符串
            xQuery.each(classNames, (outerCI, outerCV) => {
                this.each((innerCI, innerCV) => {
                    if ($(innerCV).hasClass(outerCV)) { // 如果不包含待检测的类名
                        innerCV.className = (" " + innerCV.className + " ").replace(" " + outerCV + " ", " ");
                        innerCV.className = xQuery.trim(innerCV.className);
                    }
                });
            });
        }

        return this;
    },

    toggleClass: function (arg) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            this.removeClass();
        }

        /* 传入参数 */
        else {
            var classNames = arg.split(" ");    // 将类名以空格为分隔符拆分为若干个字符串
            xQuery.each(classNames, (outerCI, outerCV) => {
                this.each((innerCI, innerCV) => {
                    if ($(innerCV).hasClass(outerCV)) { // 如果包含待检测的类名
                        $(innerCV).removeClass(outerCV);
                    } else {    // 如果不包含待检测的类名
                        $(innerCV).addClass(outerCV);
                    }
                });
            });
        }

        return this;
    }
})

说明如下:

  • attr
    • 传入一个参数(属性节点),将调用此方法的 jQuery 实例中保存的第一个 DOM 元素的属性节点相应的属性值返回
    • 传入两个参数(属性节点以及属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的属性节点替换为特定的属性值,并将调用此方法的 jQuery 实例返回
    • 传入一个对象(若干属性节点以及相应的属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的所有特定属性节点替换为特定的属性值,并将调用此方法的 jQuery 实例返回
  • prop
    • 传入一个参数(属性),将调用此方法的 jQuery 实例中保存的第一个 DOM 元素的属性相应的属性值返回
    • 传入两个参数(属性以及属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的属性替换为特定的属性值,并将调用此方法的 jQuery 实例返回
    • 传入一个对象(若干属性以及相应的属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的所有特定属性替换为特定的属性值,并将调用此方法的 jQuery 实例返回
  • css
    • 传入一个参数(样式属性),将调用此方法的 jQuery 实例中保存的第一个 DOM 元素的样式属性相应的属性值返回
    • 传入两个参数(样式属性以及属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的样式属性替换为特定的属性值,并将调用此方法的 jQuery 实例返回
    • 传入一个对象(若干样式属性以及相应的属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的所有特定样式属性替换为特定的属性值,并将调用此方法的 jQuery 实例返回
  • val
    • 未传入参数,将调用此方法的 jQuery 实例中保存的第一个元素的 value 属性值返回
    • 传入一个参数(属性值),将调用此方法的 jQuery 实例中保存的所有 DOM 元素的 value 属性设置为特定的值
  • hasClass
    • 未传入参数,返回 false
    • 传入一个参数(类名),将判断调用此方法的 jQuery 实例中保存的所有 DOM 元素是存在特定的类,如果有一个 DOM 元素包含则返回 true,否则返回 false
  • addClass
    • 未传入参数,返回调用此方法的 jQuery 实例
    • 传入一个参数(一个或多个以空格间隔的类名),将类添加到调用此方法的 jQuery 实例中的所有 DOM 元素,如果 DOM 元素已经包含此类,则不会添加,返回调用此方法的 jQuery 实例
  • removeClass
    • 未传入参数,将调用此方法的 jQuery 实例中保存的所有 DOM 元素的所有类删除,返回调用此方法的 jQuery 实例
    • 传入一个参数(一个或多个以空格间隔的类名),将类从调用此方法的 jQuery 实例中的所有 DOM 元素中删除,如果 DOM 元素不包含此类,则不会删除,返回调用此方法的 jQuery 实例
  • toggleClass
    • 未传入参数,将调用此方法的 jQuery 实例中保存的所有 DOM 元素的所有类删除,返回调用此方法的 jQuery 实例
    • 传入一个参数(一个或多个以空格间隔的类名),将类添加到调用此方法的 jQuery 实例中的所有 DOM 元素 / 或将类从调用此方法的 jQuery 实例中的所有 DOM 元素中删除,如果 DOM 元素已经包含此类,则不会添加,如果 DOM 元素不包含此类,则不会删除

属性与属性节点的不同之处在于,属性是一个 DOM 实例的属性,而属性节点保存在 DOM 实例的属性 attributes 中,attributes 属性是一个实例,此实例的属性即为属性节点,在 jQuery 中,方法 attr 通常用于获取属性节点、方法 prop 通常用于获取属性

事件

xQuery.prototype.extend({
    on: function (eventName, fn) {
        this.each(function (outerCI, outerCV) {
            if (!outerCV.eventCache) {   // 如果调用此方法的 xQuery 实例中的 DOM 元素没有 eventCache 属性(实例)
                outerCV.eventCache = new Object();  // 创建 eventCache 属性(实例)
            }
            if (!outerCV.eventCache[eventName]) {    // 如果调用此方法的 xQuery 实例中的 DOM 元素的 eventCache 属性(实例)没有相应事件名称的属性(数组)
                outerCV.eventCache[eventName] = new Array(); // 为 eventCache 属性(实例)创建一个 相应事件名称的属性(数组)
                outerCV.eventCache[eventName].push(fn);  // 将相应事件名称的回调函数保存到相应事件名称的数组中
                xQuery.registerEvent(outerCV, eventName, function () {   // 为调用此方法的 xQuery 实例中的 DOM 元素注册事件并绑定回调函数
                    xQuery.each(outerCV.eventCache[eventName], function (innerCI, innerCV) {   // 回调函数中遍历相应事件名称的数组
                        innerCV.call(outerCV);   // 执行函数
                    })
                });
            } else {    // 如果调用此方法的 xQuery 实例中的 DOM 元素的 eventCache 属性(实例)有相应事件名称的属性(数组)
                outerCV.eventCache[eventName].push(fn);  // 将相应事件名称的回调函数保存到相应事件名称的数组中
            }
        });
    },

    off: function (eventName, fn) {
        /* 未传入参数 */
        if (arguments.length === 0) {
            this.each(function (ci, cv) {
                cv.eventCache = new Object();
            });
        }

        /* 传入一个参数 */
        else if (arguments.length === 1) {
            this.each(function (ci, cv) {
                cv.eventCache[eventName] = new Array();
            });
        }

        /* 传入两个参数 */
        else if (arguments.length === 2) {
            this.each(function (outerCI, outerCV) {
                xQuery.each(outerCV.eventCache[eventName], function (innerCI, innerCV) {
                    if (innerCV === fn) {
                        outerCV.eventCache[eventName].splice(innerCI, 1);
                    }
                })
            });
        }
    }
})

说明如下:

  • on
    • 为调用此方法的 jQuery 实例中的所有 DOM 元素注册特定的事件,并为此事件绑定回调函数
  • off
    • 未传入参数,将删除调用此方法的 jQuery 实例中的所有 DOM 元素的所有事件
    • 传入一个参数(事件名称),将删除调用此方法的 jQuery 实例中的所有 DOM 元素的特定事件
    • 传入两个参数(事件名称以及相应的回调函数名称),将删除调用此方法的 jQuery 实例中的所有 DOM 元素的特定事件的特定回调函数

补充内容

(伪)数组

在 jQuery 中,伪数组分为两类:

  • 系统(示例:使用 querySelectorAll 方法查询 DOM 元素)
  • 自定义
数组 >> 伪数组
let arr = [1, 3, 5];
let obj = new Object();
[].push.apply(obj, arr);
console.log(obj);   // { "0":1, "1":3, "2":5, "length":3 }
伪数组 >> 数组

不论是将伪数组转换为数组还是另一个伪数组,必须先将伪数组转换为数组

let oDivs = document.querySelectorAll("div");
let obj = {
    0: 1,
    1: 3,
    2: 5,
    length: 2
};

let divArr = [].slice.call(oDivs);
console.log(divArr);    // [{}, {}, {}]

let objArr = [].slice.call(obj);
console.log(objArr);    // [1, 3, 5]

在上述示例中,将伪数组转换为数组时,必须使用数组的 slice.call 方法,而不能使用 push.apply 方法(兼容性问题),当某个数组调用 slice 方法时,如果不传入参数,那么将创建一个新数组,将调用此函数的数组的值拷贝到新建数组中并将新数组返回

总结

学习 jQuery 源代码的过程如下:

  1. 研究框架的运行原理
  2. 研究框架的模块组织
  3. 研究框架的模块内容
  4. 研究框架的功能实现

Ajax

Ajax 用于在浏览器和服务器之间传送信息,在开始之前,我们必须先搭建 Web 服务器

Web 服务器

  • Web 服务相关软件
    • Apache
    • IIS
    • Tomcat
    • Nginx
    • NodeJS

此处我们使用 WAMPServer 软件,在 Windows 上使用,集成了 Apache、MySQL 以及 PHP,在启动软件之后,在浏览器中访问 IP 地址 127.0.0.1 即可

PHP 是一个类似于 JavaScript 的脚本语言,由于语法及其相似,故不赘述,示例如下:

<?php
$name = "Reyn Morales";
echo $name;

echo "<br>";

$a = 10;
$b = 20;
$res = $a + $b;
echo $res;

echo "<br>";

$arr = array(1, 3, 5, 7, 9);    // 数组
print_r($arr);
echo $arr[2];
$arr[2] = 11;
echo $arr[2];
print_r($arr);

echo "<br>";

$dict = array(  // 字典
    "name" => "Reyn Morales",
    "age" => 21
);
print_r($dict);
echo $dict["age"];
$dict["age"] = 23;
echo $dict["age"];
print_r($dict);
?>

在上述示例中,说明了 PHP 与 JavaScript 在语法上的不同之处,未说明的内容(分支、循环)等语法与 JavaScript 完全相同

GET

在 HTML 中的 form 标签中,可以使用 action 属性指定接收数据的服务器地址,可以使用 method 属性指定数据的请求方式,将 method 属性值设置为 get 即为 GET 请求方式,示例如下:

<form action="./get.php" method="get">
    NAME: <input type="text" name="userName">
    <br>
    PASSWORD: <input type="password" name="userPassword">
    <br>
    <input type="submit" value="SUBMIT">
</form>

在上述示例中,必须为 type 属性为 text 和 password 的 input 标签指定 name 属性,否则无法提交

在 PHP 中,预定义的 $_GET 变量用于收集来自 method=“get” 的表单中的值,示例如下:

<?php
print_r($_GET);
?>

运行结果如下:

Array ( [userName] => Reyn Morales [userPassword] => 123456 )

此时浏览器的 URL 栏中如下:

http://127.0.0.1/jquery/get.php?userName=Reyn+Morales&userPassword=123456

原理如下:

  • 用户在浏览器中访问网页
  • 浏览器向服务器请求资源(请求报文)
  • 服务器响应浏览器请求
  • 服务器将网页资源返回(响应报文)
  • 浏览器渲染 HTML 文件
  • 用户填写表单并提交
  • 浏览器向服务器请求结果(请求报文)
  • 服务器响应浏览器请求
  • 服务器将计算结果返回(响应报文)
  • 浏览器渲染计算结果

POST

在 HTML 的 form 标签中,将 method 属性的属性值设置为 post 即为 POST 请求方式,示例如下:

<form action="./post.php" method="get">
    NAME: <input type="text" name="userName">
    <br>
    PASSWORD: <input type="password" name="userPassword">
    <br>
    <input type="submit" value="SUBMIT">
</form>

在 PHP 中,预定义的 $_POST 变量用于收集来自 method=“post” 的表单中的值,示例如下:

<?php
print_r($_POST);
?>

运行结果如下:

Array ( [userName] => Reyn Morales [userPassword] => 123456 )

此时浏览器的 URL 栏中如下:

http://127.0.0.1/jquery/post.php

GET vs POST

相同
  • 功能
    • 将数据发送到远程服务器
不同
  • 存储位置
    • GET 将数据保存到浏览器的 URL 地址栏中
    • POST 将数据保存到浏览器的请求报文中
  • 数据容量
    • GET 请求方式的数据存在大小限制(最多 2000 个字符)
    • POST 请求方式的数据大小无限制
  • 应用场景
    • GET
      • 非敏感数据
      • 数据量少
    • POSt
      • 敏感数据
      • 数据量多

上传文件

在 HTML 中可以使用 type 属性为 file 的 input 标签上传文件,示例如下:

<form action="./upFile.php" method="post" enctype="multipart/form-data">
    <input type="file" name="upFile">
    <br>
    <input type="submit" value="SUBMIT">
</form>

在上述示例中,由于文件大小未知,使用 post 请求方式,此外,在 form 表单中,使用了 enctype 属性指定上传的文件类型,具体说明如下:

enctype文件类型
multipart/form-data浏览器请求前不编码字符,在使用包含文件上传的 input 时,必须使用该值
text/plain将空格转换为 + 符号,不编码特殊字符
application/x-www-form-urlencoded浏览器请求前将编码所有字符(默认)

通过使用 PHP 的全局数组 $_FILES,你可以从客户计算机向远程服务器上传文件,示例如下:

<?php
print_r($_FILES);
?>

运行结果如下:

Array ( [upFile] => Array ( [name] => 开发工具.jpg [type] => image/jpeg [tmp_name] => D:\wamp64\tmp\php593.tmp [error] => 0 [size] => 30194 ) )

不难发现,在响应结果中保存了上传文件的文件名、文件类型、绝对路径和文件大小,由于服务器自动将文件保存到了 tmp 目录下(在此目录下的文件并不会永久保存,一定时间后自动删除),因此必须将文件转移到其它目录下,示例如下:

<?php
$fileInfo = $_FILES["upFile"];

$fileName = $fileInfo["name"];
$filePath = $fileInfo["tmp_name"];

move_uploaded_file($filePath, "./source/".$fileName);
?>

在上述示例中,使用 move_uploaded_file 方法将文件移动到指定目录

在上传文件时,如果数据很大,将上传失败,原因是服务器存在传输限制,必须在配置文件中修改限制才能上传成功,在 WAMPServer 软件中的配置文件为 php.ini,在此文件中更改如下项目:

file_uploads        = On    # 是否允许上传文件 On/Off 默认是 On
upload_max_filesize = 2048M # 上传文件的最大限制
post_max_size       = 2048M # 使用 POST 传输数据的最大值
max_execution_time  = 30000 # 脚本的最长执行时间、单位为秒
max_input_time      = 30000 # 传输数据的时间限制、单位为秒
memory_limit        = 2048M # 内存消耗的最大值

Ajax - GET

AJAX 是与服务器相互传输数据并在不重新加载整个页面的情况下更新部分网页的艺术

在 JavaScript 中如何使用 Ajax,步骤如下:

  1. 创建一个异步对象
  2. 设置异步对象的请求方式和请求地址
  3. 发送请求
  4. 监听状态变化
  5. 处理返回结果

示例如下:

window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        // 1. 创建一个异步对象
        let xmlhttp = new XMLHttpRequest();
        // 2. 设置异步对象的请求方式和请求地址
        xmlhttp.open("GET", "./ajax-get.php", true);
        // 3. 发送请求
        xmlhttp.send();
        // 4. 监听状态变化
        xmlhttp.onreadystatechange = function () {
            // 5. 处理返回结果
            if (xmlhttp.readyState === 4) { // 请求已完成且响应已就绪
                if (xmlhttp.status >= 200 & xmlhttp.status < 300 || xmlhttp.status === 304) {   // 成功
                    alert(xmlhttp.responseText);
                } else {
                    alert("ERROR");
                }
            }
        }
    }
}

在上述示例中,异步对象在 open 方法中的参数为:method、url 以及 async,method 表示请求方式,url 表示请求地址,async 表示请求是否异步(由于 AJAX 的目的就是发送异步请求,因此永远都为 true),此外,异步对象的 responseText 属性保存了服务器响应的字符串数据,而异步对象的 readyState 属性保存了异步对象的状态,如下所示:

状态说明
0请求未初始化
1服务器连接已建立
2请求已收到
3正在处理请求
4请求已完成且响应已就绪

异步对象的 status 属性保存了响应请求的状态号,例如 “404 Not Found”,详情可参考 HTML 状态消息

状态号 2xx 表示成功

在低级版本的浏览器(IE5、IE6)中,并不支持 XMLHttpRequest 方法,而必须使用 ActiveXObject 方法,兼容性处理如下:

var xhr;
if (window.XMLHttpRequest) {
    xhr = new XMLHttpRequest();
} else {
    xhr = new ActiveXObject("Microsoft.XMLHTTP");   // code for IE6, IE5
}

此外,如果异步对象请求的 URL 从未改变,即使 URL 响应的内容已经发生变化,在低级版本的浏览器(IE5、IE6)中的响应结果并不会改变(IE 缓存问题),即 IE 浏览器认为同一个 URL 只有一个结果,因此,必须更改异步对象请求的 URL,才能获取服务器的最新内容,可行的解决方案如下:

xhr.open("GET", "URL?t=" + (new Date().getTime()), true);

在上述示例中,通过在 URL 之后添加一个参数(随机生成的时间序列)来更改异步对象请求的 URL,从而保证可以获取服务器的最新内容(添加在 URL 之后的内容不会影响请求)

此外,后端使用 PHP 输出任意字符串即可,示例如下:

<?php
echo "I LOVE YOU";
?>

Ajax 将通过使用异步对象的 responseText 访问此字符串

Ajax - POST

通过 AJAX 发送 POST 异步请求的过程与通过 AJAX 发送 GET 异步请求的过程基本相同,不同之处在于:

  • 使用 GET 方式请求时,参数通过 URL 以 key=value 的形式传递,在每一个键值对之间以 & 间隔,形式如下:
    • ?userName=Reyn+Morales&userPassword=123456
  • 使用 POST 方式请求时,参数通过 send 方法传递,示例如下:
window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        let xhr = new XMLHttpRequest();
        xhr.open("POST", "./ajax-post.php", true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send("userName=Reyn&userPwd=1024");
        xhr.onreadystatechange = function () {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 & xhr.status < 300 || xhr.status === 304) {
                    alert(xhr.responseText);
                } else {
                    alert("ERROR");
                }
            }
        }
    }
}

在上述示例中,使用 POST 方式向服务器传递参数时,必须在 open 方法和 send 之间使用 setRequestHeader 方法配置请求参数,此外,在 send 方法中使用类似于 GET 方式中 URL 的参数 —— key=value,在每一个键值对之间以 & 间隔

此外,后端使用 PHP 中预定义的 $_POST 全局变量访问相应的 key 即可,示例如下:

<?php
echo $_POST["userName"];
echo $_POST["userPwd"];
?>

Ajax 小结

使用 Ajax 时,步骤如下:

  1. 创建一个异步对象
  2. 设置异步对象的请求方式和请求地址
  3. 发送请求
  4. 监听状态变化
  5. 处理返回结果

为了使用方便,我们将所有过程都封装到一个方法中,此外,我们将解决如下问题:

  • 兼容性(IE 异步对象、IE 缓存)
  • 超时请求
  • 编码
    • 在浏览器的 URL 中,只能出现字母、数字、下划线以及 ASCII 码,如果出现中文字符,那么浏览器将转换为 UNICode 编码(encodeURIComponent)存储

示例如下:

function args2str(args) {
    args = args || new Object();
    args.t = (new Date()).getTime();    // IE 缓存
    let arr = new Array();
    for (key in args) {
        arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(args[key]));    // 编码
    }
    return arr.join("&");
}

function ajax(type, url, args, timeout, success, error) {
    let xhr, str = args2str(args), timer;
    if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
    } else {
        xhr = ActiveXObject("Microsoft.XMLHTTP");   // IE 异步对象
    }

    if (type === "GET") {
        xhr.open(type, url + "?" + str, true);
        xhr.send();
    } else {
        xhr.open(type, url, true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send(str);
    }

    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                success(xhr);
            } else {
                error(xhr);
            }
        }
    }

    if (timeout) {
        timer = setInterval(function () {   // 超时请求
            xhr.abort();    // 终止
            clearInterval(timer);
        }, timeout);
    }
}

以下是一个示例:

window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        ajax("GET", "./myAjax.php", {
            "userName": "Reyn",
            "userPwd": 1024
        }, 5000, function (xhr) {
            alert(xhr.responseText);
        }, function (xhr) {
            alert("ERROR");
        });
    }
}

在上述示例中,ajax 函数存在一个致命缺陷,即如果在调用此函数时不传入所有的参数,那么将调用失败,且如果参数顺序不同,也将调用失败,解决方案如下:

function args2str(args) {
    args = args || new Object();
    args.t = (new Date()).getTime();
    let arr = new Array();
    for (key in args) {
        arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(args[key]));
    }
    return arr.join("&");
}

function ajax(option) {
    let xhr, str = args2str(option.args), timer;
    if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
    } else {
        xhr = ActiveXObject("Microsoft.XMLHTTP");
    }

    if (option.type.toLowerCase() === "get") {
        xhr.open(option.type, option.url + "?" + str, true);
        xhr.send();
    } else {
        xhr.open(option.type, option.url, true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send(str);
    }

    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                option.success(xhr);
            } else {
                option.error(xhr);
            }
        }
    }

    if (option.timeout) {
        timer = setInterval(function () {
            xhr.abort();
            clearInterval(timer);
        }, option.timeout);
    }
}

在上述示例中,将函数的所有参数都保存到了一个 option 实例中,从而实现在调用此方法时,不用关注传入参数的顺序以及是否必须传入所有传输,此即为 jQuery 中 Ajax 的原理,以下是一个示例:

window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        ajax({
            type: "post",
            args: {
                "userName": "Reyn",
                "userPwd": 1024
            },
            url: "./ajax-jquery.php",
            success: function (xhr) {
                alert(xhr.responseText);
            }
        });
    }
}

此外,后端使用 PHP 中预定义的 $_POST 全局变量访问相应的 key 即可,示例如下:

<?php
echo $_POST["userName"];
echo $_POST["userPwd"];
?>

Ajax - XML

当浏览器和服务器通信时,通常将数据以 XML 或 JSON 文件的格式存储或传输数据,XML(EXtensible Markup Language)是可扩展标记语言,语言中没有预定义的标签,所有标签都是自定义的,此外所有标签必须是闭合的,且大小写敏感,示例如下:

<?xml version="1.0" encoding="UTF-8" ?>
<people>
    <person>
        <name>Reyn Morales</name>
        <age>21</age>
        <gender>male</gender>
    </person>
    <person>
        <name>Mary Jane</name>
        <age>20</age>
        <gender>female</gender>
    </person>
</people>

在 XML 文件中,必须存在 DTD 声明

如果将 XML 格式的数据保存到一个 .txt 文件中,那么在 PHP 中可以使用 file_get_contents 方法获取文本文件中的内容,示例如下:

<?php
header("content-type:text/xml; charset=utf-8");
echo file_get_contents("./ajax-xml.xml");
?>

如果在 PHP 中返回了 XML 数据,或在执行结果中存在中文,那么必须使用 header 方法在顶部设置内容类型以及字符集,如果是中文,那么 content-type 的类型为 text/html

在 HTML 中,异步对象可以使用 responseXML 属性来访问响应的以 XML 格式存储的数据,示例如下:

window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        ajax({
            type: "POST",
            url: "./ajax-xml.php",
            success: function (xhr) {
                let docPeo = xhr.responseXML;   // #document
                let peoName = docPeo.querySelectorAll("name");
                let peoAge = docPeo.querySelectorAll("age");
                let peoGender = docPeo.querySelectorAll("gender");
                console.log(`Name: ${peoName[0].innerHTML} / Age: ${peoAge[0].innerHTML} / Gender: ${peoGender[0].innerHTML}`);
                console.log(`Name: ${peoName[1].innerHTML} / Age: ${peoAge[1].innerHTML} / Gender: ${peoGender[1].innerHTML}`);
            },
            error: function (xhr) {
                alert(xhr.status);
            }
        });
    }
}

在上述示例中,使用了 ajax-jquery.js 文件中的 ajax 方法,此外,如果使用 console.log 方法输出 docPeo,在浏览器的开发者工具中显示为 #document,展开内容如下:

<people>
    <person>
        <name>Reyn Morales</name>
        <age>21</age>
        <gender>male</gender>
    </person>
    <person>
        <name>Mary Jane</name>
        <age>20</age>
        <gender>female</gender>
    </person>
</people>

不难发现,内容和 .xml 文档内容基本一致,在 DOM 中提供了 document 对象代表整个 HTML 文档,此处的 document 代表整个 XML 文档,从而在 JavaScript 中以使用 DOM 提供的 document 的方式使用 XML 提供的 document

Ajax - JSON

在 JavaScript 中介绍了 JSON,此处说明 JSON 在 Ajax 中的使用方法,.json 文件如下所示:

{
    "people": [
        {
            "name": "Reyn Morales",
            "age": 21,
            "gender": "male"
        },
        {
            "name": "Mary Jane",
            "age": 20,
            "gender": "female"
        }
    ]
}

由于 JSON 的本质是一个字符串,因此在 PHP 中同样可以使用 file_get_contents 方法获取文本文件中的内容,示例如下:

<?php
echo file_get_contents("./ajax-json.json");
?>

在 HTML 中,异步对象可以使用 responseText 属性来访问响应的以 JSON 格式存储的数据,示例如下:

window.onload = function () {
    let oBtn = document.querySelector("button");
    oBtn.onclick = function () {
        ajax({
            type: "POST",
            url: "./ajax-json.php",
            success: function (xhr) {
                let peoInfo = JSON.parse(xhr.responseText).people;
                console.log(`Name: ${peoInfo[0].name} / Age: ${peoInfo[0].age} / Gender: ${peoInfo[0].gender}`);
                console.log(`Name: ${peoInfo[1].name} / Age: ${peoInfo[1].age} / Gender: ${peoInfo[1].gender}`);
            },
            error: function (xhr) {
                alert(xhr.status);
            }
        });
    }
}

在上述示例中,在使用 responseText 属性访问数据时,使用 JavaScript 中内置的 JSON 对象的 parse 方法将 JSON 字符串转换为一个对象,然后访问它的 people 属性,如果输出 peoInfo 将看到它是一个数组,每一个元素是一个对象,从而可以使用 . 运算符访问每一个对象的属性(name、age 和 gender)

当被转换的 JSON 不是标准的 JSON 时,将出现如下错误:

Uncaught SyntaxError: Expected property name or ‘}’ in JSON

出现此错误的代码如下:

let jsonStr = '{name: "reyn", age: 21}';
let jsonObj = JSON.parse(jsonStr);  // error
console.log(jsonObj);

在上述示例中,jsonStr 并不是标准的 JSON 字符串,因为标准 JSON 字符串中的 key 必须使用 " ,虽然非标准的 JSON 字符串不能使用 JSON 的 parse 方法,但是可以使用 eval 方法,示例如下:

let jsonStr = '{name: "reyn", age: 21}';
let jsonObj = eval("(" + jsonStr + ")");    // correct
console.log(jsonObj);

在上述示例中,使用 eval 时,必须在 JSON 字符串左侧添加 (,在 JSON 字符串右侧添加 )

Cookie

Cookie 是一种使用在客户端的会话跟踪技术,与此类似的是 Session —— 使用在服务端的会话跟踪技术,Cookie 的作用是将数据存储在浏览器中,以便之后可以重复使用

基本使用

在 document 对象中有一个 cookie 属性可以操作浏览器中的 Cookie 内容,通常以 key=value; 的形式出现,示例如下:

document.cookie = "name=reyn;";
console.log(document.cookie);   // name=reyn

在浏览器的开发者工具中的应用程序一栏中可以查看浏览器中存储的 Cookie,通常设置的属性有 key、value、domain、path 以及 expires

生命周期

Cookie 的生命周期默认是一次会话(浏览器被关闭后表示一次会话结束),可以通过 expires 属性设置 Cookie 的过期时间,从而修改 Cookie 的生命周期:

  • 如果时间任未过期,那么下一次打开浏览器之后,Cookie 将任然存在
  • 如果时间已经过期,那么过期的 Cookie 将立刻被删除

示例如下:

let cookieDate = new Date();
cookieDate.setDate(cookieDate.getDate() + 7);
document.cookie = `name=reyn;expires=${cookieDate.toGMTString()};`;

如果将 cookieDate 设置为今天之前的时间(即使用 - 号),那么将立刻删除 key 为 age 的 Cookie

注意点

  • Cookie 默认不会存储任何数据
  • Cookie 不能一次性存储若干数据,即以下语句将存储 key 为 name 的数据:
    • document.cookie = “name=reyn;age=21;”;
  • Cookie 存在大小和个数限制
    • 大小大约 4KB 左右
    • 个数大约 20 ~ 50 个左右

作用范围

Cookie 存储的数据只在同一个浏览器的同一个目录及其子目录下才可以访问,即如果使用另一个浏览器访问网页时,在上一个浏览器中存储的 Cookie 将不存在,使用浏览器访问网页的本质是访问另一台主机(服务器)的一个目录,而在一个目录下存储的 Cookie 只在此目录及其子目录下访问,而在上一级目录则无法访问 Cookie 数据,可以设置 Cookie 的 path 属性为根目录从而实现在上一级目录中访问站点目录中的 Cookie,示例如下:

document.cookie = "name=reyn;path=/;";

此外,Cookie 也有域名限制,即只有在同一个域名下的目录才可以访问,即在域名为 www.reyn.org 下存储的 Cookie,不能在 edu.reyn.org 下访问,可以设置 Cookie 的 domain 属性为二级域名从而实现在以 reyn.org 为二级域名下的所有域名都可以访问二级域名中的 Cookie,示例如下:

document.cookie = "name=reyn;domain=reyn.org;"

综上所示,通常添加一条 Cookie 的方式如下:

let cookieDate = new Date();
cookieDate.setDate(cookieDate.getDate() + 7);
document.cookie = `name=reyn;expires=${cookieDate.toGMTString()};path=/;domain=reyn.org;`;

Cookie 封装

现在我们将使用 Cookie 常用的操作封装为方法,并将它们作为 jQuery 的扩展静态方法使用

添加
function addCookie(key, value, days, path, domain) {
    days = days || 0;
    let cookieStretch = new Date();
    cookieStretch.setDate(cookieStretch.getDate() + days);

    let pathStr = window.location.pathname;
    let lastIndex = pathStr.lastIndexOf("/");
    path = path || pathStr.substring(0, lastIndex);

    domain = domain || window.location.hostname;

    document.cookie = `${key}=${value};expires=${cookieStretch};path=${path};domain${domain}`;
}
获取
function getCookie(key) {
    let cookies = document.cookie.split(";");
    for (let i = 0; i < cookies.length; i++) {
        let [curKey, curValue] = (cookies[i]).split("=");

        curKey = curKey.trim();
        curValue = curValue.trim();

        if (curKey === key) {
            return curValue;
        }
    }
}
删除
function delCookie(key, path) {
    addCookie(key, getCookie(key), -1, path);
}

在删除 Cookie 时,如果待删除的 Cookie 的 path 不是默认路径,那么必须传入此 Cookie 在添加时的路径,否则无法删除

如何为 jQuery 扩充方法

现在我们将 Cookie 的所有方法扩充到 jQuery 中,文件名称命名为 jquery.cookie.js,示例如下:

;(function ($, window) {
    $.extend({
        addCookie: function (key, value, days, path, domain) {
            days = days || 0;
            let cookieStretch = new Date();
            cookieStretch.setDate(cookieStretch.getDate() + days);

            let pathStr = window.location.pathname;
            let lastIndex = pathStr.lastIndexOf("/");
            path = path || pathStr.substring(0, lastIndex);

            domain = domain || window.location.hostname;

            document.cookie = `${key}=${value};expires=${cookieStretch};path=${path};domain${domain}`;
        },

        getCookie: function (key) {
            let cookies = document.cookie.split(";");
            for (let i = 0; i < cookies.length; i++) {
                let [curKey, curValue] = (cookies[i]).split("=");

                curKey = curKey.trim();
                curValue = curValue.trim();

                if (curKey === key) {
                    return curValue;
                }
            }
        },

        delCookie: function (key, path) {
            addCookie(key, getCookie(key), -1, path);
        }
    });
})(jQuery, window);

在上述示例中,使用了 jQuery 的静态 expend 方法,此方法将参数中的所有函数添加到 jQuery 类中,从而可以使用 jQuery 调用,如果使用 jQuery 的实例调用 expend 方法,此方法将参数中的所有函数添加的调用此实例的原型对象中

补充内容

Cookie 虽然可以将数据存储在浏览器中,但并不能跨浏览器访问 Cookie,此时,可以使用 JavaScript 中的 hash 来保存数据,示例如下:

window.location.hash = "0";
console.log(location.hash); // #0

在浏览器的 URL 栏中,内容如下:

http://localhost:52330/code/hash.html#0

在 url 最后的 #0 即为 hash,如此,在将 url 拷贝到另外的浏览器时,也可以使用 hash 中存储的信息

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值