js---es6

149 阅读13分钟

原文链接 es6.ruanyifeng.com/#docs

1. let 和 const 命令

① let声明的变量只在它所在的代码块有效,块级作用域。
{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

function f1() {
  let n = 5;
  if (true) {
    let n = 10;
  }
  console.log(n); // 5
}
② 不存在变量提升
console.log(bar); // 报错ReferenceError
let bar = 2;
③ const 和 let 大体相同。另外const 定义的若是基本数据类型,变量的值不得改动;若定义的是引用类型,则地址不能改变,可以修改对象、数组的属性等。
const a = [ ];
a.push('Hello'); // 可执行
a.length = 0;    // 可执行
a = ['Dave'];    // 报错
④ var定义和window对象上直接定义区别

[1]var若是全局定义变量、函数都会变成window对象的属性和方法。

var name = "bwf";
window.name; // "bwf"

[2]var定义的变量无法删除,window定义的属性是可以的。

var num1=123;
window.num2=456;
delete num1;  // false
delete num2;  //true

[3]var定义的变量会存在变量提升,window定义的属性是不会的。 [4]在函数中使用var定义的变量是局部变量,但是window定义的属性任何时候都是全局的。

function f1() {
  var num1 = 123;
  window.num2 = 456;
}
f1();
num1;  // num1 is not defined
num2;  // 456
  • 注意点
// 内存时开辟5个定时器空间,分别存储这 5 个index(0,1,2,3,4)
 for (let index = 0; index < 5; index++) {
    setTimeout(() => {
      console.log('index', index);
    }, 1000);
  }
  // 1s后打印 0 1 2 3 4
  
  // 内存时开辟5个定时器空间,取最终的全局的index
  for (var index = 0; index < 5; index++) {
    setTimeout(() => {
      console.log('index', index);
    }, 1000);
  }
  // 1s后打印 5 5 5 5 5
  

2.解构赋值(数组、对象、函数参数)

①数组的解构赋值是依照索引赋值给对应变量,而对象根据属性名相同再赋值给对应的变量。 ①数组的解构赋值是依照索引赋值给对应变量,而对象根据属性名相同再赋值给对应的变量。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
②只有赋值右边严格等于undefined,变量默认值才会生效。
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
③解构可以用于嵌套结构的对象
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
④函数参数的解构赋值注意写法不同导致结果不同。建议采用 {x = 0, y = 0} = {} 式写法。
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]


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]

3.模板字符串

①模板字符串${}之中,可以放入变量、表达式、函数等
function fn() {
  return "Hello World";
}
`foo ${fn()} bar`    // foo Hello World bar

4.函数的扩展

①函数的参数可以设置默认值
function log(x, y = 'World') {
  console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
②rest 参数(形式为...变量名), rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
  function add(...values) {
    console.log(values); // [2, 5, 3]
  }
  add(2, 5, 3) 
③箭头函数, 箭头函数里面的this跟定义该函数环境中的this一致, 定义时就决定了其中的this, 不受执行时影响,不受call、bind、apply等调用的影响。
function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数中的 this 和此时外面的this是同一个 所以指向该实例, 所以执行时timer.s2++
  setInterval(() => this.s1++, 1000);
  
  // 普通函数中的 this 指向window, 所以执行时 window.s2++
  setInterval(function () {
    this.s2++;
  }, 1000);
}
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

[1]普通函数中的this指向调用它的对象

function fn(){
  console.log(this) ; // 此时普通函数fn里面的this指向window
}
fn();  //全局调用该函数,全局默认省略了window.fn()

var name = "Bob";
var obj = {
    name: "Dancy",
    fn: function() {    
         console.log(this); //this指向obj对象
         console.log(this.name); //Dancy
    }
}
 obj.fn();  //该普通函数是被obj调用
 
 var name = "Bob";
var obj = {
    name: "Dancy",
    fn: () => {    
         console.log(this); //this指向window
         console.log(this.name); //Bob
    }
}
 obj.fn();  //该函数虽然是被obj调用,但是该函数是箭头函数,因此该箭头函数里面的this永远指向定义环境中的this,window
 
   var name = "Bob";
  var obj = {
    name: "Dancy",
    fn: () => {
      console.log('this', this); //箭头函数中的this在定义时就已经定型,不受call、apply影响,还是指向window
      console.log(this.name); //Bob
    }
  }
  obj.fn.call(obj);

