第一部分 类型和语法

第1章 类型

  1. 函数对象的length属性是其声明的参数的个数
  2. JavaScript中变量是没有类型的,只有值有。变量可以随时持有任何类型的值。

第2章 值

数组

  1. 使用 delete 运算符可以将单元从数组中删除,但是请注意,单元删除后,数组的 length 属性并不会发生变化。
  2. 在创建“稀疏”数组(即含有空白或空缺单元的数组)时要特别注意:
var a = [];
a[0] = 1;
// 此处没有设置a[1]单元
a[2] = [ 3 ];
a[1]; // undefined
a.length; // 3
  1. 数组通过数字进行索引,但它们也是对象,所以也可以包含字符串键值和属性(但这些并不计算在数组长度内):
var a = [];
a[0] = 1;
a["foobar"] = 2;
a.length; // 1
a["foobar"]; // 2
a.foobar; // 2

但是,如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。

var a = [];
a["13"] = 42;
a.length; // 14
  1. 类数组:可以通过索引值属性访问元素,并拥有length属性,但不能使用数组的方法。比如DOM查询操作返回的DOM元素列表和arguments对象。可以使用Array.prototype.slice.call或Array.from来将伪数组转换为数组。

字符串

  1. 许多数组函数用来处理字符串很方便。虽然字符串没有这些函数,但可以通过“借用”(使用call方法)数组的非变更方法(如join和map)来处理字符串。可惜我们无法“借用”数组的可变更成员函数(如reverse)。翻转字符串时,可以使用str.split("").reverse().join(""),前提是str不能包含复杂字符(如多字节字符等)。

数字

  1. JavaScript 中的“整数”就是没有小数的十进制数。所以 42.0 即等同于“整数” 42。
  2. 特别大和特别小的数字默认用指数格式显示,与 toExponential() 函数的输出结果相同。
var a = 5E10;
a; // 50000000000
a.toExponential(); // "5e+10"
var b = a * a;
b; // 2.5e+21
var c = 1 / a;
c; // 2e-11
  1. toFixed方法可以指定小数部分的显示位数。toFixed返回值是字符串类型的。
  2. toPrecision用来指定有效数位的显示位数。
  3. 上面的方法不仅适用于数字变量,也适用于数字常量。不过对于 . 运算符需要给予特别注意,因为它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。
// 无效语法:
42.toFixed( 3 ); // SyntaxError
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000"
0.42.toFixed( 3 ); // "0.420"
42..toFixed( 3 ); // "42.000"
42 .toFixed(3); // "42.000" (注意空格)
  1. 0x、0o、0b开头的数字分别表示十六进制、八进制、二进制。
  2. 0.1 + 0.2 === 0.3为false,遵循IEEE 754标准的语言都存在类似问题。解决方案是设置一个机器精度(即误差范围值)。从ES6开始,该值定义在Number.EPSILON中。
if (!Number.EPSILON) {
  Number.EPSILON = Math.pow(2,-52);
}
function numbersCloseEnoughToEqual(n1,n2) {
  return Math.abs( n1 - n2 ) < Number.EPSILON;
}
var a = 0.1 + 0.2;
var b = 0.3;
numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false
  1. 能够呈现的最大浮点数Number.MAX_VALUE为1.798e+308,最小浮点数Number.MIN_VALUE为5e-324。
  2. 数字的呈现方式决定了“整数”的安全值范围远远小于 Number.MAX_VALUE。能够被“安全”呈现的最大整数是 2^53 - 1 ,即 9007199254740991 ,在 ES6 中被定义为Number.MAX_SAFE_INTEGER 。最小整数是-9007199254740991 ,在 ES6 中被定义为 Number.MIN_SAFE_INTEGER 。
  3. Number.isInteger检查是否是整数。
Number.isInteger( 42 ); // true
Number.isInteger( 42.000 ); // true
Number.isInteger( 42.3 ); // false
// polyfill
if (!Number.isInteger) {
  Number.isInteger = function(num) {
    return typeof num == "number" && num % 1 == 0;
  };
}

Number.isSafeInteger检查是否是安全的整数。

