JS學(xué)習(xí)20(高級技巧)

高級函數(shù)

函數(shù)本質(zhì)上是很簡單且過程化的,但是由于JS天生的動(dòng)態(tài)的特性,從使用方式上可以很復(fù)雜。

安全的類型檢測

雖然JS中是有類型檢測的,但是由于瀏覽器實(shí)現(xiàn)等它們并不完全可靠。比如typeof在Safari中對正則表達(dá)式也返回function。
instanceof在存在多個(gè)全局作用域時(shí)也會(huì)把同種卻不同作用域中構(gòu)造函數(shù)的實(shí)例識別為不同的實(shí)例:

var isArray = value instanceof Array;

這個(gè)表達(dá)式要是想返回true,value必須是個(gè)數(shù)組,且必須與Array構(gòu)造函數(shù)在同一個(gè)全局作用域中,如果value是另一個(gè)全局作用域中定義的數(shù)組,那這個(gè)表達(dá)式返回false。
檢測某個(gè)對象是原生的還是開發(fā)人員自定義的對象時(shí)也會(huì)有問題。因?yàn)闉g覽器開始原生支持JSON了,而有些開發(fā)人員還是在用第三方庫來實(shí)現(xiàn)JSON,這個(gè)庫里會(huì)有全局的JSON對象,這樣想確定JSON對象是不是原生的就麻煩了。
解決這些問題的辦法就是使用Object的toString方法,這個(gè)方法會(huì)返回一個(gè)[object NativeConstructorName]格式的字符串。

function isArray(value){
    return Object.prototype.toString.call(value) == "[object Array]";
}
function isFunction(value){
    return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value){
    return Object.prototype.toString.call(value) == "[object RegExp]";
}

不過要注意的是,對于在IE中任何以COM形式實(shí)現(xiàn)的函數(shù),isFunction()都會(huì)返回false。
對于JSON是否為原生的問題可以這樣:

var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";

作用域安全的構(gòu)造函數(shù)

之前我們說的構(gòu)造函數(shù)是這么使用的:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
}
var person = new Person("Nicholas", 29, "Software Engineer");

在這里,由于使用了new操作符,this被綁定在了新創(chuàng)建的Person對象上,如果不用new操作符直接調(diào)用Person(),this就會(huì)被綁定到window上,這顯然是不行的。

function Person(name, age, job){
    if (this instanceof Person){
        this.name = name;
        this.age = age;
        this.job = job;
    } else {
        return new Person(name, age, job);
    }
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name);      //""
alert(person1.name);     //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name);     //"Shelby"

不過在使用了這樣作用域安全的構(gòu)造函數(shù)后,如果使用基于構(gòu)造函數(shù)竊取的繼承,就會(huì)有問題:

function Polygon(sides){
    if (this instanceof Polygon) {
        this.sides = sides;
        this.getArea = function(){
            return 0;
        };
    } else {
        return new Polygon(sides);
    }
}
function Rectangle(width, height){
    //這里調(diào)用時(shí),傳進(jìn)去的this是Rectangle類型的,沒辦法拓展side屬性
    Polygon.call(this, 2);
    this.width = width;
    this.height = height;
    this.getArea = function(){
        return this.width * this.height;
    };
}
var rect = new Rectangle(5, 10);
alert(rect.sides);        //undefined

解決方法就是使Rectangle也是Polygon的一個(gè)實(shí)例就好啦

Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides);        //2

惰性載入函數(shù)

由于瀏覽器差異,大量的判斷瀏覽器能力的函數(shù)需要被使用(通常是大量的if),然而這些判斷一般其實(shí)不必每次都執(zhí)行,在執(zhí)行一次后,瀏覽器的能力就確定了,以后就應(yīng)該不用在判斷了。比如:

