關注公衆號「執鳶者」,回覆「書籍」獲取大量前端學習資料,回覆「前端視頻」獲取大量前端教學視頻,回覆「代碼實現」獲取本節總體思惟導圖。前端
使用思惟導圖來對new、instanceof、Object.create()、Object.assign()、map()、filter()、reduce()、flat()、call()、apply()、bind()、防抖、節流、深拷貝的實現原理進行闡述,而後利用js代碼進行實現,爲前端切圖仔在求職工做中再添一門武功祕籍,提高自身內功。本節爲第一節,後面將繼續探索Promise、Async、Axios、發佈訂閱等的實現,請各位大佬關注指正。node
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(),有興趣的能夠下載思惟導圖進行完善。ios
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;
}
複製代碼
防抖與節流函數是一種最經常使用的 高頻觸發優化方式,能對性能有較大的幫助。bash
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;
}
複製代碼
相關章節
圖解JavaScript————基礎篇
圖解JavaScript————進階篇
圖解23種設計模式(TypeScript版)app
歡迎你們關注公衆號(回覆「代碼實現」獲取本節的思惟導圖,回覆「書籍」獲取大量前端學習資料,回覆「前端視頻」獲取大量前端教學視頻) 函數
![]()