11.21 頭條面試官問我的幾種源碼實現,還好我都會

閱讀源碼的好處,不用說都知道,首先進大廠必備,還可以提升自己的能力,學習前人的經驗。源碼往往是前人留下的最佳實踐,我們跟著前人的腳步去學習會讓我們事半功倍。

  • call、aplly、bind 實現
  • new 實現
  • class 實現繼承
  • async/await 實現
  • reduce 實現
  • 實現一個雙向數據綁定
  • instanceof 實現
  • Array.isArray 實現
  • Object.create 的基本實現原理
  • getOwnPropertyNames 實現
  • promise 實現
  • 手寫一個防抖/節流函數
  • 柯里化函數的實現
  • 手寫一個深拷貝

call、aplly、bind 實現

<code>call、aplly、bind/<code> 本質都是改變 <code>this/<code> 的指向,不同點 <code>call、aplly/<code> 是直接調用函數,<code>bind/<code> 是返回一個新的函數。<code>call/<code> 跟 <code>aplly/<code> 就只有參數上不同。

bind 實現

  • 箭頭函數的 <code>this/<code> 永遠指向它所在的作用域
  • 函數作為構造函數用 <code>new/<code> 關鍵字調用時,不應該改變其 <code>this/<code> 指向,因為 <code>new綁定/<code> 的優先級高於 <code>顯示綁定/<code> 和 <code>硬綁定/<code>
<code>Function.prototype.mybind = function(thisArg) {
if (typeof this !== 'function') {
throw TypeError("Bind must be called on a function");
}
// 拿到參數,為了傳給調用者
const args = Array.prototype.slice.call(arguments, 1),
// 保存 this
self = this,
// 構建一個乾淨的函數,用於保存原函數的原型
nop = function() {},
// 綁定的函數
bound = function() {
// this instanceof nop, 判斷是否使用 new 來調用 bound
// 如果是 new 來調用的話,this的指向就是其實例,
// 如果不是 new 調用的話,就改變 this 指向到指定的對象 o
return self.apply(
this instanceof nop ? this : thisArg,
args.concat(Array.prototype.slice.call(arguments))
);
};

// 箭頭函數沒有 prototype,箭頭函數this永遠指向它所在的作用域
if (this.prototype) {
nop.prototype = this.prototype;
}
// 修改綁定函數的原型指向
bound.prototype = new nop();

return bound;
}

}
/<code>
  1. <code>測試 mybind/<code>
<code>const bar = function() {
console.log(this.name, arguments);
};

bar.prototype.name = 'bar';

const foo = {
name: 'foo'
};

const bound = bar.mybind(foo, 22, 33, 44);
new bound(); // bar, [22, 33, 44]
bound(); // foo, [22, 33, 44]
/<code>

call 實現

<code>bind/<code> 是封裝了 <code>call/<code> 的方法改變了 <code>this/<code> 的指向並返回一個新的函數,那麼 <code>call/<code> 是如何做到改變 <code>this/<code> 的指向呢?原理很簡單,在方法調用模式下,<code>this/<code> 總是指向調用它所在方法的對象,<code>this/<code> 的指向與所在方法的調用位置有關,而與方法的聲明位置無關(箭頭函數特殊)。先寫一個小 <code>demo/<code> 來理解一下下。

<code>const foo = { name: 'foo' };

foo.fn = function() {
// 這裡的 this 指向了 foo
// 因為 foo 調用了 fn,
// fn 的 this 就指向了調用它所在方法的對象 foo 上
console.log(this.name); // foo
};
/<code>

利用 <code>this/<code> 的機制來實現 <code>call/<code>

<code>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;
};
/<code>

aplly 實現

<code>Function.prototype.myapply = function(thisArg) {
if (typeof this !== 'function') {
throw this + ' is not a function';
}

const args = arguments[1];

thisArg.fn = this;

const result = thisArg.fn(...arg);

delete thisArg.fn;

return result;
};
/<code>

<code>測試 mycall myaplly/<code>

<code>const bar = function() { 

console.log(this.name, arguments);
};

bar.prototype.name = 'bar';

const foo = {
name: 'foo'
};

