在编程语言中,能够表示并操作的值的类型称为数据类型(type)。
JavaScript的数据类型分为两类:
原始类型
和
对象类型
。5种原始类型:
数 字
、
字符串
、
布尔值
、
null
(空)、
undefined
(未定义)。原始类型之外的就是对象类型了,
对象是属性的集合,每个属性都由“名/值对”
(值可以是原始 值、也可以是对象)
构成
。
普通的JavaScript对象是“命名值”的无序集合,特殊对象--
数组表示带编 号的值的有序集合
。另一特殊对象--
函数是具有与它相关联的可执行代码的对象,通过调用函数来运行可执行代码,并返回运算结果
。如果函数用来初始化(使用new关键字)一个新建的对象,则称之为
构造函数
。每个构造函数定义了一类(class)对象--由构造函数初始化的对象组成的集合。类可以看做是对象类型的子类型。JavaScript语言核心还定义了三种有用的类:
日期(Date)类
、
正则(RegExp)类
、
错误(Error)类
。
JavaScript
变量是无类型的
,
变量可以被赋予任何类型的值
,同一变量也可以重新赋予其他类型的值。使用
var
关键字来声明(declare)变量,
不在任何函数中声明的变量叫全局变量
(
var声明的是全局变量,可使用let关键字声明局部变量
),它在JavaScript程序中的任何地方都是可见的。
数字
JavaScript中
不区分整数值和浮点数值
。JavaScript可以识别十进制整型直接量(所谓直接量,就是程序中直接使用的数据值),和16进制值(
以0x或0X为前缀,a~f表示10~15
,那个是数字0不是字母o)。尽管ECMAScript标准不支持八进制直接量,但JavaScript的某些实现可以采用八进制形式表示整数(
以数字0为前缀
),不过在ECMAScript6的严格模式下,八进制直接量是明令禁止的。浮点型直接量有两种写法。①
传统的实数写法
:有整数部分、小数点和小数部分组成;②
指数计数法
:即在实数后跟字母e或E,后面跟正负号,其后再跟一个整型的指数,如6.3e-5表示6.3×10的-5次方。
算术运算的溢出
基本运算符:+ - * / %
复杂运算通过Math对象中定义的函数和常量实现,常用的有:
Math.abs(-5) //绝对值
Math.ceil(1.5) //向上取整
Math.floor(1.5) //向下取整
Math.random() //[0-1)随机数
Math.round(1.5) //四舍五入
Math.pow(2,3) //2的3次幂
Math.max(1,2,3) //返回最大值
Math.min(1,2,3) //返回最小值
Math.PI //π:圆周率
Math.E //e:自然对数的底数
JavaScript中算术运算在
溢出(overflow)
、
下溢(underflow)
或
被0整除
时不会报错。
溢出:当运算结果超出了JavaScript所能表示的数字上限,结果为正无穷大
Infinity
或负无穷大
-Infinity
。无穷大值的行为特性也符合现实:基于它们的加、减、乘和除运算结果还是无穷大值(当然保留它们的正负号);
下溢
:当运算结果无限接近于零并比JavaScript能表示的最小值还小的时候发生的情形。这种情况下,将会返回0。当一个负数发生下溢时返回特殊值“负零”。负零和整零基本是相等的,除了作为除数之外。被0整除会返回正无穷大或者负无穷大值。但0除以0会返回NaN(
非数字值,Java-Script预定义对象Number的NaN属性的值
)。
返回NaN的有四种情况:
①
0除以0
②
无穷大除以无穷大
③
给任意负数作开方运算
④
算术运算符与不是数字或无法转换为数字的操作数一起使用时。
NaN值有一点特殊:
它和任何值都不相等,包括自身
。
判断一个变量x是否为NaN的两种方法:
①
使用函数
isNaN()
②
是使用
x != x
判断,当且仅当x为NaN时,表达式结果才为true。
JavaScript中还有一个类似的函数
isFinite()
,在参数不是NaN、Infinity或-Infinity的时候返回true。
二进制浮点数和四舍五入错误
实数有无数个,但
JavaScript通过浮点数的形式只能表示其中有限个数
。也就是说,在JavaScript中使用实数的时候,常常只是一个真实值的一个近似表示。JavaScript采用了IEEE-754浮点数表示法,这是一种二进制表示法,可以精确的表示如1/2、1/8和1/1024、这样的分数,但是十进制分数1/10、1/100等并不能精确的表示。例如:
let x = 0.3 -0.2; //x=0.09999999999999998
let y = 0.2 - 0.1; // y=0.1
x == y //false
x == 0.1 //false
y == 0.1 //true
0.1 == 0.1 //true
let z = x + y; //z=0.19999999999999998
日期和时间
JavaScript提供了Date()构造函数,用来创建日期和时间的对象
,并提供了日期计算的一些简单API:
let now = new Date(); //获取当前日期时间
let then = new Date(2019,3,9); //设置日期
let later = new Date(2019,3,10,17,10,30); //设置日期和时间
let elapsed = later - then; //计算时间间隔的毫秒数
later.getFullYear() //获取年later.getMonth() //获取月,从0计数
later.getDate() //获取日,从1计数
later.getDay() //获取星期,星期天为 0, 星期一为 1, 以此类推
later.getHours() //获取时
later.getUTCHours() //基于UTC时区获取小时
let d=new Date();
let weekday=new Array(7);
weekday[0]="Sunday";
weekday[1]="Monday";
weekday[2]="Tuesday";
weekday[3]="Wednesday";
weekday[4]="Thursday";
weekday[5]="Friday";
weekday[6]="Saturday";
let n = weekday[d.getDay()];
字符串、字符集
字符串(string)是一组由16位(2 bytes,js 采用 unicode 编码,每个字符需要两个字节)值组成的不可变的有序序列,每个字符通常来自于Unicode字符集
。字符串的长度(length)是其所含16位值的个数。JavaScript通过字符串类型来表示文本。注意:JavaScript中并没有表示单个字符的“字符型”。要表示一个16位值,只需将其赋值给字符串变量即可。
JavaScript采用UTF-16编码的Unicode字符集,JavaScript字符串是由一组无符号的16位值组成的序列。那些不能表示为16位的Unicode字符则遵循UTF-16编码规则——用两个16位值组成一个序列(或称作“
代理项对
”)表示。这意味着一个长度为2的JavaScript字符串有可能表示一个Unicode字符。注意:JavaScript定义的各式字符串的操作方法均作用于16位值,而非字符,且不会对代理项对做单独处理。
字符串的定界符可以是单引号或者双引号
(JavaScript的单引号和双引号没有任何区别)。这两种形式的定界符可以嵌套,但是不能多层嵌套(比如,双引号可以包含单引号,这时单引号中不能再包含双引号了)。
一个字符串值可以拆分为数行,每行必须以反斜线(\)结束
,这时反斜线和行结束符都不算是字符串内容,即字符串本身并非是多行,只是写成了多行的形式。
注意:
①
在JavaScript中字符串是固定不变的(除非重新赋值),类似replace()和toUpperCase()的方法都返回新字符串,原字符串本身并没有变化;
②
字符串可以当做只读数组,除了使用charAt()方法来查询一个单一字符,也可以使用方括号的方式来访问字符串中的单个字符(16位值),例如:
s = "hello, world";
s[0] //=>"h"
转义字符
转义字符 | 含义 |
---|---|
\o | NULL字符(\u0000) |
\b | 退格符(\u0008) |
\t | 水平制表符(\u0009) |
\n | 换行符(\u000A) |
\v | 垂直制表符(\u000B) |
\f | 换页符(\u000C) |
\r | 回车符(\u000D) |
\" | 双引号(\u0022) |
\' | 撇号或单引号(\u0027) |
\\ | 反斜线(\u005C) |
\xXX | 由两位十六进制数XX指定的Latin-1字符 |
\uXXXX | 由四位十六进制数XXXX指定的Unicode字符 |
注意:如果"\"字符位于没有在表中列出的字符前,则忽略"\"。比如,"\#"和"#"等价。别忘了反斜线还有一个作用就是多行字符串中每行结束处使用反斜线。
使用“+”可以对字符串进行拼接,同时也有对字符串进行操作的相关函数:
let str = "my name is yh"; //undefined
str.charAt(0) //"m"
str.charAt(str.length-1) //"h"
str.substring(1,4) //"y n"
str.slice(1,4) //"y n"
str.slice(-3) //" yh"
str.indexOf('y') //1
str.indexOf('y',3) //11
str.lastIndexOf('y') //11
str.split(' ') //Array(4) [ "my", "name", "is", "yh" ]
str.replace('m','M') //"My name is yh"
str.toUpperCase() //"MY NAME IS YH"
str.toLowerCase() //"my name is yh"
模式匹配
JavaScript定义了RegExp()构造方法,用来创建表示文本匹配模式的对象,这些模式称为
正则表达式
,两个 / 之间的文本构成了一个正则表达式直接量。字符串具有可以接收RegExp参数的方法:
let text = "testing:1,2,3"; //undefined
let pattern = /\d+/g; //匹配包含一个或多个数字的实例 undefined
pattern.test(text) //true
text .search(pattern) //查找首次匹配成功的位置 8
text .match(pattern) //所有匹配组成数组 Array(3) [ "1", "2", "3" ]
text.replace(pattern,"#") //正则表达式替代 "testing:#,#,#"
text.split(/\D+/); //非数字字符截取字符串 Array(4) [ "", "1", "2", "3" ]
text.split(/\d+/); //数字字符截取字符串 Array(4) [ "testing:", ",", ",", "" ]
布尔值
在JavaScript中的值都可以转换为布尔值
。其中,
null、undefined、0、-0、NaN、""(空字符串),这6个值会被转换成false
,false和这六个值有时称做“假值”;
其他所有值,包括对象(数组)都会转换成true
,true和这些值相应的被称做“真值”。注意:布尔值包含toString()方法,因此可以使用这个方法将字符串转换成“true”或 “false”,但它并不包含其他有用的方法。
注意:一个值转换为另一个值并不意味着两个值相等。比如,如果在期望使用布尔值的地方使用了undefined,它将会转换为false,但这并不表明undefined == false(这个表达式结果为false)。
“&&”
都真才为真
“||”
都假才为假
“!”
取反
null和undefined
null,它表示一个特殊值,常用来描述“空值”
。对null执行typeof运算,结果返回字符“object”,也就是说,
可以将null认为是一个特殊的对象值
,含义是“非对象”。实际上,
通常认为null是它自有类型的唯一一个成员,它可以表示数字、字符串和对象是“无值”的
。
JavaScript还有第二个值来表示值的空缺,就是
undefined
,用未定义的值表示更深层次的“空值”。undefined出现有4种情况:
①
变量声明但没有初始化时
②
要查询的对象属性或数组的元素不存在时
③
如果函数没有任何返回值,则返回undefined
④
引用没有提供实参的函数形参的值也只会得到undefined。
两者相同点
:
①
正如前面所说,它们都是“假值”,也就是说JavaScript期望使用一个布尔值时,它们都会被转换成false;
②
它们两个都不包含任何属性和方法;
③
在根据需要转换成对象时两者都会报异常,即throws TypeError。
两者不同点
:
①
null是JavaScript语言的关键字,而undefined是JavaScript预定义的全局变量,不是关键字。并且,在ECMAScript 3中,undefined是可读、可写的变量,可以给它赋任何值,这个错误在ECMAScript 5中做了修正,在该版本中undefined是只读的;
②
执行typeof运算,null返回“object”字符串,undefined返回“undefined”字符串;
③
undefined在根据需要自行转换为字符串是转换为"undefined",而null转换为"null";
④
undefined在根据需要自行转换为数字时转换为NaN,而null转换为0。把null和undefined做比较,
null == undefined 返回true,null === undefined 返回false
。可以认为undefined是表示系统级的、出乎意料的或类似错误的值的空缺,而null是表示程序级的、正常的或在意料之中的值的空缺。如果想把它们赋值给变量或属性或者当做参数传入函数,最好选择使用null。
全局对象
对于任何JavaScript程序,当程序开始运行时,JavaScript解释器都会初始化一个全局对象以供程序使用。这个JavaScript自身提供的全局对象的功能包括:
①
全局属性
:比如undefined、Infinity以及NaN。
②
全局对象
:比如Math、JSON和Number
③
全局函数
:比如isNaN()、isFinite()、parseInt()和eval()等。
④
全局构造器
:constructor,也即全局类。比如Date()、RegExp()、String()、 Object()和Array()等。
除了JS全局对象,对于运行在浏览器端的JavaScript程序,还有另一个全局对象:
window
。window全局对象提供了与当前窗口、页面有关的诸多属性与方法。除了这些与浏览器有关的全局属性和方法,window对象还封装了JS全局对象,并向外暴露JS全局对象的属性与接口;因此,当进行浏览器端JavaScript编程时,只需关心window全局对象即可。
对于JavaScript程序中的this,如果this不属于任何function,那么这个this就指代JS全局对象;如果是浏览器端运行的JS程序,那么这个this就指代window全局对象。
包装对象
JavaScript对象是一种复合值:它是属性和已命名值的集合。通过"."符号来
引用属性值。当属性值是一个函数时,称为方法。
var s = "hello world!";
var word = s.substring(s.indexOf(" ")+1,s.length);
只要引用了字符串s的属性,JavaScript就会将字符串值通过调用new String(s)的方式转换成对象,这个对象继承了字符串(String)对象的方法,并被用来处理属性的引用。一旦属性引用结束,这个新创建的对象就会被销毁。
同字符串一样,数字和布尔值也有各自的方法:通Number()和Boolean()构造函数创建一个临时对象
。
存取字符串、数字或布尔值的属性时创
建的临时对象就是包装对象
。5种原始类型中的剩余两种
null和undefined没有包装对象
:访问它们的属性会造成一个类型错误(Uncaught TypeError)。
var s = "test";
s.len = 4;//给它设置一个属性
var t = s.len;
第二行代码只是创建了一个临时字符串对象,并给len属性赋值为4,随即销毁这个对象。而第三行又是通过原始字符串s创建一个新字符串对象(这个不是第二行代码创建的对象,第二行代码创建的对象已经被销毁了)并尝试读取其len属相,这个属性自然不存在,因此表达式的结果为undefined。这段代码说明了
在读取字符串、数字和布尔值的属性值或方法(实际上是它们对应包装对象的属性值或方法)表现的像对象一样。但如果你试图给属性赋值,则会忽略这个操作:修改只是发生在临时对象身上,而这个临时对象并不会继续保留下来
。
可通过String(),Number(),Boolean()构造函数来显示创建包装对象:
var s = "test",n=1,b=true;//一个字符串、数字和布尔值
var S = new String(s);//一个字符串对象
var N = new Number(n);//一个数值对象
var B = new Boolean(b);//一个布尔对象
JavaScript会在必要时将包装对象转换成原始值,因此上段代码中的对象S、N和B常常但不总是表现的和值s、n和b一样。"
==
"等于运算符将原始值和其包装对象视为相等,但“
===
”全等运算将它们视为不等。
不可变的原始值和可变的对象引用
JavaScript中的原始值(undefined、null、布尔值、字符串和数字)与对象有着根本区别。原始值是不可变的。
var s = "hello";
s.toUpperCase(); // => "HELLO",但并没有改变s的值
consle.log(s); // => "hello",原始字符串并未改变
对象和原始值不同,首先,它们是可变的:
var o = {x:1};
o.x = 2; // 可以修改对象属性值
o.y = 3; // 可以给对象添加新属性
var a = [1,2,3]; // 数组也是可修改的
a[0] = 0; // 更改数组的一个元素
a[3] = 4; // 给数组添加一个新元素
对象的比较并非值的比较:即使两个对象包含同样的属性以及相同的值,他们也是不相等的。此外,各个索引元素完全相等的两个数组也不相等。
var o = { x:1 },p={ x:1 };
o === p // => false:两个单独的对象永远不相等
var a = [],b = [];
a === b // => false:两个单独的数组永远不相等
通常将对象称为引用类型(reference type)
,以此来和JavaScript的基本类型区分开来。
对象值都是引用(reference),对象的比较均是引用的比较:
当且仅当它
们引用自同一个基对象时,它们才相等
。
var a = [];
var b = a;
b[0] = 1;
a[0] // => 1:变量a也会修改
a === b // => true:a和b引用自同一数组,因此它们相等
类型转换
值 | 转换为字符串 | 转换为数字 | 转换为布尔值 | 转换为对象 |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throws TypeError |
null | "null" | 0 | false | throws TypeError |
true | "true" | 1 | new Boolean(true) | |
false | "false" | 0 | new Boolean(false) | |
"" | 0 | false | new String("") | |
"1.2" | 1.2 | true | new String("1.2") | |
"one" | NaN | true | new String("one") | |
0 | "0" | false | new Number(0) | |
-0 | "0" | false | new Number(-0) | |
NaN | "NaN" | false | new Number(NaN) | |
Infinity | "Infinity" | true | new Number(Infinity) | |
-Infinity | "-Infinity" | true | new Number(-Infinity) | |
1 | "1" | true | new Number(1) | |
{} | true | |||
[] | "" | 0 | true | |
[9] | "9" | 9 | true | |
["a"] | 使用join()方法 | NaN | true | |
function(){} | NaN | true |
转换和相等性
null == undefined //这两值被认为相等
“0” = 0 //在比较之前字符串转换成数字
0 == false //在比较之前布尔值转换成数字
“0” == false //在比较之前字符串和布尔值都转换成数字
“==”等于运算符在判断两个值是否相等时会进行类型转换,但一个值转换为另一个值并不意味着两个值相等。而“===”恒等运算符在判断相等时并未做任何类型转换。
显示类型转换
使用
Boolean()
、
Number()
、
String()
或
Object()
函数进行显示类型转换。例如:
Number(“3”) //3
String(false) //”false”,等价于false.toString()
Boolean([]) //true
Object(3) //new Number(3)
注意:除了null、undefined之外的任何值都有toString()方法,Number类定义的toString()方法可以接收表示转换基数(radix)【转换基数指二进、八进制、十六进制】,不指定则转换规则默认为十进制,radix取值范围为2~36之间。如果通过Number()转换函数传入一个字符串,它会试图将其转换为一个整数或浮点数直接量,这个方法只能基于十进制进行转换,并且不能出现非法的
尾随字符。parseInt()和parseFloat()函数(它们是全局函数,不从属于任何类的方法)则灵活点,
parseInt()只解析整数,而parseFloat()则可以解析整数和浮点数
。如果字符串前缀是“0x”或者“0X”,parseInt()将其解释为十六进制数,parseInt()和parseFloat()都会跳过任意数量的前导空格,尽可能解析更多数值字符,并忽略后面的内容。如果第一个非空字符是非法的数字直接量,将返回NaN:
parseInt(”3 blind mice”) //3
parseFloat(“3.14 meters”) //3.14
parseInt(“0xFF”) //255
parseFloat(“.1”) //0.1
parseInt(“0.1”) //0
parseInt(“.1”) //NaN:整数不能以“.”开始
parseInt()可以接收第二个参数,这个参数指定数字转换的基数,合法的取值范围是2~36
parseInt("11",2)
js中的某些运算符会做隐式的类型转换。如果”+”运算符的一个操作数是
字符串,它将会把另外一个操作数转换为字符串
。
一元“+”运算符将其操作数
转换为数字。一元“!”运算符将其操作数转换为布尔值并取反
。
x + "" //等价于String(x)
+x //等价于Number(x),也可以写成x-0
!!x //等价于Boolean(x)
对象转换为原始值
对象到布尔值的转换非常简单:所有对象(包括数组和函数)都转换为true。
所有的对象继承了两个转换方法:
①
toString()
:它的作用是返回一个反映这个对象的字符串。默认的toString()方法并不会返回一个有趣的值;
②
valueOf()
:如果存在任意原始值,它就默认将对象转换为表示它的原始值。对象是复合值,而且大多数对象无法真正表示为一个原始值,因此
默认的valueOf()方法简单地返回对象本身,而不是返回一个原始值
。
js中对象到字符串的转换经过如下步骤:
①如果对象有toString()方法,则调用这个方法。如果返回一个原始值,js将这个值转换为字符串(如果本身不是字符串),并返回这个字符串结果;
②如果对象没有toString()方法,或者这个方法返回的并不是一个原始值,那么js会调用valueOf()方法(如果存在此方法)。如果返回值是原始值,js将这个值转换为字符串(如果本身不是字符串的话),并返回这个字符串结果;
③否则,js无法从toString()或valueOf()获得一个原始值,因此这时它将抛出一个类型错误异常。
js中对象到数字的转换过程中,js做了同样的事情,只是它会首先尝试使用valueOf()方法:
①如果对象具有valueOf()方法,或者返回一个原始值,则js将这个原始值转换为数字(如果需要的话)并返回、这个数字;
②否则,如果对象具有toString()方法,后者返回一个原始值,则js将其转换并返回;
③否则,js抛出一个类型错误异常;
函数作用域
var scope = "global";
function t(){
console.log(scope);
var scope = "local"
console.log(scope);
}
t();
第一句输出的是: "undefined",而不是 "global" ,第二句输出的是:"local" ,在Java/C++中,花括号内中的每一段代码都具有各自的作用域,而且变量在声明它们的代码段之外是不可见的。而
Javascript压根没有块级作用域,而是函数作用域
。
函数作用域:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的。这意味着变量在声明之前甚至已经可用,这种特性被称为声明提
前
(hoisting)。
根据函数作用域的意思,可以将上述代码重写如下:
var scope = "global";
function t(){
var scope;
console.log(scope);
scope = "local"
console.log(scope);
}
t();
可以看到,由于函数作用域的特性,局部变量在整个函数体始终是有定义的,我们可以将变量声明”提前“到函数体顶部,同时变量初始化还在原来位置。
为什么说Js没有块级作用域?以下代码:
var name = "global"; //这里要注意var与let的区别
if(true){
var name = "local";
console.log(name)
}
console.log(name);
都输出是“local",如果有块级作用域,明显if语句将创建局部变量name,并不会修改全局name,可是没有这样,所以Js没有块级作用域。
变量作用域
在JavaScript中全局变量的作用域比较简单,它的作用域是全局的,在代码的任何地方都是有定义的。然而函数的参数和局部变量只在函数体内有定义。另外
局部变量的优先级要高于同名的全局变量,也就是说当局部变量与全局变量重名时,局部变量会覆盖全局变量,但并不是说全局变量改变了
。
var num = 1; //声明一个全局变量
function func() {
var num = 2; //声明一个局部变量
return num;
}
console.log(func()); //输出:2
当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说无法通过delete运算符删除:
var name = 1 //不可删除
sex=“girl” //可删除
this.age = 22 //可删除
特别注意:尽管在声明全局作用域时可以不加var关键字,但声明局部变量时则必须使用var关键字,否则会显示地声明一个新的全局变量或修改同名全局变量的值。(现在可以使用let关键字声明局部变量)
scope = "global";
function s(){
scope = "local";
myscope = "local";
return [scope , myscope];
}
s() //Array [ "local", "local" ]
//scope "local" //全局变量的值被修改了!
//myscope "local" //创建的新的全局变量
作用域链
当代码在一个环境中执行时,会创建变量对象的一个
作用域链
(scope chain,不简称sc)来保证对执行环境
有权访问的变量
和
函数的有序访问
。
作用域第一个对象始终是当前执行代码所在环境的变量对象(VO):
name = "yh";
function t(){
var name="yhyh";
function s(){
var name="yhyhyh";
console.log(name);
}
function ss(){
console.log(name);
}
s();
ss();
}
t();
当执行s时,将创建函数s的执行环境(调用对象),并将该对象置于链表开头,然后将函数t的调用对象链接在之后,最后是全局对象。然后从链表开头寻找变量name,很明显name是"yhyhyh"。但执行ss()时,作用域链是: ss() -> t() -> window,所以name是”yhyh"。