第四章:語言模塊

1.字符串的擴展與修復javascript

語言腳本都對字符串特別關注,有關的方法特別多,這些方法有三大類:css

第一類:與標籤無關的實現:charAt,charCodeAt,concat,lastIndexOf,localeCompare,match,replace,slice,split,substr,substring,toLocaleLowerCase,toLocalUpperCase,toLowerCase,toUpperCase以及從Object繼承來的方法。如,toString,valueOfhtml

第二類:與標籤有關的實現,都是對原字符串添加的一對標籤:anchor,big,blink,blod,fixed,fontcolor,italics,link,small,strike,sub,sup前端

第三類是後來添加或未標準化的瀏覽器方法,如trim,quote,toSource,trimLeft,trimRight.其中,trim已經標準化,後四個是FF的私有實現。java


先看看prototypejs添加的擴展,node

gsub,sub,scan,truncate,strip,stripTags,stripScript,extractScripts,evalScripts,escapeHYML,unescapeHTML,parseQuery,toArray,succ,times,camelize,capitalize,underscore,dasherize,inspect,unfliterJSON,isJSON,evalJSON,include,starsWith,endsWith, empty,blank,interpolate。git

其中,gsub,sub,scan與正則相關,直接取ruby的命名程序員

truncate是字符串截取,很是有用github

strip即trim,已標準化ajax

stripTags去掉字符串的標籤對,很是有用。

stripScript是單單把script標籤去掉,露出script.text

escapeHTML與unescapeHTML是用戶輸入輸出的操做定義,很是有用。

parseQuery基本是對url的search部分的操做。

inspect就是在兩端加雙引號,用於構建json。

empty和blank是對空白進行斷定,很簡單的方法。

prototype.js這些框架常常被其它框架抄去,看抄去的功能,咱們就知道哪些方法有價值。

rightJs的字符串擴展:include , black , camelize , capitalize , dasherize , empty, endsWith,evalScripts , extarctScripts, includes, on , startsWidth , stripScripts, stripTags, toFlaot, toInt, trim , underscored

Mootools的字符擴展(原型擴展) test, contains , trim , clean , camelCase, hyphenate , capitalize , escapeRegExp , toInt , toFloat , hexToRgb, rgbToHex , substitute , stripScripts

dojo的字符串擴展, rep , pad , substitute , trim , rep就是repeat方法

Ext字符串擴展,capitalize , ellipsis , escape , escapeRegex format htmlDecode, htmlEncode leftPad, parseQueryString , trim , urlAppend

百度七巧板的字符擴展有:decodeHTML ,encodeHTML , escapeReg , filterFormat , format, formatColor , stripTags , toCamelCase, toHalfWidth , trim , wbr


下面舉例實現方法:

contains方法斷定一個字符串是否包含另外一個字符串。常規思惟,使用正則,但每次都要用new RegExp來構造,太麻煩,性能太差。轉而使用原生字符串方法。如indexOf , lastIdexOf , search 

    function contains (target, it) {
        return target.lastIndexOf(it) != -1; //indexOf改爲search, lastIndexOf也能夠
    }

在mootools版中,咱們看到它支持更多的參數,目的是斷定一個元素的className是否包含某個特定的class。衆所周知,元素能夠添加多個class,中間以空格隔開,使用mootoos的contains就很方便檢測包含關係了。

    function contains (target, str, separator) {
        return separator ? ( separator + target + separator).indexOf(separator + str +separator) > -1 : target.indexOf(str) > -1;
    }

repeat方法: 將一個字符串重複n次,如repeat("ruby",2)獲得rubyruby

版本1:利用空數組的join方法

    function repeat (target, n) {
        return (new Array(n + 1)).join(target);
    }

版本2.....6...

版本7,遞歸在瀏覽器下作了優化 ,包括ie6,屬於最好的實現方式之一

    function repeat (target, n) {
        if (n == 1){
            return target
        }
        var s = repeat(target, Math.floor(n/2));
        s += s;
        if (n % 2) {
            s += target;
        }
        return s;
    }

byteLen方法:取得一個字符串全部字節的長度。這是一個後端轉過來的方法。在前端,咱們要用戶填寫文本,須要字節有限制。

版本1:假設字符每一個字符Unicode編碼小於等於255,byteLength爲字符串長度。再遍歷字符串,遇到unicode編碼大於255時,爲byteLength加1

