类型
function
是object
的子类型,但是函数对象function
拥有自己的属性:
var a = function(a, b) {}
console.log(a.length) // 2,参数个数
undefined
和undeclared(未声明)
typeof
处理未定义和未声明时的方式一样的。
var a;
typeof a // undefined
typeof b // undefined;
这属于typeof
的安全防范机制,在引用变量之前,有必要判断变量是否申明时很有用(避免产生ReferenceError
错误)。也可以使用windows
来实现相同的功能,但是不能兼容到非浏览器环境。
typeof func() // ReferenceError: func is not defined
typeof
一个未声明的函数时会报错。
在给代码写polyfill
时,避免实用var
来定义变量,即使if
条件不成立也是如此,因为在编译阶段,var
定义的变量已经全局声明了。
Javascript
内置的七种类型:null
、undefined
、boolean
、number
、string
、object
和symbol
。
值
数组
创建“稀疏”数组时要注意:
var arr = [];
arr[0] = 1;
arr[2] = 2;
arr.length; // 3
console.log(arr[1]) // undefined
此时的arr[1]
和显示的指定arr[1] = undefined
,是有区别的。
如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。
字符串
字符串属于类数组。和数组不相同的一点是,在JavaScript
中字符串是不可变的,其中不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符串。
字符串也有concat()
方法。
数字
特别大和特别小的数字默认用指数格式显示,与
toExponential()
函数的输出结果相同。
var a = 5E10; // 50000000000
a.toExponential(); // 5e+10
toFixed()
和toPrecision()
等方法同样适用于数字常量,但是.
运算符是一个有效的数字字符,会优先识别为数字常量,然后才是对象属性访问运算符,所以以下是无效语法:
42.toFixed(3); // SyntaxError
以下语法才有效:
(42).toFixed(3); // "42.000"
42..toFixed(3); // "0.420"
0.42.toFixed(3); // "42.000"
较小的数值
二进制浮点数(不仅JavaScript
,所有遵循IEEE 754
规范的语言都是如此)最大的问题是精度问题:
0.1 + 0.2 === 0.3; // false
解决方案是设置一个误差范围,对于JavaScript
的数字来说,这个值通常是2^-52 (2.220446049250313e-16)
。从ES6
始,该值定义在Number.EPSILON
中。
// 给不支持es6的代码写polyfills
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
// 判断两个数的差是否在误差范围内
function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON; // 相减取绝对值后和‘机器误差’比较
}
能够呈现的最大浮点数:Number.MAX_VALUE
,最小浮点数:Number.MIN_VALUE
。
整数的安全范围
能够“安全”呈现的最大整数:Number.MAX_SAFE_INTEGER
,能够“安全”呈现的最小整数Number.MIN_SAFE_INTEGER
。
最大整数小于最大浮点数,最小整数(复数)要小于最小浮点数(最小正数)。
32位有符号整数
虽然整数最大能够达到
53
位,但是有些数字操作(如数位操作)只适用于32
位数字,所以这些操作中数字的安全范围就要小很多,变成从Math.pow(-2,31)
(-2147483648
, 约-21亿
)到Math.pow(2,31) - 1
(2147483647
,约21亿
)。
特殊数值
不是值的值
undefined
和null
的名称既是类型也是值。null
是一个特殊关键字,不是标识符;而undefined
则是一个内置的标识符,在非严格模式下可以给全局标识符undefined
赋值(但是千万不要这么做)。
void
运算符,表达式void ___
没有返回值,因此返回结果就是undefined
。**void
并不改变表达式的结果,只是让表达式不返回值**。
function doSomething() {
if (!APP.ready) {
// setTimeout会返回一个标识符,会使下面的if语句误判
return void setTimeout(doSomething, 100);
}
var result;
// 其他
return result;
}
if (doSomething()) {
// 立即执行下一个任务
}
// 下面的代码也能有同样的效果
setTimeout(doSomething, 100);
return
总之,如果要将代码中的值(如表达式的返回值)设为
undefined
,就可以使用void
。这种做法并不多见,但在某些情况下却很有用。
特殊的数字
不是数字的数字
NaN
是一个“警戒值”(sentinel value
,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。它仍然是数字类型。
window.isNaN
有一个严重的缺陷,它的逻辑是——检查参数是否不是NaN
,也不是数字(即会将参数使用Number()
转换一次)。
isNaN('foo'); // true,很明显,foo并不是NaN
ES6
开始我们可以使用Number.isNaN
,弥补了这一缺陷。
可以给不兼容ES6
的代码写polyfill
:
if (!Number.isNaN) {
Number.isNaN = function(n) {
// 利用了NaN不等于自身的特性
return n !== n;
};
}
零值
有些应用程序中的数据需要以级数形式来表示(比如动画帧的移动速度),数字的符号位(
sign
)用来代表其他信息(比如移动的方向)。此时如果一个值为0
的变量失去了它的符号位,它的方向信息就会丢失。所以保留0
值的符号位可以防止这类情况发生。
0
乘以或除以一个负数会得到一个负零,加法和减法不会得到负零。
JSON
处理负零的方式很奇怪:
JSON.stringify({"a": -0, "b": 0}); // "{"a":0,"b":0}"
JSON.parse('{"a":0,"b":0}'); // {"a":0,"b":0}
JSON.parse('{"a":-0,"b":0}'); // {"a":-0,"b":0}
在工作中尤其要注意!
特殊等式
NaN
和自身不相等,而-0
等于0
,ES6
中新加入了一个工具方法Object.is()
,可以判断两个值是否绝对相等:
var a = 1 / 'foo';
var b = 0 * -3;
a === NaN; // false
Object.is(a, NaN); // true
Object.is(b, 0); // false
Object.is(b, -0); // true
值和引用
简单值(即标量基本类型值,
scalar primitive
)总是通过值复制的方式来赋值 / 传递,包括null
、undefined
、字符串、数字、布尔和ES6
中的symbol
。复合值(
compound value
)——对象(包括数组和封装对象)和函数,则总是通过引用复制的方式来赋值 / 传递。
函数参数中的值引用
function foo(x) {
x.push(4); // 3.函数中我们可以通过引用改变数组的值,因为a和x指向的是同一个内存地址
x; // 4.此时内存中的数组为[1,2,3,4]
// 然后
x = [4, 5, 6]; // 5.此时x这个变量更改了引用,这个动作不能修改a的引用,a还是指向[1, 2, 3, 4]
x.push(7); // 6.通过x这个引用修改了内存中新的数组,与[1, 2, 3, 4]一点关系都没有
x; // [4,5,6,7]
}
var a = [1, 2, 3]; // 1.内存中创建了一个数组,a引用了这个数组的地址
foo(a); // 2.传参时,实际是将引用a的一个副本赋值给x
console.log(a); // 7.是[1,2,3,4],不是[4,5,6,7]
总结:如果是对象通过参数的方式传入函数,传入的是这个对象的引用(通过函数参数变量引用),如果函数参数变量更改了引用(例如:重新给这个变量引用了另外一个对象),则之后对这个参数变量的操作将无法对传入的对象有丝毫影响。工作中特别要注意的是处理数组时,会返回新数组的方法赋值给参数变量后是无法修改传入的数组的!
我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!