第一部分 01 类型 值 和 原生函数

类型

类型是值的内部特征,它定义了值的行为,以使其区别于其他值。

1.2 内置类型

JS有七种内置类型:

  1. 空值 null
  2. 未定义 undefined
  3. 布尔值 boolean
  4. 数字 Number
  5. 字符串 String
  6. 对象 Object
  7. 符号 Symbol

除对象外,其他统称为基本类型。

可用typeof 运算符查看值的类型,返回类型的字符串值。

typeof undefined === "undefined";   // true
typeof true === "boolean";  // true
typeof 42 === "number";  // true
typeof "42" === "string";  // true
typeof { life: 42 } === "object";  // true
typeof Symbol() === "symbol";  // true

null 不在此列。它比较特殊,topeof对它处理有问题:

typeof null === "object";  // true

这个bug由来已久。我们需要使用复合条件来检测null值的类型:

var a = null;
(!a && typeof a === "object");  // true

null 是基本类型中唯一一个“假值”类型,typeof对它的返回值为"object"

还有一种情况:

typeof function a() { /*...*/ } === "function";  // true

函数不仅是对象,还拥有属性:

function a (b, c) { /*...*/ }
a.length;  // 2  

函数对象的length属性是其声明的参数的个数

在来看看数组:

typeof [1, 2, 3] === "object";  //true

数组也是对象。确切地说,它也是object的一个"子类型".

1.3 值和类型

JS中变量是没有类型的,只有值才有。变量可随时持有任何类型的值。

1.3.1 undefined 和 undeclared

变量在未持有值是时候为undefined。此时typeof返回“undefined”

已在作用域中声明但还没赋值的变量,是undefined的。
还没有在作用域中声明过的变量是undecleared的。

var a;
a;  // undefined
b; // ReferenceError: b is not defined

这个提示容易让人误解为"b is undefined"。“b is not found” 或 "b is not declared"会更准确。

1.3.2 typeof Undeclared

如何在程序中检查全局变量DEBUG才会出现ReferenceError错误。这时typeof的安全防范机制就成了好帮手:

// 这样会抛出错误
if(DEBUG){
    //...
}

//这样是安全的
if(typeof DEBUG !== "undefined"){
    //...
}

从技术角度说,typeof的安全防范机制对于非全局变量也很管用,如检查变量是否已经在宿主程序中定义过:

function doSomethingCool() {
    var helper = (typeof FeatureXYZ !== 'undefined') ? FeatureXYZ : function() { /*...*/ }
    var val = helper();
}

还有依赖注入“dependency injection”设计模式,就是将依赖通过参数显式地传递到函数中:

function doSomethingCool(FeatureXYZ){
    var helper = FeatureXYZ || function() { /*...*/ }
    var val = helper();
}

2.1数组

类数组

有时需要将类数组转换为真正的数组,这一般通过数组工具函数(如indexOf()、concat()、forEach() 等)来实现。

例如:一些DOM查询操作会返回DOM元素列表,或arguments并非真正的数组。工具函数slice()经常被用于这种类转换:

function foo() {
    var arr = Array.prototype.slice.call( arguments );
    arr.push( "bam" );
    console.log( arr );
}
foo( "bar", "baz" );   // ["bar", "baz", "bam"]

slice() 返回参数列表的一个数组复本。ES6的Array.from() 也能实现同样的功能:

var arr = Array.from( arguments );

2.2 字符串

JS中字符串和字符数组并不是一回事

var a = "foo";
var b = ["f", "o", "o"];

它们都是类数组,都有length属性以及indexOf()(从ES5开始数组支持此方法) 和concat()方法

var a = "foo";
var b = ["f", "o", "o"];

a.length;  //3
b.length; //3

a.indexOf( "o" );  //1
b.indexOf( "o" );  //1

var c = a.concat( "bar" );   //  "foobar"
var d = b.concat( ["b", "a", "r"] );   //  ["f", "o", "o", "b", "a", "r"]

a === c;   // false
b === d;  // false

a;   // "foo"
b;  // ["f", "o", "o"]

JS中字符串是不可变的,而数组是可变的。许多数组函数用来处理字符串很方便。

a.join;   //undefined
a.map;  // undefined

var c = Array.prototype.join.call( a, "-" );
var d = Array.prototype.map.call( a, function(v){
    return v.toUpperCase() + ".";
} ).join("");

c;   // "f-o-o"
d;  // "F.O.O"

数组有一个反转函数 reverse(),可惜字符串无法借用,因为字符串是不可变的。
一个变通的办法是选将字符串转换为数组,处理后在转换回字符串:

var c = a.split( "" ).reverse().join();

c;  // "oof" 

2.4 特殊数值

  1. undefined 指从未赋值
  2. null 指曾赋过值,但目前没值

void 运算符
undefined是一个内置标识符,值为undefined。通过void运算符即可得到该值。

表达式 void __ 没有返回值,因此返回结果是undefined。void并不改变表达式的结果,只是表达式不返回值:

var a = 42;
console.log( void a, a );   // undefined 42