Number.isSafeInteger( Number.MAX_SAFE_INTEGER ); // true
Number.isSafeInteger( Math.pow( 2, 53 ) ); // false
Number.isSafeInteger( Math.pow( 2, 53 ) - 1 ); // true
// polyfill
if (!Number.isSafeInteger) {
  Number.isSafeInteger = function(num) {
    return Number.isInteger( num ) &&
      Math.abs( num ) <= Number.MAX_SAFE_INTEGER;
  };
}
  1. 不要使用window身上的isNaN,要使用Number.isNaN:
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true——晕!

很明显 "foo" 不是一个数字,但是它也不是 NaN 。这个 bug 自 JavaScript 问世以来就一直存在。从 ES6 开始我们可以使用工具函数 Number.isNaN:

Number.isNaN( a ); // true
Number.isNaN( b ); // false——好!
  1. 在JavaScript中,1 / 0为Infinity,-1 / 0为-Infinity。
  2. 在JavaScript中,我们无法决定是值复制还是引用复制,一切由值的类型来决定。

第3章 原生函数

  1. 如果想要自行封装基本类型值,也可以使用Object()函数。
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. 如果想要拆封对象中的基本类型的值,可以使用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
  1. 我们可以创建包含空单元的数组,比如将length属性设置为超过实际单元数的值,或者通过delete删除数组中的某个元素。
  2. 我们可以通过下述方式来创建包含undefined单元(而非空单元)的数组:
let arr = Array.apply(null, { length: 3 });
a; // [ undefined, undefined, undefined ];
  1. 如果需要动态定义正则表达式,则可以使用RegExp函数。
var name = "Kyle";
var namePattern = new RegExp( "\\b(?:" + name + ")+\\b", "ig" );
var matches = someText.match( namePattern );
  1. 有些原生原型并非普通对象那么简单。Function.prototype是一个函数,Array.prototype是一个数组,RegExp.prototype是一个正则表达式。
typeof Function.prototype // 'function'
Array.isArray(Array.prototype) // true
RegExp.prototype.toString() // "/(?:)/" —— 空正则表达式

第4章 强制类型转换

  1. 如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值。
  2. 所有安全的 JSON 值(JSON-safe)都可以使用 JSON.stringify(..) 字符串化。安全的JSON 值是指能够呈现为有效 JSON 格式的值。常见的不安全的JSON 值: undefined 、 function 、 symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象。
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 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。

  1. 我们可以向 JSON.stringify(..) 传递一个可选参数 replacer,它可以是数组或者函数,用来指定对象序列化过程中哪些属性应该被处理,哪些应该被排除。如果 replacer 是一个数组,那么它必须是一个字符串数组,其中包含序列化要处理的对象的属性名称,除此之外其他的属性则被忽略。如果 replacer 是一个函数,它会对对象本身调用一次,然后对对象中的每个属性各调用一次,每次传递两个参数,键和值。如果要忽略某个键就返回 undefined ,否则返回指定的值。
var a = {
  b: 42,
  c: "42",
  d: [1,2,3]
};
JSON.stringify( a, ["b","c"] ); // "{"b":42,"c":"42"}"
JSON.stringify( a, function(k,v){
  if (k !== "c") return v;
});
// "{"b":42,"d":[1,2,3]}"

如果 replacer 是函数,它的参数 k 在第一次调用时为 undefined (就是对对象本身调用的那次)。 if 语句将属性 "c" 排除掉。由于字符串化是递归的,因此数组 [1,2,3] 中的每个元素都会通过参数 v 传递给 replacer,即 1 、 2 和 3 ,参数 k 是它们的索引值,即 0 、 1 和 2 。
JSON.string 还有一个可选参数 space,用来指定输出的缩进格式。space 为正整数时是指定每一级缩进的字符数,它还可以是字符串,此时最前面的十个字符被用于每一级的缩进:

