日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

如果你是一個JAVAScript新手或僅僅最近才在你的開發(fā)工作中接觸它,你可能感到沮喪。所有的語言都有自己的怪癖(quirks)——但從基于強類型的服務(wù)器端語言轉(zhuǎn)移過來的開發(fā)人員可能會感到困惑。我就曾經(jīng)這樣,幾年前,當(dāng)我被推到了全職JavaScript開發(fā)者的時候,有很多事情我希望我一開始就知道。在這篇文章中,我將分享一些怪癖,希望我能分享給你一些曾經(jīng)令我頭痛不已的經(jīng)驗。這不是一個完整列表——僅僅是一部分——但希望它讓你看清這門語言的強大之處,可能曾經(jīng)被你認為是障礙的東西。

我們將看下列技巧:

  • 相等
  • 點號vs括號
  • 函數(shù)上下文
  • 函數(shù)聲明vs函數(shù)表達式
  • 命名vs匿名函數(shù)
  • 立即執(zhí)行函數(shù)表達式
  • typeof vs Object.prototype.toString

1.) 相等

C#出身的我非常熟悉==比較運算符。值類型(或字符串)當(dāng)有相同值是是相等的。引用類型相等需要有相同的引用。(我們假設(shè)你沒有重載==運算符,或?qū)崿F(xiàn)你自己的等值運算和GetHashCode方法)我很驚訝為什么JavaScript有兩個等值運算符:==和===。最初我的大部分代碼都是用的==,所以我并不知道當(dāng)我運行如下代碼的時候JavaScript為我做了什么:

var x = 1;

if(x == "1") {
    console.log("YAY! They're equal!");
}

這是怎么回事?整數(shù)1是如何和字符串”1”相等的?

在JavaScript中,有相等(==)和嚴格相等(===)之說。相等運算符將強制轉(zhuǎn)換兩邊的操作數(shù)為相同類型后執(zhí)行嚴格相等比較。所以在上面的例子中,字符串”1”會被轉(zhuǎn)換為整數(shù)1,這個過程在幕后進行,然后與變量x進行比較。

嚴格相等不進行類型轉(zhuǎn)換。如果操作數(shù)類型不同(如整數(shù)和字符串),那么他們不全等(嚴格相等)。

var x = 1;

// 嚴格平等,類型必須相同
if(x === "1") {
    console.log("Sadly, I'll never write this to the console");
}

if(x === 1) {
    console.log("YES! Strict Equality FTW.")
}

你可能正在考慮可能發(fā)生強制類型轉(zhuǎn)換而引起的各種恐怖問題——假設(shè)你的引用中發(fā)生了這種轉(zhuǎn)換,可能導(dǎo)致你非常困難找到問題出在哪里。這并不奇怪,這也是為什么經(jīng)驗豐富的JavaScript開發(fā)者總是建議使用嚴格相等。

2.) 點號 vs 括號

這取決于你來自其他什么語言,你可能見過或沒見過這種方式(這就是廢話)。

// 獲取person對象的firstName值
var name = person.firstName;

// 獲取數(shù)組的第三個元素
var theOneWeWant = myArray[2]; // remember, 0-based index不要忘了第一個元素的索引是0

然而,你知道它也可以使用括號引用對象的成員嗎?比如說:

var name = person["firstName"];

為什么會這樣有用嗎?而你會用點符號的大部分時間,有幾個實例的括號使某些方法可能無法這樣做。例如,我會經(jīng)常重構(gòu)大開關(guān)語句到一個調(diào)度表,所以這樣的事情:

為什么可以這樣用?你以前可能對使用點更熟悉,有幾個特例只能用括號表示法。例如,我經(jīng)常會將switch語句重構(gòu)為查找表(速度更快),其實就像這樣:

var doSomething = function(doWhat) {
    switch(doWhat) {
        case "doThisThing":
            // more code...
            break;
        case "doThatThing":
            // more code...
            break;
        case "doThisOtherThing":
            // more code....
            break;
        // additional cases here, etc.
        default:
            // default behavior
            break;
    }
}

可以轉(zhuǎn)化為像下面這樣:

var thingsWeCanDo = {
    doThisThing : function() { /* behavior */ },
    doThatThing : function() { /* behavior */ },
    doThisOtherThing : function() { /* behavior */ },
    default : function() { /* behavior */ }
};