[2]构造函数中的this指向新创建的对象本身。new的时候做了以下事情。 a. 创建一个对象obj b. 改变构造函数中的this指向,让其指向当前new的对象obj c. 在我们添加name、age等属性之后,构造函数最后默认返回这个对象

function Person(obj) {
     this.name = obj.name;
     this.age = obj.age;
     this.height = obj.height;
     console.log(this); //当前new的对象
}
var p1 = new Person({name: "Bob", age: 25, height: 166})
console.log(p1); //与上面的一样

5.数组的扩展

①扩展运算符,将一个数组转为用逗号分隔的参数序列。主要用来解开数组,多个数组合并
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
// 数组里面若是基本数据类型,使用扩展运算符合并是深拷贝,但是对应数组里面是引用类型元素,使用扩展运算符是浅拷贝。
let arr1 = [{ name: 'bwf', age: 18 }, 90]
let arr2 = [...arr1]
arr1[0].name = 'wmy'
arr1[1] = 91
//arr1数组里面第1项是应用类型,所以改变arr1第1项会影响arr2;  但是arr1第2项是基本数据类型,所以改变arr1第2项是不会影响arr2
console.log(arr2) // [{ name: 'wmy', age: 18 }, 90]
②Array.from(),用于将两类对象转为真正的数组,一类是具有length属性的对象(包括字符串等),另一类是 Set 和 Map结构。还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
let arr = Array.from(new Set([1, 2, 2, 4]), item => item * 2)
console.log(arr); // [2, 4, 8]

// Array.from() 和  扩展运算符··· 不同点是
let arrayLike = { length: 3 }
let arr1 = Array.from(arrayLike)   // [undefined, undefined, undefined]
let arr2 = [...arrayLike]  //报错 arrayLike is not iterable
// Array.from() 和  扩展运算符··· 相同点是:数组里面若是基本数据类型,使用扩展运算符合并是深拷贝,但是对应数组里面是引用类型元素,使用扩展运算符是浅拷贝。
let arr1 = [{ name: 'bwf' }, 20]
let arr2 = Array.from(arr1)
arr1[0].name = 'wmy'
arr1[1] = 21
console.log(arr2)  // [{ name: 'wmy' }, 20]
③find(), 直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的索引,如果所有成员都不符合条件,则返回-1。
let findI = [{ name: 'bwf', age: 20 }, { name: 'wmy', age: 30 }].find(item => item.age > 25)
console.log(findI)  // { name: 'wmy', age: 30 }
④includes(),方法返回一个布尔值,表示某个数组是否包含给定的值,第二个参数表示搜索的起始位置
[1, 2, 3].includes(2)   // true
[1, 2, 3].includes(3, 3);  // false
⑤flat()默认只会“拉平”一层,以将flat()方法的参数写成一个整数,表示想要拉平的层数。可以用Infinity表示无限拉平。
[1, [2, [3]]].flat(Infinity) // [1, 2, 3]

6.对象的扩展以及对象的新增方法

①属性、方法的简写
const foo = 'bar';
const baz = {foo};  //属性名和变量名相同就可以简写
baz // {foo: "bar"}

const o = {
  method() {     // 属性中的方法可以省略  : function
    return "Hello!";
  }
};
②动态属性名、方法名 []
let lastWord = 'last word';

const a = {
  'first word': 'hello',
  [lastWord]: 'world'
};

a['first word'] // "hello"
a[lastWord] // "world"
a['last word'] // "world"
③对象的扩展运算符, 可以给对象赋值,合并对象等( 等同于Object.assign() )
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
z // { a: 3, b: 4 }

//使用扩展运算符等配合结构赋值,若属性的值是基本类型,则是深拷贝;若属性的值是引用类型,则是浅拷贝
  let obj = { a: { b: 1 }, name: 'bwf' };
  let { ...x } = obj;
  obj.a.b = 2;
  obj.name = 'wmy'  //属性a的值是引用类型,所以是浅拷贝; 属性name的值是基本数据类型,所以是浅拷贝
 console.log(x)  // { a: { b: 2 }, name: 'bwf' };
 
 // 合并多个对象时,若有相同属性名,后面的会覆盖前面的, 就算后面的属性的值是undefined 也会覆盖前面的
  let a = { name: 'bwf', age: [21, 20] }
  let b = { name: undefined}
  let ab = { ...a, ...b }  //{ name: undefined, age: [21, 20] }
