JavaScript 面向对象(番外)JS字面量
javascript字面量
在JavaScript里面,字面量包括:
字符串字面量(string literal )、
数组字面量(array literal)和
对象字面量(object literal),
另外还有函数字面量(function literal)。
1、字符串字面量(String Literal)
是指双引号引住的一系列字符,双引号中可以没有字符,可以只有一个字符,也可以有很多个字符(来源:互动百科)。
在编程语言中,字面量是一种表示值的记法。
示例:var test="hello world!";
"hello world!"就是字符串字面量,test是变量名。
2.数组字面量(array literal)
示例:var team=["tom","john","smith","kobe"];
["tom","john","smith","kobe"]是数组字面量
3.对象字面量(object literal)
示例:var person={name:"tom",age:"26",sex:"male"};
{name:"tom",age:"26",sex:"male"}为对象字面量
4.函数字面量(function literal)
var person={ name:"tom", age:"23", tell:function(){alert(this.name);} }
其中tell的值function{alert(name);}被认为是函数字面量,在调用时,函数不会执行,而是被当做数据来传递。
当然如果想把函数字面量当作函数来运行,可以使用eval(String)函数,让String里面的JavaScript执行运算:eval('person.tell()')
看到上面的示例,也许你会想到JSON(JavaScript Object Notation),对的,两者的确是有联系的。
JSON(JavaScript对象记法),它是一种用于描述文件和数组的记法,JSON由JavaScript字面量的一个子集组成。JSON可以用于交换数据,通常用它来替代xml。
为什么说对象字面量赋值比new Object()高效?
虽然平时都是以对象字面量来初始化js变量的,即
var obj={};
也没有去具体去纠结为什么这样就比var obj=new Object();
来的好
就直接在chrome上做了个对比试验,结果如下:console.time("no1"); for(var i=0;i<10000;i++) var a1={}; console.timeEnd("no1"); console.time("no2"); for(var i=0;i<10000;i++) var a2=new Object(); console.timeEnd("no2");网上别人的解释
{}是字面量,可以立即求值,
而new Object()本质上是方法(只不过这个方法是内置的)调用,既然是方法调用,就涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的堆栈信息,方法调用结束后,还要释放该堆栈。
但最快的是自己定义一个构造函数,调用函数。
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 ErrorNaN 即 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, "")) // 123re即是一个正则表达式,它将普通字符串转换为数值字符串。正斜杠后面的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和程序语言里定义字符串的方法。