[TOC]javascript
本部分主要是針對於JavaScript中經常使用的數據結構類型進行分析說明。css
在JavaScript中,基本的變量聲明能夠用var方式。JavaScript容許省略var,直接對未聲明的變量賦值。也就是說,var a = 1 與 a = 1,這兩條語句的效果相同。可是因爲這樣的作法很容易不知不覺地建立全局變量(尤爲是在函數內部),因此建議老是使用var命令聲明變量。html
在ES6中,對於變量聲明的方式進行了擴展。引入了let方式,let便是定義塊級別的變量,不會以閉包方式擴展到塊外面。另外一種引入了能夠用於定義常量的const聲明。前端
function f() { { let x; { // okay, block scoped name const x = "sneaky"; // error, const x = "foo"; } // error, already declared in block let x = "inner"; } }
JavaScript是解釋執行的語言, JavaScript引擎的工做方式是,先解析代碼,獲取全部被聲明的變量,而後再一行一行地運行。這形成的結果,就是全部的變量的聲明語句,都會被提高到代碼的頭部,這就叫作變量提高(hoisting)。java
console.log(a); var a = 1;
上面代碼首先使用console.log方法,在控制檯(console)顯示變量a的值。這時變量a尚未聲明和賦值,因此這是一種錯誤的作法,可是實際上不會報錯。由於存在變量提高,真正運行的是下面的代碼:node
var a; console.log(a); a = 1;
最後的結果是顯示undefined,表示變量a已聲明,但還未賦值。請注意,變量提高只對var命令聲明的變量有效,若是一個變量不是用var命令聲明的,就不會發生變量提高。git
console.log(b); b = 1;
上面的語句將會報錯,提示「ReferenceError: b is not defined」,即變量b未聲明,這是由於b不是用var命令聲明的,JavaScript引擎不會將其提高,而只是視爲對頂層對象的b屬性的賦值。es6
在計算機編程中,全局變量指的是在全部做用域中都能訪問的變量。全局變量是一種很差的實踐,由於它會致使一些問題,好比一個已經存在的方法和全局變量的覆蓋,當咱們不知道變量在哪裏被定義的時候,代碼就變得很難理解和維護了。在ES6中能夠利用let
關鍵字來聲明本地變量,好的 JavaScript 代碼就是沒有定義全局變量的。有一些技術能夠幫助你讓全部的事情都保持在本地:github
爲了不全局變量,第一件事情就是要確保全部的代碼都被包在函數中。最簡單的辦法就是把全部的代碼都直接放到一個函數中去:web
(function(win) { "use strict"; // 進一步避免建立全局變量 var doc = window.document; // 在這裏聲明你的變量 // 一些其餘的代碼 }(window));
var MyApp = { namespace: function(ns) { var parts = ns.split("."), object = this, i, len; for(i = 0, len = parts.lenght; i < len; i ++) { if(!object[parts[i]]) { object[parts[i]] = {}; } object = object[parts[i]]; } return object; } }; // 定義命名空間 MyApp.namespace("Helpers.Parsing"); // 你如今可使用該命名空間了 MyApp.Helpers.Parsing.DateParser = function() { //作一些事情 };
另外一項開發者用來避免全局變量的技術就是封裝到模塊 Module
中。一個模塊就是不須要建立新的全局變量或者命名空間的通用的功能。不要將全部的代碼都放一個負責執行任務或者發佈接口的函數中。最多見的 JavaScript 模塊類型就是異步模塊定義 Asynchronous Module Definition (AMD)
。
//定義 define( "parsing", //模塊名字 [ "dependency1", "dependency2" ], // 模塊依賴 function( dependency1, dependency2) { //工廠方法 // Instead of creating a namespace AMD modules // are expected to return their public interface var Parsing = {}; Parsing.DateParser = function() { //do something }; return Parsing; } ); // 經過 Require.js 加載模塊 require(["parsing"], function(Parsing) { Parsing.DateParser(); // 使用模塊 });
解構賦值容許你使用相似數組或對象字面量的語法將數組和對象的屬性賦給各類變量。這種賦值語法極度簡潔,同時還比傳統的屬性訪問方法更爲清晰。
傳統的訪問數組前三個元素的方式爲:
var first = someArray[0]; var second = someArray[1]; var third = someArray[2];
而經過解構賦值的特性,能夠變爲:
var [first, second, third] = someArray;
以上是數組解構賦值的一個簡單示例,其語法的通常形式爲:
[ variable1, variable2, ..., variableN ] = array;
這將爲variable1到variableN的變量賦予數組中相應元素項的值。若是你想在賦值的同時聲明變量,可在賦值語句前加入var
、let
或const
關鍵字,例如:
var [ variable1, variable2, ..., variableN ] = array; let [ variable1, variable2, ..., variableN ] = array; const [ variable1, variable2, ..., variableN ] = array;
事實上,用變量
來描述並不恰當,由於你能夠對任意深度的嵌套數組進行解構:
var [foo, [[bar], baz]] = [1, [[2], 3]]; console.log(foo); // 1 console.log(bar); // 2 console.log(baz); // 3
此外,你能夠在對應位留空來跳過被解構數組中的某些元素:
var [,,third] = ["foo", "bar", "baz"]; console.log(third); // "baz"
並且你還能夠經過「不定參數」模式捕獲數組中的全部尾隨元素:
var [head, ...tail] = [1, 2, 3, 4]; console.log(tail); // [2, 3, 4]
當訪問空數組或越界訪問數組時,對其解構與對其索引的行爲一致,最終獲得的結果都是:undefined
。
console.log([][0]); // undefined var [missing] = []; console.log(missing); // undefined
請注意,數組解構賦值的模式一樣適用於任意迭代器:
function* fibs() { var a = 0; var b = 1; while (true) { yield a; [a, b] = [b, a + b]; } } var [first, second, third, fourth, fifth, sixth] = fibs(); console.log(sixth); // 5
經過解構對象,你能夠把它的每一個屬性與不一樣的變量綁定,首先指定被綁定的屬性,而後緊跟一個要解構的變量。
var robotA = { name: "Bender" }; var robotB = { name: "Flexo" }; var { name: nameA } = robotA; var { name: nameB } = robotB; console.log(nameA); // "Bender" console.log(nameB); // "Flexo"
當屬性名與變量名一致時,能夠經過一種實用的句法簡寫:
var { foo, bar } = { foo: "lorem", bar: "ipsum" }; console.log(foo); // "lorem" console.log(bar); // "ipsum"
與數組解構同樣,你能夠隨意嵌套並進一步組合對象解構:
var complicatedObj = { arrayProp: [ "Zapp", { second: "Brannigan" } ] }; var { arrayProp: [first, { second }] } = complicatedObj; console.log(first); // "Zapp" console.log(second); // "Brannigan"
當你解構一個未定義的屬性時,獲得的值爲undefined
:
var { missing } = {}; console.log(missing); // undefined
請注意,當你解構對象並賦值給變量時,若是你已經聲明或不打算聲明這些變量(亦即賦值語句前沒有let
、const
或var
關鍵字),你應該注意這樣一個潛在的語法錯誤:
{ blowUp } = { blowUp: 10 }; // Syntax error 語法錯誤
爲何會出錯?這是由於JavaScript語法通知解析引擎將任何以{開始的語句解析爲一個塊語句(例如,{console}
是一個合法塊語句)。解決方案是將整個表達式用一對小括號包裹:
({ safe } = {}); // No errors 沒有語法錯誤
當你要解構的屬性未定義時你能夠提供一個默認值:
var [missing = true] = []; console.log(missing); // true var { message: msg = "Something went wrong" } = {}; console.log(msg); // "Something went wrong" var { x = 3 } = {}; console.log(x); // 3
因爲解構中容許對對象進行解構,而且還支持默認值,那麼徹底能夠將解構應用在函數參數以及參數的默認值中。
function removeBreakpoint({ url, line, column }) { // ... }
當咱們構造一個提供配置的對象,而且須要這個對象的屬性攜帶默認值時,解構特性就派上用場了。舉個例子,jQuery的ajax
函數使用一個配置對象做爲它的第二參數,咱們能夠這樣重寫函數定義:
jQuery.ajax = function (url, { async = true, beforeSend = noop, cache = true, complete = noop, crossDomain = false, global = true, // ... 更多配置 }) { // ... do stuff };
一樣,解構也能夠應用在函數的多重返回值中,能夠相似於其餘語言中的元組的特性:
function returnMultipleValues() { return [1, 2]; } var [foo, bar] = returnMultipleValues();
function myFunction(x, y, z) { } var args = [0, 1, 2]; myFunction(...args);
還有另一個好處就是能夠用來替換Object.assign來方便地從舊有的對象中建立新的對象,而且可以修改部分值。譬如
var obj = {a:1,b:2} var obj_new_1 = Object.assign({},obj,{a:3}); var obj_new_2 = { ...obj, a:3 }
typeof運算符能夠返回一個值的數據類型,可能有如下結果。
(1)原始類型
數值、字符串、布爾值分別返回number、string、boolean。
typeof 123 // "number" typeof "123" // "string" typeof false // "boolean"
(2)函數
函數返回function。
// 定義一個空函數 function f(){} typeof f // "function"
(3)undefined
undefined返回undefined。
typeof undefined // "undefined"
利用這一點,typeof能夠用來檢查一個沒有聲明的變量,而不報錯。
v // ReferenceError: v is not defined typeof v // "undefined"
實際編程中,這個特色一般用在判斷語句。
// 錯誤的寫法 if (v){ // ... } // ReferenceError: v is not defined // 正確的寫法 if (typeof v === "undefined"){ // ... }
(4)其餘
除此之外,都返回object。
typeof window // "object" typeof {} // "object" typeof [] // "object" typeof null // "object"
從上面代碼能夠看到,空數組([])的類型也是object,這表示在JavaScript內部,數組本質上只是一種特殊的對象。另外,null的類型也是object,這是因爲歷史緣由形成的,爲了兼容之前的代碼,後來就無法修改了,並非說null就屬於對象,本質上null是一個相似於undefined的特殊值。
typeof對數組(array)和對象(object)的顯示結果都是object,那麼怎麼區分它們呢?instanceof運算符能夠作到。
var o = {}; var a = []; o instanceof Array // false a instanceof Array // true
當遇到如下幾種狀況,JavaScript會自動轉換數據類型:
不一樣類型的數據進行互相運算;
對非布爾值類型的數據求布爾值;
對非數值類型的數據使用一元運算符(即「+」和「-」)。
random() 方法可返回介於 0 ~ 1 之間的一個隨機數。
<script type="text/javascript"> document.write(Math.random()) </script>
使用Number函數,能夠將任意類型的值轉化成數字。
數值:轉換後仍是原來的值。
字符串:若是能夠被解析爲數值,則轉換爲相應的數值,不然獲得NaN。空字符串轉爲0。
布爾值:true轉成1,false轉成0。
undefined:轉成NaN。
null:轉成0。
Number函數將字符串轉爲數值,要比parseInt函數嚴格不少。基本上,只要有一個字符沒法轉成數值,整個字符串就會被轉爲NaN。
parseInt('011') // 9 parseInt('42 cats') // 42 parseInt('0xcafebabe') // 3405691582 Number('011') // 11 Number('42 cats') // NaN Number('0xcafebabe') // 3405691582
若是Number傳入的參數是一個對象,那麼轉換規則會相對複雜一點,具體而言描述以下:
先調用對象自身的valueOf方法,若是該方法返回原始類型的值(數值、字符串和布爾值),則直接對該值使用Number方法,再也不進行後續步驟。
若是valueOf方法返回複合類型的值,再調用對象自身的toString方法,若是toString方法返回原始類型的值,則對該值使用Number方法,再也不進行後續步驟。
若是toString方法返回的是複合類型的值,則報錯。
這裏用的是numeraljs,格式化顯示的效果以下所示:
Number
Number | Format | String |
---|---|---|
10000 | '0,0.0000' | 10,000.0000 |
10000.23 | '0,0' | 10,000 |
10000.23 | '+0,0' | +10,000 |
Currency
Number | Format | String |
---|---|---|
1000.234 | '$0,0.00' | $1,000.23 |
1000.2 | '0,0[.]00 $' | 1,000.20 $ |
Bytes
Number | Format | String |
---|---|---|
100 | '0b' | 100B |
2048 | '0 b' | 2 KB |
Percentages
Number | Format | String |
---|---|---|
1 | '0%' | 100% |
0.974878234 | '0.000%' | 97.488% |
Time
Number | Format | String |
---|---|---|
25 | '00:00:00' | 0:00:25 |
238 | '00:00:00' | 0:03:58 |
布爾值表明「真」和「假」兩個狀態。「真」用關鍵字true表示,「假」用關鍵字false表示。布爾值只有這兩個值。若是JavaScript預期某個位置應該是布爾值,會將該位置上現有的值自動轉爲布爾值。轉換規則是除了下面六個值被轉爲false,其餘值都視爲true。
undefined
null
false
0
NaN
""(空字符串)
全部對象的布爾值都是true,甚至連false對應的布爾對象也是true。
Boolean(new Boolean(false)) // true
JavaScript中常見的空類型爲undefined與null,不過typeof undefined === ‘undefined’
而 typeof null === ‘object’
。
null表示"沒有對象",即該處不該該有值。典型用法是:
做爲函數的參數,表示該函數的參數是對象。
做爲對象原型鏈的終點。
undefined表示"缺乏值",就是此處應該有一個值,可是還未定義。典型用法是:
變量被聲明瞭,但沒有賦值時,就等於undefined。
調用函數時,應該提供的參數沒有提供,該參數等於undefined。
對象沒有賦值的屬性,該屬性的值爲undefined。
函數沒有返回值時,默認返回undefined。
Symbols是JavaScript的第七種原始類型,它代指一個全局惟一的不可變對象。若是須要建立一個Symbol對象,則須要調用Symbol函數:
var sym1 = Symbol(); var sym2 = Symbol("foo"); var sym3 = Symbol("foo");
如上的代碼會建立三個新的符號,注意,雖然Symbol使用了」foo」這個字符串做爲輸入對象,可是每次會建立一個新的符號:
Symbol("foo") === Symbol("foo"); // false
確切地說,symbol與其它類型並不徹底相像。symbol被建立後就不可變動,你不能爲它設置屬性(在嚴格模式下嘗試設置屬性會獲得TypeError的錯誤)。他們能夠用做屬性名稱,這些性質與字符串相似。
另外一方面,每個symbol都獨一無二,不與其它symbol等同,即便兩者有相同的描述也不相等;你能夠輕鬆地建立一個新的symbol。這些性質與對象相似。
ES6中的symbol與Lisp和Ruby這些語言中更傳統的symbol相似,但不像它們集成得那麼緊密。在Lisp中,全部的標識符都是symbol;在JS中,標識符和大多數的屬性鍵仍然是字符串,symbol只是一個額外的選項。
關於symbol的忠告:symbol不能被自動轉換爲字符串,這和語言中的其它類型不一樣。嘗試拼接symbol與字符串將獲得TypeError錯誤。
> var sym = Symbol("<3"); > "your symbol is " + sym // TypeError: can't convert symbol to string > `your symbol is ${sym}` // TypeError: can't convert symbol to string
有三種獲取symbol的方法。
調用Symbol()。正如咱們上文中所討論的,這種方式每次調用都會返回一個新的惟一symbol。
調用Symbol.for(string)。這種方式會訪問symbol註冊表,其中存儲了已經存在的一系列symbol。這種方式與經過Symbol()
定義的獨立symbol不一樣,symbol註冊表中的symbol是共享的。若是你連續三十次調用Symbol.for("cat")
,每次都會返回相同的symbol。註冊表很是有用,在多個web頁面或同一個web頁面的多個模塊中常常須要共享一個symbol。
使用標準定義的symbol,例如:Symbol.iterator。標準根據一些特殊用途定義了少量的幾個symbol。
ES6中開始支持較爲複雜的模板字符串方式:
// Basic literal string creation `In JavaScript '\n' is a line-feed.` // Multiline strings `In JavaScript this is not legal.` // String interpolation var name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?` // Construct an HTTP request prefix is used to interpret the replacements and construction GET`http://foo.org/bar?a=${a}&b=${b} Content-Type: application/json X-Credentials: ${credentials} { "foo": ${foo}, "bar": ${bar}}`(myOnReadyStateChangeHandler);
使用String函數,能夠將任意類型的值轉化成字符串。規則以下:
數值:轉爲相應的字符串。
字符串:轉換後仍是原來的值。
布爾值:true轉爲「true」,false轉爲「false」。
undefined:轉爲「undefined」。
null:轉爲「null」。
而對於較複雜一點的對象類型,轉換規則以下:
先調用toString方法,若是toString方法返回的是原始類型的值,則對該值使用String方法,再也不進行如下步驟。
若是toString方法返回的是複合類型的值,再調用valueOf方法,若是valueOf方法返回的是原始類型的值,則對該值使用String方法,再也不進行如下步驟。
若是valueOf方法返回的是複合類型的值,則報錯。
function html_encode(str) { var s = ""; if (str.length == 0) return ""; s = str.replace(/&/g, ">"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/\'/g, "'"); s = s.replace(/\"/g, """); s = s.replace(/\n/g, "<br>"); return s; } function html_decode(str) { var s = ""; if (str.length == 0) return ""; s = str.replace(/>/g, "&"); s = s.replace(/</g, "<"); s = s.replace(/>/g, ">"); s = s.replace(/ /g, " "); s = s.replace(/'/g, "\'"); s = s.replace(/"/g, "\""); s = s.replace(/<br>/g, "\n"); return s; }
str.split('').reverse().join('');
根據毫秒計算對應的天/時/分/秒
var left_time = <!--{$left_time}-->; function GetRTime(){ //var NowTime = new Date(); <!--獲取當前時間,並使用當前時間減去開始時間--> //var nMS = startTime.getTime() - NowTime.getTime() left_time = left_time - 1000; var nMS = left_time; var nD = Math.floor(nMS/(1000 * 60 * 60 * 24)); var nH = Math.floor(nMS/(1000*60*60)) % 24; var nM = Math.floor(nMS/(1000*60)) % 60; var nS = Math.floor(nMS/1000) % 60; if (nMS < 0){ }else{ } }
moment().format('MMMM Do YYYY, h:mm:ss a'); // August 26th 2015, 9:13:04 pm moment().format('dddd'); // Wednesday moment().format("MMM Do YY"); // Aug 26th 15 moment().format('YYYY [escaped] YYYY'); // 2015 escaped 2015 moment().format(); // 2015-08-26T21:13:04+08:00 var dateString = moment.unix(value).format("MM/DD/YYYY");
若是須要顯示本地化時間,須要引入locale目錄下對應文件,而且使用moment.locale(String);
進行設置。另外注意,Unix時間戳指的是
var a = moment([2007, 0, 29]); var b = moment([2007, 0, 28]); a.diff(b) // 86400000 milliseconds a.diff(b, 'days') // 1
若是須要將duration格式化爲較好的顯示,能夠參考moment-duration-format。
在JavaScript中,Array是一個全局的對象能夠用來建立數組,是一個高級別的有點相似於列表的集合。能夠直接使用Array.length
來獲取某個數組的長度。
JavaScript建立新的數組,能夠採用以下方式:
arr = [] arr = new Array() arr = new Array([PreSize]) arr = [] arr[1] = 1 // arr = [undefined , 1] 對於不存在的自動賦值爲undefined
slice() 方法把數組中一部分的淺複製(shallow copy)存入一個新的數組對象中,並返回這個新的數組。array.slice(begin[, end]).
slice會提取原數組中索引從 begin 到 end 的全部元素(包含begin,但不包含end)。
slice(1,4) 提取原數組中的第二個元素開始直到第四個元素的全部元素 (索引爲 1, 2, 3的元素)。
例子:返回數組中的一部分
// Our good friend the citrus from fruits example var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"]; var citrus = fruits.slice(1, 3); // puts --> ["Orange","Lemon"]
例子:使用 slice
在下例中, slice從
`myCar中建立了一個新數組`newCar
.兩個數組都包含了一個myHonda
對象的引用. 當myHonda
的color屬性改變爲purple, 則兩個數組中的對應元素都會隨之改變.
// 使用slice方法從myCar中建立一個newCar. var myHonda = { color: "red", wheels: 4, engine: { cylinders: 4, size: 2.2 } }; var myCar = [myHonda, 2, "cherry condition", "purchased 1997"]; var newCar = myCar.slice(0, 2); // 輸出myCar, newCar,以及各自的myHonda對象引用的color屬性. print("myCar = " + myCar.toSource()); print("newCar = " + newCar.toSource()); print("myCar[0].color = " + myCar[0].color); print("newCar[0].color = " + newCar[0].color); // 改變myHonda對象的color屬性. myHonda.color = "purple"; print("The new color of my Honda is " + myHonda.color); //輸出myCar, newCar中各自的myHonda對象引用的color屬性. print("myCar[0].color = " + myCar[0].color); print("newCar[0].color = " + newCar[0].color);
上述代碼輸出:
myCar = [{color:"red", wheels:4, engine:{cylinders:4, size:2.2}}, 2, "cherry condition", "purchased 1997"] newCar = [{color:"red", wheels:4, engine:{cylinders:4, size:2.2}}, 2] myCar[0].color = red newCar[0].color = red The new color of my Honda is purple myCar[0].color = purple newCar[0].color = purple
類數組(Array-like)對象
slice
方法能夠用來將一個類數組(Array-like)對象/集合轉換成一個數組。你只需將該方法綁定到這個對象上。下述代碼中 list 函數中的 arguments
就是一個類數組對象。
function list() { return Array.prototype.slice.call(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3]
除了使用 Array.prototype.slice.call(arguments)
,你也能夠簡單的使用 [].slice.call(arguments)
來代替。另外,你可使用 bind
來簡化該過程。
var unboundSlice = Array.prototype.slice; var slice = Function.prototype.call.bind(unboundSlice); function list() { return slice(arguments); } var list1 = list(1, 2, 3); // [1, 2, 3]
JavaScript的Array支持從頭部插入移除,從尾部插入移除等多種方式:
var fruits = ["Apple", "Banana"]; var newLength = fruits.push("Orange");//從尾部插入,返回的數組長度 // ["Apple", "Banana", "Orange"] var last = fruits.pop(); // remove Orange (from the end),返回刪除的對象 // ["Apple", "Banana"]; var first = fruits.shift(); // remove Apple from the front // ["Banana"]; var newLength = fruits.unshift("Strawberry") // add to the front // ["Strawberry", "Banana"];
var array = [1, 2, 3]; array.includes(1); // → true
若是在數組中須要反向索引某個元素的位置,可使用indexOf
方法
fruits.push("Mango"); // ["Strawberry", "Banana", "Mango"] var pos = fruits.indexOf("Banana"); // 1 var array = [2, 9, 9, 4, 3, 6]; var result = array.lastIndexOf(9); console.log(result); // output: 2
若是存儲的是複雜的對象,則可使用find
方法,返回數組中知足測試條件的一個元素,若是沒有知足條件的元素,則返回 undefined。
_.find(users, function(o) { return o.age < 40; }); // output: object for 'barney' // Native var users = [ { 'user': 'barney', 'age': 36, 'active': true }, { 'user': 'fred', 'age': 40, 'active': false }, { 'user': 'pebbles', 'age': 1, 'active': true } ]; users.find(function(o) { return o.age < 40; }); // output: object for 'barney' var index = users.findIndex(function(o) { return o.age >= 40; }); console.log(index); // output: 1
fruits.forEach(function (item, index, array) { console.log(item, index); }); // Apple 0 // Banana 1
集合也是一種常見的數據結構,在ES6中添加了對於集合的支持,其基本用法以下:
// Sets var s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true;
WeakSet提供了一種自回收的存儲方式:
// Weak Sets var ws = new WeakSet(); ws.add({ data: 42 }); // Because the added object has no other references, it will not be held in the set
將某個列表中的元素映射到新的列表中。
// Native var array1 = [1, 2, 3]; var array2 = array1.map(function(value, index) { return value*2; }); console.log(array2); // output: [2, 4, 6]
var array = [0, 1, 2, 3, 4]; var result = array.reduce(function (previousValue, currentValue, currentIndex, array) { return previousValue + currentValue; }); console.log(result); // output: 10 //reduceRight,正好方向相反 var array = [0, 1, 2, 3, 4]; var result = array.reduceRight(function (previousValue, currentValue, currentIndex, array) { return previousValue - currentValue; }); console.log(result); // output: -2
// Native function isBigEnough(value) { return value >= 10; } var array = [12, 5, 8, 130, 44]; var filtered = array.filter(isBigEnough); console.log(filtered); // output: [12, 130, 44]
JavaScript中Object是一個混合了相似於Dictionary與Class的用法,基本上來講也是一種鍵值類型。其中鍵的類型主要包含三種:
Identifier:包含任何有效地標識符,包括了ES的保留關鍵字。
字符串:single ('
) or double ("
) quotes. 'foo'
, "bar"
,'qu\'ux'
, ""
(the empty string), and 'Ich \u2665 B\xFCcher'
are all valid string literals.
數字:decimal literal (e.g. 0
, 123
, 123.
, .123
, 1.23
, 1e23
, 1E-23
, 1e+23
, 12
, but not 01
, +123
or -123
) or a hex integer literal (0[xX][0-9a-fA-F]+
in regex, e.g. 0xFFFF
, 0X123
,0xaBcD
).
var object = { // `abc` is a valid identifier; no quotes are needed abc: 1, // `123` is a numeric literal; no quotes are needed 123: 2, // `012` is an octal literal with value `10` and thus isn’t allowed in strict mode; but if you insist on using it, quotes aren’t needed 012: 3, // `π` is a valid identifier; no quotes are needed π: Math.PI, // `var` is a valid identifier name (although it’s a reserved word); no quotes are needed var: 4, // `foo bar` is not a valid identifier name; quotes are required 'foo bar': 5, // `foo-bar` is not a valid identifier name; quotes are required 'foo-bar': 6, // the empty string is not a valid identifier name; quotes are required '': 7 };
注意,與object不一樣的是,JSON 只容許用雙引號 ("
) 包裹的字符串做爲鍵名。而若是要根據鍵名進行索引的話,可使用方括號,這種方式對於三種鍵值皆有效:
object['abc']; // 1
有時候也可使用點操做符,不過這種方式只能夠被用於鍵爲有效地Identifier狀況:
object.abc; // 1
若是須要獲取全部的鍵名的話,可使用Object.keys方法:
注意,全部的Object的方法只能用Object.methodName方式調用
// Native var result2 = Object.keys({one: 1, two: 2, three: 3}); console.log(result2); // output: ["one", "two", "three"] //也能夠等效獲取大小 var result2 = Object.keys({one: 1, two: 2, three: 3}).length; console.log(result2); // output: 3
其基本語法爲:
Object.create(proto, { propertiesObject })
這裏須要注意的是,propertiesObject不是一個簡單的鍵值類型,而是有固定格式的object。
var o; // 建立一個原型爲null的空對象 o = Object.create(null); o = {}; // 以字面量方式建立的空對象就至關於: o = Object.create(Object.prototype); o = Object.create(Object.prototype, { // foo會成爲所建立對象的數據屬性 foo: { writable:true, configurable:true, value: "hello" }, // bar會成爲所建立對象的訪問器屬性 bar: { configurable: false, get: function() { return 10 }, set: function(value) { console.log("Setting `o.bar` to", value) } }}) function Constructor(){} o = new Constructor(); // 上面的一句就至關於: o = Object.create(Constructor.prototype); // 固然,若是在Constructor函數中有一些初始化代碼,Object.create不能執行那些代碼 // 建立一個以另外一個空對象爲原型,且擁有一個屬性p的對象 o = Object.create({}, { p: { value: 42 } }) // 省略了的屬性特性默認爲false,因此屬性p是不可寫,不可枚舉,不可配置的: o.p = 24 o.p //42 o.q = 12 for (var prop in o) { console.log(prop) } //"q" delete o.p //false //建立一個可寫的,可枚舉的,可配置的屬性p o2 = Object.create({}, { p: { value: 42, writable: true, enumerable: true, configurable: true } });
Object.assign
:一層淺複製Object.assign() 方法能夠把任意多個的源對象所擁有的自身可枚舉屬性拷貝給目標對象,而後返回目標對象。Object.assign
方法只會拷貝源對象自身的而且可枚舉的屬性到目標對象身上。注意,對於訪問器屬性,該方法會執行那個訪問器屬性的 getter
函數,而後把獲得的值拷貝給目標對象,若是你想拷貝訪問器屬性自己,請使用 Object.getOwnPropertyDescriptor()
和Object.defineProperties()
方法。
注意,在屬性拷貝過程當中可能會產生異常,好比目標對象的某個只讀屬性和源對象的某個屬性同名,這時該方法會拋出一個 TypeError
異常,拷貝過程當中斷,已經拷貝成功的屬性不會受到影響,還未拷貝的屬性將不會再被拷貝。
注意, Object.assign
會跳過那些值爲 null
或 undefined
的源對象。
Object.assign(target, ...sources)
例子:淺拷貝一個對象
var obj = { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); // { a: 1 }
例子:合併若干個對象
var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目標對象自身也會改變。
例子:拷貝 symbol 類型的屬性
var o1 = { a: 1 }; var o2 = { [Symbol("foo")]: 2 }; var obj = Object.assign({}, o1, o2); console.log(obj); // { a: 1, [Symbol("foo")]: 2 }
例子:繼承屬性和不可枚舉屬性是不能拷貝的
var obj = Object.create({foo: 1}, { // foo 是個繼承屬性。 bar: { value: 2 // bar 是個不可枚舉屬性。 }, baz: { value: 3, enumerable: true // baz 是個自身可枚舉屬性。 } }); var copy = Object.assign({}, obj); console.log(copy); // { baz: 3 }
例子:原始值會被隱式轉換成其包裝對象
var v1 = "123"; var v2 = true; var v3 = 10; var v4 = Symbol("foo") var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 源對象若是是原始值,會被自動轉換成它們的包裝對象, // 而 null 和 undefined 這兩種原始值會被徹底忽略。 // 注意,只有字符串的包裝對象纔有可能有自身可枚舉屬性。 console.log(obj); // { "0": "1", "1": "2", "2": "3" }
例子:拷貝屬性過程當中發生異常
var target = Object.defineProperty({}, "foo", { value: 1, writeable: false }); // target 的 foo 屬性是個只讀屬性。 Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4}); // TypeError: "foo" is read-only // 注意這個異常是在拷貝第二個源對象的第二個屬性時發生的。 console.log(target.bar); // 2,說明第一個源對象拷貝成功了。 console.log(target.foo2); // 3,說明第二個源對象的第一個屬性也拷貝成功了。 console.log(target.foo); // 1,只讀屬性不能被覆蓋,因此第二個源對象的第二個屬性拷貝失敗了。 console.log(target.foo3); // undefined,異常以後 assign 方法就退出了,第三個屬性是不會被拷貝到的。 console.log(target.baz); // undefined,第三個源對象更是不會被拷貝到的。
不過須要注意的是,assign是淺拷貝,或者說,它是一級深拷貝,舉兩個例子說明:
const defaultOpt = { title: { text: 'hello world', subtext: 'It\'s my world.' } }; const opt = Object.assign({}, defaultOpt, { title: { subtext: 'Yes, your world.' } }); console.log(opt); // 預期結果 { title: { text: 'hello world', subtext: 'Yes, your world.' } } // 實際結果 { title: { subtext: 'Yes, your world.' } }
上面這個例子中,對於對象的一級子元素而言,只會替換引用,而不會動態的添加內容。那麼,其實assign並無解決對象的引用混亂問題,參考下下面這個例子:
const defaultOpt = { title: { text: 'hello world', subtext: 'It\'s my world.' } }; const opt1 = Object.assign({}, defaultOpt); const opt2 = Object.assign({}, defaultOpt); opt2.title.subtext = 'Yes, your world.'; console.log('opt1:'); console.log(opt1); console.log('opt2:'); console.log(opt2); // 結果 opt1: { title: { text: 'hello world', subtext: 'Yes, your world.' } } opt2: { title: { text: 'hello world', subtext: 'Yes, your world.' } }
var map = new Map(); map.set("foo", "bar"); console.log(map.get("foo")); //logs "bar" var animalSounds = new Map(); animalSounds.set("dog", "woof"); animalSounds.set("cat", "meow"); animalSounds.set("frog", "ribbit"); console.log(animalSounds.size); //logs 3 console.log(animalSounds.has("dog")); //logs true animalSounds.delete("dog"); console.log(animalSounds.size); //logs 2 console.log(animalSounds.has("dog")); //logs false animalSounds.clear(); console.log(animalSounds.size); //logs 0
usersMap = new Map(); usersMap.set(1, "sally"); usersMap.set(2, "bob"); usersMap.set(3, "jane"); console.log(usersMap.get(1)); //logs "sally" usersMap.forEach(function (username, userId) { console.log(userId, typeof userId); //logs 1..3, "number" if (userId === 1) { console.log("We found sally."); } }); //若是用for...of方式遍歷,每次返回的是一個Array for (data of usersMap) { console.log(data);//Array [1,"sally"] }
Map的鍵的類型能夠是object、NaN等等。
var obj, map; map = new Map(); obj = {foo: "bar"}; map.set(obj, "foobar"); obj.newProp = "stuff"; console.log(map.has(obj)); //logs true console.log(map.get(obj)); //logs "foobar"
FaceBook-Immutable
Immutable 詳解及 React 中實踐
Immutable 對象一旦被建立以後即不可再更改,這樣可使得應用開發工做變得簡化,再也不須要大量的保護性拷貝,使用簡單的邏輯控制便可以保證內存控制與變化檢測。Immutable.js雖然和React同期出現且跟React配合很爽,但它可不是React工具集裏的(它的光芒被掩蓋了),它是一個徹底獨立的庫,不管基於什麼框架均可以用它。意義在於它彌補了Javascript沒有不可變數據結構的問題。不可變數據結構是函數式編程中必備的。前端工程師被OOP洗腦過久了,組件根本上就是函數用法,FP的特色更適用於前端開發。
Javascript中對象都是參考類型,也就是a={a:1}; b=a; b.a=10;
你發現a.a
也變成10了。可變的好處是節省內存或是利用可變性作一些事情,可是,在複雜的開發中它的反作用遠比好處大的多。因而纔有了淺copy和深copy,就是爲了解決這個問題。舉個常見例子:
var defaultConfig = { /* 默認值 */}; var config = $.extend({}, defaultConfig, initConfig); // jQuery用法。initConfig是自定義值 var config = $.extend(true, {}, defaultConfig, initConfig); // 若是對象是多層的,就用到deep-copy了
而
var stateV1 = Immutable.fromJS({ users: [ { name: 'Foo' }, { name: 'Bar' } ] }); var stateV2 = stateV1.updateIn(['users', 1], function () { return Immutable.fromJS({ name: 'Barbar' }); }); stateV1 === stateV2; // false stateV1.getIn(['users', 0]) === stateV2.getIn(['users', 0]); // true stateV1.getIn(['users', 1]) === stateV2.getIn(['users', 1]); // false
如上,咱們可使用===來經過引用來比較對象,這意味着咱們可以方便快速的進行對象比較,而且它可以和React中的PureRenderMixin 兼容。基於此,咱們能夠在整個應用構建中使用Immutable.js。也就是說,咱們的Flux Store應該是一個具備不變性的對象,而且咱們經過 將具備不變性的數據做爲屬性傳遞給咱們的應用程序。
若是要建立Immutable對象,使用fromJS
方法既能夠將簡單的JS中的objects與arrays轉化爲不可變的Maps與Lists
fromJS(json: any, reviver?: (k: any, v: Iterable<any, any>) => any): any
若是reviver
這個屬性被提供了,那麼它會傳入一個Seq對象而且被循環調用,對於頂層對象,它的默認的鍵爲""
。
Immutable.fromJS({a: {b: [10, 20, 30]}, c: 40}, function (key, value) { var isIndexed = Immutable.Iterable.isIndexed(value); return isIndexed ? value.toList() : value.toOrderedMap(); }); // true, "b", {b: [10, 20, 30]} // false, "a", {a: {b: [10, 20, 30]}, c: 40} // false, "", {"": {a: {b: [10, 20, 30]}, c: 40}}
對於轉化而來的Immutable對象,能夠經過Iterable.is*
方法來判斷其是列表仍是映射或者其餘數據類型。
要更新Map中的某個元素值,須要調用updateIn方法,該方法會根據傳入的keyPath尋找到該值,而後進行安全修正並返回一個新的對象。若是keyPath值並不存在,那麼返回的Map對象會自動建立該鍵,若是KeyPath沒有提供指定值,那麼會自動調用notSetValue
或者賦值爲undefined
updateIn(keyPath: Array<any>, updater: (value: any) => any): Map<K, V> updateIn( keyPath: Array<any>, notSetValue: any, updater: (value: any) => any ): Map<K, V> updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): Map<K, V> updateIn( keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any ): Map<K, V>
var data = Immutable.fromJS({ a: { b: { c: 10 } } }); data = data.updateIn(['a', 'b', 'c'], val => val * 2); // { a: { b: { c: 20 } } }