万物科技 学,以致用

JavaScript 奇怪的JS

2017-06-01
Geng

本部分参考文献较多, 而且应该会不断更新:

Strict mode(严格模式)

严格模式是ES 5添加了第二种运行模式, 它让JS运行在更严格的环境中,严格模式有下列几点作用:

  • 捕捉Javascript奇葩代码并发出警告;
  • 避免代码不安全动作,或者报错(比如访问全局对象);
  • 提高编译器效率,增加运行速度;
  • 禁止Javascript不完善的特性;
  • 为未来新版本的Javascript做好铺垫。

可以使用: 'use strict' 进入严格模式. 老版本浏览器会把它当成一个字符串而已, 但是新版本浏览器可以识别出其作用. 它可以全局生效, 也可以局部生效, 我们稍后结合其他内容来看.

分号

JS行尾分号相加就加, 想不加就不加, 看自己了.

变量和常量

变量

在之前的介绍中, 我们已经大量使用了变量, 通过var声明一个变量即可. 但是JS奇葩的特点很多啊,我们一个一个看:

没有var

在正常模式中,如果一个变量没有声明就赋值,默认是全局变量。严格模式禁止这种用法,全局变量必须显式声明:

name = 'wanwu.tech'
'wanwu.tech'
'use strict'
age = 19
ReferenceError: age is not defined

    at evalmachine.<anonymous>:2:5

    at ContextifyScript.Script.runInThisContext (vm.js:23:33)

    at Object.runInThisContext (vm.js:95:38)

    at run ([eval]:617:19)

    at onRunRequest ([eval]:388:22)

    at onMessage ([eval]:356:17)

    at emitTwo (events.js:106:13)

    at process.emit (events.js:194:7)

    at process.nextTick (internal/child_process.js:766:12)

    at _combinedTickCallback (internal/process/next_tick.js:73:7)

观察上面两个例子可以看到, 第一个变量name在正常模式中, 没有声明直接赋值, 没有任何问题. 但是age因为工作在严格模式, 禁止不声明就使用, 所以报错.

注意我们仅仅在age = 19前面增加了'use strict'便进入了严格模式

作用域

一般来说, 变量如果在一个函数内声明, 那么它的作用域就是这个函数. 但是JS同学偏偏是’二班’的:

function foo() {
    var x = 1
    y = x + 1
}

foo()

y = y + 2
console.log('y: ' + y)
x
y: 4



ReferenceError: x is not defined

    at evalmachine.<anonymous>:10:1

    at ContextifyScript.Script.runInThisContext (vm.js:23:33)

    at Object.runInThisContext (vm.js:95:38)

    at run ([eval]:617:19)

    at onRunRequest ([eval]:388:22)

    at onMessage ([eval]:356:17)

    at emitTwo (events.js:106:13)

    at process.emit (events.js:194:7)

    at process.nextTick (internal/child_process.js:766:12)

    at _combinedTickCallback (internal/process/next_tick.js:73:7)

根据上面没有var部分的介绍, 在正常模式中, var x = 1x的作用域限制在foo()函数中, 而y因为没有用var声明, 直接变成了全局变量.

全局对象说法不是很准确, 但是其中故事太多了, 说多了都是泪, 有余力可以研究顶层对象的属性

let与var

ES6新增了let命令,用来声明变量。它的用法类似于var,但let声明的变量只存在于所在的代码块, 相对的var声明的变量存在于所在的函数. 而且, let声明的变量不存在变量提升.

变量提升: 变量可以在声明之前使用,值为undefined

查看下面代码理解上面的内容:

console.log(iAmVar)  // 声明前使用输出undefined

try {
    console.log(iAmLet)  // 声明前使用抛出异常
}
catch(exception){
    console.log('iAmLet: exception')
}

// 声明
var iAmVar = 'by var'
let iAmLet = 'by let'

// 声明后使用
console.log(iAmVar)
console.log(iAmLet)

function foo() {
    // *i*可见, *j*不可见
    
    for (var i = 0; i < 10; i++) {  
        // *i*在foo()内都可见
    }
    
    for (let j = 0; i < 10; i++) {
        // *j*只在此for()循环代码块中可见
        // 每个循环都是不同的*j*, 当前的*j*只在本轮循环有效, 所以每一次循环的*j*其实都是一个新的变量
    }
    
    console.log(i)  // for循环外部可见
    
    try {
        console.log(j)  // for循环外部不可见
    }
    catch(exception){
        console.log('j: exception')
    }
}

foo()
undefined
iAmLet: exception
by var
by let
10
j: exception





undefined

不管是var, 还是let, 它们声明的都是变量, 值是可以改变的.

iAmVar = 'another value'
iAmLet = 'also another value'

console.log(iAmVar)
console.log(iAmLet)
another value
also another value





undefined

全局中的var和let

看代码:

var byVar = 1;
let byLet = 2;

console.log(`${byVar} is by var`);
console.log(`${byLet} is by let`);

console.log(`${global.byVar} is by var`);
console.log(`${global.byLet} is by let`);
1 is by var
2 is by let
1 is by var
undefined is by let

有没有发现全局中没有byLet.

常量

ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域.

const PI = 3.14;
PI = 3; 
SyntaxError: Identifier 'PI' has already been declared

    at evalmachine.<anonymous>:1:1

    at ContextifyScript.Script.runInThisContext (vm.js:23:33)

    at Object.runInThisContext (vm.js:95:38)

    at run ([eval]:617:19)

    at onRunRequest ([eval]:388:22)

    at onMessage ([eval]:356:17)

    at emitTwo (events.js:106:13)

    at process.emit (events.js:194:7)

    at process.nextTick (internal/child_process.js:766:12)

    at _combinedTickCallback (internal/process/next_tick.js:73:7)

可见, 试图改变const声明的PI是会报错的.

const? let? var?

我不是JS专家, 目前只能根据大牛们的意见给出一个总结: 能用const就用const, 不行就是用let, 还是不行考虑var.

多维数组

多维数组可以如下创建:

var items = [
  [1, 2],
  [3, 4],
  [5, 6]
];

console.log(items[1][1])
items[1][1] = 100
console.log(items[1][1])
console.log(items)
4
100
[ [ 1, 2 ], [ 3, 100 ], [ 5, 6 ] ]





undefined

但是如果这么做的话, 再看看效果:

var items = Array(3).fill(Array(2).fill(0));

console.log(items[1][1])
items[1][1] = 100
console.log(items[1][1])
console.log(items)
0
100
[ [ 0, 100 ], [ 0, 100 ], [ 0, 100 ] ]





undefined

第一行全都变成了100, 而不仅仅是第一行第一列

Can you please explain to me why it is that var arr2D = new Array(5).fill(new Array(3));

虽然在形式上, 上面的两步 fill() 创建了一个二维数组, 但是它创建的数组, 其实都引用 Array(2).fill(0) 创建的数组. 那么其实每行都会一样

看看手册:

When the fill method gets passed an object, it will copy the passed object, and fill the array with a reference to the copy.


Comments

你可以请我喝喝茶,聊聊天,鼓励我

Wechat Pay
wechat

Thanks!