温故而知新--JavaScript书摘(二)

前言

毕业到入职腾讯已经差很少一年的时光了,接触了不少项目,也积累了不少实践经验,在处理问题的方式方法上有很大的提高。随着时间的增长,越发发现基础知识的重要性,不少开发过程当中遇到的问题都是由最基础的知识点遗忘形成,基础不牢,地动山摇。因此,就再次回归基础知识,从新学习JavaScript相关内容,加深对JavaScript语言本质的理解。日知其所亡,身为有追求的程序员,理应不断学习,不断拓展本身的知识边界。本系列文章是在此阶段产生的积累,以记录下以往没有关注的核心知识点,供后续查阅之用。

2017

03/13

ECMAScript 语言类型包 括 Undefined、Null、Boolean、String、Number 和 Object,符号(symbol,ES6 中新增)
“类型”:对语言引擎和开发人员来讲,类型是值 的内部特征,它定义了值的行为,以使其区别于其余值。
检测null :
var a = null;
 (!a && typeof a === "object"); // true
JavaScript 中的变量是没有类型的,只有值才有。变量能够随时持有任何类型的值。  
在对变量执行 typeof 操做时,获得的结果并非该变量的类型,而是该变量持有的值的类 型,由于 JavaScript 中的变量没有类型。
已在做用域中声明但尚未赋值的变量,是 undefined 的。相反,尚未在做用域中声明 过的变量,是 undeclared 的。

03/14

对于 undeclared(或者 not defined)变量,typeof 照样返回 "undefined",这是由于 typeof 有一个特殊的安全防范 机制。
如何在程序中检查全局变量 DEBUG 才不会出现 ReferenceError 错误。这时 typeof 的 安全防范机制就成了咱们的好帮手;还有一种不用经过 typeof 的安全防范机制的方法,就是检查全部全局变量是不是全局对象 的属性,浏览器中的全局对象是 window。
使用 delete 运算符能够将单元从数组中删除,可是请注意,单元删除后,数 组的 length 属性并不会发生变化。数组经过数字进行索引,但有趣的是它们也是对象,因此也能够包含字符串键值和属性 (但这些并不计算在数组长度内): 若是字符串键值可以被强制类型转换为十进制数字的话,它 就会被看成数字索引来处理。
字符串不可变是指字符串的成员函数不会改变其原始值,而是建立并返回一个新的字符 串。而数组的成员函数都是在其原始值上进行操做。
字符串和数组的确很类似,它们都是类数组,都有 length 属性以及 indexOf(..)(从 ES5 开始数组支持此方法)和 concat(..) 方法。
特别大和特别小的数字默认用指数格式显示,与 toExponential() 函数的输出结果相同。 toPrecision(..) 方法用来指定有效数位的显示位数。
不过对于 . 运算符须要给予特别注 意,由于它是一个有效的数字字符,会被优先识别为数字常量的一部分,而后才是对象属 性访问运算符。
// 无效语法: 
42.toFixed( 3 ); // SyntaxError 
// 下面的语法都有效:
(42).toFixed( 3 ); // "42.000" 
0.42.toFixed( 3 ); // "0.420" 
42..toFixed( 3 ); // "42.000"
a | 0 能够将变量 a 中的数值转换为 32 位有符号整数,由于数位运算符 | 只适用于 32 位 整数(它只关心 32 位之内的值,其余的数位将被忽略)。所以与 0 进行操做便可截取 a 中 的 32 位数位。
  • null 指空值(empty value)
  • undefined 指没有值(missing value)
null 是一个特殊关键字,不是标识符,咱们不能将其看成变量来使用和赋值。然而 undefined 倒是一个标识符,能够被看成变量来使用和赋值。

03/15

void 并不改变表达式的结果, 只是让表达式不返回值。void 0、void 1 和 undefined 之间并无实质上的区别。  
NaN 是一个“警惕值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误 状况,即“执行数学运算没有成功,这是失败后返回的结果”。
NaN 是一个特殊值,它和自身不相等,是惟一一个非自反(自反,reflexive,即 x === x 不 成立)的值。而 NaN != NaN 为 true,isNaN(..) 有一个严重的缺陷,它的检查方式过于死板,就 是“检查参数是否不是 NaN,也不是数字”。 
要区分 -0 和 0,不能仅仅依赖开发调试窗口的显示结果,还须要作一些特殊处理: function isNegZero(n) { n = Number( n ); return (n === 0) && (1 / n === -Infinity); }
  • 简单值(即标量基本类型值,scalar primitive)老是经过值复制的方式来赋值 / 传递,包括 null、undefined、字符串、数字、布尔和 ES6 中的 symbol。
  • 复合值(compound value)——对象(包括数组和封装对象)和函数,则老是经过引用复制的方式来赋值 / 传递。  因为引用指向的是值自己而非变量,因此一个引用没法更改另外一个引用的指向。