function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        return new XMLHttpRequest();
    } else if (typeof ActiveXObject != "undefined"){
        if (typeof arguments.callee.activeXString != "string"){
            var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                    "MSXML2.XMLHttp"],
                i,len;
            for (i=0,len=versions.length; i < len; i++){
                try {
                    new ActiveXObject(versions[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                } catch (ex){
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    } else {
        throw new Error("No XHR object available.");
    }
}

這里的創(chuàng)建XHR對象的函數(shù),每次創(chuàng)建對象時(shí)都會(huì)判斷一次瀏覽器能力,這是不必要的。
惰性載入有兩種方式,第一種就是在函數(shù)第一次被調(diào)用時(shí),根據(jù)不同情況,用不同的新函數(shù)把這個(gè)函數(shù)覆蓋掉,以后調(diào)用就不需要再判斷而是直接執(zhí)行該執(zhí)行的操作。

function createXHR(){
    if (typeof XMLHttpRequest != "undefined"){
        createXHR = function(){
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObject != "undefined"){
        createXHR = function(){
            if (typeof arguments.callee.activeXString != "string"){
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                        "MSXML2.XMLHttp"],
                        i, len;
                for (i=0,len=versions.length; i < len; i++){
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (ex){
                        //skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        createXHR = function(){
            throw new Error("No XHR object available.");
        };
    }
    return createXHR();
}
createXHR();
alert(createXHR);

第二種思路一樣,只不過是在聲明函數(shù)時(shí)就指定新函數(shù),不在第一次調(diào)用時(shí)再指定。兩種辦法其實(shí)本質(zhì)上是一樣的,看你想怎么用了。

var createXHR = (function(){
    if (typeof XMLHttpRequest != "undefined"){
        return function(){
            return new XMLHttpRequest();
        };
    } else if (typeof ActiveXObject != "undefined"){
        return function(){
            if (typeof arguments.callee.activeXString != "string"){
                var versions = ["MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0",
                    "MSXML2.XMLHttp"],
                    i, len;
                for (i=0,len=versions.length; i < len; i++){
                    try {
                        new ActiveXObject(versions[i]);
                        arguments.callee.activeXString = versions[i];
                        break;
                    } catch (ex){
                        //skip
                    }
                }
            }
            return new ActiveXObject(arguments.callee.activeXString);
        };
    } else {
        return function(){
            throw new Error("No XHR object available.");
        };
    }
})();
alert(createXHR);

函數(shù)綁定

函數(shù)綁定解決的問題是調(diào)用函數(shù)時(shí)函數(shù)的this對象被改為我們不想要的對象的問題。這種情況經(jīng)常出現(xiàn)在指定事件處理函數(shù)時(shí)。看個(gè)例子:

var handler = {
    message: "Event handled",
    handleClick: function(event){
        alert(this);
        alert(this.message);
    }
};
var btn = document.getElementById("myButton");
EventUtil.addHandler(btn, "click", handler.handleClick);  //[object HTMLButtonElement]  undefined
handler.handleClick();  //   [object Object]   Event handled

這里的this被改成了按鈕元素。
解決辦法就是將真正的函數(shù)套在一個(gè)閉包中,以此來保存著個(gè)函數(shù)的環(huán)境

var handler = {
    message: "Event handled",
    handleClick: function(event){
        alert(this); //   [object Object]   
        alert(this.message); //   Event handled
    } };
var btn = document.getElementById("myButton");
EventUtil.addHandler(btn, "click", function(event){
    alert(this);  //[object HTMLButtonElement]
    handler.handleClick(event);
});

可以看到,閉包的this被改為了button,而因?yàn)檫@個(gè)閉包的保護(hù),我們的處理函數(shù)的環(huán)境保存住了。
為了不每次都手動(dòng)創(chuàng)建一個(gè)閉包,我們可以創(chuàng)建一個(gè)工具函數(shù)bind:

function bind(fn, context){
    alert(arguments[0]);  //fn本身
    return function(){
        alert(arguments[0]); //event
        return fn.apply(context, arguments);
    };
}

這里就是把一個(gè)函數(shù)使用apply在特定的環(huán)境下調(diào)用,注意一下arguments對象,最后應(yīng)該使用的是return過去的匿名函數(shù)(閉包)的arguments才對,這樣事件給事件處理函數(shù)傳遞的參數(shù)才能穿到我們的函數(shù)里。

var handler = {
    message: "Event handled",
    handleClick: function(event){
        alert(this); //   [object Object]
        alert(this.message); //   Event handled
    } };
var btn = document.getElementById("myButton");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));

ES5中為所有函數(shù)都定義了一個(gè)原生的bind()方法。直接使用就行。

EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));

只要是將某個(gè)函數(shù)指針以值的形式進(jìn)行傳遞,同時(shí)該函數(shù)必須在特定環(huán)境中執(zhí)行,被綁定函數(shù)的效果就顯現(xiàn)了

函數(shù)柯里化

這個(gè)是用來創(chuàng)建已經(jīng)設(shè)置好一個(gè)或多個(gè)參數(shù)的函數(shù),用一個(gè)例子看看基本思想:

function add(num1, num2){
    return num1 + num2;
}
function curriedAdd(num2){
    return add(5, num2);
}
alert(add(2, 3));     //5
alert(curriedAdd(3)); //8

創(chuàng)建柯里化函數(shù)的通用方式:

function curry(fn){
    var args = Array.prototype.slice.call(arguments, 1);
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return fn.apply(null, finalArgs);
    };
}