var doSomething = function(doWhat) {
    var thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "default"
    thingsWeCanDo[thingToDo]();
}

使用switch并沒有錯誤(并且在許多情況下,如果被迭代多次并且非常關(guān)注性能,switch可能比查找表表現(xiàn)更好)。然而查找表提供了一個很好的方法來組織和擴展代碼,并且括號允許你的屬性延時求值。

3.) 函數(shù)上下文

已經(jīng)有一些偉大的博客發(fā)表了文章,正確理解了JavaScript中的this上下文(在文章的結(jié)尾我會給出一些不錯的鏈接),但它確實應(yīng)該加到“我希望我知道”的列表。它真的困難看懂代碼并且自信的知道在任何位置this的值——你僅需要學(xué)習(xí)一組規(guī)則。不幸的是,我早起讀到的許多解釋只是增加了我的困惑。因此我試圖簡明扼要的做出解釋。

第一——首先考慮全局情況(Global)

默認情況下,直到某些原因改變了執(zhí)行上下文,否則this的值都指向全局對象。在瀏覽器中,那將會是window對象(或在node.js中為global)。

第二——方法中的this值

當(dāng)你有一個對象,其有一個函數(shù)成員,沖父對象調(diào)用這方法,this的值將指向父對象。例如:

var marty = {
    firstName: "Marty",
    lastName: "McFly",
    timeTravel: function(year) {
        console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);
    }
}

marty.timeTravel(1955);
// Marty McFly is time traveling to 1955

你可能已經(jīng)知道你能引用marty對象的timeTravel方法并且創(chuàng)建一個其他對象的新引用。這實際上是JavaScript非常強大的特色——使我們能夠在不同的實例上引用行為(調(diào)用函數(shù))。

var doc = {
    firstName: "Emmett",
    lastName: "Brown",
}

doc.timeTravel = marty.timeTravel;

所以——如果我們調(diào)用doc.timeTravel(1885)將會發(fā)生什么?

doc.timeTravel(1885);
// Emmett Brown is time traveling to 1885

再次——上演黑暗魔法。嗯,并不是真的。記住,當(dāng)你調(diào)用一個方法的時候,this上下文是被調(diào)用函數(shù)父的父對象。

當(dāng)我們保存marty.TimeTravel方法的引用然后調(diào)用我們保存的引用時發(fā)生了什么?讓我們看看:

var getBackInTime = marty.timeTravel;
getBackInTime(2014);
// undefined undefined is time traveling to 2014
為什么是“undefined undefined”?!而不是“Matry McFly”?

讓我們問一個關(guān)鍵的問題:當(dāng)我們調(diào)用我們的getBackInTime函數(shù)時父對象/容器對象是什么?當(dāng)getBackIntTime函數(shù)存在于window中時,我們調(diào)用它作為一個函數(shù),而不是一個對象的方法。當(dāng)我們像這樣調(diào)用一個函數(shù)——沒有容器對象——this上下文將是全局對象。David Shariff有一個偉大的描述關(guān)于這:

無論何時調(diào)用一個函數(shù),我們必須立刻查看括號的左邊。如果在括號的左邊存在一個引用,那么被傳遞個調(diào)用函數(shù)的this值確定為引用所屬的對象,否則是全絕對象。

由于getBackInTime的this上下文是window——沒有firstName和lastName屬性——這解釋了為什么我們看見“undefined undefined”。

因此我們知道直接調(diào)用一個函數(shù)——沒有容器對象——this上下文的結(jié)果是全局對象。然而我也說我早就知道我們的getBackInTime函數(shù)存在于window上。我是如何知道的?好的,不像上面我包裹getBackInTime在不同的上下文(我們探討立即執(zhí)行函數(shù)表達式的時候),我聲明的任何變量都被添加的window。來自Chrome控制臺的驗證:

七個最有用的JavaScript技巧

 

是時候討論下this的主要用武之地之一了:訂閱事件處理。