function byteLen (target) {
        var byteLength = target.length, 
            i = 0;
        for ( ; i < target.length ; i++) {
            if (target.charCodeAt(i) > 255) {
                byteLength++;
            }
        }
        return byteLength;
    }

truncate方法,用於對字符串進行截斷處理,當超過限定長度,默認添加三個點號等

    function truncate (target, length , truncation) {
        length = length || 30;
        truncation = truncation === void(0) ? '...' : truncation;
        return target.length > length ? target.slice(0, length - truncation.length) + truncation : String(target);
    }

camelize方法,轉換爲駝峯命名風格

    function camelize (target) {
        if (target.indexOf('_') < 0 && target.indexOf('_') < 0) {
            return target; //提早判斷,提升響應效率
        }
        return target.replace(/[-_][^-_]/g , function(match){
            return match.charCodeAt(1).toUpperCase();
        })
    }

underscored方法。轉換爲下劃線風格

function underscored(target){
        return target.replace(/([a-z\d])([A-Z])/g , '$1_$2').replace(/\-/g , '_').toLowerCase();
    }

dasherize方法,轉換爲連字符風格,亦指css變量風格(承上面的方法)

    function dasherize(target){
        return underscored(target).replace(/_/g, '-');
    }

capitalize方法,首字母大寫

    function capitalize(target) {
        return target.charAt(0).toUpperCase() + target.substring(1).toLowerCase();
    }

stripTags方法,移除字符中的html標籤。但有一個缺陷,若是其中有script標籤,會把不應顯示的腳本也顯示出來。

    function stripTags(target){
        return String(target || "") .replace(/<[^>]+>/g, '');
    }

escapeHTML和unescapeHTML略

escapeRegExp方法:將字符串安全格式轉換爲正則表達式的源碼

    function escapeRegExp(target){
        return target.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
    }

 pad方法,與trim方法相反,pad能夠爲字符串的某一端添加字符串。常見的是在日曆的月份前面加補0,所以,被稱爲fillZero

    function pad(target,n){
        var zero = new Array(n).join('0');
        var str = zero + target;
        var resulet = str.substr(-n);
        return resulet;
    }

高級方法,也是mass Framework使用的版本,支持更多的參數。容許從左邊或從右填充。以及使用什麼內容進行填充。

    function pad (target, n, filling, right, radix){
        var num = target.toString(radix || 10);
        filling = filling || "0";
        while (num.length < n){
            if(!right){
                num = filling + num;
            } else {
                num += filling;
            }
            return num;
        }
    }

