常用的ES6+新特性

632 阅读13分钟

概念

  • javascript @ web ECMAScript + BOM + DOM
  • javascript @ node ECMAScript + fs + net + etc.

一、let、const

  1. 块级作用域
  • 解决循环计数中变量覆盖问题
for(var i = 0; i < 3; i++){
    for(var i = 0; i < 3; i++){
        console.log(i) // 0 1 2
    }
}

for(let i = 0; i < 3; i++){
    for(let i = 0; i < 3; i++){
        console.log(i) // 三次 0 1 2
    }
}
  • 解决循环中变量累加问题
// var定义的变量是全局的, 所以全局只有一个变量
var element = [{},{},{}]
for(var i = 0; i <element.length; i++){
    element[i].onclick = function(){
        console.log(i) // 3
    }
}
element[0].onclick() 
// 闭包解决
var element = [{},{},{}]
for(var i = 0; i <element.length; i++){
    element[i].onclick = (function(i){
        return function(){
            console.log(i) // 0
        }
    })(i)
}
element[0].onclick() 
// let解决
var element = [{},{},{}]
for(let i = 0; i <element.length; i++){
    element[i].onclick = function(){
        console.log(i) // 0
    }
}
element[0].onclick()
  • 拆解代码
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) // foo
}
...
  1. 无声明提升
  • 修复官方Bug
  1. const只读特性
  • 必须声明初始值
  • 不允许指向新的地址,但可以修改衡量中的属性成员
  1. 暂时性死区
  • 如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成封闭作用域。凡是在声明之前就使用这些变量,就会报错。 这就是暂时性死区

二、解构

  1. 数组
// 基础类型解构
let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1, 2, 3

// 对象数组解构
let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}

// ...解构
let [head, ...tail] = [1, 2, 3, 4]
console.log(head, tail) // 1, [2, 3, 4]

// 嵌套解构
let [a, [b], d] = [1, [2, 3], 4]
console.log(a, b, d) // 1, 2, 4

// 解构不成功为undefined
let [a, b, c] = [1]
console.log(a, b, c) // 1, undefined, undefined

// 解构默认赋值
let [a = 1, b = 2] = [3]
console.log(a, b) // 3, 2
  1. 对象