這個(gè)函數(shù)主要的工作就是將外部函數(shù)和內(nèi)部函數(shù)的參數(shù)都獲取到傳遞給了返回的函數(shù)中。

function add(a,b) {
    alert(a);
    alert(b);
    alert(a+b);
}
var curriedAdd = curry(add, 5);
curriedAdd(3);   //8
curriedAdd = curry(add, 3);
curriedAdd(5);   //8
curriedAdd = curry(add, 5,3);
curriedAdd();   //8

可以將其利用在bind函數(shù)中來給事件處理函數(shù)傳入多個(gè)參數(shù)。

function bind(fn, context){
    var args = Array.prototype.slice.call(arguments, 2); 
    return function(){
        var innerArgs = Array.prototype.slice.call(arguments); 
        var finalArgs = args.concat(innerArgs);
        return fn.apply(context, finalArgs);
    };
}
var handler = {
    message: "Event handled",
    handleClick: function(name, event){
        alert(this.message + ":"+ name + ":"+ event.type);
    }
};
var btn = document.getElementById("myButton");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "myButton"));

這里要注意的是參數(shù)的順序,比如這里name和event反了可就不對了呦。
ES5中的bind也實(shí)現(xiàn)了柯里化,直接使用就可以。

EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "myButton"));

防篡改對象

JS共享的本質(zhì)使任意對象都可被隨意修改。這樣有時(shí)很不方便。ES5增加了幾個(gè)方法來設(shè)置對象的行為。一旦將對象設(shè)置為防篡改就不能撤銷了。

不可拓展對象

不可以添加新的屬性和方法

var person = { name: "Nicholas" };
Object.preventExtensions(person);
person.age = 29;
alert(person.age); //undefined
alert(Object.isExtensible(person)); //false
person.name = "hahah";
alert(person.name); //hahah

密封的對象

不可以添加或刪除屬性,已有成員的[[Configurable]]被設(shè)置為false。

var person = { name: "Nicholas" };
Object.seal(person);
person.age = 29; 
alert(person.age); //undefined
delete person.name; 
alert(person.name); //"Nicholas"
alert(Object.isExtensible(person)); //false
alert(Object.isSealed(person));     //true

凍結(jié)的對象

不可拓展,密封,且對象數(shù)據(jù)屬性的[[Writable]]將被設(shè)為false。如果定義了[[Set]],訪問器屬性依然可寫。

var person = { name: "Nicholas" };
Object.freeze(person);
person.age = 29; alert(person.age); //undefined
delete person.name; alert(person.name); //"Nicholas"
person.name = "Greg"; alert(person.name); //"Nicholas"

alert(Object.isExtensible(person));//false
alert(Object.isSealed(person));//true
alert(Object.isFrozen(person));//true

