JS:关于JS字面量及其容易忽略的12个小问题

简要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
  • 函数名,无关紧要
  • 包含在括号内的参数,参数也是无关紧要的,括号却不能少
  • 包裹在大括号内的语句块,即函数要执行的具体代码

 

(三)这是使用构造函数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个小问题

相关文章
相关标签/搜索