jQuery源码分析之Callbacks详解


Posted in Javascript onMarch 13, 2015

代码的本质突出顺序、有序这一概念,尤其在javascript——毕竟javascript是单线程引擎。

javascript拥有函数式编程的特性,而又因为javascript单线程引擎,我们的函数总是需要有序的执行。优秀代码常常 把函数切割成各自的模块,然后在某一特定条件下执行,既然这些函数是有序的执行,那么我们为什么不编写一个统一管理的对象,来帮助我们管理这些函数——于是,Callbacks(回调函数)诞生。

什么是Callbacks

javascript中充斥着函数编程,例如最简单的window.onload承接的就是一个函数,悲催的是window.onload直接赋值的话只能接收一个函数,如果有好几个函数想要在onload中执行,那么我们就需要编写如下代码:

        function a(elem) {

            elem.innerHTML = '我是函数a,我要改变Element的HTML结构';

        };

        function b(elem) {

            elem.innerHTML = '我的函数b,我要改变Element的style';

        }

        window.onload = function () {

            var elem = document.getElementById('test');

            a(elem);

            b(elem);

        };

回调函数初衷就是建立在这么个玩意儿的上面,不再让我们分散这些函数,而是把这些函数统一整理。可以看见,我们在window.onload中希望针对一个Element做两件事情:先改变html结构,然后改变这个html的style。两个函数同样是针对一个Element操作,而这两个函数最终的执行都是有序进行的。那么我们为什么不编写一个这样的对象管理这些函数呢。当然, 这只是回调函数的最基础的存在意义,我们需要的不仅仅是这样一个简单的回调函数对象,我们需要一个更加强大的回调函数。好吧,这只是一个简单的用例,那么我可以告诉你这个回调函数除了一个个执行函数之外,它还可以做什么。

Callbacks本质就是控制函数有序的执行,Javascript是单线程引擎,也就说,javascript同一时间只会有一处代码在运行——即便是Ajax、setTimeout。 这两个函数看起来好像都是异步的,其实并非如此,浏览器在运行javascript代码的时候,这些代码都会被有序的压入一个队列中,当你运行Ajax的时候,浏览器会把Ajax 压入代码队列,浏览器在处理javascript代码是从这个代码队列中一个一个取代码执行的——Callbacks,迎合了这种单线程引擎。

当然,我们要的,不仅仅是这样一个简单的工具对象——在jQuery源码中,Callbacks提供了一组函数的基本管理,为Deferred(异步队列)提供了基础,同时也服务于Queue(同步队列)。 Deferred用于抹平/扁平化金字塔编程(大量的回调函数嵌套,例如Ajax中需要根据请求返回码决定执行的代码); 而Queue,驱动着jQuery.animate(动画引擎)。

那么我们就来编写一个Callbacks吧。

Callbacks模型

Array(数组):
既然我们Callbacks要承接一系列函数,那么必然需要有一个容器。我们可以使用一个数组,并把每一个函数压到该数组中,需要执行的时候,循环数组项执行。

工作模型

这个Callbacks需要非常的强大,并不仅仅是压入函数,然后执行这么简单,这个Callbacks应该拥有良好的执行模型。
once:当前Callbacks对象中所有的函数只会执行一次,执行一次完之后就会被释放掉,我们可以为使用Callbacks对象的用户提供一个稳定有效的方案,确保函数只会执行一次,之后不再执行,稳定了这些函数的线程。
auto:自动执行模型,这是个有意思的模型,有些函数依赖上一层函数,例如函数b的执行依赖于函数a,那么我们提供一个自动执行的模型:第一次执行这个Callbacks之后,每次添加函数到Callbacks的时候,自动执行过去添加的那些函数,并把最后一次给定的参数数据传递给过去的那些函数,这样就从Callbacks中抹平了这些依赖函数之间需要反复触发的关系,这是个有意思的模型。
once&auto:我们可以让它更强大,同时工作once和auto模型,即:当每次添加函数到Callbacks中的时候,过去的函数都会执行,然后,释放掉这些过去的函数,下次继续添加函数的时候,过去的那些函数不会再执行,因为once模型,已经把它们释放掉了。

