# es6 新增内容

在这里插入图片描述

# 1. 变量

  • var声明的变量是function scope也就是在函数中可以访问到,并不是在大括号里面声明的,外层就访问不到。let const声明的变量都是block scope块级作用域,也就是在大括号里面可以访问到。

  • letconst 都不允许重复声明。他们两个都不可以在同一个作用域中进行重复声明,比如,在函数中声明的变量跟在函数外声明的同名的变量是不同的。let 定义的变量可以重新赋值,const是不可以的。

  • const 声明的对象,对象是一个引用类型的值,可以进行修改对象的属性。如果想要定义的对象的属性是不可以改变可以使用es5中的Object.freeze()这个方法:const jelly = Object.freeze(person)

  • letconst都是存在暂时性死区的,也就是说;我们在声明前使用调用变量,会报RefrenceError的错误;而var声明的变量是没有的;这样,我们也可以养成习惯,在使用变量之前,进行声明。

  • letconst 避免了全局变量与顶层对象的属性挂钩。不再像 var 声明的变量一样挂载到 window 对象上;

  • const 只是不能修改变量所指的内存地址;object.freeze 方法只是浅冻结如果属性还是 object,不会进行冻结

# 2. 箭头函数

# 2.1 优点:

  • 简明语法
  • 可以隐式返回:也就是可以不使用return关键字进行返回;删除{}大括号。
  • 不绑定this:箭头函数没有自己的this值,他的this值是继承他的父级作用域的,this值是定义的时候就绑定了。
  • 箭头函数也是匿名函数,我们可以通过定义变量,等于箭头函数,当进行调用:
const gree = name => { alert (`hello ${name}`)}

一个函数在他独立运行的时候,也就是说他没有作为对象的方法去调用,也没用callapply等方法进行绑定;这里的数组里面map的回调函数就是一个独立运行的,所以他的this值是在运行的时候动态绑定的,绑定到了window对象上,所以不会获取到name值。

const jelly = {
  name: 'jiegiser',
  bobbies: ['sleeping', 'coding', 'reading'],
  print: function () {
    this.hobbies.map(function (hobby) {
      console.log(`${this.name} loves ${hobby}`)
    })
  }

}

# 2.2 箭头函数的局限性:

  • 作为构造函数,一个方法需要绑定到对象:下面的代码。Person对象中使用箭头函数,并没有将this绑定到它本身。所以,这里需要使用原始的函数。下面的给原型绑定方法,同样也是,this指向的是父级的this所以指向window对象;
const Person = (name, points) => {
  this.name = name;
  this.points = points;
}
const jelly = new Person ('Jelly', 5);
Person.prototype.updatePoints = () => {
  this.points ++;
  console.count(this.points)
}
  • 当你真的需要this的时候:下面代码的this指向的是调用addEventListener方法的对象;箭头函数的this是不会绑定的,所以这里的也还是window对象。setTimeout里面应该是使用箭头函数,来邦定this值在该作用域。
const button = document.querySelector('.zoom');
button.addEventListener('click', () => {
  this.classList.add('in');
  setTimeout(() => {
    this.classList.remove('in')
  }, 2000);
})
  • 需要使用arguments对象:箭头函数中是没有arguments对象的。
const sum = () => {
  return Array.from(arguments).reduce((prevSum, value) => prevSum + value,0)
}
  • 箭头函数不能作为构造函数来使用;
  • this 指向定义时所在的对象,而不是调用时所在的对象;

# 3. ES6 参数默认值

我们在函数中可以通过下面的方法进行设置默认参数值:

function add (a = 1, b = 5) {
  return a + b;
}

当我们需要第一个参数使用默认值的时候,这样add(undeffined, 2)这样,如果add(1)这样调用的话,第二个参数使用默认值。

# 4. 模板字符串

es6中的模板字符串是可以嵌套的,以及在${}中可以直接调用方法。 还有一个标签模板字符串:

function highLight(string, user, topic) {
  return 'templa'
}
const user = 'jie';
const topic = 'learn es6';
const sentence = highLight`${user} has commented on your topic ${topic}`

然后我们查看sentence,他就会返回标签模板字符串中返回的东西; 在这里插入图片描述 对应标签模板字符串是有是三个参数的,string, user, topic第一个参数是返回模板字符串中默认内容;返回值是一个数组,如果不是以模板字符串传入的变量开头的,那数组的第一个不死空字符串;如上面代码中,返回的是[" ",has commented on your topic," "],然后后面的参数就是模板字符串中传入的变量。我们这里传入的是${user}${topic},但是如果我们传入了很多的参数,这个时候我们可以使用es6的剩余参数:...values他是参数的数组,这样我们就获取到所有的参数。我们可以使用标签模板字符串,来返回我们想要的值。

function highLight(string, ...values) {
  debugger;
  return 'templa'
}

我们可以使用标签模板字符串,去处理我们输入的参数,比如在留言板等功能中,为了防止xss攻击,我们可以通过模板字符串进行过滤用户输入的内容。可以使用第三方的包:DOMPurify进行过滤。

# 5. 对象解构

我们有一个对象如下:

const Tom = {
  name: 'tom',
  age: 18,
  family: {
    mother: 'tom mother',
    father: 'tom father'
  }
}

我们每次访问的时候,需要进行Tom.name这样进行访问,如果属性有很多的话,就比较麻烦,我们可以这样写:这样去访问他的属性


const Tom = {
  name: 'tom',
  age: 18,
  family: {
    mother: 'tom mother',
    father: 'tom father'
  }
}

const { name, age } =Tom
console.log(name)
console.log(age)

如果我们想要先声明变量,然后进行结构对象,可以如下面这样写:

let name = '';
({ name, age } = Tom);

对象解构也可以进行嵌套,如上面的代码,我们想要访问family属性,我们可以这样写:

const Tom = {
  name: 'tom',
  age: 18,
  family: {
    mother: 'tom mother',
    father: 'tom father'
  }
}

const { father, mother } =Tom.family
console.log(mother)
console.log(father)

我们在解构的时候也可以进行重新命名,类似sql中的as;可以这样写:father: f这样,将father属性赋值给f变量。

const Tom = {
  name: 'tom',
  age: 18,
  family: {
    mother: 'tom mother',
    father: 'tom father'
  }
}

const { father: f, mother } =Tom.family
console.log(mother)
console.log(father)

我们在对象解构的时候,也可以给解构后的变量赋予默认值,如果被解构的对象没有该属性为undefined,就使用我们的默认值,如下代码:sister = 'haove no sister'这样。

const Tom = {
  name: 'tom',
  age: 18,
  family: {
    mother: 'tom mother',
    father: 'tom father'
  }
}

const { father: f, mother, sister = 'haove no sister' } =Tom.family
console.log(mother)
console.log(father)

如下面代码,经常会在封装一些插件中,会使用默认值: 在这里插入图片描述

# 6. 数组解构

数组的解构基本跟对象的解构类似:下面代码是解构了数组的第一项,跟第二项

const number = ['one', 'two', 'three', 'four'];
const [one, two] = number;
console.log(one, two)

如果我们想回去第一个跟第三个:需要添加一个逗号进行分隔,流出第二个位置。

const number = ['one', 'two', 'three', 'four'];
const [one, , two] = number;
console.log(one, two)

如果想要获取到第一个值,跟后面所有的值,我们可以使用扩展运算符,将剩余项组成一个数组:

const number = ['one', 'two', 'three', 'four'];
const [one, ...other] = number;
console.log(one, other)

rest参数只能使最后一个,也就是说解析后面所有的,不能解析某一部分:const [one, ...other, four] = number;这样就会报错。

数组的结构也可以进行赋予默认值:同样相应的值为undefined的时候,才会使用默认的值。

const number = ['one', 'two', 'three'];
const [one, two, three = 'three'] = number;