wbr方法,爲目標字符串添加wbr換行。不過須要注意的是,它不是爲每一個字符串都插入<wbr>字樣,而是至關於在組成文本節點的報語法中的每一個字符後插入<wbr>字樣。若是是aa<span>bb</span>cc,返回a<wbr>a<span>b<wbr>b</span>c<wbr>c<wbr>,在opear瀏覽器上,默認的css不會添加上wbr樣式,須要在css上添加,wbr:after{content:"\00200B"}解決此問題

    function wbr (target){
        return String(target).replace(/(?:<[^>]+>) | (?:&#?[0-9a-z]{2,6};) | (.{1})/gi,'$&<wbr>').replace(/><wbr>/g,'>');
    }

format方法,在c語言中,有一個printf方法,咱們能夠在後面添加不一樣的類型的參數嵌入到將要輸出的字符串中。這是很是有用的方法,由於在javascript中涉及到大量這樣字符串拼接的工做 ,若是涉及邏輯,咱們能夠用模板,若是輕量點,咱們能夠用這個方法。

在不一樣的框架中名字不一樣,prototype.js 叫interpolate,Base2叫format,mootools叫substitute

    function format (str, object){
        var array = Array.prototype.slice.call(arguments, 1);
        return str.replace(/\\?\#{([^{}]+)\}/gm,function(match, name) {
            if(match.charAt(0) == '\\')
                return match.slice(1);
            var index = Number(name)
            if(index >= 0)
                return array[index];
            if (object && object[name] !== void 0)
                return object[name];
            return '';
        });
    }
    var a = format("resulet is #{0}, #{1}",22,33)
    console.log(a) // resulet is 22, 33
    var b = format ( "#{name} is a #{sex} #{am}" ,{
        name:"wangjie",
        sex:"man",
        am:"111"
    });
    console.log(b) // wangjie is a man 111

它支持兩種傳參方法,若是字符串的佔位符爲0,1,2這樣的非零整數,要求傳入兩個或以上的參數,不然就傳入一個對象,鍵名爲佔位符。

quote方法,在字符串的兩端加上雙引號。而後內部須要轉義的地方都要轉義。用於接裝JSON的鍵名或模析系統中

    //code.google.com/jQuery-json
    var escapeable = /["\\\x00-\x1f\x7f-\x9f]/g,
                    meta = {
                        '\b':'\\b',
                        '\t':'\\t',
                        '\n':'\\n',
                        '\f':'\\f',
                        '\r':'\\r',
                        '"':'\\"',
                        '\\':'\\\\'
                    };
    function quote(target){
        if (target.match(escapeable)){
            return '"' + target.replace(escapeable,function(a) {
                var c = meta[a];
                if(typeof c === 'string') {
                    return c;
                }
                return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4)
            }) + '"';
        }
        return '"' + target + '"';
    }               

固然,若是瀏覽器支持原生的JSON,咱們直接用JSON.stringify就好了,另外,FF在JSON發明以前,就支持String.prototype.quote與String.quote方法了,咱們使用quote以前斷定瀏覽器是否內置這些方法

字符串好像沒有打的瀏覽器兼容問題,有的話是IE6,IE7不支持用數組中括號取它的每個字符,須要用charAt來取。IE678不支持垂直分表符,所以有以下hack

var isIe678 = !+"\v1";

修復舊版本IE中的trim函數。這是一個很經常使用的操做,一般咱們須要把表單的兩側空白清除掉

版本1,用了兩次正則,實際速度很是驚人,主要得益於瀏覽器的內部優化。base2使用這種優化,引發了其它瀏覽器的跟風。因而正則的實現再也比不過字符串方法了。一個著名的例子,字符串拼接。直接相加比Array作成的StringBuffer還快。而StringBuffer技術在早些年備受推崇。

    function trim(str){
        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
    }

版本2

利用兩個候選操做符連接兩個正則,這樣作就失去了瀏覽器優化的機會。因爲看起來很優雅,不少類庫都使用它。如jQuery,mootools

    function trim(str){
        return str.replace(/^\s+|\s+$/g, '');
    }

版本3

更好的改進版

    function trim(str){
        var str = str.replace(/^\s\s*/, ""),
                ws = /\s/,
                i = str.length;
    while (ws.test(str.charAt(--i)))
        return str.slice(0, i + 1);
    }

2.數組的擴展與修復

得益於Prototype.js的ruby式數組方法的侵略,讓jser()的前端工程師大開眼界,原來對數組的操做是如此的豐富多彩。原來的javascript數組方法就是基於棧與隊列的一套方法,像splice()仍是很晚的時候加入的。讓咱們回顧一下用法。

pop()方法,出棧操做,刪除並返回數組的最後一個元素

push()方法,出棧操做,向數組的末尾添加一個或更多元素,並返回新的長度。

shift()方法,出隊操做,刪除並返回數組的第一個元素

unshift()方法,入隊操做,向數組的開頭添加一個或更多的元素,返回新的長度

slice()方法,切片操做,從數組中分離出一個子數組,功能相似字符串的substring、slice、substr這三兄弟。此方法也經常使用於轉換類數組對象爲真正的數組

sort()方法,對數組元素進行排序,有一個可選參數,爲比較函數。

reverse()方法,顛倒數組中元素的順序。

splice()方法,用於用於同時對原數組進行增刪操做,數組的remove方法就是基於它而寫的

concat()方法,用於把原數組與參數合併成一個新數組,若是參數爲數組,那麼它會把其第一維的元素放入新的數組中。所以咱們能夠利用它實現數組的平坦化操做和克隆操做。

join()方法,把數組的全部元素放入一個字符串。元素經過指定的分隔符進行分隔。你能夠想象成字符串的split的反操做。

在ecma262v5中,它把標準瀏覽器早已經實現的幾個方法進行了入戶處理。今後,咱們能夠安心的使用forEach()方法,不用擔憂他們被廢棄掉了。

indexOf()方法,定位操做。同上,不是從後遍歷。索引操做能夠說是字符串的同名方法的翻版,存在就返回非負操做。不存在就返回-1.

forEach()方法。迭代操做。將數組的元素依次傳入一個函數中執行,prototype.js對應的名字爲 each。

map()方法,收集操做。將數組的元素依次傳入一個函數中執行,而後把它們的返回值組成一個新數組返回。prototype.js對應的名字爲collect.

fliter()方法。過濾操做,將數組的元素依次傳入一個函數中執行,而後把返回值爲true的那個元素放入新的數組中返回。prototype.js中它有三個名字,select、filter、findAll

some()方法,只要數組中有一個元素知足條件(放進函數返回true)。那麼它就返回true.prototype.js對應的名字爲any

every()方法,只有數組的元素知足調條件(放進給定函數返回true)它才返回true。prototype.js對應的名字爲any

reduce()方法,歸化操做。將數組中的元素歸化爲一個簡單的數值。prototype.js對應的名字爲inject

reduceRight()方法:歸化操做,將數組中的元素歸化爲一個簡單的數值。同上,不過是從後遍歷。

因爲許多擴展也基於這些新的標準方法,所以咱們先給出IE678兼容方案,所有在數組原型上修復他們。

    Array.prototype.indexOf = function(item, index){
        var n = this.length, i = ~~index;
        if(i<0)
            i += n;
        for(; i<n; i++)
            if(this[i] === item)
                return i;
            return -1;
    }

    Array.prototype.lastIndexOf = function(ietm, index){
    var n = this.length,
        i = index == null ? n - 1 : index;
    if (i < 0)
        i = Math.max(0, n + 1);
    for (; i >= 0; i--)
        if (this[i] === item)
           return i;
    return -1;
    }

像forEach map filter some every這幾個方法,在結構上很是類似,咱們能夠這樣生成他們。

    function iterator (vars, body, ret){
        var fun = 'for (var ' + vars + 'i = 0,n =  this.length; i < n; i++ ){' +
             body.replace('_', '((i in this) && fn.call(scope,this[i],i,this))') 
             + '}' + ret
             return Function ("fn,scope", fun);
    }
    Array.prototype.forEach = iterator('','_','');
    Array.prototype.filter = iterator('r=[],j=0', 'if(_)r[j++]=this[i]', 'return r');
    Array.prototype.map = iterator('r=[],', 'r[i]=_' , 'return r');
    Array.prototype.some = iterator('','if(_)return true', 'return false');
    Array.prototype.every = iterator('','if(!_)return false','return true')

造輪子的同窗注意:數組的空元素不會再上述方法中遍歷出來。

    [1,2,,4].forEach(function(e){
        console.log(e)
    })

接下來,咱們將對數組的擴展進行總結,除去上述的

prototype.js的數組擴展:eachSlice , detect , grep , include , inGruopsOf , invoke, max, min, partition, pluck , reject, sortBy , zip , size, clear, first, last, compact, faltten , without, uniq , intersect, clone, inspect

Right.js 的數組擴展有 include, clean , clone , conpact, empty , first , flatten , includes, last, max , merge , min , random, reject, shuffle , size , sortBy, sum , uniq ,walk, without

mootools的數組擴展,clean, clone , associate , link , contains, append, getLast , getRandom, include, combine, erase, empty, flatten, min, max, mean, sum, erase, insert.

Ext的數組擴展, contains, pluck, clean, qunique, form , remove, include, merge,insersect, difference, flatten, min , max, mean, sum, erase, insert

Underscore.js 的數組擴展, detect, reject, invoke , pluck, sortBy, groupBy, sortedIndex, first, last, compact, flatten,without , union, intersection , difference, quiq, zip

qooxdoo數組擴展, insertAfter, insertAt, insertBefore , max , min , remove , removeAll , removeAt, sum , unique.

百度七巧板擴展, contains ,empty , find , remove , removeAt , unique

咱們能夠發現,Prototype.js 的一套方法影響深遠, 許多庫都有它的影子。咱們能夠根據須要與框架宗旨指定本身的數組擴展, 咱們在這些方面考慮以下:
少包含:平坦化 去重亂序移除這幾個操做,其次是兩個集合間的操做,如取並集差集交集
下面是各類具體實現。

contains方法, 斷定數組是否包含指定的目標

    function contains(target, item){
        return target.indexOf(item) > -1
    }

removeAt方法,移除數組中指定位置的元素,返回布爾表示成功與否

    function removeAt(target, index){
        return !!target.splice(index, 1).length
    }

remove方法,移除數組中第一個匹配傳參的那個元素,返回布爾表示成功與否。

    function remove(target, ietm){
        var index = target.indexOf(ietm);
        if(~index)
            return removeAt(target,index);
        return false;
    }

shuffle方法,對數組進行洗牌。若不影響原數組,能夠先拷貝一份出來操做,有關洗牌,能夠參考
博文:http://bost.ocks.org/mike/shuffle/

    function shuffle(target){
        var j, x,  i = target.length;
        for(; i > 0; j = parseInt(Math.random() * i) ,
            x = target[--i],target[i] = target[j] = x) {
        }
        return target
    }

random方法,從數組中隨機選出一個元素出來

    function random(target){
        return target[Math.floor(Math.random() * target.length)];
    }

flatten方法,對數組進行平坦化處理,返回一個一維的新數組

    function flatten(target){
        var resulet = [];
        target.forEach(function(item){
            if (Array.isArray(item)) {
                resulet = resulet.concat(flatten(item));
            } else{
                resulet.push(ietm)
            }
        });
        return resulet;
    }

unique方法,對數組去重,返回一個沒有重複元素的新數組

    function unique(target){
        var resulet = [];
        loop: for (var i = 0, n = target.length; i < n; i++){
            for (var x = i + 1; x < n; x++){
                if (target[x] === target[i])
                    continue loop;
            }
            resulet.push(target[i]);
        }
        return resulet;
    }

compact方法,過濾數組中的null和undefined,但不影響數組。

    function compact (target) {
        return target.filter(function(el){
            return el != null;
        });
    }

pluck方法,得到對象數組中的每一個元素的指定屬性,組成數組返回

    function pluck(target, name){
        var resulet = [], prop;
        target.forEach(function(ietm){
            prop = ietm[name];
            if (prop != null)
                resulet.push(prop);
        });
        return resulet;
    }

groupBy方法:根據指定條件(如回調或對象的某個屬性)進行分組,構成對象返回

    function groupBy(target, val){
        var resulet = {};
        var iterator = $.isFunction(val) ? val : function (obj){
            return obj[val];
        };
        target.forEach(function(value, index){
            var key = iterator (value, index);
            (resulet[key] || resulet[key] = [])).push(value);
        });
        return resulet
    }

