[TOC]
### 手寫 instanceof 方法
instanceof 運算符用于判斷構造函數的 prototype 屬性是否出現在對象的原型鏈中的任何位置。
實現步驟:
1. 首先獲取類型的原型
2. 然后獲得對象的原型
3. 然后一直循環判斷對象的原型是否等于類型的原型,直到對象原型為 `null`,因為原型鏈最終為 `null`
具體實現:
```
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 獲取對象的原型
prototype = right.prototype; // 獲取構造函數的 prototype 對象
// 判斷構造函數的 prototype 對象是否在對象的原型鏈上
while (true) {
if (!proto) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
```
### 手寫 new 操作符
在調用 `new` 的過程中會發生以上四件事情:
1. 首先創建了一個新的空對象
2. 設置原型,將對象的原型設置為函數的 prototype 對象。
3. 讓函數的 this 指向這個對象,執行構造函數的代碼(為這個新對象添加屬性)
4. 判斷函數的返回值類型,如果是值類型,返回創建的對象。如果是引用類型,就返回這個引用類型的對象。
```
function objectFactory() {
let newObject = null;
let constructor = Array.prototype.shift.call(arguments);
let result = null;
// 判斷參數是否是一個函數
if (typeof constructor !== "function") {
console.error("type error");
return;
}
// 新建一個空對象,對象的原型為構造函數的 prototype 對象
newObject = Object.create(constructor.prototype);
// 將 this 指向新建對象,并執行函數
result = constructor.apply(newObject, arguments);
// 判斷返回對象
let flag = result && (typeof result === "object" || typeof result === "function");
// 判斷返回結果
return flag ? result : newObject;
}
// 使用方法
objectFactory(構造函數, 初始化參數);
```
### 手寫Promise
```
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保存初始化狀態
var self = this;
// 初始化狀態
this.state = PENDING;
// 用于保存 resolve 或者 rejected 傳入的值
this.value = null;
// 用于保存 resolve 的回調函數
this.resolvedCallbacks = [];
// 用于保存 reject 的回調函數
this.rejectedCallbacks = [];
// 狀態轉變為 resolved 方法
function resolve(value) {
// 判斷傳入元素是否為 Promise 值,如果是,則狀態改變必須等待前一個狀態改變后再進行改變
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保證代碼的執行順序為本輪事件循環的末尾
setTimeout(() => {
// 只有狀態為 pending 時才能轉變,
if (self.state === PENDING) {
// 修改狀態
self.state = RESOLVED;
// 設置傳入的值
self.value = value;
// 執行回調函數
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 狀態轉變為 rejected 方法
function reject(value) {
// 保證代碼的執行順序為本輪事件循環的末尾
setTimeout(() => {
// 只有狀態為 pending 時才能轉變
if (self.state === PENDING) {
// 修改狀態
self.state = REJECTED;
// 設置傳入的值
self.value = value;
// 執行回調函數
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 將兩個方法傳入函數執行
try {
fn(resolve, reject);
} catch (e) {
// 遇到錯誤時,捕獲錯誤,執行 reject 函數
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判斷兩個參數是否為函數類型,因為這兩個參數是可選參數
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {
throw error;
};
// 如果是等待狀態,則將函數加入對應列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果狀態已經凝固,則直接執行對應狀態的函數
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
```
### 手寫 Promise.then
`then` 方法返回一個新的 `promise` 實例,為了在 `promise` 狀態發生變化時(`resolve` / `reject` 被調用時)再執行 `then` 里的函數,我們使用一個 `callbacks` 數組先把傳給then的函數暫存起來,等狀態改變時再調用。
**那么,怎么保證后一個** `**then**` **里的方法在前一個** `**then**`**(可能是異步)結束之后再執行呢?**
我們可以將傳給 `then` 的函數和新 `promise` 的 `resolve` 一起 `push` 到前一個 `promise` 的 `callbacks` 數組中,達到承前啟后的效果:
* 承前:當前一個 `promise` 完成后,調用其 `resolve` 變更狀態,在這個 `resolve` 里會依次調用 `callbacks` 里的回調,這樣就執行了 `then` 里的方法了
* 啟后:上一步中,當 `then` 里的方法執行完成后,返回一個結果,如果這個結果是個簡單的值,就直接調用新 `promise` 的 `resolve`,讓其狀態變更,這又會依次調用新 `promise` 的 `callbacks` 數組里的方法,循環往復。。如果返回的結果是個 `promise`,則需要等它完成之后再觸發新 `promise` 的 `resolve`,所以可以在其結果的 `then` 里調用新 `promise` 的 `resolve`。
```
then(onFulfilled, onReject){
// 保存前一個promise的this
const self = this;
return new MyPromise((resolve, reject) => {
// 封裝前一個promise成功時執行的函數
let fulfilled = () => {
try{
const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) : resolve(result); //啟后
}catch(err){
reject(err)
}
}
// 封裝前一個promise失敗時執行的函數
let rejected = () => {
try{
const result = onReject(self.reason);
return result instanceof MyPromise? result.then(resolve, reject) : reject(result);
}catch(err){
reject(err)
}
}
switch(self.status){
case PENDING:
self.onFulfilledCallbacks.push(fulfilled);
self.onRejectedCallbacks.push(rejected);
break;
case FULFILLED:
fulfilled();
break;
case REJECT:
rejected();
break;
}
})
}
```
**注意:**
* 連續多個 `then` 里的回調方法是同步注冊的,但注冊到了不同的 `callbacks` 數組中,因為每次 `then` 都返回新的 `promise` 實例(參考上面的例子和圖)
* 注冊完成后開始執行構造函數中的異步事件,異步完成之后依次調用 `callbacks` 數組中提前注冊的回調
### 手寫 Promise.all
**核心思路**
1. 接收一個 Promise 實例的數組或具有 Iterator 接口的對象作為參數
2. 這個方法返回一個新的 promise 對象,
3. 遍歷傳入的參數,用Promise.resolve()將參數"包一層",使其變成一個promise對象
4. 參數所有回調成功才是成功,返回值數組與參數順序一致
5. 參數數組其中一個失敗,則觸發失敗狀態,第一個觸發失敗的 Promise 錯誤信息作為 Promise.all 的錯誤信息。
**實現代碼**
一般來說,Promise.all 用來處理多個并發請求,也是為了頁面數據構造的方便,將一個頁面所用到的在不同接口的數據一起請求過來,不過,如果其中一個接口失敗了,多個請求也就失敗了,頁面可能啥也出不來,這就看當前頁面的耦合程度了。
```
function promiseAll(promises) {
return new Promise(function(resolve, reject) {
if(!Array.isArray(promises)){
throw new TypeError(`argument must be a array`)
}
var resolvedCounter = 0;
var promiseNum = promises.length;
var resolvedResult = [];
for (let i = 0; i < promiseNum; i++) {
Promise.resolve(promises[i]).then(value=>{
resolvedCounter++;
resolvedResult[i] = value;
if (resolvedCounter == promiseNum) {
return resolve(resolvedResult)
}
},error=>{
return reject(error)
})
}
})
}
// test
let p1 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(1)
}, 1000)
})
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(2)
}, 2000)
})
let p3 = new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(3)
}, 3000)
})
promiseAll([p3, p1, p2]).then(res => {
console.log(res) // [3, 1, 2]
})
```
### 手寫 Promise.race
該方法的參數是 Promise 實例數組, 然后其 then 注冊的回調方法是數組中的某一個 Promise 的狀態變為 fulfilled 的時候就執行. 因為 Promise 的狀態**只能改變一次**, 那么我們只需要把 Promise.race 中產生的 Promise 對象的 resolve 方法, 注入到數組中的每一個 Promise 實例中的回調函數中即可。
```
Promise.race = function (args) {
return new Promise((resolve, reject) => {
for (let i = 0, len = args.length; i < len; i++) {
args[i].then(resolve, reject)
}
})
}
```
### 手寫防抖函數
函數防抖是指在事件被觸發 n 秒后再執行回調,如果在這 n 秒內事件又被觸發,則重新計時。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發送多次請求。也可以[點擊](http://www.hmoore.net/vvmily_king/vvmily/2331774)查看以往。
```
// 函數防抖的實現
function debounce(fn, wait) {
let timer = null;
return function() {
let context = this,
args = arguments;
// 如果此時存在定時器的話,則取消之前的定時器重新記時
if (timer) {
clearTimeout(timer);
timer = null;
}
// 設置定時器,使事件間隔指定事件后執行
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
```
### 手寫節流函數
函數節流是指規定一個單位時間,在這個單位時間內,只能有一次觸發事件的回調函數執行,如果在同一個單位時間內某事件被觸發多次,只有一次能生效。節流可以使用在 scroll 函數的事件監聽上,通過事件節流來降低事件調用的頻率。也可以[點擊](http://www.hmoore.net/vvmily_king/vvmily/2331774)查看以往。
```
// 函數節流的實現;
function throttle(fn, delay) {
let curTime = Date.now();
return function() {
let context = this,
args = arguments,
nowTime = Date.now();
// 如果兩次時間間隔超過了指定時間,則執行函數。
if (nowTime - curTime >= delay) {
curTime = Date.now();
return fn.apply(context, args);
}
};
}
```
### 手寫 call 函數
call 函數的實現步驟:
1. 判斷調用對象是否為函數,即使我們是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。
2. 判斷傳入上下文對象是否存在,如果不存在,則設置為 window 。
3. 處理傳入的參數,截取第一個參數后的所有參數。
4. 將函數作為上下文對象的一個屬性。
5. 使用上下文對象來調用這個方法,并保存返回結果。
6. 刪除剛才新增的屬性。
7. 返回結果。
```
// call函數實現
Function.prototype.myCall = function(context) {
// 判斷調用對象
if (typeof this !== "function") {
console.error("type error");
}
// 獲取參數
let args = [...arguments].slice(1),
result = null;
// 判斷 context 是否傳入,如果未傳入則設置為 window
context = context || window;
// 將調用函數設為對象的方法
context.fn = this;
// 調用函數
result = context.fn(...args);
// 將屬性刪除
delete context.fn;
return result;
};
```
### 手寫 apply 函數
apply 函數的實現步驟:
1. 判斷調用對象是否為函數,即使我們是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。
2. 判斷傳入上下文對象是否存在,如果不存在,則設置為 window 。
3. 將函數作為上下文對象的一個屬性。
4. 判斷參數值是否傳入
5. 使用上下文對象來調用這個方法,并保存返回結果。
6. 刪除剛才新增的屬性
7. 返回結果
```
// apply 函數實現
Function.prototype.myApply = function(context) {
// 判斷調用對象是否為函數
if (typeof this !== "function") {
throw new TypeError("Error");
}
let result = null;
// 判斷 context 是否存在,如果未傳入則為 window
context = context || window;
// 將函數設為對象的方法
context.fn = this;
// 調用方法
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
// 將屬性刪除
delete context.fn;
return result;
};
```
### 手寫 bind 函數
bind 函數的實現步驟:
1. 判斷調用對象是否為函數,即使我們是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。
2. 保存當前函數的引用,獲取其余傳入參數值。
3. 創建一個函數返回
4. 函數內部使用 apply 來綁定函數調用,需要判斷函數作為構造函數的情況,這個時候需要傳入當前函數的 this 給 apply 調用,其余情況都傳入指定的上下文對象。
```
// bind 函數實現
Function.prototype.myBind = function(context) {
// 判斷調用對象是否為函數
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 獲取參數
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根據調用方式,傳入不同綁定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
```
### 實現AJAX請求
AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。
創建AJAX請求的步驟:
* **創建一個 XMLHttpRequest 對象。**
* 在這個對象上**使用 open 方法創建一個 HTTP 請求**,open 方法所需要的參數是請求的方法、請求的地址、是否異步和用戶的認證信息。
* 在發起請求前,可以為這個對象**添加一些信息和監聽函數**。比如說可以通過 setRequestHeader 方法來為請求添加頭信息。還可以為這個對象添加一個狀態監聽函數。一個 XMLHttpRequest 對象一共有 5 個狀態,當它的狀態變化時會觸發onreadystatechange 事件,可以通過設置監聽函數,來處理請求成功后的結果。當對象的 readyState 變為 4 的時候,代表服務器返回的數據接收完成,這個時候可以通過判斷請求的狀態,如果狀態是 2xx 或者 304 的話則代表返回正常。這個時候就可以通過 response 中的數據來對頁面進行更新了。
* 當對象的屬性和監聽函數設置完成后,最后調**用 sent 方法來向服務器發起請求**,可以傳入參數作為發送的數據體。
```
const SERVER_URL = "/server";
let xhr = new XMLHttpRequest();
// 創建 Http 請求
xhr.open("GET", SERVER_URL, true);
// 設置狀態監聽函數
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 當請求成功時
if (this.status === 200) {
handle(this.response);
} else {
console.error(this.statusText);
}
};
// 設置請求失敗時的監聽函數
xhr.onerror = function() {
console.error(this.statusText);
};
// 設置請求頭信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");
// 發送 Http 請求
xhr.send(null);
```
### 實現淺拷貝
淺拷貝是指,一個新的對象對原始對象的屬性值進行精確地拷貝,如果拷貝的是基本數據類型,拷貝的就是基本數據類型的值,如果是引用數據類型,拷貝的就是內存地址。如果其中一個對象的引用內存地址發生改變,另一個對象也會發生變化。
1. `Object.assign()`:是ES6中對象的拷貝方法,接受的第一個參數是目標對象,其余參數是源對象,用法:`Object.assign(target, source_1, ···)`,該方法可以實現淺拷貝,也可以實現一維對象的深拷貝。
2. 擴展運算符:使用擴展運算符可以在構造字面量對象的時候,進行屬性的拷貝。語法:`let cloneObj = { ...obj };`。
3. **Array.prototype.slice**:`arr.slice()`。
4. **Array.prototype.concat**:`arr.concat()`。
5. 手寫淺拷貝:
```
// 淺拷貝的實現;
function shallowCopy(object) {
// 只拷貝對象
if (!object || typeof object !== "object") return;
// 根據 object 的類型判斷是新建一個數組還是對象
let newObject = Array.isArray(object) ? [] : {};
// 遍歷 object,并且判斷是 object 的屬性才拷貝
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key];
}
}
return newObject;
}
```
### 實現深拷貝
* **淺拷貝:** 淺拷貝指的是將一個對象的屬性值復制到另一個對象,如果有的屬性的值為引用類型的話,那么會將這個引用的地址復制給對象,因此兩個對象會有同一個引用類型的引用。淺拷貝可以使用 ?Object.assign 和展開運算符來實現。
* **深拷貝:** 深拷貝相對淺拷貝而言,如果遇到屬性值為引用類型的時候,它新建一個引用類型并將對應的值復制給它,因此對象獲得的一個新的引用類型而不是一個原有類型的引用。深拷貝對于一些對象可以使用 JSON 的兩個函數來實現,但是由于 JSON 的對象格式比 js 的對象格式更加嚴格,所以如果屬性值里邊出現函數或者 Symbol 類型的值時,會轉換失敗。
1. JSON.stringify()
* `JSON.parse(JSON.stringify(obj))`是目前比較常用的深拷貝方法之一,它的原理就是利用`JSON.stringify` 將`js`對象序列化(JSON字符串),再使用`JSON.parse`來反序列化(還原)`js`對象。
* 這個方法可以簡單粗暴的實現深拷貝,但是還存在問題,拷貝的對象中如果有函數,undefined,symbol,當使用過`JSON.stringify()`進行處理之后,都會消失。
```
let obj1 = { a: 0,
b: {
c: 0
}
};
let obj2 = JSON.parse(JSON.stringify(obj1));
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
```
2. 手寫實現深拷貝函數
```
// 深拷貝的實現
function deepCopy(object) {
if (!object || typeof object !== "object") return;
let newObject = Array.isArray(object) ? [] : {};
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] =
typeof object[key] === "object" ? deepCopy(object[key]) : object[key];
}
}
return newObject;
}
```
### 實現數組的扁平化
**遞歸實現**
* 普通的遞歸思路很容易理解,就是通過循環遞歸的方式,一項一項地去遍歷,如果每一項還是一個數組,那么就繼續往下遍歷,利用遞歸程序的方法,來實現數組的每一項的連接:
```
let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]
```
**reduce 函數迭代**
* 從上面普通的遞歸函數中可以看出,其實就是對數組的每一項進行處理,那么其實也可以用reduce 來實現數組的拼接,從而簡化第一種方法的代碼,改造后的代碼如下所示:
```
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(prev, next){
return prev.concat(Array.isArray(next) ? flatten(next) : next)
}, [])
}
console.log(flatten(arr));// [1, 2, 3, 4,5]
```
**擴展運算符實現**
* 這個方法的實現,采用了擴展運算符和 some 的方法,兩者共同使用,達到數組扁平化的目的:
```
let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]
```
### 實現數組去重
給定某無序數組,要求去除數組中的重復數字并且返回新的無重復數組。(方法很多,就不一一列舉了)
* ES6方法(使用數據結構集合):
```
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]
```
* ES5方法:使用map存儲不重復的數字
```
const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];
uniqueArray(array); // [1, 2, 3, 5, 9, 8]
function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}
```
### 大數相加
如果想要對一個超大的整數(`> Number.MAX_SAFE_INTEGER`)進行加法運算,但是又想輸出一般形式,那么使用 + 是無法達到的,一旦數字超過 `Number.MAX_SAFE_INTEGER` 數字會被立即轉換為科學計數法,并且數字精度相比以前將會有誤差。
實現一個算法進行大數的相加:
```
function sumBigNumber(a, b) {
let res = '';
let temp = 0;
a = a.split('');
b = b.split('');
while (a.length || b.length || temp) {
temp += ~~a.pop() + ~~b.pop();
res = (temp % 10) + res;
temp = temp > 9
}
return res.replace(/^0+/, '');
}
```
其主要的思路如下:
* 首先用字符串的方式來保存大數,這樣數字在數學表示上就不會發生變化
* 初始化res,temp來保存中間的計算結果,并將兩個字符串轉化為數組,以便進行每一位的加法運算
* 將兩個數組的對應的位進行相加,兩個數相加的結果可能大于10,所以可能要僅為,對10進行取余操作,將結果保存在當前位
* 判斷當前位是否大于9,也就是是否會進位,若是則將temp賦值為true,因為在加法運算中,true會自動隱式轉化為1,以便于下一次相加
* 重復上述操作,直至計算結束
### 大數相乘
```
function multiplyBigNum(num1, num2) {
//判斷輸入是不是數字
if (isNaN(num1) || isNaN(num2)) return "";
num1 = num1 + ""
num2 = num2 + ""
let len1 = num1.length,
len2 = num2.length;
let pos = [];
//j放外面,先固定被乘數的一位,分別去乘乘數的每一位,更符合豎式演算法
for (let j = len2 - 1; j >= 0; j--) {
for (let i = len1 - 1; i >= 0; i--) {
//兩個個位數相乘,最多產生兩位數,index1代表十位,index2代表個位
let index1 = i + j,
index2 = i + j + 1;
//兩個個位數乘積加上當前位置個位已累積的數字,會產生進位,比如08 + 7 = 15,產生了進位1
let mul = num1[i] * num2[j] + (pos[index2] || 0);
//mul包含新計算的十位,加上原有的十位就是最新的十位
pos[index1] = Math.floor(mul / 10) + (pos[index1] || 0);
//mul的個位就是最新的個位
pos[index2] = mul % 10;
}
}
//去掉前置0
let result = pos.join("").replace(/^0+/, "");
return result - 0 || '0';
}
```
### 實現 add(1)(2)(3)
函數柯里化概念: 柯里化(Currying)是把接受多個參數的函數轉變為接受一個單一參數的函數,并且返回接受余下的參數且返回結果的新函數的技術。
```
function add (a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}
console.log(add(1)(2)(3)); // 6
```
### 實現類數組轉化為數組
類數組轉換為數組的方法有這樣幾種:
* 通過 call 調用數組的 slice 方法來實現轉換
```
Array.prototype.slice.call(arrayLike);
```
* 通過 call 調用數組的 splice 方法來實現轉換
```
Array.prototype.splice.call(arrayLike, 0);
```
* 通過 apply 調用數組的 concat 方法來實現轉換
```
Array.prototype.concat.apply([], arrayLike);
```
* 通過 Array.from 方法來實現轉換
```
Array.from(arrayLike);
```
### 將js對象轉化為樹形結構
```
// 轉換前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 轉換為:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 1,
name: 'div'
}]
}
}]
```
代碼實現:
```
function jsonToTree(data) {
// 初始化結果數組,并判斷輸入數據的格式
let result = []
if(!Array.isArray(data)) {
return result
}
// 使用map,將當前對象的id與當前對象對應存儲起來
let map = {};
data.forEach(item => {
map[item.id] = item;
});
//
data.forEach(item => {
let parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
return result;
}
```
### 實現prototype繼承
所謂的原型鏈繼承就是讓新實例的原型等于父類的實例:
```
//父方法
function SupperFunction(flag1){
this.flag1 = flag1;
}
//子方法
function SubFunction(flag2){
this.flag2 = flag2;
}
//父實例
var superInstance = new SupperFunction(true);
//子繼承父
SubFunction.prototype = superInstance;
//子實例
var subInstance = new SubFunction(false);
//子調用自己和父的屬性
subInstance.flag1; // true
subInstance.flag2; // false
```
### 實現雙向數據綁定
```
let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 數據劫持
Object.defineProperty(obj, 'text', {
configurable: true,
enumerable: true,
get() {
console.log('獲取數據了')
},
set(newVal) {
console.log('數據更新了')
input.value = newVal
span.innerHTML = newVal
}
})
// 輸入監聽
input.addEventListener('keyup', function(e) {
obj.text = e.target.value
})
```
### 實現斐波那契數列
```
// 遞歸
function fn (n){
if(n==0) return 0
if(n==1) return 1
return fn(n-2)+fn(n-1)
}
// 優化
function fibonacci2(n) {
const arr = [1, 1, 2];
const arrLen = arr.length;
if (n <= arrLen) {
return arr[n];
}
for (let i = arrLen; i < n; i++) {
arr.push(arr[i - 1] + arr[ i - 2]);
}
return arr[arr.length - 1];
}
// 非遞歸
function fn(n) {
let pre1 = 1;
let pre2 = 1;
let current = 2;
if (n <= 2) {
return current;
}
for (let i = 2; i < n; i++) {
pre1 = pre2;
pre2 = current;
current = pre1 + pre2;
}
return current;
}
```
### 使用 setTimeout 實現 setInterval
setInterval 的作用是每隔一段指定時間執行一個函數,但是這個執行不是真的到了時間立即執行,它真正的作用是每隔一段時間將事件加入事件隊列中去,只有當當前的執行棧為空的時候,才能去從事件隊列中取出事件執行。所以可能會出現這樣的情況,就是當前執行棧執行的時間很長,導致事件隊列里邊積累多個定時器加入的事件,當執行棧結束的時候,這些事件會依次執行,因此就不能到間隔一段時間執行的效果。
針對 setInterval 的這個缺點,我們可以使用 setTimeout 遞歸調用來模擬 setInterval,這樣我們就確保了只有一個事件結束了,我們才會觸發下一個定時器事件,這樣解決了 setInterval 的問題。
實現思路是使用遞歸函數,不斷地去執行 setTimeout 從而達到 setInterval 的效果
```
function mySetInterval(fn, timeout) {
// 控制器,控制定時器是否繼續執行
var timer = {
flag: true
};
// 設置遞歸函數,模擬定時器執行。
function interval() {
if (timer.flag) {
fn();
setTimeout(interval, timeout);
}
}
// 啟動定時器
setTimeout(interval, timeout);
// 返回控制器
return timer;
}
```
- 首頁
- 2021年
- 基礎知識
- 同源策略
- 跨域
- css
- less
- scss
- reset
- 超出文本顯示省略號
- 默認滾動條
- 清除浮動
- line-height與vertical-align
- box-sizing
- 動畫
- 布局
- JavaScript
- 設計模式
- 深淺拷貝
- 排序
- canvas
- 防抖節流
- 獲取屏幕/可視區域寬高
- 正則
- 重繪重排
- rem換算
- 手寫算法
- apply、call和bind原理與實現
- this的理解-普通函數、箭頭函數
- node
- nodejs
- express
- koa
- egg
- 基于nodeJS的全棧項目
- 小程序
- 常見問題
- ec-canvas之橫豎屏切換重繪
- 公眾號后臺基本配置
- 小程序發布協議更新
- 小程序引入iconfont字體
- Uni-app
- 環境搭建
- 項目搭建
- 數據庫
- MySQL數據庫安裝
- 數據庫圖形化界面常用命令行
- cmd命令行操作數據庫
- Redis安裝
- APP
- 控制縮放meta
- GIT
- 常用命令
- vsCode
- 常用插件
- Ajax
- axios-services
- 文章
- 如何讓代碼更加優雅
- 虛擬滾動
- 網站收藏
- 防抖節流之定時器清除問題
- 號稱破解全網會員的腳本
- 資料筆記
- 資料筆記2
- 公司面試題
- 服務器相關
- 前端自動化部署-jenkins
- nginx.conf配置
- https添加證書
- shell基本命令
- 微型ssh-deploy前端部署插件
- webpack
- 深入理解loader
- 深入理解plugin
- webpack注意事項
- vite和webpack區別
- React
- react+antd搭建
- Vue
- vue-cli
- vue.config.js
- 面板分割左右拖動
- vvmily-admin-template
- v-if與v-for那個優先級高?
- 下載excel
- 導入excel
- Echart-China-Map
- vue-xlsx(解析excel)
- 給elementUI的el-table添加骨架
- cdn引入配置
- Vue2.x之defineProperty應用
- 徹底弄懂diff算法的key作用
- 復制模板內容
- 表格操作按鈕太多
- element常用組件二次封裝
- Vue3.x
- Vue3快速上手(第一天)
- Vue3.x快速上手(第二天)
- Vue3.x快速上手(第三天)
- vue3+element-plus搭建項目
- vue3
- 腳手架
- vvmily-cli
- TS
- ts筆記
- common
- Date
- utils
- axios封裝
- 2022年
- HTML
- CSS基礎
- JavaScript 基礎
- 前端框架Vue
- 計算機網絡
- 瀏覽器相關
- 性能優化
- js手寫代碼
- 前端安全
- 前端算法
- 前端構建與編譯
- 操作系統
- Node.js
- 一些開放問題、智力題