ES6-基本特性

233 阅读9分钟
原文链接: hhqqnu.github.io

环境准备 - 编译ES6

ES6特性不是所有的浏览器都支持,所以需要通过对ES6语法的编译。
JAVASCRIPT打包处理方式有很多,Grunt,Gulp,Webpack,Rollup,这些都是针对于现在前端模块化开发而产生的。

Webpack是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle.

ES6的编译采用WEBPACK+BABEL LOADER进行.
在BABEL中可以使用BABEL PRESETS来控制编译后的浏览器支持。

  • Babel Presets

    optsions: targets、targets.browsers、targets.browsers: ‘last 2 version’、target.browsers: ‘> 1%’
    这些浏览器的控制在browserslist或Can i use 网站可以查找。
  • 环境准备步骤如下:

  1. install nodejs
  2. npm init -y
  3. npm install webpack -g
  4. npm install webpack babel-loader babel-core babel-preset-evn –save-dev
  5. add webpack.config.js
  6. webpack || package.json定义npm scripts命令

基本语法

变量和常量

  • let

允许声明在范围内的变量,使用其上使用的块、语句或表达式。不同于var关键字,定义全局变量或者局部定义,而不考虑块范围。

示例代码:


function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}
function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
}
  • const

const是块范围内定义的变量。通过定义分配一个常量不能改变值,且不能重定义,常量必须初始化.


const number = 42;
try {
  number = 99;
} catch(err) {
  console.log(err);
  // expected output: TypeError: invalid assignment to const `number'
  // Note - error messages will vary depending on browser
}
console.log(number);

解构赋值

解构赋值语法是一个JavaScript表达式,可以将值从数组,或从对象的属性,分为不同的变量。


var a, b, rest;
[a, b] = [10, 20];
console.log(a);
// expected output: 10
console.log(b);
// expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: [30,40,50]

正则扩展

 正则表达式是javascript操作字符串的一个重要组成部分,但在以往的版本中并未有太多改变。在ES6中,随着字符串操作的变更, ES6也对正则表达式进行了一些更新。

  • 构造函数

    在ES5中,RegExp构造函数的参数有两种情况。
    1、 参数是字符串,第二个参数为正则表过式修饰符。
    2、 参数为一个正则表达式,没有第二个参数

    
    var regex = new RegExp('xyz','i');
    var regex = new RegExp(/xyz/i);
    var regex = /xyz/i;

    在ES6中,RegExp构造函数为正则表达式时,允许提供第二参数,第二个参数则会替换原有的正则表达式修饰符。会在返回值里flags,值为替换后的正则表达式修饰符。

    
    var regex = new RegExp(/xyz/ig,'i')
    console.log(regex.flags)
  • u修改符

    正则表达式可以完成简单的字符串操作,但默认将字符串中的每一个字符按照16位编码处理。为了解决这个问题, ES6 对正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

    console.log(/^\uD83D/u.test('\uD83D\uDC2A'));
    console.log(/^\uD83D/.test('\uD83D\uDC2A'));
  • 点号

    点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码位大于0xFFFF的 Unicode 字符,点字符不能识别,必须加上u修饰符

    
    var text = "𠮷" ;
    console.log(text.length); // 2
    console.log(/^.$/.test(text));//false
    console.log(/^.$/u.test(text)); //true
  • 大括号

    ES6 新增了使用大括号表示 Unicode 字符,这种表示法在正则表达式中必须加上u修饰符,才能识别当中的大括号,否则会被解读为量词。

    
    console.log(/\u{61}/.test('a'));
    console.log(/\u{61}/u.test('a'));
    console.log(/\u{20BB7}/u.test('𠮷'))
  • 量词

    使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的 Unicode 字符

    
    console.log(/a{2}/.test('aa'));
    console.log(/a{2}/u.test('aa'));
    console.log(/𠮷{2}/.test('𠮷𠮷'));
    console.log(/𠮷{2}/u.test('𠮷𠮷'));
  • y修饰符

    除了u修饰符,ES6 还为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。
    y修饰符的作用与g修饰符类似,也是全局匹配,后一 次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

    
    let s = 'bbb_bb_b';
    let r1 = /bb+/g;
    let r2 = /bb+/y;
    console.log('r1 sticky:', r1.sticky);
    console.log('r2 sticky:', r2.sticky);
    console.log('f:', r1.exec(s));
    console.log('f:', r2.exec(s));
    console.log('s:', r1.exec(s));
    console.log('s:', r2.exec(s));