第三(僅僅是#2的擴展)——異步調(diào)用方法中的this值

所以,讓我們假裝我們想調(diào)用我們的marty.timeTravel方法當(dāng)有人點擊一個按鈕時:

var flux = document.getElementById("flux-capacitor");
flux.addEventListener("click", marty.timeTravel);

在上面的代碼中,當(dāng)用戶點擊按鈕是,我們會看見“undefined undefined is time traveling to [object MouseEvent]”。什么?好——首先,非常明顯的問題是我們沒有給我們的timeTravel方法提供year參數(shù)。反而,我們直接訂閱這方法作為事件處理程序,并且MouseEvent參數(shù)被作為第一個參數(shù)傳遞個事件處理程序。這是很容易修復(fù)的,但真正的問題是我們再次見到“undefined undefined”。不要無望——你已經(jīng)知道為什么會發(fā)生這種情況(即使你還沒意識到)。讓我們修改我們的timeTravel函數(shù),輸出this,從而幫助我們搞清事實:

marty.timeTravel = function(year) {
    console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);
    console.log(this);
};

現(xiàn)在——當(dāng)我們點擊這按鈕,我們將類似下面的輸出 在你的瀏覽器控制臺:

七個最有用的JavaScript技巧

 

當(dāng)方法被調(diào)用時,第二個console.log輸出出this上下文——它實際上是我們訂閱事件的按鈕元素。你感到吃驚嗎?就像之前——當(dāng)我們將marty.timeTravel賦值給getBackInTime變量時——對marty.timeTravel的引用被保存到事件處理程序,并被調(diào)用,但容器對象不再是marty對象。在這種情況下,它將在按鈕實例的點擊事件中異步調(diào)用。

所以——有可能將this設(shè)置為我們想要的結(jié)果嗎?絕對可以!在這個例子里,解決方法非常簡單。不在事件處理程序中直接訂閱marty.timeTravel,而是使用匿名函數(shù)作為事件處理程序,并在匿名函數(shù)中調(diào)用marty.timeTravel。這也能修復(fù)year參數(shù)丟失的問題。

flux.addEventListener("click",%20function(e)%20{
%20%20%20%20marty.timeTravel(someYearValue);
});

點擊按鈕將會在控制臺輸出類似下面的信息:

 

成功了!但為什么這樣可以?思考我們是如何調(diào)用timeTravel方法的。在我們按鈕點擊的第一個例子中,我們在事件處理程序中訂閱方法自身的引用,所以它沒有從父對象marty上調(diào)用。在第二個例子中,通過this為按鈕元素的匿名函數(shù),并且當(dāng)我們調(diào)用marty.timeTravel時,我們從其父對象marty上調(diào)用,所以this為marty。

第四——構(gòu)造函數(shù)中的this值

當(dāng)你用構(gòu)造函數(shù)創(chuàng)建對象實例時,函數(shù)內(nèi)部的this值就是新創(chuàng)建的對象。例如:

var TimeTraveler = function(fName, lName) {
		this.firstName = fName;
		this.lastName = lName;
		// Constructor functions return the
		// newly created object for us unless
		// we specifically return something else
};

var marty = new TimeTraveler("Marty", "McFly");
console.log(marty.firstName + " " + marty.lastName);
// Marty McFly

Call,Apply和BindCall

你可能開始疑惑,上面的例子中,沒有語言級別的特性允許我們在運行時指定調(diào)用函數(shù)的this值嗎?你是對的。存在于函數(shù)原型上的call和apply方法允許我們調(diào)用函數(shù)并傳遞this值。

call方法的第一個參數(shù)是this,后面是被調(diào)用函數(shù)的參數(shù)序列:

someFn.call(this, arg1, arg2, arg3);

apply的第一個參數(shù)也是this,后面是其余參數(shù)組成的數(shù)組:

someFn.apply(this, [arg1, arg2, arg3]);

我們的doc和marty實例他們自己能時間旅行,但einstein(愛因斯坦)需要他們的幫助才能完成時間旅行。所以讓我們給我們的doc實例添加一個方法,以至于doc能幫助einstein完成時間旅行。

doc.timeTravelFor = function(instance, year) {
		this.timeTravel.call(instance, year);
		// 如果你使用apply使用下面的語法
		// this.timeTravel.apply(instance, [year]);
};

現(xiàn)在它可以傳送Einstein 了:

var einstein = {
firstName: "Einstein",
lastName: "(the dog)"
};
doc.timeTravelFor(einstein, 1985);
// Einstein (the dog) is time traveling to 1985

我知道這個例子有些牽強,但它足以讓你看到應(yīng)用函數(shù)到其他對象的強大之處。

