ES6重點(diǎn)新增特性及注意事項(xiàng)

一、let 和 const

1、塊級(jí)作用域
  • 先來舉個(gè)栗子
for(var i=0;i<=3;i++){ 
    setTimeout(function() {  
        console.log(i)  
    }, 10);
} 
  • 會(huì)輸出4個(gè)4,因?yàn)椋?/li>
  1. var定義的變量是全局的, 所以全局只有一個(gè)變量i.
  2. setTimeout是異步, 在下一輪事件循環(huán), 等到執(zhí)行的時(shí)候, 去找i變量的引用。所以函數(shù)找到了遍歷完后的i, 此時(shí)它已經(jīng)變成了4。
  • 如何修改可以使其輸出0,1,2,3?

方法一:調(diào)用立即執(zhí)行函數(shù),它會(huì)將每一次的作為參數(shù)傳入函數(shù)中保存下來。

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

方法二:替換成let, let引入了塊級(jí)作用域的概念, 創(chuàng)建setTimeout函數(shù)時(shí),變量i在作用域內(nèi)。對(duì)于循環(huán)的每個(gè)迭代,引用的i是i的不同實(shí)例

for(let i=0;i<=3;i++){ 
    setTimeout(function() {  
        console.log(i)  
    }, 10);
} 
2、變量提升的問題
console.log(i) // 1
var i = 1;

console.log(letI) // 報(bào)錯(cuò)
let letI = 2;
  • const就很簡(jiǎn)單了, 在let的基礎(chǔ)上, 不可被修改

二、箭頭函數(shù)

1、普通函數(shù)里的this是使用的時(shí)候決定的,箭頭函數(shù)里的this是定義的時(shí)候決定的
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
2、簡(jiǎn)寫箭頭函數(shù)
const arrowFn = (value) => Number(value);
  • 需要注意的是,如果需要返回一個(gè)空對(duì)象,正確的寫法是
const arrow = () => ({})
console.log(arrow()) // {}
  • 而不是
const arrow = () => {}
console.log(arrow()) // undefined
3、箭頭函數(shù)不能被用作構(gòu)造函數(shù)

構(gòu)造函數(shù)執(zhí)行的時(shí)候會(huì)改變this指新實(shí)例出來的對(duì)象.
箭頭函數(shù)this指向是定義的時(shí)候就決定的. 這兩者是沖突的。

const arrow = () => {} // 此時(shí)this指向widow
const newArrow = new Arrow() // 此時(shí)this指向newArrow

三、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) // 實(shí)例屬性
console.log(Test.getFormatName()) // 靜態(tài)屬性

四、模板字符串

const b = 'lubai'
const a  = `$ - xxxx`;
const c = `我是換行
我換行了!
我又換行了!
`;
const d = '換行\(zhòng)n換行' // 原本的寫法
  • 面試題:編寫render函數(shù), 實(shí)現(xiàn)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);  // 高階函數(shù)

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

// 在這里實(shí)現(xiàn)功能:
function render(template) {
    return function(context) {
        return template.replace(/\$\{(.*?)\}/g, (match, key) => context[key]); // 該函數(shù)的返回值將替換掉正則匹配到的結(jié)果
    }
}

五、解構(gòu)賦值

1、數(shù)組解構(gòu)
// 基礎(chǔ)類型解構(gòu)
let [a, b, c] = [1, 2, 3]
console.log(a, b, c) // 1, 2, 3

// 對(duì)象數(shù)組解構(gòu)
let [a, b, c] = [{name: '1'}, {name: '2'}, {name: '3'}]
console.log(a, b, c) // {name: '1'}, {name: '2'}, {name: '3'}

// ...解構(gòu)
let [head, ...tail] = [1, 2, 3, 4]
console.log(head, tail) // 1, [2, 3, 4]

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

// 解構(gòu)不成功為undefined
let [a, b, c] = [1]
console.log(a, b, c) // 1, undefined, undefined