sortBy方法:根據指定條件進行排序,一般用於對象數組

    function sortBy(target, fn, scope){
        var array = target.map(function(ietm, index){
            return {
                el: ietm,
                re: fn.call(scope, ietm, index)
            };
        }).sort(function(left, right){
            var a = left.re, b = right.re;
            return a < b ? -1 : a > b ? 1: 0;
        })
        return pluck(array, 'el');
    }

union方法,對兩個數組取並集

    function union (target, array){
        return unique(target.concat(array));
    }

intersect方法:對兩個數組取交集

    function intersect(target, array){
        return target.filter(function(n){
            return ~array.indexOf(n);
        });
    }

diff方法,對兩個數組取差集

    function diff(target, array){
        var resulet = target.slice();
        for (var i = 0, i < resulet.length; j++) {
            for (var j = 0; j < resulet.length; j++) {
                if (resulet[i] === array[j]){
                    resulet.splice(i, 1);
                    i--;
                    break;
                }
            }
        }
        return resulet;
    }

min方法,返回數組的最小值,用於數字數組

    function min (target){
        return Math.min.apply(0, target);
    }

max方法,返回數組中的最大值。用於數字數組

    function max(target){
        return Math.max.apply(0, target);
    }

 3.數值的擴展與修復

數值沒有什麼好擴展的,並且javascript的數值精度問題一貫臭名昭著,修復不是一兩行代碼的事情。先看擴展。