這種方法還有我們沒有發(fā)現(xiàn)的另一種用處。讓我們給我們的marty實例添加一個goHome方法,作為this.timeTravel(1985)的快捷方式。

marty.goHome = function() {
		this.timeTravel(1985);
}

然而,我們知道如果我們訂閱marty.goHome作為按鈕的點擊事件處理程序,this的值將是按鈕——并且不幸的是按鈕沒有timeTravel方法。我們能用上面的方法解決——用個一匿名函數(shù)作為事件處理程序,并在其內(nèi)部調(diào)用上述方法——但我們有另一個選擇——bind函數(shù):

flux.addEventListener("click", marty.goHome.bind(marty));

bind函數(shù)實際上會返回一個新函數(shù),新函數(shù)的this值根據(jù)你提供的參數(shù)設(shè)置。如果你需要支持低版本瀏覽器(例如:ie9以下版本),你可能需要bind函數(shù)的shim(或者,如果你使用jQuery你可以用$.proxy代替,underscore和lodash都提供_.bind方法)。

記住重要一點,如果你直接使用原型上的bind方法,它將創(chuàng)建一個實例方法,這將繞過原型方法的優(yōu)點。這不是錯誤,做到心里清楚就行了。我寫了關(guān)于這個問題得更多信息在這里。

4.) 函數(shù)表達式vs函數(shù)聲明

函數(shù)聲明不需要var關(guān)鍵字。事實上,如Angus Croll所說:“把他們想象成變量聲明的兄弟有助于理解”。例如:

function timeTravel(year) {
		console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);
} 上面例子里的函數(shù)名字timeTravel不僅在它聲明的在作用域可見,同時在函數(shù)本身內(nèi)部也是可見的(這對遞歸函數(shù)調(diào)用非常有用)。函數(shù)聲明,本質(zhì)上說其實就是命名函數(shù)。換句話說,上面函數(shù)的名稱屬性是timeTravel。

函數(shù)表達式定義一個函數(shù)并指派給一個變量。典型應(yīng)用如下:

var someFn = function() {
		console.log("I like to express myself...");
}; 也可以對函數(shù)表達式命名——然而,不像函數(shù)聲明,命名函數(shù)表達式的名字僅在它自身函數(shù)體內(nèi)可訪問:

var someFn = function iHazName() {
		console.log("I like to express myself...");
		if(needsMoreExpressing) {
				iHazName(); // 函數(shù)的名字在這里可以訪問
		}
};

// 你可以在這里調(diào)用someFn(),但不能調(diào)用iHazName()
someFn();

討論函數(shù)表達式和函數(shù)聲明不能不提“hoisting(提升)”——函數(shù)和變量聲明被編譯器移到作用域的頂部。在這里我們無法詳細解釋hoisting,但你可以讀Ben Cherry和Angus Croll兩個人的偉大解釋。

5.) 命名vs匿名函數(shù)

基于我們剛才的討論,你可能一進猜到“匿名”函數(shù)其實就是一個沒有名字的函數(shù)。大多數(shù)JavaScript開發(fā)者能迅速識別瞎買年第一個參數(shù)為匿名函數(shù):

someElement.addEventListener("click", function(e) {
		// I'm anonymous!
});

然而,同樣的我們的marty.timeTravvel方法也是一個匿名函數(shù):

var marty = {
		firstName: "Marty",
		lastName: "McFly",
		timeTravel: function(year) {
				console.log(this.firstName + " " + this.lastName + " is time traveling to " + year);
		}
}

因為函數(shù)聲明必須有一個唯一的名字,只有函數(shù)表達式可以沒有名字。

6.) 立即執(zhí)行函數(shù)表達式

因為我們正在談?wù)摵瘮?shù)表達式,有一個東西我希望我早知道:立即執(zhí)行函數(shù)表達式(IIFE)。有很多關(guān)于IIFE的好文章(我將在文章結(jié)尾出列出),但用一句話來形容,函數(shù)表達式不是通過將函數(shù)表達式賦值給一個標量,稍后再執(zhí)行,而是理解執(zhí)行。可以在瀏覽器控制臺看這一過程。

首先——讓我們先敲入一個函數(shù)表達式——但不給它指派變量——看看會發(fā)什么:

七個最有用的JavaScript技巧

 