// 对象属性解构
let { f1, f2 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2

// 可以不按照顺序,这是数组解构和对象解构的区别之一
let { f2, f1 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2

// 解构对象重命名
let { f1: rename, f2 } = { f1: 'test1', f2: 'test2' }
console.log(rename, f2) // test1, test2

// 嵌套解构
let { f1: {f11}} = { f1: { f11: 'test11', f12: 'test12' } }
console.log(f11) // test11

// 默认值
let { f1 = 'test1', f2: rename = 'test2' } = { f1: 'current1', f2: 'current2'}
console.log(f1, rename) // current1, current2
  1. 解构原理
  • 针对可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments对象,NodeList对象)的Iterator接口,通过遍历器按顺序获取对应的值进行赋值.

可迭代对象是Iterator接口的实现。这是ECMAScript 2015的补充,它不是内置或语法,而仅仅是协议。任何遵循该协议点对象都能成为可迭代对象。可迭代对象得有两个协议:可迭代协议和迭代器协议。

  • 可迭代协议:对象必须实现iterator方法。即对象或其原型链上必须有一个名叫Symbol.iterator的属性。该属性的值为无参函数,函数返回迭代器协议。

  • 迭代器协议:定义了标准的方式来产生一个有限或无限序列值。其要求必须实现一个next()方法,该方法返回对象有done(boolean)和value属性。

Iterator是一种接口,为各种不一样的数据解构提供统一的访问机制。任何数据解构只要有Iterator接口,就能通过遍历操作,依次按顺序处理数据结构内所有成员。ES6中的for of的语法相当于遍历器,会在遍历数据结构时,自动寻找Iterator接口。

  • 为各种数据解构提供统一的访问接口
  • 使得数据解构能按次序排列处理
  • 可以使用ES6最新命令 for of进行遍历
function generateIterator(array) {
    let nextIndex = 0
    return {
        next: () => nextIndex < array.length ? {
            value: array[nextIndex++],
            done: false
        } : {
            value: undefined,
            done: true
        }
    };
}

const iterator = generateIterator([0, 1, 2])

console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
  • 实现一个可以for of遍历的对象:通过以上可知,自定义数据结构,只要拥有Iterator接口,并将其部署到自己的Symbol.iterator属性上,就可以成为可迭代对象,能被for of循环遍历。
const obj = {
    count: 0,
    [Symbol.iterator]: () => {
        return {
            next: () => {
                obj.count++;
                if (obj.count <= 10) {
                    return {
                        value: obj.count,
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true
                    }
                }
            }
        }
    }
}

for (const item of obj) {
    console.log(item)
}
  • 或者
const iterable = {
    0: 'a',
    1: 'b',
    2: 'c',
    length: 3,
    [Symbol.iterator]: Array.prototype[Symbol.iterator],
};

for (const item of iterable) {
    console.log(item);
}

三、模板字符串

  1. 可换行
const b = 'lubai'
const a  = `${b} - xxxx`;
const c = `我是换行
我换行了!
我又换行了!
`;
const d = '换行\n换行' // 原本的写法
  1. 支持差值表达式
  2. 标签函数
const str = console.log`hello world` // [ 'hello world' ]
function myTagFunc(str, name, gender) {
    console.log(str, name, gender) // [ 'hey,', ' is ', '' ] tom true
    return 'result'
}
const name = 'tom'
const gender = true
const result = myTagFunc`hey,${name} is ${gender}`
  1. 编写render函数, 实现template render功能.
const year = '2021'; 
const month = '10'; 
const day = '01'; 

let template = '${year}-${month}-${day}';
let context = { year, month, day };

const str = render(template)(context);  // 高阶函数

// 目的:
console.log(str) // 2021-10-01

// 在这里实现功能:
function render(template) {
    return function(context) {
        return template.replace(/\$\{(.*?)\}/g, (match, key) => context[key]); // 该函数的返回值将替换掉正则匹配到的结果
    }
}

四、字符串

  1. includes()
  2. startsWith()
  3. endWith()
const message = 'I am a boy'
console.log(message.startsWith('I'))
console.log(message.includes('am'))
console.log(message.endsWith('boy'))

五、参数

  1. 默认值
  • 带有默认值的参数要放在最后
function item (age, value = 1){
    console.log(age, value) // 18 1
}
item(18)
  1. 剩余参数
  • 之前只能使用arguments伪数组接收
// 剩余参数只能放在形参末位,并且只能使用一次
function foo(...args){
    console.log(...args)
}
foo(1,2,3,4)
  1. 展开运算符
// 之前
const arr = [1,2,3,4]
console.log.apply(console,arr)
// ES6
const arr = [1,2,3,4]
console.log(...arr)

六、箭头函数

  1. 不会改变this指向
  • 普通函数里的this是使用的时候决定的,箭头函数里的this是定义的时候决定的
const teacher = {
    name: 'lubai',
    getName: function() {
        return `${this.name}`
    }
}
console.log(teacher.getName()); // 指向teacher

const teacher = {
    name: 'lubai',
    getName: () => {
        return `${this.name}`
    }
}
console.log(teacher.getName()); // 指向window
  1. 简化函数写法
const person = { 
    name: 'John',
    say: function(){
        // const _this = this
        // setTimeout(function(){
        //     console.log(_this.name)
        // },0)
        setTimeout(()=>{
            console.log(this.name)
        },0)
    }
}
person.say()
  1. 箭头函数不能被用作构造函数
  • 构造函数执行的时候会改变this指新实例出来的对象. 箭头函数this指向是定义的时候就决定的. 这两者是冲突的。
const arrow = () => {} // 此时this指向widow
const newArrow = new Arrow() // 此时this指向newArrow

七、对象

  1. 计算属性名
// ES6
const obj = { 
    [math.random()]: 123
}
// ES5
obj[math.random()] = 123
  1. Object.assign()
  • 用后面对象的属性覆盖前面的对象,浅拷贝, 类似于 { ...a, ...b };
Object.assign({}, {a:1},{b:2})
  1. Object.is()
  • 判断两个值是否相等
  • 解决了三等运算符问题:两个NAN不相等、无法区分正0和负0
const a = {
    name: 1
};
const b = a;
console.log(Object.is(a, b)) // true

console.log(Object.is({}, {})) // false
  1. Proxy
  • ES5:Object.defineProperty()
const person = {
    name: 'John',
    age: 20,
}
const personProxy = new PersonProxy(person, {
    get(target, property){
        return property in target ? target[property] : 'default'
    },
    set(target, property, value) {
        if(property === 'age'){
            if(!Number.isInteger(value)){
                throw new Error('not an int')
            }
        }
        target[property] = value
    }
})
  • Proxy优势:
    • 可以有例如删除对象等defineProperty做不到的操作
    • Proxy用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
    const obj = new Proxy({}, {
        get: function (target, propKey, receiver) {
            console.log(`getting ${propKey}`);
            return target[propKey];
        },
        set: function (target, propKey, value, receiver) {
            console.log(`setting ${propKey}`);
            return Reflect.set(target, propKey, value, receiver);
        }
    });
    
    obj.something = 1;
    console.log(obj.something);
    
    • 但是要注意, 通过defineProperty设置writable为false的对象, 就不能用Proxy了
    const target = Object.defineProperties({}, {
        foo: {
            value: 123,
            writable: false,
            configurable: false
        },
    });
    
    
    const proxy = new Proxy(target, {
        get(target, propKey) {
            return 'abc';
        }
    });
    
    proxy.foo
    
    • proxy可以操作数组
    const listProxy = new ListProxy(list, {
        set(target, property, value) {
            target[property] = value;
            return true;
        }
    })
    ListProxy.push(100)
    
    • Proxy是以非侵入的方式监管了对象的读写
  1. Relect
  • 它的成员方法是Proxy处理对象的默认实现
    • 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法
    • 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。
    • Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。
const obj = {
    foo:'123',
    bar:'456',
}
const proxy = new Proxy(obj,{
    get(target, property){
        console.log('...')
        return Reflect.get(target, property)
    }
})
  • 提供了统一的一套用于操作对象的API
const obj = {
    foo:'123',
    bar:'456',
}
// 不用Relect
console.log('foo' in obj)
console.log(delete obj['bar'])
console.log(Object.keys(obj))
// 用Relect
console.log(Relect.has(obj,'foo'))
console.log(Relect.deleteProperty(obj,'bar'))
console.log(Relect.ownKeys(obj))

八、class类

  • 实例方法、静态方法
class Test {
    _name = '';
    constructor(name) {
        this.name = name;
    }

    static getFormatName() {
        return `${this.name} - xixi`;
    }

    get name() {
        return this._name;
    }

    set name(val) {
        console.log('name setter');
        this._name = val;
    }
}

const inst = new Test('123')
console.log(inst.name) // 实例属性
console.log(Test.getFormatName()) // 静态属性
  • 类的继承
class Person {
    constructor(name){
        this.name = name;
    }
    say(){
        console.log(this.name)
    }
}

class Student extends Person {
    constructor(name, number){
        super(name)
        this.number = number;
    }
    hello(){
        super.say()
        console.log(this.number)
    }
}

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

九、Set、Map数据结构

  1. Set
const s = new Set()
// 链式调用
s.add(1).add(2).add(3)
// 遍历
s.forEach(i => console.log(i))
for(let i of s){
    console.log(i)
}
// 长度
s.size()
// 判断存在
s.has(100)
// 删除
s.delete(3)
// 清空
s.clear()

// 去重
const arr = [1,2,3,3]
const result = Array.from(new Set(arr))
const result = [...new Set(arr)]
  1. Map
  • 键可以是任意数据类型
const m = new Map()
const tom = {name: 'top'}
m.set(tom,90)
m.get(tom)
m.has()
m.delete()
m.clear()
m.forEach((value, key)=>{
    console.log(value, key)
})

十、Symbol数据类型

  • 表示一个独一无二的值
// 解决对象键名冲突问题(ES6以后,对象的属性名除了字符串外,也能是Symbol类型)
const obj = {}
obj[Symbol()] = '123'
// 创建私有变量
const name = Symbol()
const person = {
    [name]: '123',
    say(){
        console.log(this[name])
    }
}
  • 其他方法
// 实现复用
console.log(Symbol('foo') === Symbol('foo')) // false
const s1 = Symbol.for('foo')
const s2 = Symbol.for('foo')
console.log(s1 === s2) // true
// 定义迭代器
Symbol.iterator
// 判断某对象是否为某构造器的实例
Symbol.hasInstance
// 设置标签
const obj = {
    [Symbol.toStringTag]: 'XO'
}
console.log(obj.toString()) // [object XO]
// 获取键名
const obj = {
    [Symbol()]: 'XO',
    foo: 'bar'
}
console.log(Object.keys(obj)) // [ 'foo' ]
console.log(Object.getOwnPropertySymbols(obj)) // [ Symbol() ]

十一、迭代器、生成器

  1. for...of循环
  • 可终止、可以遍历伪数组、set和map数据结构等,不能遍历对象
  • for in 不仅会遍历当前对象,还包括原型链上的可枚举属性;for of 仅遍历当前对象
  • for of、for in 允许中断,forEach不允许
const arr = [100,200,300]
for(const item of arr){
    console.log(item)
    if(item > 100){
        break;
    }
}
// 遍历map
const map = new Map();
const name = 'ztt'
map.set(name, 18)
for(const item of map){
    console.log(item); //[ 'ztt', 18 ]
}
  1. 迭代器Iterable
  • 实现
const obj = { 
    store: [1,2,3],
    [Symbol.iterator]: function(){
        let index = 0;
        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)
}
  • 迭代器模式:对外提供统一遍历接口,让外部不用关心内部数据结构
// 无Iterable
const todos = {
    life: ['吃饭','睡觉'],
    learn: ['阅读','吉他','英语'],
    work: ['业务','课程','算法'],
    each: function(callback) {
        const all = [].concat(this.life, this.learn, this.work);
        for (const item of all) {
            callback(item)
        }
    }
}
todos.each(function(item) {
    console.log(item)
})
// 有Iterable
const todos = {
    life: ['吃饭','睡觉'],
    learn: ['阅读','吉他','英语'],
    work: ['业务','课程','算法'],
    [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){
    console.log(item);
}
  1. 生成器Generator
  • 作用:减少回调函数嵌套产生的问题
  • 基本用法
function * foo(){
    console.log('1111')
    yield 100 //作为next的结果返回
    console.log('2222')
    yield 200 
    console.log('3333')
    yield 300 
}
const g = foo()
console.log(g.next());
// 1111
// { value: 100, done: false }
  • 应用场景
// 1. 发号器
function * create(){
    let id = 1;
    while(true){
        yield id++
    }
}
const g = create()
console.log(g.next().value);
console.log(g.next().value);
console.log(g.next().value);
// 2. 实现Iterable
const todos = {
    life: ['吃饭','睡觉'],
    learn: ['阅读','吉他','英语'],
    work: ['业务','课程','算法'],
    [Symbol.iterator]: function * (){
        const all = [...this.life, ...this.learn, ...this.work];
        for(const item of all){
            yield item
        }
    }
}

十二、babel

Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中. Babel提供了插件化的功能, 一切功能都可以以插件来实现. 方便使用和弃用.

抽象语法树

这个处理过程中的每一步都涉及到创建或是操作抽象语法树,亦称 AST。 这样一段代码, 会被转换成什么呢?

function square(n) {
  return n * n;
}

大概是这样的

{
  type: "FunctionDeclaration",
  id: {
    type: "Identifier",
    name: "square"
  },
  params: [{
    type: "Identifier",
    name: "n"
  }],
  body: {
    type: "BlockStatement",
    body: [{
      type: "ReturnStatement",
      argument: {
        type: "BinaryExpression",
        operator: "*",
        left: {
          type: "Identifier",
          name: "n"
        },
        right: {
          type: "Identifier",
          name: "n"
        }
      }
    }]
  }
}

每一层都有相同的结构

{
  type: "FunctionDeclaration",
  id: {...},
  params: [...],
  body: {...}
}
{
  type: "Identifier",
  name: ...
}
{
  type: "BinaryExpression",
  operator: ...,
  left: {...},
  right: {...}
}
  • 这样的每一层结构也被叫做 节点(Node)。 一个 AST 可以由单一的节点或是成百上千个节点构成。 它们组合在一起可以描述用于静态分析的程序语法。

  • 字符串形式的 type 字段表示节点的类型(如: "FunctionDeclaration","Identifier",或 "BinaryExpression")。 每一种类型的节点定义了一些附加属性用来进一步描述该节点类型。

  • Babel 还为每个节点额外生成了一些属性,用于描述该节点在原始代码中的位置。比如 start end

Babel 的处理步骤

Babel 的三个主要处理步骤分别是: 解析(parse),转换(transform),生成(generate)。

1. 解析

解析步骤接收代码并输出 AST。

  • 词法分析

词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。.

你可以把令牌看作是一个扁平的语法片段数组:

n * n

转换成tokens是这样的

[ { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } }, { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } }, { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } }, ... ]

  • 语法分析 语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作。