高級定時(shí)器

setTimeout()和setInterval()是很實(shí)用的功能,不過有些事情是要注意的。
JS是單線程的,這就意味著定時(shí)器實(shí)際上是很有可能被阻塞的。我們在這兩個(gè)函數(shù)中所設(shè)置的定時(shí),其實(shí)是代表將代碼加入到執(zhí)行隊(duì)列的事件,如果在加入時(shí)恰巧JS是空閑的,那么這段代碼會(huì)立即被執(zhí)行,也就是說這個(gè)定時(shí)被準(zhǔn)時(shí)的執(zhí)行了。相反,如果這時(shí)JS并不空閑或隊(duì)列中還有別的優(yōu)先級更高的代碼,那就意味著你的定時(shí)器會(huì)被延時(shí)執(zhí)行。

重復(fù)的定時(shí)器

使用setInterval創(chuàng)建定時(shí)器的目的是使代碼規(guī)則的插入到隊(duì)列中。這個(gè)方式的問題在于,存在這樣一種可能,在上次代碼還沒執(zhí)行完的時(shí)候代碼再次被添加到隊(duì)列。JS引擎會(huì)解決這個(gè)問題,在將代碼添加到隊(duì)列時(shí)會(huì)檢查隊(duì)列中有沒有代碼實(shí)例,如果有就不添加,這確保了定時(shí)器代碼被加入隊(duì)列中的最小間隔是規(guī)定間隔。但是在某些特殊情況下還是會(huì)出現(xiàn)兩個(gè)問題,某些間隔因?yàn)镴S的處理被跳過,代碼之間的間隔比預(yù)期的小。
所以盡量使用setTimeout()模擬間隔調(diào)用。

setTimeout(function(){ 
    setTimeout(arguments.callee, interval);
}, interval);

Yielding Processes

如果你的頁面中要進(jìn)行大量的循環(huán)處理,每次循環(huán)會(huì)消耗大量的時(shí)間,那就會(huì)阻塞用戶的操作。這時(shí)分塊處理數(shù)據(jù)就是個(gè)好辦法。
這個(gè)例子每100ms取一個(gè)數(shù)組元素并添加到頁面。

function chunk(array, process, context){
    setTimeout(function(){
        var item = array.shift();
        process.call(context, item);
        if (array.length > 0){
            setTimeout(arguments.callee, 100);
        }
    }, 100);
}
var data = [12,123,1234,453,436,23,23,5,4123,45,346,5634,2234,345,342];
function printValue(item){
    var div = document.getElementById("myDiv");
    div.innerHTML += item + "<br>";
}
chunk(data, printValue);

函數(shù)節(jié)流

這個(gè)是為了避免某個(gè)操作連續(xù)不停的觸發(fā),比如涉及到DOM操作,連續(xù)大量的DOM操作非常耗資源。
函數(shù)節(jié)流的基本思想是,每一次調(diào)用其實(shí)是設(shè)置一個(gè)真正調(diào)用的setTimeout操作,每次調(diào)用都會(huì)先清除當(dāng)前的setTimeout再設(shè)置一個(gè)新的。如果短時(shí)間內(nèi)大量調(diào)用,就回一直設(shè)置新的setTimeout而不執(zhí)行setTimeout內(nèi)的操作。只有停止調(diào)用足夠長的時(shí)間,直到setTimeout時(shí)間到了,內(nèi)部的真正操作才會(huì)執(zhí)行一次。這個(gè)對onresize事件特別有用。

function throttle(method, context) {
    clearTimeout(method.tId);
    method.tId= setTimeout(function(){
        method.call(context);
    }, 100);
}
function reDiv(){
    var div = document.getElementById("myDiv");
    div.innerHTML += "qqqqqq" + "<br>";
}
window.onresize = function(){
    throttle(reDiv);
};

這樣只有當(dāng)你停下調(diào)整窗口大小100ms后才會(huì)執(zhí)行reDiv操作。

自定義事件