// 解構(gòu)默認(rèn)賦值
let [a = 1, b = 2] = [3]
console.log(a, b) // 3, 2
2、對(duì)象解構(gòu)
// 對(duì)象屬性解構(gòu)
let { f1, f2 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2

// 可以不按照順序,這是數(shù)組解構(gòu)和對(duì)象解構(gòu)的區(qū)別之一
let { f2, f1 } = { f1: 'test1', f2: 'test2' }
console.log(f1, f2) // test1, test2

// 解構(gòu)對(duì)象重命名
let { f1: rename, f2 } = { f1: 'test1', f2: 'test2' }
console.log(rename, f2) // test1, test2

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

// 默認(rèn)值
let { f1 = 'test1', f2: rename = 'test2' } = { f1: 'current1', f2: 'current2'}
console.log(f1, rename) // current1, current2
3、解構(gòu)原理

針對(duì)可迭代對(duì)象的Iterator接口,通過遍歷器按順序獲取對(duì)應(yīng)的值進(jìn)行賦值.

Iterator

Iterator是一種接口,為各種不一樣的數(shù)據(jù)解構(gòu)提供統(tǒng)一的訪問機(jī)制。任何數(shù)據(jù)解構(gòu)只要有Iterator接口,就能通過遍歷操作,依次按順序處理數(shù)據(jù)結(jié)構(gòu)內(nèi)所有成員。ES6中的for of的語法相當(dāng)于遍歷器,會(huì)在遍歷數(shù)據(jù)結(jié)構(gòu)時(shí),自動(dòng)尋找Iterator接口。

  • 為各種數(shù)據(jù)解構(gòu)提供統(tǒng)一的訪問接口
  • 使得數(shù)據(jù)解構(gòu)能按次序排列處理
  • 可以使用ES6最新命令 for of進(jìn)行遍歷
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())
可迭代對(duì)象

可迭代對(duì)象是Iterator接口的實(shí)現(xiàn)。這是ECMAScript 2015的補(bǔ)充,它不是內(nèi)置或語法,而僅僅是協(xié)議。任何遵循該協(xié)議點(diǎn)對(duì)象都能成為可迭代對(duì)象??傻鷮?duì)象得有兩個(gè)協(xié)議:可迭代協(xié)議和迭代器協(xié)議。

  • 可迭代協(xié)議:對(duì)象必須實(shí)現(xiàn)iterator方法。即對(duì)象或其原型鏈上必須有一個(gè)名叫Symbol.iterator的屬性。該屬性的值為無參函數(shù),函數(shù)返回迭代器協(xié)議。

  • 迭代器協(xié)議:定義了標(biāo)準(zhǔn)的方式來產(chǎn)生一個(gè)有限或無限序列值。其要求必須實(shí)現(xiàn)一個(gè)next()方法,該方法返回對(duì)象有done(boolean)和value屬性。

實(shí)現(xiàn)一個(gè)可以for of遍歷的對(duì)象

通過以上可知,自定義數(shù)據(jù)結(jié)構(gòu),只要擁有Iterator接口,并將其部署到自己的Symbol.iterator屬性上,就可以成為可迭代對(duì)象,能被for of循環(huán)遍歷。

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、for in

遍歷數(shù)組時(shí),key為數(shù)組下標(biāo)字符串;遍歷對(duì)象,key為對(duì)象字段名。

let obj = {a: 'test1', b: 'test2'}
for (let key in obj) {
    console.log(key, obj[key])
}
  • for in 不僅會(huì)遍歷當(dāng)前對(duì)象,還包括原型鏈上的可枚舉屬性
2、for of

可迭代對(duì)象(包括 Array,Map,Set,String,TypedArray,arguments對(duì)象,NodeList對(duì)象)上創(chuàng)建一個(gè)迭代循環(huán),調(diào)用自定義迭代鉤子,并為每個(gè)不同屬性的值執(zhí)行語句。

let arr = [{age: 1}, {age: 5}, {age: 100}, {age: 34}]
for(let {age} of arr) {
    if (age > 10) {
        break // for of 允許中斷
    }
    console.log(age)
}
  • for of 僅遍歷當(dāng)前對(duì)象

七、Object

1、Object.keys

該方法返回一個(gè)給定對(duì)象的自身可枚舉屬性組成的數(shù)組。

const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [a, b]
  • 實(shí)現(xiàn)
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
}))
2、Object.values

該方法返回一個(gè)給定對(duì)象自身的所有可枚舉屬性值的數(shù)組。

