jq ajax伪数组,GitHub - everlose/jqfree: this is an imitation jquery, it has fewer fuctions,but only 300...

项目说明

项目启动步骤

git clone 下来项目

运行 npm install 安装依赖

全局环境依赖有webpack和webpack-dev-server,没有装过的同学需要 npm install -g webpack 和 npm install -g webpack-dev-server

运行 npm run test 启动项目,并自动访问 localhost:8088/index.html

可以看到首页运行了单元测试的结果

687474703a2f2f37786e346d772e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f31372d342d31382f34383432303536352d66696c655f313439323439383931393136325f323761662e706e67

项目目录说明

|____dist 源代码编译后的生成文件的目录

| |____bundle.js 生成的js文件

|____src

| |____ajax.js ajax函数库

| |____attributes.js dom的属性的增删改查函数

| |____cookie.js cookie操作函数

| |____core.js jqfree核心函数

| |____css.js dom元素上的css样式操作函数

| |____dom.js dom元素自身的增删改函数

| |____effect.js dom元素的展现特效,这里没有加入动画,只有show和hide两个方法

| |____events.js dom的事件的绑定、解绑、触发函数

| |____filter.js dom元素的过滤查找函数

| |____init.js 初始化jqfree对象的函数

| |____main.js 打包代码的入口文件

| |____traverse.js dom元素遍历的函数

| |____utils.js 工具函数

|____test 单测目录

| |____api.json 测试接口返回数据

| |____index.html 测试页面,也是 `localhost:8088/index.html` 页面入口

| |____mocha.css mocha框架带的样式,不需要更改

| |____mocha.js mocha框架带的脚本,不需要更改

| |____test.js 单元测试的脚本文件

|____CHANGELOG.md 项目变更记录

|____package.json

|____README.md

|____webpack.config.js webpack配置函数

jqfree

this is an imitation jquery, it has fewer fuctions, but only 400+ lines of code. jqfree can not be used in project, we just take few time to do some research and practice about jquery。

jqfree core

var $ = function(selector, context) {

return new $.fn.init(selector, context);

};

$.fn = $.prototype;

$.fn.init = function(selector, context) {

if (selector.nodeType === 1) {

this[0] = selector;

this.length = 1;

return this;

}

var parent = context || document;

var nodeList = parent.querySelectorAll(selector);

this.length = nodeList.length;

for (var i=0; i

this[i] = nodeList[i];

}

return this;

};

$.fn.init.prototype = $.fn;

我们需要一个包装着DOM Elements的伪数组,此伪数组对象使用原型链去挂载共享的DOM处理方法,原理如下图。

687474703a2f2f37786e346d772e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f31362d372d33312f32333838313934302e6a7067

//选择器

$('body'); //返回$.fn.init {0: body, length: 1, selector: "body"}

$('.class');

$('#id');

$('#id .class');

extend

jqfree中的extend函数参照了prototype.js的实现方式,$.extend和$.fn.extend功能相同,也都是通过浅拷贝的方式,把第二个参数上的对象扩展添加到第二个参数的对象上,如果没有指定第二个参数,则会把第一个参数添加到this上。需要给DOM元素添加方法的话,使用$.fn.extend如$.fn.append,而需要给全局$对象添加扩展的话,使用$.extend,如$.ajax。

$.extend = $.fn.extend = function (destination, source) {

//if source is not exist,copy the destination to this。

if (typeof source === 'undefined') {

source = destination;

destination = this;

}

for (var property in source) {

if (source.hasOwnProperty(property)) {

destination[property] = source[property];

}

}

return destination;

};

traverse

遍历jqfree对象中的DOM Elements。实际上是遍历了$.fn.init {0: body, length: 1, selector: "body"}这样的一个伪数组中的类似数组的那一部分。

$.fn.extend({

each: function (func) {

var i=0,

length = this.length;

for (; i

func.call(this[i], this[i], i);

}

return this;

},

});

接受一个回调函数,其第一个参数为dom元素,第二个参数为序号,调用代码如

$('body').each(function(val, index){

console.log(val, index)

});

DOM processor。

文档操作。添加了append,prepend,remove,empty的方法,功用同原版jquery。因为生成的$.fn.init是个包含DOM的伪数组,所以操作中就需要遍历这个数组做append操作,目的是为了让选中的所有DOM元素都append一遍。appendChild为DOM level2方法,从IE6开始就支持。

$.fn.extend({

append: function (child) {

if ($.isString(child)) {

child = $(child)[0];

}

this.each(function(v, k) {

v.appendChild(child);

});

child = null;

return this;

},

});