2. 转换

转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程 同时也是插件将要介入工作的部分。

3. 生成

代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。.

代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。

简单写一个babel插件
1. 一个插件就是一个函数
export default function(babel) {
}
// babel里我们主要用到types属性

export default function({ types: t }) {
}

Babel Types模块拥有每一个单一类型节点的定义,包括节点包含哪些属性,什么是合法值,如何构建节点、遍历节点,以及节点的别名等信息。

单一节点类型的定义形式如下:

defineType("BinaryExpression", {
  builder: ["operator", "left", "right"],
  fields: {
    operator: {
      validate: assertValueType("string")
    },
    left: {
      validate: assertNodeType("Expression")
    },
    right: {
      validate: assertNodeType("Expression")
    }
  },
  visitor: ["left", "right"],
  aliases: ["Binary", "Expression"]
});
2. 返回一个对象

visitor 属性是这个插件的主要访问者。visitor中的每个函数都接收两个参数 state 和 path.

export default function({ types: t }) {
  return {
    visitor: {
    }
  };
};

AST 通常会有许多节点,那么节点直接如何相互关联呢? 我们可以使用一个可操作和访问的巨大可变对象表示节点之间的关联关系,或者也可以用Paths(路径)来简化这件事情。

Path 是表示两个节点之间连接的对象。

