常见的原生函数有:
String()Number()Boolean()Array()Object()Function()RegExp()Date()Error()Symbol()
原生函数可以被当作构造函数来使用,但其构造出来的对象可能会和我们设想的有所出入:
var a = new String()
type of a // "object"
a instanceof String // true
Object.prototype.toString.call(a) // "[object String]"
console.log(a) // String { 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc" }
通过构造函数创建出来的是封装了基本类型值的封装函数
内部属性 [[Class]]
所有 typeof 返回值为 object的对象(如数组)都包含一个内部属性 [[Classs]]
我们可以把它看作一个内部的分类,该属性无法直接访问,通常通过 Object.prototype.toString(..)来查看
Object.prototype.toString.call([1, 2, 3]) // "[object Array]"
Object.prototype.toString.call(/regex-literal/i) // "[object RegExp]"
多数情况下,对象的内部 [[Class]] 属性和创建该对象的内建原生构造函数相对性,但并非总是如此
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
虽然 Null() 和 Undefined() 正阳的原生构造函数不存在,但是其内部 [[Class]] 属性值仍是 ”Null“ 和 ”Undefined“
其他基本类型值得情况有所不同,通常成为 ”包装“:
Object.prototype.toString.call("abc") // "[[object, String]]"
Object.prototype.toString.call(42) // "[[object, Number]]"
Object.prototype.toString.call(true) // "[[object, Boolean]]"
很显然, 基本类型值被各自的封装对象自动包装,所以它们的内部 [[Class]] 属性值分别为对应的内建原生构造函数
封装对象包装
封装对象扮演者十分重要的角色,由于基本类型值没有 .length 和 .toString 这样的属性方法,需要通过封装对象才能访问,此时 JavaScript 会自动为基本类型值包装一个封装对象
var a = "abc"
a.length // 3
a.toUpperCase() // "ABC"
如果需要经常用到这些字符串属性和方法,比如在for循环中使用i < a.length,那么从 一开始就创建一个封装对象也许更为方便,这样 JavaScript 引擎就不用每次都自动创建了
但实际证明这并不是一个好办法,因为浏览器已经为 .length 这样的常见情况做了性能优 化,直接使用封装对象来“提前优化”代码反而会降低执行效率
一般情况下,我们不需要直接使用封装对象。最好的办法是让 JavaScript 引擎自己决定什 么时候应该使用封装对象。换句话说,就是应该优先考虑使用 "abc" 和 42 这样的基本类型 值,而非 new String("abc") 和 new Number(42)
注意
var a = new Boolean(false)
if (!a) {
console.log('handsome boy') // 执行不到这里
}
这里为 false 创建了一个封装对象,显然该对象是真值
如果想要自行封装基本类型值,可以使用 Object(...) 函数
var a = "abc"
var b = new String(a)
var c = Object(a)
typeof a // "string"
typeof b // "obejct"
typeof c // "object"
b instanceof String // true
c instanceof String // true
Obejct.prototype.toString.call(b) // "[object, String]"
Object.prototype.toString.call(c) // "[object, String]"
一般不推荐直接使用封装对象,但是它们偶尔也会派上用场
拆封
如果想要得到封装对象中的基本类型值,可以使用 valueOf()函数:
var a = new String("abc")
var b = new Number(42)
var c = new Boolean(true)
a.valueOf() // "abc"
b.valueOf() // 42
c.valueOf() // true
在需要用到封装对象中的节本类型值得地方会发生隐式拆封
var a = new String("abc")
var b = a + "" // b的值变为 "abc"
typeof a // "obejct"
typeof b // "string"
原生函数作为构造函数
关于 array、object、function 和正则表达式, 我们通常喜欢以常量的形式来创建它们,实际上,使用常量和使用构造函数的效果是一样的———创建的值都是通过封装函数对象来包装
Array(..)
var a = new Array(1, 2, 3)
a // [1, 2, 3]
var b = [1, 2, 3]
b // [1, 2, 3]
构造函数
Array只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素
var a = new Array(2)
a // []
a.length // 2
属实耐人寻味,这样奇特的数据结构会导致一些怪异行为,而一切都归咎于已被废止的就特性(类似于 arguments 这样的类数组)
我们将包含至少一个 ”空单元“ 的数组称为 ”稀疏数组“
var a = new Array(3)
var b = [undefined, undefined, undefined]
var c = []
c.length = 3
我们可以创建包含空单元的数组,如上例中的 c,只要将
length属性设置为超过实际单元数的值,就能隐式地制造出空单元,另外还可以通过delete b[1]在数组 b 中制造出一个空单元
var a = new Array(3)
var b = [undefined, undefined, undefined]
var c = []
c.length = 3
a.join("-") // "- -"
b.join("-") // "- -"
a.map((v, i) => {
return i
})
// [ undefined x 3 ]
b.map((v, i) => {
return i
})
// [0, 1, 2]
join(...)首先假定数组不为空,然后通过length属性值来遍历其中的元素。而map(...)并不做这样的假定
我们可以通过以下方式创建包含 undefined 单元(而非 ”空单元“)的数组:
var a = Array.apply(null, { length: 3 })
a // [ undefined, undefined, undefined ]
虽然 Array.apply(null, { length: 3 }) 在创建 undefined 值得数组时有些奇怪和繁琐,但是其结果远比 Array(3) 更准确可靠
总之,永远不要创建和使用空单元数组
Object(..)、Function(..) 和 RegExp(..)
除非万不得已,否则尽量不要使用
Object(..)/Function(..)/ RegExp(..)
var c = new Object()
c.foo = "bar"
c // { foo: "bar" }
var d = { foo: "bar" }
d // { foo: "bar" }
var e = new Function("a", "return a * 2")
var f = function(a) {
return a * 2
}
function g(a) {
return a * 2
}
var h = new RegExp("^a*b+", "g")
var i = /^a*b+/g
在实际情况中没有必要使用 new Object() 来创建对象,因为这样就无法像常量形式那样一次设定多个属性,而必须逐一设定
构造函数 Function 只在极少数情况下很有用,比如动态定义函数参数和函数体的时候
强烈建议使用常量形式 (如/^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(...) 的用处要大很多,因为没有对应的常量形式来作为它们的替代
Date(...)主要用来获得当前的 Unix 时间戳,该值可以通过日期对象中的getTime()来获得, ES5 静态函数Date.now()也可以
如果调用 Date() 时不带 new 关键字,则会得到当前日期的字符串值
构造函数 Error(...) 带不带 new 关键字都可
创建错误对象,主要是为了获得当前运行栈的上下文,大部分 JavaScript 引擎通过只读属性
.stack来访问,栈上下文信息包括函数调用栈信息和产生错误的代码行号,以便于 debug
function foo (x) {
if(!x){
throw new Error("x wasn't provided")
}
}
通常错误对象至少包含一个 message 属性,有时也不乏其他属性(必须作为只读属性访问),如 type。除了访问 stack 属性外,最好的办法是调用 toString() 来获得经过格式化的便于阅读的错误信息
除了 Error(...) 之外,还有一些针对特定错误类型的原生构造函数,如 EvalError(...) 、 RangeError(...) 、 ReferenceError(...) 、 SyntaxError(...) 、 TypeError(...) 、 URIError(...) , 这些构造函数很少直接被使用,它们在程序发生异常的时候会被自动调用
Symbol(...)
ES6 中新加了一个基本数据类型 ———— 符号(Symbol)
符号是具有唯一性的特殊值(并非绝对),用它来命名对象属性不容易导致重名
符号可以用作属性名,但无论是在代码还是开发控制台中都无法查看和访问它的值,只会显示诸如 Symbol(Symbol.create) 这样的值
我们可以使用 Symbol(..) 原生构造函数来自定义符号
var mysym = Symbol("my own symbol")
mysym // "Symbol(my own symbol)"
mysym.toString() // "Symbol(my own symbol)"
typeof mysym // "symbol"
var a = {}
a[mysym] = "foobar"
Object.getOwnPropertySymbols(a) // [Symbol(my own symbol)]
虽然符号实际上并非私有属性(通过 Object.getOwnPropertySymbols(..)便可以公开获得对象中的所有符号),但它主要用于私有或特殊属性
符号并非对象,而是一种简单标量基本类型
原生原型
原生构造函数有自己的 .prototype 对象,如 Array.prototype、String.prototype 等
这些对象包含其对应子类型所特有的行为特征
例如,将字符串值封装为字符串对象之后,就能访问 String.prototype 中定义的方法
-
String.prototype.indexOf(...)在字符串中找到指定子字符串的位置
-
String.prototype.charAt(...)获得字符串指定位置的字符
-
String.prototype.substr(...)、String.prototype.substring(...)、String.prototype.slice(...)获得字符串指定部分
-
String.ptototype.toUpperCase()和String.prototype.toLowerCase()将字符串转换大小写
-
String.prototype.trim()去掉字符串前后的空格,返回新的字符串
以上方法并不改变原字符串的值,而是返回一个新字符串
其他构造函数的原型包含它们各自类型所特有的行为特征,比如 Number.prototype.toFixed(..) 和 Array.protptype.concat(...),所有你的函数都可以调用 Function.prototype 中的 apply(...)、call(...) 和 bind(...)
然而,有些原生函数并非普通对象那么简单
typeof Function.prototype // "function"
Function.prototype() // “空函数”
RegExp.prototype.toString() "/(?:)/" ———— 空正则表达式
"abc".match(RegExp.prototype) // [""]
更糟糕的是,我们甚至可以修改它们
Array.isArray(Array.prototype) // true
Array.prototype.push(1, 2, 3) // 3
Array.prototype // [1, 2, 3]
// 需要将 Array.prototype 设置回空,否则会导致问题
Array.prototype.length = 0
总结
JavaScript 为基本数据类型值提供了封装对象,称为原生函数(如
String、Number、Boolean等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如String.prototype.trim()和Array.prototype.cancat(...))
对简单标量基本类型值,比如 “abc”, 如果要访问它的
length属性 或String.prototype方法, JavaScript 引擎会自动对该值进行封装来实现对这些属性和方法的访问