ECMAScript2015,算是新时代标准的代表版本
1.相比于上一个版本ES5.1变化比较大
2.从这个版本开始,标准命名规则就发生了变化

所以很多开发者喜欢用ES6这个名称泛指之后的所有的新版本
(比如async await是ES2017的标准)

ECMA 2015

可以看看这个链接里的内容简单对ES6的变更有一个认知

我们要谈到的此版本的内容主要有以下四个:
1.解决原有语法的一些不足或者问题(let const块级作用域)
2.对原有语法进行增强(解构,展开,参数默认值)
3.全新的对象,全新的方法,全新的功能
4.全新的数据类型,数据结构

块级作用域 & let关键词

花括号包裹起来的范围就叫做块。以前块是没有单独的作用域的,导致我们定义的成员在外部也可以访问到

for (var i = 0; i < 3; i++) {
    for (var i = 0; i < 3; i++) {
        console.log(i)
    }
}

// 打印三次0,1,2

为什么会这样?
内层也用var声明了i,并且覆盖了外层的i,导致内层跑了三次之后,i = 3,外层的循环不再调用,所以只打印了三次

修改为let的话就会作为内部的块级作用域的局部成员,并不会影响外部,也就是运行九次。

为了方便后期理解代码,不要使用同名的计数器

再来一个例子:

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
  elements[i].onclick = function () {
    console.log(i)
  }
}
elements[0].onclick()
elements[1].onclick()
elements[2].onclick()
// 全都是3

显而易见,在访问onclick的时候其实循环已经结束,var i的值已经增加完毕了,累加到了3,无论打印哪一个元素,都是结果3,这其实也是闭包的典型应用:(使用函数作用域摆脱全局作用域的影响)

var elements = [{}, {}, {}]
for (var i = 0; i < elements.length; i++) {
  elements[i].onclick = (function (i) {
    return function () {
      console.log(i)
    }
  })(i)
}
elements[0].onclick()

但是现在有了块级作用域之后就没有这么麻烦了,我们可以使用let将i限制在一个作用域内被访问,问题就迎刃而解了。

其实这个内部也是一个闭包的机制,在onclick事件触发的时候,循环机制早就结束了,实际上的i早就销毁掉了,因为闭包的机制,我们才可以拿到原本循环里的i的值。

for循环会造成两层作用域:

for (let i = 0; i < 3; i++) {
  let i = 'foo'
  console.log(i)
}

// 可以拆成下面的逻辑:

let i = 0

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

if (i < 3) {
  let i = 'foo'
  console.log(i)
}

i++

let i = 'foo'这个实际是if内部的局部变量,外部的let 实际上是外部的局部变量,互不影响。

let的声明不会出现提升的情况,var会提升到最开始

console.log(foo)
var foo = 'zce'
// zce

console.log(foo)
let foo = 'zce'
// undefined

在ES6语法里,修改了这个bug,要求先声明变量,再调用变量

tips:为什么不直接对var进行升级?而是定义了新的关键词let

因为如果对var直接进行升级会造成以前的很多项目无法使用

const

声明常量,在let的基础上多了一个只读特性
这个部分需要注意三个点:
变量经过声明之后不能再经过修改
在声明变量的时候就应该赋值,而不是先声明,再赋值
恒量只是要求内层指向不允许被修改,对于数据成员的修改是没有问题的

const name = 'zce'
// 恒量声明过后不允许重新赋值
name = 'jack'

// 恒量要求声明同时赋值
const str
str = 'zce'

// 恒量只是要求内层指向不允许被修改
const obj = {}
// 对于数据成员的修改是没有问题的
obj.name = 'zce'

// 不允许
obj = {}

最佳实践:不用var,主要使用const,let配合使用

数组的解构

通过一个代码例子就能够很清晰的明白用法了:

// 数组的解构
const arr = [100, 200, 300];

const [foo, bar, baz] = arr;
console.log(foo, bar, baz);

const [, , baz] = arr;
console.log(baz);

// ...只能在解构写法的最后一位使用
// [200, 300]
const [foo, ...rest] = arr;
console.log(rest);

// undefined
const [foo, bar, baz, more] = arr;
console.log(more);

//123, default value
const [foo, bar, baz = 123, more = "default value"] = arr;
console.log(bar, more);