const obj = { a: 1, b: 2 };
const keys = Object.keys(obj); // [1, 2]
  • 實(shí)現(xiàn)
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
}))
3、Object.entries

該方法返回一個(gè)給定對(duì)象自身可枚舉屬性的鍵值對(duì)數(shù)組。

const obj = { a: 1, b: 2 };
const keys = Object.entries(obj); // [ [ 'a', 1 ], [ 'b', 2 ] ]
  • 實(shí)現(xiàn)
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
}))
4、Object.getOwnPropertyNames

該方法返回一個(gè)數(shù)組,該數(shù)組對(duì)元素是 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"]
5、Object.getOwnPropertyDescriptor

對(duì)象對(duì)應(yīng)的屬性描述符, 是一個(gè)對(duì)象. 包含以下屬性:

  • configurable。 如果為false,則任何嘗試刪除目標(biāo)屬性或修改屬性特性(writable, configurable, enumerable)的行為將被無效化。所以通常屬性都有特性時(shí),可以把configurable設(shè)置為true即可。
  • writable 是否可寫。設(shè)置成 false,則任何對(duì)該屬性改寫的操作都無效(但不會(huì)報(bào)錯(cuò),嚴(yán)格模式下會(huì)報(bào)錯(cuò)),默認(rèn)false。
  • enumerable。是否能在for-in循環(huán)中遍歷出來或在Object.keys中列舉出來。
const object1 = {};
Object.defineProperty(object1, 'p1', {
  value: 'lubai',
  writable: false // 設(shè)置為不可寫
});

object1.p1 = 'not lubai';

console.log(object1.p1); // "lubai"
6、Object.defineProperty

直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性,并返回此對(duì)象。

const obj = {};
let val = undefined;
Object.defineProperty(obj, 'a', {
    set: function (value) {
        console.log(`${value} - xxxx`);
        val = value;
    },
    get: function () {
        return val;
    },
    configurable: true,
})

obj.a = 111;

console.log(obj.a)
Proxy

用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

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設(shè)置writable為false的對(duì)象, 就不能用Proxy了
const target = Object.defineProperties({}, {
    foo: {
        value: 123,
        writable: false,
        configurable: false
    },
});


const proxy = new Proxy(target, {
    get(target, propKey) {
        return 'abc';
    }
});

proxy.foo
Reflect
  • 將Object對(duì)象的一些明顯屬于語言內(nèi)部的方法(比如Object.defineProperty),放到Reflect對(duì)象上?,F(xiàn)階段,某些方法同時(shí)在Object和Reflect對(duì)象上部署,未來的新方法將只部署在Reflect對(duì)象上。也就是說,從Reflect對(duì)象上可以拿到語言內(nèi)部的方法
  • 讓Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。
  • Reflect對(duì)象的方法與Proxy對(duì)象的方法一一對(duì)應(yīng),只要是Proxy對(duì)象的方法,就能在Reflect對(duì)象上找到對(duì)應(yīng)的方法。這就讓Proxy對(duì)象可以方便地調(diào)用對(duì)應(yīng)的Reflect方法,完成默認(rèn)行為,作為修改行為的基礎(chǔ)。也就是說,不管Proxy怎么修改默認(rèn)行為,你總可以在Reflect上獲取默認(rèn)行為。

7、Object.assign

淺拷貝, 類似于 { ...a, ...b };

function shallowClone(source) {
    const target = {};
    for (const i in source) {
        if (source.hasOwnProperty(i)) {
            target[i] = source[i];
        }
    }

    return target;
}

const a = {
    b: 1,
    c: {
        d: 111
    }
}

const b = shallowClone(a);


b.b = 2222;

b.c.d = 333;

console.log(b)
console.log(a)

8、Object.create()

Object.create()方法創(chuàng)建一個(gè)新的對(duì)象,并以方法的第一個(gè)參數(shù)作為新對(duì)象的proto屬性的值(根據(jù)已有的對(duì)象作為原型,創(chuàng)建新的對(duì)象。)

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) // 創(chuàng)建一個(gè)非常干凈的對(duì)象, 沒有任何原型鏈上的屬性

Object.create()方法還有第二個(gè)可選參數(shù),是一個(gè)對(duì)象,對(duì)象的每個(gè)屬性都會(huì)作為新對(duì)象的自身屬性,對(duì)象的屬性值以descriptor(Object.getOwnPropertyDescriptor(obj, 'key'))的形式出現(xiàn),且enumerable默認(rèn)為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)