Prototype.js爲它添加了8個原型方法,Succ是加1,times是將回調重複執行制定次數,toPaddingString與上面提到的字符串擴展方法pad做用同樣,toColorPart是轉十六機制,abs,ceil,floor,是從Math中提取出來的

mootools的狀況,limit是從數值限在一個閉開間中,若是大於或小於其邊界,則等於其最大值或最小值,times與prototype.js用法類似,round是Math.round的加強版,添加了精度控制,toFloat,toInt是從window中偷學來的。

看看mass Framework對數字的擴展

limit方法,確保數值在[n1,n2]閉區間以內,若是超出界限,則置換爲離它最近的最大值或最小值

    function limit(target, n1, n2){
        var a = [n1, n2].sort();
        if (target < a[0])
            target = a[0];
        if(target > a[1])
            target = a[1];
        return target;
    }

nearer方法,求出距離數值最近的那個數

    function nearer(target, n1, n2){
        var diff1 = Math.abs(target - n1),
            diff2 = Math.abs(target - n2);
        return diff1 < diff2 ? n1 : n2
    }

Number下惟一要修復的方法是toFixed,它是用來校訂精確度,最後的那個數會四捨五入操做,但在一些瀏覽器中,並未這麼操做。
簡單修復能夠這樣處理

    if(0.9.toFixed(0) !== '1') {
        Number.Prototype.toFixed = function(n) {
            var power = Math.pow(10, n);
            var fixed = (Math.round(this*power)/power).toString();
                if(n == 0)
                    return fixed;
                if(fixed.indexOf('.') < 0)
                    fixed += '.';
                var padding = n + 1 (fixed.length - fixed.indexOf('.'));
                for (var i = 0; i < padding; i++)
                    fixed += '0';
                return fixed;
        }
    }