API:

add(function) - 添加一个(或多个)函数到Callbacks对象中:当然,如果你并不添加函数只是好奇看看Callbacks,我们也将让你继续享受你的乐趣——我们并不会抛出异常,因为这对于我们来说并不擅长。
remove(function) - 移除一个Callbacks中的一个函数:既然有了添加,那么我们也应该提供反悔的方案,我们是多么的平易近人,容忍着别人过去所做的一切。
has(function) - 判断Callbacks中是否包含一个函数:哦?你竟然不确定是否包含这个函数,当初可是你丢进来的啊!你怎么如此马虎?不过既然你问我的话,我仍然会告诉你Callbacks是否包含这个函数,我知道你很繁忙,并不能记住和确定所有的事情。
empty() - 清空Callbacks:这些函数对于你失去了意义了么?什么?已经执行过你就不想要了?所以你希望可以清空它?好吧,为了内存君我还是忍下你这个需求。
disable() - 废掉一个Callbacks:为了和别人的代码稳定的存在,我选择了自我牺牲——没错,这个方法可以废掉Callbacks,彻底的废掉,就如同它曾经尚未存在过一般。
disabled() - 判断这个Callbacks是否已经被废掉:如果你仍然不相信Callbacks是否真的自我牺牲,那么这个方法可以让你安心。
lock(boolean) - 锁定这个Callbacks对象:你害怕它并不稳定,但是你又不想舍弃它,lock是个不错的方法,它接收一个Boolean的参数,表示是否需要锁定这个对象,当然,无参的它用于让你确定Callbacks是否被锁定。
fire(data) - 执行这个Callbacks中的函数:我们做的这一切,不都是为了这一刻执行的宿命么?参数将会成为这些需要执行的函数的参数。
fireWith(context,data) - 执行Callbacks中的函数,并且指定上下文。在fire()里,所有的函数的Context(上下文)都是Callbacks对象,而fireWidth(),可以让你重新定义这些要执行的函数的上下文,多么自由的编程啊,Callbacks为你考虑了一切。
fired() - 判断这个Callbacks过去是否已经执行过:我们相信,多数时候你并不知道过去做过什么,但是我们记录了你做的一切,如果你过去曾经执行过这个Callbacks对象,那么你休想否认,因为我们知道过去你是否执行了这个Callbacks。

基本模块实现

简单的实现
我们先来简单的实现一个Callbacks:

(function (window, undefined) {

            var Callbacks = function () {

                //通过闭包保护这些私有变量

                var list = [],//回调函数列表

                    fired;//是否执行过

                //返回一个闭包的Callbakcs对象

                return {

                    add: function (fn) {

                        //当Callbacks废弃掉的时候,list为undefined

                        if (list) {

                            //添加一个回调函数

                            list.push(fn);

                            //支持链式回调

                        }

                        return this;

                    },

                    fireWith: function (context, data) {

                        //触发回调函数,并指定上下文

                        if (list) {

                            fired = true;

                            for (var i = 0, len = list.length; i < len; i++) {

                                //当Callbacks中某一个函数返回false的时候,停止Callbacks后续的执行

                                if (list[i].apply(context, data) === false)

                                    break;

                            }

                        }

                        return this;

                    },

                    fire: function () {

                        //触发回调函数

                        //调用fireWith并指定上下文

                        return this.fireWith(this, arguments);

                    },

                    empty: function () {

                        //清空list即可

                        if (list)//当这个Callbacks废弃掉的时候,Callbacks不应该可以继续使用

                            list = [];

                        return this;

                    },

                    disable: function () {

                        //废弃这个Callbacks对象,后续的回调函数列表不再执行

                        list = undefined;

                        return this;

                    },

                    disabled: function () {//检测这个Callbacks是否已经废掉

                        //转换为boolean返回

                        return !list;

                    },

                    fired: function () {//这个callbacks是否执行过

                        return !!fired;

                    }

                };

            };

            //注册到window下

            window.Callbacks = Callbacks;

        }(window));