数组的解构的使用技巧可以用在交换两个变量的值:

let a = 10;
let b = 20;
[a,b] = [b,a];

# 7. for of 用法

forEach循环不能终止循环,for in循环,循环的是index值,遍历的是变量的可枚举属性。即使是原型的属性也会进行循环出来。使用for of是循环的属性值:在循环中,也可以使用break进行中断循环。

const number = ['one', 'two', 'three', 'four'];

for (item of number) {
  console.log(item)
}

for of循环还可以用于遍历数组的遍历器属性:

const number = ['one', 'two', 'three', 'four'];

for ( let [index, fruit] of number.entries()){
  console.log(index, number)
}

for of不支持对象的循环。可以应用于字符串:

const number = 'sddsdsd;

for ( let code of number){
  console.log(code)
}

for of循环还可以用于nodeList进行循环:

const lis = document.querySelectorAll('li')
for ( let li of lis) {
  li.addEventListener('click', function () {
    //执行方法
  })
}

# 8. Array.from() Array.of()

# 8.1 Array.from()

这两个方法并不是数组原型的方法,我们需要通过Array.from() Array.of()这种去调用,而不是声明一个数组点上这个方法。Array.from()方法是将一个类素组对象转换为数组对象。类素组对象也就是拥有length属性的对象,modeList就是一个类数组对象。

const lis = document.querySelectorAll('li')
const lisArr = Array.form(lis)
const names = lisArray.map(li => li.textContent)

Array.from()方法有两个参数,第一个参数是需要准换的类数组,第二个参数类似数组的map方法,会给转化后的数组中的每一项执行该方法,于是,上面的代码可以简写为下面:

const lis = document.querySelectorAll('li')
const names = Array.form(lis, li => li.textContent)

也可以利用Array.from()方法进行转换函数中的arguments属性进行转化。 我们也可以利用Array.from()方法将字符串也能转换为一个数组。

const number = 'sddsdsd;
console.log(Array.from(number))

# 8.2 Array.of()

Array.of()方法就是根据传入的参数,返回一个由传入的参数组成的数组;

# 9. 数组的其他方法

# 9.1 .find()

查找是否有bananas这个选项;返回的是找到的该选项,以对象返回;

const inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5},
]
const bananas = inventory.find(fruit => {
  if (fruit.name === 'bananas') {
    return true
  }
  return false
})

# 9.2 .findIndex()

findIndex()返回的是要查找的选项的索引值:

const inventory = [
  {name: 'apples', quantity: 2},
  {name: 'bananas', quantity: 0},
  {name: 'cherries', quantity: 5},
]
const bananas = inventory.findIndex(fruit => fruit.name === 'bananas');//返回的是索引值1

# 9.3 .some()

返回的布尔值;如果有一部分满足测试函数,就返回true

const isEnough= inventory.some(fruit => fruit.quantity > 0);//返回true

# 9.4 .every()

返回的布尔值;如果所有的都满足测试函数,就返回true

const isAllenough= inventory.every(fruit => fruit.quantity > 0);//返回false

# 10. 剩余参数

function sum (...numbers) {
  console.log(numbers)//一个数组[1, 2, 3, 4]
}
sum (1, 2, 3, 4)

数组相关:

let arr = [1, 2, 3, 3, 4, 5, 5]

for(let i = 0; i < arr.length; i++) {
  if (arr[i] === 2) {
    // break
    // 跳出这次循环-在 for 中都可以使用
    continue
  }
  console.log(arr[i])
}

// forEach
arr.forEach((elem, index, array) => {
  // 不能使用 break、continue
  console.log(elem, index)
})


// map - 同样不能使用 break、continue
arr.map(val => console.log(val))

// filter
let res = arr.filter(val => val > 2)
console.log(res, 'filter')

// some
let resSome = arr.some(val => val > 2)
console.log(resSome, 'some') // boolean

// every
let every = arr.every(val => val > 2)
console.log(every, 'every')

// reduce
let resR = arr.reduce((prev, cur) => prev + cur)
console.log(resR, 'reduce')
// 求出数组最大值
let max = arr.reduce((prev, cur) => Math.max(prev, cur))
console.log(max, 'max')
// 去重
let resC = arr.reduce((prev, cur) => {
  prev.indexOf(cur) === -1 && prev.push(cur)
  return prev
}, [])
console.log(resC)

// find 返回第一个测试通过的元素
let find = arr.find(val => val === 2)
console.log(find, 'find')

// findIndex
let findIndex = arr.findIndex(val => val === 2)
console.log(findIndex, 'findIndex')

// for of
for(let [index, item] of arr.entries()) {
  console.log(index, item)
}
  • includes

es6 新增的 includes 跟 es5 的 indexOf 是一样的作用,返回数组中是否存在某个元素,但是对于 NaN 来说,indexOf 返回 -1,而 includes 是返回 true;

# 11. 扩展运算符

我们如果想要两个数组,合并为一个数组,并且,在中间插入一个值,如下我们以前写的代码:

const youngers = ['george' , 'john', 'Thomas'];
const olders = ['James', 'Adrew', 'Martin'];

let members = [];
members = members.concat(youngers);
members.push('Mary');
members = members.concat(olders);

使用es6可以这样写:

const youngers = ['george' , 'john', 'Thomas'];
const olders = ['James', 'Adrew', 'Martin'];

let members = [...youngers, 'Mary', ...olders];

可以通过扩展运算符将一个字符串变为以每一个字符变为数组中的一项:

[...'jiegiser']
//["j", "i", "e", "g", "i", "s", "e", "r"]

可以借助扩展运算符,放置我们将数组赋值给另一个数组,修改另一个数组中的数据,会将源数组的值进行改变:

const currentMembers = [...numbers];

我们可以使用扩展运算符将可遍历对象扩展到一个新的数组中:

const todos = [...document.querySelectorAll('li')]

比如我们需要删除一个数组中,对象属性id为2的一项;代码如下:

const todos = [
  {id: 1, name: 'Go to store', completed: false},
  {id: 2, name: 'Wacth TV', completed: true},
  {id: 3, name: 'Go Shopping', completed: false},
]
// 要删除的项的id为2
const id = 2;
const todoIndex = todos.findIndex(todo => todo.id === id)

const newTodos = [...todos.slice(0, todoIndex), ...todos.slice(todoIndex + 1)];

我们还可以将一个数组通过扩展运算符,追加到另一个数组后:

const fruit = ['apple', 'bananas', 'pear'];
const newFruit = ['orange', 'mongo'];
fruit.push(...newFruit)

还可以利用扩展运算符,将数组扩展为函数的参数:

const dateField = [2019, 6, 2];
const data = new Date(...dateField)
console.log(data);

# 12. 对象的计算属性

假如我们需要定义一个对象:我们希望我们的'user -1': 1这个数组是每次加一,然后进行定义变量。

const userIds = {
  'user -1': 1
  'user-2': 2
}

我们可以这样写:

let id = 0
const userIds = {
  [`user-${++id}`]: id,
  [`user-${++id}`]: id,
  [`user-${++id}`]: id,
}

如果我们想要定义一个对象,对象的键是一个数组,对应的每一个值是一个数组,我们可以这样写:

const keys = ['name', 'age', 'birthday'];
const values = ['jiegiser', 18, '0501'];
const jiegiser = {
  [keys.shift()]: values.shift(),
  [keys.shift()]: values.shift(),
  [keys.shift()]: values.shift(),
}

# 13. ES6 的 Promise 对象