關於javascript偏差運算不在這裏呈現了,但在工做中,咱們儘可能避免小數操做與大數操做,或者轉交後臺處理,實在避免不了,能夠引入專業的庫來實現

4.函數的擴展與修復

V5對函數的惟一擴展就是bind函數,衆所周知,這來自prototype.js,此外,其它重要的函數都來自prototype.js

prototype.js的函數擴展以下

argumentNames:取得函數的形參,以字符串形式返回,這隻要用於其類工廠的方法鏈設計

bind,不用多描述,劫持做用域,並預先添加更多的參數。

bindAsEventListener 如bind類似,但強制返回函數的第一個參數爲事件對象,這是用於修復IE多投事件API與標準API的差別。

curry 函數柯里化,用於一個操做分紅多步進行,並能夠改變原函數的行爲。

wrap:AOP 的實現。

delay:setTimeout的偷懶寫法。

defer:強制延遲0.01秒才執行原函數

methodize:將一個函數變成其調用對象的方法,這也是爲其類工廠的方法服務。

首先咱們看bind方法,它用到了著名的閉包(所謂閉包,就是引用着外部變量的內部函數),好比下面這個函數

    var observable = function(val){
        var cur = val; //一個內部變量
        function field(neo) {
            if (arguments.length){ //setter
                if (cur !== neo) {
                    cur = neo
                }
            } else { //getter
                return cur;
            }
        }
        field();
        return field;
    }

它裏邊的field函數將於外部的cur構成一個閉包。prototype.js中的bind方法只要依仗原來=函數與通過切片話的args構成閉包,而這個方法就是名副其實的curry柯里化,用戶最初的那個傳參,劫持到返回函數修正this的指向。

    Function.Prototype.bind = function(context) {
        if (arguments.length < 2 && context == void 0)
            return this;
        var _method = this, args = [].slice.call(arguments, 1);
        return function() {
            return _method.apply(context, args.context.apply(args, arguments));
        }
    }

由於有這個原型擴展,咱們才能夠修復ie的多投事件API attachEvent回到的this問題,它老是指向window對象,而標準的瀏覽器的的addEventListener中的this則其調用對象

    var addEvent = document.addEventListener ? 
           function (el, type, fn, capture){
               el.addEventListener (type, fn, capture)
           } : 
           function (el, type, fn) {
               el.attachEvent("on" + type, fn.bind(el, event))
           }


ECMA62 V5對其認證之後,惟一的加強是對調用者進行監測,確保它是一個函數。順便總結一下這三個東西。

call是obj.method(a, b, c)到method(obj,[a, b, c])的變換,它要求第二個參數必須存在,必定是數組或Arguments這樣的類數組,NodeList這樣具備爭議性的東西就不能傳入進去!
所以,jQuery對兩個數組或類數組的合併就使用jQuery.merge,放棄使用Array.prototype.push.apply

bind就是apply的變種,保證返回值是一個函數。