调用代码如

var element = document.createElement('div');

$('body').append(element);

css

添加了css的方法,功用同原版jquery。现将css规则转为驼峰式,然后利用style属性插入,如background-color: #FFF,会被当作dom.style.backgroundColor = '#FFF'这样的插入。

$.fn.extend({

css: function (cssRules, value) {

//cssName need to Converted into camel casing。

var transformHump = function (name) {

return name.replace(/\-(\w)/g, function(all, letter){

return letter.toUpperCase();

});

};

if ($.isString(cssRules)) {

if ($.isUndefined(value)) {

return document.defaultView.getComputedStyle(this[0], null)

.getPropertyValue(cssRules);

} else {

this.each(function(v, k) {

v.style[transformHump(cssRules)] = value;

});

}

} else {

for (var i in cssRules) {

this.each(function(v, k) {

v.style[transformHump(i)] = cssRules[i];

});

}

}

return this;

},

});

支持两种写法,参数1和参数2可以互为键值对,或者参数1为一个对象,另外这里只第一个dom元素的css规则生效。调用代码如

// 获取选中的第一个元素的某个css属性,其属性名不需要驼峰式写法

$('body').css('font-size');

//设置div元素的css属性。提供了以下两种写法。

$('div').css('color', '#FFF');

$('div').css({

color: '#FFF',

background: 'green'

});

DOM filter

添加了dom过滤的几个函数,如children、parent、siblings。返回出去的DOM对象会再次被$.fn.init对象包装。

$.fn.extend({

children: function (selector) {

return $(selector, this[0]);

}

});

只对第一个DOM元素生效,调用代码如下:

$('body').children('.class'); //获取第一个body元素下的所有class名为'.class'的元素

attributes

获取属性,实现了attr,removeAttr,addClass,hasClass,removeClass,data,html这几个api,功能和jq相似。

拿addClass举例来说,classList为H5的API,不支持IE9及以下。所有被匹配的dom元素都会被addClass处理。

$.fn.extend({

addClass: function (className) {

this.each(function(v, k) {

//please use 'v.className += className' if you need support IE9.

v.classList.add(className);

});

return this;

},

});

调用方式如下:

$('body').addClass('someClass');

event

事件操作。绑定事件使用on,取消绑定事件使用off,触发事件使用trigger。拿on举例,直接使用了addEventListener监听,不支持IE8及以下。需要支持IE8级以下的话,请使用attachEvent兼容。

$.fn.extend({

on: function (event, func) {

this.each(function(v, k) {

//dom level 2,IE8 not support。

v.addEventListener(event, func, false);

});

return this;

},

});

第一个参数为事件名,第二个参数为回调,调用代码如下:

$('body').on('click', function(e){

console.log('click');

})

effect

其他效果,鉴于动画用css3会更直观,所以这里只实现了show和hide两个方法。所有匹配的DOM元素都会被影响,这里只是简单设置了display属性为block或者none,有待改进。

$.fn.extend({

show: function() {

this.each(function() {

this.style.display = 'block';

});

return this;

},

});

调用代码如下:

$('body').hide();

ajax

抽离jsonp,$.jsonp独立于$.ajax,毕竟jsonp的原理和ajax完全没有关系,如果使用$.ajax的话有些误导别人。

$.ajax和$.jsonp方法最后都会返回一个Promise对象,。笔者这里直接使用了ES6提供的Promise对象,ES6提供的Promise在chrome从33版开始支持,IE系列并不支持,如果有需要自己写一个Promise的话请打开下面代码中的注释并加入jqfree.js中。此Promise参照了这里的方案。

/* ES6自带了Promise,有需要理解Promise如何实现的话请看这段注释代码

var Promise = function (fn) {

var state = 'pending';

var doneList = [];

var failList= [];

this.then = function(done ,fail){

switch(state){

case 'pending':

doneList.push(done);

//每次如果没有推入fail方法,我也会推入一个null来占位

failList.push(fail || null);

return this;

break;

case 'fulfilled':

done();

return this;

break;

case 'rejected':

fail();

return this;

break;

}

}

function tryToJson(obj) {

var value;

try {

value = JSON.parse(obj);

} catch (e) {

value = obj;

}

return value

}

function resolve(newValue){

state = 'fulfilled';

setTimeout(function(){

var value = tryToJson(newValue);

for (var i = 0; i < doneList.length; i++){

var temp = doneList[i](value);

if (temp instanceof Promise) {

var newP = temp;

for (i++; i < doneList.length; i++) {

newP.then(doneList[i], failList[i]);

}

} else {

value = temp;

}

}

}, 0);

}

function reject(newValue){

state = 'rejected';

setTimeout(function(){

var value = tryToJson(newValue);

var tempRe = failList[0](value);

//如果reject里面传入了一个promise,那么执行完此次的fail之后,将剩余的done和fail传入新的promise中

if(tempRe instanceof Promise){

var newP = tempRe;

for (i=1;i

newP.then(doneList[i],failList[i]);

}

} else {

//如果不是promise,执行完当前的fail之后,继续执行doneList

value = tempRe;

doneList.shift();

failList.shift();

resolve(value);

}

}, 0);

}

fn(resolve,reject);

}

*/