事件這樣的交互其實(shí)就是觀察者模式,這類模式由兩類對象組成:主體和觀察者。主體負(fù)責(zé)發(fā)布事件,觀察者通過訂閱這些事件來觀察該主體。
創(chuàng)建自定義事件實(shí)際上就是創(chuàng)建一個(gè)管理事件的對象,并在里面存入各種事件類型的處理函數(shù),觸發(fā)事件時(shí),只要你給出事件類型,這個(gè)對象就會(huì)找到相應(yīng)的事件處理程序并執(zhí)行。
下面是一個(gè)事件管理對象的大體形式:

function EventTarget(){
    this.handlers = {};
}
EventTarget.prototype = {
    constructor: EventTarget,
    addHandler: function(type, handler){
        if (typeof this.handlers[type] == "undefined"){
            this.handlers[type] = [];
        }
    this.handlers[type].push(handler);
    },
    fire: function(event){
        if (!event.target){
            event.target = this;
        }
        if (this.handlers[event.type] instanceof Array){
            var handlers = this.handlers[event.type];
            for (var i=0, len=handlers.length; i < len; i++){
                handlers[i](event);
            }
        }
    },
    removeHandler: function(type, handler){
        if (this.handlers[type] instanceof Array){
            var handlers = this.handlers[type];
            for (var i=0, len=handlers.length; i < len; i++){
                if (handlers[i] === handler){
                    break;
                }
            }
        handlers.splice(i, 1);
        }
    }
};

添加事件處理程序時(shí),addHandler會(huì)按照事件的類型將處理函數(shù)存入handlers屬性中對應(yīng)的數(shù)組里(如果還沒有則新建)。
觸發(fā)事件時(shí)使用fire,傳入一個(gè)至少有type屬性的對象。
使用時(shí)就像這樣:

function handleMessage(event){
    alert("Message received: " + event.message);
}
var target = new EventTarget();
target.addHandler("message", handleMessage);
target.fire({ type: "message", message: "Hello world!"});
target.removeHandler("message", handleMessage);
target.fire({ type: "message", message: "Hello world!"});

自定義事件經(jīng)常用來解耦對象之間的交互,使用事件就不需要有對象與對象之間的引用,使事件處理和事件觸發(fā)保持隔離。

拖放

使用原始的鼠標(biāo)事件

創(chuàng)建一個(gè)單例,使用模塊模式來創(chuàng)建一個(gè)拖動(dòng)的插件,返回兩個(gè)方法,分別用來添加和移除所有的事件處理程序。

var DragDrop = function(){
    var dragging = null;
    var diffX = 0;
    var diffY = 0;
    function handleEvent(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        switch(event.type){
            case "mousedown":
                if (target.className.indexOf("draggable") > -1){
                    dragging = target;
                    diffX = event.clientX - target.offsetLeft;
                    diffY = event.clientY - target.offsetTop;
                }
                break;
            case "mousemove":
                if (dragging !== null){
                    dragging.style.left = (event.clientX - diffX) + "px";
                    dragging.style.top = (event.clientY - diffY) + "px";
                }
                break;
            case "mouseup":
                dragging = null;
                break;
        }
    };
    return {
        enable: function(){
            EventUtil.addHandler(document, "mousedown", handleEvent);
            EventUtil.addHandler(document, "mousemove", handleEvent);
            EventUtil.addHandler(document, "mouseup", handleEvent);
        },
        disable: function(){
            EventUtil.removeHandler(document, "mousedown", handleEvent);
            EventUtil.removeHandler(document, "mousemove", handleEvent);
            EventUtil.removeHandler(document, "mouseup", handleEvent);
        }
    }
}();
DragDrop.enable();

這樣看來拖動(dòng)的功能是實(shí)現(xiàn)了,不過有個(gè)問題。比如說這是我寫的一個(gè)插件,使用的人想在拖動(dòng)開始的時(shí)候做一些事情,那么他就不得不在起的源碼里做出修改。他需要把所有要執(zhí)行的代碼和函數(shù)加到case "mousedown"里。如果這個(gè)插件我加密了呢,那想在這個(gè)時(shí)間點(diǎn)做些事情就更麻煩了。這樣的做法顯然并不科學(xué)。
這時(shí)如果使用了自定義事件,就可以很好的解決這個(gè)問題。

