ES6

对象

defineProperty

ES5提供,这里为了跟 Proxy 形成对比。

Object.defineProperty 方法会直接在一个对象定义一个新属性,或者修改一个对象的现有属性,并返回这个对象,先来看一下它的语法:

Object.defineProperty(obj, prop, descriptor);

obj 是要在其上定义的对象;prop 是要定义或修改的属性的名称;descriptor 是将被定义或修改的属性描述符。

举个例子:

const obj = {};
// 定义属性,采用数据描述符
Object.defineProperty(obj, "name", {
  value: "Jecyu",
  writable: true,
  enumerable: true,
  configurable: true
})
// 对象 obj 具有属性 value,值为 Jecyu

虽然我们可以直接添加属性和值,但是使用这种方式,我们能进行更多的配置。

函数的第三个参数descriptor 所表示的属性描述符有两种形式:数据描述符和存取描述符

描述符可同时具有的键值

configurable enumerable value writable get set
数据描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes

值得注意的是:

属性描述符必须是数据描述符活着存取描述符两种形式之一,不能同时是两者。这意味着:

// 定义属性,采用数据描述符
  Object.defineProperty(obj, "name", {
    value: "Jecyu",
    writable: true,
    enumerable: true,
    configurable: true
  })

// 定义属性,采用存取描述符
const value = "man";
Object.defineProperty(obj, "sex", {
  get: function() {
    return value;
  },
  set: function(newValue) {
    value = newValue
  },
  enumerable: true,
  configurable: true
})

此外,所有的属性描述符都是非必须的,但是 descriptor 这个字段是必须的,如果不进行任何配置,你可以这样:

Object.defineProperty(obj, "age", {})
console.log(obj); // { age: undefined }

具体配置值:见文档

Setters 和 Getters

defineProperty 的描述符中的 getset,这两个方法又被称为 getter 和 setter。由 getter 和 setter 定义的属性称做“存取器属性”。

当程序查询存取器属性的值时,JavaScript 调用 getter 方法。这个方法的返回值就是属性存取表达式的值。当程序设置一个存取器属性的值时,JavaScript 调用 setter 方法,将赋值表达式右侧的值当做参数传入 setter。从某种意义上讲,这个方法负责“设置”属性值。可以忽略 setter 方法的返回值。

举个例子:

 const obj = {};
  let value = null;
  Object.defineProperty(obj, "num", {
    get: function() {
      console.log("执行了 get 操作");
      return value;
    },
    set: function(newValue) {
      console.log("执行了 set 操作", "oldVal: " + value, "newValue: " + newValue);
      value = newValue;
    }
  })
  obj.num = 1; // 执行了 set 操作
  console.log(obj.num); // 执行了 get 操作

watch API

一旦对象拥有了 getter 和 setter,我们可以简单地把这个对象称为响应式对象。我们就可以监控数据的改变,然后自动进行渲染工作。 举个例子:

HTML 中有个 span 标签和 button 标签。

<span id="container">1</span>
<button id="button">点击加1</button>

传统的做法

document.getElementById('button').addEventListener('click', function() {
  const container = document.getElementById('container');
  container.innerHTML = Number(container.innerHTML) + 1;
})

使用 defineProperty 实现改变数据,驱动更新 DOM

const obj = { value: 1 }
let value = 1; // 储存 obj.value 的值
Object.defineProperty(obj, "value", {
  get: function() {
    // return obj.value;
    return value;
  },
  set: function(newValue) {
    // obj.value = newValue; // 直接使用 obj.value = newValue 会导致栈溢出,不断调用 set 函数,因此需要一个中间变量
    value = newValue;
    document.getElementById('container').innerHTML = newValue;
  }
})
document.getElementById('button').addEventListener('click', function() {
  obj.value +=1;
})

上面的写法,我们还需要单独声明一个变量存储 obj.value 的值,因为如果你在 set 中直接 obj.value = newValue 就会陷入无限的循环中。此外,我们可能需要监控很多属性值的改变,要是一个一个写,也很累呐,所以我们简单写个 watch 函数。

