JavaScript札記

實現一個 sleep 函數javascript


====================================================================html


若是繼承的屬性是可遍歷的,那麼就會被for...in循環遍歷到。可是,通常狀況下,都是隻想遍歷對象自身的屬性,因此使用for...in的時候,應該結合使用hasOwnProperty方法,在循環內部判斷一下,某個屬性是否爲對象自身的屬性。java


====================================================================
jquery

閉包(closure)是 Javascript 語言的一個難點,也是它的特點,不少高級應用都要依靠閉包實現。es6

理解閉包,首先必須理解變量做用域。前面提到,JavaScript 有兩種做用域:全局做用域和函數做用域。函數內部能夠直接讀取全局變量。web


上面代碼中,函數f1能夠讀取全局變量najax

可是,函數外部沒法讀取函數內部聲明的變量。算法


上面代碼中,函數f1內部聲明的變量n,函數外是沒法讀取的。json

若是出於種種緣由,須要獲得函數內的局部變量。正常狀況下,這是辦不到的,只有經過變通方法才能實現。那就是在函數的內部,再定義一個函數。跨域


上面代碼中,函數f2就在函數f1內部,這時f1內部的全部局部變量,對f2都是可見的。可是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是 JavaScript 語言特有的"鏈式做用域"結構(chain scope),子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。

既然f2能夠讀取f1的局部變量,那麼只要把f2做爲返回值,咱們不就能夠在f1外部讀取它的內部變量了嗎!


上面代碼中,函數f1的返回值就是函數f2,因爲f2能夠讀取f1的內部變量,因此就能夠在外部得到f1的內部變量了。

閉包就是函數f2,即可以讀取其餘函數內部變量的函數。因爲在 JavaScript 語言中,只有函數內部的子函數才能讀取內部變量,所以能夠把閉包簡單理解成「定義在一個函數內部的函數」。閉包最大的特色,就是它能夠「記住」誕生的環境,好比f2記住了它誕生的環境f1,因此從f2能夠獲得f1的內部變量。在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

閉包的最大用處有兩個,一個是能夠讀取函數內部的變量,另外一個就是讓這些變量始終保持在內存中,即閉包可使得它誕生環境一直存在。請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果。


上面代碼中,start是函數createIncrementor的內部變量。經過閉包,start的狀態被保留了,每一次調用都是在上一次調用的基礎上進行計算。從中能夠看到,閉包inc使得函數createIncrementor的內部環境,一直存在。因此,閉包能夠看做是函數內部做用域的一個接口。

爲何會這樣呢?緣由就在於inc始終在內存中,而inc的存在依賴於createIncrementor,所以也始終在內存中,不會在調用結束後,被垃圾回收機制回收。

閉包的另外一個用處,是封裝對象的私有屬性和私有方法。


上面代碼中,函數Person的內部變量_age,經過閉包getAgesetAge,變成了返回對象p1的私有變量。

注意,外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。所以不能濫用閉包,不然會形成網頁的性能問題。

閉包的做用總結

一、可以訪問函數定義時所在的詞法做用域(阻止其被回收)。

二、私有化變量


三、模擬塊級做用域


四、建立模塊


模塊模式具備兩個必備的條件(來自《你不知道的JavaScript》)

  • 必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的模塊實例)
  • 封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,而且能夠訪問或者修改私有的狀態。

====================================================================

典型的「相似數組的對象」是函數的arguments對象,以及大多數 DOM 元素集,還有字符串。


上面代碼包含三個例子,它們都不是數組(instanceof運算符返回false),可是看上去都很是像數組。

數組的slice方法能夠將「相似數組的對象」變成真正的數組。

var arr = Array.prototype.slice.call(arrayLike);複製代碼

====================================================================

避免數組處理方法中的this

數組的mapforeach方法,容許提供一個函數做爲參數。這個函數內部不該該使用this


上面代碼中,foreach方法的回調函數中的this,實際上是指向window對象,所以取不到o.v的值。緣由跟上一段的多層this是同樣的,就是內層的this不指向外部,而指向頂層對象。

