JS 筆試手寫程式碼

舞動乾坤發表於2018-03-30

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換行。不過需要注意的是,它不是為每個字串都插入字樣,而是相當於在組成文字節點的報語法中的每個字元後插入字樣。如果是aabbcc,返回aabb<wbr>cc,在opear瀏覽器上,預設的css不會新增上wbr樣式,需要在css上新增,wbr:after{content:"\00200B"}解決此問題<wbr><wbr><wbr><wbr><wbr>

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, 33var 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-jsonvar 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);
        returnfalse;
    }
複製程式碼

**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方法,它用到了著名的閉包(所謂閉包,就是引用著外部變數的內部函式),比如下面這個函式

[

複製程式碼
](javascript:void(0);)

var observable = function(val){
        var cur = val; //一個內部變數function field(neo) {
            if (arguments.length){ //setterif (cur !== neo) {
                    cur = neo
                }
            } else { //getterreturn cur;
            }
        }
        field();
        return field;
    }
複製程式碼

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

    Function.Prototype.bind = function(context) {
        if (arguments.length < 2 && context == void 0)
            returnthis;
        var _method = this, args = [].slice.call(arguments, 1);
        returnfunction() {
            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);
            returnfunction(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);
            returnfunction() {
                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);
        returnfunction() {
            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) {
        voidfunction() {
            function pad (number) {
                var r = String(number);
                if (r.length === 1) {
                    r = '0' + r ;
                }
                return r
            }
        Date.prototype.toJSON = Date.prototype.toISOString = function() {
            returnthis.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(){
            returnthis.getFullYear() - 1900;
        };
    Date.prototype.setYear = function(year){
        returnthis.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) {
        returnnew Date(Date.getFullYear(), Date.getMonth(), 1);
    }
複製程式碼

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

var getLastDateInMouth = function(date) {
        returnnew Date(Date.getFullYear(), Date.getMonth() + 1, 0);
    }
複製程式碼

判斷是否是閏年

    Date.prototype.isLeapYear = function(){
        returnnew 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) {
        returnnew Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
    }
複製程式碼

dateFormat方法太長,此處略。

相關文章