如下代码,then里面是执行ajax执行成功之后的回调函数,catch是如果请求出现异常,执行的方法。这里是返回一个promise对象,所以可以继续在外层使用.then()方法。return axios.get(https://api.github.com/users/${username}/repos);

let username;
const usersPromise = axios.get('https://api.github.com/users');

usersPromise
  .then(response => {
    username = response.data[0].login;
    return axios.get(`https://api.github.com/users/${username}/repos`);
  })
  .then(response => {
    console.log(response.data)
  })
  .catch(err => {
    console.log(err)
  })

一个promise对象的实例:

const p = new Promise((resolve, reject) => {
  //请求成功执行
  resolve('success');
  // 请求失败的执行
  reject(Error('error'))
})
//请求成功执行的回调
p.then(data => {
  console.log(data)
})
// 请求失败的执行的回调
  .catch(err => {console.log(err)})

如果我们的页面中有多个Promise对象,这些对象之间执行的顺序时不相关的,互不影响的,我们可以使用下面的方法,处理这些Promise对象返回的结果:这里需要注意的是,.all()方法返回的结果是对应的执行Promise对象的结果,返回的是一个数组,我们可以使用对象解构,去得到不同的请求返回的结果。只有当.all()方法里面的Promise对象全部返回的是resolve的时候,才会执行.then()方法。否则执行.catch()

Promise
  .all([userPromise, movePromise])
  .then(response => {
    const [users, movice] = response;
    console.log(users);
    console.log(movice);
  })
  .catch(err => {
    console.log(err)
  })

.all()方法相对的是一个·Promise.race()方法,他同样也是处理多个Promise实例,但是,他是处理的Promise实例中,只要第一个Promise实例是执行resolve也就是回调成功,就会去执行.then()方法。否则执行.catch()

这里需要注意的是,下面代码并不是说只要执行 reject,后面的代码不会执行;链式操作 .then 是看前一个 promise 返回的结果而已;

new Promise((resolve, reject) => {
   resolve('1')
}).then(res => {
  console.log(res)
  return new Promise((resolve, reject) => {reject('2')})
}).then(res => {
  console.log(res)
}, err => {
  console.log(err)
}).then(res => {
  console.log(res)
})

// 1
// 2
// undefined


new Promise((resolve, reject) => {
   resolve('1')
}).then(res => {
  console.log(res, '1')
}).then(res => {
  console.log(res, '2')
}, err => {
  console.log(err, '3')
}).then(res => {
  console.log(res, '4')
})

// 1 1
// undefined "2"
// undefined "4"

如果说我们使用 catch 去处理错误请求,那么 .then 的链式操作如果出现 reject;不会往下执行:

new Promise((resolve, reject) => {
   resolve('1')
}).then(res => {
  console.log(res)
  return new Promise((resolve, reject) => { reject('2') })
}).then(res => {
  console.log(res)
}).then(res => {
  console.log(res) // 不会执行
}).catch(err => {
  console.log(err, 'err')
})

// 1
// 2 error

# 14. Symbol

symbol是生成一个唯一的标识符,如下代码:

const one = Symbol('one')
const two = Symbol('two')

console.log(one === two) // false

我们可以使用symbol来给 对象定义一个相同属性名,但是值不同:

const classRoom = {
  [Symbol('lily')]: {grade: 60, gender: 'female'},
  [Symbol('nina')]: {grade: 80, gender: 'female'},
  [Symbol('nina')]: {grade: 90, gender: 'female'},
}

symbol类型的值是不能遍历的,如上面的属性,是不能通过for in等进行遍历的。可以使用Object.getOwnPropertySymbols(classRoom)进行遍历,获取到属性名。获取属性值:classrom[Symbol(lily)]这样进行获取,不能通过.的方法进行获取。前面定义symbol属性的时候,是需要使用计算属性的方式进行定义。

const classRoom = {
  [Symbol('lily')]: {grade: 60, gender: 'female'},
  [Symbol('nina')]: {grade: 80, gender: 'female'},
  [Symbol('nina')]: {grade: 90, gender: 'female'},
}
Object.getOwnPropertySymbols(classRoom)

初始化的时候如果传递一个对象,他会把对象的 toString 方法的返回值设置为标识:

let a = {
  name: 'a',
  toString() {
    return this.name
  }
}

let s = Symbol(a)
console.log(s) // Symbol(a)

通过 description 获取标识


let s = Symbol('foo')
console.log(s.description)

通过 Symbol.for 声明全局描述符 (全局环境下):会进行查找,之前有没有声明过;

let s1 = Symbol.for('foo')
let s2 = Symbol.for('foo')
console.log(s1 === s2) // true

可以通过 Symbol.keyFor 查找标识是否全局定义:

let s1 = Symbol('foo')
console.log(Symbol.keyFor(s1)) // undefined
let s2 = Symbol.for('foo')
console.log(Symbol.keyFor(s2)) // foo

不能使用 for in、Object.keys() 来遍历通过 symbol 在对象上定义的属性,可以通过 Reflect.ownKeys 来遍历获取到普通属性以及 symbol 属性:

const sym = Symbol('im')

class User {
  constructor(name) {
    this.name = name
    this[sym] = 'imc'
  }

  getName() {
    return this.name + this[sym]
  }
}

const user = new User('jie')
console.log(user.getName()) // jieimc

for (let key in user) {
  console.log(key) // name
}

// 只能获取到 symbol 类型的属性
for (let key of Object.getOwnPropertySymbols(user)) {
  console.log(key) // Symbol(im)
}

// 获取所有属性
for (let key of Reflect.ownKeys(user)) {
  console.log(key)
}
// name
// Symbol(im)

Symbol 另一种用法:消除魔术字符串:

const shapeType = {
  triangle: Symbol(),
  circle: Symbol()
}

function getArea(shape) {
  let area = 0
  switch(shape) {
    case shapeType.triangle:
         area = 1
         break
    case shapeType.circle:
       area = 2
       break
  }
  return area
}

console.log(getArea(shapeType.triangle))

# 对象新增

  • 属性简洁表示法:如果对象键跟属性相等可以省略属性值
let name = 'jie'

let person = {
  name,
  age: 12
}
  • 属性名表达式:属性的键可以动态赋值
let name = 'jie'
let dk = 'school'

let person = {
  name,
  age: 12,
  [dk]: 'gasu'
}

  • Object.is 判断两个值是否相等;与 === 区别是,两个 NaN 返回的结果是 true:Object.is(NaN, NaN)
console.log(Object.is(NaN, NaN)) // true
console.log(Object.is(+0, -0)) // false
  • 扩展运算符与 Object.assign()
let x = {
  a: 3, 
  b: 3
}

let y = {...x}
console.log(y)

let z = {}
Object.assign(z, x)
console.log(z)
  • in:是否包含该属性
let x = {
  a: 3, 
  b: 3
}

console.log('a' in x)
  • 对象的遍历方式
for (const key in x) {
  console.log(key, x[key])
}

Object.keys(x).forEach(key => console.log(key, x[key]))

Object.getOwnPropertyNames(x).forEach(key => console.log(key, x[key]))

Reflect.ownKeys(x).forEach(key => console.log(key, x[key]))

# 深拷贝与浅拷贝

# 深拷贝

// 检查类型
const checkType = taregt => Object.prototype.toString.call(taregt).slice(8, -1)

const deepClone = target => {
  let targetType = checkType(target)
  let result
  if (targetType === 'Object') {
    result = {}
  } else if (targetType === 'Array') {
    result = []
  } else {
    return target
  }

  for (const key in target) {
    let value = target[key]
    let valueType = checkType(value)
    if (valueType === 'Object' || valueType === 'Array') {
      result[key] = deepClone(value)
    } else {
      result[key] = value
    }
  }
  return result
}

# 浅拷贝

Object.assign()

# 15. ESLint

输入命令:npm install eslint -D进行本地安装,然后输入命令进行初始化eslint的配置:eslint --init然后根据自己的需要进行配置, 在这里插入图片描述 输入eslint init.js进行检测我们的文件的书写规范。 在这里插入图片描述 可以输入后面的报错信息到https://eslint.cn/docs/rules/这里进行查看。 我们可以查看前面生成的eslint的规则配置:可以看到我们的规则是继承了"extends": "eslint:recommended",也就是https://eslint.cn/docs/rules/这里面标绿色箭头的选项,我们可以在我们的rules里面进行配置我们自己的规则。

module.exports = {
    "env": {
        "browser": true,
        "commonjs": true,
        "es6": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 2015,
        "sourceType": "module"
    },
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "windows"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
};

禁用eslint,比如说我们写vue的时候,是引入了vue,这个时候eslint会提示,,没有定义vue,我们可以在代码顶部加一个:

/*global Vue*/   //把某一个变量当做全局对象使用

如果想要禁用一个规则,如下:

 /*eslint-disable no-new*/   //禁用 no-new 规则

我们也可以在某一行,重新打开我们禁用的规则:

 /*eslint-enable no-new*/   //打开 no-new 规则,关闭禁用no-new

还有检查html中的js书写,安装eslint-plugin-html插件

# 16. import export

默认导出:一个模块只能有一个默认导出;

 const apikey = 'abc123';

 export default apikey;

默认导出的引入方式:

import api from './config.js'
console.log(api)

命名导出:这里的apikey就是导出的名字,

export const apikey = 'abc123';

引入的时候,必须使用同样的名字,而且,需要使用{}来包裹起来:

import { apikey } from './config'

可以引入多个:

import { apikey,a } from './config'

可以用下面的方式导出多个:

export { apikey, age, greet }

也可以在导出的时候,进行重命名:然后引入的时候,必须使用as命名后的名字;

export { apikey as apk, age, greet }

当然,也可以在引入的时候重新命名:在模块使用的时候,就必须使用重命名的名字;

import { apikey as apk, a } from './config'

默认导出的,吗唉导入的时候,可以使用任意的命名,而命名导出,需要使用我们导出的名字,在导入的时候,进行导入。 一个第三方包:slug,过滤用户名。还有md5包。

引入命名导出以及默认导出的变量:User默认导出的内容。

import User, { apikey as apk, a } from './config'

# 17. 使用 SystemJS 进行打包

一个非常简单的打包工具,不用进行类似webpack繁琐的配置。我们可以使用jspm.io进行加载npm 或者github上面的包。在标签中引入systemJS,然后进行配置:

<script>
System.config({transpiler: 'babel'})

System.import('./main.js')
</script>

js中引入第三方模块的方式也是不一样的,如下:意思就是在npm里面查找我们引入的包,进行引入。

import { sum } from 'npm:lodash';

引入本地模块跟前面是一样的。

# 18. Class

ES5 中的组合式继承,是构造函数继承 + 原型链继承,构造函数继承是:

function Animal(name) {
  this.name = name
}

function Dog(name, color) {
  // 继承属性
  Animal.call(this, name)
  this.color = color
}

原型链继承:

function Animal(name) {
  this.name = name
}

function Dog(name, color) {
  this.color = color
}

// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

组合式继承:

function Animal(name) {
  this.name = name
}

function Dog(name, color) {
  // 继承属性
  Animal.call(this, name)
  this.color = color
}

// 继承方法
Dog.prototype = new Animal()
Dog.prototype.constructor = Dog

# 18.1 Class基本用法

是特殊的函数,定义方式:

//第一种
class User {

}
//第二种
const User = class {

}

typeof User打印出的结果是function,函数是有函数提升的,而类是没有的。在声明之前使用,会报错。

这里需要注意的是,类里面定义函数,之间是不需要使用逗号隔开的,加了逗号会报错;

class User {
    constructor (name, email) {
        this.name = name;
        this.email = email;
    }
    info () {
        console.log(`I'm ${this.name}`)
    }
}

const jiegiser = new User('jiegiser', 'jiegiser@163.com')

console.log(jiegiser)

静态方法的定义:静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法

class User {
    constructor (name, email) {
        this.name = name;
        this.email = email;
    }
    info () {
        console.log(`I'm ${this.name}`)
    }
    //静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
    static descript () {
        console.log(`hi jj`)
    }
}

const jiegiser = new User('jiegiser', 'jiegiser@163.com')

console.log(jiegiser)

定义get set方法:

class User {
    constructor (name, email) {
        this.name = name;
        this.email = email;
    }
    info () {
        console.log(`I'm ${this.name}`);
    }
    //静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
    static descript () {
        console.log(`hi jj`);
    }

    set github (value) {
        this.githubName = value;
    }
    get github () {
        return `http://github.com/${this.githubName}`;
    }
}

const jiegiser = new User('jiegiser', 'jiegiser@163.com')

console.log(jiegiser)

# 18.2 Class的扩展

在类的定义中,定义方法的时候,也可以使用计算属性的方式进行定义:

let methodName = 'info';
class User {
    constructor (name, email) {
        this.name = name;
        this.email = email;
    }
    [methodName] () {
        console.log(`I'm ${this.name}`);
    }
    //静态方法--不能实例化调用,只能在原型对象调用,一般将原型对象里面的方法定义为静态方法
    static descript () {
        console.log(`hi jj`);
    }

    set github (value) {
        this.githubName = value;
    }
    get github () {
        return `http://github.com/${this.githubName}`;
    }
}

类必须要使用new关键字进行调用。下面是类的继承:注意super(name);进行调用父类构造函数。

class Animal {
    constructor (name) {
        this.name  = name;
        this.belly = [];
    }
    eat (food) {
        this.belly.push(food)
    } 
}
class Dog extends Animal {
    constructor (name, age) {
        //在子类中调用父类构造函数
        super(name);
        this.name = name;
        this.age = age;
    }
    bark () {
        console.log(`Barl bark!`);
        
    }
}
const lucky = new Dog('lucky', 2);

# 18.3 Class 进行扩展内建对象

我们可以通过class扩展javascript中的内建对象,比如扩展Array对象:

class MyArray extends Array {
    constructor () {
        super();
    }
}

const colors = new MyArray();
colors[0] = 'red';
console.log(colors.length);
colors.length = 0;
console.log(colors[0])

在子类中其实可以通过this来访问父级Array的一些属性跟方法的。这里就是调用了Arraypush方法。

class movieCollaction {
    constructor (name, ...items) {
        super(...items)
        this.name = name;
    }
    add (item) {
        this.push(item)
    }
    toRated (limit = 10) {
        return this.sort((a, b) => (a.scores > b.scores) ? -1 : 1).slice(0, limit);
    }
}
const movies = new movieCollaction('favorite movies',
  { name: 'the croods', scored: 8.7},
  { name: 'the days', scored: 9.6},
  { name: 'the shawshank', scored: 9.4},
  { name: 'the summer', scored: 8.0},
);

# 19. Proxy

在 es5 中,我们是使用 Object.defineProperty():

let obj = {}
let newVal = 'jiegiser'
Object.defineProperty(obj, 'name', {
  get() {
    console.log('get')
    return newVal
  },
  set(val) {
    console.log('set')
    newVal = val
  }
})

console.log(obj.name) // 'get' 'jiegiser'

es6 中是 proxy;比如我们做一个对数组获取值进行代理,如果取得下标没有对应的下标就返回 error

# get 拦截操作

let arr = [7, 8, 9]
arr = new Proxy(arr, {
  get(target, prop) {
    console.log(target, prop) // [7, 8, 9], "1"
    return prop in target ? target[prop] : 'error'
  }
})

console.log(arr[1])

# set 拦截操作

帮助我们重写对象上的默认方法。比如下面的的方法:

const personn = { name: 'jiegiser', age: 200};
// 第一个参数为要代理的对象,第二个参数就是一个对象,包含重写的方法。也就类似vue计算属性
const personProxy = new Proxy(personn, {
  get(target, key) {
    return target[key].toUpperCase()
  },
  set (target, key, value) {
    if(typeof value === 'string'){
      target[key] = value.trim()
    }
  }
})
personProxy.name = 'jie'

我们用 proxy 去代理一个对象的时候,并不会破坏对象的原有属性;

let arr = []
arr = new Proxy(arr, {
  set(target, prop, val) {
    if (typeof val === 'number') {
      target[prop] = val
      return true
    } 
    return false
  }
})

arr.push(5)
arr.push(6)
console.log(arr[0], arr[1], arr.length) // 5, 6, 2

Proxy的一个例子:对电话号码进行格式化输出

const phonerHandle = {
  set(target, key, value) {
    target[key] = value.match(/[0-9]/g).join('');
  },
  get(target, key) {
    return target[key].replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
  }
}
const pgoneNumber = new Proxy({}, phonerHandle)

# has (拦截 in 操作符) 拦截操作

拦截 propKey in proxy 的操作,返回一个布尔值。

let range = {
  start: 1,
  end: 5
}

range = new Proxy(range, {
  has(target, prop) {
    return prop >= target.start && prop <= target.end
  }
})

console.log(2 in range)
console.log(9 in range)

# ownKeys 拦截操作

拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。

let obj = {
  name: 'imooc',
  [Symbol('es')]: 'es6'
}
console.log(Object.getOwnPropertyNames(obj)) // ["name"]
console.log(Object.getOwnPropertySymbols(obj)) // [Symbol(es)]
console.log(Object.keys(obj)) // ["name"]
for (let key in obj) {
  console.log(key)
}
// ["name"]

// 使用 Reflect.ownKeys 遍历所有属性
for (let key of Reflect.ownKeys(obj)) {
  console.log(key)
}

使用代理:比如带下划线的属性为私有属性,下面代码是当遍历对象属性的时候,不对私有属性进行遍历;

let userinfo = {
  username: 'xiecheng',
  age: 34,
  _password: '***'
}
userinfo = new Proxy(userinfo, {
  ownKeys(target) {
    return Object.keys(target).filter(key => !key.startsWith('_'))
  }
})

for (let key in userinfo) {
  console.log(key)
}
// username
// age
console.log(Object.keys(userinfo))

# deleteProperty 拦截操作

拦截 delete proxy[propKey] 的操作,返回一个布尔值。

比如有下面一个场景,我们对一个对象的带有下划线属性进行代理,设置他不能获取、设置、删除、不能循环遍历,如下代码:

let user = {
  name: 'jiegiser',
  age: 18,
  _password: '******'
}

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      return target[prop]
    }
  },

  set(target, prop, val) {
    if (prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      target[prop] = val
      return true
    }
  },


  // 删除拦截
  deleteProperty(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('不可删除')
    } else {
      delete target[prop]
      return true
    }
  },

  ownKeys(target) {
    return Object.keys(target).filter(key => !key.startsWith('_'))
  }
})


