一、类型
七种内置类型:null undefined boolean number string object symbol 除了object其他6种为基本数据类型
变量没有类型,但他们持有的值有类型。 除了typeof null ===“object” 其他数据的typeof值都和他的数据类型名称相对应
typeof function a(){..}===“function”
typeof [1,2,3] ===“object”
本质上函数和数组都是object的“子类型”,函数是“可调用对象”,数组也是一个对象。
在对变量执行typeof操作时,得到的返回值是该变量持有的值的类型。JavaScript中的变量没有类型。也可以进行强制类型转换。强制类型转换后,typeof的返回值是强制类型转换之后的类型值。例如:a变量开始是string,通过强制类型转变变成number,那么现在typeof a === number。
变量已经在作用域中声明但未赋值时typeof的返回值为undefined。
变量还没有在作用域中声明 是undeclared。此时typrof的返回值也为undefined,在调用这个没有声明的变量时会报错:变量 is not defined 等同于 undeclared。(两种情况报错返回值相同,比较容易搞混,实际开发中一定要注意这个问题)
解决方法:
①typeof的安全防范机制 对用户定义的变量:
// 这样会抛出错误
if (DEBUG) {
console.log( "Debugging is starting" );
}
// 这样是安全的
if (typeof DEBUG !== "undefined") {
console.log( "Debugging is starting" );
}
对内建的API:
if (typeof atob === "undefined") {
atob = function() { /*..*/ };
}
②检查所有全局变量是否是全局对象的属性(浏览器中的全局对象是window)
if (window.DEBUG) {
// ..
}
if (!window.atob) {
// ..
}
二、值
数组
数组里可以写字符串、数字、对象、其他数组(多维数组的实现), 对数组声明后即可向其中加入值,不需要预先设定大小。
使用 delete 运算符可以将单元从数组中删除,但是单元删除后,数组的 length 属性并不会发生变化。
数组通过数字进行索引,但数组也是对象,所以也可以包含字符串键值和属性(但是这些并不计算在数组长度内)
var a=[]
a[0] = 1;
a["a"] = 2;
a.length; // 1
a["a"]; // 2
a.a; //2
如果加入的字符串键值看起来像“数字”(能够被强制类型转换为十进制数字)的话,它就会被当作数字索引来处理。
var a = [ ];
a["13"] = 42;
a.length; // 14
不了解原理的话很容易出“事故”而又找不到原因。
建议最好用对象来存放键值/属性值,用数组来存放数字索引值。
类数组
有时候需要将类数组(例如elementcollection,nodeList,classList)转换成真正的数组,最先想到的方法就是通过数组工具函数(indexof(),consat(),forEach())或者是循环遍历出来push进一个新的数组中来实现。
我们还可以用一下集中方法:
①es6中的扩展运算符或者Array.from()方法来转换
let oDiv = document.getElementsByClassName('div')
let divArr=[...oDiv]
let divArr2=Array.from(oDiv)
②slice()返回参数列表
let oDiv = document.getElementsByClassName('div')
let divArr=Array.prototype.slice.call(oDiv)
字符串
字符串经常被当成字符数组。字符串的内部实现究竟有没有使用数组并不好说,但JavaScript 中的字符串和字符数组并不是一回事,最多只是看上去相似而已。 例如下面两个值:
var a = "foo";
var b = ["f","o","o"];
字符串和数组的确很相似,它们都是类数组,都有 length 属性以及 indexOf(..)(从 ES5开始数组支持此方法)和 concat(..) 方法:
a.length; // 3
b.length; // 3
a.indexOf( "o" ); // 1
b.indexOf( "o" ); // 1
var c = a.concat( "bar" ); // "foobar"
var d = b.concat( ["b","a","r"] ); // ["f","o","o","b","a","r"]
a === c; // false
b === d; // false
a; // "foo"
b; // ["f","o","o"]
但这并不意味着它们都是“字符数组”,比如:
a[1] = "O";
b[1] = "O";
a; // "foo"
b; // ["f","O","o"]
JavaScript 中字符串是不可变的,而数组是可变的。并且 a[1] 在 JavaScript 中并非总是合法语法,在老版本的 IE 中就不被允许(现在可以了)。正确的方法应该是 a.charAt(1)。字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。而数组的成员函数都是在其原始值上进行操作。
c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"
b.push( "!" );
b; // ["f","O","o","!"]
许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数组的非变更方法来处理字符串:
a.join; // undefined
a.map; // undefined
var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
return v.toUpperCase() + ".";
} ).join( "" );
c; // "f-o-o"
d; // "F.O.O."
另一个不同点在于字符串反转(JavaScript 面试常见问题)。数组有一个字符串没有的可变更成员函数 reverse():
a.reverse; // undefined
b.reverse(); // ["!","o","O","f"]
b; // ["f","O","o","!"]
可惜我们无法“借用”数组的可变更成员函数,因为字符串是不可变的:
唯一一个办法是先将字符串转换为数组,待处理完后再将结果转换回字符串:
var c = a
// 将a的值转换为字符数组
.split( "" )
// 将数组中的字符进行倒转
.reverse()
// 将数组中的字符拼接回字符串
.join( "" );
c; // "oof"
上述方法对于包含复杂字符(Unicode,如星号、多字节字符等)的字符串并不适用。如果需要经常以字符数组的方式来处理字符串的话,倒不如直接使用数组。这样就不用在字符串和数组之间来回折腾。可以在需要时使用 join("") 将字符数组转换为字符串。
数字
JavaScript中的数字常量一般用十进制表示。
var a = 14;
var b = 14.1
数字最前面和最后面的0可以省略不写。
var a = .14
a; //0.14
var b = 14.//不常见,不建议这么写,代码可能性较差
var c = 14.0
默认情况下大部分数字都以十进制显示,小数部分最后面的0被省略。
var a = 14.1000
a; //14.1
特别大和特别小的数字默认用指数格式显示,与toExponential()含糊的输出结果相同。
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
数字值可以用Number对象进行封装,多以数字值可以调用Number.prototype中的方法。例如,用tofixes()方法指定小数部分的显示位数
var a = 42.59;
a.toFixed( 0 ); // "43"
a.toFixed( 1 ); // "42.6"
a.toFixed( 2 ); // "42.59"
a.toFixed( 3 ); // "42.590"
a.toFixed( 4 ); // "42.5900"
toPrecision(..) 方法用来指定有效数位的显示位数:
var a = 42.59;
a.toPrecision( 1 ); // "4e+1"
a.toPrecision( 2 ); // "43"
a.toPrecision( 3 ); // "42.6"
a.toPrecision( 4 ); // "42.59"
a.toPrecision( 5 ); // "42.590"
a.toPrecision( 6 ); // "42.5900"
上面的方法不仅适用于数字变量,也适用于数字常量。不过对于 . 运算符需要给予特别注意,因为它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。
// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
也可以用空格来代替两个.
42 .toFixed(3); //"42.000"
但是这样的语法对数字常量而言很容易引起误会,不建议使用。
数字常量还可以用二进制、八进制和十六进制表示。(十六进制、八进制、二进制的x、o、b都不区分大小写)
0xf3; // 243的十六进制
0Xf3; // 同上
0o363; // 243的八进制
0O363; // 同上
0b11110011; // 243的二进制
0B11110011; // 同上
还有一个要注意的地方就是二进制的浮点数的问题
var a = 0.1;
var b = 0.2;
a + b === 0.3; //false
实际上在日常中应该是等于的。但是二进制浮点数中的 0.1 和 0.2 并不是十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字 0.30000000000000004,所以条件判断结果为 false。
能够被“安全”呈现的最大整数是 2^53 - 1,即 9007199254740991,在 ES6 中被定义为Number.MAX_SAFE_INTEGER。最小整数是 -9007199254740991,在 ES6 中被定义为 Number.MIN_SAFE_INTEGER。
在需要处理一些比较大的数据(例如数据库中的64位ID)时,必须将他们保存(转换)为字符串,因为JavaScript的数字类型无法精确呈现64位数值。
特殊数值
undefined和null
undefined类型只有一个值,即undefined。null类型也只有一个值,即null。他们的名称既是类型也是值。
undefined和null常被用来表示“空的”值或“不是值”的值。二者之间有一些细微的差别。例如:
- null 指空值(empty value)
- undefined 指没有值(missing value)
或者:
- undefined 指从未赋值
- null 指曾赋过值,但是目前没有值
null是一个特殊关键字,不是标识符,我们不能把它当做变量来使用和赋值。
undefined是一个标识符,可以被当做变量来使用和赋值。
NaN
如果数学运算的操作数不是数字类型(或者无法解析为常规的十进制或十六进制数字),就无法返回一个有效的数字,这种情况下返回值为 NaN。
var a = 2 / "foo"; // NaN
typeof a === "number"; // true
但是NaN仍然是数字类型。
NaN是一个有特殊用途的常规值,用于支出数字类型中的错误情况。但是NaN和自身是不相等的。
var a = 2 / "foo";
a == NaN; // false
a === NaN; // false
但是NaN !=NaN为true.
所以我们可以使用内建的全局工具函数 isNaN() 来判断一个值是否是 NaN。
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true
又出现了新的问题,明显“foo”不是数字也不是NaN,但是返回结果为true。这个 bug 自 JavaScript 问世以来就一直存 在,至今已超过 19 年。
但是从ES6开始我们可以使用工具函数Number.isNaN()
var a = 2 / "foo";
var b = "foo";
Number.isNaN( a ); // true
Number.isNaN( b ); // false
值和引用
简单值总是通过值复制的方式来赋值 / 传递,包括null、undefined、字符串、数字、布尔和 ES6 中的 symbol。例如:
var a = 2;
var b = a; // b是a的值的一个副本
b++;
a; // 2
b; // 3
复合值——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值 / 传递。例如:
var c = [1,2,3];
var d = c; // d是[1,2,3]的一个引用
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]
JavaScript 中的引用和其他语言中的引用 / 指针不同,它们不能指向别的变量 / 引用,只能指向值。