你不知道的JavaScript

变量和值

  • JavaScript 整数最大能达到53位。

  • | 0 可以将变量a 中的数值转换为32 位有符号整数,因为数位运算符| 只适用于32 位整数(它只关心32 位以内的值,其他的数位将被忽略)

  • JavaScript 引用指向的是值。如果一个值有10 个引用,这些引用指向的都是同一个值,它们相互之间没有引用/ 指向关系。JavaScript 对值和引用的赋值/ 传递在语法上没有区别,完全根据值的类型来决定。

  • undefined 类型只有一个值,即undefined。null 类型也只有一个值,即null。它们的名称既是类型也是值。

  • null 是一个特殊关键字,不是标识符,我们不能将其当作变量来使用和赋值。然而undefined 却是一个标识符,可以被当作变量来使用和赋值。

  • 表达式void ___ 没有返回值,void 并不改变表达式的结果,只是让表达式不返回值。

  • NaN 意指“不是一个数字”(not a number),将它理解为“无效数值”“失败数值”或者“坏数值”可能更准确些。

  • 简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/ 传递,包括null、undefined、字符串、数字、布尔和ES6 中的symbol。

  • 复合值(compound value)——对象(包括数组和封装对象,参见第3 章)和函数,则总是通过引用复制的方式来赋值/ 传递。

  • 请记住:我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。

原生函数

  1. Array()
  • Array 构造函数只带一个数字参数的时候,该参数会被作为数组的预设长度(length),而非只充当数组中的一个元素。

类型转换

1.显式类型转换

  • 基本类型值的字符串化规则为:null 转换为"null",undefined 转换为"undefined",true转换为"true"。数字的字符串化则遵循通用规则,不过那些极小和极大的数字使用指数形式
  • 所有安全的JSON 值(JSON-safe)都可以使用JSON.stringify(..) 字符串化。安全的JSON 值是指能够呈现为有效JSON 格式的值。(undefined、function、symbol(ES6+)和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON结构标准,支持JSON 的语言无法处理它们。)
  • JSON.stringify(..) 在对象中遇到undefined、function 和symbol 时会自动将其忽略,在数组中则会返回null(以保证单元位置不变)。
  • 如果对象中定义了toJSON() 方法,JSON 字符串化时会首先调用该方法,然后用它的返回值来进行序列化。可以用toJSON()方法对含有非法JSON值的对象返回一个安全的JSON值。
  • +c 是+ 运算符的一元(unary)形式(即只有一个操作数)。+ 运算符显式地将c 转换为数字,而非数字加法运算。 一元运算符+ 的另一个常见用途是将日期(Date)对象强制类型转换为数字
  • ~操作。它首先将值强制类型转换为32 位数字,然后执行字位操作“非”(对每一个字位进行反转)。
    • 对~ 还可以有另外一种诠释,源自早期的计算机科学和离散数学:~ 返回2 的补码
    • ~x 大致等同于-(x+1)
    • ~ 和indexOf() 一起可以将结果强制类型转换(实际上仅仅是转换)为真/ 假值:
  • JavaScript 有一处奇特的语法,即构造函数没有参数时可以不用带()。于是我们可能会碰到var timestamp = +new Date; 这样的写法。
  • “抽象渗漏”,意思是在代码中暴露了底层的实现细节,比如在使用indexOf()方法时,判断条件使用了===-1和>==0。
  • 解析字符串中的数字(parseInt( a );)和将字符串强制类型转换为数字(Number( a );)的返回结果都是数字。但解析和转换两者之间还是有明显的差别。
    • 解析允许字符串中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换不允许出现非数字字符,否则会失败并返回NaN。
    • 不要忘了parseInt(..) 针对的是字符串值。向parseInt(..) 传递数字和其他类型的参数是没有用的,比如true、function(){...} 和[1,2,3]。
    • ES5 之前的parseInt(..) 有一个坑导致了很多bug。即如果没有第二个参数来指定转换的基数(又称为radix),parseInt(..) 会根据字符串的第一个字符来自行决定基数。
  • parseInt( 1/0, 19 ); // 18。parseInt(..) 先将参数强制类型转换为字符串再进行解析,parseInt(1/0, 19) 实际上是parseInt("Infinity", 19)。第一个字符是"I"('I'在19~36进制下可以被转换为数字,为18),以19 为基数时值为18。第二个字符"n" 不是一个有效的数字字符,解析到此为止