添加自定義事件

我們在這里新定義一個(gè)dragdrop變量,它是EventTarget類型的對象,在它上面我們可以添加事件處理函數(shù)或觸發(fā)事件。在拖動(dòng)開始時(shí),過程中,結(jié)束時(shí),都觸發(fā)了自定義事件,這樣有人想在這幾個(gè)節(jié)點(diǎn)做什么就直接添加事件處理函數(shù)就可以了。

var DragDrop = function(){
    //這里的dragdrop是之前的EventTarget類型,可以用來保存和觸發(fā)事件
    var dragdrop = new EventTarget(),
        dragging = null,
        diffX = 0,
        diffY = 0;
    function handleEvent(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        switch(event.type){
            case "mousedown":
                if (target.className.indexOf("draggable") > -1){
                    dragging = target;
                    diffX = event.clientX - target.offsetLeft;
                    diffY = event.clientY - target.offsetTop;
                    //觸發(fā)自定義事件
                    dragdrop.fire({type:"dragstart", target: dragging,
                        x: event.clientX, y: event.clientY});
                }
                break;
            case "mousemove":
                if (dragging !== null){
                    dragging.style.left = (event.clientX - diffX) + "px";
                    dragging.style.top = (event.clientY - diffY) + "px";
                    dragdrop.fire({type:"drag", target: dragging,
                        x: event.clientX, y: event.clientY});
                }
                break;
            case "mouseup":
                dragdrop.fire({type:"dragend", target: dragging,
                    x: event.clientX, y: event.clientY});
                dragging = null;
                break;
        }
    };
    dragdrop.enable = function(){
        EventUtil.addHandler(document, "mousedown", handleEvent);
        EventUtil.addHandler(document, "mousemove", handleEvent);
        EventUtil.addHandler(document, "mouseup", handleEvent);
    };
    dragdrop.disable = function(){
        EventUtil.removeHandler(document, "mousedown", handleEvent);
        EventUtil.removeHandler(document, "mousemove", handleEvent);
        EventUtil.removeHandler(document, "mouseup", handleEvent);
    };
    return dragdrop;
}();
DragDrop.addHandler("dragstart", function(event){
    var status = document.getElementById("myDiv");
    status.innerHTML = "Started dragging " + event.target.id;
});
DragDrop.addHandler("drag", function(event){
    var status = document.getElementById("myDiv");
    status.innerHTML += "<br/> Dragged " + event.target.id + " to (" + event.x +
        "," + event.y + ")";
});
DragDrop.addHandler("dragend", function(event){
    var status = document.getElementById("myDiv");
    status.innerHTML += "<br/> Dropped " + event.target.id + " at (" + event.x +
        "," + event.y + ")";
});
DragDrop.enable();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,663評論 19 139
  • https://nodejs.org/api/documentation.html 工具模塊 Assert 測試 ...
    KeKeMars閱讀 6,610評論 0 6
  • 第一部分 準(zhǔn)入訓(xùn)練 第1章 進(jìn)入忍者世界 js開發(fā)人員通常使用js庫來實(shí)現(xiàn)通用和可重用的功能。這些庫需要簡單易用,...
    如201608閱讀 1,410評論 1 2
  • 古典的代表作《拆掉思維里的墻》,想必很多人都看過了。大家看題目多半覺得這是一碗心靈雞湯,也許我不能否認(rèn),但是我要說...
    講真書畫閱讀 613評論 0 8
  • 兒子放暑假,我們搬回了二十公里外東邊的家。兩年前,為了兒子上學(xué)方便,一直在西邊的家住著,西邊屬于高鐵新區(qū),...
    花香001閱讀 190評論 0 0

友情鏈接更多精彩內(nèi)容