const path = "/foo/bar/baz";
// const tmp = path.split('/')
// const rootdir = tmp[1]

const [, rootdir] = path.split("/");
console.log(rootdir);

对象的解构

// 对象的解构

const obj = { name: "zce", age: 18 };

const { name } = obj;
console.log(name);

// 同名的变量会引发冲突,可以使用:重命名变量
// zce
const name = "tom";
const { name: objName } = obj;
console.log(objName);

// 可以在重命名的时候添加默认值
// jack
const name = "tom";
const { name: objName = "jack" } = obj;
console.log(objName);

const { log } = console;
log("foo");
log("bar");
log("123");

模板字符串

// 模板字符串

// 反引号包裹
const str = `hello es2015, this is a string`;

// 允许换行(传统字符串不支持换行)
const str = `hello es2015,

this is a \`string\``;

console.log(str);

const name = "tom";
// 可以通过 ${} 插入表达式,表达式的执行结果将会输出到对应位置,不光是变量,如何标准的js表达式都可以(最终有return返回值的都可以)
const msg = `hey, ${name} --- ${1 + 2} ---- ${Math.random()}`;
console.log(msg);

模板字符串标签函数

// 带标签的模板字符串

// 模板字符串的标签就是一个特殊的函数,
// 使用这个标签就是调用这个函数
// const str = console.log`hello world`

const name = "tom";
const gender = false;

function myTagFunc(strings, name, gender) {
  console.log(strings, name, gender);
  // ['hey, ', ' is a ', '.']
  const sex = gender ? "man" : "woman";
  return strings[0] + name + strings[1] + sex + strings[2];
}

const result = myTagFunc`hey, ${name} is a ${gender}.`;

console.log(result);
// 'hey, tom is a woman.'

字符串的常见的拓展方法

  • includes()
  • startsWith()
  • endsWith()
// 字符串的扩展方法
const message = "Error: foo is not defined.";

console.log(
  // true 是否以Error开头
  message.startsWith("Error"),
  // true 是否以.结尾
  message.endsWith("."),
  // true 是否包含foo字段
  message.includes("foo")
);

参数默认值

// 函数参数的默认值

// 以前的默认值写法:
function foo (enable) {
  // 短路运算很多情况下是不适合判断默认参数的,例如 0 '' false null
  // enable = enable || true
  enable = enable === undefined ? true : enable
  console.log('foo invoked - enable: ')
  console.log(enable)
}

// ES6:
// 默认参数一定是在形参列表的最后
function foo (enable = true) {
  console.log('foo invoked - enable: ')
  console.log(enable)
}

foo(false)

剩余参数

// 剩余参数

// 伪数组arguments
function fooBase() {
  // { '0': 1, '1': 2, '2': 3, '3': 4 }
  console.log(arguments);
}

// ES6 剩余参数:
function foo(first, ...args) {
  // [2, 3, 4]
  // 这种操作符只能使用一次,出现在形参的最后一位
  console.log(args);
}

foo(1, 2, 3, 4);
fooBase(1, 2, 3, 4);

展开数组

// 展开数组参数

const arr = ["foo", "bar", "baz"];

// apply可以用数组的形式接受形参列表
console.log.apply(console, arr);

// ES6
console.log(...arr);

// foo bar baz

箭头函数

function inc (number) {
  return number + 1
}

// 最简方式
const inc = n => n + 1

// 完整参数列表,函数体多条语句,返回值仍需 return
const inc = (n, m) => {
  console.log("inc invoked");
  return n + 1;
};

const arr = [1, 2, 3, 4, 5, 6, 7];

// arr.filter(function (item) {
//   return item % 2
// })

// 常用场景,回调函数
arr.filter((i) => i % 2);

箭头函数与this

箭头函数不会改变this的指向

// 箭头函数与 this
// 箭头函数不会改变 this 指向

const person = {
  name: "tom",
  // sayHi: function () {
  //   console.log(`hi, my name is ${this.name}`)
  // }
  sayHi: () => {
    // undefined
    console.log(`hi, my name is ${this.name}`);
  },
  sayHiAsync: function () {
    // const _this = this
    // setTimeout(function () {
    //   console.log(_this.name)
    // }, 1000)

    // 上面的代码this需要使用闭包先行保存一下↑↑↑
    console.log(this);
    // 箭头函数始终指向当前作用域的this
    setTimeout(() => {
      // console.log(this.name)
      console.log(this);
    }, 1000);
  },
};