解決這個問題的一種方法,就是前面提到的,使用中間變量固定this


另外一種方法是將this看成foreach方法的第二個參數,固定它的運行環境。


====================================================================

JS的數據類型

1.基本類型(值類型或者原始類型): Number、Boolean、String、NULL、Undefined以及ES6的Symbol
2.引用類型:Object、Array、Function、Date等

在內存中的位置

  • 基本類型: 佔用空間固定,保存在棧中

  • 引用類型:佔用空間不固定,保存在堆中

棧(stack)爲自動分配的內存空間,它由系統自動釋放;使用一級緩存,被調用時一般處於存儲空間中,調用後被當即釋放。
堆(heap)則是動態分配的內存,大小不定也不會自動釋放。使用二級緩存,生命週期與虛擬機的GC算法有關

當一個方法執行時,每一個方法都會創建本身的內存棧,在這個方法內定義的變量將會逐個放入這塊棧內存裏,隨着方法的執行結束,這個方法的內存棧也將天然銷燬了。所以,全部在方法中定義的變量都是放在棧內存中的;棧中存儲的是基礎變量以及一些對象的引用變量,基礎變量的值是存儲在棧中,而引用變量存儲在棧中的是指向堆中的數組或者對象的地址,這就是爲什麼修改引用類型總會影響到其餘指向這個地址的引用變量。

當咱們在程序中建立一個對象時,這個對象將被保存到運行時數據區中,以便反覆利用(由於對象的建立成本一般較大),這個運行時數據區就是堆內存。堆內存中的對象不會隨方法的結束而銷燬,即便方法結束後,這個對象還可能被另外一個引用變量所引用(方法的參數傳遞時很常見),則這個對象依然不會被銷燬,只有當一個對象沒有任何引用變量引用它時,系統的垃圾回收機制纔會在覈實的時候回收它。

#賦值、淺拷貝、深拷貝

  • 對於基本類型值,賦值、淺拷貝、深拷貝時都是複製基本類型的值給新的變量,以後二個變量之間操做不在相互影響。

  • 對於引用類型值,

  • 賦值後二個變量指向同一個地址,一個變量改變時,另外一個也一樣改變;

    淺拷貝後獲得一個新的變量,這個與以前的已經不是指向同一個變量,改變時不會使原數據中的基本類型一同改變,但會改變會原數據中的引用類型數據

    深拷貝後獲得的是一個新的變量,她的改變不會影響元數據

- 和原數據是否指向同一對象 第一層數據爲基本數據類型 原數據中包含子對象
賦值 改變會使原數據一同改變 改變會使原數據一同改變
淺拷貝 改變不會使原數據一同改變 改變會使原數據一同改變
深拷貝 改變不會使原數據一同改變 改變不會使原數據一同改變


##淺拷貝

數組經常使用的淺拷貝方法slice,concat,Array.from() ,以及es6的析構


對象經常使用的淺拷貝方法Object.assign(),es6析構


本身實現一個淺拷貝


##深拷貝

比較簡單粗暴的的作法是使用JSON.parse(JSON.stringify(obj))


JSON.parse(JSON.stringify(obj)) 看起來很不錯,不過MDN文檔 的描述有句話寫的很清楚:

undefined、任意的函數以及 symbol 值,在序列化過程當中會被忽略(出如今非數組對象的屬性值
中時)或者被轉換成 null(出如今數組中時)。
複製代碼

JSON.parse(JSON.stringify(obj)) 的一些缺陷:

  1. 對象的屬性值是函數時,沒法拷貝。
  2. 原型鏈上的屬性沒法拷貝
  3. 不能正確的處理 Date 類型的數據
  4. 不能處理 RegExp
  5. 會忽略 symbol
  6. 會忽略 undefined

可是在平時的開發中JSON.parse(JSON.stringify(obj))已經知足90%的使用場景了。
下面咱們本身來實現一個