console.log(user.age)
console.log(user._password)
user.age = 18
console.log(user.age)
try {
  user._password = 'xxx'
} catch (e) {
  console.log(e.message)
}

try {
  // delete user.age
  delete user._password
} catch (e) {
  console.log(e.message)
}
console.log(user.age)

for (let key in user) {
  console.log(key)
}

# apply 拦截操作

拦截 Proxy 实例作为函数调用的操作,比如 proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

let sum = (...args) => {
  let num = 0
  args.forEach(item => {
    num += item
  })
  return num
}

sum = new Proxy(sum, {
  apply(target, ctx, args) {
    return target(...args) * 2
  }
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2, 3)) // 12
console.log(sum.apply(null, [1, 2, 3])) // 12

# construct 拦截操作

拦截 Proxy 实例作为构造函数调用的操作,比如 new proxy(...args)。

let User = class {
  constructor(name) {
    this.name = name
  }
}

User = new Proxy(User, {
  constructor(target, args, newTarget) {
    console.log('constructor', target, args, newTarget)
    return new target(...args)
  }
})

console.log(new User('jiegiser'))

# 20. Set

# 基本用法

一种唯一的数组,也就是数组中的数据是唯一的,不会重复,不能通过索引进行获取。使用add添加值,delete删除某个值。检验某个值是否存在has,移除所有的元素.clear().values是一个set的一个遍历器。可以使用for of进行循环遍历,.size 属性是获取 set 元素的个数;

