简要

问题1:不能使用typeof判断一个null对象的数据类型

问题2:用双等号判断两个一样的变量,可能返回false

问题3:对于非十进制,如果超出了数值范围,则会报错

问题4:JS浮点数并不精确,0.1+0.2 != 0.3

问题5:使用反斜杠定义的字符串并不换行,使用反引号才可以

问题6:字符串字面量对象都是临时对象,无法保持记忆

问题7:将字符转义防止页面注入攻击

问题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个小问题

JS:关于JS字面量及其容易忽略的12个小问题的更多相关文章

  1. js中对象字面量

    一.对象字面量语法 var person={ name:'小王', age:18, _pri:233 } 成员名称的单引号不是必须的 最后一个成员结尾不要用逗号,不然在某些浏览器中会抛出错误 成员名相 ...

  2. JS基础_字面量和变量

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  3. JS基础二--字面量和变量

       /*       字面量,都是一些不可改变的值,       比如:1 2 3 4 5       字面量都是可以直接使用,但是我们一般不会直接使用字面量.       变量,变量可以用来保存字 ...

  4. js拾遗: 函数字面量

    今天落叶同学发我一篇文章,我看到一个"新"名词 "函数字面量" (也可叫直接量),当时我就郁闷了,这是什么东西? 我怎么没听说过..回头翻了下权威指南,在第 4 ...

  5. js基础知识:字面量 关键字和保留字

    js中的字面量概念我的理解就是:字体表面的常量 如:数字 100, 字符串  "ssss"或'ssss'  布尔值:false ,正则 以及null对象. 这些都是常量. 关键字: ...

  6. 04_Swift2基础之类型安全和类型推测+字面量+类型别名

    1. 类型安全和类型推测 1> 类型安全 Swift 是一个 _类型安全(type safe)_ 的语言.类型安全的语言可以让你清楚地知道代码要处理的值的类型.如果你的代码需要一个`String ...

  7. Swift编程语言学习1.4——数值型字面量、数值类型转换

    数值型字面量 整数字面量能够被写作: 一个十进制数,没有前缀 一个二进制数,前缀是0b 一个八进制数,前缀是0o 一个十六进制数,前缀是0x 以下的全部整数字面量的十进制值都是17: let deci ...

  8. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║Vue基础:JS面向对象&字面量& this字

    缘起 书接上文<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史>,昨天咱们说到了以我的经历说明的web开发经历的 ...

  9. js字面量

    以前一直对js字面量模棱两可. '字面量是一种表示值的记法.' js字面量(literal) 分为以下几个 number literal        8   就是数字字面量 string liter ...

随机推荐

  1. 解决dubbo的服务发布注解@service不能和事务注解不能共用的方案

    最近在项目的开发中遇到了一个问题,就是服务提供方使用@service发布dubbo服务时候,服务消费方@Reference无法注入bean导致空指针异常的问题.分析原因为@service注解并没有将服 ...

  2. lucene中Field简介

    Lucene 6.1.0中存在的field种类如下(后缀是Field): 下面介绍几个常用的Field类型: TextField A field that is indexed and tokeniz ...

  3. selenium grid 使用方法

    代码和selenium driver相同 只是 启动环境方式不同.至少启动一个hub 一个 node .如需要多个,可以使用端口进行区分. java -jar selenium-server-stan ...

  4. SPICE简史

    如今每一天都有不知其数的半导体芯片设计公司与设计验证工程师,在用着电路仿真软件SPICE.SPICE广泛应用在仿真模拟电路(例如运放Op Amp,能隙基准稳压电源Bandgap Reference,数 ...

  5. May 05th 2017 Week 18th Friday

    No matter what have happened today, the sun will just rise in the morning of tomorrow. 无论今天发生了什么,明天早 ...

  6. C语言总结的知识点

    什么是有效数字? ------------------------- 数据类型 运算符: 函数: 程序结构: 存储结构 内存管理 关键字: ------------------------- C语言: ...

  7. Sliding Window - The Smallest Window II(AIZU) && Leetcode 76

    http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DSL_3_B For a given array a1,a2,a3,...,aNa1 ...

  8. 2019.03.13 ZJOI2019模拟赛 解题报告

    得分: \(55+12+10=77\)(\(T1\)误认为有可二分性,\(T2\)不小心把\(n\)开了\(char\),\(T3\)直接\(puts("0")\)水\(10\)分 ...

  9. python selenium 下拉框

    下拉框的处理如下代码: 定位select有很多种方式,这里介绍两种定位方式 1.二次定位 先定位到下拉框:self.dr.find_element_by_css_selector('#business ...

  10. svn: 处于冲突状态

    svn: 提交失败(细节如下):svn: 提交终止: “/home/usa/svn/aispeech/air201102/branches/opt-vite/wvite” 处于冲突状态 删除文件夹wv ...