var a = {
  b: 42,
  c: "42",
  d: [1,2,3]
};
JSON.stringify( a, null, 3 );
// "{
//    "b": 42,
//    "c": "42",
//    "d": [
//       1,
//       2,
//       3
//    ]
// }"
JSON.stringify( a, null, "-----" );
// "{
// -----"b": 42,
// -----"c": "42",
// -----"d": [
// ----------1,
// ----------2,
// ----------3
// -----]
// }"
  1. JSON.stringify(..) 并不是强制类型转换,但其具有以下表现:
  • 字符串、数字、布尔值和 null 的 JSON.stringify(..) 规则与 ToString 基本相同。
  • 如果传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方法,那么该方法会在字符串化前调用,以便将对象转换为安全的 JSON 值。
  1. 对于ToNumber这一抽象操作:
  • true转换为1,false转换为0,undefined转换为NaN,null转换为0
  • 对字符串的处理基本遵循数字常量的相关规则/语法。
  • 对象会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其转换为数字。
  • 将对象转换为基本类型的值:将对象转换为基本类型的值,涉及到的抽象操作是ToPrimitive。如果有valueOf,则调用该方法;没有的话调用toString方法。
  1. JavaScript中的值可以分为以下两类:
  • 假值:可以被强制类型转换为false的值。
  • 真值:其他(被强制类型转换为true的值)。
    JavaScript定义了一小撮假值:
  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""(字符串中唯一一个假值)
    假值对象:浏览器在JavaScript基础上定义了一些宿主对象,比如document.all,将它们强制类型为布尔值的结果为false。Chrome中Boolean(document.all)为false。但IE中Boolean(document.all)为true,有时用此来判断宿主环境是否是IE。
if (document.all) { /* It's IE. */ }
  1. 示例代码
-'1' // -1
- -'1' // 1
  1. ~x大致等同于-(x+1),因此可以使用if (~str.indexOf(char))代替if (str.indexOf(char) !== -1)。
  2. 《你不知道的JavaScript(中卷)》将字符串和数字之间的类型转换分为了两节:4.3.1 字符串和数字之间的显示转换、4.3.2 显式解析数字字符串。4.3.1中讲解了Number等函数的用法,4.3.2中讲解了parseInt和parseFloat的用法。这样的讲解顺序是我见过的最合理的。它并没有笼统地把Number、parseInt、parseFloat放在一起讲,而是通过小标题的形式分成了两节,这样我就能牢牢记住——Number和parseInt、parseFloat之间是存在一定差别的。在此之前,我很容易把Number('42px')的结果也误记为42,我觉得就是因为知识点的学习和总结方式不恰当。其实Number('42px')为NaN,而parseInt('42px')的结果才是42。强制类型转换和解析数字字符串是有区别的——解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止解析;而转换不允许出现非数字字符,否则会失败并返回NaN。
  3. parseInt传递第二个参数的含义是什么?在此之前我一直记不清。其实就是指定按哪种进制解析第一个参数。第二个参数其实是ES5中加入的特性。因为在ES5之前,parseInt会根据字符串的第一个字符来指定转换的基数,如果第一个字符是X或x,则基数为16;如果第一个字符是0,则基数为8。这导致了很多问题,比如我处理时分秒时,parseInt('09')会被错误地转换成0(因为9并非合法的八进制数)。因此,从ES5开始,parseInt采用的默认基数是10,并且新增了第二个参数。
  4. 其次,parseInt的第一个参数如果不是字符串,会先转换为字符串。这也就不难理解,为什么parseInt(1 / 0, 19);为18了。因为1/0为Infinity,相当于parseInt('Infinity', 19)。在19进制中,I表示18,而n为非法字符,因此解析到n时便停止解析(n不包含在解析结果内)。结果为18。
  5. a + ""(隐式)和前面的 String(a)(显式)之间有一个细微的差别需要注意。根据ToPrimitive 抽象操作规则,a + "" 会对 a 调用 valueOf() 方法,然后通过 ToString 抽象操作将返回值转换为字符串。而 String(a) 则是直接调用 ToString()。

第5章 语法

  1. ES6中定义了一个新概念,叫做暂时性死区。指的是由于代码中的变量还没有初始化而不能被引用的情况。比如在let a;之前访问a变量。注意:对未声明的变量使用typeof不会产生错误,但在暂时性死区中却会。

附录A 混合环境JavaScript

  1. polyfill能够有效地为不符合最新规范的老版本浏览器填补缺失的功能。
  2. 使用script标签引入或编写js代码,如果script中的代码发生错误,它会像独立的js程序那样停止,但是后续的script中的代码依然会接着执行,不受影响。
  3. 保留字有四类:关键字、预留关键字、null常量和true/false布尔值。像function和switch都是关键字,预留关键字包括enum等,它们中很多已经在ES6中被用到(如class、extend等)。另外还有一些在严格模式中使用的保留字,如interface。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容