ES6常用知识

202 阅读7分钟

es6出来好久了一直都没学,东西挺多的,找了一些博客整理一些常用的知识点,按照自己理解敲了一遍代码,想了解全部的那无疑就是阮一峰大大的博客啦。
参考博客:
ECMAScript 6 入门
实例感受-es6的常用语法和优越性
ES6常用知识点概述
JS几种数组遍历方式以及性能分析对比

let与const

  • 块级作用域
  • 声明不提升
  • 不可重复声明
  • const声明的为常量,如果是基本类型,值不可更改,如果是引用类型,引用不可改变,但是值可以改变
if(true){
    var a=10;
    let b=20;
}
console.log(a); // 10
console.log(b); // 报错,没有定义,只能在块级作用域中使用

function fn(){
    console.log(a1);  // undefined 变量声明提升,有声明无初始值
    var a1=10;
    console.log(a2); // 报错,没有定义
    let a2=20;
}
fn();

var b=10;
let b=12; //报错 a已经被声明

const c1=10;
c1=20; // 报错,不能更改const常量

const c2=[1,2]; // 数组c2的地址不可更改
c2[1]=3; // 可以,更改值可以
console.log(c2); // [1,3]
c2=[1,3]; // 报错,不可重新赋值地址

编程风格

  • 考虑到var和let语义相同且let没有全局污染的缺点,应该全面用let替换var
  • let与const都可以时,应该使用const,所有的函数表达式都应该使用const,所有的全局参数都应该尽量使用const

字符串

字符串模板

用es5写一些较长的含有变量的字符串时,总是使用+号进行拼接,极其不方便,es6提供了字符串模板的方式填充变量。
字符串模板必须使用在反引号``中

// es5
var a=10;
var b='<div id="'+a+'"></div>'; // 拼接很麻烦,尤其是涉及到单双引号
console.log(b); // <div id="10"></div>

// es6
let c=`<div id="${a}"></div>` // 放到反引号中
console.log(c) // <div id="10"></div>

// 反引号中所有的空格都会保留,可以使用trim()去除头尾的空格

字符串遍历/数组遍历/对象遍历

es6新增了字符串的遍历接口

let str='abcdefg';
for(let s of str){
  console.log(s); // a b c d e f g
}

再整理一下数组和对象常用的遍历方法

// 数组遍历

// for循环是最常用的一种
for(let i=0;i<arr.length;i++){
}

// 将数组长度保存下来,是上面一种的改进版本,几种遍历方法中性能最好
for(let i=0,len=arr.length;i<len;i++){
}

// for in,用法上是for循环的变体,但效率是最低的
for(let key in arr){
    console.log(arr[key])    
}

// for of,es6提供的,同时支持一些类数组的遍历 
for(let value of arr){
    console.log(value)
}

// forEach(),可以同时操作value和key,但是性能比不上for循环
arr.forEach(function(value,key,arr){
})

// 对象遍历

// for in ,遍历对象自身和继承的可枚举属性
for(let key in obj){
    console.log(obj[key])
}

// Object.keys(obj)
// Object.keys返回一个数组,包括对象自身的所有可枚举属性的键名。
Object.keys(obj).forEach(function(key){
    console.log(obj[key])
})

// Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性,包括不可枚举属性的键名。
Object.getOwnPropertyNames(obj).forEach(function(key){
    console.log(obj[key])
})

解构赋值

es6允许按照一定的模式从数组和对象中提取值,然后对变量进行赋值。

// 一一对应
let [a,b,c] = [1,2,3]
console.log(`${a}====${b}====${c}`) // 1====2====3

let [s1,arr] = [1,[1,2,3]] 
console.log(s1) // 1
console.log(arr) // [1,2,3]

// 左少右多
let [a,b]=[1,2,3,4] // a=1,b=2
// 左多右少
let [a,b,c,d]=[1,2] // a=1,b=2,c=undefined,d=undefined

// 可以默认值
let [a,b,c=10]=[1,2] // a=1,b=2,c=10

// 与扩展运算符一起使用
let [a,...arr]=[1,2,3,4]
console.log(a) // 1
console.log(arr) // [2,3,4]

// 对象的解构
let {name, age} = {name:'xx',age: 10} 
console.log(name) // xx
console.log(age) // 10

// 对象的解构赋值与顺序没有关系,而是属性名相同
// let {name, age} = {name:'xx',age: 10} 与 let {age,name} = {name:'xx',age: 10}结果相同

// let {age,name} 是let {name:name,age:age}的简写,赋值的是后面的属性变量
let { foo: baz , bar } = { foo: "aaa", bar: "bbb" }; 
foo // foo is not defined
baz // 'aaa'
bar // 'bbb

编程风格

  • 当使用多个数组元素时,优先考虑解构赋值
const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;
  • 当函数参数为对象时,优先考虑解构赋值
// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;
}

// good
function getFullName(obj) {
  const { firstName, lastName } = obj;
}

// best
function getFullName({ firstName, lastName }) {
}

函数

箭头函数

// es5
var add=function(x,y){
    rerturn x+y;
}

// es6
const add=(x,y)=>x+y

