簡要javascript
問題1:不能使用typeof判斷一個null對象的數據類型html
問題2:用雙等號判斷兩個同樣的變量,可能返回false前端
問題3:對於非十進制,若是超出了數值範圍,則會報錯java
問題4:JS浮點數並不精確,0.1+0.2 != 0.3程序員
問題5:使用反斜槓定義的字符串並不換行,使用反引號才能夠es6
問題6:字符串字面量對象都是臨時對象,沒法保持記憶正則表達式
問題7:將字符轉義防止頁面注入攻擊shell
問題8:使用模板標籤過濾敏感字詞編程
問題9:格式相同,但不是同一個正則對象數組
問題10:非法標識符也能夠用用對象屬性,但只能被數組訪問符訪問
問題11:數組字面量尾部逗號會忽略,但中間的不會
問題12:函數表達式也能夠有函數名稱
JS這種語言一不當心就會寫錯。爲何前端技術專家工資那麼高,可能要解決的疑難雜症最多吧。
什麼是字面量?
在JS中,以特定符號或格式規定的,建立指定類型變量的,不能被修改的便捷表達式。由於是表達式,字面量都有返回值。字面量是方便程序員以簡單的句式,建立對象或變量的語法糖,但有時候以字面量建立的「對象」和以標準方法建立的對象的行爲並不徹底相同。
null 字面量
舉個票子,最簡單的空值字面量。例如:
var obj = null
問題1:不能使用typeof判斷一個null對象的數據類型
null 就是一個字面量,它建立並返回Null類型的惟一值null,表明對象爲空。null是Null類型,但若是以關鍵字typeof關鍵字檢測之,以下所示:
typeof null // object
返回倒是object類型。這是一個歷史遺留Bug,在寫JS代碼時,不能夠用這樣的方式判斷null的對象類型:
if (typeof 變量 == "object") {
console.log("此時變量必定是object類型?錯!")
}
問題2:用雙等號判斷兩個同樣的變量,可能返回false
在JS中共有種七種基本數據類型:Undefined、Null、布爾值、字符串、數值、對象、Symbol。其中Null、Undefined是兩個特殊的類型。這兩個基本類型,均是隻有一個值。
null作爲Null類型的惟一值,是一個字面量;undefined做爲Undefined類型的惟一值,卻不是字面量。undefined與NaN、Infinity(無窮大)都是JS全局定義的只讀變量,它們均可以被二次賦值:
undefined = 123
NaN = 123
Infinity = 123
null = 123 // 報錯:Uncaught Reference Error
NaN 即 Not a Number ,不是一個數字。NaN是惟一一個不等於自身的JS常量:
console.log(NaN == NaN) //false
var a = NaN, b = NaN
console.log(a == b) //false
在上面代碼中,用雙等號判斷兩個變量a、b是否相等,結果返回false。仍然理論上它們是同樣的。
isNaN() 用於檢查一個值是否能被 Number() 成功轉換,能轉換返回true,不能返回false 。但並不能檢測是否是純數字,例如:
isNaN('123ab') // true 不能轉換
isNaN('123.45abc')// true 不能轉換
整數字面量
JS整數共有四種字面量格式:十進制、二進制、八進制、十六進制。
問題3:對於非十進制,若是超出了數值範圍,則會報錯
八進制
八進制字面值的第一位必須是0,而後是八進制數字序列(0-7)。若是字面值中的數值超出了範圍,那麼前導0將被忽略,後面的數值被看成十進制數解析。例如:
var n8 = 012
console.log(n8) //10
var n8 = 09
console.log(n8) //9,超出範圍了
在es5以前,使用Number()轉化八進制,會按照十進制數字處理,如今能夠了。以下所示:
Number(010) //輸出8
十六進制
十六進制字面值的前兩位必須是0x,後跟十六進制數字序列(0-9,a-f),字母可大寫可小寫。若是十六進制中字面值中的數值超出範圍則會報錯。
var n16 = 0x11
console.log(n16) //17
var n17 = 0xw
console.log(n17) //報錯
二進制
二進制字面值的前兩位必須是0b,若是出現除0、1之外的數字會報錯。
var n2 = 0b101
console.log(n2) //5
var n3 = 0b3
console.log(n3) //報錯
浮點字面量
在JS中,全部數值都是使用64位浮點類型存儲。
問題4:JS浮點數並不精確,0.1+0.2 != 0.3
因爲JS採用了IEEE754格式,浮點數並不精確。例如:
console.log(0.1 + 0.2 === 0.3) // false
console.log(0.3 / 0.1) // 不是3,而是2.9999999999999996
console.log((0.3 - 0.2) === (0.2 - 0.1)) // false
由於浮點數不精確,因此軟件中關於錢的金額都是用分表示,而不是用元。那爲何會不精準?
人類寫的十進制小數,在計算機世界會轉化爲二進制小數。例如10.111這個二進制小數,換算爲十進制小數是2.875,以下:
1*2^1 + 0 + 1*2^-1 + 1*2^-2 + 1*2^-3 = 2+1/2+1/4 +1/8= 2.875
對於上面提到的0.3這個十進制小數,換算成二進制應該是什麼?
0.01 = 1/4 = 0.25 //小
0.011 =1/4 + 1/8 = 0.375 //又大了
0.0101 = 1/4 + 1/16 = 0.334 //還大
0.01001 = 1/4 + 1/32 = 0.28125 //又小了
0.010011 = 1/4+ 1/32 + 1/64 = 0.296875 //接近了
小數點後面每一位bit表明的數額不一樣,攢在一塊兒組成的總數額也不是均勻分佈的。只能無限的接近,並不能確準的表達。準確度是浮動的,因此稱爲浮點數。但這種浮動也不是無限的。
根據國際標準IEEE754,JS的64浮點數的二進制位是這樣組成的:
1: 符號位,0正數,1負數
11: 指數位,用來肯定範圍
52: 尾數位,用來肯定精度
後面的有效數字部分,最多有52個bit。這52個bit用完了,若是仍未準確,也只能這樣了。在作小數比較時,比較的是最後面52位bit,它們相等纔是相等。因此,0.1 + 0.2不等於0.3也不稀奇了,在數學上它們相等,在計算機它們不等。
但這種不精確並非JS的錯,全部編程語言的浮點數都面臨一樣問題。
字符串字面量
字符串字面量是由雙引號(")對或單引號(')括起來的零個或多個字符。格式符必須是成對單引號或成對雙引號。例如:
"foo"
'bar'
問題5:使用反斜槓定義的字符串並不換行,使用反引號才能夠
使用反斜槓能夠書寫多行字符串字面量:
var str = "this string \
is broken \
across multiple\
lines."
可是這種多行字符串在輸出並非多行的:
console.log(str) //輸出"this string is broken across multiplelines."
若是想實現Here文檔(注1)的字符串效果,可使用轉義換行符:
var poem =
"Roses are red,\n\
Violets are blue.\n\
Sugar is sweet,\n\
and so is foo."
在es6裏面,定義了模板字符串字面量,使用它創造多行字符串更簡單:
var poem = `Roses are red,
Violets are blue.
Sugar is sweet,
and so is foo.`
問題6:字符串字面量對象都是臨時對象,沒法保持記憶
在字符串字面值返回的變量上,可使用字符串對象的全部方法。例如調用length屬性:
console.log("Hello".length)
可是字面量字符串返回的對象,並不徹底等於字符串對象。前者與String()建立的對象有本質不一樣,它沒法建立並保持屬性:
var a = "123"
a.abc = 100
console.log(a.abc) //輸出undefined
a = new String("123")
a.abc = 100
console.log(a.abc) //輸出100
能夠認爲,使用字符串字面量建立的對象均是臨時對象,當調用字符串字符量變量的方法或屬性時,均是將其內容傳給String()從新建立了一個新對象,因此調用方法能夠,調用相似於方法的屬性(例如length)也能夠,可是使用動態屬性不能夠,由於在內存堆裏已經不是同一個對象了。
想象這個場景多是這樣的:
程序員經過字面量建立了一個字符串對象,並把一個包裹交給了他,說:「拿好了,一會交給我」。字符串對象進CPU車間跑了一圈出來了,程序員一看包裹丟了,問:「剛纔給你的包裹哪裏了?」。字符串對象納悶:「你何時給我包裹了?我是第一次見到你」
特殊符號
使用字符串避不開特殊符號,最經常使用的特殊符號有換行(\n),製表符(\t)等。
在這裏反斜槓(\)是轉義符號,表明後面的字符具備特殊含義。雙此號(")、單引號(')還有反引號(`),它們是定義字符串的特殊符號,若是想到字符串使用它們的本意,必須使用反斜槓轉義符。例如:
console.log("雙引號\" ,反斜槓\\,單引號\'")
//雙引號" ,反斜槓\,單引號'
這裏是一份常規的轉義符說明:
一個特殊符號有多種表示方式,例如版本符號,這三種方式均可以:
console.log("\251 \xA9 \u00A9") //輸出"© © ©"
這是一份經常使用轉義符號使用16進製表示的Unicode字符表:
像上面的示例:
console.log("雙引號\" ,反斜槓\\,單引號\'")
也能夠這樣寫:
console.log("雙引號\u0022 ,反斜槓\u005C,單引號\u0027")
//輸出"雙引號" ,反斜槓\,單引號'"
論裝逼指數,這種誰也看不明白的Unicode碼,比直觀的轉義序列碼難度係數更高。
問題7:將字符轉義防止頁面注入攻擊
含有Html標籤符號的字符串,在數據存儲或頁面展現時,有時候須要將它們轉義;有時候又須要將它們反轉義,以便適合人類閱讀:
function unescapeHtml(str) {
var arrEntities={'lt':'<','gt':'>','nbsp':' ','amp':'&','quot':'"'};
return str.replace(/&(lt|gt|nbsp|amp|quot);/ig,function(all,t){return arrEntities[t];});
}
function htmlEscape(text){
return text.replace(/[<>"&]/g, function(match, pos, originalText){
switch(match){
case "<": return "<";
case ">":return ">";
case "&":return "&";
case "\"":return """;
}
});
}
htmlEscape("<hello world>") // "<hello world>"
unescapeHtml("<hello world>") // "<hello world>"
模板字符串字面量
在es6中,提供了一種模板字符串,使用反引號(`)定義,這也是一種字符串字面量。這與Swift、Python等其餘語言中的字符串插值特性很是類似。例如:
let message = `Hello world` //使用模板字符串字面量建立了一個字符串
使用模板字符串,原來須要轉義的特殊字符例如單引號、雙引號,都不須要轉義了:
console.log(`雙引號" ,單引號'`)//雙引號" ,單引號'
使用模板字面量聲明多行字符串,前面已經講過了。須要補充的是,反引號中的全部空格和縮進都是有效字符 。
模板字符串最方便的地方,是可使用變量置換,避免使用加號(+)拼接字符串。例如:
var name = "李寧"
var msg = `歡迎你${name}同窗`
console.log(msg)//歡迎你李寧同窗
問題8:使用模板標籤過濾敏感字詞
模板字面量真正的強大之處,不是變量置換,而是模板標籤。模板標籤像模板引擎的過濾函數同樣,能夠將原串與插值在函數中一同處理,將將處理結果返回。這能夠在運行時防止注入攻擊和替換一些非法違規字符。
這是一個模板標籤的使用示例:
let name = '李寧', age = 20
let message = show`我來給你們介紹${name},年齡是${age}.`;
function show(arr, ...args) {
console.log(arr) // ["我來給你們介紹", ",年齡是", ".", raw: Array(3)]
console.log(args[0]) // 張三
console.log(args[1]) // 20
return "隱私數據拒絕展現"
}
console.log(message) //隱私數據拒絕展現
變量message的右值部分是一個字符串模板字面量,show是字面量中的模板標籤,同時也是下方聲明的函數名稱。模板標籤函數的參數,第一個是一個被插值分割的字符串數組,後面依次是插值變量。在模板標籤函數中,能夠有針對性對插值作一些技術處理,特別當這些值來源於用戶輸入時。
正則表達式字面量
JS正則表達式除了使用new RegExp()聲明,使用字面量聲明更簡潔。定義正則表達式字面量的符號是正斜槓(/)。例如:
var re = /[a-z]/gi
console.log("abc123XYZ".replace(re, "")) // 123
re便是一個正則表達式,它將普通字符串轉換爲數值字符串。正斜槓後面的g與i是模式修飾符。經常使用的模式修飾符有:
g 全局匹配
m 多行匹配
i 忽略大小寫匹配
模式修飾符能夠以任何順序或組合出現,無前後之分。上面的正則表達式,使用標準形式建立是這樣的:
var re = new RegExp("[a-z]","gi")
console.log("abc123XYZ".replace(re, "")) // 123
顯然,使用字面量聲明正則更簡單。
正則表達式字面量不能爲空,若是爲空將開始一個單行註釋。若是要指定一個空正則,使用/(?:)/。
問題9:格式相同,但不是同一個正則對象
在es5以前,使用字面量建立的正則,若是正則規則相同,則它們是同一個對象:
function getReg() {
var re = /[a-z]/
re.foo = "bar"
return re
}
var reg1 = getReg()
var reg2 = getReg()
console.log(reg1 === reg2) // true
reg2.foo = "baz"
console.log(reg1.foo) // "baz"
從上面代碼中,能夠看出reg1與reg2是值與類型全等。改變reg2的屬性foo,reg1的foo屬性同步改變。它們是內存堆中是一個對象。這種Bug在es5中已經獲得修正。
對象字面量
重點來了,這是被有些人稱爲神乎其技的對象字面量。
JS的字面量對象,是一種簡化的建立對象的方式,和用構造函數建立對象同樣存在於堆內存當中。對象字面值是封閉在花括號對({})中的一個對象的零個或多個"屬性名-值"對的元素列表。不能在一條語句的開頭就使用對象字面值,這將致使錯誤或產生超出預料的行爲, 由於此時左花括號({)會被認爲是一個語句塊的起始符號。
這是是一個對象字面值的例子:
var car = {
name: "sala",
getCar: function(){},
special: "toyota"
}
對象字面值能夠嵌套,能夠在一個字面值內嵌套上另外一個字面值,可使用數字或字符串字面值做爲屬性的名字。例如:
var car = { other: {a: "san", "b": "jep"} }
問題10:非法標識符也能夠用用對象屬性,但只能被數組訪問符訪問
數字自己是不能做爲標識符的,但在對象字面中卻能夠做爲屬性名。在訪問這樣的「非法」屬性時,不能使用傳統的點訪問符,須要使用數組訪問符:
var foo = {a: "alpha", 2: "two"}
console.log(foo.a) // alpha
console.log(foo[2]) // two
console.log(foo.2) // 錯誤
除了數字以外,其它非法標識符例如空格、感嘆號甚至空字符串,均可以用於屬性名稱中。固然訪問這些屬性仍然離不了數組訪問符:
var s = {
"": "empty name",
"!": "bingo"
}
console.log(s."") // 語法錯誤
console.log(s[""]) // empty name
console.log(unusualPropertyNames["!"])
加強性字面量支持
在es6中,對象字面量的屬性名能夠簡寫、方法名能夠簡寫、屬性名能夠計算。例如:
var name = "nana", age = 20, weight = 78
var obj = {
name, // 等同於 name: nana
age, // 等同於 age: 20
weight, // 等同於 weight: 78
sayName() { // 方法名簡寫,能夠省略 function 關鍵字
console.log(this.name);
},
// 屬性名是可計算的,等同於over78
['over' + weight]: true
}
console.log(ogj) // {name: "nana", age: 20, weight: 78, over78: true, descripte: ƒ}
注意每一個對象元素之間,須要以逗號分隔,每一個元素沒有字面上的鍵名,但其實也是一個鍵值對。甚至在建立字面量對象時,可使用隱藏屬性__proto__設置原型,而且支持使用super調用父類方法:
var superObj = {
name: "nana",
toString(){
return this.name
}
}
var obj = {
__proto__: superObj,
toString() {
return "obj->super:" + super.toString();
}
}
console.log(obj.toString()) // obj->super:nana
屬性賦值器(setter)和取值器(getter),也是採用了屬性名簡寫:
var cart = {
wheels: 4,
get wheels () {
return this.wheels
},
set wheels (value) {
if (value < this.wheels) {
throw new Error(' 數值過小了! ')
}
this.wheels = value;
}
}
由於有增長性的屬性名、方法名簡寫,當在CommonJS 模塊定義中輸出對象時,可使用簡潔寫法:
module.exports = { getItem, setItem, clear }
// 等同於
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
}
數組字面量
數組字面量語法很是簡單,就是逗號分隔的元素集合,而且整個集合被方括號包圍。例如:
var coffees = ["French", 123, true,]
console.log(a.length) // 1
等號右值便是一個數組字面量。使用Array()構造方法建立數組,第一個參數是數組長度,而不是數組元素:
var a = new Array(3)
console.log(a.length) // 3
console.log(typeof a[0]) // "undefined"
問題11:數組字面量尾部逗號會忽略,但中間的不會
尾部逗號在早期版本的瀏覽器中會報錯,如今若是在元素列表尾部添加一個逗號,它將被忽略。可是若是在中間添加逗號:
var myList = ['home', , 'school', ,]
卻不會被忽略。上面這個數組有4個元素,list[1]與list[3]均是undefined。
函數字面量
函數是JS編程世界的一等公民。JS定義函數有兩種方法,函數聲明與函數表達式,後者又稱函數字面量。日常所說的匿名函數均指採用函數字面量形式的匿名函數。
(一)這是使用關鍵字(function)聲明函數:
function fn(x){ alert(x) }
(二)這是函數字面量:
var fn = function(x){ alert(x) }
普通函數字面量由4部分組成:
(三)這是使用構造函數Function()建立函數:
var fn= new Function( 'x', 'alert(x)' )
最後一種方式不但使用不方便,性能也堪憂,因此不多有人說起。
問題12:函數表達式也能夠有函數名稱
函數字面量仍然能夠有函數名,這方面遞歸調用:
var f = function fact(x) {
if (x < = 1) {
return 1
} else {
return x*fact(x-1)
}
}
箭頭函數
在es6中出現了一種新的方便書寫的匿名函數,箭頭函數。例如:
x => x * x
沒有function關鍵字,沒有花括號。它延續lisp語言lambda表達式的演算風格,不求最簡只求更簡。箭頭函數沒有名稱,可使用表達式賦值給變量:
var fn = x => x * x
做者認爲它仍然是一種函數字面量,雖然不多有人這樣稱呼它。
布爾字面量
布爾字面量只有true、false兩個值。例如:
var result = false // false字面量
注1:here文檔,又稱做heredoc,是一種在命令行shell和程序語言裏定義字符串的方法。
參考資料
【1】《javascript權威指南(第6版)》
【2】《javascript高級程序設計(第3版)》
【3】《javascript語言精粹(修訂版)》
【4】《javascript DOM編程藝術(第2版)》
【5】《javascript啓示錄》
首先於微信公衆號「藝述思惟」:關於JS字面量及其容易忽略的12個小問題