(function() {
  const root = this;
  function watch(obj, name, callback) {
    let value = obj[name]; // 缓存值
    Object.defineProperty(obj, name, {
      get: function() {
        return value;
      },
      set: function(newValue) {
        value = newValue;
        callback(value);
      }
    })
  }
  this.watch = watch;
})()

const obj = { value: 1, age: 0 }

watch(obj, "value", function(newValue) {
  document.getElementById('container').innerHTML = newValue; 
})
// watch(obj, "age", function(newValue) {
//   document.getElementById('container').innerHTML = newValue; 
// })

document.getElementById('button').addEventListener('click', function() {
  obj.value +=1;
  // obj.age += 1; // defineProperty 需要重新定义属性的 get 和 set 才能监听到数据的变化
})

proxy

使用 defineProperty 只能重定义属性的读取(get)和 设置(set)行为,到了 ES6,提供了 Proxy,可以重定义更多的行为,比如 in、delete、函数调用等更多行为。

函数

rest 参数

ES6引入 rest 参数(形式为 ...变量名),用于获取函数的多余参数,这样就不需要使用arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

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

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

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用。

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数)

Promise 基础

Promise: 三个状态、两个过程、一个方式

  • 三个状态:pendingfulfilledrejected
  • 两个过程(单向不可逆)
    • pending -> fulfilled
    • pending -> rejected
  • 一个方法then:Promise 本质上只有一个方法,catchall方法都是基于 then 方法实现的。
// 构造 Promise 时候,内部函数立即执行
new Promise((resolve, reject) => {
  console.log('new Promise);
  resolve('success);
})
console.log('finish);

// then 中用到了 return,那么 return 的值会被 Promise.resolve() 包装
Promise.resolve(1) 
  .then(res => {
    console.log(res); // => 1
    return 2; // 包装成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res); // => 2
  })

Class 的继承

super 关键字

super既可以当作函数使用,也可以当作对象使用。

第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次 super 函数

class A {}
class B extends A {
  constructor() {
    super()
  }
}

注意,super 虽然代表了父类 A 的构造韩素好,但是返回的是子类 B 的实例,即super内部的 this 指的是 B 的实例,因此 super() 在这里相当于 A.prototype.connstructor.call(this)

模块

命名导出(Named exports)

就是每一个需要导出的数据类型都要有一个name统一引入一定要带有{},即便只有一个需要导出的数据类型。

export 直接放到声明前面就可以省略 {}

导出模块

// lib.js
export const sqrt = Math.sqrt;
export function square(x) {
  return x * x;
}
export function diag(x) {
  return sqrt(square(x) + square(y))
}

导入模块

// main.js
import { square, diag } from 'lib';				
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

export 放到最后

导出模块

//------ lib.js ------
const sqrt = Math.sqrt;
function square(x) {
    return x * x;
}
function diag(x, y) {
    return sqrt(square(x) + square(y));
}

export {sqrt, square, diag}

导入模块

// main.js
import { square, diag } from 'lib';				
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

无论怎样导出,引入的时候都需要{}

导入

常规导入

import { square, diag } from 'a.js';	
import diag from 'b.js';	

默认导出(Default exports)

// export.js
export default function foo() {}

注意:这里引入不用加花括号,因为只导出一个默认函数 foo

// import.js
import foo from './export.js'

除了上面的引入方式外,导入模块的方式还有别名引入和命名空间引入。

别名导入

  1. 当从不同模块引入的变量名相同的时候

Before:

import { speak } from './cow.js'
import { speak } from './goat.js'

After:

import { speak as cowSpeak } from '.cow.js'
import { speak as goatSpeak } from 'goat.js'

或者是想从其他功能文件夹,引入到当前功能的文件下,改为更加语义的名称。

命名空间导入

注意解决上面,当从每个模块需要引入的方法很多的时候,避免冗长、繁琐。

import * as cow from "./cow.js"
import * as goat from "./goat.js"

变量的解构赋值

交换变量的值

从函数返回多个值

Last Updated: 11/25/2019, 5:55:58 PM