JS的原生函数

JS的原生函数,包括:String()、Number()、Boolean()、Array()、Object()、Function()、RegExp()、Date()、Error()、Symbol(),实际上,它们就是内建函数

1、内部属性[[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]]属性和创建该对象的内建原生构造函数相对应,但并非总是如此,如果括号中填写null或者undefined,虽然Null()和undefined()的原生构造函数并不存在,但是内部[[Class]]属性仍然是“Null”和“undefined”

2、封装对象包装

由于基本类型没有.length和.toString()这样的属性和方法,需要通过封装对象才能访问,此时JS会自动为基本类型值包装一个封装对象

但是使用封装对象时有些地方需要特别注意:

比如Boolean

var a = new Boolean( false );
if (!a) {
	console.log( "Oops" ); // 执行不到这里
}

如果想要自行封装基本类型值,可以使用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; // true
c instanceof String; // true
Object.prototype.toString.call( b ); // "[object String]"
Object.prototype.toString.call( c ); // "[object String]"

但是,不推荐直接使用封装对象

  1. 封装对象可能会增加额外的内存消耗和函数调用开销,因为每次访问对象的属性或方法时都需要经过一层函数调用
  2. 封装对象可能会导致访问数据变得繁琐,因为需要通过方法来获取或修改数据。这种间接性可能会使代码更难以理解和维护

3、拆封

如果想要得到封装对象中的基本类型的值,可以使用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; // "object"
typeof b; // "string"

4、原生函数作为构造函数

4.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; // 3
a; // [空属性×3]

这意味着它有三个值为 undefined的单元,但实际上单元并不存在

var a = new Array( 3 );
var b = [ undefined, undefined, undefined ];
var c = [];
c.length = 3;
a;
b;
c;

上述代码中,b 在当前版本的 Chrome 中显示为 [ undefined, undefined, undefined ],而 a 和 c 则显示为 [ undefined x 3 ]

更糟糕的是,上例中 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 ]

a.map(…) 之所以执行失败,是因为数组中并不存在任何单元,所以 map(…) 无从遍历。而join(…) 却不一样,join(…) 首先假定数组不为空,然后通过 length 属性值来遍历其中的元素。而 map(…) 并不做这样的假定,因此结果也往往在预期之外,并可能导致失败。

我们可以通过下述方式来创建包含 undefined 单元(而非“空单元”)的数组:

var a = Array.apply( null, { length: 3 } );
a; // [ undefined, undefined, undefined ]

总之,永远不要创建和使用空单元数组。

4.2、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;

同样,除非万不得已,否则尽量不要使用 Object(…)/Function(…)/RegExp(…)

  1. 在实际情况中没有必要使用 new Object() 来创建对象,因为这样就无法像常量形式那样一次设定多个属性,而必须逐一设定。
  2. 构造函数 Function 只在极少数情况下很有用,比如动态定义函数参数和函数体的时候。
  3. 强烈建议使用常量形式(如 /^a*b+/g)来定义正则表达式,这样不仅语法简单,执行效率也更高,因为 JavaScript 引擎在代码执行前会对它们进行预编译和缓存。与前面的构造函数不同,RegExp(…) 有时还是很有用的,比如动态定义正则表达式时
var name = "Kyle";
var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
var matches = someText.match( namePattern );

4.3、Date(…)和Error(…)

创建日期对象必须使用 new Date()。Date(…) 可以带参数,用来指定日期和时间,而不带参数的话则使用当前的日期和时间。

从 ES5 开始引入了一个更简单的方法,即静态函数 Date.now()。

构造函数 Error(…)带不带 new 关键字都可。创建错误对象(error object)主要是为了获得当前运行栈的上下文(大部分 JavaScript 引擎通过只读属性 .stack 来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号,以便于调试(debug)。

错误对象通常与 throw 一起使用:

function foo(x) {
    if (!x) {
    	throw new Error( "x wasn’t provided" );
    }
    // ..
}

通常错误对象至少包含一个 message 属性,有时也不乏其他属性(必须作为只读属性访问),如 type。除了访问 stack 属性以外,最好的办法是调用(显式调用或者通过强制类型转换隐式调用)toString() 来获得经过格式化的便于阅读的错误信息。

4.4、Symbol(…)

ES6 中新加入了一个基本数据类型 ——符号(Symbol)。符号是具有唯一性的特殊值(并非绝对),用它来命名对象属性不容易导致重名。

符号并非对象,而是一种简单标量基本类型。

4.5、原生属性

原生构造函数有自己的 .prototype 对象,如 Array.prototype、String.prototype 等。这些对象包含其对应子类型所特有的行为特征。例如,将字符串值封装为字符串对象之后,就能访问 String.prototype 中定义的方法。

// 在字符串中找到指定子字符串的位置。
String#indexOf(..)
// 获得字符串指定位置上的字符。
String#charAt(..)
// 获得字符串的指定部分。
String#substr(..)、String#substring(..) 和 String#slice(..)
// 将字符串转换为大写或小写。
String#toUpperCase() 和 String#toLowerCase()
// 去掉字符串前后的空格,返回新的字符串。
String#trim()

以上方法并不改变原字符串的值,而是返回一个新字符串。

5、总结

JavaScript 为基本数据类型值提供了封装对象,称为原生函数(如 String、Number、Boolean等)。它们为基本数据类型值提供了该子类型所特有的方法和属性(如:String#trim() 和Array#concat(…))。

对于简单标量基本类型值,比如 “abc”,如果要访问它的 length 属性或 String.prototype方法,JavaScript 引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。

  • 35
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值