語法錯誤——這被認為是函數(shù)聲明,缺少函數(shù)名字。然而,為了使其變?yōu)楸磉_式,我們僅需將其包裹在括號內(nèi):

七個最有用的JavaScript技巧

 

讓其變?yōu)楸磉_式后控制臺返回給我們一個匿名函數(shù)(記住,我們沒有為其指派值,但表達式會有返回值)。所以——我們知道“函數(shù)表達式”是“立即調(diào)用函數(shù)表達式”的一部分。為了等到“立即執(zhí)行”的特性,我們通過在表達式后面添加另一個括號來調(diào)用返回的表達式(就像我們調(diào)用其他函數(shù)一樣):

七個最有用的JavaScript技巧

 

“但是等一下,Jim!(指作者)我想我以前見過這種調(diào)用方式”。 事實上你可能見過——這是合法的語法(眾所周知的是Douglas Crockford的首選語法)

七個最有用的JavaScript技巧

 

這兩種方法都起作用,但是我強烈建議你讀一讀這里。

OK,非常棒——現(xiàn)在我們已經(jīng)知道了IIFE是什么——以及為什么要用它?

它幫助我們控制作用域——任何JavaScript教程中非常重要的部分!前面我們看到的許多實例都創(chuàng)建在全局作用域。這意味著window(假設(shè)環(huán)境是瀏覽器)對象將有很多屬性。如果我們?nèi)堪凑者@種方式寫我們的JavaScript代碼,我們會迅速在全局作用域積累一噸(夸張)變量聲明,window代碼會被污染。即使在最好的情況下,在全局變量暴漏許多細節(jié)是糟糕的建議,但當(dāng)變量的名字和已經(jīng)存在的window屬性名字相同時會發(fā)生什么呢?window屬性會被重寫!

例如,如果你最喜歡的“Amelia Earhart”網(wǎng)站在全局作用域聲明了一個navigator變量,下面是設(shè)置之前和之后的結(jié)果:

七個最有用的JavaScript技巧

 

哎呀!

顯而易見——全局變量被污染是糟糕的。JavaScript使用函數(shù)作用域(而不是塊作用域,如果你來自C#或Java,這點非常重要!),所以保持我們的代碼和全局作用域分離的辦法是創(chuàng)建一個新作用域,我們可以使用IIFE來實現(xiàn),因為它的內(nèi)容在它自己的函數(shù)作用域內(nèi)。在下面的例子中,我將在控制臺向你顯示window.navigator的值,然后我常見一個IIFE(立即執(zhí)行函數(shù)表達式)去包裹Amelia Earhart的行為和數(shù)據(jù)。IIFE結(jié)束后返回一個作為我們的“程序命名空間”的對象。我在IIFE內(nèi)聲明的navigator變量將不會重寫window.navigator的值。

七個最有用的JavaScript技巧

 

作為額外好處,我們上面創(chuàng)建的IIFE是JavaScript中模塊模式的啟蒙。我將在結(jié)尾處包括一些我瀏覽的模塊模式的鏈接。

7.) ‘typeof’操作符和’Object.prototype.toString’

最終,可能發(fā)現(xiàn)在某些情況下,你需要檢查傳遞給函數(shù)參數(shù)的類型,或其他類似的東西。typeof運算符會是顯而易見的選擇,但是,這并不是萬能的。例如,當(dāng)我們對一個對象,數(shù)組,字符串或正則表達式,調(diào)用typeof運算符時會發(fā)生什么?

七個最有用的JavaScript技巧

 

還好——至少我們可以將字符串和對象,數(shù)組,正則表達式區(qū)分開,對嗎?幸運的是,我們可以得到更準確的類型信息,我們有其他不同的方法。我們將使用Object.prototype.toString方法,并且應(yīng)用我們前面提到的call方法:

七個最有用的JavaScript技巧

 

為什么我們要使用Object.prototype上的toString方法?因為第三方庫或你自己的代碼可能重寫實例的toString方法。通過Object.prototype,我們可以強制實現(xiàn)實例原來的toString行為。

如果你知道typeof將會返回什么那么你不需要進行多余的檢查(例如,你僅需要知道是或不是一個字符串),此時用typeof非常好。然而,如果你需要區(qū)分數(shù)組和對象,正則表達式和對象,等等,那么使用Object.prototype.toString吧。

分享到:
標簽:JavaScript
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定