void 运算符在其他地方也能派上用场,比如不让表达式返回任何结果

function doSomething() {
    // 注:APP.ready 由程序自己定义
    if(!APP.ready) {
        // 稍后在试
        return void setTimeout( doSomething, 100 );
    }
    var result;
    // 其他
    return result;
}

这里setTimeout() 函数返回一个数值,但是为了确保if语句不产生误报(false positive),我们要void掉它。

2.4.3 特殊的数字

NaN 意指 “不是一个数字” not a number
NaN 是一个特殊值,它和自身不相等,是唯一一个非自反的值。
从ES6起可使用工具函数 Number.isNaN()。

JS的运算结果有可能溢出,此时结果为Infinity 或 -Infinity

JSON.stringify(-0) 返回 “0”,而JSON.parse("-0"); 返回 -0

2.4.4 特殊等式

由于NaN和自身不相等,所以必须使用ES6中的Number.isNaN()。而-0 等于 0,因此我们必须使用isNgeZero()这样的工具函数

ES6加入了一个工具方法Object.is() 来判断两个值是否绝对相等,可用来处理上述所有的特殊情况

2.5 值和引用

简单值(scalar primitive),总是通过值复制的方式来赋值/传递,包括null 、nudefined 、 字符串、数字、布尔和Symbol。
复合值(compound value)-对象和函数,则总是通过引用复制的方式来赋值/传递。
由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

var a = [1, 2, 3];
var b = a;
a;  // [1, 2, 3]
b; // [1, 2, 3]

// 然后
b = [4, 5, 6];
a;  // [1, 2, 3]
b; // [4, 5, 6]

原生函数可被当作构造函数来使用,但其构造出来的对象可能会和我们设想的有所出入:

var a = new String( "abc" );
typeof a;    // Object
a instanceof String;  // true
Object.prototype.toString.call( a );   // "[object String]"

通过构造函数创建出来的是封装了基本类型值的封装对象。

3.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]"

null 和 undefined这样的原生构造函数并不存在,但是内部[[Class]]属性值仍然是 "Null" 和 "Undefined"

基本类型值通常称为“包装”

3.2封装对象包装

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

var a = "abc";
a.length;  //3
b.toUpperCase();   // "ABC"

一般情况下,我们不需要直接使用封装对象,最好的办法是让JS引擎自己决定什么时候应该使用封装对象。

3.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

3.4 原生函数作为构造函数

应尽量避免使用构造函数,除非十分必要,同为它们经常会产生意想不到的结果。

3.4.1 Array()

var a = new Array(1, 2, 3);
a;  // [1, 2, 3]

var b = [1, 2, 3];
b;  // [1, 2, 3]

Array构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。更关键的是,数组并没有预设长度这个概念。这样创建出来的只是一个空数组,只不过它的length 属性被设置成了指定的值。这会导致一些怪异行为。

我们可创建包含空单元的数组,只要将length属性设置为超过实际单元的值,就能隐式地制造出空单元。还可通过delete来制造出一个空单元。

join() 首先假定数组不为空,然后通过length属性值来遍历其中的元素。
map() 直接遍历数组中的元素,无元素则执行失败

array.apply(null, {length: 3}); 执行的实际上是 Array( undefined, undefined, undefined);虽然这有些奇怪和繁琐,但是其结果远比Array(3) 更准确可靠

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

3.4.2 Object() Function() RegExp()

同样,除非万不得已,否则尽量不要使用 Object() Function() RegExp()
在实际情况中没有必要使用new Object() 来创建对象,因为这样就无法像常量形式那样一次设定多个属性,而必须逐一设定。

构造函数Function 只在极少数情况下很有用,比如动态定义函数和函数体的时候。
强烈建议使用常量(如 /^a*b+/g)形式来定义正则表达式。这样不仅语法简单,执行效率也更高,因为JS引擎在代码执行前会对它们进行预编译和缓存。

有时RegExp()很有用,比如动态定义正则表达式时:

var name = "Kyle";
var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
var matches = someText.match( namePattern );

3.4.3 Date() Error()

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

Date() 主要用来获得当前的Unix时间戳 (从 1970.1.1 开始计算,以秒为单位)。该值可通过日期对象中的getTime() 来获得。
从ES5 开始可使用Date.now() 来获得。如果调用Date() 时不带new关键字,则会得到当前日期的字符串值。

if(!Date.now){
    Date.now = function(){
        return (new Date()).getTime();
    }
}

构造函数Error() 带不带new关键字都可。
创建错误对象主要是为了获得当前运行栈的上下文。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,211评论 0 4
  •   引用类型的值(对象)是引用类型的一个实例。   在 ECMAscript 中,引用类型是一种数据结构,用于将数...
    霜天晓阅读 1,036评论 0 1
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,095评论 0 3
  • 本章内容 使用对象 创建并操作数组 理解基本的 JavaScript 类型 使用基本类型和基本包装类型 引用类型的...
    闷油瓶小张阅读 676评论 0 0
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,521评论 0 5