类型
JavaScript
中有七种内置类型: null
,undefined
,boolean
,number
,string
,object
,其他的类型称为基本类型。
变量没有类型, 只有变量的值有类型。
可以使用 typeof
运算符查看值的类型。 需要注意的是 typeof null
的结果是 object
。
undefined 和 undeclared
变量声明后在未持有值的时候为 undefined
,但是如果没有在作用域中声明变量,却使用了变量是 undeclared 的。
var a;
a //undefined
b //undeclared
使用 undeclared 的变量会导致浏览器报错。
可以使用下面两种方法来避免报错。
if (DEBUG) { // DEBUG is not defined.
}
if (typeof DEBUG !== "undefined") {
}
或者是
if (window.DEBUG) {}
值
数组
可以使用 delete
运算符删除数组中的元素, 但是删除后该数组的 length
不会发生变化。
稀疏数组
稀疏数组是含有空白或者空缺单元的数组。
var a = [];
a[0] = 1;
a[2] = 3;
a[1] // undefined
a.length // 3
空白单元可能会产生意想不到的结果, 虽然与 a[1] = undefined
直接赋值的结果一致, 但是还是有所区别。
使用非数字的键值作为数组的索引,会导致数组的 length
属性不会增加。
类数组
选取 DOM
元素的方法多是返回的是类数组, 但是有时候我们想调用 Array
上的函数来处理它, 这时候我们需要转换。
var doc = document.querySelectorAll('*');
doc = Array.prototype.slice.call(doc,0);
doc = Array.from(doc);
字符串
JavaScript
中的字符串是不可变的, 而数组是可变的。
var a = "foo";
var b = ["f","o","o"];
a[1] = "O";
b[1] = "O";
a // "foo"
b // ["f","O","o"]
并且a[1]
并不总是合法的语法,正确的方式是应该使用 a.charAt(1)
。
多数字符串的成员函数不会修改原有的字符串, 而是重新返回一个新的字符串。
许多数组的函数来处理字符串很方便, 虽然字符串没有这些函数, 但是可以通过将字符串转换数组的方式, 进行调用。
var c = Array.prototype.join.call(a,"-");
var d = Array.prototype.map.call(a,function (v) {
return v.toUpperCase() + '.';
});
c // "f-o-o"
d // "F.O.O"
数字
数字的部分语法
特别大的和特别小的数字用指数格式显示。
var a = 5E10;
a // 50000000000
a.toExponential(); // "5e+10" 这是个字符串...
var a = 42.59;
a.toFixed(0); // 43
a.toFixed(1); // 42.6
a.toFixed(2); // 42.59
a.toFixed(3); // 42.590
var a = 42.59;
a.toPrecision(1); // "4e+1"
a.toPrecision(2); // "43"
a.toPrecision(3); // "42.6"
a.toPrecision(4); // "42.59"
a.toPrecision(5); // "42.590"
上面方法不仅适用于数字变量, 而且还适用于数字常量。 不过对于 .
运算符需要注意, 它首先会被识别成数字常量的一部分,然后才会被认为是对象属性访问运算符。
42.toFixed(0); // Syntax Error
(42).toFixed(0);
0.42.toFixed(0);
42..toFixed(0);
较小的数字
应该如何在 JavaScript
当中判断 0.1 + 0.2
时候和 0.3
相等。
常见的方法是设置一个误差范围值, 从 ES6
开始这个值被定义在 Number.EPSILON
中。
特殊的数字
不是数字的数字
NaN
是一个数字类型, 但是它代表的意思却不是一个数字。
var a = 2 / "foo" // NaN
typeof a === "number" // true
NaN
是唯一一个与自身不相等的值。
判断一个值是否是 NaN
方法是 使用 ES6
的 Number.isNaN()
, 现在已经不推荐使用 window.isNaN()
。
if (!Number.isNaN) {
Number.isNaN = function (n) {
return n !== n;
}
}
生成器
打破完整运行
在 ES6
之前的语法中, 函数的运行不会被任何其他代码能够打断它的运行, 但是在 ES6
里, 新的 Generator
语法, 能够打断函数的执行。
var x = 1;
function *foo () {
x++;
yield;
console.log("x:",x);
}
function bar () {
x++;
}
我们要如何运行前面这段代码:
var it = foo();
it.next(); // 在这条语句启动 foo
x; // 2
bar();
x; //3
it.next(); // x: 3
上面的代码做了这些事:
it = foo()
运算并没有执行生成器*foo()
, 而是构造了一个迭代器, 这个迭代器会控制它的执行。第一个
it.next()
启动了生成器*foo()
,并且运行了*foo()
第一行的x++
。*foo()
在yield
语句处暂停, 这一点上第一个it.next()
调用结束。 此时*foo()
仍在运行并且是活跃的, 但处在暂停状态。查看
x
的值。我们调用
bar()
,它通过x++
再次递增。再次查看
x
的值。最后的
it.next()
调用从暂停处恢复生成器*foo()
的执行, 并且运行console.log()
语句。
输入和输出
生成器函数是一种特殊的函数, 但是它依然是一个函数, 这意味着它仍有一些函数的基本特性, 比如接受参数和有返回值。
function *foo(x,y) {
return x * y;
}
var it = foo(6,7);
var res = it.next();
res.value; // 42
每次调用 it.next()
都会返回一个对象,里面包含一个 value
属性, 是函数的返回值。 对象中其他属性后面有介绍。
迭代消息传递
除了能够接受参数并接受返回值外, 生成器提供了内建消息输入输出能力。
function *foo(x) {
var y = x * (yield);
return y;
}
// 启动 foo()
var it = foo(6);
it.next();
var res = it.next(7);
res.value; // 42
首先, 将 6
作为参数传入。 然后调用 it.next()
, 启动 *foo()
。
在 *foo()
内部, 开始执行语句 var y = x ..
, 但是随后就遇到了一个 yield
表达式。 它就停在了这个赋值语句的中间。 并且要求调用代码为 yield
语句提供一个结果值。 接下来, 调用 it.next(7)
, 这条语句将作为结果, 把 7
传回被暂停 yield
表达式。
然后 return y
作为返回值, 体现在 res.value
中。
根据你的视角不同, yield
和 next
调用有一个不匹配。 一般来说, next
的调用要比 yield
语句多一个。 第一个 next()
调用总是启动一个生成器。
yield 的双向消息传递
yield
作为一个表达式可以发出接受从 next()
来的消息, 也能够从向 next()
返回一些值。
function *foo(x) {
var y = x * (yield "hello");
return y;
}
var it = foo(6);
var res = it.next(); // 第一个 next() ,并不传入任何东西
res.value; // "hello"
res = it.next(7);
res.value; // 42
只有暂停的 yield
才能接受从 next()
传递的值, 而在生成器的起始处并没有这样的 yield
。 规范和兼容的浏览器都会静默丢弃传递给第一个 next()
的任何值。
多个迭代器
其实, 我们每次构建一个迭代器时, 实际上隐式构建了生成器的一个实例, 通过这个迭代器来控制的是这个生成器的实例。
function *foo() {
var x = yield 2;
z++;
var y = yield (x * z);
console.log(x,y,z);
}
var z = 1;
var it1 = foo();
var it2 = foo();
var val1 = it1.next().value; // 2 -- yield 2;
var val2 = it2.next().value; // 2 -- yield 2;
val1 = it1.next(val2 10).value; // 40 -- x: 20,z: 2
val2 = it2.next(val1 5).value; // 600 -- x: 200,z: 3
it1.next(val2 / 2); // x: 20,y: 300,z: 3
it2.next(val1 / 4); // x: 200,y: 10,z: 3