function foo(x) {
    x.push(4);
    x; // [1,2,3,4] 
    x = [4, 5, 6];
    x.push(7);
    x; // [4,5,6,7]
}
var a = [1, 2, 3];
foo(a);
a; // 是[1,2,3,4],不是[4,5,6,7]
咱们不能经过引用 x 来更改引用 a 的指向,只能更改 a 和 x 共同指向的值。
slice(..) 不带参数会返回当前数组的一个浅复本(shallow copy)。

03/16

经常使用的原生函数有:
  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()——ES6 中新加入的!
全部 typeof 返回值为 "object" 的对象(如数组)都包含一个内部属性 [[Class]](咱们可 以把它看做一个内部的分类,而非传统的面向对象意义上的类)。这个属性没法直接访问, 通常经过 Object.prototype.toString(..) 来查看。 
虽然 Null() 和 Undefined() 这样的原生构造函数并不存在,可是内部 [[Class]] 属性值仍 然是 "Null" 和 "Undefined"。
通常状况下,咱们不须要直接使用封装对象。最好的办法是让 JavaScript 引擎本身决定什 么时候应该使用封装对象。
若是想要自行封装基本类型值,可使用 Object(..) 函数(不带 new 关键字)。 
若是想要获得封装对象中的基本类型值,可使用 valueOf() 函数。在须要用到封装对象中的基本类型值的地方会发生隐式拆封。
强烈建议使用常量形式(如 /^a*b+/g)来定义正则表达式,这样不只语法简单,执行效率 也更高,由于 JavaScript 引擎在代码执行前会对它们进行预编译和缓存。与前面的构造函 数不一样,RegExp(..) 有时仍是颇有用的,好比动态定义正则表达式时。
建立错误对象(error object)主要是为了得到当前运行栈的上下文(大部分 JavaScript 引擎 经过只读属性 .stack 来访问)。栈上下文信息包括函数调用栈信息和产生错误的代码行号, 以便于调试(debug)。
符号并不是对象,而是一种简单标量基本类型。

03/17

将值从一种类型转换为另外一种类型一般称为类型转换(type casting),这是显式的状况隐 式的状况称为强制类型转换(coercion)。
JavaScript 中的强制类型转换老是返回标量基本类型值,如字 符串、数字和布尔值,不会返回对象和函数。
类型转换发生在静态类型语言的编译阶段,而强制类型转换则发生在动态类型语言的运行时(runtime)。 在 JavaScript 中一般将它们统称为强制类型转换。
基本类型值的字符串化规则为:null 转换为 "null",undefined 转换为 "undefined",true 转换为 "true"。数字的字符串化则遵循通用规则,不过那些极小和极大的 数字使用指数形式。 
对普通对象来讲,除非自行定义,不然 toString()(Object.prototype.toString())返回内部属性 [[Class]] 的值,如 "[object Object]"。  
数组的默认 toString() 方法通过了从新定义,将全部单元字符串化之后再用 "," 链接起 来, toString() 能够被显式调用,或者在须要字符串化时自动调用。
全部安全的 JSON 值(JSON-safe)均可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指可以呈现为有效 JSON 格式的值。 为了简单起见,咱们来看看什么是不安全的 JSON 值。undefined、function、symbol (ES6+)和包含循环引用(对象之间相互引用,造成一个无限循环)的对象都不符合 JSON 结构标准,支持 JSON 的语言没法处理它们。  JSON.stringify(..) 在对象中遇到 undefined、function 和 symbol 时会自动将其忽略,在 数组中则会返回 null(以保证单元位置不变)。  对包含循环引用的对象执行 JSON.stringify(..) 会出错。  
若是对象中定义了 toJSON() 方法,JSON 字符串化时会首先调用该方法,而后用它的返回 值来进行序列化。 若是要对含有非法 JSON 值的对象作字符串化,或者对象中的某些值没法被序列化时,就 须要定义 toJSON() 方法来返回一个安全的 JSON 值。  
(1) 字符串、数字、布尔值和 null 的 JSON.stringify(..) 规则与 ToString 基本相同。
(2) 若是传递给 JSON.stringify(..) 的对象中定义了 toJSON() 方法,那么该方法会在字符 串化前调用,以便将对象转换为安全的 JSON 值。
var a = {
    b: 42,
    c: "42",
    d: [1, 2, 3]
};
JSON.stringify(a, null, 3);
/* 
"{
   "b": 42,
   "c": "42",
   "d": [
      1,
      2,
      3
   ]
}"
*/

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]}"