const colors = new Set();
colors.add('red')
colors.add('green')
colors.add('blue')

可以使用下面的的方法,进行遍历:

const colors = new Set();
colors.add('red')
colors.add('green')
colors.add('blue')
const iterator = colors.values();
iterator.next();

可以链式调用:

const colors = new Set();
colors.add('red').add('green')

对于 set 元素的遍历,可以使用数组的遍历方法,进行遍历元素:

使用for of

for (let color of colors) {
    console.log(color)
}

使用forEach进行遍历;

colors.forEach((item, key, ownSet) => {
    console.log(item,key,ownSet)
})

set接收的是一个可遍历对象,我们也可以传入一个数组,进行初始化:

const fruits = new Set(['apple', 'banana', 'mongo'])

# 应用

求交集

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]

let s1 = new Set(arr1)
let s2 = new Set(arr2)
let result = new Set(arr1.filter(item => s2.has(item)))
console.log([...result])

求差集

let arr1 = [1, 2, 3, 4]
let arr2 = [2, 3, 4, 5, 6]

let s1 = new Set(arr1)
let s2 = new Set(arr2)

let arr3 = new Set(arr1.filter(item => !s2.has(item)))
let arr4 = new Set(arr2.filter(item => !s1.has(item)))
console.log([...arr3, ...arr4])

weakSetset非常相似,只不过,他存储的项只能是对象,不能存储字符串,而且不能通过for of进行循环,他没有迭代器,也不能使用forEach进行循环。他没有clear()清除全部,他可以自己进行检测进行注销对应的数据。

weakSet 是弱引用;不会进入垃圾回收机制进行计数;

# 21. Map

mapset非常相似,不同的是,map通过.set('key', value)这种方法进行添加元素,他也相当于一个数组,数组中是一个个对象。对象的键可以是任意数据类型;

const people = new Map();
people.set('jelly', 23);
people.set('{}',3)

在这里插入图片描述 获取对应的值:

people.get('jellu')//传入键

获取里面j键值对的数量:

people.size()

删除某个项:

people.delete('jelly')

删除所有元素:

people.clear()

循环遍历元素:

forEach进行循环;

people.forEach((value, key, map) => {
  console.log(value, key, map)
})

使用for of进行循环:

for (let [key,value] of people) {
  console.log(key, value)
}

使用 .entries() 方法:

for (let [key, value] of people.entries()) {
  console.log(key, value)
}

初始化参数:

const fruits= new Map([
  ['apple', 6],
  ['banans', 5]
])

wekMap没有size属性,他的 key 只能是引用类型的数据;不能循环遍历;没有 clear、size 等属性以及方法;他也是弱引用,垃圾回收机制,会自动回收。

# 22. 函数的参数

  • 参数可以设置默认值
function foo(x, y = 'foo') {
  // 不能再次声明
  // let x = 1
  console.log(x, y)
}
  • 函数的 length 是返回没有指定默认值的参数的个数;

  • 函数参数会形成作用域

let x = 1
function foo(x, y = x) {
  console.log(y) // 2
}

foo(2)
  • 函数 name 属性
console.log(new Function().name) // anonymous


// bind 改变
function foo(x, y) {
  console.log(this, x, y)
}

console.log(foo.bind({}).name) // bound foo


// 匿名函数使用 bind
console.log((function() {}).bind({}).name) // bound

# 23. 字符串的扩展

# Unicode 表示法

ES6 加强了对 Unicode 的支持,允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。

"\u0061"
// "a"

但是,这种表示法只限于码点在 \u0000~\uFFFF 之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。

"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// " 7"

