这片文章主要是基于阮一峰老师的ECMAScript 6 入门。在看了阮一峰老师的这ES6入门以后,本身作了一下总结,将一些以为对本身目前有用的东西整理出来方便往后再来巩固复习。总以为看别人的东西当时懂了过了一段时间就忘记了,因此我老是会将别人的东西验证一遍,这样对知识的理解是能提高一个层次的。javascript
前者是后者的规格,后者是前者的一种实现。ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版之后的 JavaScript 的下一代标准,涵盖了 ES201五、ES201六、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。html
let用来声明变量。它的用法相似于var,可是所声明的变量,只在let命令所在的代码块内有效。
let和const有几个特色:
一、 不存在变量声明提高;
二、 暂时性死区(只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。);
三、 不容许重复声明。不容许在相同做用域内,重复声明同一个变量。
四、 块级做用域java
块级做用域的例子
{
let a=12;
var b=23;
}
console.log(b);//23
console.log(a);// a is not defined
for(let i=0;i<10;i++){
}
console.log(i);// is not defined
-----------------------------------------------------------------
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
复制代码
分析:变量i是let声明的,因此i只在let声明的代码块内有效。for循环一共循环了10次,每一次都是一个独立的代码块——{},因此每次循环中的i都是独立的,当前的i只在当前循环有效,因此每一次循环的i其实都是一个新的变量,因此最后输出的是6。
for循环还有一个特别之处,就是设置循环变量的那部分是一个父做用域,而循环体内部是一个单独的子做用域。JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。node
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
复制代码
2.2 不存在变量声明提高es6
// var 的状况
console.log(foo); // 输出undefined,变量声明提高,至关于在输出以前就var foo;
var foo = 2;
// let 的状况
console.log(bar); // 报错ReferenceError,没有变量声明提高
let bar = 2;
————————————————————————————————————————————————————————————
复制代码
2.3 暂时性死区(temporal dead zone,简称 TDZ)
在区块中使用let和const命令声明的变量,从一开始就造成了封闭做用域。凡是在声明以前就使用这些变量,就会报错(声明以前都是死区)。本质:只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。ajax
2.4 不容许重复声明算法
let不容许在相同做用域内,重复声明同一个变量。编程
function func() {
let a = 10;
var a = 1;
}
funb()// // 报错 Identifier 'a' has already been declared
function func() {
let a = 10;
let a = 1;
}
func()// 报错 Identifier 'a' has already been declared
————————————————————————————————————————————————————————
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错 参数x默认值等于另外一个参数y,而此时y尚未声明,属于"死区"(参数读取从左至右)。
复制代码
不能在函数内部从新声明参数。json
function funb(arg) {
let arg;
}
func() // 报错Identifier 'arg' has already been declared 形参arg跟局部变量arg在同一个{}内,因此报错
function func(arg) {
{
let arg;
console.log(arg);//undefined
}
console.log(arg);//34
}
func(34)
复制代码
2.5 块级做用域数组
优势:
一、没有块级做用域,内层变量可能会覆盖外层变量(变量声明提高)。
二、用来计数的循环变量会泄露为全局变量。
特色:
一、 容许任意嵌套。
二、 外层做用域没法读取内层做用域的变量。
三、 使得当即执行函数再也不必要了。
四、 容许在块级做用域中声明函数,函数声明相似于var,函数声明会提高到所在的块级做用域的头部。
2.6 const
const一旦声明变量,就必须当即初始化,不能留到之后赋值,且变量的值也不能改变。本质:并非变量的值不得改动,而是变量指向的那个内存地址所不得改动。
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,所以等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即老是指向一个固定的地址),至于它指向的数据结构是否是可变的,就彻底不能控制了。
一、变量指向的是对象时,能够改变该对象的属性。可是不可将该变量指向另外一个对象。
const obj = {}
// 为 foo 添加一个属性,能够成功
obj.prop = 123;
// 将 obj 指向另外一个对象,就会报错,此时已经改变了obj所指向的内存地址了
obj = {}; // TypeError: "foo" is read-only
一、变量指向的是数组时,能够改变该数组中的元素及数组的属性。可是不可将该变量指向另外一个数组。
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错,指向了另外一个数组
复制代码
2.7 ES6声明变量的6种方式
var、function、let、const、class、import。es5只有var和function两种。
顶层对象的差别: 在浏览器环境指的是window对象,在 Node中 指的是global对象,在Web Worker 里面,self也指向顶层对象。
ES5 之中,顶层对象的属性与全局变量是等价的。ES6中的var命令和function命令声明的全局变量,依旧是顶层对象的属性;let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
window.a // 1
let b = 1;
window.b // undefined
复制代码
ES6容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。本质上,这种写法属于模式匹配,只要等号两边的模式相同,左边的变量就会被赋予对应的值。 若是解构不成功,变量的值就等于undefined。
let [foo = true] = [];foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null]; x //null null不严格等于undefined,可是null==undefined
let [x = 1, y = x] = []; // x=1; y=1
let [x = 1, y = x] = [2]; // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = []; // y is not undefined
从左到右的读取。
let [x , y] = [];//[undefined,undefined]
复制代码
注意:ES6 内部使用严格相等运算符(===),判断一个位置是否有值。因此,只有当一个数组成员严格等于undefined,默认值才会生效。若是一个数组成员是null,默认值就不会生效,由于null不严格等于undefined。
对象的解构与数组有一个重要的不一样。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };baz // undefined
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz//"aaa",foo是匹配的模式,baz才是变量名
let { foo, bar } = { foo: "aaa", bar: "bbb" };
是下面表示的简写。
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
复制代码
因为数组本质是特殊的对象,所以能够对数组进行对象属性的解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于属性名表达式。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
复制代码
字符串被转换成了一个相似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
复制代码
相似数组的对象都有一个length属性,所以还能够对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
复制代码
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
复制代码
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。 因为undefined和null没法转为对象,因此对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
复制代码
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]
复制代码
上面代码中,函数move为变量x和y指定默认值,函数move的参数是一个对象,经过对这个对象进行解构,获得变量x和y的值。若是解构失败,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],至关于{x,y}={x,3}
move({}); // [undefined, undefined],至关于{x,y}={};
move(); // [0, 0]
复制代码
上面代码是为函数move的参数(形参)指定默认值,而不是为变量x和y指定默认值,因此会获得与前一种写法不一样的结果。这种写法直接是将所传参数将默认参数进行覆盖。用实参将{x:0,y:0}覆盖。
上面两种写法本质上都是用所传参数将默认参数进行覆盖。
let{x,y}={y,x};
复制代码
(2) 从函数返回多个值;
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
复制代码
(3) 函数参数的定义;
function f([x, y, z]) { ... }
f([1, 2, 3]);
复制代码
(4) 提取json数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
复制代码
(5) 输入模块的指定方法;
const { SourceMapConsumer, SourceNode } = require("source-map");
复制代码
(6) 函数参数的默认值(这样避免了在函数内部再设置默认值)
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
复制代码
(7) 遍历map结构(map原生支持iterator接口)
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
复制代码
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。 endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
这三个方法都支持第二个参数,表示开始搜索的位置。endsWith的行为与其余两个方法有所不一样,n表示的是结束搜索的位置,而其余两个方法针n个表示的是开始搜索的位置。
repeat方法返回一个新字符串,表示将原字符串重复n次。 参数若是是小数,会被取整。至关于调用了parseInt()。
”a”.repeat(1.9)==>”a”.
复制代码
若是repeat的参数是字符串,则会先转换成数字。
'na'.repeat('na') // "" Number("na")等于NAN
'na'.repeat('3') // "nanana"
复制代码
padStart()用于头部补全,padEnd()用于尾部补全。padStart()和padEnd()一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。 (1) 若是原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。
'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'
'xxx'.padStart(5, 'ab') // 'abxxx'
'xxx'.padEnd(5, 'ab') // 'xxxab'
复制代码
(2) 若是用来补全的字符串与原字符串,二者的长度之和超过了最大长度,则会截去超出位数的补全字符串。
'abc'.padStart(10, '0123456789') // '0123456abc'
复制代码
(3) 若是省略第二个参数,默认使用空格补全长度。
'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '
复制代码
是加强版的字符串,用反引号(`)标识。
(1) 若是在模板字符串中须要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
复制代码
(2) 全部模板字符串的空格和换行,都是被保留的,好比ul标签前面会有一个换行。若是你不想要这个换行,可使用trim方法消除它。
$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());
复制代码
(3)模板字符串中嵌入变量,须要将变量名写在${}之中。
(4)大括号内部能够放入任意的 JavaScript 表达式,能够进行运算,以及引用对象属性。若是大括号中的值不是字符串,将按照通常的规则转为字符串。本质:模板字符串的大括号内部,就是执行 JavaScript 代码。
let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
复制代码
(5)模板字符串之中还能调用函数。
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
复制代码
这两个新方法只对数值有效,不会先调用Number()方法。
Number.isFinite(): 用来检查一个数值是否为有限的(finite)。若是参数类型不是数值,Number.isFinite一概返回false。
Number.isNaN(): 用来检查一个值是否为NaN。Number.isNaN()只有对于NaN才返回true,非NaN一概返回false。
Number.isSafeInteger()则是用来判断一个整数是否落在这个范围以内。javaScript 可以准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,没法精确表示这个值。Number.MAX_SAFE_INTEGER和Number.MIN_SAFE_INTEGER这两个常量,用来表示这个范围的上下限。
扩展方法在使用时都会参数使用Number()转为数值来来处理。
Math.trunc(): 用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值(本质上就是parseInt()方法)。
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
复制代码
Math.sign(): 用来判断一个数究竟是正数、负数、仍是零。
• 参数为正数,返回+1;
• 参数为负数,返回-1;
• 参数为 0,返回0;
• 参数为-0,返回-0;
• 其余值,返回NaN。
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
复制代码
Math.cbrt(): 用于计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。
Math.cbrt(-1) // -1
Math.cbrt(0) // 0
Math.cbrt(1) // 1
Math.cbrt(2) // 1.2599210498948734
Math.cbrt("8")//2
复制代码
Math.imul(): 方法返回两个数以 32 位带符号整数形式相乘的结果,返回的也是一个 32 位的带符号整数。
Math.imul(2, 4) // 8
Math.imul(-1, 8) // -8
Math.imul(-2, -2) // 4
复制代码
Math.hypot(): 方法返回全部参数的平方和的平方根。
Math.hypot(3, 4); // 5
Math.hypot(3, 4, 5); // 7.0710678118654755
Math.hypot(); // 0
Math.hypot(NaN); // NaN
Math.hypot(3, 4, 'foo'); // NaN
Math.hypot(3, 4, '5'); // 7.0710678118654755
Math.hypot(-3); // 3
复制代码
参数变量是默认声明的,因此不能用let或const再次声明。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
foo()//Identifier 'x' has already been declared
复制代码
指定了默认值之后,函数的length属性,将返回没有指定默认值以前的的参数的个数。 也就是说,指定了默认值后,length属性将失真。默认值后面的参数将不参加计算。函数的length属性,不包括 rest 参数。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
复制代码
一旦设置了参数的默认值,函数进行声明初始化时,参数会造成一个单独的做用域(context)。等到初始化结束,这个做用域就会消失。(本质上是暂时性死区和不能重复声明)
var x = 1;
function f(x, y = x) {
//let x=3;Identifier 'x' has already been declared
//let y=7;// Identifier 'y' has already been declared
console.log(y);//2
}
f(2)
复制代码
形式为(...变量名),用于获取函数的多余参数,这样就不须要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。注意:rest参数以后不能再有其余参数(即只能是最后一个参数),不然会报错。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log(item);
});
}
var a = [];
push(a, 1, 2, 3)
复制代码
若是将一个匿名函数赋值给一个变量,ES5的name属性,会返回空字符串,而 ES6 的name属性会返回实际的函数名。 若是将一个具名函数赋值给一个变量,则 ES5 和 ES6 的name属性都返回这个具名函数本来的名字。
const bar = function baz() {};
// ES5
bar.name // "baz"
// ES6
bar.name // "baz"
复制代码
ES6 容许使用“箭头”(=>)定义函数。若是箭头函数不须要参数或须要多个参数,就使用一个圆括号表明参数部分。若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return语句返回。 若是箭头函数直接返回一个对象,必须在对象外面加上括号 ,不然会报错。
// 报错
let getTempItem = id => { id: id, name: "Temp" };
// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });
复制代码
箭头函数须要注意的地方有如下几点
1. 函数体内的this对象,就是定义时所在的对象(固定不变),而不是使用时所在的对象。
2. 不能够看成构造函数, 也就是说,不可使用new命令,不然会抛出一个错误。 3. 不可使用arguments对象, 该对象在函数体内不存在。若是要用,能够用 rest 参数代替。
4. 不可使用yield命令, 所以箭头函数不能用做 Generator 函数。
function Timer() {
this.s1 = 0;
this.s2 = 0;
// 箭头函数
setInterval(() => this.s1++, 1000);
// 普通函数
setInterval(function () {
this.s2++;//this表示window,setInterval是window的属性
}, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
复制代码
上面代码中,Timer函数内部设置了两个定时器,分别使用了箭头函数和普通函数。前者的this绑定定义时所在的做用域(即Timer函数),后者的this指向运行时所在的做用域(即全局对象)。因此,3100 毫秒以后,timer.s1被更新了 3 次,而timer.s2一次都没更新。 this固定化的本质:并非由于箭头函数内部有绑定this的机制,实际缘由是箭头函数根本没有本身的this,致使内部的this就是外层代码块的this。 正常状况下,this引用的是函数据以执行的环境对象,或者说是调用该函数的对象。
function foo() {
return () => {
return () => {
return () => {
console.log('id:', this.id);
};
};
};
}
var f = foo.call({id: 1});
var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
复制代码
上面代码之中,只有一个this,就是函数foo的this,因此t一、t二、t3都输出一样的结果。由于全部的内层函数都是箭头函数,都没有本身的this,它们的this其实都是最外层foo函数的this。
箭头函数不适用场合:
一、定义函数的方法(此时应该用普通函数的方式)
var lives=18;
const cat = {
lives: 9,
a:this.lives,//this指向的是window,
say:function(){
console.log(this.lives);//this指的是cat
},
jumps: () => {
this.lives--;//this指的是window,定义时的this指的就是window
}
}
cat.say();
cat.jumps();
console.log(cat.a);
复制代码
二、须要动态this的时候
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');//this指的是window
})
复制代码
适用场合:回调
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);//this指的是handler
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);//this指的是hander
}
};
复制代码
是指某个函数的最后一步是调用另外一个函数。 尾调用因为是函数的最后一步操做,因此不须要保留外层函数的调用帧,由于调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就能够了。(就是说外层函数的做用域链会被销毁,但它的活动对象任然会留在内存中)
function f(x){
return g(x);
}
复制代码
尾调用自身,就称为尾递归。缺点:把全部用到的内部变量改写成函数的参数。优势:不会发生栈溢出,相对节省内存。
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
复制代码
采用es6语法(参数的默认值)能够解决这个缺点
function factorial(n, total=1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
复制代码
扩展运算符(spread)是三个点(...)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。 扩展运算符背后调用的是遍历器接口(Symbol.iterator),若是一个对象没有部署这个接口,就没法转换。
注意:扩展运算符(spread)是三个点(...)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。本质上就是rest参数
console.log(…[1,2,3])// 1 2 3
复制代码
扩展运算符的应用:
一、 替代函数的apply用法
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
复制代码
二、求取数组中的最大值:
// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);
复制代码
三、 简化push函数的用法
// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);
// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);
复制代码
四、 复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
写法一二至关于把数组中a1的元素复制到a2中
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]
上面两个方法修改a2都不会对a1产生影响。
复制代码
五、 合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
这两种方法都是浅拷贝,使用的时候须要注意。
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ...a2];
console.log(a3[0] === a1[0]) // true 指向相同的内存地址
console.log(a4[0] === a1[0]) // true 指向相同的内存地址
a3[0].foo=2;
console.log(a1)//{foo: 2}
复制代码
a3和a4是用两种不一样方法合并而成的新数组,可是它们的成员都是对原数组成员的引用,这就是浅拷贝。若是修改了原数组的成员,会同步反映到新数组。
六、 与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest) // [2, 3, 4, 5]
const [first, ...rest] = [];
console.log(first) // undefined
console.log(rest) // []
const [first, ...rest] = ["foo"];
console.log(first) // "foo"
console.log(rest) // []
复制代码
将扩展运算符用于数组赋值,只能放在参数的最后一位,不然会报错。跟rest参数同样。
const [...butLast, last] = [1, 2, 3, 4, 5];// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];//报错,Rest element must be last element
复制代码
七、 字符串
扩展运算符还能够将字符串转为真正的数组。
[...'hello']; // [ "h", "e", "l", "l", "o" ]
[…'hello'].length;//5
复制代码
八、 实现了 Iterator 接口的对象
任何定义了遍历器(Iterator)接口的对象,均可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];//实现了Iterator接口
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr = [...arrayLike];// // TypeError: Cannot spread non-iterable object.
//能够改为下面这样
let arr=Array.form(arrayLike)//把对象变成数组,把相似数组的变成数组
复制代码
map结构:
let map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
let arr = [...map.keys()]; // [1, 2, 3]
Generator 函数:
const go = function*(){
yield 1;
yield 2;
yield 3;
};
[...go()] // [1, 2, 3]
复制代码
Array.from方法用于将两类对象转为真正的数组:相似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
任何有length属性的对象,均可以经过Array.from方法转为数组,而此时扩展运算符就没法转换。
Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
复制代码
一、相似于数组的对象
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
复制代码
二、可遍历的对象:
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
复制代码
三、不支持该方法的浏览器,能够用下面这种方法来进行兼容:
const toArray = (() =>
Array.from ? Array.from : obj => [].slice.call(obj)
)();
复制代码
四、Array.from还能够接受第二个参数,做用相似于数组的map方法,用来对每一个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x);
// 等同于
Array.from(arrayLike).map(x => x * x);
Array.from([1, 2, 3], (x) => x * x)// [1, 4, 9]
let spans = document.querySelectorAll('span.name');
// map()
let names1 = Array.prototype.map.call(spans, s => s.textContent);
// Array.from()
let names2 = Array.from(spans, s => s.textContent)
复制代码
五、Array.from()能够将各类值转为真正的数组。
Array.from({ length: 2 }, () => 'jack')// ['jack', 'jack']
复制代码
六、将字符串转为数组,而后返回字符串的长度
function countSymbols(string) {
return Array.from(string).length;
}
复制代码
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
复制代码
数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其余位置(会覆盖原有成员),而后返回当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
target(必需):从该位置开始替换数据。若是为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。若是为负值,表示倒数。
end(可选):到该位置前中止读取数据,默认等于数组长度。若是为负值,表示倒数。
[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]
上面代码表示将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2。
[1, 2, 3, 4, 5].copyWithin(0, 2)// [3, 4, 5, 4, 5]
[1, 2, 3, 4, 5].copyWithin(0, 2,3)// [3, 2, 3, 4, 5]
复制代码
[1, 4, -5, 10].find((n) => n < 0)// -5
[1, 5, 10, 15].find(function(value, index, arr) {
return value > 9;
}) // 10
复制代码
数组实例的findIndex(),返回第一个符合条件的数组成员的位置,若是全部成员都不符合条件,则返回-1。
[1, 5, 10, 15].findIndex(function(value, index, arr) {
return value > 9;
}) // 2
复制代码
这两个方法均可以接受第二个参数,用来绑定回调函数的this对象。
function f(v){
return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person); // 26
复制代码
这两个方法均可以发现NaN,弥补了数组的indexOf方法的不足。
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
复制代码
Object.is()用来比较两个值是否严格相等,与严格相等运算符(===)同样。
//使用给定值,填充一个数组,并返回填充后的数组。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
复制代码
数组中已有的元素,会被所有抹去。 fill方法还能够接受第二个和第三个参数,用于指定填充的起始位置和结束位置。这根splice()很像,只是splice方法没有返回值。
['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']
复制代码
注意:若是填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。
let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
console.log(arr)// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
复制代码
let letter = ['a', 'b', 'c'];
let keys=letter.keys();
let values=letter.values()
let entries = letter.entries();
console.log(keys,values,entries)
// Array Iterator {} Array Iterator {} Array Iterator {}
for (let index of keys) {
console.log(index);
} //0, //1,//2
复制代码
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
复制代码
该方法的第二个参数表示搜索的起始位置,默认为0。若是第二个参数为负数,则表示倒数的位置,若是这时它大于数组长度(好比第二个参数为-4,但数组长度为3),则会重置为从0开始。
indexOf方法有两个缺点。
一、不够语义化,它的含义是找到参数值的第一个出现位置,因此要去比较是否不等于-1。
二、它内部使用严格相等运算符(===)进行判断,这会致使对NaN的误判。
[NaN].includes(NaN) //true
复制代码
能够用以下方法来判断当前环境是否支持该方法。
const contains = (() =>
Array.prototype.includes ?
(arr, value) => arr.includes(value) :
(arr, value) => arr.some(el => el === value)
)();
console.log(contains(['foo', 'bar'], 'baz')); // => false
复制代码
用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。flat()默认只会“拉平”一层,若是想要“拉平”多层的嵌套数组,能够将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]]
[1, 2, [3, [4, 5]]].flat(2) // [1, 2, 3, 4, 5]
[1, [2, [3]]].flat(Infinity) // [1, 2, 3],这种方式无论嵌套多少层,都会被拉平。
复制代码
若是原数组有空位,flat()方法会跳过空位。
[1, 2, , 4, 5].flat() // [1, 2, 4, 5]
复制代码
flatMap()方法对原数组的每一个成员执行一个函数(至关于执行Array.prototype.map()),而后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。flatMap()只能展开一层数组。
// 至关于 [[[2]], [[4]], [[6]], [[8]]].flat()
[1, 2, 3, 4].flatMap(x => [[x * 2]])
// [[2], [4], [6], [8]]
复制代码
ES5:
一、forEach(), filter(), reduce(), every() 和some()都会跳过空位。
二、map()会跳过空位,但会保留这个值。
三、join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
ES6:明确将空位转为undefined。
const foo = 'bar';
const baz = {foo};
console.log(baz) // {foo: "bar"}
// 等同于
const baz = {foo: foo};
复制代码
ES6 容许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // {x: 1, y: 2}
复制代码
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
复制代码
ES6 容许字面量定义对象时,用方法二(表达式)做为对象的属性名,即把表达式放在方括号内。
let lastWord = 'last word';
const a = {
'first word': 'hello',
[lastWord]: 'world'
};
a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
复制代码
表达式还能够用于定义方法名。
let obj = {
['h' + 'ello']() {
return 'hi';
}
};
obj.hello() // hi
复制代码
注意,属性名表达式若是是一个对象,默认状况下会自动将对象转为字符串[object Object],这一点要特别当心。
const keyA = {a: 1};
const keyB = {b: 2};
const myObject = {
[keyA]: 'valueA',
[keyB]: 'valueB'
};
myObject // Object {[object Object]: "valueB"}
复制代码
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
复制代码
若是对象的方法使用了取值函数(getter)和存值函数(setter),则name属性不是在该方法上面,而是该方法的属性的描述对象的get和set属性上面,返回值是方法名前加上get和set。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
复制代码
若是对象的方法是一个 Symbol 值,那么name属性返回的是这个 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
复制代码
有两种特殊状况:bind方法创造的函数,name属性返回bound加上原函数的名字;Function构造函数创造的函数,name属性返回anonymous(匿名)。
(new Function()).name // "anonymous"
var doSomething = function() {
// ...
};
doSomething.bind().name // "bound doSomething"
复制代码
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,//可枚举性
// configurable: true
// }
复制代码
目前,有四个操做会忽略enumerable为false的属性。
一、 for...in循环:只遍历对象自身的和继承的可枚举的属性。
二、 Object.keys():返回对象自身的可枚举的属性的属性。
三、 JSON.stringify():只串行化对象自身的可枚举的属性。
四、 Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
总结:尽可能不要用for...in循环,而用Object.keys()代替。
二、属性的遍历
一、 for…in:for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
二、 Object.keys(obj) 返回一个数组,包括对象自身的全部可枚举属性(不含 Symbol 属性)的键名。
三、 Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的全部属性(不含 Symbol 属性,可是包括不可枚举属性)的键名。
四、 Object.getOwnPropertySymbols(obj) 返回一个数组,包含对象自身的全部 Symbol 属性的键名。
五、 Reflect.ownKeys(obj) 返回一个数组,包含对象自身的全部键名,无论键名是 Symbol 或字符串,也无论是否可枚举。
以上的 5 种方法遍历对象的键名,都遵照一样的属性遍历的次序规则。
一、首先遍历全部数值键,按照数值升序排列。
二、其次遍历全部字符串键,按照加入时间升序排列。
三、最后遍历全部 Symbol 键,按照加入时间升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 }) // ['2', '10', 'b', 'a', Symbol()]
复制代码
对象的解构赋值 (在=赋值左边) 用于从一个对象取值,至关于将目标对象自身的全部可遍历的(enumerable)、但还没有被读取的属性,分配到指定的对象上面。全部的键和它们的值,都会拷贝到新对象上面。属于浅拷贝。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1 y // 2 z // { a: 3, b: 4 }
复制代码
对象的解构赋值的注意事项
一、因为解构赋值要求等号右边是一个对象,因此若是等号右边是undefined或null,就会报错,由于它们没法转为对象。
let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误
复制代码
二、解构赋值必须是最后一个参数,不然会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
复制代码
三、扩展运算符的解构赋值,不能复制继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined
Object.create({ x: 1, y: 2 });建立的是原型对象
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;//newObj只能获取z的值
let { y, z } = newObj;
x // 1
y // undefined
z // 3
复制代码
四、 变量声明语句之中,若是使用解构赋值,扩展运算符后面必须是一个变量名,而不能是一个解构赋值表达式。
let { x, ...{ y, z } } = o;
// SyntaxError: ... must be followed by an identifier in declaration contexts
复制代码
扩展运算符: (在等号=右边) 对象的扩展运算符(...)用于取出参数对象的全部可遍历属性,拷贝到当前对象之中。
对象的扩展运算符的注意事项
一、因为数组是特殊的对象,因此对象的扩展运算符也能够用于数组。
let foo = { ...['a', 'b', 'c'] };
console.log(foo);// {0: "a", 1: "b", 2: "c"}
复制代码
二、 若是扩展运算符后面不是对象,则会自动将其转为对象。
// 等同于 {...Object(1)}
{...1} // {} 因为该对象没有自身属性,因此返回一个空对象。
// 等同于 {...Object(true)}
{...true} // {}
// 等同于 {...Object(undefined)}
{...undefined} // {}
// 等同于 {...Object(null)}
{...null} // {}
复制代码
三、 若是扩展运算符后面是字符串,它会自动转成一个相似数组的对象,所以返回的不是空对象。
{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
复制代码
四、 对象的扩展运算符等同于使用Object.assign()方法。
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
复制代码
五、 若是想完整克隆一个对象,还拷贝对象原型的属性,能够采用下面的写法。
// 写法1
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法2
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
复制代码
六、 扩展运算符能够用于合并两个对象。
let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);
复制代码
七、 若是用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。
let a={x:2,y:3;z:4}
let aWithOverrides = { ...a, x: 1, y: 2 };
console.log(aWithOverrides)// {x: 1, y: 2, z: 4}
let arr={a:1,b:2}
let arr1={b:3,c:4}
let arr2={...arr,...arr1}
console.log(arr2) // {a: 1, b: 3, c: 4}
复制代码
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
复制代码
ES5 能够经过下面的代码,部署Object.is。
Object.defineProperty(Object, 'is', {
value: function(x, y) {
if (x === y) {
// 针对+0 不等于 -0的状况
return x !== 0 || 1 / x === 1 / y;
}
// 针对NaN的状况
return x !== x && y !== y;
},
configurable: true,
enumerable: false,
writable: true
});
复制代码
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
const target={};
Object.assign(target, source1, source2);
Console.log(target); // {a:1, b:2, c:3}
复制代码
注意1:因为undefined和null没法转成对象,因此若是它们做为参数,就会报错。若是undefined和null不在首参数,就不会报错。
Object.assign(undefined) // 报错
Object.assign(null) // 报错
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
复制代码
注意2:其余类型的值(即数值、字符串和布尔值)不在首参数,也不会报错。可是,除了字符串会以数组形式,拷贝入目标对象,其余值都不会产生效果。
const v1 = 'abc';
const v2 = true;
const v3 = 10;
const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }
复制代码
注意3:Object.assign拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
Object.assign的特色
一、 Object.assign()是浅拷贝。
二、 同名属性的替换;(后者替换前者)
三、 数组的处理。Object.assign把数组视为属性名为 0、一、2 的对象,所以源数组的 0 号属性4覆盖了目标数组的 0 号属性1。
Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]
复制代码
四、 取值函数的处理;(求值后再复制)
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
复制代码
Object.assign的常见用途:
一、 为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
复制代码
二、 为对象添加方法
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
},
anotherMethod() {
}
});
// 等同于下面的写法
SomeClass.prototype.someMethod = function (arg1, arg2) {
};
SomeClass.prototype.anotherMethod = function () {
};
复制代码
三、 克隆对象(克隆自身与其继承的值)
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
复制代码
四、 合并多个对象
const merge = (target, ...sources) => Object.assign(target, ...sources);
复制代码
五、 为属性指定默认值
const DEFAULTS = {
logLevel: 0,
outputFormat: 'html'
};
function processContent(options) {
options = Object.assign({}, DEFAULTS, options);
}
复制代码
返回指定对象全部自身属性(非继承属性)的描述对象。主要是为了解决Object.assign()没法正确拷贝get属性和set属性的问题。
__proto__属性:
用来读取或设置当前对象的prototype对象。目前,全部浏览器(包括 IE11)都部署了这个属性。建议不要使用此属性。使用下面的Object.setPrototypeOf()(写操做)、Object.getPrototypeOf()(读操做)、Object.create()(生成操做)代替。
Object.setPrototypeOf(): 用来设置一个对象的prototype对象。若是第一个参数不是对象,会自动转为对象。可是因为返回的仍是第一个参数,因此这个操做不会产生任何效果。 因为undefined和null没法转为对象,因此若是第一个参数是undefined或null,就会报错。
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
复制代码
Object.getPrototypeOf() 用于读取一个对象的原型对象。若是参数不是对象,会被自动转为对象。若是参数是undefined或null,它们没法转为对象,因此会报错。
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
复制代码
Object.create() 从指定原型对象建立一个新的对象.
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.sayName=function(){
console.log(this.name)
}
function Teacher(subject,name,age){
this.subject=subject;
return Person.call(this,name,age);//继承Person实例属性
}
//继承原型属性,指向同一引用地址
Teacher.prototype=Object.create(Person.prototype);
var person1=new Person();
var person2=Object.create(person1);
person2.__proto__===person1;//true;
复制代码
Object.keys(): 返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键名。
Object.values(): 返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值。属性名为数值的属性,是按照数值大小,从小到大遍历的,所以返回的顺序是b、c、a。Object.values会过滤属性名为 Symbol 值的属性。
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc'] 会过滤属性名为 Symbol 值的属性。
若是参数不是对象,Object.values会先将其转为对象。若是Object.values方法的参数是一个字符串,会返回各个字符组成的一个数组。
Object.values('foo') // ['f', 'o', 'o']
Object.values(42) // []
Object.values(true) // []
复制代码
Object.entries(): 返回一个数组,成员是参数对象自身的(不含继承的)全部可遍历(enumerable)属性的键值对数组。除了返回值不同,该方法的行为与Object.values基本一致。
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
复制代码
主要用途: 一、 遍历对象的属性。 二、 将对象转为真正的Map结构。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
复制代码
Object.fromEntries(): 是Object.entries()的逆操做,用于将一个键值对数组转为对象。
o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop'); // 返回 true
o.hasOwnProperty('toString'); // 返回 false
o.hasOwnProperty('hasOwnProperty'); // 返回 false
复制代码
新的原始数据类型Symbol,表示独一无二的值,是javascript的第七种数据类型。前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
注意事项:
一、Symbol函数前不能使用new命令,不然会报错。因为 Symbol 值不是对象,因此不能添加属性。基本上,它是一种相似于字符串的数据类型。 Symbol函数能够接受一个字符串做为参数,表示对 Symbol 实例的描述。
let s1 = Symbol('foo');
let s2 = Symbol('bar');
console.log(s1) // Symbol(foo)
console.log(s1.toString()) // Symbol(foo)
复制代码
二、若是 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,而后才生成一个 Symbol 值。
const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
console.log(sym) // Symbol(abc)
复制代码
三、Symbol函数的参数只是表示对当前 Symbol 值的描述,所以相同参数的Symbol函数的返回值是不相等的。
// 没有参数的状况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的状况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false
复制代码
四、Symbol 值不能与其余类型的值进行运算,会报错。
let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string
复制代码
五、Symbol 值能够显式转为字符串。也能够转为布尔值,可是不能转为数值。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
!sym // false
if (sym) {
}
Number(sym) // TypeError
Console.log(sym + 2) // TypeError
复制代码
const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';//此处的mySymbol只能当作是字符串,而不能当作是一个Symbol值
console.log(a[mySymbol]) // undefined
consoe.log(a['mySymbol']) // "Hello!"
复制代码
在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。
let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);
复制代码
Symbol 类型还能够用于定义一组常量,保证这组常量的值都是不相等的。 Symbol 值做为属性名时,该属性仍是公开属性,不是私有属性。 魔术字符串:在代码之中屡次出现、与代码造成强耦合的某一个具体的字符串或者数值。利用Symbol来消除。
const shapeType = {
triangle: Symbol('Triangle')
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = 5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });
复制代码
Symbol.for(“foo”): 接受一个字符串做为参数,而后搜索有没有以该参数做为名称的 Symbol 值。若是有,就返回这个 Symbol 值,不然就新建并返回一个以该字符串为名称的 Symbol 值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
console.log(s1 === s2) // true
复制代码
Symbol.keyFor: 返回一个已登记的 Symbol 类型值的key。返回一个使用Symbol.for()方法定义的key。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
复制代码
注意:Symbol.for为 Symbol 值登记的名字,是全局环境的,能够在不一样的 iframe 或 service worker 中取到同一个值。
Symbol.hasInstance: 对象的Symbol.hasInstance属性,指向一个内部方法。当其余对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。好比,foo instanceof Foo在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)。
class MyClass {
[Symbol.hasInstance](foo) {
return foo instanceof Array;
}
}
Console.log([1, 2, 3] instanceof new MyClass()) // trueSymbol.isConcatSpreadable
复制代码
Symbol.isConcatSpreadable: 等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否能够展开。
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false;
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
复制代码
相似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才能够展开。
let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
复制代码
Symbol.species: 指向一个构造函数。建立衍生对象时,会使用该属性。下面例子中b、c是a的衍生对象。
class MyArray extends Array {
static get [Symbol.species]() { return Array; }
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x);
const c = a.filter(x => x > 1);
b instanceof MyArray // false
b instanceof Array //true
c instanceof MyArray // false
复制代码
主要的用途: 有些类库是在基类的基础上修改的,那么子类使用继承的方法时,做者可能但愿返回基类的实例,而不是子类的实例。
还有其余属性就不一一例举了:Symbol.match、Symbol.replace、Symbol.search、Symbol.split、Symbol.iterator、Symbol.toPrimitive、Symbol.toStringTag(toString())、Symbol.unscopables(with)
一种数据结构,相似于数组,可是成员的值都是惟一的,没有重复的值。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
复制代码
Set函数能够接受一个数组(或者具备 iterable 接口的其余数据结构)做为参数,用来初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
console.log([...set])
// [1, 2, 3, 4]
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
const set = new Set(document.querySelectorAll('div'));
set.size // 56
复制代码
将set转为数组的方式
[...new Set('ababbc')]
复制代码
能够用于去重
[...new Set('ababbc')].join('') //abc
复制代码
向 Set 加入值的时候,不会发生类型转换,因此5和"5"是两个不一样的值。在 Set 内部,两个NaN是相等。
实例属性:
一、Set.prototype.constructor:构造函数,默认就是Set函数。
二、Set.prototype.size:返回Set实例的成员总数。
操做方法:
一、add(value):添加某个值,返回 Set 结构自己。
二、delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
三、has(value):返回一个布尔值,表示该值是否为Set的成员。
四、clear():清除全部成员,没有返回值。
Array.from方法能够将 Set 结构转为数组。
const items = new Set([1, 2, 3, 4, 5]);
//方法1
const array = Array.from(items);
//方法2
const arr=[…items]
复制代码
遍历方法:
一、keys():返回键名的遍历器。
二、values():返回键值的遍历器。
三、entries():返回键值对的遍历器。
四、forEach():使用回调函数遍历每一个成员。
因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),因此keys方法和values方法的行为彻底一致。 Set 结构的实例默承认遍历,它的默认遍历器生成函数就是它的values方法。
Set.prototype[Symbol.iterator] === Set.prototype.values
// true
复制代码
这意味着,能够省略values方法,直接用for...of循环遍历 Set。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red // green // blue
复制代码
数组的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}
复制代码
WeakSet 结构与 Set 相似,可是,它与 Set 有两个区别。
首先:WeakSet 的成员只能是对象,而不能是其余类型的值。
const ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
复制代码
其次:WeakSet 中的对象都是弱引用,垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,若是其余对象都再也不引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 所以,WeakSet 适合临时存放一组对象。因为上面这个特色,WeakSet 的成员是不适合引用的,由于它会随时消失。WeakSet 不可遍历。
做为构造函数,WeakSet 能够接受一个数组或相似数组的对象做为参数。注意,是a数组的成员成为 WeakSet 的成员,而不是a数组自己。这意味着,数组的成员只能是对象。
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
复制代码
有如下几个实例方法:add()、delete()、has()。
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),可是传统上只能用字符串看成键。
Map: 一种数据结构,相似于对象,也是键值对的集合,可是“键”的范围不限于字符串,各类类型的值(包括对象)均可以看成键。
做为构造函数,Map 也能够接受一个数组做为参数。该数组的成员是一个表示键值对的数组。任何具备 Iterator 接口、且每一个成员都是一个双元素的数组的数据结构均可以看成Map构造函数的参数。这就是说,Set和Map均可以用来生成新的 Map。
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"
map.has('title') // true
map.get('title') // "Author"
复制代码
Map构造函数接受数组做为参数,实际上执行的是下面的算法。
const items = [
['name', '张三'],
['title', 'Author']
];
const map = new Map();
items.forEach(
([key, value]) => map.set(key, value)
);
复制代码
若是对同一个键屡次赋值,后面的值将覆盖前面的值。只有对同一个对象的引用,Map 结构才将其视为同一个键
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined //这两个[“a”]的内存地址不同
复制代码
注意: 虽然NaN不严格相等于自身,但 Map 将其视为同一个键。
size、set()、get()、has()、delete()、clear()
一、 keys():返回键名的遍历器。
二、values():返回键值的遍历器。
三、entries():返回全部成员的遍历器。
四、forEach():遍历 Map 的全部成员。
Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()] // [1, 2, 3]
[...map.values()] // ['one', 'two', 'three']
[...map.entries()] // [[1,'one'], [2, 'two'], [3, 'three']]
[...map] // [[1,'one'], [2, 'two'], [3, 'three']]
复制代码
结合数组的map方法、filter方法,能够实现 Map 的遍历和过滤(Map 自己没有map和filter方法)。
const map0 = new Map().set(1, 'a').set(2, 'b').set(3, 'c')
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
复制代码
const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
[...myMap] //Map转数组
// [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
复制代码
数组转Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
复制代码
二、 Map与对象的转换
Map转对象
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map() .set('yes', true).set('no', false);
strMapToObj(myMap) // { yes: true, no: false }
复制代码
对象转Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false}) // Map {"yes" => true, "no" => false}
复制代码
三、 Map与Json的转换 Map 的键名都是字符串,这时能够选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
复制代码
Map 的键名有非字符串,这时能够选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
复制代码
Json转Map,全部键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
复制代码
Json转Map,整个 JSON 就是一个数组,且每一个数组成员自己,又是一个有两个成员的数组。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
复制代码
首先,WeakMap只接受对象做为键名(null除外),不接受其余类型的值做为键名。
其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。 WeakMap的专用场合就是,它的键所对应的对象,可能会在未来消失。WeakMap结构有助于防止内存泄漏。
注意 ,WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用。
const wm = new WeakMap();
let key = {};
let obj = {foo: 1};
wm.set(key, obj);
obj = null;//解除引用,至关于切断联系,可是{foo:1}所占内存空间依然存在。
console.log(wm.get(key))
// Object {foo: 1}
复制代码
WeakMap 与 Map 在 API 上的区别主要是两个,一是没有遍历操做(即没有keys()、values()和entries()方法),也没有size属性。WeakMap只有四个方法可用:get()、set()、has()、delete()。
weakMap的用途:
一、 就是 DOM 节点做为键名
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
复制代码
二、 部署私有属性
const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
constructor(counter, action) {
_counter.set(this, counter);
_action.set(this, action);
}
dec() {
let counter = _counter.get(this);
if (counter < 1) {
return;
}
_counter.set(this, --counter);
if (counter === 0) {
_action.get(this)();
}
}
}
const c = new Countdown(2, () => console.log('DONE'));
c.dec()
c.dec()
// DONE
复制代码
Proxy 用于修改某些操做的默认行为。 Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。
var proxy = new Proxy(target, handler);
复制代码
target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
注意:要使得Proxy起做用,必须针对Proxy实例进行操做,而不是针对目标对象进行操做。若是handler没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
复制代码
Proxy 实例也能够做为其余对象的原型对象。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
复制代码
二、 set(target, propKey, value, receiver):拦截对象属性的设置,好比proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
三、has(target, propKey):拦截propKey in proxy的操做,返回一个布尔值。可是has拦截对for...in循环不生效。
四、deleteProperty(target, propKey):拦截delete proxy[propKey]的操做,返回一个布尔值。
五、ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象全部自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
六、getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
七、defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
八、preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
九、getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
十、isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。不然返回值会被自动转为布尔值。
十一、setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。若是目标对象是函数,那么还有两种额外操做能够拦截。
十二、apply(target, object, args):拦截 Proxy 实例做为函数调用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
1三、construct(target, args):拦截 Proxy 实例做为构造函数调用的操做,好比new proxy(...args)。回的必须是一个对象,不然会报错。
let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
复制代码
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
const jane = new Person('Jane');
console.log(jane.name) // 'Jane'
const proxy = new Proxy(jane, {});
console.log(proxy.name) // undefined
复制代码
若是handler没有设置任何拦截,那就等同于直接通向原对象。由于经过调用构造函数时this指的是Person,而proxy.name获取name时的this指的是proxy,因此此时取不到值。
实例:经过拦截使得this绑定原始对象。
const target = new Date('2015-01-01');
const handler = {
get(target, prop) {
if (prop === 'getDate') {
return target.getDate.bind(target);
}
return Reflect.get(target, prop);
}
};
const proxy = new Proxy(target, handler);
proxy.getDate() // 1
复制代码
1. 将Object对象的一些明显属于语言内部的方法(好比Object.defineProperty),放到Reflect对象上。
2. 修改某些Object方法的返回结果,让其变得更合理。
3. 让Object操做都变成函数行为。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
复制代码
4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
5. Reflect对象的静态方法有13个,跟Proxy的静态方法时一一对应的。
1)Reflect.get(target, name, receiver):查找并返回target对象的name属性,若是没有该属性,则返回undefined。
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
console.log(Reflect.get(myObject, 'foo') )// 1
console.log(Reflect.get(myObject, 'baz') )// 3
var myReceiverObject = {
foo: 4,
bar: 4,
};
console.log((Reflect.get(myObject, 'baz', myReceiverObject)) // 8
复制代码
若是name属性部署了读取函数(getter),则读取函数的this绑定receiver。
2)Reflect.set(target, name, value, receiver): Reflect.set方法设置target对象的name属性等于value。若是name属性设置了赋值函数,则赋值函数的this绑定receiver。若是第一个参数不是对象,Reflect.set会报错。
var myObject = {
foo: 1,
set bar(value) {
return this.foo = value;
},
}
myObject.foo // 1
Reflect.set(myObject, 'foo', 2);
myObject.foo // 2
Reflect.set(myObject, 'bar', 3)
myObject.foo // 3
复制代码
3)Reflect.has(obj,name):对应name in obj里面的in运算符。若是第一个参数不是对象,Reflect.has和in运算符都会报错。
var myObject = {
foo: 1,
};
// 旧写法
'foo' in myObject // true
// 新写法
Reflect.has(myObject, 'foo') // true
复制代码
….
实例:利用proxy实现观察者模式
const queuedObservers = new Set();//观察者队列
const observe = fn => queuedObservers.add(fn);//添加观察者
const observable = obj => new Proxy(obj, {set});//添加代理
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
//观察目标
const person = observable({
name: '张三',
age: 20
});
//观察者
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
复制代码
Promise对象有如下两个特色。
1) 对象的状态不受外界影响。 Promise对象表明一个异步操做,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
2) 一旦状态改变,就不会再变, 任什么时候候均可以获得这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种状况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。若是改变已经发生了,你再对Promise对象添加回调函数,也会当即获得这个结果。
优势: 将异步操做以同步操做的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操做更加容易。
不足:
一、没法取消Promise,一旦新建它就会当即执行,没法中途取消。
二、若是不设置回调函数,Promise内部抛出的错误,不会反应到外部。
三、当处于pending状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操做成功 */){
resolve(value);
} else {
reject(error);
}
});
复制代码
resolve函数的做用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;
reject函数的做用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
Promise实例生成之后,能够用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
复制代码
实例:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value); //done
});
复制代码
Promise新建后就会当即执行:then方法指定的回调函数,将在当前脚本全部同步任务执行完才会执行。
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
顺序:// Promise // Hi! // resolved
复制代码
它的做用是为 Promise 实例添加状态改变时的回调函数。 前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。所以能够采用链式写法,即then方法后面再调用另外一个then方法。
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {//参数post是上一个then返回的数据-json.post
// ...
});
复制代码
若是then()返回的是一个Promise,那么接下来的代码能够这么写:
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),//成功
err => console.log("rejected: ", err)//失败
);
复制代码
方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。用于捕获Promise对象和then()方法指定的回调函数中的错误。
Promise 对象的错误具备“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误老是会被下一个catch语句捕获。 若是 Promise 状态已经变成resolved,再抛出错误是无效的。
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
复制代码
Promise 在resolve语句后面,再抛出错误,不会被捕获,等于没有抛出。由于 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
用于指定无论 Promise 对象最后状态如何,都会执行的操做。无论前面的 Promise 是fulfilled仍是rejected,都会执行回调函数callback。
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 undefined
Promise.resolve(2).finally(() => {})
// resolve 的值是 2
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 undefined
Promise.reject(3).finally(() => {})
// reject 的值是 3
复制代码
const p = Promise.all([p1, p2, p3]);
复制代码
参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。
(1)只有p一、p二、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p一、p二、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p一、p二、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
const p = Promise.race([p1, p2, p3]);
复制代码
只要p一、p二、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
将现有对象转为 Promise 对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
复制代码
Promise.resolve方法的参数分红四种状况。
(1)参数是一个 Promise 实例。那么Promise.resolve将不作任何修改、原封不动地返回这个实例。
(2)参数是一个thenable对象。thenable对象指的是具备then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,而后就当即执行thenable对象的then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
复制代码
(3)参数不是具备then方法的对象,或根本就不是对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
复制代码
(4)不带有任何参数。直接返回一个resolved状态的 Promise 对象。注意: 当即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。setTimeout(fn, 0)在下一轮“事件循环”开始时执行,
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
复制代码
setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是当即执行,所以最早输出。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
复制代码
注意: Promise.reject()方法的参数,会原封不动地做为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable) // true
})
复制代码
Promise.try(() => database.users.get({id: userId})).then(...).catch(...)
复制代码
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve(src,path);
image.onerror = reject(src);
image.src = path;
});
};
preloadImage(“http://www.tj.com/123.jpg”).then(function(img,path){
//此时的参数path为undefined,由于此时的path是preloadImage(path)中的形参,是局部变量,在调用结束后他的内存就被释放了。
},function(img){})
复制代码
- 为各类数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员可以按某种次序排列;
- ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
ES6 规定,默认的 Iterator接口部署在数据结构的Symbol.iterator属性上,或者说,一个数据结构只要具备Symbol.iterator属性,就能够认为是“可遍历”的。
const obj = {
[Symbol.iterator] : function () {
return {
next: function () {
return {
value: 1,
done: true
};
}
};
}
};
复制代码
原生具有 Iterator 接口的数据结构以下。
一、Array
二、Map
三、Set
四、String
五、TypedArray
六、函数的 arguments 对象
七、NodeList 对象
下面是两个经过调用Symbol.iterator方法来Iterator接口的例子。
//数组的遍历 部署 Iterator 接口 let iter = arr[Symbol.iterator]();
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()) // { value: 'a', done: false }
console.log (iter.next()) // { value: 'b', done: false }
console.log (iter.next()) // { value: 'c', done: false }
console.log (iter.next()) // { value: undefined, done: true }
//字符串的遍历 部署 Iterator 接口 someString[Symbol.iterator]();
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next()
复制代码
对于相似数组的对象(存在数值键名和length属性),部署 Iterator 接口,有一个简便方法,就是Symbol.iterator方法直接引用数组的 Iterator 接口。
NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
// 或者
NodeList.prototype[Symbol.iterator] = [][Symbol.iterator];
[...document.querySelectorAll('div')] // 能够执行了
复制代码
(1) 解构赋值
var set=[1,2,3]
let [first, ...rest] = set;
复制代码
(2) 扩展运算符
var str = 'hello';
console.log([...str]) // ['h','e','l','l','o']
复制代码
(3) yield*
let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
复制代码
(4) 其余一些场合(for…of、Array.form())
JavaScript 原有的for...in循环,只能得到对象的键名,不能直接获取键值。ES6 提供for...of循环,容许遍历得到键值。数组的遍历器接口只返回具备数字索引的属性。
var arr = ['a', 'b', 'c', 'd'];
arr.foo = 'hello';
//获取的是键名
for (let a in arr) {
console.log(a); // 0 1 2 3
}
//获取的是键值
for (let a of arr) {
console.log(a); // a b c d
}
复制代码
Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组,该数组的两个成员分别为当前 Map 成员的键名和键值。
let map = new Map().set('a', 1).set('b', 2);
for (let pair of map) {
console.log(pair); // ['a', 1] // ['b', 2]
}
for (let [key, value] of map) {
console.log(key + ' : ' + value); // a : 1 // b : 2
}
复制代码
并非全部相似数组的对象都具备 Iterator 接口,一个简便的解决方法,就是使用Array.from方法将其转为数组。
let arrayLike = { length: 2, 0: 'a', 1: 'b' };
// 报错
for (let x of arrayLike) {
console.log(x);
}
// 正确
for (let x of Array.from(arrayLike)) {
console.log(x);
}
复制代码
for...in循环有几个缺点。
一、数组的键名是数字,可是for...in循环是以字符串做为键名“0”、“1”、“2”等等。
二、for...in循环不只遍历数字键名,还会遍历手动添加的其余键,甚至包括原型链上的键。
三、某些状况下,for...in循环会以任意顺序遍历键名。
总之,for...in循环主要是为遍历对象而设计的,不适用于遍历数组。
for...of循环相比上面几种作法,有一些显著的优势:
一、有着同for...in同样的简洁语法,可是没有for...in那些缺点。 二、不一样于forEach方法,它能够与break、continue和return配合使用。 三、提供了遍历全部数据结构的统一操做接口。