bar.mycall(foo, 1, 2, 3); // foo [1, 2, 3]
bar.myaplly(foo, [1, 2, 3]); // foo [1, 2, 3]
/<code>

reduce 實現原理

arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])

<code>Array.prototype.myreduce = function reduce(callbackfn) {
// 拿到數組
const O = this,
len = O.length;
// 下標值
let k = 0,
// 累加器
accumulator = undefined,
// k下標對應的值是否存在
kPresent = false,
// 初始值
initialValue = arguments.length > 1 ? arguments[1] : undefined;

if (typeof callbackfn !== 'function') {
throw new TypeError(callbackfn + ' is not a function');
}

// 數組為空,並且有初始值,報錯
if (len === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
}

// 如果初始值存在
if (arguments.length > 1) {
// 設置累加器為初始值
accumulator = initialValue;
// 初始值不存在

} else {
accumulator = O[k];
++k;
}

while (k < len) {
// 判斷是否為 empty [,,,]
kPresent = O.hasOwnProperty(k);

if (kPresent) {
const kValue = O[k];
// 調用 callbackfn
accumulator = callbackfn.apply(undefined, [accumulator, kValue, k, O]);
}
++k;
}

return accumulator;
};
/<code>

<code>測試/<code>

<code>const rReduce = ['1', null, undefined, , 3, 4].reduce((a, b) => a + b, 3);
const mReduce = ['1', null, undefined, , 3, 4].myreduce((a, b) => a + b, 3);

console.log(rReduce, mReduce);
// 31nullundefined34 31nullundefined34
/<code>

new 實現

我們需要知道當 <code>new/<code> 的時候做了什麼事情

  1. 創建一個新對象;
  2. 將構造函數的作用域賦給新對象(因此 this 就指向了這個新對象)
  3. 執行構造函數中的代碼(為這個新對象添加屬性)
  4. 返回新對象。

因為 new 沒辦法重寫,我們使用 <code>myNew/<code> 函數來模擬 <code>new/<code>

<code>function myNew() {
// 創建一個實例對象
var obj = new Object();
// 取得外部傳入的構造器
var Constructor = Array.prototype.shift.call(arguments);
// 實現繼承,實例可以訪問構造器的屬性
obj.__proto__ = Constructor.prototype;
// 調用構造器,並改變其 this 指向到實例
var ret = Constructor.apply(obj, arguments);
// 如果構造函數返回值是對象則返回這個對象,如果不是對象則返回新的實例對象
return typeof ret === 'object' ? ret : obj;
}
/<code>

<code>測試 myNew/<code>

<code>// ========= 無返回值 =============
const testNewFun = function(name) {
this.name = name;
};

const newObj = myNew(testNewFun, 'foo');

console.log(newObj); // { name: "foo" }
console.log(newObj instanceof testNewFun); // true
// ========= 有返回值 =============
const testNewFun = function(name) {
this.name = name;
return {};
};

const newObj = myNew(testNewFun, 'foo');

console.log(newObj); // {}
console.log(newObj instanceof testNewFun); // false

/<code>

class 實現繼承

主要使用 <code>es5/<code> 跟 <code>es6/<code> 對比看下 <code>class/<code> 繼承的原理

實現繼承 <code>A extends B/<code>

使用 <code>es6/<code> 語法

<code>class B {
constructor(opt) {
this.BName = opt.name;
}
}
class A extends B {
constructor() {
// 向父類傳參
super({ name: 'B' });
// this 必須在 super() 下面使用
console.log(this);
}
}
/<code>

使用 <code>es5/<code> 語法

使用寄生組合繼承的方式

  1. 原型鏈繼承,使子類可以調用父類原型上的方法和屬性
  2. 借用構造函數繼承,可以實現向父類傳參
  3. 寄生繼承,創造乾淨的沒有構造方法的函數,用來寄生父類的 prototype
<code>// 實現繼承,通過繼承父類 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);
/<code>

<code>測試 class/<code>

<code>const a = new A();

console.log(a.BName, a.constructor); // B ,ƒ A() {}
/<code>

async/await 實現