将子节点 Identifier 表示为一个路径(Path)的话,看起来是这样的:

{
  "parent": {
    "type": "FunctionDeclaration",
    "id": {...},
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "square"
  }
}
3. 创建plugin.js

yarn add @babel/core yarn add babel-template

const template = require('babel-template');

const temp = template("var b = 1")

module.exports = function ({
    types: t
}) {
    // 插件内容
    return {
        visitor: {
            // 接收两个参数path, state
            VariableDeclaration(path, state) {
                // 找到AST节点
                const node = path.node;
                // 判断节点类型 是否是变量节点, 申明方式是const
                if (t.isVariableDeclaration(node, {
                        kind: "const"
                    })) {
                    // 将const 声明编译为let
                    node.kind = "let";
                    // var b = 1 的AST节点
                    const insertNode = temp();
                    // 插入一行代码var b = 1
                    path.insertBefore(insertNode);
                }
            }
        }
    }
}
4. 使用插件

babel.js

const myPlugin = require('./plugin')
const babel = require('@babel/core');
const content = 'const name = lubai';
// 通过你编写的插件输出的代码
const {
    code
} = babel.transform(content, {
    plugins: [
        myPlugin
    ]
});

console.log(code);
常见插件
  1. @babel/preset-env 替换之前所有babel-presets-es20xx插件
  2. @babel/preset-stage-x 指处于某一阶段的js语言提案
  3. @babel/plugin-transform-runtime 是对 Babel 编译过程中产生的 helper 方法进行重新利用(聚合),以达到减少打包体积的目的。
  4. @babel/polyfill core-js 和 regenerator-runtime 补丁的实现库