然后我们测试一下这个Callbacks:

        var test = new Callbacks();

        test.add(function (value) {

            console.log('函数1,value是:' + value);

        });

        test.add(function (value) {

            console.log('函数2,value是:' + value);

        });

        test.fire('这是函数1和函数2的值');

        console.log('查看函数是否执行过:' + test.fired());

        test.disable();//废弃这个Callbacks

        console.log('查看函数是否被废弃:' + test.disabled());

        test.add(function () {

            console.log('添加第三个函数,这个函数不应该被执行');

        });

        test.fire();

打开浏览器的控制台我们可以看见运行结果正常。

once和auto(memory)实现

once:
once让这个callbacks中的函数运行一次之后就不再运行。原理非常的简单,上面的代码中,我们可以看见有一个变量list承接函数列表,所以我们只需要把过去执行过的代码清空即可。我们用一个全局变量,保存当前执行模型,如果是once模型,就在fireWith()里让这个list失效即可:

(function (window, undefined) {

            var Callbacks = function (once) {

                //通过闭包保护这些私有变量

                var list = [],//回调函数列表

                    fired;//是否执行过

                //返回一个闭包的Callbakcs对象

                return {

                    //...省略部分代码

                    fireWith: function (context, data) {

                        //触发回调函数,并指定上下文

                        if (list) {

                            fired = true;

                            for (var i = 0, len = list.length; i < len; i++) {

                                //当Callbacks中某一个函数返回false的时候,停止Callbacks后续的执行

                                if (list[i].apply(context, data) === false)

                                    break;

                            }

                        }

                        //如果配置了once模型,则全局变量once为true,则list重置

                        if (once) list = undefined;

                        return this;

                    }

                    //...省略部分代码

                };

            };

            //注册到window下

            window.Callbacks = Callbacks;

        }(window));

auto:

auto(memory)模型在jQuery中是以memory命名的,最初被这个命名给混淆了,仔细看了用法才确定改成auto——它的作用就是“第一次fire()之后,后续add()的函数自动执行”,以下情况可以用到:当添加一组函数到Callbacks之后,临时又需要追加一个函数,那么即时运行这个新追加的函数——不得不说,为了使用的便利,这个模式变得有点难以理解。实现起来就是在add()的时候判断是否是auto模型,如果是auto模型,则执行这个函数。 但是,我们需要在第一次fire()之后才自动执行,没有fire()过的Callbacks并不该被自动执行,并且,每次自动执行后,还需要把最后一次使用的参数传递传递给这个自动执行的函数。

或许大家会想到如下代码:

(function (window, undefined) {

            var Callbacks = function (once, auto) {

                var list = [],

                    fired,

                    lastData;//保存最后一次执行的参数

                return {

                    add: function (fn) {

                        if (list) {

                            list.push(fn);

                            // — 自动执行模式

                            //最后一次使用的参数传递过去,这里丢失了Context(上下文)

                            //为了不让这里丢失上下文,我们或许还需要声明一个变量保存最后一次使用的Context

                            if (auto) this.fire(lastData);

                        }

                        return this;

                    },

                    fireWith: function (context, data) {

                        if (list) {

                            lastData = data;// — 记录最后一次使用的参数

                            fired = true;

                            for (var i = 0, len = list.length; i < len; i++) {

                                if (list[i].apply(context, data) === false)

                                    break;

                            }

                        }

                        if (once) list = [];

                        return this;

                    }

                    //部分代码省略

                };

            };

            //注册到window下

            window.Callbacks = Callbacks;

        }(window));

但是在jQuery里采用了更奇妙的用法,获取jQuery作者也自豪这种用法,所以命名这个模型为memory——就是让上面的变量auto不仅仅表示当前是auto执行模式,并且作为最后一次参数的容器,它既表示了auto,也表示了memory。(下面的代码非jQuery是根据jQuery代码思路而写,非源码):

