Web

JavaScript 走马观花

[src: 粗点心战争]

为了写博客被迫学了一点点JavaScript,把受的苦记下来,引用内容全来自MDN。

自动分号插入

MDN上描述:

一些 JavaScript 语句必须用分号结束,所以会被自动分号补全 (ASI)影响:

  • 空语句
  • let、const、变量声明
  • import、export、模块定义
  • 表达式语句
  • debugger
  • continue、break、throw
  • return

ECMAScript 规格提到自动分号补全的三个规则。

  1. 当出现一个不允许的行终止符或“}”时,会在其之前插入一个分号。

    { 1 2 } 3 
    // 将会被 ASI 转换为
    { 1 2 ;} 3;

  2. 当捕获到标识符输入流的结尾,并且无法将单个输入流转换为一个完整的程序时,将在结尾插入一个分号。 在下面这段中,由于在 b 和 ++ 之间出现了一个行终止符,所以 ++ 未被当成变量 b 的后置运算符。

    a = b
    ++c
    // 将被 ASI 转换为
    a = b;
    ++c;

  3. 当语句中包含语法中的限制产品后跟一个行终止符的时候,将会在结尾插入一个分号。带“这里没有行终止符”规则的语句有:

    • 后置运算符(++ 和 --)
    • continue
    • break
    • return
    • yield、yield*
    • module
return
a + b
// 将被 ASI 转换为
return;
a + b;

可能比较坑的只有 3,权衡一下还是不打分号对手指和键盘比较友好

Array.prototype.sort()

活见鬼的函数,默认将元素转为字符串操作:

[1, 30, 4, 21, 100000].sort()
// [1, 100000, 21, 30, 4]

对Number排序需要提供比较函数,比较函数是三路比较,和某语言新标准的飞船运算符一样:

[1, 30, 4, 21, 100000].sort((a,b)=>a-b)
// [1, 4, 21, 30, 100000]

更难受的是排序算法没有提供不修改原序列的版本,简直对不起其它的一堆FP味的函数。

var, let, const

var

看下面一个例子:

var change = "hello";
function f() {
var change = "scram";
}
f();
console.log(change); // 输出 scram

变量声明,无论发生在何处,都在执行任何代码之前进行处理。用 var 声明的变量的作用域是它当前的执行上下文,它可以是嵌套的函数,或者对于声明在任何函数外的变量来说是全局。如果你重新声明一个 JavaScript 变量,它将不会丢失其值。

重复的 var 定义将被忽视,但是初始化语句会执行。

赋值给未声明的变量

当赋值给未声明的变量, 则执行赋值后, 该变量会被隐式地创建为全局变量(它将成为全局对象的属性)。 声明和未声明变量之间的差异是:

  1. 声明变量的作用域限制在其声明位置的上下文中,而非声明变量总是全局的。

    function x() {
    y = 1; // 在严格模式(strict mode)下会抛出 ReferenceError 异常
    var z = 2;
    }

    x();

    console.log(y); // 打印 "1"
    console.log(z); // 抛出 ReferenceError: z 未在 x 外部声明

  2. 声明变量在任何代码执行前创建,而非声明变量只有在执行赋值操作的时候才会被创建。

    console.log(a);                // 抛出ReferenceError。
    console.log('still going...'); // 打印"still going..."。
    var a;
    console.log(a); // 打印"undefined"或""(不同浏览器实现不同)。
    console.log('still going...'); // 打印"still going..."。

  3. 声明变量是它所在上下文环境的不可配置属性,非声明变量是可配置的(如非声明变量可以被删除)。

    var a = 1;
    b = 2;

    delete this.a; // 在严格模式(strict mode)下抛出TypeError,其他情况下执行失败并无任何提示。
    delete this.b;

    console.log(a, b); // 抛出ReferenceError。
    // 'b'属性已经被删除。

由于这三个差异,未能声明变量将很可能导致意想不到的结果。因此,建议始终声明变量,无论它们是在函数还是全局作用域内。 而且,在 ECMAScript 5 严格模式下,分配给未声明的变量会引发错误。

let 以及 const

let 关键字就像在其他语言中所做的工作一样,不允许重复声明,也认真对待作用域:

function varTest() {
var x = 1;
{
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}

function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}

位于函数或代码顶部的var声明会给全局对象新增属性, 而let不会。例如:

var x = 'global';
let y = 'global';
console.log(this.x); // "global"
console.log(this.y); // undefined

通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

function do_something() {
console.log(bar); // undefined
console.log(foo); // ReferenceError
var bar = 1;
let foo = 2;
}

const 和 let 差不多但不接受重赋值操作。

Hoisting

另外一个值得一提的概念——变量提升(Hoisting),MDN上描述:

变量提升意味着变量和函数的声明会在物理层面移动到代码的最前面,但这么说并不准确。实际上变量和函数声明在代码里的位置是不会动的,而是在编译阶段被放入内存中。

前面已经给了变量的例子,对于函数也有:

f(); // 没有问题

function f() {
console.log("hello");
}

JSFuck 隐式转换

false       =>  ![]
true => !![]
undefined => [][[]]
NaN => +[![]]
0 => +[]
1 => +!+[]
2 => !+[]+!+[]
10 => [+!+[]]+[+[]]
eval => []["filter"]["constructor"](CODE)()
window => []["filter"]["constructor"]("return this")()

内容来自 JSFuck

比C++过分

彩蛋

Spin the globe,应该算Quine?该作者的主页还有更多被玩坏的JS。

JS真值表

分享到