ES7

  1. includes
  2. 指数运算符

ES8

  1. Object.values
  • 该方法返回一个给定对象自身的所有可枚举属性值的数组。
const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [1, 2]
  • 实现
function getObjectValues(obj) {
    const result = [];
    for (const prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            result.push(obj[prop]);
        }
    }

    return result;
}

console.log(getObjectValues({
    a: 1,
    b: 2
}))
  1. Object.entries 返回一个给定对象自身可枚举属性的键值对数组,可以将对象转化成Map类型对象
const obj = { a: 1, b: 2 };
const keys = Object.entries(obj); // [ [ 'a', 1 ], [ 'b', 2 ] ]
  • 实现
function getObjectEntries(obj) {
    const result = [];
    for (const prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            result.push([prop, obj[prop]]);
        }
    }

    return result;
}

console.log(getObjectEntries({
    a: 1,
    b: 2
}))
  1. Object.getOwnPropertyDescriptors 用来获取一个对象的所有自身属性的描述符
    • configurable。 如果为false,则任何尝试删除目标属性或修改属性特性(writable, configurable, enumerable)的行为将被无效化。所以通常属性都有特性时,可以把configurable设置为true即可。
    • writable 是否可写。设置成 false,则任何对该属性改写的操作都无效(但不会报错,严格模式下会报错),默认false。
    • enumerable。是否能在for-in循环中遍历出来或在Object.keys中列举出来。