#參數的傳遞

全部的函數參數都是按值傳遞。也就是說把函數外面的值賦值給函數內部的參數,就和把一個值從一個變量賦值給另外一個同樣;

基本類型


引用類型


不少人錯誤地覺得在局部做用域中修改的對象在全局做用域中反映出來就是說明參數是按引用傳遞的。
可是經過上面的例子能夠看出若是person是按引用傳遞的最終的person.name應該是Tom。
實際上當函數內部重寫obj時,這個變量引用的就是一個局部變量了。而這個變量會在函數執行結束後銷燬。(這是是在js高級程序設計看到的,還不是很清楚)

#判斷方法

基本類型用typeof,引用類型用instanceof

特別注意typeof null是"object", null instanceof Object是true;複製代碼


Object.prototype.toString.call([]).slice(8, -1)有興趣的同窗能夠看一下這個是幹什麼的複製代碼

#總結


====================================================================

#搞懂JSONP

script標籤的src屬性中的連接能夠訪問跨域的js腳本,利用這個特性,服務端再也不返回JSON格式的數據,而是返回一段調用某個函數的js代碼,在src中進行了調用,這樣實現了跨域。(不只如此,咱們還發現凡是擁有」src」這個屬性的標籤都擁有跨域的能力,好比<\script>、<\img>、<\iframe>)

##JSONP客戶端具體實現

無論jQuery也好,extjs也罷,又或者是其餘支持jsonp的框架,他們幕後所作的工做都是同樣的,下面我來按部就班的說明一下jsonp在客戶端的實現:

一、咱們知道,哪怕跨域js文件中的代碼(固然指符合web腳本安全策略的),web頁面也是能夠無條件執行的。

遠程服務器remoteserver.com根目錄下有個remote.js文件代碼以下:

alert('我是遠程文件');複製代碼

本地服務器localserver.com下有個jsonp.html頁面代碼以下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>複製代碼

毫無疑問,頁面將會彈出一個提示窗體,顯示跨域調用成功。

二、如今咱們在jsonp.html頁面定義一個函數,而後在遠程remote.js中傳入數據進行調用。

jsonp.html頁面代碼以下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    var localHandler = function(data){
        alert('我是本地函數,能夠被跨域的remote.js文件調用,遠程js帶來的數據是:' + data.result);
    };
    </script>
    <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
 
</body>
</html>複製代碼

remote.js文件代碼以下:

localHandler({"result":"我是遠程js帶來的數據"});複製代碼

運行以後查看結果,頁面成功彈出提示窗口,顯示本地函數被跨域的遠程js調用成功,而且還接收到了遠程js帶來的數據。
很欣喜,跨域遠程獲取數據的目的基本實現了,可是又一個問題出現了,我怎麼讓遠程js知道它應該調用的本地函數叫什麼名字呢?畢竟是jsonp的服務者都要面對不少服務對象,而這些服務對象各自的本地函數都不相同啊?咱們接着往下看。

三、聰明的開發者很容易想到,只要服務端提供的js腳本是動態生成的就好了唄,這樣調用者能夠傳一個參數過去告訴服務端 「我想要一段調用XXX函數的js代碼,請你返回給我」,因而服務器就能夠按照客戶端的需求來生成js腳本並響應了。

看jsonp.html頁面的代碼:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script type="text/javascript">
    // 獲得航班信息查詢結果後的回調函數
    var flightHandler = function(data){
        alert('你查詢的航班結果是:票價 ' + data.price + ' 元,' + '餘票 ' + data.tickets + ' 張。');
    };
    // 提供jsonp服務的url地址(無論是什麼類型的地址,最終生成的返回值都是一段javascript代碼)
    var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
    // 建立script標籤,設置其屬性
    var script = document.createElement('script');
    script.setAttribute('src', url);
    // 把script標籤加入head,此時調用開始
    document.getElementsByTagName('head')[0].appendChild(script); 
    </script>
</head>
<body>
 
