原生函数可以被当做构造函数来使用,但通过构造函数创建出来的是封装了基本类型的封装对象
var a = new String("abc");typeof a; // "object"a instanceof String; // trueObejct.prototype.toString.call(a); // "[object String]"复制代码
可以这样来查看封装对象:
console.log(a);复制代码
由于不同的浏览器在开发控制台中显示对象的方式不同(对象序列化),所以结果也不同。
chrome 显示:String{0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"};老版本:String{0: "a", 1: "b", 2: "c"}。最新版本的firefox: String["a","b","c"];老版本:"abc",并且可以点击打开对象查看器。输出结果随浏览器变换而变化。
注:new String("abc")创建的是字符串"abc"的封装对象,而非基本类型值"abc"。
一、内部属性[[Class]]
所有typeof返回值为“object”对象(如数组)都包含一个内部属性[[Class]]。这个属性无法直接访问,一般通过Object.prototype.toString(..)来查看
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"复制代码
上例中,数组的内部[[Class]]属性值是"Array",正则表达式的值是"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]]属性值分别为"String"、"Number"、"Boolean"
二、封装对象包装
由于基本类型值没有.length和.toString()这样的属性和方法,需要通过封装对象才能访问,此时js会自动为基本类型值包装(box或wrap)一个封装对象:
var a = "abc";a.length; // 3a.toUpperCase(); // "ABC"复制代码
- 封装对象释疑 使用封装对象时有些地方需要特别注意
var a = new Boolean(false);if(!a) { console.log("Oops"); // 执行不到这里}复制代码
我们为false创建了一个封装对象,然而该对象是真值,所以这里使用封装对象得到的结果和使用false截然相反。
如果想要自行封装基本类型值,可以使用Object(..)函数(不带new关键字)
var a = "abc";var b = new String(a);var c = Object(a);typeof a; // "string"typeof b; // "object"typeof c; // "object"b instanceof String; // truec instanceof String; // trueObejct.prototype.toString.call(b); // "[object String]"Obejct.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(); // 42c.valueOf(); // true复制代码
在需要用到封装对象的基本类型值的地方会发生隐式拆封
var a = new String("abc");var b = a+ ""; // b的值为"abc"typeof a; // "object"typeof b; // "string"复制代码
四、原生函数作为构造函数
关于数组、对象、函数和正则表达式,我们通常喜欢以常量的形式来创建它们。实际上,使用常量和使用构造函数的效果是一样的(创建的值都是通过封装对象来包装)
如前所述,应该尽量避免使用构造函数,除非十分必要,因为它们经常产生意想不到的结果。
1、Array(..)
var a = new Array(1,2,3);a; // [1,2,3]var b = [1,2,3];b;// [1,2,3]复制代码
构造函数Array(..)不要求必须带new关键字。不带时,会被自动补上。因此Array(1,2,3)和new Array(1,2,3)效果是一样的
var a = new Array(3);a.length; // 3a;复制代码
a 在Chrome中显示为[undefined x 3],这意味着它有三个值为undefined的单元,但实际上单元并不存在
var a = new Array(3);var b = [undefined, undefined, undefined];var c = [];c.length = 3;a; b;c;复制代码
我们可以创建包含空单元的数组,如c。只要将length属性设置为超过实际单元数的值,就能隐式的制造出空单元。另外还可以通过delete b[1]在数组b中制造出一个空单元
b在当前版本的Chrome中显示为[undefined, undefined, undefined],而a和c则显示为[undefined x 3],firefox中a,c显示为[,,,]。这其中有三个逗号代表四个空单元,而不是三个。
firefox在输出结果后面多添加了一个,原因是从ES5规范开始就允许在列表(数组值、属性列表等)末尾多加一个逗号,目的是为了让复制粘贴结果更为准确
上例中a和b的行为有时相同,有时又大相径庭:
a.join("-"); // "--"b.join("-"); // "--"a.map(function(v,i){ return i; }); // [ undefined x 3 ]b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]复制代码
(..)之所以执行失败,是因为数组中并存在任何单元,所以map(..)无从遍历。而join(..)却不一样
function fakeJoin(arr, connector) { var str = ""; for(var i = 0; i < arr.length; i++) { if (i > 0) { str += connector; } if (arr[i] !== undefined) { str += arr[i]; } } return str;}var a = new Array(3);fakeJoin(a, "-"); // "--"复制代码
从中可以看出,join(..)首先假定数组不为空,然后通过length属性值来遍历其中的元素。而map(..)并不做这样的假设,因此结果也往往在预期之外,并可能导致失败。
我们可以通过下述方式来创建包含undefined单元(而非“空单元”)的数组:
var a = Array.apply(null, { length: 3 });a; // [undefined,undefined,undefined]复制代码
Array.apply(..)调用Array(..)函数,并将{length:3}作为函数的参数。
我们可以设想apply(..)内部该数组参数名为arr,for循环遍历数组:arr[0]、arr[1]、arr[2]。由于{ length: 3 }中并不存在这些属性,所以返回值为undefined。
我们执行的实际上是Array(undefined,undefined,undefined),所以结果是单元值为undefined的数组,而非空单元数组。
虽然Array.apply(null, { length: 3 })在创建undefined值的数组时有些奇怪和繁琐,但其结果远比Array(3)更准确可靠。
2、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;复制代码
3、Date(..)和Error(..)
创建日期对象必须使用new Date()。Date(..)可以带参数,用来指定日期和时间,而不带参数的话则可以使用当前的日期和时间。
Date(..)主要用来获得当前Unix时间戳,该值可以通过日期对象中的getTime()来获得。
从ES5开始引入Date.now(),ES5之前使用:
if (!Date.now) { Date.now = function() { return (new Date()).getTime(); }}复制代码
构造函数Error(..)可不带new关键字
创建错误对象主要是为了获得当前运行栈的上下文(大部分js引擎通过只读属性.stack来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号,以便于调试。
错误对象通常与throw一起使用:
function foo(x) { if(!x) { throw new Error("x wasn't provided"); }}复制代码
4、Symbol(..)
符号是具有唯一性的特殊值(并非绝对),用它来命名对象属性不同导致重名。
符号可以用作属性名,但无论是在代码还是开发控制台中都无法查看和访问它的值,只会显示为诸如Symbol(Symbol.create)这样的值。
ES6有一些预定义符号,以Symbol的静态属性形式出现,如Symbol.create、Symbol.iterator
obj[Symbol.iterator] = function() { /*..*/}复制代码
我们可以使用Symbol(..)原生构造函数来定义符号。但它比较特殊,不能带new关键字,否则会出错:
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(..)便可以公开获得对象中所有符号),但它主要用于私有或特殊属性。
5、原生原型
-
String#indexOf(..)
在字符串中找到指定子字符串的位置
-
String#charAt(..)
获得字符串指定位置上的字符
-
String#substr(..)、String#substring(..)、String#slice(..)
获得字符串的指定部分
-
String#toUpperCase()和String#toLowerCase()
将字符串转为大写或小写
-
String#trim()
去掉字符串前后的空格,返回新的字符串。
以上方法并不改变原字符串的值,而是返回一个新字符串
其他构造函数的原型包含他们各自类型所持有的行为特征,如Number#toFixed(..)(将数字转换为指定长度的整数字符串)和Array#concat(..)(合并数组)。所有的函数都可以调用Function.prototype中的apply(..)、call(..)、bind(..)
然而有些原生原型并非普通对象那么简单:
typeof Function.prototype; // "function"Function.prototype(); // 空函数RegExp.prototype.toString(); // "/(?:)/" ---空正则表达式"abc".match(RexgExp.prototype); // [""]复制代码
我们可以修改他们(不仅仅是添加属性):
Array.isArray(Array.prototype); // trueArray.prototype.push(1,2,3); // 3Array.prototype; // [1,2,3]// 需要将Array.prototype设置回空,否则会导致问题Array.prototype.length = 0;复制代码
将原型作为默认值
Function.prototype是一个空函数,RegExp.prototype是一个“空”的正则表达式,而Array.prototype是一个空数组。对未赋值的变量来说,他们是很好的默认值。
function isThisCool(vals, fn, rx) { vals = vals || Array.prototype; fn = fn || Function.prototype; rx = rx || RegExp.prototype; return rx.test( vals.map(fn).join("") )}isThisCool(); // trueisThisCool( ["a","b","c"], function(v){ return v.toUpperCase(); }); // false复制代码
这种方法的一个好处是.prototype已被创建并且仅创建一次。相反,如果将[]、function(){}和/(?:)/作为默认值,则每次调用isThisCool(..)时他们会被创建一次(具体创建与否取决于js引擎),这样无疑会造成内存和CPU资源的浪费
注意:如果默认值随后会被更改,就不要使用Array.prototype。