零基础打造自己的 js 类库(1)

写作不易,转载请注明出处,谢谢。

文章类别:Javascript基础(面向初学者)

前言

在之前的章节中,我们已经不依赖jQuery,单纯地用JavaScript封装了很多方法,这个时候,你一定会想,这些经常使用的方法能不能单独整理成一个js文件呢?

当然可以,封装本来就是干这个用的。放在一个单独js文件里固然不错,其实我们也可以单独整一个js类库,一方面可以锻炼一下自己封装方法的能力,另一方面,也可以将自己学到的东西做一个整理。

出于这个目的,本文将介绍如何封装一个简单的js类库。

1. 总体设计

所谓的js库,其实也就是一个js文件,我思前想后,决定取个名字叫“miniQuery”,是不是山寨的味道十足呢?哈,请不要在意这些小细节。

大概的设计如下:

  1. 扩展方法的兼容(主要写一些兼容的扩展方法,比如 forEach 方法等)

  2. 工具包定义 (就是之前封装的utils.js,我们的miniQuery需要依赖这个工具包,为了方便,就干脆写在一个文件里面了。)

  3. miniQuery定义

2. 扩展方法的兼容

// ------------------------ 基本扩展, 字符串,数组等---------------------------------//
function extend_base (){
    
    if(!String.prototype.format ){
        String.prototype.format = function() {
            var e = arguments;
            return this.replace(/{(\d+)}/g,function(t, n) {
                return typeof e[n] != "undefined" ? e[n] : t
            })
        };
    }
    
    if (!Array.prototype.forEach && typeof Array.prototype.forEach !== "function") {
        Array.prototype.forEach = function(callback, context) {
           // 遍历数组,在每一项上调用回调函数,这里使用原生方法验证数组。
           if (Object.prototype.toString.call(this) === "[object Array]") {
               var i,len;
               //遍历该数组所有的元素
               for (i = 0, len = this.length; i < len; i++) {
                   if (typeof callback === "function"  && Object.prototype.hasOwnProperty.call(this, i)) {
                       if (callback.call(context, this[i], i, this) === false) {
                           break; // or return;
                       }
                   }
               }
           }
        };
    }
    
    if(!String.prototype.format ){
        Array.isArray = function(obj){
            return obj.constructor.toString().indexOf('Array') != -1;
        }
    }


    //待补充 ...
    
}

我们定义一个extend_base方法,里面主要对js内置对象的api做了一些兼容性补充,目前还不完善,只有寥寥几个方法。当然,如果你不考虑IE678的话,那么基本上不需要这一部分了。

定义完成后立即调用。

extend_base(); 

2. 工具包整合

// ------------------------ 工具包---------------------------------//
var utils = {
            center : function(dom){
                dom.style.position = 'absolute';
                dom.style.top = '50%';
                dom.style.left = '50%';
                dom.style['margin-top'] = - dom.offsetHeight / 2 + 'px';
                dom.style['margin-left'] = - dom.offsetWidth / 2 + 'px';
            },
        
            /** dom相关 * */
            isDom : ( typeof HTMLElement === 'object' ) ?
                function(obj){
                    return obj instanceof HTMLElement;
                } :
                function(obj){
                    return obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string';
                } ,
             
            /** 数组相关 * */
            isArray : function(obj){
                return obj.constructor.toString().indexOf('Array') != -1;
            }
            
        }
  • center :控制dom元素相对于父盒子居中
  • isDom :判断是否为dom元素
  • isArray :判断是否为数组

3. miniQuery总体设计

终于到miniQuery了,在写代码之前,先简单说一下自执行函数。

可能你在很多书上,或者下载的源码里面,经常会看到这样的代码:

(function(){
    
})();

这样子你或许觉得很奇怪,没事,我们一起来分析。

《JavaScript: 零基础轻松学闭包(1)》 里面已经说过,在js中,你如果把函数看作一个数据类型,和其他语言中的 Integer, Float , String等等一样,就会理解很多事情了。当然,其实在js中,函数本身就是一个对象,不然的话就不会出现call方法了。因为只有对象才可以调用方法嘛。不过,大部分情况下,你把函数理解为数据类型就可以了。

匿名函数:

function(){
    
}

这是一个函数,因为没有函数名,所以是一个匿名函数。你定义了它,如果接下来你不想通过函数调用的方式来执行它,那么是不是可以直接给它打一个括号来执行呢?

像这样:

function(){
    
}();

不过,因为js语法的关系,这样子是不能执行的,你需要用一对圆括号来包一下:

