關注公衆號「 執鳶者」,回覆「 書籍</font>」獲取大量前端學習資料,回覆「 前端視頻」獲取大量前端教學視頻,回覆「 代碼實現」獲取本節總體思惟導圖。
使用思惟導圖來對 new、instanceof、Object.create()、Object.assign()、map()、filter()、reduce()、flat()、call()、apply()、bind()、防抖、節流、深拷貝的實現原理進行闡述,而後利用js代碼進行實現,爲前端切圖仔在求職工做中再添一門武功祕籍,提高自身內功。本節爲第一節,後面將繼續探索 Promise、Async、Axios、發佈訂閱等的實現,請各位大佬關注指正。
function New (Fn, ...arg) { // 一個新的對象被建立 const result = {}; // 該對象的__proto__屬性指向該構造函數的原型 if (Fn.prototype !== null) { Object.setPrototypeOf(result, Fn.prototype); } // 將執行上下文(this)綁定到新建立的對象中 const returnResult = Fn.apply(result, arg); // 若是構造函數有返回值,那麼這個返回值將取代第一步中新建立的對象。不然返回該對象 if ((typeof returnResult === "object" || typeof returnResult === "function") && returnResult !== null) { return returnResult; } return result; }
function Instanceof(left, right) { let leftVal = Object.getPrototypeOf(left); const rightVal = right.prototype; while (leftVal !== null) { if (leftVal === rightVal) return true; leftVal = Object.getPrototypeOf(leftVal); } return false; }
Object上有不少靜態方法,本次只實現Object.create()和Object.assign(),有興趣的能夠下載思惟導圖進行完善。
Object.ObjectCreate = (proto, propertiesObject)=> { // 對輸入進行檢測 if (typeof proto !== 'object' && typeof proto !== 'function' && proto !== null) { throw new Error(`Object prototype may only be an Object or null:${proto}`); } // 新建一個對象 const result = {}; // 將該對象的原型設置爲proto Object.setPrototypeOf(result, proto); // 將屬性賦值給該對象 Object.defineProperties(result, propertiesObject); // 返回該對象 return result; }
function ObjectAssign(target, ...sources) { // 對第一個參數的判斷,不能爲undefined和null if (target === undefined || target === null) { throw new TypeError('cannot convert first argument to object'); } // 將第一個參數轉換爲對象(不是對象轉換爲對象) const targetObj = Object(target); // 將源對象(source)自身的全部可枚舉屬性複製到目標對象(target) for (let i = 0; i < sources.length; i++) { let source = sources[i]; // 對於undefined和null在源角色中不會報錯,會直接跳過 if (source !== undefined && source !== null) { // 將源角色轉換成對象 // 須要將源角色自身的可枚舉屬性(包含Symbol值的屬性)進行復制 // Reflect.ownKeys(obj) 返回一個數組,包含對象自身的全部屬性,無論屬性名是Symbol仍是字符串,也不論是否可枚舉 const keysArray = Reflect.ownKeys(Object(source)); for (let nextIndex = 0; nextIndex < keysArray.length; nextIndex ++) { const nextKey = keysArray[nextIndex]; // 去除不可枚舉屬性 const desc = Object.getOwnPropertyDescriptor(source, nextKey); if (desc !== undefined && desc.enumerable) { // 後面的屬性會覆蓋前面的屬性 targetObj[nextKey] = source[nextKey]; } } } } return targetObj; } // 因爲掛載到Object的assign是不可枚舉的,直接掛載上去是可枚舉的,因此採用這種方式 if (typeof Object.myAssign !== 'function') { Object.defineProperty(Object, "myAssign", { value : ObjectAssign, writable: true, enumerable: false, configurable: true }); }
數組有不少方法,咱們此處只實現了比較常見的map()、filter()、reduce()、flat(),有興趣的童鞋能夠繼續補充。
Array.prototype.myMap = function(fn) { // 判斷輸入的第一個參數是否是函數 if (typeof fn !== 'function') { throw new TypeError(fn + 'is not a function'); } // 獲取須要處理的數組內容 const arr = this; const len = arr.length; // 新建一個空數組用於裝載新的內容 const temp = new Array(len); // 對數組中每一個值進行處理 for (let i = 0; i < len; i++) { // 獲取第二個參數,改變this指向 let result = fn.call(arguments[1], arr[i], i, arr); temp[i] = result; } // 返回新的結果 return temp; }
Array.prototype.myFilter = function (fn) { if (typeof fn !== 'function') { throw new TypeError(`${fn} is not a function`); } // 獲取該數組 const arr = this; // 獲取該數組長度 const len = this.length >>> 0; // 新建一個新的數組用於放置該內容 const temp = []; // 對數組中每一個值進行處理 for (let i = 0; i < len; i++) { // 處理時注意this指向 const result = fn.call(arguments[1], arr[i], i, arr); result && temp.push(arr[i]); } return temp; }
Array.prototype.myReduce = function(fn) { if (typeof fn !== 'function') { throw new TypeError(`${fn} is not a function`); } const arr = this; const len = arr.length >>> 0; let value;// 最終返回的值 let k = 0;// 當前索引 if (arguments.length >= 2) { value = arguments[1]; } else { // 當數組爲稀疏數組時,判斷數組當前是否有元素,若是沒有索引加一 while (k < len && !( k in arr)) { k++; } // 若是數組爲空且初始值不存在則報錯 if (k >= len) { throw new TypeError('Reduce of empty array with no initial value'); } value = arr[k++]; } while (k < len) { if (k in arr) { value = fn(value, arr[k], k, arr); } k++; } return value; }
// 使用reduce和concat Array.prototype.flat1 = function () { return this.reduce((acc, val) => acc.concat(val), []); }
// 使用reduce + concat + isArray +recursivity Array.prototype.flat2 = function (deep = 1) { const flatDeep = (arr, deep = 1) => { // return arr.reduce((acc, val) => Array.isArray(val) && deep > 0 ? [...acc, ...flatDeep(val, deep - 1)] : [...acc, val], []); return deep > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, deep - 1) : val), []) : arr.slice(); } return flatDeep(this, deep); }
// 使用forEach + concat + isArray +recursivity // forEach 遍歷數組會自動跳過空元素 Array.prototype.flat3 = function (deep = 1) { const result = []; (function flat(arr, deep) { arr.forEach((item) => { if (Array.isArray(item) && deep > 0) { flat(item, deep - 1); } else { result.push(item); } }) })(this, deep); return result; }
// 使用for of + concat + isArray +recursivity // for of 遍歷數組會自動跳過空元素 Array.prototype.flat4 = function (deep = 1) { const result = []; (function flat(arr, deep) { for(let item of arr) { if (Array.isArray(item) && deep > 0) { flat(item, deep - 1); } else { // 去除空元素,由於void 表達式返回的都是undefined,不適用undefined是由於undefined在局部變量會被重寫 item !== void 0 && result.push(item); } } })(this, deep); return result; }
// 使用堆棧stack Array.prototype.flat5 = function(deep = 1) { const stack = [...this]; const result = []; while (stack.length > 0) { const next = stack.pop(); if (Array.isArray(next)) { stack.push(...next); } else { result.push(next); } } // 反轉恢復原來順序 return result.reverse(); }
js中有三種方式改變this指向,分別是call、apply和bind。
Function.prototype.call1 = function(context, ...args) { // 獲取第一個參數(注意第一個參數爲null或undefined是,this指向window),構建對象 context = context ? Object(context) : window; // 將對應函數傳入該對象中 context.fn = this; // 獲取參數並執行相應函數 let result = context.fn(...args); delete context.fn;
Function.prototype.apply1 = function(context, arr) { context = context ? Object(context) : window; context.fn = this; let result = arr ? context.fn(...arr) : context.fn(); delete context.fn; return result; }
Function.prototype.bind1 = function (context, ...args) { if (typeof this !== 'function') { throw new TypeError('The bound object needs to be a function'); } const self = this; const fNOP = function() {}; const fBound = function(...fBoundArgs) { // 指定this // 看成爲構造函數時,this 指向實例,此時 this instanceof fBound 結果爲 true return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]); } // 修改返回函數的 prototype 爲綁定函數的 prototype,爲了不直接修改this的原型,因此新建了一個fNOP函數做爲中介 if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }
防抖與節流函數是一種最經常使用的 高頻觸發優化方式,能對性能有較大的幫助。
function debounce(fn, wait, immediate) { let timer = null; return function(...args) { // 當即執行的功能(timer爲空表示首次觸發) if (immediate && !timer) { fn.apply(this, args); } // 有新的觸發,則把定時器清空 timer && clearTimeout(timer); // 從新計時 timer = setTimeout(() => { fn.apply(this, args); }, wait) } }
// 時間戳版本 function throttle(fn, wait) { // 上一次執行時間 let previous = 0; return function(...args) { // 當前時間 let now = +new Date(); if (now - previous > wait) { previous = now; fn.apply(this, args); } } }
// 定時器版本 function throttle(fn, wait) { let timer = null; return function(...args) { if (!timer) { timer = setTimeout(() => { fn.apply(this, args); timer = null; }, wait) } } }
// 乞巧版 function cloneDeep1(source) { return JSON.parse(JSON.stringify(source)); }
// 遞歸版 function cloneDeep2(source) { // 若是輸入的爲基本類型,直接返回 if (!(typeof source === 'object' && source !== null)) { return source; } // 判斷輸入的爲數組函數對象,進行相應的構建 const target = Array.isArray(source) ? [] : {}; for (let key in source) { // 判斷是不是自身屬性 if (Object.prototype.hasOwnProperty.call(source, key)) { if (typeof source === 'object' && source !== null) { target[key] = cloneDeep2(source[key]); } else { target[key] = source[key]; } } } return target; }
// 循環方式 function cloneDeep3(source) { if (!(typeof source === 'object' && source !== null)) { return source; } const root = Array.isArray(source) ? [] : {}; // 定義一個棧 const loopList = [{ parent: root, key: undefined, data: source, }]; while (loopList.length > 0) { // 深度優先 const node = loopList.pop(); const parent = node.parent; const key = node.key; const data = node.data; // 初始化賦值目標,key爲undefined則拷貝到父元素,不然拷貝到子元素 let res = parent; if (typeof key !== 'undefined') { res = parent[key] = Array.isArray(data) ? [] : {}; } for (let key in data) { if (data.hasOwnProperty(key)) { if (typeof data[key] === 'object' && data !== null) { loopList.push({ parent: res, key: key, data: data[key], }); } else { res[key] = data[key]; } } } } return root; }
相關章節<br/>
圖解JavaScript————基礎篇
圖解JavaScript————進階篇
圖解23種設計模式(TypeScript版)歡迎你們關注公衆號(回覆「代碼實現」獲取本節的思惟導圖,回覆「書籍」獲取大量前端學習資料,回覆「前端視頻」獲取大量前端教學視頻)
前端