這三個方法很是有用,咱們設法將其還原出來。

    var bind = function(bind) {
        return {
            bind: bind.bind(bind),
            call: bind.bind(bind.call),
            apply: bind.bind(bind.apply)
        }
    } (Function.Prototype.bind)

那麼怎麼使用它們呢,好比咱們想合併兩個數組,直接調用concat方法:

    var concat = bind.apply([].concat);
    var a = [1, [2,3], 4];
    var b = [1, 3];


使用bind.bind方法能夠將它們的結果進一步平坦化

    var concat = bind.apply([].concat);
    console.log(concat(b,a)); //=> [1,3,1,2,3,4]

又如切片化操做,它常常用於轉換類數組對象爲純數組

    var slice = bind([].slice)
    var array = slice({
        0: "aaa",
        1: "bbb",
        2: "ccc",
        3: "ddd"
        length: 4
    });
    console.log(array) //=> ["aaa","bbb","ccc","ddd"]

更經常使用的操做是轉換arguments對象,目的是爲了使用數組的一系列方法

    function test() {
        var args = slice(arguments)
        console.log(args)//=> [1,2,3,4,5]
    }
    test(1, 2, 3, 4, 5)

 

咱們能夠將hasOwnProperty提取出來,斷定對象是否在本地就擁有某屬性

    var hasOwn = bind.call(Object.Prototype.hasOwnProperty);
    hasOwn([],"xx") //false
    //使用bind.bind就須要多執行一次
    var hasOwn2 = bind.bind(Object.Prototype.hasOwnProperty);
    hasOwn2([],"xx")() //false

上面的bind.bind的行爲就是一種curry,它給了你一種傳參的機會,這樣你就能夠在內部斷定參數的個數,決定是否繼續返回函數仍是結果。這在設計計算器的連續運算上很是有用。今後角度,咱們能夠獲得一條信息,bind着重做用域的劫持,curry在於參數的不斷補充

咱們能夠編寫一個curry,當全部步驟輸入的參數個數等於最初定義時的函數形參個數,就執行它。

    function curry(fn) {
        function inner(len, arg) {
            if (len == 0)
                return fn.apply(null, arg);
            return function(x) {
                return inner(len - 1, arg.concat(x));
            };
        }
        return inner(fn.length, []);
    }
    function sum(x, y, z, w){
        return x + y + z + w;
    }

    curry(sum)('a')('b')('c')('d'); //=> 'abcd'

不過這裏咱們假定了用戶每次都只傳入一個參數,咱們能夠改進下

    function curry2(fn) {
        function inner (len, arg){
            if (len <= 0)
                return fn.apply(null, arg);
            return function() {
                return inner (len - arguments.length,
                    arg.concat(Array.apply([],arguments)));
            }
        }
        return inner(fn.length, []);
    }

這樣,咱們就能夠在中途傳遞多個參數,或不傳遞參數

curry2(sum)('a')('b','c')('d'); //=> 'abcd'
curry2(sum)('a')()('b','c')()('d'); //=> 'abcd'    

不過,上面的函數形式有個更帥氣的名稱,叫self-curry,或recurry.它強調的是遞歸自身來補全參數。
與curry類似的partial。curry的不足 參數老是經過push的方式來補全,而partial則是在定義時全部參數都已經有了,但某些位置上的參數值是個佔位符,咱們在接下來的傳參只是替換掉它們。博客上專門有《partial application in javascript》來介紹這個內容。

    Function.Prototype.partial = function(){
        var fn = this, args = Array.prototype.slice.call(arguments);
        return function() {
            var arg = 0;
            for (var i = 0 ;  i < args.length && arg < arguments.length; i++)
                if (args[i] === undefined)
                    args[i] = arguments[args++];
            return fn.apply(this, args);
        }
    }

它是使用undefined做爲佔位符的。

    var delay = setTimeout.partial(undefined, 10);
    //接下來的工做就代替掉第一個參數
    delay(function() {
        console.log("A  call to this function will be temporarily delayed")
    })

curry、partial應用場景在前端世界應用的並很少,前端講究的是即時顯示,許多API都是同步的,後端因爲IO操做等耗時夠長,像node.js提供了大量的異步函數來提升性能,防止堵塞。可是過多的異步函數必然帶來回調嵌套的問題。所以,咱們須要curry等函數變換。將嵌套減小到能夠接受的程度。在ajax中會講述他們的使用辦法。