(function (window, undefined) {

            var Callbacks = function (auto) {

                var list = [],

                    fired,

                    memory,//主演在这里,就是memory

                    coreFire = function (data) {

                        //真正的触发函数方法

                        if (list) {

                            //&&表达式妙用

                            memory = auto && data;//记录最后一次的参数,如果不是auto模式则不会记录这个参数

                            //如果是auto模式,那么这个auto将不会为false,它会是一个数组

                            fired = true;

                            for (var i = 0, len = list.length; i < len; i++) {

                                if (list[i].apply(data[0], data[1]) === false)

                                    break;

                            }

                        }

                    };

                return {

                    add: function (fn) {

                        if (list) {

                            //添加一个回调函数

                            list.push(fn);

                            //自动执行模式,注意如果auto模型

                            //memory是在coreFire()里赋值的,默认是false

                            if (memory) coreFire(auto);

                        }

                        //支持链式回调

                        return this;

                    },

                    fireWith: function (context, data) {

                        if (once) list = [];

                        //这里调用coreFire,把参数转换为数组了

                        coreFire([context, data]);

                        return this;

                    }

                    /*部分代码省略*/

                };

            };

            window.Callbacks = Callbacks;

        }(window));

我们在上一个auto实现的代码中看到我们丢失了Context,jQuery早在fireWith()中修复了这个bug——在fireWith()中修复参数。jQuery把fireWith()中本来应该执行函数的逻辑给抽离出来,我们暂时将它命名为coreFire(),在原fireWith()中,将参数拼接成一个数组:第一个参数表示上下文,第二个参数表示传递进来的参数。然后执行coreFire()。

在add()的时候,jQuery并没有给变量auto(memory)赋值,而是选择在coreFire()中给auto(memory)赋值,这样就保证了第一次fire()之后才会开启自动执行。

按照上面所说,coreFire()接收的参数其实是一个数组,第一个参数是上下文,第二个参数是外面传递进来的参数。同时把这个数组赋值给auto(memory),这样,变量auto(是否自动执行模式)的定义就变成了memory(记忆最后一次传递的参数)。
真是一石二鸟的神思路,神想法,不得不点赞。我定义这个为auto是因为它的本身就是一个自动执行的模型,顺便保存了最后一次fire()的参数,而jQuery定义为memory或许也是作者感叹这里的鬼斧神工吧。

至于once&auto就是把这两个代码揉合到一起而已,只需要在coreFire()里判定如果是auto模式,那么就把list重置为一个新的数组,否则直接设置为undefined即可。

源码

这份代码是自己对应jQuery手写的一份,将一些jQuery公有的函数都写了进来,并非代码片段,所以可以直接引用运行。