④链判断运算符(?) 、null undefined判断符(??)
a?.b
// 等同于 注意a为 ' ' null  undefined false等   a?.b的结果都是 undefined
a == null ? undefined : a.b  

a?.[x]
// 等同于
a == null ? undefined : a[x]

aaa ?? bbb //aaa为null或者undefined时,才会取到bbb,否则就是aaa
⑤Object.assign()方法用于对象的合并、克隆对象、添加对象的属性方法等。
//Object.assign() 同名属性后面会覆盖前面的,且属性值是基本数据类型则是深拷贝,若是基本类型则是浅拷贝。同扩展运算符
const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2
⑥Object.keys(),Object.values(),Object.entries() 返回一个数组

Object.keys() 返回键名组成的数组 Object.values() 返回键值组成的数组 Object.entries() 返回 [ [key1, value1], [key2, value2] ] 组成的二维数组

7.Set 和 Map 数据结构

①set它类似于数组,但是成员的值都是唯一的,对于set结构,Array.from 和 扩展运算符可以将其变成真正的数组。Set 结构的键名就是键值(两者是同一个值)
const set = new Set([1, 2, 3, 4, 4]);
[...set] // [1, 2, 3, 4]

[...new Set('ababbc')].join('')
// "abc"
②Set 实例的属性和方法

·size:返回Set实例的成员总数。 .add(value):添加某个值,返回 Set 结构本身。 .delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 .has(value):返回一个布尔值,表示该值是否为Set的成员。 .clear():清除所有成员,没有返回值。

③Map 本质上是键值对的集合(Hash 结构),键名可以是对象。
const map = new Map();
map.set(1, 'aaa').set(1, 'bbb');
map.get(1) // "bbb"
④Map 实例的属性和方法

·size 属性 键值对个数 .set(key, value) 设置键值对 .get(key) 通过键名获取键值 .has(key) 表示某个键是否在当前 Map 对象之中,返回一个布尔值

8.Promise 对象

①主要用来解决异步回调地狱问题,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)
②Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject,一般异步操作成功,调用resolve 函数,随后调用该promise实例指定的then回调; 若失败调用reject函数,随后执行该实例指定的处理失败的回调。注意若没指定catch, promise内部的错误也会被自己的机制解决掉,不会影响到外部代码执行。
function timeout(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
	  if(success){
	      resolve(data)
	   }else{
	      reject(error)
	   }
	}, ms, 'done');
  });
}

timeout(100).then(data => {
  console.log(value);
}).catch(e => {});
③捕获错误优先推荐使用catch结构
getJSON('/post/1.json').then(function(post) {
  return getJSON(post.commentURL);
}).then(function(comments) {
  // some code
}).catch(function(error) {
  // 处理前面三个Promise产生的错误
});
④then()、catch()返回的是一个新的Promise实例, 因此可以无限链式。那么then()、catch()中有return的话,就可以把return后面的数据交给下一个链式结构的形参
new Promise((resolve, reject) => { resolve(1111) })
.catch(err => {console.log(err)})
.then(data1 => {console.log(data1)})  // 1111 
.then(data2 => {
console.log(data2)  // undefined  
return { name: 'bwf' };
}).then(data3 => {
console.log(data3)  // {name:'bwf'} 
})
//前面一个then会返回一个新的promise, 没有return相当于resolve(), 所以后面的then回调函数中的结果undefined,  有return相当于resolve({ name: 'bwf' }), 所以后面的then回调函数中的结果为{ name: 'bwf' }
⑤Promise.all()
  • 参数为[p1, p2, p3] promise数组
  • 只要p1、p2、p3之中有一个被rejected,且未被自己的catch处理的话,那Promise.all()结果是reject
  • 都成功,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
  return getJSON('/post/' + id + ".json");
});