const p1 = {
    first:'z',
    last:'t',
    get full(){
        return this.first + this.last
    }
}
// 无getOwnPropertyDescriptors
const p2 = Object.assign({}, p1)
p2.last = 'tt'
console.log(p2.full) // zt
// 有getOwnPropertyDescriptors
const d = Object.getOwnPropertyDescriptors(p1)
const p2 = Object.defineProperties({},d)
p2.last = 'tt'
console.log(p2.full) // ztt
  1. padEnd,padStart 如果某个字符串不够指定长度,会在头部或尾部补全
const books = {
    html: 4,
    css: 3,
    ES: 6,
}
// 不补全
for(const [name, value] of Object.entries(books)){
    console.log(name, value)
    // html 4
    // css 3
    // ES 6
}
// 补全
for(const [name, value] of Object.entries(books)){
    console.log(`${name.padEnd(16,'-')}|${value.toString().padStart(3,'0')}`)
    // html------------|004
    // css-------------|003
    // ES--------------|006
}
  1. async

其他对象方法

  1. Object.keys

该方法返回一个给定对象的自身可枚举属性组成的数组。

const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [a, b]
  • 实现
function getObjectKeys(obj) {
    const result = [];
    for (const prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            result.push(prop);
        }
    }

    return result;
}

console.log(getObjectKeys({
    a: 1,
    b: 2
}))
  1. Object.getOwnPropertyNames

该方法返回一个数组,该数组对元素是 obj自身拥有的枚举或不可枚举属性名称字符串。

Object.prototype.aa = '1111';

const testData = {
    a: 1,
    b: 2
}

for (const key in testData) {
    console.log(key);
}

console.log(Object.getOwnPropertyNames(testData)) // ["a", "b"]
  1. Object.create()

Object.create()方法创建一个新的对象,并以方法的第一个参数作为新对象的__proto__属性的值(根据已有的对象作为原型,创建新的对象。)