(function (window, undefined) {

    /*

    * 一个回调函数工具对象,注意这个工作对象工作完成之后就会清空数组:

    *   提供一组普通的API,但它有如下工作模型 - 

    *                     once - 单次执行模型:每次工作一次,后续不再工作

    *                     auto - 自动执行模型:每添加一个回调函数,自动执行现有的回调函数集合里的所有回调函数,并将本次的参数传递给所有的回调函数

    *

    */
    //工具函数

    var isIndexOf = Array.prototype.indexOf,    //Es6

        toString = Object.prototype.toString,   //缓存toString方法

        toSlice = Array.prototype.slice,        //缓存slice方法

        isFunction = (function () {             //判定一个对象是否是Function

            return "object" === typeof document.getElementById ?

            isFunction = function (fn) {

                //ie下对DOM和BOM的识别有问题

                try {

                    return /^\s*\bfunction\b/.test("" + fn);

                } catch (x) {

                    return false

                }

            } :

            isFunction = function (fn) { return toString.call(fn) === '[object Function]'; };

        })(),

        each = function () {                    //循环遍历方法

            //第一个参数表示要循环的数组,第二个参数是每次循环执行的函数

            if (arguments.length < 2 || !isFunction(arguments[1])) return;

            //为什么slice无效??

            var list = toSlice.call(arguments[0]),

                fn = arguments[1],

                item;

            while ((item = list.shift())) {//没有直接判定length,加速

                // 为什么这里用call就可以,而apply就不行?

                //搞定 - apply的第二个参数必须是一个array对象(没有验证array-like是否可以,而call没有这个要求)

                //apply是这样描述的:如果 argArray(第二个参数) 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。 

                fn.call(window, item);

            }

        },

        inArray = function () {                     //检测数组中是否包含某项,返回该项索引

            //预编译

            return isIndexOf ? function (array, elem, i) {

                if (array)

                    return isIndexOf.call(array, elem, i);

                return -1;

            } : function (elem, array, i) {

                var len;

                if (array) {

                    len = array.length;

                    i = i ? i < 0 ? Math.max(0, len + i) : i : 0;

                    for (; i < len; i++) {

                        if (i in array && array[i] === elem) {

                            return i;

                        }

                    }

                }

                return -1;

            }

        }();
    var Callbacks = function (option) {

        option = toString.call(option) === '[object Object]' ? option : {};

        //使用闭包,因为每个新建的callbacks都有自己的状态

        var list = [],      //回调列表

            _list = [],     //如果锁定这个callbacks对象,则清空list,将原list置入_list

            fired,          //是否执行过

            firingStart,    //当前回调函数列表执行的函数索引(起点)

            firingLength,   //回调函数的数组长度

            auto,   //标志是否自动执行,如果需要自动执行,则auto记忆着最后一次回调的参数(最后一次fire的参数),这是一个很诡异的且奇葩的用法

            //这个变量用法很诡异和犀利,既包含了是否指定执行的标志,又记录了数据

            //这个auto配合once简直就是丧心病狂:【第一次】执行了fire后才会自动执行,配合once可以做到:一次执行,后面不再追加和执行代码,保证了一组回调数据的稳定和安全

            stack = !option.once && [],     //一个callbacks栈,如果当前正在执行回调数组,而在执行中又新添了回调函数,那么把新的回调函数,那么新的回调函数都会压入该栈

            firing = false, //callbacks是否正在工作/执行

        //触发回调函数

            fire = function (data) {

                //注意这个data是个数组,如果配置了auto模式,那么auto永远不会为false,因为auto会是个数组

                auto = option.auto && data; //在这里,如果配置要求记忆最后的参数,则记忆这个参数(非常犀利的用法,直接取了数据)

                fired = true;

                firingIndex = firingStart || 0;

                firingStart = 0;//清空firingStart(不清空下次执行有出问题啦)

                firingLength = list.length;         //缓存list长度,外界可以访问

                firing = true; //正在执行回调函数

                for (; firingIndex < firingLength; firingIndex++) {

                    if (list[firingIndex].apply(data[0], data[1]) === false) {

                        //注意,如果配置了option.auto(自动执行),并且stack(栈)里存在函数,那么add()代码里有一段对于auto判定会直接执行本方法的代码

                        //我们要阻止掉那段代码,所以设置auto为false

                        auto = false;

                        break;

                    }//当函数返回false,终止执行后续队列

                }

                firing = false; //标志状态已经执行完毕回调函数[stack(栈)里面的函数尚未执行]

                //如果这个栈在没有配置once的情况下肯定是[],所以一定存在

                //这里主要作用是,如果没有配置once,则拦截下面的代码,如果配置了once,执行完代码清空数据

                if (stack) {

                    if (stack.length)//先把下面清空list状态的代码拦截掉,再判定是否有栈

                        fire(stack.shift()); //从栈头部取出,并递归fire()方法

                }

                else if (auto)    //代码走到这里,证明已经配置了option.once(只执行一次),于是把list清空

                    list = [];

                else                //证明没有配置auto,但是配置了once,那么祭出终极大法,直接废了这个callbacks对象

                    self.disable();

            };

        var self = {

            add: function () {//添加一个回调函数

                if (list) {

                    var start = list.length;

                    (function addCallback(args) {

                        each(args, function (item) {

                            if (isFunction(item)) {//是函数,则压入回调列表

                                list.push(item);

                                //注意typeof 和Object.prototype.toString是不一样的

                            } else if (toString.call(item) === '[object Array]') {//如果是个数组,则递归压入回调列表,这个判定抛弃了array-like

                                addCallback(item);

                            }

                        });

                    })(arguments);

                }

                if (firing)//如果当前正有回调函数在执行,那么需要更新当前回调函数列表的length,否则这个新压入的回调函数就会被掠过。

                    firingLength = list.length;

                else if (auto) {//如果当前没有执行回调函数,并且要求自动执行

                    //注意这里是给firingStart赋值,上面fire方法中正在使用的是firingIndex,这里不会影响到上面代码的执行线路

                    firingStart = start;

                    //执行我们新加入的小伙伴

                    fire(auto);

                }

                return this;

            },

            fire: function () {//触发回调函数

                self.fireWith(this, arguments);

                return this;

            },

            fireWith: function (context, args) {//触发回调函数,并指定上下文

                //如果配置了once,stack将为undefined,而once又需要保证只执行一次,所以一旦执行过一次,这里的代码不会再执行

                if (list && (!fired || stack)) {

                    //修正参数

                    //在这里,context索引为0

                    //而参数列表索引为2

                    //转换为数组访问是因为对象表示更加的消耗资源,在顶层的fire()代码中有auto[记忆参数,自动执行]这个功能,如果采用对象则开销了更大的内存

                    args = [context,

                        args ?

                        args.slice && args.slice()

                        || toSlice.call(args) :

                        []

                    ];

                    fire(args);

                }

                return this;

            },

            remove: function () {//移除一个回调函数

                if (list) {

                    each(arguments, function (item) {

                        var index;

                        //可能有多项,index可以在循环中表示检索的范围,之前检索的过的可以不用再检索

                        while ((index = inArray(item, list, index)) > -1) {

                            list.splice(index, 1);

                            if (firing) {

                                //保证上面fire中正在执行的函数列表能够正确运行,fire中设定全局这些变量为的就是这里可以异步移除

                                if (index <= firingLength)//修正长度

                                    firingLength--;

                                if (index <= firingLength)//修正索引

                                    firingIndex--;

                            }

                        }

                    });

                }

                return this;

            },

            has: function (fn) {//是否包含一个回调函数

                return fn ? inArray(fn, list) > -1 : list && list.length;

            },

            empty: function () {//清空这个callbacks对象

                list = [];

                firingLength = 0;

                return this;

            },

            disable: function () {//废掉这个callbacks对象,后续的回调函数列表不再执行

                list = stack = auto = undefined;

                return this;

            },

            disabled: function () {//是否已经废掉

                return !list; //转换为boolean

            },

            lock: function (isLock) {//锁定或解锁这个callbacks对象

                //无参,判断这个callbacks是否被锁定

                if (isLock == null) return !!_list;

                if (isLock) {//锁

                    _list = stack && list.concat(stack) || list;

                    list = undefined;

                } else {//解锁,jQuery并没有提供解锁功能,解锁让Callbacks变得不稳定

                    list = _list;

                    _list = undefined;

                }

                return this;

            },

            fired: function () {//这个callbacks是否执行过

                //转换为boolean,包括undefined,null,''等

                return !!fired;

            }

        };

        return self;

    };

    window.$ = window.$ || {};

    window.$.Callbacks = window.Callbacks = Callbacks;

}(window));

