JS:关于JS字面量及其容易忽略的12个小问题
简要
问题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) //输出undefineda = 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: 78sayName() { // 方法名简写,可以省略 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个小问题的更多相关文章
- js中对象字面量
一.对象字面量语法 var person={ name:'小王', age:18, _pri:233 } 成员名称的单引号不是必须的 最后一个成员结尾不要用逗号,不然在某些浏览器中会抛出错误 成员名相 ...
- JS基础_字面量和变量
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- JS基础二--字面量和变量
/* 字面量,都是一些不可改变的值, 比如:1 2 3 4 5 字面量都是可以直接使用,但是我们一般不会直接使用字面量. 变量,变量可以用来保存字 ...
- js拾遗: 函数字面量
今天落叶同学发我一篇文章,我看到一个"新"名词 "函数字面量" (也可叫直接量),当时我就郁闷了,这是什么东西? 我怎么没听说过..回头翻了下权威指南,在第 4 ...
- js基础知识:字面量 关键字和保留字
js中的字面量概念我的理解就是:字体表面的常量 如:数字 100, 字符串 "ssss"或'ssss' 布尔值:false ,正则 以及null对象. 这些都是常量. 关键字: ...
- 04_Swift2基础之类型安全和类型推测+字面量+类型别名
1. 类型安全和类型推测 1> 类型安全 Swift 是一个 _类型安全(type safe)_ 的语言.类型安全的语言可以让你清楚地知道代码要处理的值的类型.如果你的代码需要一个`String ...
- Swift编程语言学习1.4——数值型字面量、数值类型转换
数值型字面量 整数字面量能够被写作: 一个十进制数,没有前缀 一个二进制数,前缀是0b 一个八进制数,前缀是0o 一个十六进制数,前缀是0x 以下的全部整数字面量的十进制值都是17: let deci ...
- 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十五 ║Vue基础:JS面向对象&字面量& this字
缘起 书接上文<从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史>,昨天咱们说到了以我的经历说明的web开发经历的 ...
- js字面量
以前一直对js字面量模棱两可. '字面量是一种表示值的记法.' js字面量(literal) 分为以下几个 number literal 8 就是数字字面量 string liter ...
随机推荐
- int **a 和 int (*a)[]的区别
关于理论知识隔壁们的教程说的很详细了我就不多赘述了.我这边主要贴一段代码来看看这两种东西使用上的区别到底在哪. #include <stdio.h> int main(int argc, ...
- gradle方式集成融云sdk dlopen failed: library "libsqlite.so" not found
1.gradle implementation 'cn.rongcloud.android:IMLib:2.8.6' implementation 'cn.rongcloud.android:IMKi ...
- 光标显示样式 css 中 cursor 属性使用
记录一下 cursor 的各种样式,方便自己查找.之前用到不常用的每次去 百度 或 Google 找不如自己记录下好找些. cursor光标类型 auto default none context-m ...
- ios RSA 验签加密解密
关于公钥和私钥的生成,网上有很多本地生产的方法,我遇到的问题是,按照网上生产的方式生成7个文件,本地使用没有问题,但是和后台交互就不行了. 发现生成公钥和私钥的没有那么麻烦,使用在线生产工具就能使用, ...
- 2018.8.18 servlet使用的会话跟踪除session外还有哪些方式
解释HTTP HTTP是一种无连接的协议,如果一个客户端只是单纯地请求一个文件(HTML或GIF),服务器端可以响应给客户端,并不需要知道一连串的请求是否来自于相同的客户端,而且也不需要担心客户端是否 ...
- MyBatis中解决字段名与实体类属性名不相同的冲突
一: 通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致,这样就可以表的字段名和实体类的属性名一一对应上了,这种方式是通过在sql语句中定义别名来解决字段名和属性名的映射关系 ...
- 永久免费开源的卫星地形图地图下载工具更新Somap2.13版本功能更新 更新时间2019年2月22日13:59:05
一.下载地址 最新版本下载地址:SoMap2.13点击此处下载 二.系统自主开发特色功能展示 1.上百种地图随意下载 高德.百度.arcgis.谷歌.bing.海图.腾讯.Openstreet.天地 ...
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...
- 【杂题总汇】NOIP2013(洛谷P1967) 货车运输
[洛谷P1967] 货车运输 重做NOIP提高组ing... +传送门-洛谷P1967+ ◇ 题目(copy from 洛谷) 题目描述 A国有n座城市,编号从1到n,城市之间有m条双向道路.每一条道 ...
- Hibernate无法提取结果集
原因:表结构错误或 与 映射文件不一致 org.springframework.dao.InvalidDataAccessResourceUsageException: could not extra ...