@FunC
2016-11-19T16:37:11.000000Z
字数 6689
阅读 2397
前端
JavaScript
let
块级作用域
不存在变量提升
暂时性死区(TDZ):进入let 声明所在区块时,在用let声明前都无法使用、更改该变量(即使在区块外已用var定义)
不允许在相同作用域内重复声明同一变量(另一声明用var也不行、同时也不能重复声明函数参数)
ES6中块级作用域中用function
声明函数与let
类似,仅在块级作用域中有效。(但因为会带来严重的兼容性问题,目前只在ES6实现的浏览器做出如上表现)
const
不存在变量提升
一旦声明不能更改
对于复合类型,指向的是地址,无法保证该地址的数据不变
let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
ES5中顶层对象在各种实现里不统一(浏览器、Web Worker、Node)
尚未实现的提案:引入global作为顶层对象
var
let
const
var [a, b, c] = [1, 2, 3];
//相当于
//var a = 1;
//var b = 2;
//var c = 3;
左边数量少于右边:不完全解构(但也是成功的解构)
左边数量大于右边:多出的为undefined
允许设置默认值
在数组成员严格等于(===
)undefined
时生效
见下面例子:
var [x = 1] = [undefined];
x // 1
var [x = 1] = [null];
x // null
配置默认值的逻辑如下:
let x;
if ([1][0] === undefined) {
x = f();
} else {
x = [1][0];
}
可见,如果默认值是表达式,那么将在右侧严格等于undefined时才为默认值
这点与一般默认值的认知有所差别,请注意
根据属性名称匹配(所以可以无序排列)
例子:
var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
实质为:
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
结构赋值的规则:等号右边不是对象的话先转为对象。
而undefined
null
不能转化为对象,对其进行解构赋值时会报错。
见下面代码:
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
这是为变量x,y设定默认值
注意与下面写法的区别:
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
这是为函数参数设定默认值(似乎直接给变量写默认值为更优选择,见下文函数的扩展)
大概原则就是能不加圆括号就不加圆括号,不然容易导致解析有歧义而报错。
具体见阮一峰的ES6入门
[x, y] = [y, x];
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
const { SourceMapConsumer, SourceNode } = require("source-map");
ES5中只支持\u0000 ~ \uFFFF 之间的字符,超出范围时要用双字节的形式表达
ES6支持四字节的UTF-16编码,用{}
把\u
后的码点括起来即可
未完待续......
1.参数默认值:直接在参数后写默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p = new Point();
p // { x: 0, y: 0 }
2.配合解构赋值默认值使用(不建议单独使用解构赋值默认值,原因见下文)
此处容易混淆,大致的区分方法为:不在
{}
内部的逗号分隔开的是参数,参数进行了赋值就是参数默认值;花括号内部的赋值为解构赋值默认值。当某参数只设置了解构赋值默认值时,即该参数无默认值,所以缺少该参数时会报错。
两种写法的区分
// 写法一:个人更推荐
function m1({x = 0, y = 0} = {}) {
return [x, y];
}
// 写法二
//该函数参数为一个对象,且未设置解构赋值默认值,所以一旦有参数传入但无x,y属性时,x,y便是undefined,失去默认值的意义
function m2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
需要注意的是,定义了默认值的参数应该是函数的尾参数。因为如果是非尾参数的话,在传入参数时,中间值无法省略(只能通过显式设定undefined
来忽略)
先看如下代码
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
//rest参数也不会计入length属性
(function(...args) {}).length // 0
为什么会这样呢?那是因为length属性的含义是:该函数预期传入的参数个数。
注意:如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
若参数的默认值是一个变量,那么该变量的作用域规则与其他变量作用域规则一致。即先函数作用域、后全局作用域。
见下面代码
var x = 1;
//下面x为第一个参数,先在函数作用域中生成
function f(x, y = x) {
console.log(y);
}
f(2) // 2
若参数的默认值是一个函数,那么情况将复杂得多。该函数的作用域是其声明时所在的作用域。
例如下面这个稍微复杂一点的例子:
var x = 1;
function foo(x, y = function() { x = 2; }) {
var x = 3;//若去掉var则最后结果为2,即全部的x都是一样的
y();
console.log(x);
}
foo() // 3
y函数内部的x是作为形参的x,在foo内部声明了x之后x就是一个变量了,切断了与原来形参的联系(参考以对象作参数,然后重新声明一个与参数名相同的对象)
设置函数默认值除了可以省略某参数以外,还能制定某一参数不可省略,若省略则报错。
实现方式如下:
function throwIfMissing() {
throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
return mustBeProvided;
}
foo()
// Error: Missing parameter
值得注意的是:参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(即函数名之后有一对圆括号),这揭示了参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行),这与python语言不一样。
ES6引入rest参数(形式为:...变量名),用于获取函数的多余参数。rest参数搭配的变量是一个数组(故能使用所有数组特有的方法),该变量将多余的参数放入数组中。
以改写数组push方法为例:
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
扩展运算符(spread)是三个点...
。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
该运算符主要用于函数调用:
function add(x, y) {
return x + y;
}
var numbers = [4, 38];
add(...numbers) // 42
1.合并数组
// ES5
[1, 2].concat(more)
// ES6
[1, 2, ...more]
var arr1 = ['a', 'b'];
var arr2 = ['c'];
var arr3 = ['d', 'e'];
// ES5的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
2.与解构赋值结合
用于生成数组:
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
const [first, ...rest] = [];
first // undefined
rest // []:
const [first, ...rest] = ["foo"];
first // "foo"
rest // []
如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
3.函数的返回值
JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个问题的一种变通方法。
4.扩展运算符还可以将字符串转为真正的数组
重点是能正确识别32位Unicode字符:
//三个字符分别为x、\uD83D\uDE80、y
'x\uD83D\uDE80y'.length // 4
[...'x\uD83D\uDE80y'].length // 3
凡是涉及到操作32位Unicode字符的函数,都有这个问题。因此,最好都用扩展运算符改写:
function length(str) {
return [...str].length;
}
5.实现了Iterator接口的对象
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组。
6.Map和Set结构,Generator函数
ES6中允许使用“箭头”=>
定义函数
//ES5
var f = function(v) {
return v;
};
//ES6
var f = v => v;
当不需要参数或需要多个参数时,使用圆括号括起来。
代码块多于一条语句时,用大括号括起来(因此,如果像返回一个对象,要在对象外再加一个括号)
可与解构解析结合使用
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象。- 不可当作构造函数(即不能使用
new
命令,否则报错)- 不可使用
arguement
对象,该对象在函数体内不存在(为什么?arguement
对象是通过function
命令生成的吗?)。可使用Rest参数代替。- 不可使用yield命令,所以箭头函数不能用作Generator函数
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。
除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:arguments
、super
、new.target
。
类似于数组的对象,但成员唯一。
Set本身是一个构造函数,用来生成Set数据结构
Set函数可以接受一个数组(或类似数组的对象)进行初始化。
var items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
Set可用于去除数组重复成员:
// 去除数组的重复成员,下文还会提供另一种方法
[...new Set(array)]
值得注意的是:像Set中加入值时,不会发生类型转换。所以5和"5"是不同的值。Set内部判断两个值是否不同的算法类似于精确相等运算符===
,区别在于该算法判断两个NaN
是相等的。另外,两个对象总是不相等的
属性:
Set.prototype.constructor
:构造函数,默认就是Set函数。
Set.prototype.size
:返回Set实例的成员总数。
方法:
add(value)
:添加某个值,返回Set结构本身。
delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。
has(value)
:返回一个布尔值,表示该值是否为Set的成员。
clear()
:清除所有成员,没有返回值。
Array.from
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
keys()
:返回键名的遍历器
values()
:返回键值的遍历器
entries()
:返回键值对的遍历器(在Set中键名和键值是同一个值)
forEach()
:使用回调函数遍历每个成员
Set
的遍历顺序就是插入顺序(对比对象的for...in遍历,是无序的)。这个特性有时非常有用,比如使用Set保存一个回调函数列表,调用时就能保证按照添加顺序调用。forEach()
:
let set = new Set([1, 2, 3]);
set.forEach((value, key) => console.log(value * 2) )
// 2
// 4
// 6
上述例子说明forEach
方法的参数就是一个处理函数。该函数的参数依次为键值、键名、集合本身(上例中省略了)。另外,forEach
方法还可以有第二个参数,表示绑定的this对象。
扩展运算符(...
)内部使用for...of
循环,所以也可以用于Set结构。
故可用于去除数组的重复成员。
另外,数组的map
和filter
方法也可以用于Set:
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}
let set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// 返回Set结构:{2, 4}
因此使用Set可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
如果想在遍历中同步更新原来的Set结构,目前没有直接的方法。变通的方法如下:
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6