Promise.all(promises).then(function (posts) {
  // ...
}).catch(function(reason){
  // ...
});



// p2有自己的`catch`方法,该方法返回的是一个新的 Promise 实例, 该实例执行完`catch`方法后,也会变成`resolved`;导致`Promise.all()`方法参数里面的两个实例都会`resolved`
// 如果p2没有自己的`catch`方法,就会调用`Promise.all()`的`catch`方法。
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));

// ["hello", Error: 报错了]

Promise.resolve Promise.reject 返回成功失败的promise对象, catch()方法返回的还是一个 Promise 对象
const promise = new Promise(function(resolve, reject) {
  throw new Error('test');
});
promise.catch(function(error) {
  console.log(error);
});
// Error: test



const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on

9.for...of 循环, 主要解决map forEach遍历中无法使用break、continue。

const arr = [{ name: 'bwf', age: 19 }, { name: 'wmy', age: 21 }, { name: 'xxx', age: 18 }];

arr.map(item => {
console.log('map---begin')
if (item.age > 20) {
  break   //类似continue, 不会结束遍历,只是本轮遍历后面的代码不再执行
}
console.log(item.age) //19  18
})

arr.forEach(item => {
console.log('forEach---begin')
if (item.age > 20) {
  return   //类似continue, 不会结束遍历,只是本轮遍历后面的代码不再执行
}
console.log(item.age) //19  18
})

for (let item of arr) {
console.log('for---begin')
if (item.age > 20) {
  break; //跳出遍历结构,结束所有遍历
}
console.log(item.age) // 19
}

10.async、await 函数,将异步处理成同步

  • await必须放到async函数里面
  • await等待一个或多个promise
  • async函数返回的是一个新的promise
async function f1(ms) {
const p = await new Promise((resolve) => {
  setTimeout(() => {
    resolve(1111)
  }, ms);
});
console.log(p); //1111
return p
}

async function f2(value, ms) {
let res = await f1(ms);//此时await等待的是一个同步
console.log(res)    //1111
console.log(value); //hello world
}

f2('hello world', 50).then(res => {
console.log(res)//undefined
});
①多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。推荐用下面test2式写法
function getFoo() {
return new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ code: 200, data: { name: 'getFoo' } })
  }, 2000)
})
}
function getBar() {
return new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve({ code: 200, data: { name: 'getBar' } })
  }, 3000)
})
}
async function test1() {
let startTime = new Date()
let foo = await getFoo();  //花费2秒
let bar = await getBar();  //花费3秒
let endTime = new Date()
console.log('test1', endTime - startTime) //花费5秒左右
console.log(foo, bar)
}
async function test2() {
let startTime = new Date()
let [foo, bar] = await Promise.all( [getFoo(), getBar()] )
let endTime = new Date()
console.log('test2', endTime - startTime)  //花费3秒左右
console.log(foo, bar)
}
test1()
test2()
注意点
  • await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。
async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法
async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}
  • await命令只能用在async直接父函数之中,如果用在普通函数,就会报错。
// 报错
async function dbFuc(db) {
  let docs = [{}, {}, {}];
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

// 正常
async function dbFuc(db) {
  let docs = [{}, {}, {}];

  for (let doc of docs) {
    await db.post(doc);
  }
}

11.import、export default、export命令

①export,可以输出变量表达式、function函数、对象, 一个文件中可以导出多个变量
export var year = 1958;
export { year };
export function year(x, y) {
  return x * y;
};
//此时import命令接受一对大括号,里面指定要从其他模块导入的变量名。必须与被导入模块对外接口的名称相同。
// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}
export function circumference(radius) {
  return 2 * Math.PI * radius;
}
//other.js
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
②export default 只能导出一个对象

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。

③export default、export命令区别

1、export与export default均可用于导出常量、函数、文件、模块等 2、在一个文件或模块中,export、import可以有多个,export default仅有一个 3、通过export方式导出,在导入时要加{ }且接收变量名称必须跟导出一致;export default则不需要 { },可以指定任意名称

export const aa = { name: 'bwf' }  // 可以
export {aa}  //可以
export default aa  //可以
export default const aa = { name: 'bwf' }  //报错

export function f1(){}  //可以
export default function f2 () {} //可以