上面代码表示,如果直接在 \u 后面跟上超过 0xFFFF 的数值(比如\u20BB7),JavaScript 会理解成 \u20BB+7。由于 \u20BB 是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

"\u{20BB7}"
// "𠮷"

有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。

'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

# 字符串的遍历器接口

可以通过 for of 去遍历字符串:

for(let item of 'jiegiser') {
  console.log(item)
}

# 带标签的模板字符串

const foo = (a, b, c, d) => {
  console.log(a)
  console.log(b)
  console.log(c)
  console.log(d)
}

const name = 'jiegiser'
const age = 18
foo`这是${name},他的年龄是${age}岁, ${22}`
// ["这是", ",他的年龄是", "岁, ", ""]
// jiegiser
// 18
// 22

# 扩展方法

  • String.prototype.fromCodePoint()

用于从 Unicode 码点返回对应字符,并且可以识别大于0xFFFF的字符。

// ES5
console.log(String.fromCharCode(0x20BB7))

// ES6
console.log(String.fromCodePoint(0x20BB7))
  • String.prototype.includes()

ES5中可以使用indexOf方法来判断一个字符串是否包含在另一个字符串中,indexOf返回出现的下标位置,如果不存在则返回-1。

const str = 'imooc'

console.log(str.indexOf('mo'))

ES6提供了includes方法来判断一个字符串是否包含在另一个字符串中,返回boolean类型的值。

const str = 'imooc'

console.log(str.includes('mo'))
  • String.prototype.startsWith() 判断参数字符串是否在原字符串的头部, 返回boolean类型的值。
const str = 'imooc'

console.log(str.startsWith('im'))
  • String.prototype.endsWith()

判断参数字符串是否在原字符串的尾部, 返回boolean类型的值。

const str = 'imooc'

console.log(str.endsWith('mooc'))
  • String.prototype.repeat()

repeat 方法返回一个新字符串,表示将原字符串重复n次。

const str = 'imooc'

const newStr = str.repeat(10)

console.log(newStr)

# 24. 正则表达式的扩展

在 es5 时,有三个修饰符:i(忽略大小写)、m(多行匹配)、g(全局匹配),es6 新增了两个:y、u 修饰符;

# y 修饰符(粘连修饰符)

y 修饰符的作用与 g 修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g 修饰符只要剩余位置中存在匹配就可,而 y 修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

const s = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

r1.exec(s) // ["a"]
r2.exec(s) // ["aaa"] // 重新开始匹配

上面代码有两个正则表达式,一个使用 g 修饰符,另一个使用 y 修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是 _aa_a。由于 g 修饰没有位置要求,所以第二次执行会返回结果,而 y 修饰符要求匹配必须从头部开始,所以返回 null。

如果改一下正则表达式,保证每次都能头部匹配,y 修饰符就会返回结果了。

const s = 'aaa_aa_a'
const r = /a+_/y

r.exec(s) // ["aaa_"]
r.exec(s) // ["aa_"]

上面代码每次匹配,都是从剩余字符串的头部开始。使用 lastIndex 属性,可以更好地说明 y 修饰符。

const regexp = /a/g

// 指定从2号位置(y)开始匹配
regexp.lastIndex = 2

// 匹配成功
const match = regexp.exec('xaya')

// 在 3 号位置匹配成功
console.log(match.index) // 3

// 下一次匹配从 4 号位开始
console.log(regexp.lastIndex) // 4

// 4 号位开始匹配失败
regexp.exec('xaxa') // null

上面代码中,lastIndex 属性指定每次搜索的开始位置,g 修饰符从这个位置开始向后搜索,直到发现匹配为止。

y 修饰符同样遵守 lastIndex 属性,但是要求必须在 lastIndex 指定的位置发现匹配。

const regexp = /a/y

// 指定从 2 号位置开始匹配
regexp.lastIndex = 2

// 不是粘连,匹配失败
regexp.exec('xaya') // null

// 指定从 3 号位置开始匹配
regexp.lastIndex = 3

// 3 号位置是粘连,匹配成功
const match = regexp.exec('xaxa')
console.log(match.index) // 3
console.log(regexp.lastIndex) // 4

进一步说,y 修饰符号隐含了头部匹配的标志 ^。

const reg = /b/y
reg.exec('aba')
// null
console.log(reg.lastIndex)

sticky 模式在正则匹配过程中只会影响两件事:

  1. 匹配必须从 re.lastIndex 开始(相当于正则表达中的 ^)
  2. 如果匹配到会修改 re.lastIndex(相当于 g 模式)

# u 修饰符

ES6 为正则表达式添加了 u 修饰符,含义为 “Unicode模式”,用来正确处理大于 \uFFFF 的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。

// 表示一个字符
const str = '\uD83D\uDC2A'
/^\uD83D/u.test(str) // es6 false

/^\uD83D/.test(str) // es5 true

上面代码中, \uD83D\uDC2A 是一个四个字节的UTF-16编码,代表一个字符 "🐪"。但是,ES5 不支持四个字节的 UTF-16 编码,会将其识别为两个字符,导致第二行代码结果为 true。加了 u 修饰符以后,ES6 就会识别其为一个字符,所以第一行代码结果为 false。

一旦加上u修饰符号,就会修改下面这些正则表达式的行为。

  1. 点字符

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

let s = '𠮷'

/^.$/.test(s) // false

/^.$/u.test(s) // true

上面代码表示,如果不添加u修饰符,正则表达式就会认为字符串为两个字符,从而匹配失败。

'𠮷'这个字读 jí,是'吉'字的异形体,Unicode 码点 U+20BB7

  1. Unicode 字符表示法

ES6 新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。

/\u{61}/.test('a') // false

/\u{61}/u.test('a') // true

/\u{20BB7}/u.test('𠮷') // true

上面代码表示,如果不加 u 修饰符,正则表达式无法识别 \u{61} 这种表示法,只会认为这匹配 61 个连续的 u。

  1. 量词 使用 u 修饰符后,所有量词都会正确识别码点大于 0xFFFF 的 Unicode 字符。
/a{2}/.test('aa') // true

/a{2}/u.test('aa') // true

/𠮷{2}/.test('𠮷𠮷') // false

/𠮷{2}/u.test('𠮷𠮷') // true

另外,只有在使用 u 修饰符的情况下,Unicode表达式当中的大括号才会被正确解读,否则会被解读为量词。

/^\u{3}$/.test('uuu') // true

上面代码中,由于正则表达式没有 u 修饰符,所以大括号被解读为量词。加上 u 修饰符,就会被解读为 Unicode 表达式。

/\u{20BB7}{2}/u.test('𠮷𠮷') // true

使用 u 修饰符之后 Unicode 表达式+量词也是可以的。

  1. 预定义模式 u 修饰符也影响到预定义模式,能否正确识别码点大于 0xFFFF 的 Unicode 字符。
/^\S$/.test('𠮷') // false

/^\S$/u.test('𠮷') // true

上面代码的 \S 是预定义模式,匹配所有不是空格的字符。只有加了 u 修饰符,它才能正确匹配码点大于 0xFFFF 的 Unicode 字符。

利用这一点,可以写出一个正确返回字符串长度的函数

function codePointLength(text) {
  const result = text.match(/[\s\S]/gu)
  return result ? result.length : 0
}

const s = '𠮷𠮷'

s.length // 4
codePointLength(s) // 2
  1. i 修饰符 有些 Unicode 字符的编码不同,但是字型很相近,比如,\u004B 与 \u212A 都是大写的 K。
/[a-z]/i.test('\u212A') // false

/[a-z]/iu.test('\u212A') // true

上面代码中,不加 u 修饰符,就无法识别非规范的 K 字符。

# 数值的扩展

# 进制转换

// es5
// 十进制 -> 二进制
const a = 5
console.log(a.toString(2)) // 101

