第3章 原生函数
JavaScript的内建函数,也叫原生函数,如String和Number。
经常使用的原生函数有:正则表达式
实际上,它们就是内建函数。
原生函数能够被看成构造函数来使用,但其构造出来的对象可能会和咱们设想的有所出入。数组
let a = new String('abc'); typeof a; // 是"object",不是"string" a instanceof String; // true Object.prototype.toString.call(a); // "[object String]"
经过构造函数建立出来的是封装了基本类型值(如"abc")的封装对象。
请注意:typeof在这里返回的是对象类型的子类型。
再次强调,new String("abc")建立的是字符串"abc"的封装对象,而非基本类型值"abc"。浏览器
3.1 内部属性 [[Class]]
全部typeof返回值为"object"的对象(如数组)都包含一个内部属性[[Class]](咱们能够把它看做一个内部的分类,而非传统的面向对象意义上的类)。这个属性没法直接访问,通常经过Object.prototype.toString(..)来查看。例如:安全
Object.prototype.toString.call([1, 2, 3]); // "[object Array]"
多数状况下,对象的内部[[Class]]属性和建立该对象的内建原生构造函数相对应,但并不是老是如此。性能优化
Object.prototype.toString.call(null); // "[Object Null]" Object.prototype.toString.call(undefined); // "[Object undefined]" // 虽然Null()和Undefined()这样的原生构造函数并不存在,可是内部[[Class]]属性值仍然是"Null"和"Undefined"。
其余基本类型值(如字符串、数字和布尔)的状况有所不一样,一般称为“包装”。app
Object.prototype.toString.call("abc"); // "[object String]" Object.prototype.toString.call(42); // "[object Number]" Object.prototype.toString.call(true); // "[object Boolean]"
3.2 封装对象包装
封装对象(object wrapper)扮演着十分重要的角色。因为基本类型值没有.length和.toString()这样的属性和方法,须要经过封装对象才能访问,此时JavaScript会自动为基本类型值包装(box或者wrap)一个封装对象:ide
let a = "abc"; a.length; // 3 a.toUpperCase(); // "ABC"
若是须要常常用到这些字符串属性和方法,那么从一开始就建立一个封装对象也许更为方便,这样JavaScript引擎就不用每次都自动建立了。但实际证实这并非一个好办法,由于浏览器已经为.length这样的常见状况作了性能优化,直接使用封装对象来“提早优化”代码反而会减低执行效率。
通常状况下,咱们不须要直接使用封装对象。最好的办法是让JavaScript引擎本身决定何时应该使用封装对象。
tip: 优先考虑使用基本类型值。函数
封装对象释疑
使用封装对象时有些地方须要特别注意。工具
let a = new Boolean(false); if(!a) { console.log('here'); // 执行不到这里 }
3.3 拆封
若是想要获得封装对象中的基本类型,可使用valueOf()函数:性能
let a = new String('abc'); let b = new Number(42); let c = new Boolean(true); a.valueOf(); // "abc" b.valueOf(); // 42 c.valueOf(); // true
3.4 原生函数做为构造函数
关于数组(array)、对象(object)、函数(function)和正则表达式,咱们一般喜欢以常量的形式来建立它们。实际上,使用常量和是同构造函数的效果是同样的(建立的值都是经过封装对象来包装)。
如前所述,应该尽可能避免使用构造函数,除非十分必要,由于它们常常会产生意想不到的结果。
3.4.1 Array(..)
Array构造函数只带一个数字参数的时候,该参数会被做为数组的预设长度(length),而非只充当数组中的一个元素。
着实非明智之举:一是容易忘记,二是容易出错。
更为关键的是,数组并无预设长度这个概念。这样建立出来的只是一个空数组,只不过它的length属性被设置成了指定的值。
3.4.2 Object(..)、Function(..)和RegExp(..)
一样,除非万不得已,不然尽可能不要使用Object(..)/Function(..)/RegExp(..)。
3.4.3 Date(..)和Error(..)
相较于其余原生构造函数,Date(..)和Error(..)的用处要大不少,由于没有对应的常量形式来做为它们的替代。
建立日期对象必须使用new Date()。Date(..)能够带参数,用来指定日期和时间,而不带参数的话则使用当前的日期和时间。
Date(..)主要用来得到当前的Unix时间戳(从1970年1月1日开始计算,以秒为单位)。该值能够经过日期对象中的getTime()来得到。
建立错误对象(error object)主要是为了得到当前运行栈的上下文(大部分JavaScript引擎经过只读属性.stack来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号,以便于调式(debug)。
错误对象一般与throw一块儿使用:
function Foo(x) { if(!x) { throw new Error("x wasn't provided"); } // .. }
一般所悟对象至少包含一个message属性,有时也不乏其余属性(必须做为只读属性访问)。
3.4.4 Symbol(..)
ES6中新加入了一个基本数据类型——符号(Symbol)。符号是具备惟一性的特殊值(并不是绝对),用它来命名对象属性不容易致使重名。
obj[Symbol.iterator] = function() { /*..*/ };
符号并不是对象,而是一种简单标量基本类型。
3.4.5 原生原型
原生构造函数有本身的.prototype对象,如Array.prototype、String.prototype等。
这些对象包含其对应子类型所特有的行为特征。
例如,将字符串值封装为字符串对象以后,就能访问String.prototype中定义的方法。
根据文档约定,咱们将String.prototype.XYZ简写为String#XYZ,对其余.prototype也一样如此。
以上方法并不改变原字符串的值,而是返回一个新字符串。
tip: trim能够用来校验是否为空字符串,可是trim只能去掉字符串先后的空格,字符之间夹杂的空格并不饿能去掉。
typeof Function.prototype; // "function" Function.prototype(); // 空函数! RegExp.prototype.toString(); // "/(?:)/"——空正则表达式 "abc".match(RegExp.prototype); // [""] Array.isArray(Array.prototype); // true
Function.prototype是一个函数,RegExp.prototype是一个空的正则表达式,而Array.prototype是一个空数组。这里,将原型做为默认值。
tips: 从ES6开始,咱们再也不须要使用vals = vals || ..这样的方式来设置默认值,由于默认值能够经过函数声明中的内置语法来设置。
3.5 小结
JavaScript为基本数据类型值提供了封装对象,称为原生函数(如String、Number、Boolean等)。它们为基本数据类型值提供了该子类型所持有的方法和属性(如:String#trim()和Array#concat())。
对于简单标量基本类型值,好比"abc",若是要访问它的length属性或String.prototype方法,JavaScript引擎会自动对该值进行封装(即用相应类型的封装对象来包装它)来实现对这些属性和方法的访问。
第4章 强制类型转换
4.1 值类型转换
将值从一种类型转换为另外一种类型一般称为类型转换,这是显式的状况;隐式的状况称为强制类型转换。
也能够这样来区分:类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时。
然而在JavaScript中一般将它们统称为强制类型转换。
JavaScript中的强制类型转换老是返回标量基本类型值,如字符串、数字和布尔值,不会返回对象和函数。
4.2 抽象值操做
4.2.1 ToString
toString负责处理非字符串到字符串的强制类型转换。
tips: 基本类型值的字符串化规则为:null转换为"null",undefined转换为"undefined",true转换为"true"。数字的字符串化规则遵循通用规则。
若是对象有本身的toString()方法,字符串化时就会调用该方法并使用其返回值。
JSON字符串化
工具函数JSON.stringify(..)在将JSON对象序列化为字符串时也用到了ToString。
全部安全的JSON值均可以使用JSON.stringify(..)字符串化。安全的JSON值是指可以呈现为有效JSON格式的值。
undefined、function、symbol和包含循环引用(对象之间相互引用,造成一个无限循环)的对象都不符合JSON结构标准,支持JSON的语言没法处理它们。
JSON.stringify(..)在对象中遇到undefined、function和symbol时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。
JSON.stringify(undefined); // undefined JSON.stringify(function () {}); // undefined JSON>stringify( [1, undefined, function() {}, 4 ] ); // "[1, null, null, 4]" JSON.stringify( { a: 2, b: function() {} } ); // "{"a": 2}"
若是对象中定义了toJSON()防范,JSON字符串化时会首先调用该方法,而后用它的返回值进行序列化。
不少人误觉得toJSON()返回的是JSON字符串化后的值,其实否则,除非咱们确实想要对字符串进行字符串化(一般不会!)。toJSON()返回的应该是一个适当的值,能够是任何类型,而后再由JSON.stringify(..)对其进行字符串化。也就是说,toJSON()应该“返回一个可以被字符串化的安全的JSON值”,而不是“返回一个JSON字符串”。
JSON.stringify(..)并非强制类型转换,涉及ToString强制类型转换,具体表如今如下两点。
(1)字符串、数字、布尔值和null的JSON.stringify(..)规则与ToString基本相同。
(2)若是传递给JSON.stringify(..)的对象中定义了toJSON()方法,那么该方法会在字符串化前调用,以便将对象转换为安全的JSON值。
4.2.2 ToNumber
ES5定义了抽象操做ToNumber。
tips:其中true转换为1,false转换为0,undefined转换为NaN,null转换为0。
ToNumber对字符串的处理基本遵循数字常量的相关规则/语法,处理失败时返回NaN。
对象(包括数组)会首先被转换为相应的基本类型值,若是返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操做ToPrimitive会首先检查该值是否有valueOf()方法;若是有而且返回基本类型值,就使用该值进行强制类型转换,若是没有就使用toString()的返回值(若是存在)来进行强制类型转换。
若是valueOf()和toString()均不返回基本类型值,会产生TypeError错误。
let c = [4, 2]; c.toString = function() { return this.join(""); // "42" } Number(c); // 42 Number(""); // 0 Number([]); // 0 Number(["abc"]); // NaN
4.2.3 ToBoolean
一、假值
JavaScript中的值能够分为如下两类:
(1)能够被强制类型转换为false的值;
(2)其余(被强制类型转换为true的值)。
JavaScript规范具体定义了一小撮能够被强制类型转换为false的值。
tips:如下这些是假值:
假值的布尔强制类型转换结果为false。
二、假值对象
三、真值
真值就是假值列表以外的值。
let a = "false"; let b = "0"; let c = "''"; let aa = []; let bb = {}; let cc = function() {}; let d = Boolean( a && b && c && aa && bb && cc); d; // true
以上的值都不在假值列表中,都是真值,不过""除外,由于它是假值列表中惟一的字符串。
也就是说真值列表能够无限长,没法一一列举,因此咱们只能用假值列表做为参考。
4.3 显式强制类型转换
4.3.1 字符串和数字之间的显式转换
一、日期显式转换为数字
tips: 建议使用Date.now()来得到当前的时间戳,使用new Date(..).getTime()来得到指定时间的时间戳。
二、奇特的~运算符
按照离散数学来解释:~返回2的补码;也就是说~x等同于-(x+1)。
~42; // -(42+1) ==> -43 let a = "Hello World"; if(a.indexOf("lo") >= 0) { // true // 找到匹配! } if(a.indexOf("lo") != -1) { // true // 找到匹配! }
=0和==-1这样的写法不是很好,称为“抽象渗漏”,意思是在代码中莫楼了底层的实现细节,这里只用-1做为失败时的返回值,这些细节应该被屏蔽掉。
if(~a.indexOf("lo")) { // true // 找到匹配! }
4.3.2 显式解析数字字符串
解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但解析和转换二者之间仍是有明显的差异。
let a = "42"; let b = "42px"; Number(a); // 42 parseInt(a); // 42 Number(b); // NaN parseInt(b); // 42
tips:解析容许字符串中含有非数字字符,解析从左到右的顺序,若是遇到非数字字符就中止。而转换不容许出现非数字字符,不然会失败并返回NaN。
4.3.3 显式转换为布尔值
布尔值状况:
显式强制类型转换为布尔值最经常使用的方法是!!。
4.4 隐式强制类型转换
隐式强制类型转换值得是那些隐蔽的强制类型转换,反作用也不是很明显。
隐式强制类型转换的做用是减小冗余,让代码更简洁。
4.4.1 隐式地简化
隐式强制类型转换一样能够用来提升代码可读性。然而隐式强制类型转换也会带来一些负面影响,有时甚至是弊大于利,可是不该该“因噎废食”。
4.4.2 字符串和数字之间的隐式强制类型转换
+运算符即能用于数字加法,也能用于字符串拼接。
let a = [1, 2]; let b = [3, 4]; a + b; // "1, 23, 4"
操做数的valueOf()操做没法获得简单基本类型值,因而转而调用toString()。
tips: a+""能够转换为字符串;a-0能够转换为数字。
4.4.3 布尔值到数字的隐式强制类型转换
4.4.6 符号的强制类型转化
ES6容许从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误。
let s1 = Symbol("cool"); String(s1); // "Symbol(cool)" let s2 = Symbol("not cool"); s2 + ""; // TypeError
符号不可以被强制类型转换为数字(显式和隐式都会产生错误),但能够被强制类型转换为布尔值(显式和隐式结果都是true)。
4.5 宽松相等和严格相等
==容许在相等比较中进行强制类型转换,而===不容许。
4.5.1 相等比较操做的性能
若是两个值的类型不一样,须要考虑有没有强制类型转换的必要,有就用==,没有就用===,不用在意性能。
==和===都会检查操做数的类型,区别在于操做数类型不一样时它们的处理方式不一样。
4.5.2 抽象相等
须要注意的是:
对象(包括函数和数组)的宽松相等==。两个对象指向同一个值时即视为相等,不发生强制类型转换。
在比较两个对象的时候,==和===的工做原理是同样的。
二、其余类型和布尔类型之间的相等比较
==最容易出错的一个地方是true和false与其余类型之间的相等比较。
let a = '42'; let b = true; a == b; // false
规范:
(1)若是Type(x)是布尔类型,则返回ToNumber(x) == y的结果;
(2)若是Type(y)是布尔类型,则返回x == ToNumber(y)的结果。
三、null和undefined之间的相等比较
规范:
(1)若是x为null,y为undefined,则结果为true;
(2)若是x为undefined,y为null,则结果为true。
在==中null和undefined相等(它们也与其自身相等),除此以外其余值都不存在这种状况。
4.5.3 比较少见的状况
'0' == false; false == 0; false == ''; false == []; '' == 0; '' == []; 0 == [];
若是两边的值中有true或者false,千万不要使用==;
若是两边的值中有[]、""或者0,尽可能不要使用==。
4.6 抽象关系比较
奇奇怪怪的东西???
let a = { b: 42 }; let b = { b: 42 }; a < b; // false a == b; // false a > b; // false a <= b; // true ?!! a >= b; // true !!!
JavaScript中<=是“不大于”的意思。好比:a <= b就是a>b的反转。emmmm有意思