常用原生函数:
String()
Number()
Boolean()
Array()
Object()
Function()
RegExp()
Date()
Error()
Symbol()
原生函数可以被当作构造函数来使用,但其构造出来的对象可能会和我们设想的有所出入:
代码语言:javascript复制var str = new String('Hello');
typeof str; // 'object' 不是 'string'
str instanceof String; // true
Object.prototype.toString.call(str); // '[object String]'
通过构造函数(如 new String("abc")
)创建出来的是封装了基本类型值(如 "Hello" )的封装对象。
# 内部属性 [[Class]]
所有 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]]
(可以把它看作一个内部的分类,而非传统的面向对象意义上的类)。这个属性无法直接访问,一般通过 Object.prototype.toString()
来查看。
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call({}); // '[object Object]'
Object.prototype.toString.call(function(){}); // '[object Function]'
Object.prototype.toString.call(/d /); // '[object RegExp]'
Object.prototype.toString.call(new Date()); // '[object Date]'
Object.prototype.toString.call(new Error()); // '[object Error]'
Object.prototype.toString.call(Symbol()); // '[object Symbol]'
多数情况下,对象的内部 [[Class]]
属性和创建该对象的内建原生构造函数相对应,但并非总是如此。
// 虽然 Null() 和 Undefined() 这样的原生构造函数并不存在
// 但是内部 [[Class]] 属性值仍然是 "Null" 和"Undefined"
Object.prototype.toString.call(null); // '[object Null]'
Object.prototype.toString.call(undefined); // '[object Undefined]'
# 封装对象包装
封装对象(object wrapper)扮演着十分重要的角色。由于基本类型值没有 .length
和 .toString()
这样的属性和方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值包装 (box 或者 wrap)一个封装对象:
var str = 'Hello';
str.length; // 5
str.toUpperCase(); // 'HELLO'
一般情况下,不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什么时候应该使用封装对象。换句话说,就是应该优先考虑使用 "abc" 和 42 这样的基本类型值,而非 new String("abc")
和 new Number(42)
。
# 需要注意的地方
为 false 创建了一个封装对象,然而该对象是真值。
代码语言:javascript复制var b = new Boolean(false);
if (!b) {
console.log('false'); // 无法执行到这里
}
如果想要自行封装基本类型值,可以使用 Object()
函数(不带 new
关键字):
var a = 'abc';
var b = new String('abc');
var c = Object('abc');
typeof a; // 'string'
typeof b; // 'object'
typeof c; // 'object'
b instanceof String; // true
c instanceof String; // true
Object.prototype.toString.call(b); // '[object String]'
Object.prototype.toString.call(c); // '[object String]'
# 拆封
如果想要得到封装对象中的基本类型值,可以使用 valueOf()
函数:
var a = new String('abc');
var b = new Number(123);
var c = new Boolean(true);
a.valueOf(); // 'abc'
b.valueOf(); // 123
c.valueOf(); // true
在需要用到封装对象中的基本类型值的地方会发生隐式拆封。
代码语言:javascript复制var a = new String('abc');
var b = a ''; // 'abc'
typeof a; // 'object'
typeof b; // 'string'
# 原生函数作为构造函数
# Array()
Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。
数组并没有预设长度这个概念,这样创建出来的只是一个空数组,只不过它的 length 属性被设置成了指定的值。
代码语言:javascript复制var a = new Array(1, 2, 3);
a; // [1, 2, 3]
var b = [1, 2, 3];
b; // [1, 2, 3]
构造函数 Array() 不要求必须带 new 关键字。不带时,它会被自动补上。
# Object()、Function() 和 RegExp()
除非万不得已,否则尽量不要使用 Object()
、Function()
、RegExp()
。
在实际情况中没有必要使用 new Object()
来创建对象,因为这样就无法像常量形式那样一次设定多个属性,而必须逐一设定。
构造函数 Function 只在极少数情况下很有用,比如动态定义函数参数和函数体的时候。不要把 Function()
当作 eval()
的替代品,基本上不会通过这种方式来定义函数。
建议使用常量形式(如 /^a*b /g
)来定义正则表达式,这样不仅语法简单,执行效率也更高,因为 JavaScript 引擎在代码执行前会对它们进行预编译和缓存。RegExp()
有时还是很有用的,比如动态定义正则表达式时:
var name = 'Kyle';
var namePattern = new RegExp('\b(?:' name ') \b', 'ig');
var matches = someText.match(namePattern);
# Date() 和 Error()
Date()
和 Error()
的用处要大很多,因为没有对应的常量形式来作为它们的替代。
创建日期对象必须使用 new Date()
。Date()
可以带参数,用来指定日期和时间,而不带参数的话则使用当前的日期和时间。
构造函数 Error()
(与 Array()
类似)带不带 new
关键字都可。
创建错误对象(error object)主要是为了获得当前运行栈的上下文(大部分 JavaScript 引擎通过只读属性 .stack
来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号,以便于调试(debug)。
# Symbol()
Symbol 是具有唯一性的特殊值(并非绝对),用它来命名对象属性不容易导致重名。该类型的引入主要源于 ES6 的一些特殊构造,此外 Symbol 也可以自行定义。
Symbol 可以用作属性名,但无论是在代码还是开发控制台中都无法查看和访问它的值,只会显示为诸如 Symbol(Symbol.create) 这样的值。
可以使用 Symbol()
原生构造函数来自定义符号。但它比较特殊,不能带 new
关键字,否则会出错:
var s = Symbol('my symbol');
s; // Symbol(my symbol)
s.toString(); // 'Symbol(my symbol)'
typeof s; // 'symbol'
var a = {};
a[s] = 'value';
Object.getOwnPropertySymbols(a); // [Symbol(my symbol)]
# 原生类型
原生构造函数有自己的 .prototype
对象,如 Array.prototype
、String.prototype
等。这些对象包含其对应子类型所特有的行为特征。
如,将字符串值封装为字符串对象之后,就能访问 String.prototype
中定义的方法。