类型和值
定义
ECMAScript 语言中所有的值都有一个对应的语言类型。 ECMAScript 语言类型包括 Undefined、 Null、 Boolean、 String、 Number 和 Object。
更精确的定义
类型是值的内部特征,它定义了值的行为,以使其区别于其他值。
string.proto === String => true
string instanceof String => false
内置类型
- null -- 空值
- undefined -- 未定义
- string -- 字符串
- number -- 数字
- object -- 对象
- boolean -- 布尔值
- symbol -- es6 符号
typeof 操作符
typeof undefined // 'undefined'
typeof string // 'string'
typeof number // 'number'
typeof object || array // 'object'
typeof boolean // 'boolean'
typeof function // 'function'
typeof symbol // 'symbol'
typeof null // 'object'
typeof String || Number || Object || Function || Boolean || Array // function
Boolean(false) => boolean false
new Boolean(false) => object true
if (Boolean(false)) {
// 不会执行里面的代码
}
if (new Boolean(false)) {
// 会执行里面的代码,此时条件为 {} => 转换为 boolean 为true
}
如何把一个值转转变为布尔类型 => !!value
值和类型
JavaScript 中的变量是没有类型的,只有值才有。变量可以随时持有任何类型的值。
换个角度来理解就是, JavaScript 不做“ 类型强制”;也就是说,语言引擎不要求变量总是
持有与其初始值同类型的值。一个变量可以现在被赋值为字符串类型值,随后又被赋值为
数字类型值。(typescirpt 让我们更写代码具备静态特性,类型声明)
在对变量执行 typeof 操作时,得到的结果并不是该变量的类型,而是该变量持有的值的类
型,因为 JavaScript 中的变量没有类型。
undefined && undeclared
undefined: 变量未持有值 => 未找到变量的值(right查询)
var a
console.log(a) // undefined
undeclared: 变量未定义 => 未找到变量(left查询)
console.log(a) // Uncaught ReferenceError: a is not defined
不合理的行为:typeof undefined === typeof undeclared // true undefined
typeof 俗称安全操作符,它的使用尽量不让程序报错,即使访问未声明的变量
number Number 相关 number 是 Number 的子类型
Boolean() // 0
Boolean(-'') // -0
Boolean('') // 0
1 / '' // Infinity
1 / -'' // -Infinity
一些判断
- 判断一个值是否是空对象或者空数组
var foo = {}
foo === {} // false 因为{} 是引用值,前面和后面的指向的不是一个地址,
JSON.stringify(foo) === '{}' // true
- 判断一个值是否为 null
function isNull(value) {
return !value && typeof value === 'object'
}
- 判断一个值是否是 NaN (非数)
// 写在 Number 调用方式 Number.isNaN()
if (!Number.isNaN) {
Number.isNaN = function (num) {
// 根据 NaN 六亲不认的特征,即 NaN 不等于自身
return num !== num
}
}
// 写在 Number 的原型上 调用方式 number.isNaN()
if (!Number.prototype.isNaN) {
Number.prototype.isNaN = function () {
return this !== this
}
}
值
数组
和其他强类型语言不同,在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串、
数字、对象( object),甚至是其他数组(多维数组就是通过这种方式来实现的)。
使用 delete 运算符可以将单元从数组中删除,但是请注意,单元删除后,数
组的 length 属性并不会发生变化。
稀疏数组
var arr = []
arr.length = 5 // [empty x 5]
arr[1] // undefined
arr = [1, 2, 3]
delete arr[1] // [1, empty, 3]
arr[1] // undefined
数组通过数字进行索引,但有趣的是它们也是对象,所以也可以包含字符串键值和属性,如果字符串键值能够被强制类型转换为十进制数字的话,它
就会被当作数字索引来处理。
arr = []
arr[10] = 10 // [empty x 10, 10]
不要这样做。
类数组 -- 通过类名选择的 Dom 集合; jquery() 返回的值集合;函数的实参列表 arguments
类数组转化为数组方法
- 借用 Array.prototype.slice
let arrayLike
let array = Array.prototype.slice.call(arrayLike)
- ES6 Array.from()
let obj = {
a: 'a',
b: 'b'
}
let arr = Array.from(obj) // []
字符串
字符串和数组的确很相似,它们都是类数组,都有 length 属性以及 indexOf(..)(从 ES5
开始数组支持此方法)和 concat(..) 方法。
注:不管是数组,还是字符串,concat() 都不会改变原来的数组,而是返回一个新数组
JavaScript 中字符串是不可变的,而数组是可变的。并且 a[1] 在 JavaScript 中并非总是合
法语法,在老版本的 IE 中就不被允许(现在可以了)。 正确的方法应该是 a.charAt(1)。
var foo = 'foo'
foo[1] // o
foo.charAt(1) // o
字符串不可变是指字符串的成员函数不会改变其原始值,而是创建并返回一个新的字符
串。而数组的成员函数都是在其原始值上进行操作。
// 转化为大写形式
var lowerCase = 'hello world'
lowerCase.toUpperCase() // 'HELLO WORLD',不会改变原字符串,需要重新定义一个变量接收
许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”数
组的非变更方法来处理字符串:
// join map
Array.prototype.join.call()
Array.prototype.map.call(, function (ele, index) {})
问题:如何实现字符串的翻转?
数字 number => Number
JavaScript 只有一种数值类型: number(数字),包括“整数”和带小数的十进制数。
数字语法:
- JavaScript 中的数字常量一般用十进制标识
- 小数点前面的 0 可以省略 (css同样可以省略,可以有效减少文件体积)
- 小数点后面的 0 可以省略
一些方法:
- toFixed() -- 采取四舍五入的规则,保留几位小数(不改变原数值,返回一个新值)
var num = 42.45
num.toFixed(1) // 42.5
var num = 42
num.toFixed(5) // 42.00000 => 无效的语法
- Number.isInteger() -- 判断一个数是否是整数,可以传入其他类型的值,返回 false
Number.isInteger(43) // true
Number.isInteger('') // false
// polyfill
if (Number.isInteger) {
Number.isInteger = function (num) {
// 如果使用 typescript 启用类型检查就不要作前面的 typeof 判断
return typeof num === 'number' && num % 1 === 0
}
}
特殊数值
undefined && null
undefined 类型只有一个值,即 undefined。 null 类型也只有一个值,即 null。它们的名
称既是类型也是值。
null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而
undefined 却是一个标识符,可以被当作变量来使用和赋值。
区别:
- null 指空值( empty value),undefined 指没有值( missing value)
- null 指曾赋过值,但是目前没有值,undefined 指从未赋值
- 非严格模式下,可以把 undefined 当做标识符一样使用
undefined = true // 不要这样做
'use strict'
undefined = true // TypeError
null === 0 // false
null > 0 // false
null < 0 // false
undefined === 0 // false
undefined > 0 // false
undefined < 0 // false
// 似乎发现了什么不得了的事...
null == undefined // true
// but
null === undefined // false
void 运算符 -- 后面接任何值,返回 undefined
void 1 // undefined
void {} // undefined
void [] // undefined
void 1+1 // NaN 为什么?
void '1'+'2' // 'undefined2' 这就是原因!
特殊的数字
不是数字的数字 NaN
NaN 是一个“警戒值”( sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果”。
- window.isNaN
window.isNaN(NaN) // true
window.isNaN(1/{}) // true
window.isNaN({}) // true
window.isNaN([]) // true
// 缺陷,不管是 NaN 还是除数值以外的类型,都返回 true
更准确的说法:isNaN() 检查参数是否不是 NaN,也不是数字
- ES6 Number.isNaN() -- 判断是否是 NaN
Number.isNaN(NaN) // true
Number.isNaN(1/{}) // true
Number.isNaN({}) // false
Number.isNaN([]) // false
// polyfill
if (Number.isNaN) {
// 配合 typeof 食用
Number.isNaN = function (value) {
return typeof value === 'number' && window.isNaN(value)
}
// NaN 不等于自身
Number.isNaN = function (value) {
return value !== value
}
}
无穷数 Infinity
1/0 // Infinity
-1/0 // -Infinity
Infinity / -Infinity // NaN
零值 0 && -0
0 === -0 // true
特殊的等式 ===
ES6 Object.is(a, b) => 判断两个值是否绝对相等
Object.is(0, -0) // false
// polyfill
if (!Object.is) {
Object.is = function (left, right) {
if (left === 0){
if (right === -0) {
return false
}
} else if (left === -0) {
if (right === 0) {
return false
}
}
}
return left === right
}
注:能使用 == 和 === 时就尽量不要使用 Object.is(..),因为前者效率更高、
更为通用。 Object.is(..) 主要用来处理那些特殊的相等比较。
值和引用
在许多编程语言中,赋值和参数传递可以通过值复制(value-copy)或者引用复制(reference-copy)来完成。
JavaScript 中没有指针,引用的工作机制也不尽相同。在 JavaScript 中变量不可能成为指向
另一个变量的引用。
JavaScript 引用指向的是值。如果一个值有 10 个引用,这些引用指向的都是同一个值, 它
们相互之间没有引用 / 指向关系。
JavaScript 对值和引用的赋值 / 传递在语法上没有区别,完全根据值的类型来决定。
- 简单值(即标量基本类型值, scalar primitive) 总是通过值复制的方式来赋值 / 传递,包括null、 undefined、字符串、数字、布尔和 ES6 中的 symbol。
var a = 2
var b = a
b ++
a // 2
b // 3
var a = [1, 2, 3, 4]
var b = a
b.push(5)
/**
* a和b只是指向值[...],并非持有该值
* a和b通过地址指向值
* 赋值的过程仅仅是引用地址的复制,而不是真实值的复制
*/
a // [1, 2, 3, 4, 5]
b // [1, 2, 3, 4, 5]
- 复合值( compound value)——对象(包括数组和封装对象,参见第 3 章)和函数,则总是通过引用复制的方式来赋值 / 传递。
基本类型的值,变量指向的是具体的值;引用类型的值,变量指向的是该值的地址,赋值给其他变量的时候,只是复制了地址过去,其他变量依然通过地址找到值,操作值的话也是通过地址找到这个值,所以操作的还是存储的值。
特殊情况
var a = [1, 2]
// 通过引用地址指向 [1, 2]
var b = a
// 给 b 重新赋值,不会对 a 造成影响
// 由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向?
b = [2, 3]
a // [1, 2]
b // [2, 3]
我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。