03/18

ToNumber  
其中 true 转换为 1,false 转换为 0。undefined 转换为 NaN,null 转换为 0。  
对象(包括数组)会首先被转换为相应的基本类型值,若是返回的是非数字的基本类型 值,则再遵循以上规则将其强制转换为数字。 为了将值转换为相应的基本类型值,抽象操做 ToPrimitive(参见 ES5 规范 9.1 节)会首先 (经过内部操做 DefaultValue,参见 ES5 规范 8.12.8 节)检查该值是否有 valueOf() 方法。 若是有而且返回基本类型值,就使用该值进行强制类型转换。若是没有就使用 toString() 的返回值(若是存在)来进行强制类型转换。 若是 valueOf() 和 toString() 均不返回基本类型值,会产生 TypeError 错误。
从 ES5 开始,使用 Object.create(null) 建立的对象 [[Prototype]] 属性为 null,而且没 有 valueOf() 和 toString() 方法,所以没法进行强制类型转换。 

03/19

ToBoolean
1. 假值(falsy value)
(1) 能够被强制类型转换为 false 的值 (2) 其余(被强制类型转换为 true 的值)
 JavaScript 规范具体定义了一小撮能够被强制类型转换为 false 的值。
如下这些是假值:
  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""
从逻辑上说,假值列表之外的都应该是真值(truthy)。但 JavaScript 规范对此并无明确 定义,只是给出了一些示例,例如规定全部的对象都是真值,咱们能够理解为假值列表以 外的值都是真值。
 假值对象(falsy object)  
浏览器在某些特定状况下,在常规 JavaScript 语法基础上本身建立了一些外来(exotic) 值,这些就是“假值对象”。 假值对象看起来和普通对象并没有二致(都有属性,等等),但将它们强制类型转换为布尔 值时结果为 false。
最多见的例子是 document.all,它是一个类数组对象,包含了页面上的全部元素,由 DOM(而不是 JavaScript 引擎)提供给 JavaScript 程序使用。它之前曾是一个真正意义上 的对象,布尔强制类型转换结果为 true,不过如今它是一个假值对象。

03/20