const person = {
    isHuman: false,
    printIntroduction: function () {
        console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`);
    }
};
const me = Object.create(person);

me.name = "lubai";
me.isHuman = true;
me.printIntroduction();

console.log(person);

const myObject = Object.create(null) // 创建一个非常干净的对象, 没有任何原型链上的属性

Object.create()方法还有第二个可选参数,是一个对象,对象的每个属性都会作为新对象的自身属性,对象的属性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出现,且enumerable默认为false

function Person(name, sex) {
    this.name = name;
    this.sex = sex;
}

const b = Object.create(Person.prototype, {
    name: {
        value: 'coco',
        writable: true,
        configurable: true,
        enumerable: true,
    },
    sex: {
        enumerable: true,
        get: function () {
            return 'hello sex'
        },
        set: function (val) {
            console.log('set value:' + val)
        }
    }
})

console.log(b.name)
console.log(b.sex)

其他数组方法

  1. Array.flat

按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回

const arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

const arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • 模拟实现Array.flat
// 使用 reduce、concat 和递归展开无限多层嵌套的数组
const arr1 = [1, 2, 3, [1, 2, 3, 4, [2, 3, 4]]];

function flatDeep(arr, d = 1) {
    if (d > 0) {
        return arr.reduce((res, val) => {
            if (Array.isArray(val)) {
                res = res.concat(flatDeep(val, d - 1))
            } else {
                res = res.concat(val);
            }
            return res;
        }, [])
    } else {
        return arr.slice()
    }
};

console.log(flatDeep(arr1, Infinity))
// [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]
  • 如果不考虑深度,直接无限扁平化
function flatten(arr) {
    let res = [];
    let length = arr.length;
    for (let i = 0; i < length; i++) {
        if (Object.prototype.toString.call(arr[i]) === '[object Array]') {
            res = res.concat(flatten(arr[i]))
        } else {
            res.push(arr[i])
        }
    }
    return res
}

// 如果数组元素都是Number类型
function flatten(arr) {
	return arr.toString().split(',').map(item => +item)
}

function flatten(arr){
    while(arr.some(item=>Array.isArray(item))){
        arr = [].concat(...arr);
    }
    return arr;
}
  1. Array.includes

判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。它有两个参

  • valueToFind 需要查找的元素值。

  • fromIndex 可选 从fromIndex 索引处开始查找 valueToFind。如果为负值,则按升序从 array.length + fromIndex 的索引开始搜 (即使从末尾开始往前跳 fromIndex 的绝对值个索引,然后往 后搜寻)。默认为 0。

[1, 2, 3].includes(2);     // true
[1, 2, 3].includes(4);     // false
[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true
[1, 2, NaN].includes(NaN); // true

// fromIndex 大于等于数组长度
var arr = ['a', 'b', 'c'];

arr.includes('c', 3);   // false
arr.includes('c', 100); // false

// 计算出的索引小于 0
var arr = ['a', 'b', 'c'];

arr.includes('a', -100); // true
arr.includes('b', -100); // true
arr.includes('c', -100); // true
  1. Array.find

返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。 callback 在数组每一项上执行的函数,接收 3 个参数:

  • element 当前遍历到的元素。
  • index可选 当前遍历到的索引。
  • array可选 数组本身。
    const test = [
      {name: 'lubai', age: 11 },
      {name: 'xxx', age: 100 },
      {name: 'nnn', age: 50}
      ];
    
      function findLubai(teacher) {
          return teacher.name === 'lubai';
      }
    
      console.log(test.find(findLubai));
    
  1. Array.from

从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。

  • arrayLike 想要转换成数组的伪数组对象或可迭代对象。
  • mapFn 可选 如果指定了该参数,新数组中的每个元素会执行该回调函数。

可以通过以下方式来创建数组对象:

  • 伪数组对象(拥有一个 length 属性和若干索引属性的任意对象)
  • 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
console.log(Array.from('foo'));

console.log(Array.from([1, 2, 3], x => x + x));

const set = new Set(['foo', 'bar', 'baz', 'foo']);
Array.from(set);
// [ "foo", "bar", "baz" ]

const map = new Map([[1, 2], [2, 4], [4, 8]]);
Array.from(map);
// [[1, 2], [2, 4], [4, 8]]

const mapper = new Map([['1', 'a'], ['2', 'b']]);
Array.from(mapper.values());
// ['a', 'b'];

Array.from(mapper.keys());
// ['1', '2'];
  • 实现伪数组转真数组
[...arguments];
Array.from(argumens);
Array.prototype.slice.call(arguments)
  • 实现数组去重
function unique (arr) {
  return Array.from(new Set(arr))
  // return [...new Set(arr)]
}
const test = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a'];
console.log(unique(test));


function unique(arr) {
    const map = new Map();
    const array = []; // 数组用于返回结果
    for (let i = 0; i < arr.length; i++) {
        if (!map.has(arr[i])) { // 如果有该key值
            array.push(arr[i]);
            map.set(arr[i], true);
        }
    }
    return array;
}

function unique(arr) {
    if (!Array.isArray(arr)) {
        console.log('type error!')
        return
    }
    const array = [];
    for (let i = 0; i < arr.length; i++) {
        if (!array.includes(arr[i])) { //includes 检测数组是否有某个值
            array.push(arr[i]);
        }
    }
    return array
}
  1. Array.of

创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型

Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]
  • 模拟实现
Array.of = function() {
    return Array.prototype.slice.call(arguments);
};