JavaScript 拥有我们期望编程语言拥有的绝大多数值:布尔值、数字、字符串、数组等等。JavaScript 中的所有正常值都具有属性。[9] 每个属性都有一个键(或名称)和一个值。您可以将属性视为记录的字段。您可以使用点 (.
) 运算符来访问属性:
> var obj = {}; // create an empty object > obj.foo = 123; // write property 123 > obj.foo // read property 123 > 'abc'.toUpperCase() // call method 'ABC'
根据ECMAScript 语言规范第 8 章的规定,JavaScript只有六种类型
ECMAScript 语言类型对应于 ECMAScript 程序员使用 ECMAScript 语言直接操作的值。ECMAScript 语言类型是
- Undefined、Null
- Boolean、String、Number 和
- Object
因此,从技术上讲,构造函数不会引入新的类型,即使它们被认为具有实例。
在静态类型语言中,变量、参数和对象成员(JavaScript 将其称为属性)的类型是编译器在编译时知道的。编译器可以使用该信息执行类型检查和优化编译后的代码。
即使在静态类型语言中,变量也有一个动态类型,即变量在运行时给定时间点的值的类型。动态类型可能与静态类型不同。例如(Java)
Object
foo
=
"abc"
;
foo
的静态类型是 Object
;其动态类型是 String
。
JavaScript 是动态类型的;变量的类型通常在编译时是未知的。
JavaScript 执行一种非常有限的动态类型检查
> var foo = null; > foo.prop TypeError: Cannot read property 'prop' of null
然而,大多数情况下,事情都会默默地失败或成功。例如,如果您访问一个不存在的属性,您将获得值 undefined
> var bar = {}; > bar.prop undefined
在 JavaScript 中,处理类型不匹配的值的主要方法是将其强制转换为正确的类型。强制转换意味着隐式类型转换。 大多数操作数都会强制转换:
> '3' * '4' 12
JavaScript 的内置转换机制仅支持 Boolean
、Number
、String
和 Object
类型。没有标准的方法将一个构造函数的实例转换为另一个构造函数的实例。
术语强类型和弱类型通常没有意义明确的定义。它们被使用,但通常使用不当。最好使用静态类型、静态类型检查等。
JavaScript对值进行了某种程度上的任意区分:
null
和 undefined
。两者之间的一个主要区别在于它们的比较方式;每个对象都有唯一的标识,并且只(严格)等于自身
> var obj1 = {}; // an empty object > var obj2 = {}; // another empty object > obj1 === obj2 false > var obj3 = obj1; > obj3 === obj1 true
相反,所有编码相同值的原始值都被认为是相同的
> var prim1 = 123; > var prim2 = 123; > prim1 === prim2 true
以下两节将更详细地解释原始值和对象。
以下是所有原始值(简称基元):
true
、false
(参见第 10 章)1736
、1.351
(参见第 11 章)'abc'
、"abc"
(参见第 12 章)undefined
、null
(参见undefined 和 null)基元具有以下特征:
比较“内容”
> 3 === 3 true > 'abc' === 'abc' true
> var str = 'abc'; > str.length = 1; // try to change property `length` > str.length // ⇒ no effect 3 > str.foo = 3; // try to create property `foo` > str.foo // ⇒ no effect, unknown property undefined
(读取未知属性始终返回 undefined
。)
所有非原始值都是对象。最常见的对象类型是:
普通对象(构造函数 Object
)可以通过对象字面量创建(参见第 17 章)
{
firstName
:
'Jane'
,
lastName
:
'Doe'
}
数组(构造函数 Array
)可以通过数组字面量创建(参见第 18 章)
[
'apple'
,
'banana'
,
'cherry'
]
前面的数组有三个元素,可以通过数字索引访问。例如,'apple'
的索引是 0。
正则表达式(构造函数 RegExp
)可以通过正则表达式字面量创建(参见第 19 章)
/^a+b+$/
对象具有以下特征
比较标识;每个对象都有自己的标识:
> ({} === {}) // two different empty objects false > var obj1 = {}; > var obj2 = obj1; > obj1 === obj2 true
您通常可以自由地更改、添加和删除属性(参见点运算符 (.):通过固定键访问属性)
> var obj = {}; > obj.foo = 123; // add property `foo` > obj.foo 123
JavaScript有两个“非值”表示缺少信息,undefined
和 null
:
undefined
表示“无值”(既不是原始值也不是对象)。未初始化的变量、缺少的参数和缺少的属性都具有该非值。如果未显式返回任何内容,则函数会隐式返回它。null
表示“无对象”。它用作预期对象的非值(作为参数、对象链中的成员等)。undefined
和 null
是唯一会导致任何类型的属性访问引发异常的值
> function returnFoo(x) { return x.foo } > returnFoo(true) undefined > returnFoo(0) undefined > returnFoo(null) TypeError: Cannot read property 'foo' of null > returnFoo(undefined) TypeError: Cannot read property 'foo' of undefined
undefined
有时也被用作表示不存在的元值。相反,null
表示空。例如,JSON 节点访问器(参见通过节点访问器转换数据)返回
undefined
以删除对象属性或数组元素null
以将属性或元素设置为 null
在这里,我们回顾了 undefined
和 null
出现的各种情况。
未初始化的变量是 undefined
:
> var foo; > foo undefined
> function f(x) { return x } > f() undefined
如果您读取一个不存在的属性,您将获得 undefined
:
> var obj = {}; // empty object > obj.foo undefined
如果未显式返回任何内容,则函数会隐式返回 undefined
:
> function f() {} > f() undefined > function g() { return; } > g() undefined
null
是原型链(对象链;参见第 2 层:对象之间的原型关系)中的最后一个元素
> Object.getPrototypeOf(Object.prototype) null
如果字符串中没有与正则表达式匹配的内容,则 RegExp.prototype.exec()
返回 null
> /x/.exec('aaa') null
在以下部分中,我们将回顾如何分别检查 undefined
和 null
,或检查两者是否存在。
严格相等 (===
) 是检查 undefined
的规范方法:
if
(
x
===
undefined
)
...
您还可以通过 typeof
运算符检查 undefined
(typeof:对基元进行分类),但通常应使用上述方法。
大多数函数允许您通过 undefined
或 null
指示缺少的值。检查这两者的一种方法是通过显式比较:
// Does x have a value?
if
(
x
!==
undefined
&&
x
!==
null
)
{
...
}
// Is x a non-value?
if
(
x
===
undefined
||
x
===
null
)
{
...
}
另一种方法是利用 undefined
和 null
都被视为 false
的事实(参见真值和假值)
// Does x have a value (is it truthy)?
if
(
x
)
{
...
}
// Is x falsy?
if
(
!
x
)
{
...
}
false
、0
、NaN
和 ''
也被视为 false
。
单个非值可以同时扮演 undefined
和 null
的角色。为什么 JavaScript 有两个这样的值?原因是历史原因。
JavaScript 采用了 Java 将值划分为基元和对象的方法。它还使用了 Java 的“非对象”值 null
。按照 C(但不是 Java)的先例,如果强制转换为数字,则 null
将变为 0:
> Number(null) 0 > 5 + null 5
请记住,第一个版本的 JavaScript 没有异常处理。因此,必须通过值来指示异常情况,例如未初始化的变量和缺少的属性。null
会是一个不错的选择,但 Brendan Eich 在当时想避免两件事
因此,Eich 将 undefined
作为另一个非值添加到语言中。它会强制转换为 NaN
> Number(undefined) NaN > 5 + undefined NaN
undefined
是全局对象(因此也是全局变量;请参阅全局对象)的属性。在 ECMAScript 3 下,读取 undefined
时必须采取预防措施,因为它很容易意外更改其值。在 ECMAScript 5 下,这不是必需的,因为 undefined
是只读的。
为了防止 undefined
被更改,有两种流行的技术(它们仍然与旧的 JavaScript 引擎相关)
隐藏全局 undefined
(它可能具有错误的值)
(
function
(
undefined
)
{
if
(
x
===
undefined
)
...
// safe now
}());
// don’t hand in a parameter
在前面的代码中,undefined
保证具有正确的值,因为它是一个参数,函数调用没有提供其值。
与 void 0
进行比较,它始终是(正确的)undefined
(请参阅void 运算符)
if
(
x
===
void
0
)
// always safe
三种基本类型 boolean、number 和 string 具有相应的构造函数:Boolean
、Number
、String
。它们的实例(所谓的包装对象)包含(包装)基本类型值。构造函数可以通过两种方式使用:
作为构造函数,它们创建的对象与其包装的基本类型值在很大程度上不兼容
> typeof new String('abc') 'object' > new String('abc') === 'abc' false
作为函数,它们将值转换为相应的基元类型(请参阅转换为 Boolean、Number、String 和 Object 的函数)。这是推荐的转换方法
> String(123) '123'
避免使用包装对象被认为是一种最佳实践。您通常不需要它们,因为对象可以做的任何事情,基本类型都可以做到(除了可以被修改之外)。(这与 Java 不同,JavaScript 从 Java 继承了基本类型和对象之间的区别!)
基本类型值(例如 'abc'
)与包装器实例(例如 new String('abc')
)根本不同:
> typeof 'abc' // a primitive value 'string' > typeof new String('abc') // an object 'object' > 'abc' instanceof String // never true for primitives false > 'abc' === new String('abc') false
包装器实例是对象,并且在 JavaScript 中无法比较对象,即使通过宽松的相等运算符 ==
也不行(请参阅相等运算符:=== 与 ==)
> var a = new String('abc'); > var b = new String('abc'); > a == b false
包装对象有一个用例:您想向基本类型值添加属性。然后,您可以包装基本类型并将属性添加到包装对象。在使用该值之前,您需要将其解包。
通过调用包装器构造函数来包装基本类型
new
Boolean
(
true
)
new
Number
(
123
)
new
String
(
'abc'
)
通过调用方法 valueOf()
来解包基本类型。所有对象都有此方法(如转换为基本类型中所述)
> new Boolean(true).valueOf() true > new Number(123).valueOf() 123 > new String('abc').valueOf() 'abc'
将包装对象正确转换为基本类型会提取数字和字符串,但不会提取布尔值:
> Boolean(new Boolean(false)) // does not unwrap true > Number(new Number(123)) // unwraps 123 > String(new String('abc')) // unwraps 'abc'
原因在转换为布尔值中进行了说明。
基本类型没有自己的方法,而是从包装器借用方法
>
'abc'
.
charAt
===
String
.
prototype
.
charAt
true
松散模式和严格模式以不同的方式处理这种借用。在松散模式下,基本类型会动态转换为包装器
String
.
prototype
.
sloppyMethod
=
function
()
{
console
.
log
(
typeof
this
);
// object
console
.
log
(
this
instanceof
String
);
// true
};
''
.
sloppyMethod
();
// call the above method
在严格模式下,将透明地使用包装器原型中的方法
String
.
prototype
.
strictMethod
=
function
()
{
'use strict'
;
console
.
log
(
typeof
this
);
// string
console
.
log
(
this
instanceof
String
);
// false
};
''
.
strictMethod
();
// call the above method
类型强制转换是指将一种类型的值隐式转换为另一种类型的值。大多数 JavaScript 运算符、函数和方法都会将操作数和参数强制转换为它们所需的类型。例如,乘法运算符 (*
) 的操作数会被强制转换为数字:
> '3' * '4' 12
再举一个例子,如果其中一个操作数是字符串,则加号运算符 (+
) 会将另一个操作数转换为字符串
> 3 + ' times' '3 times'
因此,JavaScript 很少会抱怨值的类型错误。例如,程序通常会将用户输入(来自在线表单或 GUI 小窗口)接收为字符串,即使用户输入的是数字也是如此。如果您将数字字符串视为数字,则不会收到警告,只会得到意外的结果。例如
var
formData
=
{
width
:
'100'
};
// You think formData.width is a number
// and get unexpected results
var
w
=
formData
.
width
;
var
outer
=
w
+
20
;
// You expect outer to be 120, but it’s not
console
.
log
(
outer
===
120
);
// false
console
.
log
(
outer
===
'10020'
);
// true
在上述情况下,您应该尽早转换为适当的类型
var
w
=
Number
(
formData
.
width
);
Boolean()
(请参阅转换为布尔值)将值转换为布尔值。以下值将转换为 false
;它们是所谓的“假值”:
undefined
、null
false
0
、NaN
''
所有其他值都被视为“真值”并转换为 true
(包括所有对象!)。
Number()
(请参阅转换为数字)undefined
变为 NaN
。null
变为 0
。false
变为 0
,true
变为 1
。String()
(请参阅转换为字符串)> String(null) 'null' > String(123.45) '123.45' > String(false) 'false'
Object()
(请参阅将任何值转换为对象)将对象转换为自身,将 undefined
和 null
转换为空对象,并将基本类型转换为包装的基本类型。例如:
> var obj = { foo: 123 }; > Object(obj) === obj true > Object(undefined) {} > Object('abc') instanceof String true
请注意,Boolean()
、Number()
、String()
和 Object()
是作为函数调用的。您通常不会将它们用作构造函数。然后,它们会创建自身的实例(请参阅基本类型的包装对象)。
要将值转换为数字或字符串,首先将其转换为任意基本类型值,然后将其转换为最终类型(如转换为 Boolean、Number、String 和 Object 的函数中所述)。
ECMAScript 规范有一个内部函数 ToPrimitive()
(无法从 JavaScript 访问),它执行此转换。了解 ToPrimitive()
使您能够配置如何将对象转换为数字和字符串。它具有以下签名
ToPrimitive
(
input
,
PreferredType
?
)
可选参数 PreferredType
指示转换的最终类型:它是 Number
或 String
,具体取决于 ToPrimitive()
的结果是转换为数字还是字符串。
如果 PreferredType
是 Number
,则执行以下步骤
input
是基本类型,则返回它(无需执行任何操作)。input
是一个对象。调用 input.valueOf()
。如果结果是基本类型,则返回它。input.toString()
。如果结果是基本类型,则返回它。TypeError
(指示无法将 input
转换为基本类型)。如果 PreferredType
是 String
,则步骤 2 和 3 将交换。也可以省略 PreferredType
;然后,对于日期,它被视为 String
,对于所有其他值,它被视为 Number
。这就是运算符 +
和 ==
调用 ToPrimitive()
的方式。
valueOf()
的默认实现返回 this
,而 toString()
的默认实现返回类型信息:
> var empty = {}; > empty.valueOf() === empty true > empty.toString() '[object Object]'
因此,Number()
会跳过 valueOf()
并将 toString()
的结果转换为数字;也就是说,它会将 '[object Object]'
转换为 NaN
> Number({}) NaN
以下对象自定义了 valueOf()
,这会影响 Number()
,但不会更改 String()
的任何内容
> var n = { valueOf: function () { return 123 } }; > Number(n) 123 > String(n) '[object Object]'
以下对象自定义了 toString()
。因为结果可以转换为数字,所以 Number()
可以返回一个数字
> var s = { toString: function () { return '7'; } }; > String(s) '7' > Number(s) 7