原理就是利用 <code>generator/<code>(生成器)分割代碼片段。然後我們使用一個函數讓其自迭代,每一個<code>yield/<code> 用 <code>promise/<code> 包裹起來。執行下一步的時機由 <code>promise/<code> 來控制

<code>async/await/<code> 是關鍵字,不能重寫它的方法,我們使用函數來模擬

異步迭代,模擬異步函數

<code>function _asyncToGenerator(fn) {
return function() {
var self = this,
args = arguments;
// 將返回值promise化
return new Promise(function(resolve, reject) {
// 獲取迭代器實例
var gen = fn.apply(self, args);
// 執行下一步
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
}
// 拋出異常
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
}
// 第一次觸發
_next(undefined);
});
};
}
/<code>

執行迭代步驟,處理下次迭代結果

<code>function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;

} catch (error) {
reject(error);
return;
}
if (info.done) {
// 迭代器完成
resolve(value);
} else {
// -- 這行代碼就是精髓 --
// 將所有值promise化
// 比如 yield 1
// const a = Promise.resolve(1) a 是一個 promise
// const b = Promise.resolve(a) b 是一個 promise
// 可以做到統一 promise 輸出
// 當 promise 執行完之後再執行下一步
// 遞歸調用 next 函數,直到 done == true
Promise.resolve(value).then(_next, _throw);
}
}
/<code>

<code>測試 _asyncToGenerator/<code>

<code>const asyncFunc = _asyncToGenerator(function*() {
const e = yield new Promise(resolve => {
setTimeout(() => {
resolve('e');
}, 1000);
});
const a = yield Promise.resolve('a');
const d = yield 'd';
const b = yield Promise.resolve('b');
const c = yield Promise.resolve('c');
return [a, b, c, d, e];
});

asyncFunc().then(res => {
console.log(res); // ['a', 'b', 'c', 'd', 'e']
});
/<code>

實現一個雙向綁定

<code>defineProperty/<code> 版本

<code>// 數據
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 數據劫持
Object.defineProperty(data, 'text', {
// 數據變化 --> 修改視圖
set(newVal) {
input.value = newVal;
span.innerHTML = newVal;
}
});
// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
data.text = e.target.value;
});
/<code>

<code>proxy/<code> 版本