$.extend({

ajax: function (opts) {

var xhr = new XMLHttpRequest(),

type = opts.type || 'GET',

url = opts.url,

params = opts.data,

dataType = opts.dataType || 'json';

type = type.toUpperCase();

if (type === 'GET') {

params = (function(obj){

var str = '';

for(var prop in obj){

str += prop + '=' + obj[prop] + '&'

}

str = str.slice(0, str.length - 1);

return str;

})(opts.data);

url += url.indexOf('?') === -1 ? '?' + params : '&' + params;

}

xhr.open(type, url);

if (opts.contentType) {

xhr.setRequestHeader('Content-type', opts.contentType);

}

xhr.send(params ? params : null);

//return promise

return new Promise(function (resolve, reject) {

//onload are executed just after the sync request is comple,

//please use 'onreadystatechange' if need support IE9-

xhr.onload = function () {

if (xhr.status === 200) {

var result;

try {

result = JSON.parse(xhr.response);

} catch (e) {

result = xhr.response;

}

resolve(result);

} else {

reject(xhr.response);

}

};

});

},

jsonp: function (opts) {

//to produce random string

var generateRandomAlphaNum = function (len) {

var rdmString = '';

for (; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));

return rdmString.substr(0, len);

}

var url = typeof opts === 'string' ? opts : opts.url,

callbackName = opts.callbackName || 'jsonpCallback' + generateRandomAlphaNum(10),

callbackFn = opts.callbackFn || function () {};

if (url.indexOf('callback') === -1) {

url += url.indexOf('?') === -1 ? '?callback=' + callbackName :

'&callback=' + callbackName;

}

if (typeof opts === 'object') {

var params = (function(obj){

var str = '';

for(var prop in obj){

str += prop + '=' + obj[prop] + '&'

}

str = str.slice(0, str.length - 1);

return str;

})(opts.data);

url += '&' + params;

}

var eleScript= document.createElement('script');

eleScript.type = 'text/javascript';

eleScript.id = 'jsonp';

eleScript.src = url;

document.getElementsByTagName('HEAD')[0].appendChild(eleScript);

// window[callbackName] = callbackFn;

//return promise

return new Promise(function (resolve, reject) {

window[callbackName] = function (json) {

resolve(json);

}

//onload are executed just after the sync request is comple,

//please use 'onreadystatechange' if need support IE9-

eleScript.onload = function () {

//delete the script element when a request done。

document.getElementById('jsonp').outerHTML = '';

eleScript = null;

};

eleScript.onerror = function () {

document.getElementById('jsonp').outerHTML = '';

eleScript = null;

reject('error');

}

});

}

});

$.ajax接受一个对象作为参数,并且支持使用promise的写法,调用如下

$.ajax({

url: '/test.json'

})

.then(function (d) {

console.log(d);

return $.ajax({

url: '/test.json'

})

}, function (d) {

console.log(d);

})

.then(function (d) {

console.log(d);

}, function (d) {

console.log(d);

});

//jsonp也可以接受一个url字符串作为参数。

$.jsonp({

url: '/test.json',

})

.then(function (d) {

console.log(d);

return $.jsonp({

url: '/test.json'

})

}, function (d) {

console.log(d);

})

.then(function (d) {

console.log(d);

}, function (d) {

console.log(d);

});

注意,本地没法测试ajax函数,如果有需要请在此项目目录下运行node server.js,接着去打开test.html文件的关于ajax的注释,再去localhost:3000/test.html就能看到测试ajax的内容。

cookie

将增删改查cookie操作都用一个函数搞定