// 二进制 -> 十进制
const b = 101
// console.log(parseInt(5.5))
console.log(parseInt(b, 2))

ES6 提供了二进制和八进制数值的新的写法,分别用前缀 0b(或0B)和 0o(或0O)表示。

const a = 0B0101
console.log(a) // 5
const b = 0o777
console.log(b) // 511

# 新增方法

  • Number.isFinite() 用来检查一个数值是否为有限的(finite),即不是 Infinity。 如果不是数值,都是 false;
Number.isFinite(15) // true
Number.isFinite(0.8) // true
Number.isFinite(NaN) // false
Number.isFinite(Infinity) // false
Number.isFinite(-Infinity) // false
Number.isFinite('foo') // false
Number.isFinite('15') // false
Number.isFinite(true) // false
  • Number.isNaN()

用来检查一个值是否为NaN。

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9 / NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
  • Number.parseInt()

ES6 将全局方法 parseInt() 移植到 Number 对象上面,行为完全保持不变。 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。

// ES5的写法
parseInt('12.34') // 12

// ES6的写法
Number.parseInt('12.34') // 12
  • Number.parseFloat() ES6 将全局方法 parseFloat() 移植到 Number 对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化
// ES5的写法
parseFloat('123.45#') // 123.45

// ES6的写法
Number.parseFloat('123.45#') // 123.45
  • Number.isInteger()

用来判断一个数值是否为整数。

Number.isInteger(25) // true
Number.isInteger(25.1) // false

Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false
  • js 数值精度

在 js 中,我们存储数字精度的标准是 IEEE 754,在存储 0.1 的时候(因为计算机存储数值为二进制)会存在精度缺失,该标准会因为存储的数值有一定空间,会舍弃一部分;所以在 js 中会存在 0.1 + 0.2 不等于 0.3 这样的结果:

35 -> 00100011
0.35 -> 0.011
0.1 -> 0.0000440044.....

// 超出 14 位会舍弃
console.log(0.1000000000000001) // 0.1000000000000001
console.log(0.10000000000000001) // 0.1

console.log(0.10000000000000001 === 0.1) // true
  • Number.MAX_SAFE_INTEGER、Number.MIN_SAFE_INTEGER

最大的安全整数;

Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true

Number.MAX_SAFE_INTEGER === 9007199254740991 // true

Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true

Number.MIN_SAFE_INTEGER === -9007199254740991 // true
  • Number.isSafeInteger()

JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

Math.pow(2, 53) // 9007199254740992

Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
console.log(Number.MAX_SAFE_INTEGER) // true
console.log(Number.MAX_SAFE_INTEGER + 1) // false

# Math 扩展

  • Math.trunc() 方法用于去除一个数的小数部分,返回整数部分。
console.log(Math.trunc(5.5))
console.log(Math.trunc(-5.5))
console.log(Math.trunc(true)) // 1
console.log(Number.parseInt(true)) // NaN

console.log(Math.trunc(false)) // 0
console.log(Math.trunc(NaN)) // NaN
console.log(Math.trunc(undefined)) // NaN
console.log(Math.trunc()) // NaN
  • Math.sign() 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。

它会返回五种值。

  1. 参数为正数,返回+1
  2. 参数为负数,返回-1
  3. 参数为 0,返回0
  4. 参数为-0,返回-0
  5. 其他值,返回NaN
console.log(Math.sign(5)) // 1
console.log(Math.sign(-5)) // -1
console.log(Math.sign(0)) // 0
console.log(Math.sign(NaN)) // NaN
console.log(Math.sign(true)) // 1
console.log(Math.sign(false)) // 0
  • Math.cbrt()

方法用于计算一个数的立方根。

console.log(Math.cbrt(8)) // 2

console.log(Math.cbrt('imooc')) // NaN

# Reflect

Reflect 对象与 Proxy 对象一样,也是 ES6 为了操作对象而提供的新 API。

# 设计目的

  • 将 Object 属于语言内部的方法放到 Reflect 上
let obj = {}
let newVal = ''
Reflect.defineProperty(obj, 'name', {
  get() {
    return newVal
  },
  set(val) {
    console.log('set')
    // this.name = val
    newVal = val
  }
})
obj.name = 'es'
console.log(obj.name)
  • 修改某些 Object 方法的返回结果,让其变得更合理

es5 我们通过 Object.defineProperty 定义对象属性时,有的属性是不能被定义的,如果定义操作会抛出异常,我们通过 try catch 来捕获;es6 可以通过 Reflect.defineProperty,他会返回 boolean 类型的值,表示是否可以定义;

// 老写法
try {
  Object.defineProperty(target, property, attributes)
  // success
} catch (e) {
  // failure
}

// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
  // success
} else {
  // failure
}
  • 让 Object 操作变成函数行为

比如下面代码,判断当前对象是否具有某个方法;

// 老写法
'assign' in Object // true

// 新写法
Reflect.has(Object, 'assign') // true
  • Reflect 对象的方法与 Proxy 对象的方法一一对应

只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。

let user = {
  name: 'jiegiser',
  age: 18,
  _password: '******'
}

user = new Proxy(user, {
  get(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      // return target[prop]
      return Reflect.get(target, prop)
    }
  },

  set(target, prop, val) {
    if (prop.startsWith('_')) {
      throw new Error('不可访问')
    } else {
      // target[prop] = val
      Reflect.set(target, prop, val)
      return true
    }
  },


  // 删除拦截
  deleteProperty(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('不可删除')
    } else {
      // delete target[prop]
      Reflect.deleteProperty(target, prop)
      return true
    }
  },

  ownKeys(target) {
    // return Object.keys(target).filter(key => !key.startsWith('_'))
    return Reflect.ownKeys(target).filter(key => !key.startsWith('_'))
  }
})

console.log(user.age)
try {
  console.log(user._password)
} catch(e) {
  console.log(e.message)
}

user.age = 19
console.log(user.age)

delete user.age
console.log(user.age)

for (let key in user) {
  console.log(key)
}

# 常用方法

  • Reflect.apply()

Reflect.apply(target, thisArgument, argumentsList) target: 目标函数;thisArgument:target 函数调用时绑定的 this 对象;argumentsList:target 函数调用时传入的实参列表,该参数应该是一个类数组的对象

Reflect.apply(Math.floor, undefined, [1.75])
// 1

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4

Reflect.apply(''.charAt, 'ponies', [3])
// "i"

该方法与 ES5 中 Function.prototype.apply() 方法类似:调用一个方法并且显式地指定 this 变量和参数列表 (arguments) ,参数列表可以是数组,或类似数组的对象。

Function.prototype.apply.call(Math.floor, undefined, [1.75])
  • Reflect.construct()

Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...args).

Reflect.construct(target, argumentsList[, newTarget]) target:被运行的目标函数;argumentsList:调用构造函数的数组或者伪数组;newTarget:该参数为构造函数, 参考 new.target 操作符,如果没有 newTarget 参数, 默认和 target 一样

var obj = new Foo(...args)
var obj = Reflect.construct(Foo, args)

var d = Reflect.construct(Date, [1776, 6, 4])
d instanceof Date // true
d.getFullYear() // 1776

// 如果使用 newTarget 参数,则表示继承了 newTarget 这个超类:
function someConstructor() {}
var result = Reflect.construct(Array, [], someConstructor)

Reflect.getPrototypeOf(result) // 输出:someConstructor.prototype
Array.isArray(result) // true
  • Reflect.delete​Property()

Reflect.deleteProperty 允许你删除一个对象上的属性。返回一个 Boolean 值表示该属性是否被成功删除。它几乎与非严格的 delete operator 相同

var obj = {
    x: 1,
    y: 2
}
Reflect.deleteProperty(obj, "x") // true
obj // { y: 2 }

var arr = [1, 2, 3, 4, 5]
Reflect.deleteProperty(arr, "3") // true
arr // [1, 2, 3, , 5]

