過完年立刻又要到金三銀四面試季了,想必不少同窗已經躍躍欲試,提早開始準備面試了,本文就列舉了面試過程當中一些常見的手寫代碼實現供參考。或許不少人會問,這些手寫代碼實現意義何在,社區已經有不少poly-fill
或者函數庫供選擇,何須要本身費力去折騰呢?個人理解是,在真實業務開發場景中,咱們真的用不上這些本身寫的方法,一個lodash
庫徹底能夠知足咱們的需求,但此時你僅僅只是一個API Caller ,你常用到它,但對它實現原理卻一無所知,哪怕它實現起來是很是簡單的。因此親自動手寫出它的實現過程,對你理解其中原理是頗有幫助的。另外,不要以爲用ES6
語法,或者最新的語法去實現ES5
甚至是ES3
的方法是件好笑的事情,相反,它更能體現出你對ES6
語法的掌握程度以及對JS發展的關注度,在面試中說不定會成爲你的一個亮點。javascript
null
或者undefined
時,this
指向全局對象window
,值爲原始值的指向該原始值的自動包裝對象,如 String
、Number
、Boolean
context
)的屬性發生衝突,使用Symbol
類型做爲惟一值context
)屬性執行Function.prototype.myCall = function(context, ...args) {
context = (context ?? window) || new Object(context)
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
複製代碼
注: 代碼實現使用了ES2020
新特性Null
判斷符 ??
, 詳細參考阮一峯老師的ECMAScript 6 入門vue
call
同樣Function.prototype.myApply = function(context) {
context = (context ?? window) || new Object(context)
const key = Symbol()
const args = arguments[1]
context[key] = this
let result
if(args) {
result = context[key](...args)
} else {
result = context[key]()
}
delete context[key]
return result
}
複製代碼
注:代碼實現存在缺陷,當第二個參數爲類數組時,未做判斷(有興趣可查閱一下如何判斷類數組)java
call / apply
指定 this
new
調用,綁定的上下文指向實例對象prototype
爲原函數的prototype
Function.prototype.myBind = function(context, ...args) {
const fn = this
const bindFn = function (...newFnArgs) {
fn.call(
this instanceof bindFn ? this : context,
...args, ...newFnArgs
)
}
bindFn.prototype = Object.create(fn.prototype)
return bindFn
}
複製代碼
this
綁定到空對象__proto__
指向構造函數的原型(prototype
)const createNew = (Con, ...args) => {
const obj = {}
Object.setPrototypeOf(obj, Con.prototype)
let result = Con.apply(obj, args)
return result instanceof Object ? result : obj
}
複製代碼
false
const myInstanceOf = (left, right) => {
let leftValue = left.__proto__
let rightValue = right.prototype
while(true) {
if(leftValue === null) return false
if(leftValue === rightValue) return true
leftValue = leftValue.__proto__
}
}
複製代碼
const deepClone = (target, cache = new WeakMap()) => {
if(target === null || typeof target !== 'object') {
return target
}
if(cache.get(target)) {
return target
}
const copy = Array.isArray(target) ? [] : {}
cache.set(target, copy)
Object.keys(target).forEach(key => copy[key] = deepClone(obj[key], cache))
return copy
}
複製代碼
缺點:沒法拷貝函數、Map
、Set
、正則等其餘類型git
vuex源碼es6
function find(list, f) {
return list.filter(f)[0]
}
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
}
複製代碼
如何寫出一個驚豔面試官的深拷貝?github
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag];
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
function isObject(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
function getType(target) {
return Object.prototype.toString.call(target);
}
function getInit(target) {
const Ctor = target.constructor;
return new Ctor();
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe);
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
// 克隆原始類型
if (!isObject(target)) {
return target;
}
// 初始化
const type = getType(target);
let cloneTarget;
if (deepTag.includes(type)) {
cloneTarget = getInit(target, type);
} else {
return cloneOtherType(target, type);
}
// 防止循環引用
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
// 克隆set
if (type === setTag) {
target.forEach(value => {
cloneTarget.add(clone(value, map));
});
return cloneTarget;
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value, map));
});
return cloneTarget;
}
// 克隆對象和數組
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}
複製代碼
const MY_IMMER = Symbol('my-immer1')
const isPlainObject = value => {
if (
!value ||
typeof value !== 'object' ||
{}.toString.call(value) != '[object Object]'
) {
return false
}
var proto = Object.getPrototypeOf(value)
if (proto === null) {
return true
}
var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor
return (
typeof Ctor == 'function' &&
Ctor instanceof Ctor &&
Function.prototype.toString.call(Ctor) ===
Function.prototype.toString.call(Object)
)
}
const isProxy = value => !!value && !!value[MY_IMMER]
function produce(baseState, fn) {
const proxies = new Map()
const copies = new Map()
const objectTraps = {
get(target, key) {
if (key === MY_IMMER) return target
const data = copies.get(target) || target
return getProxy(data[key])
},
set(target, key, val) {
const copy = getCopy(target)
const newValue = getProxy(val)
// 這裏的判斷用於拿 proxy 的 target
// 不然直接 copy[key] = newValue 的話外部拿到的對象是個 proxy
copy[key] = isProxy(newValue) ? newValue[MY_IMMER] : newValue
return true
}
}
const getProxy = data => {
if (isProxy(data)) {
return data
}
if (isPlainObject(data) || Array.isArray(data)) {
if (proxies.has(data)) {
return proxies.get(data)
}
const proxy = new Proxy(data, objectTraps)
proxies.set(data, proxy)
return proxy
}
return data
}
const getCopy = data => {
if (copies.has(data)) {
return copies.get(data)
}
const copy = Array.isArray(data) ? data.slice() : { ...data }
copies.set(data, copy)
return copy
}
const isChange = data => {
if (proxies.has(data) || copies.has(data)) return true
}
const finalize = data => {
if (isPlainObject(data) || Array.isArray(data)) {
if (!isChange(data)) {
return data
}
const copy = getCopy(data)
Object.keys(copy).forEach(key => {
copy[key] = finalize(copy[key])
})
return copy
}
return data
}
const proxy = getProxy(baseState)
fn(proxy)
return finalize(baseState)
}
複製代碼
函數防抖是在事件被觸發n
秒後再執行回調,若是在n
秒內又被觸發,則從新計時。 函數防抖多用於input
輸入框vuex
this
繼承自父級上下文,指向觸發事件的目標元素event
對象leading
參數,判斷是否能夠當即執行回調函數,沒必要要等到事件中止觸發後纔開始執行const debounce = (fn, wait = 300, leading = true) => {
let timerId, result
return function(...args) {
timerId && clearTimeout(timerId)
if (leading) {
if (!timerId) result = fn.apply(this, args)
timerId = setTimeout(() => timerId = null, wait)
} else {
timerId = setTimeout(() => result = fn.apply(this, args), wait)
}
return result
}
}
複製代碼
函數節流是指連續觸發事件,可是在 n 秒中只執行一次函數,適合應用於動畫相關的場景數組
const throttle = (fn, wait = 300) => {
let timerId
return function(...args) {
if(!timerId) {
timerId = setTimeout(() => {
timerId = null
return result = fn.apply(this, ...args)
}, wait)
}
}
}
複製代碼
const throttle = (fn, wait = 300) => {
let prev = 0
let result
return function(...args) {
let now = +new Date()
if(now - prev > wait) {
prev = now
return result = fn.apply(this, ...args)
}
}
}
複製代碼
方法 | 使用時間戳 | 使用定時器 |
---|---|---|
開始觸發時 | 馬上執行 | n秒後執行 |
中止觸發後 | 再也不執行事件 | 繼續執行一次事件 |
const uniqBy = (arr, key) => {
return [...new Map(arr.map(item) => [item[key], item])).values()]
}
const singers = [
{ id: 1, name: 'Leslie Cheung' },
{ id: 1, name: 'Leslie Cheung' },
{ id: 2, name: 'Eason Chan' },
]
console.log(uniqBy(singers, 'id'))
// [
// { id: 1, name: 'Leslie Cheung' },
// { id: 2, name: 'Eason Chan' },
// ]
複製代碼
原理是利用Map
的鍵不可重複閉包
const flatten = (arr) => arr.toString().split(',').map(item => +item)
複製代碼
const flatten = (arr, deep = 1) => {
return arr.reduce((cur, next) => {
return Array.isArray(next) && deep > 1 ?
[...cur, ...flatten(next, deep - 1)] :
[...cur, next]
},[])
}
複製代碼
const currying = fn =>
_curry = (...args) =>
args.length >= fn.length
? fn(...args)
: (...newArgs) => _curry(...args, ...newArgs)
複製代碼
原理是利用閉包把傳入參數保存起來,當傳入參數的數量足夠執行函數時,就開始執行函數
class EventEmitter {
#subs = {}
emit(event, ...args) {
if (this.#subs[event] && this.#subs[event].length) {
this.#subs[event].forEach(cb => cb(...args))
}
}
on(event, cb) {
(this.#subs[event] || (this.#subs[event] = [])).push(cb)
}
off(event, offCb) {
if (offCb) {
if (this.#subs[event] && this.#subs[event].length)
this.#subs[event] = this.#subs[event].filter(cb => cb !== offCb)
} else {
this.#subs[event] = []
}
}
}
複製代碼
subs
是EventEmitter
私有屬性(最新特性參考阮一峯老師的ECMAScript 6 入門),經過on
註冊事件,off
註銷事件,emit
觸發事件
function Super(foo) {
this.foo = foo
}
Super.prototype.printFoo = function() {
console.log(this.foo)
}
function Sub(bar) {
this.bar = bar
Super.call(this)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
複製代碼
class Super {
constructor(foo) {
this.foo = foo
}
printFoo() {
console.log(this.foo)
}
}
class Sub extends Super {
constructor(foo, bar) {
super(foo)
this.bar = bar
}
}
複製代碼
ES5
的繼承,實質是先創造子類的實例對象,而後將再將父類的方法添加到this
上。 ES6
的繼承,先創造父類的實例對象(因此必須先調用super
方法,而後再用子類的構造函數修改this
參考
js基礎-面試官想知道你有多理解call,apply,bind?[不看後悔系列]