9、Object.is()

判斷兩個(gè)值是否為同一個(gè)值。

const a = {
    name: 1
};
const b = a;
console.log(Object.is(a, b)) // true

console.log(Object.is({}, {})) // false

八、Promise

  • promise.all實(shí)現(xiàn)
function PromiseAll(promiseArray) {
    return new Promise(function (resolve, reject) {
        //判斷參數(shù)類型
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))
        }
        let counter = 0;
        let promiseNum = promiseArray.length;
        let resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            // 3. 這里為什么要用Promise.resolve?
            Promise.resolve(promiseArray[i]).then((value) => {
                counter++;
                resolvedArray[i] = value; // 2. 這里直接Push, 而不是用索引賦值, 有問題嗎
                if (counter == promiseNum) { // 1. 這里如果不計(jì)算counter++, 直接判斷resolvedArr.length === promiseNum, 會(huì)有問題嗎?
                    // 4. 如果不在.then里面, 而在外層判斷, 可以嗎?
                    resolve(resolvedArray)
                }
            }).catch(e => reject(e));
        }
    })
}

// 測(cè)試
const pro1 = new Promise((res, rej) => {
    setTimeout(() => {
        res('1')
    }, 1000)
})
const pro2 = new Promise((res, rej) => {
    setTimeout(() => {
        res('2')
    }, 2000)
})
const pro3 = new Promise((res, rej) => {
    setTimeout(() => {
        res('3')
    }, 3000)
})

const proAll = PromiseAll([pro1, pro2, pro3])
    .then(res =>
        console.log(res) // 3秒之后打印 ["1", "2", "3"]
    )
    .catch((e) => {
        console.log(e)
    })
  • 實(shí)現(xiàn)Promise.allSeettled,返回所有promise的狀態(tài)和結(jié)果
function PromiseAllSettled(promiseArray) {
    return new Promise(function (resolve, reject) {
        //判斷參數(shù)類型
        if (!Array.isArray(promiseArray)) {
            return reject(new TypeError('arguments muse be an array'))
        }
        let counter = 0;
        const promiseNum = promiseArray.length;
        const resolvedArray = [];
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promiseArray[i])
                .then((value) => {
                    resolvedArray[i] = {
                        status: 'fulfilled',
                        value
                    };

                })
                .catch(reason => {
                    resolvedArray[i] = {
                        status: 'rejected',
                        reason
                    };
                })
                .finally(() => {
                    counter++;
                    if (counter == promiseNum) {
                        resolve(resolvedArray)
                    }
                })
        }
    })
}

九、數(shù)組

1、Array.flat

按照一個(gè)可指定的深度遞歸遍歷數(shù)組,并將所有元素與遍歷到的子數(shù)組中的元素合并為一個(gè)新數(shù)組返回

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,可展開任意深度的嵌套數(shù)組
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]
  • 模擬實(shí)現(xiàn)Array.flat
// 使用 reduce、concat 和遞歸展開無限多層嵌套的數(shù)組
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
}

// 如果數(shù)組元素都是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;
}
2、Array.includes

判斷一個(gè)數(shù)組是否包含一個(gè)指定的值,根據(jù)情況,如果包含則返回 true,否則返回false。它有兩個(gè)參

  • valueToFind
    需要查找的元素值。

  • fromIndex 可選
    從fromIndex 索引處開始查找 valueToFind。如果為負(fù)值,則按升序從 array.length + fromIndex 的索引開始搜 (即使從末尾開始往前跳 fromIndex 的絕對(duì)值個(gè)索引,然后往
    后搜尋)。默認(rèn)為 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 大于等于數(shù)組長(zhǎng)度
var arr = ['a', 'b', 'c'];

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

// 計(jì)算出的索引小于 0
var arr = ['a', 'b', 'c'];

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

返回?cái)?shù)組中滿足提供的測(cè)試函數(shù)的第一個(gè)元素的值。否則返回 undefined。
callback
在數(shù)組每一項(xiàng)上執(zhí)行的函數(shù),接收 3 個(gè)參數(shù):

  • element
    當(dāng)前遍歷到的元素。
  • index可選
    當(dāng)前遍歷到的索引。
  • array可選
    數(shù)組本身。
    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));
    