// 如果属性不存在,返回 true
Reflect.deleteProperty({}, "foo") // true

// 如果属性不可配置,返回 false
Reflect.deleteProperty(Object.freeze({
    foo: 1
}), "foo") // false
  • Reflect.get​OwnProperty​Descriptor()

静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。

Reflect.getOwnPropertyDescriptor({
    x: 'hello'
}, 'x')
// {value: "hello", writable: true, enumerable: true, configurable: true}

Reflect.getOwnPropertyDescriptor({
    x: 'hello'
}, 'y')
// undefined

Reflect.getOwnPropertyDescriptor([], 'length')
// {value: 0, writable: true, enumerable: false, configurable: false}

如果该方法的第一个参数不是一个对象(一个原始值),那么将造成 TypeError 错误。而对于 Object.getOwnPropertyDescriptor,非对象的第一个参数将被强制转换为一个对象处理。

Reflect.getOwnPropertyDescriptor("foo", 0)
// TypeError: "foo" is not non-null object

Object.getOwnPropertyDescriptor("foo", 0)
// { value: "f", writable: false, enumerable: true, configurable: false }
  • Reflect.get​PrototypeOf()

静态方法 Reflect.getPrototypeOf() 与 Object.getPrototypeOf() 方法是一样的。都是返回指定对象的原型(即,内部的 [[Prototype]] 属性的值)。

Reflect.getPrototypeOf({a: 'a'})
// constructor: ƒ Object()
// hasOwnProperty: ƒ hasOwnProperty()
// isPrototypeOf: ƒ isPrototypeOf()
// propertyIsEnumerable: ƒ propertyIsEnumerable()
// toLocaleString: ƒ toLocaleString()
// toString: ƒ toString()
// valueOf: ƒ valueOf()
// __defineGetter__: ƒ __defineGetter__()
// __defineSetter__: ƒ __defineSetter__()
// __lookupGetter__: ƒ __lookupGetter__()
// __lookupSetter__: ƒ __lookupSetter__()
// get __proto__: ƒ __proto__()
// set __proto__: ƒ __proto__()
  • Reflect.isExtensible()

Reflect.isExtensible 判断一个对象是否可扩展 (即是否能够添加新的属性),它与 Object.isExtensible() 方法一样。

Reflect.isExtensible(target) target: 获取原型的目标对象

Reflect.isExtensible({})
// true
Reflect.isExtensible(Object.create(null))
// true
  • Reflect.prevent​Extensions()

Reflect.preventExtensions 方法阻止新属性添加到对象 例如:防止将来对对象的扩展被添加到对象中)。该方法与 Object.preventExtensions() 方法一致;

// Objects are extensible by default.
var empty = {}
Reflect.isExtensible(empty) // === true

// ...but that can be changed.
Reflect.preventExtensions(empty)
Reflect.isExtensible(empty) // === false


Reflect.preventExtensions(1)
// TypeError: 1 is not an object

Object.preventExtensions(1)
// 1
  • Reflect.set​PrototypeOf()

Reflect.setPrototypeOf 方法改变指定对象的原型 (即,内部的 [[Prototype]] 属性值

Reflect.setPrototypeOf({}, Object.prototype) // true

// It can change an object's [[Prototype]] to null.
Reflect.setPrototypeOf({}, null) // true

// Returns false if target is not extensible.
Reflect.setPrototypeOf(Object.freeze({}), null) // false

// Returns false if it cause a prototype chain cycle.
var target = {}
var proto = Object.create(target)
Reflect.setPrototypeOf(target, proto) // false

# Generator

  • 比普通函数多一个 *
  • 函数内部用 yield 来控制程序的执行的“暂停”
  • 函数的返回值通过调用 next 来“恢复”程序执行
function* foo() {
  for (let i = 0; i < 3; i ++) {
    console.log(i)
    yield i
  }
}

let f = foo()
console.log(f.next()) // {value: 0, done: false}

Generator 函数的定义不能使用箭头函数,否则会触发 SyntaxError 错误:

let generator = * () => {} // SyntaxError
let generator = () * => {} // SyntaxError
let generator = ( * ) => {} // SyntaxError

# yield 表达式

yield 关键字用来暂停和恢复一个生成器函数,yield 只能出现在 generator 函数内部,否则会报错:

// 注意在写循环的时候,类似 forEach 这种需要回调函数,往往会忽略
function* gen(args) {
  args.forEach(item => {
    yield item + 1
  })
}

yield 表达式的返回值是 undefined,但是遍历器对象的 next 方法可以修改这个默认值。

理解 yield 的含义:首先第一个输出 6,因为走到第一个 yield,x + 1 就是 6;所以,g.next() 返回是 yield 的结果;然后走到第二个 yield,因为第二个打印的 g.next() 并没有传递参数,导致第一个 yield 为 undefined;所以 2 * undefined 为 NaN,所以 y 的结果就是 NaN;得到 z 的结果也是 NaN;所以打印结果 NaN;最后 return 也是 NaN,所以最后打印也是 NaN;

这里要说一下,g.next 是可以传参的,表示上一次 yield 的执行结果;

function* gen(x) {
  let y = 2 * (yield (x + 1))
  let z = yield(y / 3)
  return x + y + z
}

let g = gen(5)
console.log(g.next()) // 6
console.log(g.next()) // NaN
console.log(g.next()) // NaN

如果我们传参,如下代码:

function* gen(x) {
  let y = 2 * (yield (x + 1))
  let z = yield(y / 3)
  return x + y + z
}

let g = gen(5)
console.log(g.next()) // 6
console.log(g.next(12)) // y = 24 8
console.log(g.next(13)) // z = 13 x = 5 42

# 应用场景

  • 按顺序读取 a.json、b.json、c.json,如果使用 Generator 该如何实现呢?
function request(url) {
  ajax(url, res => {
    getData.next(res)
  })
}

function* gen() {
  let res1 = yield request('static/a.json')
  console.log(res1)
  let res2 = yield request('static/b.json')
  console.log(res2)
  let res3 = yield request('static/c.json')
  console.log(res3)
}
let getData = gen()
getData.next()
  • 我们经常玩一些小游戏,比如数数字,敲7,到7和7的倍数,无限循环转圈去数数
function* count(x = 1) {
  while (true) {
    if (x % 7 === 0) {
      yield x
    }
    x++
  }
}
// es5 中就是个死循环 因为 es5 的循环需要有个终止值,但我们这个需求没有终止,一直在数数
let n = count()
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)

通过 Generator 我们就能轻松实现,只要调用 n.next 我们就知道下一个数是什么了,而使用普通函数却没法做到。

# Iterator

Iterator 就是 ES6 中用来实现自定义遍历的接口,按照上述的示例,我们来实现下这个接口:

let authors = {
  allAuthors: {
    fiction: [
      'Agatha Christie',
      'J. K. Rowling',
      'Dr. Seuss'
    ],
    scienceFiction: [
      'Neal Stephenson',
      'Arthur Clarke',
      'Isaac Asimov',
      'Robert Heinlein'
    ],
    fantasy: [
      'J. R. R. Tolkien',
      'J. K. Rowling',
      'Terry Pratchett'
    ]
  }
}

// 这个代码在数据结构上部署了 Iterator 接口,我们就可以用 for...of 来遍历代码了
authors[Symbol.iterator] = function() {
  let allAuthors = this.allAuthors
  let keys = Reflect.ownKeys(allAuthors)
  let values = []
  return {
    next() {
      if (!values.length) {
        if (keys.length) {
          values = allAuthors[keys[0]]
          keys.shift()
        }
      }
      return {
        done: !values.length,
        value: values.shift()
      }
    }
  }
}


for (let value of authors) {
  console.log( `${value}` )
}

评 论:

更新: 6/6/2021, 5:13:13 PM