7 个令人惊讶的 JavaScript “特

在过去的几个月里,我对 JSHint 做了一些改进,主要是,学习 ES6(我最自豪的是重新实现了变量作用域)的过程中我碰到了几个特性,它们让我惊讶,其中大部分是关于 ES6 的特性但也有一部分是 ES3 特性,这些特性我以前从未用过,而现在我将开始使用它们。

从任何一个代码块中 break

你应该已经知道你可以从任意循环中 break 和 continue —— 这是一个相当标准的程序设计语言结构。但你可能没有意识到,你可以给循环添加一个 label ,然后跳出任意层循环:

outer: for(var i = 0; i < 4; i++) {
    while(true) {
        continue outer;
    }
}

label 特性同样适用于 breakcontinue。你在 switch 语句中肯定见过 break:

switch(i) {
   case 1:
       break;
}

顺便说一句,这是为什么 Crockford 建议你的 case 不应该缩进 —— 因为 break 跳出的是 switch 而不是 case,但是我认为缩进 case 的可读性更好。你也可以给 switch 语句添加 label:

myswitch: switch(i) {
   case 1:
       break myswitch;
}

你可以做的另一件事是创建任意块(我知道你可以在 C# 里面这么写,我期望其他语言也可以)。

{
  {
      console.log("I‘m in an abritrary block");
  }
}

因此,我们可以把 label 和 break 放在一起,用来从任意代码块中跳出。

outer: {
  inner: {
      if (true) {
        break outer;
      }
  }
  console.log("I will never be executed");
}

注意到,这只适用于 break —— 因为你只能在一个循环中 continue。我从未见过 label 被使用在 JavaScript 中,我想知道为什么 —— 我想可能因为如果我需要 break 两层,说明把这个代码块放在一个函数里可能更好,这样我可以使用一个单层的 break 或者一个提前的 return 来达到同样的目的。

尽管如此,如果我想要保证每个函数只有一个 return 语句(这不是我的菜),那么我可以使用带 label 的 brock。例如,看下面这个多个 return 语句的函数:

function(a, b, c) {
  if (a) {
     if (b) {
       return true;
     }
     doSomething();
     if (c) {
       return c;
     }
  }
  return b;
}

而如果使用 label:

function(a, b, c) {
  var returnValue = b;
  myBlock: if (a) {
     if (b) {
       returnValue = true;
       break myBlock;
     }
     doSomething();
     if (c) {
       returnValue = c;
     }
  }
  return returnValue;
}

还有另一种选择,用更多代码块……

function(a, b, c) {
  var returnValue = b;
  if (a) {
     if (b) {
       returnValue = true;
     } else {
       doSomething();
       if (c) {
         returnValue = c;
       }
    }
  }
  return returnValue;
}

我最喜欢原版,然后是使用 else 的版本,最后才是使用 label 的版本 —— 但是,这可能是因为我的写码习惯?

解构一个已存在的变量

首先,有个怪异的写法我无法解释。貌似 ES3 中你可以添加一个小括号到一个简单的赋值语句左边的变量上,而这样写不会有问题:

var a;
(a) = 1;
assertTrue(a === 1);

如果你能想到为什么这样写可以,请在底下评论!

解构的过程是一个将变量从一个数组或者一个对象中拉取出来的过程。最常见的是以下例子:

function pullOutInParams({a}, [b]) {
  console.log(a, b);
}
function pullOutInLet(obj, arr) {
  let {a} = obj;
  let [b] = arr;
  console.log(a, b);
}
pullOutInParams({a: "Hello" }, ["World"]);
pullOutInLet({a: "Hello" }, ["World"]);

而你可以不使用 varletconst。对数组你可以让下面的代码如你的期望运行:

var a;
[a] = array;

但是,对于对象,你必须将整个赋值语句用小括号括起来:

var a;
({a} = obj);

必 须这样写的理由是,不加括号无法区分代码是解构赋值还是块级作用域,因为你可以使用匿名代码块而 ASI(automatic semi-colon insertion,自动插入括号)会将变量转成可以执行的表达式(如下面的例子所示,能够产生副作用……),这样就产生了歧义。

var a = {
   get b() {
     console.log("Hello!");
   }
};
with(a) {
  {
    b
  }
}

回到原始的例子,我们给我们的赋值语句里的变量加了圆括号 —— 你可能认为它也适用于解构,但它不是。

var a, b, c;
(a) = 1; //这句不是变量解构
[b] = [2];
({c} = { c : 3 });

对数值进行解构

解构的另一个方面你可能也没有意识到,属性名不是必须要是不带引号的字符串,它们也可以是数值:

`var {1 : a} = { 1: true };`

或者带引号的字符串:

`var {"1" : a} = { "1": true };`

或者你可能想要用一个计算的表达式作为名字:

var myProp = "1";
var {[myProp] : a} = { [myProp]: true };

这会很容易写出造成困惑的代码:

var a = "a";
var {[a] : [a]} = { a: [a] };

类声明是块级作用域的

函数声明会被提升,意味着你可以将函数声明写在函数调用之后:

func();
function func() {
  console.log("Fine");
}

函数表达式与此相反,因为赋值一个变量的时候,变量声明被提升,但是具体赋值没有被提升。

func(); // func 被声明, 但是值为 undefined, 所以这里抛出异常: "func is not a function"
var func = function func() {
  console.log("Fine");
};

类(Classes)成为 ES6 流行的部分,并且已被广泛吹捧为函数的语法糖。所以你可能会认为以下代码是可以工作的:

new func();

class func {
  constructor() {
    console.log("Fine");
  }
}

然而,尽管它基本上是语法糖,但前面的代码是不能工作的。这实际上等价于:

new func();

let func = function func() {
  console.log("Fine");
}

这意味着我们的 func 调用在暂时性死区(TDZ),这会导致引用错误。

同名参数

我认为不可能指定同名的参数,然而,却可以!

function func(a, a) {
  console.log(a);
}

func("Hello", "World");
// 输出 "World"

在严格模式下不行:

function func(a, a) {
  "use strict";
  console.log(a);
}

func("Hello", "World");
// 在 chrome 下报错 - SyntaxError: Strict mode function may not have duplicate parameter names

typeof 不安全

好吧,我偷了这篇文章的结论,但是这值得强调。

在 ES6 之前,众所周知使用 typeof 总是能安全地找出某个变量的定义,不管它是否被声明:

if (typeof Symbol !== "undefined") {
  // Symbol 可用
}
// 下面的代码抛异常,如果 Symbol 没有被声明
if (Symbol !== "undefined") {
}

但是,现在这个在不使用 let 或者 const 声明变量的时候才好使。因为有了 TDZ,会导致变量未声明时产生引用错误。从本质上讲,变量被提升到块级作用域的开始,但是在声明前的任何访问都会产生引用错误。在 JSHint 的作用域管理中,我必须记录一个变量的用法,如果它使用 let 或者 const 声明于当前块级作用域或者它的父级作用域,提前访问就会有引用错误。而如果是使用 var 语句声明的,那么它就是可用的,但是 JSHint 会给出一个警告,而如果它没有被声明,那么它使用全局作用域,JSHint 可能会有另外一种警告。

if (typeof Symbol !== "undefined") {
  // Symbol 不可用,产生 reference error
}
let Symbol = true;

新数组

我总是避免使用 new Array 构造函数,一部分原因是因为它的参数既可以是一个长度又可以是一个元素列表:

new Array(1); // [undefined]
new Array(1, 2); // [1, 2]

但是,一个同事最近使用它遇到了一些我以前没有见过的东西:

var arr = new Array(10);
for(var i = 0; i < arr.length; i++) {
  arr[i] = i;
}
console.dir(arr);

上面的代码产生一个 0 到 9 的数组。然而,如果将它重构为使用 map:

var arr = new Array(10);
arr = arr.map(function(item, index) { return index; });
console.dir(arr);

上面的代码运行后 arr 没有变化。似乎 new Array(length) 用指定长度创建了一个数组,但是没有设置任何值,所以引用它的长度可以工作,但是枚举元素不可以。如果我设置一个数值会怎么样?

var arr = new Array(10);
arr[8] = undefined;
arr = arr.map(function(item, index) { return index; });
console.dir(arr);

现在我得到了一个数组,第 8 个元素等于 8,但是其他所有的值依然是 undefined。看一下 map 的 polyfill 实现,它循环每一个元素(这是为什么 index 是正确的),但是它使用的是 in 来检查一个属性是否被设置。你如果使用数组直接量,也会得到同样的结果。

var arr = [];
arr[9] = undefined;
// or
var arr = [];
arr.length = 10;

原文链接:http://blog.scottlogic.com/2015/07/02/surprising-things-about-js.html
时间: 2024-09-29 12:25:53

7 个令人惊讶的 JavaScript “特的相关文章

那些短小精悍的&amp;奇葩的&amp;令人感到惊讶的JavaScript代码----更新中

自学习前端以来,陆陆续续遇见很多短小令人惊讶的js代码,固有了专门开一片日记来记录这些神奇的代码的想法,目的还是以学习,观赏为主. 1.JavaScript中 (a ==1 && a== 2 && a==3) 可能为 true 吗? 来自Stack Overflow的一个问题:链接 国外面试题,Nothing is impossible. 解决方案1: 自定义 toString(或者 valueOf)方法,每次调用改变一次返回值,从而满足判断条件. const a = {

Linux小技巧之令人惊讶的命令who is sb

[root@localhost ~]# who is iroot     pts/1        2014-07-30 20:40 (172.22.150.15)[root@localhost ~]# who is sbroot     pts/1        2014-07-30 20:40 (172.22.150.15) 看到这个,瞬间蛋疼. 大家可以在监控脚本中,写上这个,纯属娱乐. Linux小技巧之令人惊讶的命令who is sb

10种令人惊讶的方式你的日常生活中正在收集数据的大数据野兽

10种令人惊讶的方式你的日常生活中正在收集数据的大数据野兽 原文:http://www.bloomberg.com/slideshow/2014-06-03/10-surprising-ways-your-daily-life-is-feeding-the-big-data-beast.html 无处可逃 大数据是你日常生活中,不管你喜欢与否的重要组成部分 - 甚至是意识到这一点. 当你去看医生,去到你的手机上工作或获得方向,有一个很好的机会,有软件在那里悄悄地收集和分析这些信息.并根据不同的情

NodeJS无所不能:细数10个令人惊讶的NodeJS开源项目

在几年的时间里,NodeJS逐渐发展成一个成熟的开发平台,吸引了许多开发者.有许多大型高流量网站都采用NodeJS进行开发,像PayPal,此外,开发人员还可以使用它来开发一些快速移动Web框架. 除了Web应用外,NodeJS也被应用在许多方面,本文盘点了NodeJS在其它方面所开发的十大令人神奇的项目,这些项目涉及到应用程序监控.媒体流.远程控制.桌面和移动应用等等. 1.NodeOS NodeOS是采用NodeJS开发的一款友好的操作系统,该操作系统是完全建立在Linux内核之上的,并且采

[转载] NodeJS无所不能:细数十个令人惊讶的NodeJS开源项目

转载自http://www.searchsoa.com.cn/showcontent_79099.htm 在几年的时间里,Node.JS逐渐发展成一个成熟的开发平台,吸引了许多开发者.有许多大型高流量网站都采用Node.JS进行开发,像PayPal,此外,开发人员还可以使用它来开发一些快速移动Web框架. 除了Web应用外,NodeJS也被应用在许多方面,本文盘点了NodeJS在其它方面所开发的十大令人神奇的项目,这些项目涉及到应用程序监控.媒体流.远程控制.桌面和移动应用等等. 1.NodeO

10个令人惊讶的NodeJS开源项目

在几年的时间里,NodeJS逐渐发展成一个成熟的开发平台,吸引了许多开发者.有许多大型高流量网站都采用NodeJS进行开发,像PayPal,此外,开发人员还可以使用它来开发一些快速移动Web框架. 除了Web应用外,NodeJS也被应用在许多方面,本文盘点了NodeJS在其它方面所开发的十大令人神奇的项目,这些项目涉及到应用程序监控.媒体流.远程控制.桌面和移动应用等等. 1.NodeOS NodeOS是采用NodeJS开发的一款友好的操作系统,该操作系统是完全建立在Linux内核之上的,并且采

JavaScript - 收藏集 - 掘金

Angular 中的响应式编程 -- 浅淡 Rx 的流式思维 - 掘金第一节:初识Angular-CLI第二节:登录组件的构建第三节:建立一个待办事项应用第四节:进化!模块化你的应用第五节:多用户版本的待办事项应用第六节:使用第三方样式库及模块优化用第七节:给组件带来活力Rx--隐藏在 Angular 中的利剑Redux你的 A... Electron 深度实践总结 - 前端 - 掘金思维导图 前言: Electron 从最初发布到现在已经维护很长一段时间了,但是去年才开始慢慢升温.笔者个人恰好

2016年7个顶级JavaScript框架

当涉及到Web开发时,JavaScript框架往往是一些开发人员和企业最受欢迎的平台.可能,你有机会尝试过一两个顶级的JavaScript框架,但你仍然有点不确定哪个才是最佳的最值得掌握的,或者哪个值得你建议你的开发人员选择用于下一个web开发项目. JavaScript正在以惊人的速度前进,并且添加新的技能到你的存储库变得有不断的压力.为了做到这一点,知道和了解更多的顶级JavaScript框架在现在看来是必要的.在ValueCoders进行了彻底的研究后,我们入围了其中七个顶级框架,它们是:

JavaScript谁动了你的代码

到目前为止,同学你知道了JavaScript的历史,也了解其"你想是啥就是啥"的变量系统.相信凭借你深厚的Java或者C++功底,再加上程序员特有的自傲气质,你肯定会信心满满:自信写JavaScript毫无压力.我也相信写个Script对于后端攻城师们那肯定不在话下.但是,当结果匪夷所想的时候,你或许会一番吐槽:真TM见鬼了,会不会是什么bug?还是浏览器有问题?我的代码逻辑没问题啊.......就像如下代码,你能说出结果是什么吗? var a=123; var b=999; func