2.隐式类型转换

  • 隐式强制类型转换的作用是减少冗余,让代码更简洁。
  • 如果+ 的其中一个操作数是字符串(或者通过以上步骤可以得到字符串),则执行字符串拼接;否则执行数字加法。
  • a + ""(隐式)和前面的String(a)(显式)之间有一个细微的差别需要注意。根据ToPrimitive 抽象操作规则,a + "" 会对a 调用valueOf() 方法,然后通过ToString 抽象操作将返回值转换为字符串。而String(a) 则是直接调用ToString()。
  • -是数字减法运算符,因此a - 0 会将a 强制类型转换为数字。也可以使用a * 1 和a/1,因为这两个运算符也只适用于数字,只不过这样的用法不太常见。
  • &&||运算符的返回值并不一定是布尔类型,而是两个操作数其中一个的值。
    • 对于|| 来说,如果条件判断结果为true 就返回第一个操作数(a 和c)的值,如果为false 就返回第二个操作数(b)的值。
    • && 则相反,如果条件判断结果为true 就返回第二个操作数(b)的值,如果为false 就返回第一个操作数(a 和c)的值。

3.宽松相等和严格相等

  • 宽松相等(loose equals)== 和严格相等(strict equals)=== 都用来判断两个值是否“相等”,但是它们之间有一个很重要的区别,特别是在判断条件上。:“== 允许在相等比较中进行强制类型转换,而=== 不允许。”
  • 抽象相等x==y
    1. 字符串和数字之间的相等比较
      • 如果Type(x) 是数字,Type(y) 是字符串,则返回x == ToNumber(y) 的结果。
      • 如果Type(x) 是字符串,Type(y) 是数字,则返回ToNumber(x) == y 的结果。
    2. 其他类型和布尔类型之间的相等比较
      • 如果Type(x) 是布尔类型,则返回ToNumber(x) == y 的结果;
      • 如果Type(y) 是布尔类型,则返回x == ToNumber(y) 的结果。
        个人建议无论什么情况下都不要使用== true 和== false。
    3. null 和undefined 之间的相等比较
    • 如果x 为null,y 为undefined,则结果为true。
    • 如果x 为undefined,y 为null,则结果为true。
      这也就是说在== 中null 和undefined 是一回事,可以相互进行隐式强制类型转换
        var b;
        a == b; // true
        a == null; // true
        b == null; // true
    
    1. 对象和非对象之间的相等比较
    • 如果Type(x) 是字符串或数字,Type(y) 是对象,则返回x==ToPrimitive(y) 的结果;
    • 如果Type(x) 是对象,Type(y) 是字符串或数字,则返回ToPromitive(x) == y 的结果。
      但有一些值不这样,原因是== 算法中其他优先级更高的规则。
    var a = null;
    var b = Object( a ); // 和Object()一样
    a == b; // false
    var c = undefined;
    var d = Object( c ); // 和Object()一样
    c == d; // false
    var e = NaN;
    var f = Object( e ); // 和new Number( e )一样
    e == f; // false
    
    因为没有对应的封装对象,所以null 和undefined 不能够被封装(boxed),Object(null)和Object() 均返回一个常规对象。
    • ""、"\n"(或者" " 等其他空格组合)等空字符串被ToNumber 强制类型转换为0。
    1. 假值的相等比较
    "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
    

语句

1 语句的结果值

  • 语句都有一个结果值(statement completion value,undefined 也算)。
  • 语法不允许我们获得语句的结果值并将其赋值给另一个变量(至少目前不行)。

2 表达式的副作用

  • 最常见的有副作用(也可能没有)的表达式是函数调用:
function foo() {
a = a + 1;
}
var a = 1;
foo(); // 结果值:undefined。副作用:a的值被改变
  • ++ 在前面时,如++a,它的副作用(将a 递增)产生在表达式返回结果值之前,而a++ 的副作用则产生在之后。
    *++a++ 会产生ReferenceError 错误,因为运算符需要将产生的副作用赋值给一个变量。以++a++ 为例,它首先执行a++(根据运算符优先级,如下),返回42,然后执行++42,这时会产生ReferenceError 错误,因为++ 无法直接在42 这样的值上产生副作用。
  • 链式赋值常常被误用,例如var a = b = 42,看似和前面的例子差不多,实则不然。如果变量b 没有在作用域中象var b 这样声明过,则var a = b =42 不会对变量b 进行声明。在严格模式中这样会产生错误,或者会无意中创建一个全局变量

3 上下文规则

{
foo: bar()
}

foo: bar()叫作“标签语句”,foo是语句bar()的标签。

  • JavaScript 通过标签跳转能够实现goto 的部分功能。continue 和break 语句都可以带一个标签,因此能够像goto 那样进行跳转。例如:
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: 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 所在的循环/ 代码块,继续执行后面的代码”。

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

推荐阅读更多精彩内容