<code>// 數據
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 數據劫持
const handler = {
set(target, key, value) {
target[key] = value;
// 數據變化 --> 修改視圖
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data);

// 視圖更改 --> 數據變化
input.addEventListener('keyup', function(e) {
proxy.text = e.target.value;
});
/<code>

Object.create 的基本實現原理

<code>function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
/<code>

instanceof 實現

原理:<code>L/<code> 的 <code>__proto__/<code> 是不是等於 <code>R.prototype/<code>,不等於再找 <code>L.__proto__.__proto__/<code> 直到 <code>__proto__/<code> 為 <code>null/<code>

<code>// L 表示左表達式,R 表示右表達式
function instance_of(L, R) {
var O = R.prototype;
L = L.__proto__;
while (true) {
if (L === null) return false;
// 這裡重點:當 O 嚴格等於 L 時,返回 true
if (O === L) return true;
L = L.__proto__;
}
}
/<code>

Array.isArray 實現

<code>Array.myIsArray = function(o) {
return Object.prototype.toString.call(Object(o)) === '[object Array]';
};

console.log(Array.myIsArray([])); // true
/<code>

getOwnPropertyNames 實現

<code>if (typeof Object.getOwnPropertyNames !== 'function') {
Object.getOwnPropertyNames = function(o) {
if (o !== Object(o)) {
throw TypeError('Object.getOwnPropertyNames called on non-object');
}
var props = [],
p;

for (p in o) {
if (Object.prototype.hasOwnProperty.call(o, p)) {
props.push(p);
}
}
return props;
};
}
/<code>

Promise 實現

實現原理:其實就是一個發佈訂閱者模式

  1. 構造函數接收一個 <code>executor/<code> 函數,並會在 <code>new Promise()/<code> 時立即執行該函數
  2. <code>then/<code> 時收集依賴,將回調函數收集到 <code>成功/失敗隊列/<code>
  3. <code>executor/<code> 函數中調用 <code>resolve/reject/<code> 函數
  4. <code>resolve/reject/<code> 函數被調用時會通知觸發隊列中的回調

先看一下整體代碼,有一個大致的概念

頭條面試官問我的幾種源碼實現,還好我都會

完整代碼

<code>const isFunction = variable => typeof variable === 'function';

// 定義Promise的三種狀態常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
// 構造函數,new 時觸發
constructor(handle: Function) {
try {
handle(this._resolve, this._reject);
} catch (err) {
this._reject(err);
}
}
// 狀態 pending fulfilled rejected
private _status: string = PENDING;
// 儲存 value,用於 then 返回
private _value: string | undefined = undefined;
// 失敗隊列,在 then 時注入,resolve 時觸發
private _rejectedQueues: any = [];
// 成功隊列,在 then 時注入,resolve 時觸發
private _fulfilledQueues: any = [];
// resovle 時執行的函數
private _resolve = val => {
const run = () => {
if (this._status !== PENDING) return;
this._status = FULFILLED;
// 依次執行成功隊列中的函數,並清空隊列
const runFulfilled = value => {
let cb;
while ((cb = this._fulfilledQueues.shift())) {
cb(value);
}
};
// 依次執行失敗隊列中的函數,並清空隊列
const runRejected = error => {

let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(error);
}
};
/*
* 如果resolve的參數為Promise對象,
* 則必須等待該Promise對象狀態改變後當前Promsie的狀態才會改變
* 且狀態取決於參數Promsie對象的狀態
*/
if (val instanceof MyPromise) {
val.then(
value => {
this._value = value;
runFulfilled(value);
},
err => {
this._value = err;
runRejected(err);
}
);
} else {
this._value = val;
runFulfilled(val);
}
};
// 異步調用
setTimeout(run);
};
// reject 時執行的函數
private _reject = err => {
if (this._status !== PENDING) return;
// 依次執行失敗隊列中的函數,並清空隊列
const run = () => {
this._status = REJECTED;
this._value = err;
let cb;
while ((cb = this._rejectedQueues.shift())) {
cb(err);
}
};
// 為了支持同步的Promise,這裡採用異步調用

setTimeout(run);
};
// then 方法
then(onFulfilled?, onRejected?) {
const { _value, _status } = this;
// 返回一個新的Promise對象
return new MyPromise((onFulfilledNext, onRejectedNext) => {
// 封裝一個成功時執行的函數
const fulfilled = value => {
try {
if (!isFunction(onFulfilled)) {
onFulfilledNext(value);
} else {
const res = onFulfilled(value);
if (res instanceof MyPromise) {
// 如果當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調
res.then(onFulfilledNext, onRejectedNext);
} else {
//否則會將返回結果直接作為參數,傳入下一個then的回調函數,並立即執行下一個then的回調函數
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函數執行出錯,新的Promise對象的狀態為失敗
onRejectedNext(err);
}
};

// 封裝一個失敗時執行的函數
const rejected = error => {
try {
if (!isFunction(onRejected)) {
onRejectedNext(error);
} else {
const res = onRejected(error);
if (res instanceof MyPromise) {
// 如果當前回調函數返回MyPromise對象,必須等待其狀態改變後在執行下一個回調

res.then(onFulfilledNext, onRejectedNext);
} else {
//否則會將返回結果直接作為參數,傳入下一個then的回調函數,並立即執行下一個then的回調函數
onFulfilledNext(res);
}
}
} catch (err) {
// 如果函數執行出錯,新的Promise對象的狀態為失敗
onRejectedNext(err);
}
};

switch (_status) {
// 當狀態為pending時,將then方法回調函數加入執行隊列等待執行
case PENDING:
this._fulfilledQueues.push(fulfilled);
this._rejectedQueues.push(rejected);
break;
// 當狀態已經改變時,立即執行對應的回調函數
case FULFILLED:
fulfilled(_value);
break;
case REJECTED:
rejected(_value);
break;
}
});
}
// catch 方法
catch(onRejected) {
return this.then(undefined, onRejected);
}
// finally 方法
finally(cb) {
return this.then(
value => MyPromise.resolve(cb()).then(() => value),
reason =>
MyPromise.resolve(cb()).then(() => {
throw reason;
})

);
}
// 靜態 resolve 方法
static resolve(value) {
// 如果參數是MyPromise實例,直接返回這個實例
if (value instanceof MyPromise) return value;
return new MyPromise(resolve => resolve(value));
}
// 靜態 reject 方法
static reject(value) {
return new MyPromise((resolve, reject) => reject(value));
}
// 靜態 all 方法
static all(list) {
return new MyPromise((resolve, reject) => {
// 返回值的集合
let values = [];
let count = 0;
for (let [i, p] of list.entries()) {
// 數組參數如果不是MyPromise實例,先調用MyPromise.resolve
this.resolve(p).then(
res => {
values[i] = res;
count++;
// 所有狀態都變成fulfilled時返回的MyPromise狀態就變成fulfilled
if (count === list.length) resolve(values);
},
err => {
// 有一個被rejected時返回的MyPromise狀態就變成rejected
reject(err);
}
);
}
});
}
// 添加靜態race方法
static race(list) {
return new MyPromise((resolve, reject) => {
for (let p of list) {
// 只要有一個實例率先改變狀態,新的MyPromise的狀態就跟著改變

this.resolve(p).then(
res => {
resolve(res);
},
err => {
reject(err);
}
);
}
});
}
}
/<code>

防抖/截流

<code>防抖函數/<code> onscroll 結束時觸發一次,延遲執行

<code>function debounce(func, wait) {
let timeout;
return function() {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// 使用
window.onscroll = debounce(function() {
console.log('debounce');
}, 1000);
/<code>

<code>節流函數/<code> onscroll 時,每隔一段時間觸發一次,像水滴一樣

<code>function throttle(fn, delay) {
var prevTime = Date.now();
return function() {
var curTime = Date.now();
if (curTime - prevTime > delay) {
fn.apply(this, arguments);
prevTime = curTime;
}
};

}
// 使用
var throtteScroll = throttle(function() {
console.log('throtte');
}, 1000);
window.onscroll = throtteScroll;
/<code>

函數柯里化實現

其實我們無時無刻不在使用柯里化函數,只是沒有將它總結出來而已。它的本質就是將一個參數很多的函數分解成單一參數的多個函數。

實際應用中:

  • 延遲計算 (用閉包把傳入參數保存起來,當傳入參數的數量足夠執行函數時,開始執行函數)
  • 動態創建函數 (參數不夠時會返回接受剩下參數的函數)
  • 參數複用(每個參數可以多次複用)
<code>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
/<code>

手寫一個深拷貝

淺拷貝只複製地址值,實際上還是指向同一堆內存中的數據,深拷貝則是重新創建了一個相同的數據,二者指向的堆內存的地址值是不同的。這個時候修改賦值前的變量數據不會影響賦值後的變量。

要實現一個完美的神拷貝太複雜了,這裡簡單介紹一下吧,可以應用於大部分場景了

判斷類型函數

<code>function getType(obj) {
const str = Object.prototype.toString.call(obj);
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
};
if (obj instanceof Element) {
// 判斷是否是dom元素,如div等
return 'element';
}
return map[str];
}
/<code>

簡單版深拷貝,列舉三個例子 <code>array/<code> <code>object/<code> <code>function/<code>,可以自行擴展。主要是引發大家的思考

<code>function deepCopy(ori) {
const type = getType(ori);
let copy;
switch (type) {
case 'array':
return copyArray(ori, type, copy);
case 'object':
return copyObject(ori, type, copy);
case 'function':
return copyFunction(ori, type, copy);
default:
return ori;
}
}

function copyArray(ori, type, copy = []) {
for (const [index, value] of ori.entries()) {
copy[index] = deepCopy(value);
}
return copy;
}

function copyObject(ori, type, copy = {}) {
for (const [key, value] of Object.entries(ori)) {
copy[key] = deepCopy(value);
}
return copy;
}

function copyFunction(ori, type, copy = () => {}) {
const fun = eval(ori.toString());
fun.prototype = ori.prototype
return fun
}/<code>


分享到:


相關文章: