变量是一个值的符合名称,可以通过名称来获得对值的引用。JavaScript的数据类型分为两类:1. 原始数据类型(数字,字符串,布尔值,null,undefined)2.对象引用类型(数组,函数)如果函数初始化使用的new运算符新建的对象,称之为构造函数。每个构造函数定义一类对象:由构造函数初始化的对象组成的集合。
JavaScript解释器有自己的管理机制,可以自动的对内存进行垃圾回收,这说明程序可以按需创建对象,当不再有任何引用指向这个对象时,解释器会自动的回收它所占用的内存资源。
3.1、数字
在javascript中,除了十进制的整型直接量,同样识别十六进制值,十六进制的直接量是指以"0x"或"0X"为前缀,其后直接跟随十六进制的直接量。
但是ECMAScript标准不支持八进制的变量,ES6中是命令禁止的八进制直接量。
3.1.1、浮点型直接量
浮点型直接量可以含有小数点。
此外,要注意指数计数法的写法:跟在实数后面的字母 e或E,后面跟上正负号,再加上一个整型的指数,这种科学计数法是由前面的实数乘以10的指数次幂。
如:
var a = 6.01e23 // a=6.01*10^23
var b = 1.4728E-32 // b=1.4728*10^-32
3.1.2、js中的算术运算
javascript是使用语言本身提供的算术运算符进行运算的,包括(加、减、乘、除、取余(%))
除了基本的运算之外,javascript还支持更加复杂的算术运算,这些复杂运算是通过作为Math对象的属性定义的函数和变量实现的:
Math.pow(3,2) //9, 3的2次方
Math.round(0.6) //1.0,四舍五入
Math.ceil(0.6)//1.0,向上取整
Math.floor(0.6)//0.0,向下取整
Math.abs(-7)//7,求其绝对值
Math.max(x,y,z)// 返回最大值
Math.min(x,y,z)// 返回最小值
Math.random()// 生成一个大于等于0小于1的随机数
Math.PI // 圆周率
Math.E //自然对数的底数
Math.sqrt(3)// 根号3,3的平方根
Math.pow(3,1/3)// 3的立方根
Math.sin(0) //三角函数
Math.log(10)//10的自然对数
Math.log(100)/Math.LN10 //以10为底100的对数
Math.log(512)/Math.LN2 //以2为底512的对数
Math.exp(3)// e的三次幂
Javascript中的算术运算在溢出(overflow)、下溢(underflow)或被0整除时不会报错,当数字运算超过了它的上限(上溢)或超过了它的下限(下溢)。结果为正无穷大(infinity)或负无穷大(-infinity)。
一般当运算结果无限接近于0并比javascript能表示的最小值还小的时候,在这种特殊的情况下,js一般会返回0,反之,一个负数发生下溢时,js返回一个特殊的"-0",被0整除时js返回infinity,当整除运算结果是一个非数字的值时,用NaN表示。具体看如下截图:
其中,js的非数字值有些特殊,它和任何值都不相等,包括它自身,也就是说,当你想判断一个值是否为NaN时,你是无法通过 num == NaN去判断的,
同理,-0也具有一定的特殊性,它和+0的值是相等的(甚至使用了js的严格相等做判断条件)
3.1.3、二进制浮点数和四舍五入错误
javascript通过浮点数的形式只能表示其中有限的个数(确切的是18437736874454810627个),也就是说,当在js中使用实数的时候,通常只是真实值的一个近似数。
js是通过二进制表示法去精确使用分数去展示浮点数,但是二进制浮点数表示法不能精确的表示类似 0.1 这样的数值。比如:
var x = 0.3 - 0.2;
var y = 0.2 - 0.1;
但是结果却是
x != y
//结果是x != 0.1, y == 0.1
在js的真实运行环境中,0.3-0.2=0.099 999 999 999 999 98
这种通常浮点数的比较需要特别注意!
3.1.4、日期和时间
js中包括了日期的构造函数Date(),用来创建表示日期和时间的对象,并且提供了简单的API。
var then = new Date(2021,3,30) //2021年3月20日
var later = new Date(2021,3,30,10,23,18) //2021年3月30日,当地时间10:23:18am
var now = new Date() //当前的日期和时间
var elapsed = now - then; //日期减法,计算相差的时间戳
now.getFullYear(); //当前的年份
now.getMonth(); //当前的月份
now.getDate(); //从1开始计算的天数
now.getDay(); // 得到的是星期几,0代表星期日,5代表星期一
now.getHours();//获取当地的时间
now.getUTCHours(); //使用UTC表示小时的时间,基于时区
3.2、文本
字符串的定义:是由一组16位值组成的不可变的有序序列,字符来自Unicode字符集。
3.2.1、字符串直接量
在js中,字符串直接量是由单引号或者双引号括起来的字符序列。同时,单引号中可以再包含双引号,双引号中可以再包含单引号。
在处理英文缩写的撇号时,需要使用()转义
"abc\def" //定义了一个显示为两行的字符串
3.2.2、转义字符
使用了转义字符,就不再表示它们字面上的含义了。
3.2.3、字符串的使用
js的内置功能是字符串的连接
var str1 = 'a';
var str2 = 'b';
var str = str1+str2;
// str :'ab'
字符串也有很多对应的属性和方法,大致归纳:
str.length //获取字符串的长度
str.charAt(0) //拿到第一个字符
str.charAt(str.length - 1) //拿到最后一个字符
str.substring(1,4) //拿到索引大于等于1小于4中间的字符
str.slice(1,4) //拿到索引大于等于1小于4中间的字符
str.slice(-3) //拿到最后三个字符
str.indexOf('1') //拿到字符‘1’第一次出现的索引位置
str.indexOf('1',3) //在位置3及之后首次出现字符‘1’的索引位置
str.split(",") //将字符串根据逗号分隔成不同的子串存入数组中去 'a,b' => ['a','b']
str.replace('a','A') //全文字符替换 'a,b' => 'A,b'
str.toUpperCase() //全文转为大写
str.toLowerCase() //全文转为小写
注意:js中字符串本身是不会改变的,replace()以及toUpperCase()的方法都是返回的一个新的字符串!
3.2.4、正则表达式(模式匹配)
js中定义了RegExp()的构造函数,用来创建表示文本匹配模式的对象。RegExp并不是js的基本数据类型,和Date()一样,只是具有实用性API的特殊对象
/^HTML/ //匹配HTML开头的字符串
/[1-9][0-9]*/ //匹配一个非0数字
/\javascript\b/i //匹配单词'javascript',忽略大小写
3.3、布尔值
布尔值的结果只有两种(真true或假false)
布尔值包含toString()方法,可以将值转为字符串。
布尔运算中包含了三种重要运算符
// 逻辑与(and)
&& //当且仅当两个操作数都为真值时才返回true
// 逻辑或(or)
|| //当两个操作数只要有一个为真就返回true
// 一元运算符 逻辑非(!)
! 操作数为真返回false
3.4、null和undefined
null:通常用来描述空值,null是一种特殊的对象值,含义就是“非对象”,但是实际上,null是它自有类型的唯一一个成员,它可以表示很多是空值的,比如:数字,字符串,对象。
undefined:对未定义表示更深层次的“空值”,它是变量的一种取值,表示变量没有初始化,如果查询对象属性或数组元素的值不存在那么同样返回undefined。
undefined在ES3中是可读写的变量,可以赋给任何值,这个在ES5中已更正,变成只读的。
但是undefined在使用typeof查看类型时,是undefined类型,表明这个值是这个类型的唯一成员。
注意:
null == undefined //返回的是true
null === undefined //返回的是false
3.5、全局对象
当javascript解释器启动时(或者web浏览器加载新的页面时),它将创建一个新的全局对象,并给它一组定义的初始属性:
全局对象的初始属性并不是保留字,但它们应该当作保留字来对待,对于客户端js来说,window定义了一些额外的全局属性。
//js关键字this来引用全局对象
var global = this; //定义一个引用全局对象的全局变量
在客户端js中,在其表示的浏览器窗口中的所有js代码中,Window对象充当了全局对象,这个全局Window对象有一个属性window引用自身,它可以代替this来引用全局对象,当初次创建时,全局对象定义了js中所有预定义的全局值。
3.6、包装对象
js对象是一种复合值,它是属性或已命名值的集合,通过'.'成员运算符来访问去使用对应的属性值。
同理就诞生出了一个问题:前面讲过字符串是一个16位值的有序序列,自身
是固定不变的,它不是对象,但是它为什么会有属性呢?比如:length属性....
原因是因为:js中只要引用了字符串的属性,那么js会将字符串的值通过调用new String(str)的方式转换为对象,这个对象继承了字符串的所有的属性和方法,并被用来处理属性的引用,一旦属性引用结束,这个新创建的对象就会被销毁。同字符串一样,数字,布尔值一样可以有:Number()和Boolean(),而null和undefined没有包装对象,访问属性会造成类型错误!
大致的包装对象分为三种:String()、Number()、Boolean()
var s = 'test',n=1,b=true;
var str = new String(s);
var num = new Number(n);
var bool = new Boolean(b);
// 在使用 == 时它们是相等的,但是在使用 === 时就视为不等!因为原始值和其包装对象的类型不同
3.7、不可变的原始值和可变的对象引用
原始数据类型是不可改变的,针对数字、布尔值、比较好理解,改变数字本身显然是一种错误的处理方式,但是对于字符串,有人以为使用字符串的方法(像切割字符串...)等,这些并不是改变了字符串,而是返回了一个新的字符串!这点非常重要。
原始值的比较是关于值的比较,值的内容相等那么它们就是相等的。但是字符串也有所不同,字符串的相等需要索引下标一一对应的位置的元素的值相等,那么它们才会相等!
对象的比较并非是值的比较,打个比方:如果两个对象的属性相同,属性值也相同,但是这两个对象不是相等的。通常将对象称为引用类型,对象值都是引用,对象的比较也都是引用的比较,当且仅当它们都指向同一个对象地址时,它们才会是相等的。
var arrA = []; //定义一个空的数组
var arrB = arrA; //将arrB也指向arrA数组的引用地址上去
arrB[0] = 1; //向arrB数组中添加一个数据
console.log(arrA); //这时arrA数组中也有数据1,因为arrA和arrB指向的基数组是同一个
arrA === arrB; //返回的是 true
3.8、类型转换
当js想要使用一个布尔值的时候,你可以提供任意类型的值,js将自行转换类型。js如果想使用字符串,它把给定的值将转换为字符串,如果是数字,同样可以转换。
10 + "objects" // "10 objects" 数字10被转换成了字符串
"7"*"2" // 14 两个字符串又转换为了数字
var a = 1 - 'x' //NaN x无法转换为数字
a + ' obj' // 'NaN obj' NaN将转换为字符串NaN
原始数据转为原始数据相对容易,比如:转为数字,可调用运算即可自动转换,想转为字符串,加个空字符串也就可以了,而原始数据转为对象,也可以直接使用前面的包装对象,String(), Number(), Boolean()....
3.8.1、转换和相等性
由于js存在灵活的类型转换,因此“ == ”相等运算也随着含义开始多变。
null == undedined // true
"0" == 0 //true 比较之前字符串"0"转为数字0
0 == false //true 比较之前布尔值false转为数字0
"0" == false //true 比较之前"0"和false都转为数字0
如果在使用布尔值的地方用到了undefined,它将会转为false,但并不表明 undefined == false.
3.8.2、显示类型转换
js有许多隐式转换,但有时仍需要显示转换。显示转换最常用的是Boolean()、Number()、String()、或者Object()函数,当不通过new运算符去处理时,它们只是单纯的作为类型转换函数。
Number("3"); // 数字3
String(false); //字符串 "false"
Boolean([]); // true
Object(3); // 等价于 new Number(3)
值得注意的地方:js中除了null和undefined之外,任何值都具有toString()方法,当把null和undefined转换为对象时,这时不会报类型错误,而是返回一个新建的空对象!
3.8.3、toString()方法的理解
Number类中定义toString()方法是可以接收表示转换基数的可选参数,如果不指定此参数,转换规则将是基于十进制。
var n = 17;
binary_string = n.toString(2); // 等于 "10001"
octal_string ='0' + n.toString(8); //等于 "021"
hex_string = '0x' + n.toString(16); //等于 '0x11'
Number类还为数字提供了精度问题:
- toFixed():根据小数点后的指定位数将数字转换为字符串(我个人的理解是:小数点后保留几位小数的处理)
比如:
var n = 12345.789;
n.toFixed(2); // '12345.79'
n.toFixed(5); // '12345.78900'
- toExponential():使用指数计数法将数字转换为指数形式的字符串(个人理解就是:小数点后保留几位然后使用科学计数法)
var n = 12345.789;
n.toExponential(1); // '1.2e+5'
n.toExponential(5); // '1.2345e+5'
- parseInt():将Number()函数中的的字符串转换为一个整数或浮点数。只能基于十进制转换!
parseInt('7 dasdsa dwq'); // 7
parseInt(' 3.14 cvaacva'); //3.14
parseInt('0xFF'); // 255
parseInt('.8'); //NaN 整数不能以 . 开始
parseInt('$86'); // NaN 整数不能以$开头
3.8.4、对象转换为原始值
js对象有两种不同的方法来执行转换.
- 第一个是toString(),它的作用是返回一个反映这个对象的字符串。
比如:
[1,2,3].toString(); // '1,2,3'
(function(x){ f(x); }).toString(); // ' function(x){ f(x); }'
- 第二个是valueOf():如果存在任意原始数据,它将默认将对象转换为表示它的原始数据值。对象是复合值,大多数对象无法真正表示为一个原始数据,因此默认的valueOf() 方法简单地返回对象本身,而不是返回一个原始数据。
var d = new Date(2021,3,20); // d返回的是1970.1.1到目前为止的毫秒数
那么空数组被转为数字0的具体过程是:
数组先调用toString()方法,转换为空字符串,然后再由空字符串转换为数字0.
3.9、变量声明
在js中,使用一个变量之前应当先声明变量,变量是使用关键字var来声明的(ES6中还有let , const)
如果在var声明之前就使用了,那么返回的是undefined.
3.10、变量作用域
个人理解的作用域就是一个变量在代码程序中的使用范围,函数有函数作用域,(除开ES6闭包)。
全局变量拥有全局作用域,在js脚本中任何范围均可以使用,函数内声明的变量只在函数体内有定义,函数内创建的变量的作用域便是局部作用域,函数的带参的作用域同理也是局部的,在当前函数内使用,因为A函数的形参不可能成为B函数的形参。
3.10.1、函数作用域和变量声明提前
花括号内的每一段代码都是具有各自的作用域的,而且变量在声明它们的代码之外的地方是处于不可见的,我们称之为:块级作用域(js中称为函数级作用域),变量在声明它们的函数体嵌套的任意函数体内都是有定义的。
function test(num){
var i = 0; // 在整个函数内均是有定义的
if(num < 10){
var j = 0; //在整个函数内均是有定义的
for(var k = 0; k < 10;k++){
console.log(k); // 在整个函数内均是有定义的
}
console.log(k)
}
console.log(j)
}
js的函数作用域是指在函数内声明的所有变量在函数体内始终是可见的,意味着你在后面声明之前甚至可以先用这个变量。这种非正式的特性被称为“变量声明提前”
即js函数中所有声明的变量都被提到函数顶部.
var a = 'c';
function fn(){
console.log(a); // a 为undefined
var a = 'd';
console.log(a); // a= 'd'
}
var a = 'c' ;// 函数体内的同名全局变量会遮盖同名的全局变量
function fn(){
var a; //函数内的变量声明提升至函数体的顶部
console.log(a); //故打印的undefined
var a = 'd'; //函数体内再次赋值
console.log(a); //a= 'd'
}
3.10.2、作用属性的变量
当声明了一个全局变量时,实际上等价于定义了一个全局对象的一个属性。
当使用var声明一个变量时,创建的这个属性是不可配置的,也就是说该变量无法通过delete去删除!
js全局变量是全局对象的一个属性,这是在ECMA规范中强制规定的,而函数体内的局部变量并没这种,没有函数的this指向的话,this指向的是全局对象
3.10.3、作用域链
全局变量在整个程序中始终都是有定义的,局部变量在声明它的函数体内以及其所嵌套的函数体内始终是有定义的。
如果将一个局部变量看做是自定义实现的对象的属性的话,那么在每一段js代码都有一个与之关联的作用域链。这个作用域链是一个对象列表或者链表,定义了这段代码“作用域中”的变量,当js代码需要查找某个变量时,它会从链中的第一个对象开始查找,如果这个对象有关于这个的属性,那么会直接使用这个属性的值,如果第一个对象不存在这个属性,那么会继续查找下一个对象。如果整个对象列表查完了,还是不存在这个属性,那么就会抛出异常错误。(还会再查阅一些资料).