person.sayHiAsync();

对象字面量增强


// 对象字面量

const bar = '345'

const obj = {
  foo: 123,
  // bar: bar
  // 属性名与变量名相同,可以省略 : bar
  bar,
  // method1: function () {
  //   console.log('method111')
  // }
  // 方法可以省略 : function
  method1 () {
    console.log('method111')
    // 这种方法就是普通的函数,同样影响 this 指向。
    console.log(this)
  },
  // Math.random(): 123 // 不允许
  // 通过 [] 让表达式的结果作为属性名
  [bar]: 123
}

// obj[Math.random()] = 123

console.log(obj)
obj.method1()

对象扩展方法

Object.assign

将多个源对象的属性复制到一个目标对象中

// Object.assign 方法

const source1 = {
  a: 123,
  b: 123,
};

const source2 = {
  b: 789,
  d: 789,
};

const target = {
  a: 456,
  c: 456,
};

// 用后面对象中的属性去覆盖第一个对象

const result = Object.assign(target, source1, source2);

// assign返回值就是第一个对象
console.log(target);
// { a: 123, c: 456, b: 789, d: 789 }
console.log(result === target);

应用场景:

function func(obj) {
  // 在函数内部修改数据,使用assign就是全新的对象不会影响外部的数据
  const funcObj = Object.assign({}, obj);
  funcObj.name = "func obj";
  console.log(funcObj);
}

const obj = { name: "global obj" };

func(obj);
console.log(obj);

Object.is

判断两个值是否相等

// Object.is

console
  .log
  // 两等于号会在比较之前自动转换类型
  // 0 == false              // => true
  // 三等于号严格对比两者数值类型是否相等
  // 0 === false             // => false
  // 但是+0和-0是无法对比的
  // +0 === -0               // => true
  // 非数字,无限种可能,所以不相等,但是在目前来看NaN只不过是特殊的值,所以应该相等
  // NaN === NaN             // => false
  // 通过Object.is可以区分+0和-0,NaN也是相等的。
  // Object.is(+0, -0)       // => false
  // Object.is(NaN, NaN)     // => true
  ();

此方法运用的并不多,更多时候应该使用===

Proxy

捕获对象属性的读写过程:Object.defineProperty
Vue3之前的版本就是使用这个方法实现的数据响应,完成的双向数据绑定

Proxy就是专门为对象设置对象代理的,通过他可以轻松监视对象的读写

// Proxy 对象

const person = {
  name: "zce",
  age: 20,
};