(  
    function(){
    alert("你好!");
    }()  
) ;
Paste_Image.png

这样就可以了,下面是另一种写法:

(  
    function(){
    alert("你好!");
    }  
)();

这样也可以,这种写法会更多一点。它的意思就是说,我不关心你这个函数叫什么名字,反正你在被定义的时候就要给我执行,这就是所谓的自执行函数。

好,问题来了,怎么加参数呢?

以前我们习惯于这么写:

 function say(str){
    alert(str);
 } 

say("你好!");

依葫芦画瓢

(  
    function(str){
       alert(str);
    }  
)("你好!");

OK了。

是不是一样的意思呢?

没啥区别,以前怎么做,现在还怎么做,无非就是一个函数传参的事情罢了。

我们将圆括号的位置调整一下

( function(str){
    alert(str);
} )("你好!");

这样差不多就是最终的版本了,我记得初学js的时候,看这种代码很吃力,好像在看外星语言一样,后来看多了也就习惯了。

自执行函数就是这么一回事,没什么大不了的。

有了上面的解释,以后如果你再遇到这种写法,就 so easy 啦。

所以,不要再恐惧了,它就是这么回事,没什么大不了的,我这么后知后觉的人都能写,你也可以。我花了半年的时间才看明白,我相信你现在只需要几分钟。我的意思是,如果你之前不知道这些的话。

那么,什么时候用自执行函数呢?

当你觉得某个函数只需要执行一次,而且不需要在其他地方调用的时候,就用。

你可能会问了,我干嘛要这样写啊,反正就执行一次,我直接把实现代码写在外面不就行了?

原因很简单,因为那样的话,你定义的变量就会是全局的,而一般来说我们设计的原则是尽量不要使用全局变量。

而采用这种方式,我们就形成了一个匿名函数,函数的定义又会形成闭包,所以比较安全和简洁。

你可能还会觉得疑惑,我干嘛要这些写,如果我非要给函数取一个名字,然后马上调用呢?

额,其实我个人认为这也是没有问题的,但是你得费一番心思去给函数取名字,取 a,b,c,d 这样的名字肯定是不好的。那么,我私以为,还不如干脆就用匿名函数算了,省得麻烦。

如果这部分知识你以前就不知道,那么我建议你把这篇文章多看几遍,反正就是那么回事,没什么大不了的。我当初就是走了很多弯路,也没有人教我,只有靠自己在那瞎摸索和各种百度,当然,现在想想很简单了。

我们的miniQuery的定义就放在这个自执行函数里面,这样一来,只要有人调用了这个js文件,就能调用miniQuery函数了。

当然,你直接放在外面其实也没事,因为反正就一个方法,而且这个方法本来就是要暴露出去的。

这边为了说明自执行函数,就硬加进来了。

我们把miniQuery的定义丢进去。

比如,像这样子的:

(function(){
   
   var miniQuery = function(){
    alert('Hello miniQuery!');
   }
   
})();

我们尝试在外面调用:

miniQuery();

很遗憾,调不到。

Paste_Image.png

我们再回顾一下代码:

(function(){
   
   var miniQuery = function(){
        alert('Hello miniQuery!');
   }
   
})();

miniQuery();

原来,miniQuery是存在于一个闭包中的,它可以访问到父级作用域的变量,但是反过来就不行,除非函数自己用 return 的方式将私有数据暴露出去。这些在之前的关于闭包的文章里面已经解释过了,这里不再赘述。

解决方法有很多,比如,最简单的,我们直接把var去掉,这样就会发生一次变量提升,miniQuery被升级为全局变量,挂在window对象上面。

(function(){
   
   miniQuery = function(){
    alert('Hello miniQuery!');
   }
   
})();

miniQuery();
Paste_Image.png

成了,简单明了,干干净净。

虽然我觉得很有道理,但是我看别人的代码,他们封装自己的js库的时候,几乎没有这样做的,因此我们也采用一种大众的做法。

即,我们把window作为参数传进去,然后手动将miniQuery挂上去。

(function(win){
   
   var miniQuery = function(){
    alert('Hello miniQuery!');
   }

   win.miniQuery = miniQuery;
   
})(window);

miniQuery();
Paste_Image.png

是不是也可以呢?

如果你觉得每次写miniQuery太麻烦,那么我们可以给它换一个名字,比如 $

(function(win){
   
   var miniQuery = function(){
    alert('Hello miniQuery!');
   }
   
   
   win.$ = miniQuery;
   
})(window);

$();