4、Array.from

從一個(gè)類似數(shù)組或可迭代對(duì)象創(chuàng)建一個(gè)新的,淺拷貝的數(shù)組實(shí)例。

  • arrayLike
    想要轉(zhuǎn)換成數(shù)組的偽數(shù)組對(duì)象或可迭代對(duì)象。
  • mapFn 可選
    如果指定了該參數(shù),新數(shù)組中的每個(gè)元素會(huì)執(zhí)行該回調(diào)函數(shù)。

可以通過以下方式來創(chuàng)建數(shù)組對(duì)象:

  • 偽數(shù)組對(duì)象(擁有一個(gè) length 屬性和若干索引屬性的任意對(duì)象)
  • 可迭代對(duì)象(可以獲取對(duì)象中的元素,如 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'];
實(shí)現(xiàn)數(shù)組去重
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 = []; // 數(shù)組用于返回結(jié)果
    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 檢測(cè)數(shù)組是否有某個(gè)值
            array.push(arr[i]);
        }
    }
    return array
}
5、Array.of

創(chuàng)建一個(gè)具有可變數(shù)量參數(shù)的新數(shù)組實(shí)例,而不考慮參數(shù)的數(shù)量或類型

Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]
  • 模擬實(shí)現(xiàn)
Array.of = function() {
    return Array.prototype.slice.call(arguments);
};

十、babel

Babel 是一個(gè)工具鏈,主要用于將 ECMAScript 2015+ 版本的代碼轉(zhuǎn)換為向后兼容的 JavaScript 語法,以便能夠運(yùn)行在當(dāng)前和舊版本的瀏覽器或其他環(huán)境中.
Babel提供了插件化的功能, 一切功能都可以以插件來實(shí)現(xiàn). 方便使用和棄用.

抽象語法樹

這個(gè)處理過程中的每一步都涉及到創(chuàng)建或是操作抽象語法樹,亦稱 AST。
這樣一段代碼, 會(huì)被轉(zhuǎn)換成什么呢?

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"
        }
      }
    }]
  }
}

每一層都有相同的結(jié)構(gòu)

{
  type: "FunctionDeclaration",
  id: {...},
  params: [...],
  body: {...}
}
{
  type: "Identifier",
  name: ...
}
{
  type: "BinaryExpression",
  operator: ...,
  left: {...},
  right: {...}
}
  • 這樣的每一層結(jié)構(gòu)也被叫做 節(jié)點(diǎn)(Node)。 一個(gè) AST 可以由單一的節(jié)點(diǎn)或是成百上千個(gè)節(jié)點(diǎn)構(gòu)成。 它們組合在一起可以描述用于靜態(tài)分析的程序語法。

  • 字符串形式的 type 字段表示節(jié)點(diǎn)的類型(如: "FunctionDeclaration","Identifier",或 "BinaryExpression")。 每一種類型的節(jié)點(diǎn)定義了一些附加屬性用來進(jìn)一步描述該節(jié)點(diǎn)類型。

  • Babel 還為每個(gè)節(jié)點(diǎn)額外生成了一些屬性,用于描述該節(jié)點(diǎn)在原始代碼中的位置。比如 start end

Babel 的處理步驟

Babel 的三個(gè)主要處理步驟分別是: 解析(parse),轉(zhuǎn)換(transform),生成(generate)。

1. 解析

解析步驟接收代碼并輸出 AST。

  • 詞法分析

詞法分析階段把字符串形式的代碼轉(zhuǎn)換為 令牌(tokens) 流。.

你可以把令牌看作是一個(gè)扁平的語法片段數(shù)組:

n * n

轉(zhuǎ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: { ... } },
...
]

  • 語法分析
    語法分析階段會(huì)把一個(gè)令牌流轉(zhuǎn)換成 AST 的形式。 這個(gè)階段會(huì)使用令牌中的信息把它們轉(zhuǎn)換成一個(gè) AST 的表述結(jié)構(gòu),這樣更易于后續(xù)的操作。
2. 轉(zhuǎn)換