1. 日期显式转换为数字
一元运算符 + 的另外一个常见用途是将日期(Date)对象强制类型转换为数字,返回结果为 Unix 时间戳,以微秒为单位(从 1970 年 1 月 1 日 00:00:00 UTC 到当前时间)。
ES5 中新加入的静态方法 Date.now()。
2. 奇特的 ~ 运算符
字位运算符只适用于 32 位整数,运算符会强制操做数使用 32 位 格式。ToInt32 首先执行 ToNumber 强制类型转换,好比 "123" 会先被转换为 123,而后再执行 ToInt32。
Math.floor( -49.6 ); // -50 
~~-49.6; // -49
解析容许字符串中含有非数字字符,解析按从左到右的顺序,若是遇到非数字字符就停 止。而转换不容许出现非数字字符,不然会失败并返回 NaN。  
var a = "42"; var b = "42px";
Number( a ); // 42 
parseInt( a ); // 42 
Number( b ); // NaN 
parseInt( b ); // 42
parseInt(..) 针对的是字符串值,非字符串参数会首先被强制类型转换为字符串。 若是没有第二个参数来指定转换的 基数(又称为 radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数。  若是第一个字符是 x 或 X,则转换为十六进制数字。若是是 0,则转换为八进制数字。 从 ES5 开始 parseInt(..) 默认转换为十进制数,除非另外指定。若是你的代码须要在 ES5 以前的环境运行,请记得将第二个参数设置为 10。

03/24

parseInt( 1/0, 19 ); // 18  
一元运算符 ! 显式地将值强制类型转换为布尔值。可是它同时还将 真值反转为假值(或者将假值反转为真值)。因此显式强制类型转换为布尔值最经常使用的方 法是 !!,由于第二个 ! 会将结果反转回原值。 建议使用 Boolean(a) 和 !!a 来进行显式强制类型转换。

03/25

简单来讲就是,若是 + 的其中一个操做数是字符串, 则执行字符串拼接;不然执行数字加法。  a + ""(隐式)和前面的 String(a)(显式)之间有一个细微的差异须要注意。根据 ToPrimitive 抽象操做规则,a + "" 会对 a 调用 valueOf() 方法,而后经过 ToString 抽象 操做将返回值转换为字符串。而 String(a) 则是直接调用 ToString()。
“操做数选择器” :
|| 和 && 首先会对第一个操做数(a 和 c)执行条件判断,若是其不是布尔值(如上例)就 先进行 ToBoolean 强制类型转换,而后再执行条件判断。 对于 || 来讲,若是条件判断结果为 true 就返回第一个操做数(a 和 c)的值,若是为 false 就返回第二个操做数(b)的值。 && 则相反,若是条件判断结果为 true 就返回第二个操做数(b)的值,若是为 false 就返 回第一个操做数(a 和 c)的值。 || 和 && 返回它们其中一个操做数的值,而非条件判断的结果(其中可能涉及强制类型转 换)。c && b 中 c 为 null,是一个假值,所以 && 表达式的结果是 null(即 c 的值),而非 条件判断的结果 false。
但 ES6 中引入了符号类型,它的强制类型转换有一个坑,在这里有必要提一下。ES6 容许 从符号到字符串的显式强制类型转换,然而隐式强制类型转换会产生错误。符号不可以被强制类型转换为数字(显式和隐式都会产生错误),但能够被强制类型转换 为布尔值(显式和隐式结果都是 true)。

03/29

== 容许在相等比较中进行强制类型转换,而 === 不容许。== 和 === 都会检查操做数的类型。区别在于操做数类型不一样时它们的处理方 式不一样。

03/30

== 在比较两个不一样类型的值时会发生隐式强制类型转换,会将其中之 一或二者都转换为相同的类型后再进行比较。  
(1) 若是 Type(x) 是数字,Type(y) 是字符串,则返回 x == ToNumber(y) 的结果。
(2) 若是 Type(x) 是字符串,Type(y) 是数字,则返回 ToNumber(x) == y 的结果。
 
(1) 若是 Type(x) 是布尔类型,则返回 ToNumber(x) == y 的结果;
(2) 若是 Type(y) 是布尔类型,则返回 x == ToNumber(y) 的结果。
 
null 和 undefined 之间的 == 也涉及隐式强制类型转换。ES5 规范 11.9.3.2-3 规定:
(1) 若是 x 为 null,y 为 undefined,则结果为 true。
(2) 若是 x 为 undefined,y 为 null,则结果为 true。
var a = null;
var b;
a == b; // true 
a == null; // true 
b == null; // true 
a == false; // false 
b == false; // false 
a == ""; // false 
b == ""; // false 
a == 0; // false 
b == 0; // false
null 和 undefined 之间的强制类型转换是安全可靠的,上例中除 null 和 undefined 之外的 其余值均没法获得假阳(false positive)结果。我的认为经过这种方式将 null 和 undefined 做为等价值来处理比较好。  
(1) 若是 Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果;
(2) 若是 Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPromitive(x) == y 的结果。
以前介绍过的 ToPromitive 抽象操做的全部特性(如 toString()、valueOf()) 在这里都适用。若是咱们须要自定义 valueOf() 以便从复杂的数据结构返回 一个简单值进行相等比较,这些特性会颇有帮助。

03/31

"0" == null; //
false "0" == undefined; // false
"0" == false; // true -- 晕!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 晕!
false == ""; // true -- 晕!
false == []; // true -- 晕!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 晕!
"" == []; // true -- 晕!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 晕!
0 == {}; // false

04/05

""、"\n"(或者 " " 等其余空格组合)等空字符串被 ToNumber 强制类型转换 为 0。
咱们要对 == 两边的值认真推敲,如下两个原则可让咱们有效地避免出错。
  • 若是两边的值中有 true 或者 false,千万不要使用 ==。
  • 若是两边的值中有 []、"" 或者 0,尽可能不要使用 ==。 这时最好用 === 来避免不经意的强制类型转换。这两个原则可让咱们避开几乎全部强制 类型转换的坑。
a < b : 
比较双方首先调用 ToPrimitive,若是结果出现非字符串,就根据 ToNumber 规则将双方强 制类型转换为数字来进行比较。  若是比较双方都是字符串,则按字母顺序来进行比较。
var a = { b: 42 }; 
var b = { b: 43 }; 
a < b; // false
a == b; // false 
a > b; // false 
a <= b; // true 
a >= b; // true  
由于根据规范 a <= b 被处理为 b < a,而后将结果反转。由于 b < a 的结果是 false,所 以 a <= b 的结果是 true。  
 <= 应该是“小于或者等于",实际上 JavaScript 中 <= 是 “不大于”的意思(即 !(a > b),处理为 !(b < a))。同理 a >= b 处理为 b <= a。

04/06

然而 JavaScript 经过标签跳转可以实现 goto 的部分功能。continue 和 break 语句均可以带 一个标签,所以可以像 goto 那样进行跳转。
// 标签为foo的循环 foo: 
for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        // 若是j和i相等,继续外层循环 
        if (j == i) {
            // 跳转到foo的下一个循环 
            continue foo;
        }
        // 跳过奇数结果
        if ((j * i) % 2 == 1) {
            // 继续内层循环(没有标签的) 
            continue;
        }
        console.log(i, j);
    }
}
contine foo 并非指“跳转到标签 foo 所在位置继续执行”,而是“执行 foo 循环的下一轮循环”。因此这里的 foo 并不是 goto。
带标签的循环跳转一个更大的用处在于,和 break __ 一块儿使用能够实现从内层循环跳转到 外层循环。没有它们的话实现起来有时会很是麻烦: 
// 标签为foo的循环 foo: 
for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        if ((i * j) >= 3) {
            console.log("stopping!", i, j);
            break foo;
        }
        console.log(i, j);
    }
}
break foo 不是指“跳转到标签 foo 所在位置继续执行”,而是“跳出标签 foo 所在的循环 / 代码块,继续执行后面的代码”。所以它并不是传统意义上的 goto。
标签也能用于非循环代码块,但只有 break 才能够。咱们能够对带标签的代码块使用 break ___,可是不能对带标签的非循环代码块使用 continue ___,也不能对不带标签的代 码块使用 break。
JSON 的确是 JavaScript 语法的一个子集,可是 JSON 自己并非合法的 JavaScript 语法。 JSON-P(将 JSON 数据封装为函数调用, 好比 foo({"a":42}))经过将 JSON 数据传递给函数来实现对其的访问。
事实上 JavaScript 没有 else if,但 if 和 else 只包含单条语句的时候能够省略代码块的 { }。下面的代码你必定不会陌生:
if (a) doSomething(a);
else 也是如此,因此咱们常常用到的 else if 其实是这样的:
if (a) {
    // .. 
} else {
    if (b) {
        // .. 
    } else {
        // .. 
    }
}
if (b) { .. } else { .. } 其实是跟在 else 后面的一个单独的语句,因此带不带 { } 都 能够。换句话说,else if 不符合前面介绍的编码规范,else 中是一个单独的 if 语句。 else if 极为常见,能省掉一层代码缩进,因此很受青睐。但这只是咱们本身发明的用法, 切勿想固然地认为这些都属于 JavaScript 语法的范畴。  