函數的修復,這涉及到方法,apply和call,這兩個方法的本質就是生成一個新的函數。將原函數與用於的傳參放到裏面執行而已,在javascript中創造一個函數有不少辦法,常見的有函數聲明和函數表達式,次之是函數構造器,再次之是eval,setTimeout....

5.日期的擴展與修復

Date構造器是javascript中傳參最豐富的構造器。大體分爲四種

    new Date();
    new Date(value); //傳入毫秒鼠
    new Date(dateString)
    new Date(year, month, day/*, hour, minute, second, minllisecond*/)

其中,第三種能夠玩N多花樣,我的建議只使用"2009/07/12 12:34:56",後面的分秒能夠省略。這個全部的瀏覽器都支持,此構造器的兼容列表可見此文。

http://dygraphs.com/date-formats.html

若要修正它的傳參,多是個龐大的工程。而且影響Object.prototype.toString的類型的判斷。所以,不建議修正。es5.js中的源碼,能夠參考

https://github.com/kriskowal/es5-shim/blob/master/es5-shim.js

javascript的日期是抄自java.util.Date,可是Date這個類的不少方法對時區等支持不夠,且很多都過期,java程序員也推薦使用calnedar類代替Date類。javascript能夠選擇餘地比較小。如對屬性使用了先後矛盾的偏移量。月份與小時都是基於0.月份中的天數則是基於1,而年則是從1900開始的.


接下來,咱們爲舊版的瀏覽器添加幾個ecma262標準化日期方法吧

    if(!Date.now) {
        Date.now = function(){
            return + new Date;
        }
    }
    if (!Date.prototype.toISOString) {
        void function() {
            function pad (number) {
                var r = String(number);
                if (r.length === 1) {
                    r = '0' + r ;
                }
                return r
            }
        Date.prototype.toJSON = Date.prototype.toISOString = function() {
            return this.getUTCFllYear()
            + '_' + pad(this.getUTCMonth() + 1)
            + '_' + pad(this.getUTCDate())
            + 'T' + pad(this.getUTCHours())
            + ':' + pad(this.getUTCMinutes())
            + ':' + pad(this.getUTCSeconds())
            + '.' + String((this.getUTCMilliseconds()/1000).toFixed(3)).clice(2, 5)
            + 'Z';
        }
        }();
    }

IE67 中,getYear, setYear方法存在bug.這個修起來比較簡單:

    if ((new Date).getYear() > 1900){
        Date.prototype.getYear = function(){
            return this.getFullYear() - 1900;
        };
    Date.prototype.setYear = function(year){
        return this.setFullYear(year); //+1900
    };
    }

至於擴展,因爲涉及本地化的緣由,外國許多日期庫都須要改一改才能用。其中以dataFormat這個頗有用的方法爲最。先給一些經常使用的擴展。

傳入兩個Date類型的日期,求其相隔的天數

    var getDatePeriod = function(start, finish){
        return Math.abs(start * 1 - finish * 1) / 60 / 60 / 1000 / 24;
    }

傳入一個Date類型的日期,判斷它所在月的第一天。

    var getFirstDateInMouth = function(date) {
        return new Date(Date.getFullYear(), Date.getMonth(), 1);
    }

傳入一個Date類型的日期,判斷它所在月的最後一天。

    var getLastDateInMouth = function(date) {
        return new Date(Date.getFullYear(), Date.getMonth() + 1, 0);
    }

判斷是不是閏年

    Date.prototype.isLeapYear = function(){
        return new Date(this.getFullYear(),2 ,0).getDate() == 29;
    }


得到當前月份的天數

    function getDaysInMotn1(date) {
        switch (Date.getMonth()) {
            case 0:
            case 2:
            case 4:
            case 6:
            case 7:
            case 9:
            case 11:
                return 31;
            case 1:
                var y = Date.getFullYear();
                return y % 4 == 0 && y % 100 != 0 || y % 400 == 0 ? 29 : 28;
            default :
                return 30;
        }
    }

    function getDaysInMonth2(date) {
        return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
    }

dateFormat方法太長,此處略。

(本章完結

上一章: 第三章:模塊加載系統(requirejs)  下一章:第5章,瀏覽器的嗅探與特徵偵測

相關文章
相關標籤/搜索