$.extend({

cookie: function (cookieName, cookieValue, day) {

var readCookie = function (name) {

var arr,

reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'),

matched = document.cookie.match(reg);

if(arr = matched) {

return unescape(arr[2]);

} else {

return null;

}

};

var setCookie = function (name, value, time) {

var Days = time || 30;

var exp = new Date();

exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000);

document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString();

};

if (cookieName && cookieValue) {

//set cookie

setCookie(cookieName, cookieValue, day);

} else if (cookieName && $.isNull(cookieValue)) {

//delete cookie

setCookie(cookieName, '', -1);

} else if (cookieName) {

//read cookie

return readCookie(cookieName);

}

}

});

调用代码如下:

//添加cookie,前两个参数为cookie名和值,必填。第三个参数设置cookie有效时常,单位为天,可选。

$.cookie('test', 'content');

//读取cookie,只填第一个参数

$.cookie('test'); //"content"

//删除cookie, 第二个参数填null

$.cookie('test', null);

utils

添加了变量类型判断、时间解析函数、url解析函数、浮点数四舍五入小数位和获取随机位数字符串的辅助函数。

$.extend({

isUndefined: function(obj) {

return obj === void 0;

},

isNull: function(obj) {

return obj === null;

},

isBoolean: function(obj) {

return Object.prototype.toString.call(obj) === '[object Boolean]';

},

isNumber: function(obj) {

return Object.prototype.toString.call(obj) === '[object Number]';

},

isString: function(obj) {

return Object.prototype.toString.call(obj) === '[object String]';

},

isNaN: function(obj) {

return obj !== obj;

},

isFunction: function(obj) {

return typeof obj === 'function';

},

......

});

$.extend({

//$.parseTime(new Date().getTime(), 'YYYY-MM-DD hh:mm:ss')

//result: "2016-08-03 16:14:12"

parseTime: function (timeStamp, format) {

var date = new Date(timeStamp);

var o = {

'M+' : date.getMonth() + 1, //month

'D+' : date.getDate(), //day

'h+' : date.getHours(), //hour

'm+' : date.getMinutes(), //minute

's+' : date.getSeconds(), //second

'S' : date.getMilliseconds() //millisecond

}

if(/(Y+)/.test(format)) {

format = format.replace(RegExp.$1,

(date.getFullYear() + '').substr(4 - RegExp.$1.length));

}

for(var k in o) {

if (new RegExp('('+ k +')').test(format)) {

format = format.replace(RegExp.$1,

RegExp.$1.length == 1 ? o[k] : ('00'+ o[k]).substr((''+ o[k]).length));

}

}

return format;

},

//$.parseUrl(location.href)

//return an object contains the folling info.

parseUrl: function (url) {

var a = document.createElement('a');

a.href = url;

return {

source: url,

protocol: a.protocol.replace(':',''),

host: a.hostname,

port: a.port,

query: a.search,

params: (function(){

var ret = {},

seg = a.search.replace(/^\?/,'').split('&'),

len = seg.length, i = 0, s;

for (;i

if (!seg[i]) { continue; }

s = seg[i].split('=');

ret[s[0]] = s[1];

}

return ret;

})(),

file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],

hash: a.hash.replace('#',''),

path: a.pathname.replace(/^([^\/])/,'/$1'),

relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],

segments: a.pathname.replace(/^\//,'').split('/')

};

},

//$.toFixedFloat(15.658, 2)

//result: 15.66

toFixedFloat: function (value, precision) {

var power = Math.pow(10, precision || 0);

return String(Math.round(value * power) / power);

},

//for generate random string

//$.generateRandomAlphaNum(5)

//random result: like "rc3sr".

generateRandomAlphaNum: function (len) {

var rdmString = '';

for (; rdmString.length < len; rdmString += Math.random().toString(36).substr(2));

return rdmString.substr(0, len);

}

});

调用如下:

//参数1是时间戳,参数2是格式,年为Y,月为M,日为D,时h,分m,秒s,毫秒S,注意大小写,多余的位数补0

$.parseTime(new Date().getTime(), 'YYYY-MM-DD hh:mm:ss'); //"2016-08-03 16:14:12"。

//参数为url链接

$.parseUrl(location.href); //返回一个带诸多url信息的对象。

//参数1是目标浮点数,参数2是保留到第几位小数

$.toFixedFloat(15.658, 2); //四舍五入到两位小数:15.66

//参数为生成随机的字符串长度

$.generateRandomAlphaNum(5); //如"rc3sr"

说明

jqfree纯粹研究用,不考虑诸多兼容。算上注释也就只有400行,可以简单研究一下其代码是如何构建的。从jq毕业一阵子了,总得写点东西纪念下老伙计。github地址在这里,有启发的话请不吝给我的github点赞。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值