下载

Github:https://github.com/linkFly6/linkfly.so/blob/master/LinkFLy/jQuery/jQuery.LinkFLy/Callbacks.js

以上就是本文给大家分享的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
js模拟实现Array的sort方法
Dec 11 Javascript
javascript中自定义对象的属性方法分享
Jul 12 Javascript
结合JQ1.9通过js正则判断各种浏览器版本的方法
Dec 30 Javascript
node.js中的fs.readlinkSync方法使用说明
Dec 17 Javascript
JS拖拽组件学习使用
Jan 19 Javascript
jQuery+canvas实现的球体平抛及颜色动态变换效果
Jan 28 Javascript
使用jQuery操作HTML的table表格的实例解析
Mar 13 Javascript
JS实现的验证身份证及获取地区功能示例
Jan 16 Javascript
解决option标签selected=&quot;selected&quot;属性失效的问题
Nov 06 Javascript
vue二级菜单导航点击选中事件的方法
Sep 12 Javascript
vue canvas绘制矩形并解决由clearRec带来的闪屏问题
Sep 02 Javascript
VUE实现强制渲染,强制更新
Oct 29 Javascript
JavaScript获取伪元素(Pseudo-Element)属性的方法技巧
Mar 13 #Javascript
Javascript定义类(class)的三种方法详解
Mar 13 #Javascript
JavaScript中5种调用函数的方法
Mar 12 #Javascript
JavaScript实现的一个倒计时的类
Mar 12 #Javascript
JavaScript将XML转成JSON的方法
Mar 12 #Javascript
JavaScript中诡异的delete操作符
Mar 12 #Javascript
JavaScript实现计算字符串中出现次数最多的字符和出现的次数
Mar 12 #Javascript
You might like
php中http_build_query 的一个问题
2012/03/25 PHP
检查php文件中是否含有bom的函数
2012/05/31 PHP
php 判断数组是几维数组
2013/03/20 PHP
php版银联支付接口开发简明教程
2016/10/14 PHP
php删除二维数组中的重复值方法
2018/03/12 PHP
php探针不显示内存解决方法
2019/09/17 PHP
详解php反序列化
2020/06/10 PHP
Windows Live的@live.com域名注册漏洞 利用代码
2006/12/27 Javascript
把JS与CSS写在同一个文件里的书写方法
2007/06/02 Javascript
基于jquery的$.ajax async使用
2011/10/19 Javascript
jquery 插件学习(六)
2012/08/06 Javascript
javascript自定义的addClass()方法
2014/05/28 Javascript
javascript实现节点(div)名称编辑
2014/12/17 Javascript
ArtEditor富文本编辑器增加表单提交功能
2016/04/18 Javascript
jQuery处理XML文件的几种方法
2016/06/14 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
2016/07/21 Javascript
jQuery webuploader分片上传大文件
2016/11/07 Javascript
setTimeout函数的神奇使用
2017/02/26 Javascript
JavaScript实现的仿新浪微博原生态输入字数即时检查功能【兼容IE6】
2017/09/26 Javascript
基于vue实现分页效果
2017/11/06 Javascript
Vue代码分割懒加载的实现方法
2017/11/23 Javascript
Node.js readline 逐行读取、写入文件内容的示例
2018/03/01 Javascript
在vue项目中引入高德地图及其UI组件的方法
2018/09/04 Javascript
Angular6项目打包优化的实现方法
2019/12/15 Javascript
django自带的server 让外网主机访问方法
2018/05/14 Python
Python爬虫实现抓取京东店铺信息及下载图片功能示例
2018/08/07 Python
Python如何筛选序列中的元素的方法实现
2019/07/15 Python
Django实现web端tailf日志文件功能及实例详解
2019/07/28 Python
Django admin 实现search_fields精确查询实例
2020/03/30 Python
python线性插值解析
2020/07/05 Python
洲际酒店集团美国官网:IHG美国
2017/11/16 全球购物
Hammitt官网:设计师手袋
2020/05/23 全球购物
乡镇干部个人对照检查材料思想汇报(原创篇)
2014/09/28 职场文书
求职自荐信怎么写
2015/03/04 职场文书
2016见义勇为事迹材料汇总
2016/03/01 职场文书
MySQL 分区表中分区键为什么必须是主键的一部分
2022/03/17 MySQL