一、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>
- var定義的變量是全局的, 所以全局只有一個(gè)變量i.
- 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);