字符串扩展

  • 字符串识别
    在原来indexOf方法识别后,ES6添加了includes,startsWith,endsWith

    • includes

    该方法在给定文本存在于字符串中的任意位置时会返回 true ,否则返回false。

    • startsWith

    该方法在给定文本出现在字符串起始处时返回 true ,否则返回 false。

    • endsWith

    该方法在给定文本出现在字符串结尾处时返回 true ,否则返回 false。

    
    let msg = 'test content';
    console.log(msg.startsWith('test'));
  • repeat

    ES6为字符串添加了一个 repeat() 方法,它接受一个参数作为字符串的重复次数,返回一个将初始字符串重复指定次数的新字符串

    
    console.log('test '.repeat(3));
  • 字符串补全

    针对某个字符串不够指定长度,会在头部或尾部补全。

    padStart (头部补全)和padEnd(尾部补全)接受两个参数,第一个为字符串长度,第二个为需要补全的字符串。

    
    console.log('t'.padStart(3,'1'));
  • 字符串模板

模板字面量是增强版的字符串,它用反引号(`)标识。


let name = 'tom';
let message = `Hello,${name}`;
console.log(message);

数组扩展

ES6为了简化数组操作,添加了很多新功能。

  • 静态方法

    • Array.of (数组创建)
    
    let a1 = Array.of(1, 2, 3, 4)
    console.log(a1)
    let a2 = Array.of(a1, 1, 2, 3, 4)
    console.log(a2)
    • Array.from (数组创建)
    
    console.log(Array.from('foo'))
    console.log(Array.from([1, 2, 3], x => x + x))
    • includes (数组判断)
    
    var array1 = [1, 2, 3]
    console.log(array1.includes(2))
    var pets = ['cat', 'dog', 'bat']
    console.log(pets.includes('cat'))
    console.log(pets.includes('at'))
    • find & findIndex (数组查找,find是查找并返回 第一个查找到的值,findIndex是查找并返回第一个值的索引)
    var array1 = [5, 12, 8, 130, 44]
    var found = array1.find(function (element) {
      return element > 10
    });
    var foundIndex = array1.findIndex((ele)=>{
        return ele > 10;
    });
    console.log(`found value:${found},index:${foundIndex}`)
  • fill (数组数据重写)

    
    var array1 = [1, 2, 3, 4];
    console.log(array1.fill(0,2,4));
    console.log(array1.fill(5,1));
    console.log(array1.fill(6));
  • copyWithin (改变数组中的多个元素,值从本数组中进行复制)


var array1 = [1, 2, 3, 4, 5];
console.log(array1.copyWithin(0, 3, 4));
console.log(array1.copyWithin(1, 3));

函数扩展

函数在javascript中是重要组成部分,在ES6中添加了很多特性,使用函数使用更新灵活。

  • 形参默认值

let connetionSql = (connStr,timeOut=1000) => {
    console.log(`connstr:${connStr} timeout:${timeOut}`);
}
connetionSql('mysql:///');
connetionSql("mysql:///",2000);
  • 不定参数

虽然arguments对象来检查函数的所有参数,不必定义要用的参数,但arguments使用起来不是很方便。


// es5
let pick = function (obj) {
let result = Object.create(null)
for (let i = 1; i < arguments.length; i++) {
  console.log(arguments[i])
  result[arguments[i]] = obj[arguments[i]]
}
return result
}
let book = {
  title: 'ES6',
  year: 2017,
  price: 50
}
let bookData = pick(book, 'title', 'year')
console.log(bookData)
//ES 6
let pick = function pick (object, ...keys) {
  let result = Object.create(null)
  for (let i = 0, len = keys.length; i < len; i++) {
    result[keys[i]] = object[keys[i]]
  }
  return result
}
let book = {
  title: 'ES6',
  year: 2017,
  price: 50
}
let bookData = pick(book, 'title', 'year')
console.log(bookData)
  • 箭头函数

箭头函数是函数最有趣的特性,顾名思义,箭头函数是一种使用箭头(=>)定义函数的新语法,但是它与传统的JS函数有些许不同,主要集中在以下方面:

  1. 没有this、super、arguments和new.target绑定箭头函数中的this、super、arguments和new.target这些值由外围最近一层非箭头函数决定

  2. 不能通过NEW关键字调用,箭头函数没有[[construct]]方法,不能被用作构造函数,如果通过new关键字调用箭头函数,程序抛出错误

  3. 没有原型,由于不可以通过new关键字调用箭头函数,因而没有构建原型的需求,所以箭头函数不存在prototype这个属性

  4. 不可以改变this绑定,函数内部的this值不可被改变,在函数的生命周期内始终保持一致

  5. 不支持arguments对象,箭头函数没有arguments绑定,必须通过命名参数和不定参数这两种形式访问函数的参数

  6. 不支持重复的命名参数,无论在严格还是非严格模式下,箭头函数都不支持重复的命名参数;而在传统函数的规定中,只有在严格模式下才不能有重复的命名参数

在箭头函数内,其余的差异主要是减少错误以及理清模糊不清的地方。JS引擎就可以更好地优化箭头函数的执行过程,这些差异的产生有如下原因:

  1. this绑定是JS程序中一个常见的错误来源,在函数内很容易对this的值失去控制,其经常导致程序出现意想不到的行为,箭头函数消除了这方面的烦恼

  2. 如果限制箭头函数的this值,简化代码执行的过程,则JS引擎可以更轻松地优化这些操作,而常规函数往往同时会作为构造函数使用或者以其他方式对其进行修改


var materials = [
    'Hydrogen',
    'Helium',
    'Lithium',
    'Beryllium'
]
console.log(materials.map(material => material.length))
let sum = (num1, num2) => {
    return num1 + num2
}
console.log(sum(10, 20))
let f = ([a, b] = [1, 2], {
    x: c
} = {
    x: a + b
}) => a + b + c;
console.log(f());

对象扩展 - 指OBJECT

随着JS应用复杂度的不断增加,程序中使用对象的数量持续增长,ES6为了提升对象使用效率,通过多种方式来加强对象的使用,简单的语法扩展和更多操作对象及与对象交互的方法。

  • 简洁表达式

    • 属性初始值简写
    // ES5
    var name = 'javascript es6';
    var price = 50;
    var bookInfo = {
        name: name,
        price: price
    };
    //console.log(bookInfo);
    //ES6
    var bookInfoEs6 = {name,price};
    console.log(bookInfoEs6);
    • 对象方法简写
    // ES5
    var people = {
        name: 'zhangsan',
        say: function () {
            console.log('hello ',this.name);
        }
    }
    //people.say();
    //ES6
    var peopleEs6 = {
        name: 'zhangsan es6',
        say () {
            console.log('hello ',this.name)
        }
    }
    peopleEs6.say();
  • 属性表达式

// ES5
var name = 'javascript es6'
var price = 50
var bookInfo = {
  name: name,
  price: price
}
//console.log(bookInfo['name'],bookInfo['price']);
//ES6
let priceField = 'price'
var bookInfoEs6 = {
  name: name,
  [priceField]: price,
  ['sale' + priceField]: price + 10
}
console.log(bookInfoEs6[priceField], bookInfoEs6['sale' + priceField])
  • Object 新增方法

    • 判断相等 - Object.is()
      用于确定两个值是否相同。
    //ES5
    console.log(5 == 5);
    console.log(5 == '5');
    console.log(5 === '5');
    //ES6
    console.log(Object.is(5,5));
    console.log(Object.is(5,'5'));
    • 对象合并 - Object.assign()

    object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    const object1 = {
      a: 1,
      b: 2,
      c: 3
    }
    const object2 = Object.assign({c: 4, d: 5}, object1);
    console.log(object2);
    • Object.entries()

    返回给定对象自己的可枚举属性(键值)对的数组。

    
    let test = {k: 123,o: 456}
    for (let [key, value] of Object.entries(test)) {
      console.log(key, value)
    }

Set && Map

  • SET

SET是一个无重复元素的集合列表。


//声明操作
const set1 = new Set([1, 2, 3, 4, 5])
console.log(set1)
console.log(set1.has(2))
console.log(set1.has(6))
set1.add(6)
console.log(set1)
set1.delete(3)
console.log(set1)
console.log(set1.keys())
// converting between set and array
console.log([...set1])
  • WeakSet

WeakSet允许在集合中存储弱保存的对象。WeakSet与Set的区别在于WeakSet仅可以存储对象。

let weakSet = new WeakSet();
let arg = {};
weakSet.add(arg);
console.log(weakSet);
  • Map

Map对象持有键值对。任何值(对象和原始值)都可以用作键或值。

let map = new Map()
let arr = ['123']
map.set(arr, 456)
console.log('map', map, map.get(arr))
let map2 = new Map([['a', 123], ['b', 456]])
console.log(map2)
map2.forEach((item, key) => {
  console.log(key, item)
})
  • WeakMap

WeakMap是键/值对的集合,其中的键是弱引用的。键必须是对象,值可以是任意值。

Proxy && Reflect

  • Proxy

Proxy用于定义基本操作的自定义行为(例如属性查找、赋值、枚举、函数调用等)

let bookInfo = {
  name: 'javascript ES6',
  price: 50
}
let mointor = new Proxy(bookInfo, {
  get(target, key) {
    if (key === 'price') {
      return '$' + target[key]
    }
    return key in target ? target[key] : 'undefined'
  },
  set(target, key, value) {
    if (key === 'price' && Number.isInteger(value)) {
      target[key] = value
    }
  }
})
console.log(mointor.price)
console.log(mointor.name)
mointor.price = 100
console.log(mointor.price)
  • Reflect

Reflect是一个内置的对象,它提供了用于拦截JavaScript操作的方法。这些方法与代理处理程序的方法相同。反射不是一个函数对象,所以它不是可构造的。


let bookInfo = {
  name: 'javascript ES6',
  price: 50
}
console.log(Reflect.get(bookInfo, 'name'))

实例

function validator (target, validator) {
  return new Proxy(target, {
    _validator: validator,
    set(target, key, value, proxy) {
      if (target.hasOwnProperty(key)) {
        let va = this._validator[key]
        if (!!va(value)) {
          return Reflect.set(target, key, value)
        } else {
          throw Error(`${key}设置值错误`)
        }
      } else {
        throw Error(`${key} 不存在`)
      }
    }
  })
}
const personValidators = {
  name(val) {
    return typeof val === 'string'
  },
  age(val) {
    return typeof val === 'number'
  }
}
class Person {
  constructor (name, age) {
    this.name = name
    this.age = age
    return validator(this, personValidators)
  }
}
const person = new Person('zhangsan', 30)
console.log(person)
person.age = '1111'

类与对象

类声明使用基于原型的继承创建一个具有给定名称的新类。

  • 基本语法
class Parent {
  constructor (name = 'zhangsan') {
    this.name = name
  }
}
let parent = new Parent('lisi')
console.log(parent)
  • 类的继承
class Parent {
  constructor (name = 'zhangsan') {
    this.name = name
  }
}
class Child extends Parent {
  constructor (name, age) {
    super(name)
    this.age = age
  }
}
let c = new Child('lisi', 30)
console.log(c)
  • 静态方式 or 静态属性

class Parent {
  constructor (name = 'zhangsan') {
    this.name = name
  }
  static say () {
    console.log(`hello`)
  }
}
Parent.age = 30
Parent.say()
console.log(Parent.age)
  • getter or setter
class Parent {
  constructor (name = 'zhangsan') {
    this.name = name
  }
  get longName () {
    return 'get-' + this.name
  }
  set longName (name) {
    this.name = 'set-' + name
  }
}
let parent = new Parent('lisi')
console.log(parent.longName)
parent.longName = 'zhangsan'
console.log(parent.longName)

Promise

Promise对象表示异步操作的最终完成(或失败)及其产生的值。主要是用在异步编程,A函数执行完成后执行B。

let promise1 = new Promise((resolve, reject) => {
  // console.log('defined proimse')
  setTimeout(resolve, 1000, 'foo')
})
promise1.then((v) => {
  console.log('执行成功,值为:',v)
}).catch(e => {
  console.log(e)
})
//封装
function request (url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.open('GET', url)
    xhr.onload = () => resolve(xhr.responseText)
    xhr.onerror = () => reject(xhr.statusText)
    xhr.send()
  })
}
request('http:/www.baidu.com')
  .then(v => {
    console.log(v)
  }).catch(e => {
  console.error(e)
})
// promise all
function loadImg (src) {
  return new Promise((resolve, reject) => {
    let img = new Image()
    img.src = src
    img.onload = () => {
      resolve(img)
    }
    img.onerror = (e) => {
      reject(e)
    }
  })
}
function showImgs (imgs) {
  imgs.forEach(img => {
    console.log(img)
  })
}
//全部相应
Promise.all([
  loadImg('http://1.jpg'),
  loadImg('http://2.jpg'),
  loadImg('http://3.jpg')
]).then(imgs => {
  showImgs(imgs)
}).catch(e => {
  console.error(e)
})
//其中一个相应
Promise.race([
  loadImg('http://1.jpg'),
  loadImg('http://2.jpg'),
  loadImg('http://3.jpg')
]).then(imgs => {
  showImgs(imgs)
}).catch(e => {
  console.error(e)
})

Iterator

Iterator接口用于统一读取数据集合的操作,如Array,Map,Set等。

//使用
let arr = ['hello', 'world']
let map = arr[Symbol.iterator]()
console.log(map.next())
console.log(map.next())
console.log(map.next())
//for of
let arr = ['hello', 'world']
for (const value of arr) {
  console.log('value', value)
}
// custom iterator
let obj = {
  start: [1, 3, 2],
  end: [7, 8, 9],
  [Symbol.iterator]() {
    let self = this
    let index = 0
    let arr = self.start.concat(self.end)
    let len = arr.length
    return {
      next() {
        return {
          value: arr[index++],
          done: !(index < len)
        }
      }
    }
  }
}
for (let key of obj) {
  console.log(key)
}

Generator

Generator由生成器函数返回,它符合iterable协议和迭代器协议,主要是解决异步编程,使用yield关键字,调用next进行执行一下步代码。

//基本使用
function* gen() {
    yield 1
    yield 2
    yield 3
}
let g = gen()
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())
// 使用generator实现iterator
let obj = {}
obj[Symbol.iterator] = function* () {
    yield 1
    yield 2
    yield 3
}
for (const value of obj) {
    console.log(value)
}
// 状态机
let state = function* () {
    while (1) {
        yield 'A'
        yield 'B'
        yield 'C'
    }
}
let status = state()
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
//async
let state = async function () {
    while (1) {
        await 'A'
        await 'B'
        await 'C'
    }
}
let status = state()
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
console.log(status.next())
//实例 - 长轮询
let ajax = function * () {
  yield new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        code: 0
      })
    }, 200)
  })
}
let pull = function () {
  let generator = ajax()
  let step = generator.next()
  step.value.then((d) => {
    if (d.code != 0) {
      setTimeout(function () {
        console.log('wait')
        pull()
      }, 1000)
    } else {
      console.log(d)
    }
  })
}
pull()

Decorator

Decorator 是用来修改类行为,是函数、类、修改行为。Decorator仅当前类中有用。

npm install babel-plugin-transform-decorators-legacy


//定义类型属性或方法
 let readonly = function (target, name, descriptor) {
      descriptor.writable = false
      return descriptor
  }
  class Test {
      @readonly
      time() {
          return '2018-04-19'
      }
  }
  let test = new Test()
  console.log(test.time())
  /*
  test.time = function () {
      console.log('reset time method')
  }
  */
 //类修饰符
 let typename = function (target, name, descriptor) {
      target.myname = 'hello'
 }
  @typename
  class Test {}
  console.log('类修饰符', Test.myname)
// 实例 - 页面日志收集
let log = (type) => {
    return function (target, name, descriptor) {
        console.dir(descriptor)
        let src_method = descriptor.value;
        descriptor.value = (...args) => {
            src_method.apply(target, args);
            console.log(`log ${type}`)
        }
    }
}
class Page {
    @log('show')
    show() {
        console.log('show page')
    }
    @log('click')
    click(count) {
        console.log('click page:',count)
    }
}
let page = new Page()
page.show()
page.click(1)
page.click(2)

Module

模块是自动运行在严格模式下并且没有办法退出运行的JS代码。与共享一切架构相反的是,在模块顶部创建的变量不会自动被添加到全局共享作用域,这个变量仅在模块的顶级作用域中存在,而且模块必须导出一些外部代码可以访问的元素,如变量或函数。模块也可以从其他模块导入绑定。

另外两个模块的特性与作用域关系不大,但也很重要。首先,在模块的顶部,this的值是undefined;其次,模块不支持HTML风格的代码注释,这是从早期浏览器残余下来的JS特性

脚本,也就是任何不是模块的JS代码,则缺少这些特性。模块和其他JS代码之间的差异可能乍一看不起眼,但是它们代表了JS代码加载和求值的一个重要变化。模块真正的魔力所在是仅导出和导入需要的绑定,而不是将所用东西都放到一个文件。只有很好地理解了导出和导入才能理解模块与脚本的区别

  • 导出
//导出变量
export let A = 123
//导出方法
export function test () {
  console.log('test')
}
//导出类
export class Hello {
  test () {
    console.log('class')
  }
}
//导出默认类
export default class Hello2 {
    test() {
        console.log('test2')
    }
}
//统一导出,由import进行定义名称
let A = 123
let test = () => {
  console.log('fun test')
}
class Hello {
    test() {
        console.log('hello class')
    }
}
export default {
    A,
    test,
    Hello
}
  • 导入
//根据导出的名称进行导入
import { Hello, A, test } from './16-module_export'
//导入默认项
import Hello2 from './16-module_export'
//导入全部,但默认项不会此导入别名中
import * as e from './16-module_export'
//导入默认项为export default
import m from './16-module_export'