0

0

jQuery源代码学习之队列模块queue

巴扎黑

巴扎黑

发布时间:2017-06-20 11:26:32

|

1150人浏览过

|

来源于php中文网

原创

一、jquery种的队列模块

  jQuery的队列模块主要是为动画模块EFFECTS提供支持,单独抽取出一个命名空间是为了使程序员可以自定义自己的队列。

  具体API的调用方法可以参考这篇博客http://snandy.iteye.com/blog/1978428

二、队列模块的代码结构

低级方法jQuery下有queue,dequeue,_queueHooks这三种方法;低级方法不建议直接在外部调用;

高级方法有.queue,.dequeue,.clearQueue,.delay,.promise

百度文心一格
百度文心一格

百度推出的AI绘画作图工具

下载

三、实现代码

更多的注意点,思路请参见代码中的注释

 


   jQuery.fn.extend({        //.queue([queuename]);返回第一个匹配元素关联的函数队列        //.queue([queueName],newQueue);修改匹配元素关联的函数队列,使用函数数组newQueue替换当前队列        //.queue([queueName],callback(next,hooks));修改匹配元素关联的函数队列,添加callback到队列中        //如果queueName省略,则默认是动画队列fx        queue:function(type,data){            var setter=2;            if(typeof type!=='string'){                //进行参数修正
                data=type;
                type='fx';
                setter--;
            }            //靠,这种判断是获取还是设置的点子是怎么想出来的
            if(arguments.length

截止目前的myJquey.js代码全貌


(function(window,undefined){    var rootjQuery,
        core_version='2.0.3',
        idExpr=/^#([\w\-]*)$/,        //下面两个正则用于转驼峰
        rmsPrefix = /^-ms-/,
        rdashAlpha = /-([\da-z])/gi,
        rnotwhite = /\S+/g,//匹配非空白字符
        class2type={},
        core_deletedIds=[],
        core_version='2.0.3',

        _jQuery=window.jQuery,
        _$=window.$,

        core_toString=class2type.toString,
        core_hasOwn=class2type.hasOwnProperty,
        core_trim=core_version.trim,
        core_indexOf=core_deletedIds.indexOf,
        core_push=core_deletedIds.push,
        core_concat=core_deletedIds.concat,
        core_slice=core_deletedIds.slice,        //用于jQuery.camelCase转驼峰函数中        //当replace函数只有一个匹配项时,第二个参数可以是一个函数        //如果repalce中的正则没有捕获组,会向这个函数传递三个参数:模式的匹配项,模式匹配项在字符串中的位置,原始字符串        //如果replace中的正则有捕获组,也会向这个函数传递三个参数,模式的匹配项,捕获组的匹配项,模式匹配项在字符串中的位置
        fcamelCase=function(all,letter){           return letter.toUpperCase();            
        },
        jQuery=function(selector,context){            return new jQuery.fn.init(selector,context,rootjQuery);
        };     
    //jQuery相关实例方法和属性
    jQuery.fn=jQuery.prototype={
        jQuery:core_version,//其实就是版本字符串2.0.3
        constructor:jQuery,//还原constructor指向
        selector:'',//含有连续的整型属性、length属性、context属性,selector属性(在jQuery.fn.init中设置),preObject属性(在pushStack中设置)
        length:0,
        init:function(selector,context,rootjQuery){            var match,elem;           //selector是选择器表达式
           if(!selector){            return this;
           }           if(typeof selector ==='string'){
                match=idExpr.exec(selector);
                context=context||document;                if(match){
                    elem=context.getElementById(match[1]);                    if(elem&&elem.parentNode){                        this[0]=elem;                        this.length=1;                        
                    }                    this.selector=selector;                    this.context=document;                    return this;
                }else{                    //说明是复杂的选择器表达式,这里只考虑javascript原声方法                    //querySelectorAll返回所有匹配元素的nodelist                    //querySelector返回匹配的第一个元素
                    return jQuery.merge(this,context.querySelectorAll(selector));
                    
                }                
           }           //处理selector是DOM元素的情形
           if(selector&&selector.nodeType){                this[0]=selector;                this.length=1;                this.context=selector;                return this;
           }           //处理selector是函数的情形
           if(jQuery.isFunction(selector)){                return rootjQuery.ready( selector );
           } 
           //处理selector是jQuery对象的情形
           if(selector.selector){                this.selector=selector.selector;                this.context=selector.context;
           }           //处理其他情形
           return jQuery.makeArray(selector,this);

        },        //将jQuery类数组对象转换为数组        toArray:function(){            return core_slice.call(this);
        },        //如果传递了参数num,代表获取下标num的DOM元素(num可以为负数)        //如果没有传递num,则将jQuery对象转换为数组后整体返回
        get:function(num){            if(num==null){//注意这里不能用!num,因为num可以为0
                return this.toArray();
            }            return num<0?this[num+this.length]:this[num];
        },        //入栈        pushStack:function(elems){            var ret=jQuery.merge(this.constructor(),elems);
            
            ret.prevObject=this;
            ret.context=this.context;            return ret;
        },        //遍历jQuery对象        each:function(callback,args){            //在静态方法已经指定了callback的执行上下文
           return jQuery.each(this,callback,args);
        },        //加载完成事件方法,这里暂不考虑        ready:function(fn){},
        slice:function(){      
            //注意apply和call的区别                          
            return this.pushStack(core_slice.apply(this,arguments));
        },
        first:function(){            return this.get(0);
        },
        last:function(){            return this.get(-1);
        },
        eq:function(i){            var length=this.length,
                j=+i+(i<0?length:0);            return this.pushStack(j>=0&&j0&&(length-1) in obj))){            return true;
        }        
        return false;
    }
    jQuery.extend({        //一堆静态方法和属性
        expando:'jQuery'+(core_version+Math.random()).replace(/\D/g,''),        // 该函数用于释放jQuery对于全局变量$的控制权,可选的参数deep代表是否释放对全局变量jQuery的控制权        noConflict:function(deep){            if(window.$===jQuery){
                window.$=_$;
            }            if(deep&&window.jQuery===jQuery){
                window.jQuery=_jQuery;
            }            return jQuery;
        },        /********isReady,readyWait,holdReay,ready与加载事件有关,暂且略过***********/
        isReady:false,
        readyWait:1,
        holdReady:function(hold){},
        ready:function(){},        /*******/


        /****下面是一系列类型检测的静态方法*******/
        isFunction:function(obj){            //如果使用typeof,在有些浏览器中,正则也会返回function,因此这里采用jQuery处理后的方法,jQuery.type
            return jQuery.type(obj)==='function';
        },
        isArray:Array.isArray,
        isWindow:function(obj){            return obj!==null&&obj===obj.window;
        },        //判断obj是否为数字或者数字类型的字符串,并且是有效数字        isNumeric:function(obj){            return !isNaN(parseFloat(obj))&&isFinite(obj);
        },
        type:function(obj){            if(obj===null){                return String(null);
            }            //Date,Array等类型typeof都会返回object,function、正则(部分浏览器)中 typeof都会返回function
             
            if(typeof obj==='object'||typeof obj==='function'){                
                return class2type[core_toString.call(obj)]||'object';
            }            return typeof obj;
        },        //判断是否为以下两种情况:1,对象字面量;2,通过new Object()创建        isPlainObject:function(obj){            if(jQuery.type(obj)!=='object'||obj.nodeType||jQuery.isWindow(obj)){                return false;
            }            //如果是纯粹的对象,那么obj一定有constructor属性,并且方法hasOwnPropertyOf一定就在构造函数本身的原型中,而不用通过原型链查找得到
           if(obj.constructor&&!core_hasOwn.call(obj.constructor.prototype,'isPrototypeOf')){                return false;
           }           return true;

        },        //检查是否是空对象        isEmptyObject:function(obj){            for(var name in obj){                return false;
            }            return true;
        },        /******类型检测静态方法结束********/

        error:function(msg){            throw new Error(msg);
        },        //将html字符串转换为html DOM结构,        parseHTML: function( data, context, keepScripts ){

        },
        parseJSON:JSON.parse,
        parseXML:function(data){            var xml, tmp;            if ( !data || typeof data !== "string" ) {                return null;
            }            // Support: IE9
            try {
                tmp = new DOMParser();
                xml = tmp.parseFromString( data , "text/xml" );
            } catch ( e ) {
                xml = undefined;
            }            if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
                jQuery.error( "Invalid XML: " + data );
            }            return xml;
        },
        noop:function(){},        //用于在全局作用域执行javascript代码,这里暂略        globalEval:function(data){},        //转换连字符字符串为驼峰类型
        camelCase:function(string){            return string.replace(rmsPrefix,'ms-').replace(rdashAlpha,fcamelCase);
        },        //判断elem的nodeName是否=name        nodeName:function(elem,name){            return elem.nodeName&&elem.nodeName.toLowerCase()==name.toLowerCase();
        },        //jQuery遍历方法,其中args是传递给回调callback的参数,仅供jQuery内部使用;外部调用该方法时,回调的参数默认为数组下标/对象key,对应数组值/对象value
        each:function(object,callback,args){            var i,
                value, 
                length=object.length,
                isArray=isArrayLike(object);            if(args){//说明是内部调用
                if(isArray){                    for(i=0;i-1){                                 
                                    list.splice(i,1);//删除上的数值                                                            }
                        });
                    }                    return this;
                },               
                //fn有值的时候,代表判断回调函数列表是否存在函数fn                //没有参数fn的时候,代表判断回调函数列表是否为空                has:function(fn){                    return fn?jQuery.inArray(fn,list)>-1:!!(list&&list.length);
                },                
                empty:function(){                    if(list){
                        list=[];
                    }                    return this;
                },
                disable:function(){                    //list就不用说了,list置为undefined之后,几乎所有的方法都不能调用                    //memory恢复初始值undefined
                    list=memory=undefined;                    return this;
                },
                disabled:function(){                    return !list;
                },
                fireWith:function(context,args){                    if(list&&!(once&&fired)){
                        args=args||[];//主要是为了处理args为undefined的情况
                        args=[context,args.slice?args.slice():args];
                        fire(args);                        
                    }                    return this;
                },
                fire:function(){
                    self.fireWith(this,arguments);                    return this;
                },
                fired:function(){                    return !!fired;
                },                //自己加的函数,供调试用                getList:function(){                    return list;
                }
            };            return self;
   };   //实现异步队列Defered,When   //异步队列内部维护了三个回调函数列表,分别是成功,失败,消息   jQuery.extend({        //func参数仅内部使用,func的调用者是jQuery.Deferred的返回值,参数也是        Deferred:function(func){            var doneList=jQuery.Callbacks('once memory'),
                failList=jQuery.Callbacks('once memory'),
                progressList=jQuery.Callbacks('memory'),
                state='pending',
                list={                    'resolve':doneList,                    'reject':failList,                    'notify':progressList
                },
                promise={
                    done:doneList.add,
                    fail:failList.add,
                    progress:progressList.add,                    
                    state:function(){                        return state;
                    },                    //同时添加成功,失败,消息回调函数                    then:function(doneCallback,failCallback,progressCallback){
                        deferred.done(doneCallback).fail(failCallback).progress(progressCallback);
                    },                    //成功,失败时,添加同一个处理函数                    always:function(){
                        deferred.done(arguments).fail(arguments);
                    },                    //说实话,能看懂这个源代码,但搞不太懂这个pipe是干嘛用的                    //实际使用中调用的地方也不多                    //不过其源代码有不少知识点值得学习                    pipe:function(fnDone,fnFail,fnProgress){                        //这里的newDefer,就是调用jQuery.Deferred(function(newDeferred))返回的异步队列对象,由这部分代码最终的func.apply(deferred,deferred)决定;
                        return jQuery.Deferred(function(newDefer){

                            jQuery.each({
                                done:[fnDone,'resolve'],
                                fail:[fnFail,'reject'],
                                progress:[fnProgress,'notify']                              
                            },function(handler,data){                                //注意这三个局部变量定义的位置,只能定义在该闭包中,如果定义在jQuery.Deferred得到的只是函数最后的值,如果没有传递fnProgress,就会报出undefined的错误
                                var action=data[1],
                                    fn=data[0],
                                    returned;                                if(jQuery.isFunction(fn)){                                    //通过done,fail,progress添加的方法,只有在对应的回调函数队列fire的时候才会触发                                    deferred[handler](function(){                                        //这里的this,arguments是调用fire/fireWith时候传递                                        //这里的this可以通过fireWith中指定context,arguments也是fire/fireWith的时候传递的参数                                        
                                        returned=fn.apply(this,arguments);                                        //如果函数的返回值依旧是一个异步队列,则将jQuery.pipe返回的异步队列的成功,失败,消息回调添加到返回的retuned对应的回调列表中
                                        if(returned&&jQuery.isFunction(returned.promise)){
                                            returned.promise().then(newDefer.resolve,newDefer.reject,newDefer.notify);
                                        }else{                                            //如果函数返回值不是异步队列,则jQuery.pipe()返回的异步队列对应状态的方法立即触发
                                            newDefer[action+'With'](this===deferred?newDefer:this,[returned]);
                                        }
                                    });
                                }else{
                                    deferred[handler](newDefer[action]);
                                }

                            });
                        }).promise();
                    },                    //注意promise()和promise({})这两种写法是完全不同的,前者返回异步对象的只读版本,后者返回一个副本                    promise:function(obj){                        return obj==null?promise:jQuery.extend(obj,promise);
                    },  
                },
                deferred=promise.promise({}),
                key;                //为deferred添加状态改变的相关函数,与fire,fireWith相对应
            for(key in list){
                deferred[key]=list[key].fire;
                deferred[key+'With']=list[key].fireWith;
            }
            deferred.done(function(){
                state='resolved';
            },failList.disable,progressList.disable)
            .fail(function(){
                state='rejected';
            },doneList.disable,progressList.disable);                
            if(func){                //这句话决定了,通过jQuery.Deferred(func)调用的时候,func的context和参数                func.call(deferred,deferred);
            }            return deferred;

        },
        When:function(firstParam){            var resolveArgs=core_slice.call(arguments,0),//用来存放成功参数
                length=resolveArgs.length,
                count=length,//维护一个计数器
                progressArgs=new Array(length),//用来存放消息参数
                i=0,                //只有当在只有一个参数,并且该参数是延迟对象的情况下,主延迟对象等于该第一个参数,否则新建一个主延迟对象
                deferred=length<=1&&firstParam&&jQuery.isFunction(firstParam.promise)?firstParam:jQuery.Deferred(),
                promise=deferred.promise();            if(length>1){                for(;i1?core_slice.call(arguments):value;                    //每一次参数延迟对象的resolve触发,都令count的值减去一
                    if(!--count){                        //如果计算器变为0,那么主延迟对象的resolve方法触发                        deferred.resolveWith(deferred,resolveArgs);
                    }
                }
            }
            function progressFunc(i){                return function(value){
                    progressArgs[i]=arguments.length>1?core_slice.call(arguments):value;
                    
                    deferred.notifyWith(promise,progressArgs);
                }
            }            return promise;
        }
    
   });/*********数据缓存模块****************************************///数据缓存模块的整体思路//2.0.3版本的jQuery较之于1.7.3版本,使用面向对象的写法重构了数据缓存Data模块//数据缓存模块的整体依据是://data_user和data_priv在一次运行期间只有对应的唯一对象,所有DOM元素的缓存都基于这两个实例对象完成//data_user与data_priv这两个Data实例有各自的缓存对象属性cache,分别用于存储用户自定义数据和内部数据//以data_user为例,在向对应的data_user对应的缓存对象cache中保存数据时,会为每个DOM元素分配一个唯一的id,该id作为该DOM元素的附加属性//该唯一id(初始值为0,之后一次加1)会附加到DOM元素上,对应的DOM元素的属性名是data_user.expando,其对应的属性值就是id//同时,会把该id作为属性名添加到data_user的缓存对象属性cache中,对应的属性值是一个都object对象,该对象称为DOM元素的数据缓存对象,其中存储着属性名和属性值的映射//这样,通过分配唯一的id把DOM元素和该DOM元素的数据缓存对象关联起来//data_priv与之类似

    
    var data_priv,data_user,
        rbrace=/^(?:\{\s\S*\}|\[\s\S*\])$/,//匹配json字符串格式,诸如{},或者[],不用.*进行匹配的原因是.不能匹配换行符
        rmultiDash=/([A-Z])/g;//匹配任意的大写字母    

    function Data(){        //jQuery.expando是jQuery的静态属性,对于jQuery的每次加载运行期间时唯一的        //Math.random生成一个0-1之间的随机数
        this.expando=jQuery.expando+Math.random();        this.cache={};        //这里采用访问器属性的写法        //常用的写法是Object.defineProperty(对象,对象属性,{[[get]],[[set]],[[configurable]],})        //这句话的目的,this.cache中的0属性是个只读属性
        Object.defineProperty(this.cache,0,{            get:function(){                return {};
            }
        });
    }    //下面可以看到,只有当accepts为false的时候,返回的id为0
    Data.uid=1;
    Data.accepts=function(owner){        //只有DOM元素,document元素,以及普通的js对象可以操作数据缓存
        return owner.nodeType?owner.nodeType===1||owner.nodeType===9:true;
    };
    Data.prototype={        //获取(设置)owner对应的id,如果没有,则为其this.expando对应的属性,值为id,并未其在this.expando中创建缓存对象        key:function(owner){            if(!Data.accepts(owner)){                return 0;
            }            var expando=this.expando,
                id=owner[expando];            if(!id){
                id=Data.uid++;                //为owner定义expando属性,为了保证该属性不可遍历且只读,使用访问器属性进行定义                //defineProperty一次只定义一个属性,接受三个参数,对象,属性名,属性描述对象                //defineProperties可以通过描述符一次定义多个属性,接受两个参数                //具体用法可以参照讲解http://www.tuicool.com/articles/ju26riE                Object.defineProperty(owner,expando,{
                    value:id,
                }); 
            }            if(!this.cache[id]){                this.cache[id]={};
            }            return id;
        },        //为DOM元素对应的缓存设置数据        //data参数可以是字符串,也可以是对象/数组,当data是对象/数组的时候,value可以不赋值
        set:function(owner,data,value){            var id=this.key(owner),            //该DOM元素对应的缓存对象
                cache=this.cache[id],
                key;            if(typeof data==='string'){
                cache[data]=value;
            }else{                for(key in data){
                    cache[key]=data[key];
                }
            }            return cache;
        },        //获取DOM元素owner对应缓存中属性key的值        //如果参数key不赋值,则代表去除owner对应的对象缓存
        get:function(owner,key){            var id=owner[this.expando],
                cache;                if(!id){                    return undefined;
                }
                cache=this.cache[id];                return key?cache[key]:cache;
        },        //设置或获取        access:function(owner,key,value){            var tmp;            if(!key||((key&&typeof key==='string') &&!value)){//说明是获取                //先尝试key本身,不行的话尝试转驼峰
                tmp=this.get(owner,key);                return tmp? tmp: this.get(owner,jQuery.camelCase(key));
            }            //否则说明是设置
            this.set(owner,key ,value);            return value ? value : key;

        },        //如果没有传入参数key,则移除DOM元素或者javascript元素关联的所有数据        //如果传入了参数key,则移除关联的指定名称的数据        //如果key是数组或者空格分割的多个数据名,则一次可以删除多个数据,删除的时候还需要尝试camel转换之后的形式        remove:function(owner,key){            var i,camel,length,
                id=this.key(owner),
                cache=this.cache[id];            if(!key){                this.cache[id]={};
            }else{                 //可能是数组,可能是字符串,该字符串还可能使用空格分开
                 if(typeof key ==='string'){
                    key=key.match(rnotwhite);//转换为数组形式                 }
                 key=key.concat(jQuery.map(key,jQuery.camelCase));                 for(i=0,length=key.length;iDOM元素的连续数字ID——>1)typequeue:[队列函数列表],    //2)typequeueHooks:A)empty,对应一个callbacks回调函数,该回调函数在队列函数列表执行完毕的时候被调用,执行的操作包括:a)清楚缓存typequeue和typequeueHooks;    //b)如果还调用了promise,则为hooks的empty对应的回调函数callbacks添加了令监控计数器count减1的操作;    //c)如果还调用了delayed,回味typeHooks添加stop属性,用于终止timeout延时计算器    

    //队列模块的代码结构        jQuery.extend({        //该方法用于返回或者修改匹配元素关联的函数队列,根据传入参数的不同,函数实现的功能也有所不同        //这是一个低级方法,外部调用的时,应该用.queue替换        //queue(elem,[type])返回匹配元素关联的函数队列        //queue(elem,type,newQueue)参数data是函数数组,此时用newQueue替换当前队列        //queue(elem,type,callback())参数data是函数,此时将callback添加到当前的函数队列中        queue:function(elem,type,data){            var type=type||'fx'+queue,
                queue=data_priv.get(elem,type);            if(data){              //说明是设置操作
              if(!queue||jQuery.isArray(data)){                //必须使用jQuery.makeArray,针对!queue且data是function的情况                data_priv.access(elem,type,jQuery.makeArray(data));
              }else{
                queue.push(data);
              }
            }            return queue||[];
        },        //jQuery中的队列不同于队列定义的是,jQuery的队列不仅支持函数的入队和出队操作,出队的函数还会自动调用        dequeue:function(elem,type){             
            var type=type||'fx',
                queue=jQuery.queue(elem,type),
                hooks=jQuery._queueHooks(elem,type),
                startLength=queue.length,
                fn=queue.shift(),                //不能令next=jQuery.dequeue,因为不能指定参数啊啊啊
                next=function(){
                    jQuery.dequeue(elem,type);
                };          
            //这个inprogress搞不太懂,回头结合动画effects模块一起看吧
            if(fn==='inprogress'){
                fn=queue.shift();
                startLength--;
            }            if(fn){                //同样,inprogress搞不懂,看动画模块如何让inprogress出队吧
                if(type==='fx'){
                    queue.unshift('inprogress');
                }                //取消hooks上的定时器,这个依旧搞不太懂,结合delay一起看吧                delete hooks.stop;                //先不考虑动画和延时                
                fn.call(elem,next,hooks);
            }            //注意上面fn.call之后startLength并没有-1            //测试的结果是,只有在队列已经为空的情况下,再次调用dequeue进行出队,才会触发缓存清除的empty操作
            if(!startLength&&hooks){
               hooks.empty.fire();
            }

        },
        _queueHooks:function(elem,type){            var hookKey=type+'queueHooks';            return data_priv.get(elem,hookKey)||data_priv.access(elem,hookKey,{
                empty:jQuery.Callbacks('once memory').add(function(){
                    data_priv.remove(elem,[type+'queue',hookKey]);
                    console.log('empty call');
                })
            });            
        }
    });
    jQuery.fn.extend({        //.queue([queuename]);返回第一个匹配元素关联的函数队列        //.queue([queueName],newQueue);修改匹配元素关联的函数队列,使用函数数组newQueue替换当前队列        //.queue([queueName],callback(next,hooks));修改匹配元素关联的函数队列,添加callback到队列中        //如果queueName省略,则默认是动画队列fx        queue:function(type,data){            var setter=2;            if(typeof type!=='string'){                //进行参数修正
                data=type;
                type='fx';
                setter--;
            }            //靠,这种判断是获取还是设置的点子是怎么想出来的
            if(arguments.length					

相关专题

更多
Java 项目构建与依赖管理(Maven / Gradle)
Java 项目构建与依赖管理(Maven / Gradle)

本专题系统讲解 Java 项目构建与依赖管理的完整体系,重点覆盖 Maven 与 Gradle 的核心概念、项目生命周期、依赖冲突解决、多模块项目管理、构建加速与版本发布规范。通过真实项目结构示例,帮助学习者掌握 从零搭建、维护到发布 Java 工程的标准化流程,提升在实际团队开发中的工程能力与协作效率。

10

2026.01.12

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

102

2026.01.09

c++框架学习教程汇总
c++框架学习教程汇总

本专题整合了c++框架学习教程汇总,阅读专题下面的文章了解更多详细内容。

60

2026.01.09

学python好用的网站推荐
学python好用的网站推荐

本专题整合了python学习教程汇总,阅读专题下面的文章了解更多详细内容。

139

2026.01.09

学python网站汇总
学python网站汇总

本专题整合了学python网站汇总,阅读专题下面的文章了解更多详细内容。

13

2026.01.09

python学习网站
python学习网站

本专题整合了python学习相关推荐汇总,阅读专题下面的文章了解更多详细内容。

19

2026.01.09

俄罗斯手机浏览器地址汇总
俄罗斯手机浏览器地址汇总

汇总俄罗斯Yandex手机浏览器官方网址入口,涵盖国际版与俄语版,适配移动端访问,一键直达搜索、地图、新闻等核心服务。

92

2026.01.09

漫蛙稳定版地址大全
漫蛙稳定版地址大全

漫蛙稳定版地址大全汇总最新可用入口,包含漫蛙manwa漫画防走失官网链接,确保用户随时畅读海量正版漫画资源,建议收藏备用,避免因域名变动无法访问。

477

2026.01.09

php学习网站大全
php学习网站大全

精选多个优质PHP入门学习网站,涵盖教程、实战与文档,适合零基础到进阶开发者,助你高效掌握PHP编程。

52

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
jQuery 教程
jQuery 教程

共42课时 | 4.1万人学习

HTML+CSS基础与实战
HTML+CSS基础与实战

共132课时 | 9.4万人学习

tp6+adminlte搭建通用后台
tp6+adminlte搭建通用后台

共39课时 | 5.7万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号