[TOC]
1\. 數組扁平化
2\. 數組去重
3\. 類數組轉化為數組 (類數組是具有 length 屬性,但不具有數組原型上的方法)
4\. array.prototype.filter()
5\. Array.prototype.map()
6\. Array.prototype.forEach()
7\. Array.prototype.reduce()
8\. Apple() (function.prototype.apple())
9\. call
10\. Bind()
11\. Debounce () 防抖 (頻率高頻時間后 n 秒內函數只會執行一次,如果 n 秒內高頻時間再次觸發,則重新計算時間)
12\. throttle 節流 ()
13\. 函數珂里化
14\. 模擬 new 操作
15\. Instanceof 運算符用于檢測構造函數的 prototype 屬性是否出現在某一個實例對象的原型鏈上
16\. 原型繼承
17\. Object.is
18\. Object.assign () 方法用于將所有可枚舉屬性的值從一個或多個源對象復制到目標對象 (它返回目標對象 這個操作是淺拷貝)
19\. 深拷貝
20\. promise
21\. Promise.all
22\. Promise.race
23\. Promise 并行限制
24\. JSONP
25\. AJAX
26\. event 模塊 實現 node 中回調函數的機制,node 中回調函數其實是內部使用了觀察者模式
27\. 圖片懶加載
28\. 滾動加載
29\. 渲染幾萬條數據不卡住頁面 (渲染大數據時,合理利用 createDocumentFragment 和 requestAnimationFrame,將操作切分為一小段一小段執行)
30\. 打印出當前頁面使用了多少種 HTML 元素
31\. 將 virtualDom 轉化為真實 DOM 結構
32\. 字符串解析問題 ()
# 深拷貝
簡單版:
```
function deepCopy(obj){
var cloneObj;
// 當輸入數據為簡單數據類型時直接復制
if (obj && typeof obj!=='object'){ return obj;}
// 當輸入數據為對象或數組時
else if (obj && typeof obj==='object'){
// 檢測輸入數據是數組還是對象
cloneObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)){
if(obj[key]&&typeof obj[key] === 'object') {
// 若當前元素類型為對象時,遞歸調用
cloneObj[key] = deepCopy(obj[key]);
}
// 若當前元素類型為基本數據類型
else{ cloneObj[key] = obj[key]; }
}
}
}
return cloneObj;
}
```
第二種:
~~~js
/**
* Get the first item that pass the test
* by second argument function
*
* @param {Array} list
* @param {Function} f
* @return {*}
*/
export function find (list, f) {
return list.filter(f)[0]
}
/**
* Deep copy the given object considering circular structure.
* This function caches all nested objects and its copies.
* If it detects circular structure, use cached copy to avoid infinite loop.
*
* @param {*} obj
* @param {Array<Object>} cache
* @return {*}
*/
export function deepCopy (obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
~~~
# 自定義柯里化函數:
實現 1:
```
function curry(func) {
return function curried(...args) {
console.log(args);
if (args.length >= func.length) {
return func.apply(this, args);
} else {
console.log('calling else');
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};
}
// 測試
function multiply(a, b, c) {
return a * b * c;
}
let curried = curry(multiply);
console.log(curried(1, 2, 3));
console.log(curried(1)(2, 3));
console.log(curried(1)(2)(3));
```
實現 2:
```
const curry = fn =>
(judge = (...args) =>
args.length === fn.length
? fn(...args)
: (...arg) => judge(...args, ...arg));
// 測試
const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);
currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10
```
> http://zetcode.com/javascript/currying/
# call 實現
原理:在方法調用模式下,`this`總是指向調用它所在方法的對象,`this`的指向與所在方法的調用位置有關,而與方法的聲明位置無關(箭頭函數特殊)。
```
const foo = { name: 'foo' };
foo.fn = function() {
// 這里的 this 指向了 foo
// 因為 foo 調用了 fn,
// fn 的 this 就指向了調用它所在方法的對象 foo 上
console.log(this.name); // foo
};
```
實現:
```js
Function.prototype.mycall = function(thisArg) {
// this指向調用call的對象
if (typeof this !== 'function') {
// 調用call的若不是函數則報錯
throw new TypeError('Error');
}
const args = [...arguments].slice(1);
thisArg = thisArg || window;
// 將調用call函數的對象添加到 thisArg 的屬性中
thisArg.fn = this;
// 執行該屬性
const result = thisArg.fn(...arg);
// 刪除該屬性
delete thisArg.fn;
// 返回函數執行結果
return result;
};
```
# 實現 new 方法
```
function myNew(){
var obj = {}
var Constructor = Array.prototype.shift.call(arguments)
obj.__proto__ = Constructor.prototype // 把實例與構造函數它們的關系建立起來
var res = Constructor.apply(obj, arguments)
return typeof res === 'object' ? res : obj;
}
```
# 數組扁平化
```
// 方案 1
function recursionFlat(ary = []) {
const res = []
ary.forEach(item => {
if (Array.isArray(item)) {
res.push(...recursionFlat(item))
} else {
res.push(item)
}
})
return res
}
// 方案 2
function reduceFlat(ary = []) {
return ary.reduce((res, item) => res.concat(Array.isArray(item) ? reduceFlat(item) : item), [])
}
// 測試
const source = [1, 2, [3, 4, [5, 6]], '7']
console.log(recursionFlat(source))
console.log(reduceFlat(source))
```
# 對象扁平化
```
function objectFlat(obj = {}) {
const res = {}
function flat(item, preKey = '') {
Object.entries(item).forEach(([key, val]) => {
const newKey = preKey ? `${preKey}.${key}` : key
if (val && typeof val === 'object') {
flat(val, newKey)
} else {
res[newKey] = val
}
})
}
flat(obj)
return res
}
// 測試
const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));
```
# ES 5、ES 6繼承
主要使用 ES 5 跟 ES 6 對比看下`class`繼承的原理
使用`es6`語法
~~~
class B {
constructor(opt) {
this.BName = opt.name;
}
}
class A extends B {
constructor () {
// 向父類傳參
super({ name: 'B' });
// this 必須在 super() 下面使用
console.log(this);
}
}
~~~
使用`es5`語法
使用寄生組合繼承的方式
1. 原型鏈繼承,使子類可以調用父類原型上的方法和屬性
2. 借用構造函數繼承,可以實現向父類傳參
3. 寄生繼承,創造干凈的沒有構造方法的函數,用來寄生父類的 prototype
~~~
// 實現繼承,通過繼承父類 prototype
function __extends(child, parent) {
// 修改對象原型
Object.setPrototypeOf(child, parent);
// 寄生繼承,創建一個干凈的構造函數,用于繼承父類的 prototype
// 這樣做的好處是,修改子類的 prototype 不會影響父類的 prototype
function __() {
// 修正 constructor 指向子類
this.constructor = child;
}
// 原型繼承,繼承父類原型屬性,但是無法向父類構造函數傳參
child.prototype = parent === null ? Object.create(parent) : ((__.prototype = parent.prototype), new __());
}
var B = (function() {
function B(opt) {
this.name = opt.name;
}
return B;
})();
var A = (function(_super) {
__extends(A, _super);
function A() {
// 借用繼承,可以實現向父類傳參, 使用 super 可以向父類傳參
return (_super !== null && _super.apply( this, { name: 'B' })) || this;
}
return A;
})(B);
~~~
測試 class
~~~
const a = new A();
console.log(a.BName, a.constructor);
// B ,? A() {}
~~~
# Object.create 的實現
~~~
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
~~~
# 參考
[JS 基礎手寫代碼](https://hub.fastgit.org/Godiswill/blog/issues/20)
[前端面試中常見的手寫功能](https://www.h5w3.com/44219.html)
- 修仙之路
- 基礎原理篇
- JS和Node.js事件環機制剖析
- 一圖理解原型鏈
- 手寫篇
- 基礎手寫
- 手寫實現 Promise A+ 類庫
- 手寫 CommonJS
- 手寫 Express 框架
- 手寫 React Router 4.0
- 手寫虛擬 DOM 和 DOM-Diff
- 手寫 Webpack 實現
- 手寫一個 MVVM 類庫
- 手寫一個 Vue-cli 腳手架
- 手寫 JWT 類庫
- 手寫 Mobx 類庫
- 手寫前端性能和錯誤監控框架
- 手寫 Vue 路由
- 手寫 Vuex 實現
- 手寫 redux 狀態容器
- 手寫 throttle 和 debounce
- Node 高級
- Mongodb
- 安全測試篇
- CSRF原理實現
- XSS原理實現
- 九種跨域方法全解析
- 編寫單元測試
- 爬蟲篇
- 使用puppeteer破解滑動驗證碼
- 工程篇
- 使用AST語法樹手工轉譯ES6代碼
- 編寫自己的webpack插件
- 實戰篇
- webpack4.0 實戰
- Canvas+Websocket 實現彈幕
- canvas 動效
- SVG 動效
- CSS3 實現 Apple Watch 中的呼吸燈效果
- CSS3 實現動態氣泡屏保效果
- 算法篇
- 基礎知識
- 服務器端
- 分布式架構中的冪等性
- TCP/UDP
- Docker
- V8
- 動畫篇
- 貝塞爾曲線
- requestAnimationFrame
- 框架篇
- 隨記