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)
Objectfoo="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
123JavaScript有两个“非值”表示缺少信息,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 undefinedundefined 有时也被用作表示不存在的元值。相反,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===void0)// 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包装对象有一个用例:您想向基本类型值添加属性。然后,您可以包装基本类型并将属性添加到包装对象。在使用该值之前,您需要将其解包。
通过调用包装器构造函数来包装基本类型
newBoolean(true)newNumber(123)newString('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.charAttrue
松散模式和严格模式以不同的方式处理这种借用。在松散模式下,基本类型会动态转换为包装器
String.prototype.sloppyMethod=function(){console.log(typeofthis);// objectconsole.log(thisinstanceofString);// true};''.sloppyMethod();// call the above method
在严格模式下,将透明地使用包装器原型中的方法
String.prototype.strictMethod=function(){'use strict';console.log(typeofthis);// stringconsole.log(thisinstanceofString);// false};''.strictMethod();// call the above method
类型强制转换是指将一种类型的值隐式转换为另一种类型的值。大多数 JavaScript 运算符、函数和方法都会将操作数和参数强制转换为它们所需的类型。例如,乘法运算符 (*) 的操作数会被强制转换为数字:
> '3' * '4' 12
再举一个例子,如果其中一个操作数是字符串,则加号运算符 (+) 会将另一个操作数转换为字符串
> 3 + ' times' '3 times'
因此,JavaScript 很少会抱怨值的类型错误。例如,程序通常会将用户输入(来自在线表单或 GUI 小窗口)接收为字符串,即使用户输入的是数字也是如此。如果您将数字字符串视为数字,则不会收到警告,只会得到意外的结果。例如
varformData={width:'100'};// You think formData.width is a number// and get unexpected resultsvarw=formData.width;varouter=w+20;// You expect outer to be 120, but it’s notconsole.log(outer===120);// falseconsole.log(outer==='10020');// true
在上述情况下,您应该尽早转换为适当的类型
varw=Number(formData.width);
Boolean()(请参阅转换为布尔值)将值转换为布尔值。以下值将转换为 false;它们是所谓的“假值”:
undefined、nullfalse
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