// 没有参数
const f=()=>5
//等同于
var f=function(){
    return 6
}

// 只有一个参数
const f=x=>x+5
// 等同于
var f=function(x){
    return x+5
}

// 有多个参数和语句
const f=(x,y)=>{x++;y++;return x+y}

** 注意 ** 箭头函数内部没有this, 所以不可当作构造函数使用,因为没有this,所以在箭头函数内部使用this的话,会绑定到箭头函数定义时的作用域。
箭头函数的该特性特别适合用于回调函数。

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};
// 这里的this始终指向handler, 而如果是普通函数的话,则会使用调用该函数的对象,即document,就会出现错误。

reset参数

reset参数用于收集函数的多余参数,并且存入一个真正的数组中,这样就可以不必使用arguments这个伪数组参数了。

function add(...values) {
  let sum = 0;
  for (var val of values) {
    sum += val;
  }
  return sum;
}

add(2, 5, 3) // 10

// 替换arguments
// arguments变量的写法
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

编程风格

  • 单行的,简单的,不用复用的函数都尽量使用箭头函数
  • 需要绑定this的函数也使用箭头函数
  • 复杂的经常复用的函数还是需要使用一般函数
  • 使用reset参数代替arguments

class与extends

在es5中使用构造函数和原型的方式来定义类,es6封装了class来更接近java/c++等语言的编写习惯.

// es5
function Person(x,y){
    this.x=x;
    this.y=y;
}
Person.prototype.say=function(){
    return this.x+this.y
}
var p=new Person('x','y')

// es6将其改写为
class Person{
    constructor(x,y){
        this.x=x;
        this.y=y;
    }
    say(){
        return this.x+this.y
    }
}
let p=new Person(x,y) // 默认会调用constructor
p.say()

所有在类中声明的方法其实都是定义在protoytpe属性上。
在es5中一般使用组合继承的方式来实现继承,写起来很麻烦,es6采用extends关键字来实现继承,也更贴近java/c++的写法。

class Person{
  constructor(x,y){
    this.x=x;
    this.y=y;
  }
  say(){
    console.log(`${this.x}++++++${this.y}`)
  }
}

let p=new Person('xixi',10);
p.say(); // xixi++++++10

class Girl extends Person{
  constructor(name,age,sex){
    super(name,age) // 调用父类构造函数,必须要写,后面才能使用this
    this.sex=sex
  }
  play(){
    super.say() // 调用父类方法
    console.log(this.sex)
  }
}

let g=new Girl('xi',10,'girl')
g.play() // xi++++++10 girl

编程风格

  • 应该使用class与extends来代替es5中的构造函数和原型继承

CommonJS/AMD/ES6 Module

这些都是对js模块化的规范输出,所谓js模块化,我理解的就是一个js文件就是一个模块,里面封装了某些功能,可以在其他地方导入使用,像python里import某个包一样。
可以使用外联script的形式引入别人的js代码,如:

<script src='file1.js'>
<script src='file2.js'>
<script src='file3.js'>

但是这样需要注意引用顺序,如file2.js里面需要file1.js里面的某个函数,必须要放在file1.js后面,同时还存在变量污染的问题。所以就出现了模块的概念,模块里面的变量是私有的,不会影响到别的模块里的变量,同时为了可以方便的使用其他人的模块,而不是需要一个个去输入网址去找版本下载,也出现了npm之类的包管理器,将大家的模块统一管理,使用指令即可下载使用。

CommonJS

是通过node.js实现的,在后端使用的模块化规范。

// add.js
function add(x,y){
    return x+y
}
module.exports=add

// index.js
const add=require('./add.js')

导出模块的原理,首先nodejs会先准备一个module对象:

// 准备module对象:
var module = {
    id: 'add',
    exports: {}
};
var load = function (module) {
    // 读取的add.js代码:
    function add(x,y){
        return x+y
    }
    
    module.exports = add;
    // add.js代码结束
    return module.exports;
};
var exported = load(module);
// 保存module:
save(module, exported);

Node保存了所有导入的module,当我们用require()获取module时,Node找到对应的module,把这个module的exports变量返回,这样,另一个模块就顺利拿到了模块的输出:

const add=require('./add.js')

关于模块中变量不会污染,是因为nodejs在加载模块时,会将代码自动放入一个立即执行函数中形成自己的作用域,这样全局变量就变成了局部变量。

AMD

commonjs虽然可以实现模块化,但是机制是同步的,适合后端,对于浏览器端,应该使用异步机制,这就产生了AMD,Asynchromous Module Definition,异步加载模块,是requirejs关于模块化标准输出。

// myModule.js
define(['依赖的模块路径'], function(依赖模块名称){
&emsp;...
&emsp;return {
&emsp;};
});

// index.js
require(['./myModule'],function(myModule){
})

es6 module

es6也规范了模块化的概念,比requirejs更加高效,使用的是export和import

// 可以导出多种形式
export var a=10

var m=1
export {m} // 必须加{}

var fn=function(){}

export fn=function(){}
export {fn}

// 引入
import {fn} from './xxx.js'
import * from './xxx.js'