</body>
</html>複製代碼

此次的代碼變化比較大,再也不直接把遠程js文件寫死,而是編碼實現動態查詢,而這也正是jsonp客戶端實現的核心部分,本例中的重點也就在於如何完成jsonp調用的全過程。
咱們看到調用的url中傳遞了一個code參數,告訴服務器我要查的是CA1998次航班的信息,而callback參數則告訴服務器,個人本地回調函數叫作flightHandler,因此請把查詢結果傳入這個函數中進行調用。
OK,服務器很聰明,這個叫作flightResult.aspx的頁面生成了一段這樣的代碼提供給jsonp.html

(服務端的實現這裏就不演示了,與你選用的語言無關,說到底就是拼接字符串):

flightHandler({
    "code": "CA1998",
    "price": 1780,
    "tickets": 5
});複製代碼

四、到這裏爲止的話,相信你已經可以理解jsonp的客戶端實現原理了吧?剩下的就是如何把代碼封裝一下,以便於與用戶界面交互,從而實現屢次和重複調用

jQuery如何實現jsonp調用?

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
     <title>Untitled Page</title>
      <script type="text/javascript" src=jquery.min.js"></script> <script type="text/javascript"> jQuery(document).ready(function(){ $.ajax({ type: "get", async: false, url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998", dataType: "jsonp", jsonp: "callback",//傳遞給請求處理程序或頁面的,用以得到jsonp回調函數名的參數名(通常默認爲:callback) jsonpCallback:"flightHandler",//自定義的jsonp回調函數名稱,默認爲jQuery自動生成的隨機函數名,也能夠寫"?",jQuery會自動爲你處理數據 success: function(json){ alert('您查詢到航班信息:票價: ' + json.price + ' 元,餘票: ' + json.tickets + ' 張。'); }, error: function(){ alert('fail'); } }); }); </script> </head> <body> </body> </html>複製代碼

是否是有點奇怪?爲何我此次沒有寫flightHandler這個函數呢?並且居然也運行成功了!
這就是jQuery的功勞了,jquery在處理jsonp類型的ajax時(,雖然jquery也把jsonp納入了ajax,但其實它們真的不是一回事兒),自動幫你生成回調函數並把數據取出來供success屬性方法來調用,是否是很爽呀?
補充

這裏針對ajax與jsonp的異同再作一些補充說明:

一、ajax和jsonp這兩種技術在調用方式上」看起來」很像,目的也同樣,都是請求一個url,而後把服務器返回的數據進行處理,所以jquery和ext等框架都把jsonp做爲ajax的一種形式進行了封裝。

二、但ajax和jsonp其實本質上是不一樣的東西。ajax的核心是經過XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態添加

====================================================================

實現數組去重

uniq([1, 2, 3, 5, 3, 2]); // [1, 2, 3, 5]

一、利用ES6新增數據類型Set

function uniq(arry) {
    return [...new Set(arry)];
}複製代碼

二、利用indexOf

function uniq(arry) {
    var result = [];
    for(var i = 0; i < arry.length, i++){
        if(result.indexOf(arry[i]) === -1){
            result.push(arry[i]);
        }
    }
    return result;
}複製代碼

三、利用includes

function uniq(arry) {
    var result = [];
    for(var i = 0; i < arry.length, i++){
        if(!result.includes(arry[i])){
            result.push(arry[i]);
        }
    }
    return result;
}複製代碼

四、利用reduce

function uniq(arry){
    return arry.reduce((prev, cur) => prev.includes(cur) ? prev : [...prev, cur], [])
}複製代碼

五、利用Map

function uniq(arry){
    let map = new Map();
    let result = new Array();
    for(let i - 0; i < arry.length; i++){
        if(map.has(arry[i])){
            map.set(arry[i], true);
        }else {
            map.set(arry[i], false);
            result.push(arry[i]);
        }
    }
    return result;
}複製代碼

====================================================================

You will soon get something you have always desired.

相關文章
相關標籤/搜索