轉(zhuǎn)換步驟接收 AST 并對(duì)其進(jìn)行遍歷,在此過程中對(duì)節(jié)點(diǎn)進(jìn)行添加、更新及移除等操作。 這是 Babel 或是其他編譯器中最復(fù)雜的過程 同時(shí)也是插件將要介入工作的部分。

3. 生成

代碼生成步驟把最終(經(jīng)過一系列轉(zhuǎn)換之后)的 AST 轉(zhuǎn)換成字符串形式的代碼,同時(shí)還會(huì)創(chuàng)建源碼映射(source maps)。.

代碼生成其實(shí)很簡(jiǎn)單:深度優(yōu)先遍歷整個(gè) AST,然后構(gòu)建可以表示轉(zhuǎn)換后代碼的字符串。

簡(jiǎn)單寫一個(gè)babel插件
1. 一個(gè)插件就是一個(gè)函數(shù)
export default function(babel) {
}
// babel里我們主要用到types屬性

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

Babel Types模塊擁有每一個(gè)單一類型節(jié)點(diǎn)的定義,包括節(jié)點(diǎn)包含哪些屬性,什么是合法值,如何構(gòu)建節(jié)點(diǎn)、遍歷節(jié)點(diǎn),以及節(jié)點(diǎn)的別名等信息。

單一節(jié)點(diǎn)類型的定義形式如下:

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. 返回一個(gè)對(duì)象

visitor 屬性是這個(gè)插件的主要訪問者。visitor中的每個(gè)函數(shù)都接收兩個(gè)參數(shù) state 和 path.

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

AST 通常會(huì)有許多節(jié)點(diǎn),那么節(jié)點(diǎn)直接如何相互關(guān)聯(lián)呢? 我們可以使用一個(gè)可操作和訪問的巨大可變對(duì)象表示節(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系,或者也可以用Paths(路徑)來簡(jiǎn)化這件事情。

Path 是表示兩個(gè)節(jié)點(diǎn)之間連接的對(duì)象。

將子節(jié)點(diǎn) Identifier 表示為一個(gè)路徑(Path)的話,看起來是這樣的:

{
  "parent": {
    "type": "FunctionDeclaration",
    "id": {...},
    ....
  },
  "node": {
    "type": "Identifier",
    "name": "square"
  }
}
3. 創(chuàng)建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
}) {
    // 插件內(nèi)容
    return {
        visitor: {
            // 接收兩個(gè)參數(shù)path, state
            VariableDeclaration(path, state) {
                // 找到AST節(jié)點(diǎn)
                const node = path.node;
                // 判斷節(jié)點(diǎn)類型 是否是變量節(jié)點(diǎn), 申明方式是const
                if (t.isVariableDeclaration(node, {
                        kind: "const"
                    })) {
                    // 將const 聲明編譯為let
                    node.kind = "let";
                    // var b = 1 的AST節(jié)點(diǎn)
                    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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.let和const關(guān)鍵字let: 變量不能重復(fù)聲明。 塊級(jí)作用域。在if-else語句、while循環(huán)、for...
    coder勇閱讀 610評(píng)論 0 0
  • 原本想稍微整理一下 ES 新特性,沒想到花了相當(dāng)多的時(shí)間,本文也巨長(zhǎng),依然推薦使用 簡(jiǎn)悅[https://gith...
    401閱讀 2,015評(píng)論 0 5
  • ES6改動(dòng)很大,可以簡(jiǎn)單分為四類1、解決原有語法的缺陷和不足例如:let,const2、對(duì)原有語法進(jìn)行增強(qiáng)解構(gòu)、擴(kuò)...
    少_游閱讀 8,427評(píng)論 0 12
  • 一、let let 關(guān)鍵字用來聲明變量,使用 let 聲明的變量有幾個(gè)特點(diǎn): 不允許重復(fù)聲明 塊兒級(jí)作用域 不存在...
    風(fēng)時(shí)摩羯閱讀 256評(píng)論 0 0
  • 以下內(nèi)容是我在學(xué)習(xí)和研究ES6時(shí),對(duì)ES6的特性、重點(diǎn)和注意事項(xiàng)的提取、精練和總結(jié),可以做為ES6特性的字典;在本...
    科研者閱讀 3,266評(píng)論 2 9

友情鏈接更多精彩內(nèi)容