这样就差不多了。

4. miniQuery 包裹对象

我们先弄来一个测试用的网页:

.wrap {
    width:80px;
    height:80px;
    background:darkslateblue;
    margin:20px;
    border-radius: 2px;
}
<body>
    <div class='boxes'>
        <div id='box1' class='wrap'></div>
        <div id='box2' class='wrap'></div>
        <div id='box3' class='wrap'></div>
    </div>
</body>
Paste_Image.png

举一个例子,现在我们要获取id为box1的盒子,并把它的背景色改为红色。

用js代码,我们会这样做:

var box2 = document.getElementById('box2');
box2.style.backgroundColor = 'red';

思路很清晰,分为简单的两步:

第一步:获取dom对象。
第二部:设置其背景色为红色。

同样的,我们的 miniQuery 也要这么做,首先得获取对象,然后进行操作。就好像你做饭,首先得有米面吧。所谓巧妇难为,无米之炊。

于是,我们有了下面的代码:

var miniQuery = function(selector){
   var miniQuery = document.getElementById(selector);
   console.log(miniQuery);
}

selector 代表选择器,它只是一个参数名字,参数列表的名称是可以自己定义的。你写 aaa , bbb , ccc 都没问题,只要你愿意的话。

我以前经常看别人写的代码,参数里面有callback,现在我知道是回调函数的意思。可是我以前不知道,然后就觉得很困惑,作为一个英语比日语还差的js玩家,我感到很那个啥。

其实无所谓,只是一个名字而已,你写什么都行,只要符合标识符的命名规范就成。

总有人觉得,看到参数里边写了context(上下文),callback(回调函数)这样的词汇,就觉得很困惑。

不要困惑啦,不要再惊恐啦,它就是一个名称罢了!

。。。

额,扯远了,继续回来。

我们在外面调用miniQuery ~

window 上面挂的是 $ , 其实就是 miniQuery

$('box1');

运行结果

Paste_Image.png

嗯,确实取到了呢。

接下里,我们给dom元素变更背景色为红色。

var miniQuery = function(selector){
   var miniQuery = document.getElementById(selector);
   miniQuery.style.backgroundColor = 'red';
}
Paste_Image.png

效果确实出来了。

可是呢,如果用户过几天又来个需求,说我要把box1的宽度变为之前的两倍,你怎么办?

总不可能去修改源码吧!

这时候,我们就可以考虑能不能通过一个什么办法,我先用miniQuery把你传进来的东西包装成dom元素,保存起来返回给你,同时再给你返回一大堆方法,比如改变高度啊,添加背景色啊等等。那么,操作的就是之前保存的元素了。也就是你一开始希望操作的元素。

这是一个很好的想法,我们经过代码的重写,最终产生了这样的一个miniQuery函数:

var miniQuery = function(selector){
    var miniQuery = document.getElementById(selector);
    
    return {
        obj : miniQuery , //将dom元素保存起来,再返回给你
        
        // ------------------------ css 相关 ------------------------//
        backgroundColor : function(color){
            this.obj.style.backgroundColor = color;
        }
    }
}
   
   win.$ = miniQuery;
   
})(window);

我们再调用一次,看看这回它给我们返回的是什么东东?

var $box = $('box1');
console.log($box);
Paste_Image.png

可见,它给我们返回的是一个json对象,里面有 obj 变量和 backgroundColor 函数。这样的好处就是极大的扩展了我们的miniQuery,你给我一个选择器,我就包起来,然后不仅把它返回给你,而且还给你各种api方法!

于是我们就可以直接调用 backgroundColor 函数了。

var $box = $('box1');
$box.backgroundColor('red');
Paste_Image.png

成了。

我们现在返回的,不是一个单纯的dom元素,dom元素只是它的一部分。可以说,我们返回给用户的是一个miniQuery对象!

篇幅关系,先介绍到这里吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 1.几种基本数据类型?复杂数据类型?值类型和引用数据类型?堆栈数据结构? 基本数据类型:Undefined、Nul...
    极乐君阅读 5,503评论 0 106
  • 吃货的碰撞(2) 不知道是心理作用还是什么原因,后背上的那份灼热感依然存在,不安的感觉越来越重,邱然从来就不信什么...
    老街小主阅读 185评论 0 0
  • 盼着,盼着, 西风送来了信件。 枯黄色的信纸, 染着秋姑娘的芬芳清香, 一片红枫叶成了秋姑娘的礼物。 淡淡的黄色,...
    沙华_927阅读 269评论 0 0