const personProxy = new Proxy(person, {
  // 监视属性读取
  get(target, property) {
    // target是目标对象,property是调用的属性比如name, gender
    return property in target ? target[property] : "default";
  },
  // 监视属性设置
  set(target, property, value) {
    if (property === "age") {
      if (!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an int`);
      }
    }

    target[property] = value;
    // console.log(target, property, value)
  },
});

personProxy.age = 100;

personProxy.gender = true;

console.log(personProxy.name);
console.log(personProxy.xxx);

Proxy 与Object.defineProperty对比

1.defineProperty只能监视到对象数据的读写,Proxy能够监视到更多的对象操作:对象里的方法调用,delete等

const person = {
  name: "zce",
  age: 20,
};

const personProxy = new Proxy(person, {
  deleteProperty(target, property) {
    console.log("delete", property);
    delete target[property];
  },
});

delete personProxy.age;
console.log(person);

其他的操作比如:

2.Proxy可以更方便的监控数组操作,defineProperty要对数组进行操作还需要重写相关方法

const list = [];

const listProxy = new Proxy(list, {
  set(target, property, value) {
    console.log("set", property, value);
    target[property] = value;
    return true; // 表示设置成功
  },
});

listProxy.push(100);
listProxy.push(100);

3.Proxy不需要侵入对象
Proxy是以非侵入的方式监管了整个对象的读写,已经定好的对象,不需要对对象进行操作,就可以监视到读写,但是Object.defineProperty要求必须要通过特定的方式单独定义对象中需要被监视的属性
Object.defineProperty:

const person = {}

Object.defineProperty(person, 'name', {
  get () {
    console.log('name 被访问')
    return person._name
  },
  set (value) {
    console.log('name 被设置')
    person._name = value
  }
})
Object.defineProperty(person, 'age', {
  get () {
    console.log('age 被访问')
    return person._age
  },
  set (value) {
    console.log('age 被设置')
    person._age = value
  }
})

person.name = 'jack'

console.log(person.name) 

Proxy:

const person2 = {
  name: "zce",
  age: 20,
};

const personProxy = new Proxy(person2, {
  get(target, property) {
    console.log("get", property);
    return target[property];
  },
  set(target, property, value) {
    console.log("set", property, value);
    target[property] = value;
  },
});

personProxy.name = "jack";

console.log(personProxy.name);

Reflect

ES6的一个全新的内置对象,统一的对象操作API
Reflect是一个静态类。只能调用其中的一些静态方法(类似Math)

Reflect内部封装了一系列对对象的底层操作
Reflect的成员方法就是Proxy处理对象的默认实现

// Reflect 对象

const obj = {
  foo: "123",
  bar: "456",
};

const proxy = new Proxy(obj, {
  get(target, property) {
    console.log("watch logic~");

    return Reflect.get(target, property);
  },
});

console.log(proxy.foo)

使用Proxy的get或者set的逻辑时,更标准的做法是:先去实现自己的监视逻辑,最后返回通过Reflect中对应方法的一个调用结果。

Reflect的存在价值?
他提供了统一的一套操作对象的API

const obj = {
  name: "zce",
  age: 18,
};

console.log("name" in obj);
console.log(delete obj["age"]);
console.log(Object.keys(obj));

console.log(Reflect.has(obj, "name"));
console.log(Reflect.deleteProperty(obj, "age"));
console.log(Reflect.ownKeys(obj));

Promise

这个也是ES6的,之前已经讲过了,这里不赘述
Promise

class类

独立定义类的语法,比起函数定义来说,结果会更清晰一点

// class 关键词

// function Person (name) {
//   this.name = name
// }

// Person.prototype.say = function () {
//   console.log(`hi, my name is ${this.name}`)
// }

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

const p = new Person('tom')
p.say()

静态成员

static,可以直接用class的类调用
比如Promise.all就是静态成员
这其中,static静态成员的this,指向的就是类型。而不是实例对象

// static 方法

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }

  static create (name) {
    return new Person(name)
  }
}

const tom = Person.create('tom')
tom.say()

类的继承

// extends 继承

class Person {
  constructor (name) {
    this.name = name
  }

  say () {
    console.log(`hi, my name is ${this.name}`)
  }
}

class Student extends Person {
  constructor (name, number) {
    super(name) // 父类构造函数
    this.number = number
  }

  hello () {
    super.say() // 调用父类成员
    console.log(`my school number is ${this.number}`)
  }
}

const s = new Student('jack', '100')
s.hello()

Set数据结构

每一个值在同一个set中是唯一的

// Set 数据结构

const s = new Set();

s.add(1).add(2).add(3).add(4).add(2);

// console.log(s);

// s.forEach((i) => console.log(i));

// for (let i of s) {
//   console.log(i);
// }

// 与数组的length差不多是一个意思
// console.log(s.size)

// 是否包含100
// console.log(s.has(100))

// 删除某个值
// console.log(s.delete(3))
// console.log(s)

// 清空集合的全部内容
// s.clear()
// console.log(s)

// 应用场景:数组去重

// const arr = [1, 2, 1, 3, 4, 1];

// const result = Array.from(new Set(arr));
// const result = [...new Set(arr)];

// console.log(result);

// 弱引用版本 WeakSet
// 差异就是 Set 中会对所使用到的数据产生引用
// 即便这个数据在外面被消耗,但是由于 Set 引用了这个数据,所以依然不会回收
// 而 WeakSet 的特点就是不会产生引用,
// 一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。

Map数据结构

与对象非常类似,都是键值对集合
但是对象结构的键,只能是字符串类型
Map才算得上严格意义的键值对集合

// Map 数据结构

// const obj = {}
// obj[true] = 'value'
// obj[123] = 'value'
// obj[{ a: 1 }] = 'value'

// console.log(Object.keys(obj))
// console.log(obj['[object Object]'])

const m = new Map();

const tom = { name: "tom" };

m.set(tom, 90);

// Map(1) { { name: 'tom' } => 90 }
console.log(m);

console.log(m.get(tom));

// m.has()
// m.delete()
// m.clear()

m.forEach((value, key) => {
  console.log(value, key);
});

// 弱引用版本 WeakMap
// 差异就是 Map 中会对所使用到的数据产生引用
// 即便这个数据在外面被消耗,但是由于 Map 引用了这个数据,所以依然不会回收
// 而 WeakMap 的特点就是不会产生引用,
// 一旦数据销毁,就可以被回收,所以不会产生内存泄漏问题。

Symbol

一种全新的原始数据类型

const s = Symbol();
console.log(s);
console.log(typeof s);

// 两个 Symbol 永远不会相等

console.log(Symbol() === Symbol());

// 使用 Symbol 为对象添加用不重复的键

// const obj = {}
// obj[Symbol()] = '123'
// obj[Symbol()] = '456'
// console.log(obj)

// 也可以在计算属性名中使用

// const obj = {
//   [Symbol()]: 123
// }
// console.log(obj)

// 案例2:Symbol 模拟实现私有成员

// a.js ======================================

const name = Symbol();
const person = {
  [name]: "zce",
  say() {
    console.log(this[name]);
  },
};
// 只对外暴露 person

// b.js =======================================

// 由于无法创建出一样的 Symbol 值,
// 所以无法直接访问到 person 中的「私有」成员
// person[Symbol()]
person.say();

最主要的作用就是为对象添加一个独一无二的属性名
需要注意几点:
可以使用symbol的for方法来传入字符串获得一样的symbol数据(全局注册表)
如果内部传入的不是字符串,这个方法会自动转换为字符串,就会导致获得一样的symbol

const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2)

console.log(Symbol.for(true) === Symbol.for('true'))

内置 Symbol 常量:

console.log(Symbol.iterator);
console.log(Symbol.hasInstance);

const obj = {
  [Symbol.toStringTag]: "XObject",
};
console.log(obj.toString());
//[object XObject]

toStringTag就是一个内置的symbol常量

获取symbol属性名:

// Symbol 属性名获取

const obj = {
  [Symbol()]: "symbol value",
  foo: "normal value",
};

for (var key in obj) {
  // 无法拿到symbol类型的属性名
  console.log(key)
}

// symbol会被忽略掉
console.log(Object.keys(obj))
console.log(JSON.stringify(obj))

// 获取symbol属性名的方法:
console.log(Object.getOwnPropertySymbols(obj));

for...of 循环

以后会作为遍历所有数据结构的统一方式
1.对比于forEach循环,for of可以随时终止循环(break关键字)
2.伪数组数据也可以使用for of遍历,比如arguments数据。DOM操作的节点列表等

Set和Map:

// 遍历 Set 与遍历数组相同

const s = new Set(['foo', 'bar'])

for (const item of s) {
  console.log(item)
}

// 遍历 Map 可以配合数组结构语法,直接获取键值

const m = new Map()
m.set('foo', '123')
m.set('bar', '345')

for (const [key, value] of m) {
  console.log(key, value)
}

可迭代接口

for of以后会作为遍历所有数据结构的统一方式,但是普通对象不能被直接 for...of 遍历

原因:
ES中能够表示有结构的数据类型越来越多,为了给各种各样的数据结构提供一种统一的遍历方式,ES6提出了一个叫做Iterable的接口,而,实现Iterable接口就是for of的前提,只要数据结构实现了可迭代接口,那就可以使用for of遍历

可以在浏览器里对数据进行展开检查,在原型对象里,找到一个

这个就是interator接口约定的就是对象中必须要挂载的方法,调用这个方法:

再访问这个next方法:

持续调用next,value变成了bar,done还是false,调用第三次的时候,baz。done便成为了true。每调用一次next,指针就往后移动一位,done就是调用完的标志。

总结:所有的可以被for of遍历的数据结构都必须要实现这个可迭代接口,在内部必须要挂载一个这个interator的方法,这里面还有一个带有next方法的对象,不断调用next方法,就可以实现所有数据的遍历

// 迭代器(Iterator)

const set = new Set(['foo', 'bar', 'baz'])

const iterator = set[Symbol.iterator]()

// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())
// console.log(iterator.next())

while (true) {
  const current = iterator.next()
  if (current.done) {
    break // 迭代已经结束了,没必要继续了
  }
  console.log(current.value)
}

实现interable可迭代接口

const obj = {
  store: ["foo", "bar", "baz"],

  [Symbol.iterator]: function () {
    // 维护一个下标
    let index = 0;
    // 接受下当前的this
    const self = this;

    return {
      next: function () {
        const result = {
          value: self.store[index],
          done: index >= self.store.length,
        };
        index++;
        return result;
      },
    };
  },
};

for (const item of obj) {
  console.log("循环体", item);
}

迭代器模式

// 迭代器设计模式

// 场景:你我协同开发一个任务清单应用

// 我的代码 ===============================

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],

  // 提供统一遍历访问接口
  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for (const item of all) {
      callback(item)
    }
  },

  // 提供迭代器(ES2015 统一遍历访问接口)
  [Symbol.iterator]: function () {
    const all = [...this.life, ...this.learn, ...this.work]
    let index = 0
    return {
      next: function () {
        return {
          value: all[index],
          done: index++ >= all.length
        }
      }
    }
  }
}

// 你的代码 ===============================

// for (const item of todos.life) {
//   console.log(item)
// }
// for (const item of todos.learn) {
//   console.log(item)
// }
// for (const item of todos.work) {
//   console.log(item)
// }

todos.each(function (item) {
  console.log(item)
})

console.log('-------------------------------')

for (const item of todos) {
  console.log(item)
}

该使用场景就是要让你的代码和我的代码解耦,我的属性如论如何变化,都提供一个迭代器方法给你调用。
迭代器的意义就是:给外部提供一个统一的接口,让外部不再关心内部的结构到底是什么样子的。

生成器 generator

这个知识点之前的文章也提到了:
生成器generator

不再赘述,这里提供一个generator应用:

// Generator 应用

// 案例1:发号器

function * createIdMaker () {
  let id = 1
  while (true) {
    yield id++
  }
}

const idMaker = createIdMaker()

console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)
console.log(idMaker.next().value)

// 案例2:使用 Generator 函数实现 iterator 方法

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for (const item of all) {
      yield item
    }
  }
}

for (const item of todos) {
  console.log(item)
}

他最重要的是避免异步编程回调函数嵌套过深,不过最后都可以使用async await语法糖搞定,也没啥好说的。。

ES2016、ES2017

  • 数组的includes方法:
    检查数组是否包含指定元素(相对于indexOf而言,它还可以查找NaN这样的数值)
  • 指数运算符:

    console.log(Math.pow(2, 10))
    // 等效于
    console.log(2 ** 10)
  • Object.values
    返回对象中所有值组成的数组
  • Object.entries
    是以数组的形式返回对象中所有的键值对,可以直接使用for of循环去遍历普通对象了。
  • Object.getOwnPropertyDescriptors
    获取对象中的属性的完整描述信息。

    const p1 = {
    firstName: "chen",
    lastName: "ny",
    get fullName() {
      return this.firstName + this.lastName;
    },
    };
    
    // 直接使用assign,是把fullName当成了普通的属性去复制,这种情况可以使用getOwnPropertyDescriptors,获取对象属性的完整描述信息。
    const descriptors = Object.getOwnPropertyDescriptors(p1);
    
    const p2 = Object.defineProperties({}, descriptors);
    
    p2.firstName = "zhao";
    
    console.log(p2.fullName);
  • padStart / padEnd
    String的原型方法添加了padStart / padEnd

    const books = {
    html: 5,
    css: 16,
    javascript: 128,
    };
    
    for (const [name, value] of Object.entries(books)) {
    console.log(`${name.padEnd(16, "-")}|${value.toString().padStart(3, "0")}`);
    }

用给定的字符串去填充目标字符串的开始或者结束位置,直到达到指定的长度为止。

  • 还有一个小变化,在函数参数中添加尾逗号

不影响到功能。这是一种写法上的标准。

  • Async / Await
    从这里开始彻底解决了异步嵌套过深的问题
最后修改:2024 年 03 月 21 日
收款不要了,给孩子补充点点赞数吧