04/07

有时 JavaScript 会自动为代码行补上缺失的分号,即自动分号插入(Automatic Semicolon Insertion,ASI)。  请注意,ASI 只在换行符处起做用,而不会在代码行的中间插入分号。 若是 JavaScript 解析器发现代码行可能由于缺失分号而致使错误,那么它就会自动补上分 号。而且,只有在代码行末尾与换行符之间除了空格和注释以外没有别的内容时,它才会 这样作。 语法规定 do..while 循环后面必须带 ;,而 while 和 for 循环后则不须要。大多数开发人员 都不记得这一点,此时 ASI 就会自动补上分号。 ASI 是一个语法纠错机制。若将换行符看成有意义的字符来对待,就会遇到不少 问题。
向函数传递参数时,arguments 数组中的对应单元会和命名参数创建关联(linkage)以得 到相同的值。相反,不传递参数就不会创建关联。
var a = "42";
switch (true) {
    case a == 10:
        console.log("10 or '10'");
        break;
    case a == 42;
    console.log("42 or '42'");
    break;
    default:
        // 永远执行不到这里 
} // 42 or '42'

04/09

还有一个不太为人所知的事实是:因为浏览器演进的历史遗留问题,在建立带有 id 属性 的 DOM 元素时也会建立同名的全局变量。例如: <div id="foo"></div> 以及: if (typeof foo == "undefined") { foo = 42; // 永远也不会运行 } console.log( foo ); // HTML元素 你可能认为只有 JavaScript 代码才能建立全局变量,而且习惯使用 typeof 或 .. in window 来检测全局变量。可是如上例所示,HTML 页面中的内容也会产生全局变量,而且稍不注 意就很容易让全局变量检查错误百出。
JavaScript 规范对于函数中参数的个数,以及字符串常量的长度等并无限制;可是因为 JavaScript 引擎实现各异,规范在某些地方有一些限制。
  • 字符串常量中容许的最大字符数(并不是只是针对字符串值);
  • 能够做为参数传递到函数中的数据大小(也称为栈大小,以字节为单位);
  • 函数声明中的参数个数;
  • 未经优化的调用栈(例如递归)的最大层数,即函数调用链的最大长度;
  • JavaScript 程序以阻塞方式在浏览器中运行的最长时间(秒);
  • 变量名的最大长度。
相关文章
相关标签/搜索