手写函数源码
各种面试常见手写源码 😏😏😏😏
对象深拷贝
js
const obj1 = {
name: 'wozien',
age: 23,
have: ['house', 'car', 'money', function() {}]
};
function getType(val) {
return Object.prototype.toString.call(val).slice(8, -1);
}
function deepCopy(obj) {
let res,
type = getType(obj);
if (type === 'Object') {
res = {};
} else if (type === 'Array') {
res = [];
} else {
return obj;
}
// 对象每个属性的复制
for (let key in obj) {
let value = obj[key];
if (getType(value) === 'Object' || getType(value) === 'Array') {
// 递归深拷贝子对象
res[key] = deepCopy(value);
} else {
res[key] = value;
}
}
return res;
}
const obj2 = deepCopy(obj1);
obj2.name = 'marry';
obj2.have[0] = 'aasds';
console.log(obj1);
console.log(obj2);
浅拷贝可以用 Object.assign
和 [].slice
实现
数组去重
对象属性标记
js
const unique = arr => {
const temp = {};
return arr.filter(item =>
temp.hasOwnProperty(typeof item + JSON.stringify(item))
? false
: (temp[typeof item + JSON.stringify(item)] = true)
);
};
在循环中利用 indexOf
求出的索引和当前循环索引比较进行过滤,只返回相等的元素。
js
const unique = arr => arr.filter((e, i) => arr.indexOf(e) === i);
es6方法:
js
const unique = arr => Array.from(new Set(arr));
const unique = arr => [...new Set(arr)];
//利用映射map,该方法的对象标记一个意思。
const unique = arr => {
const seen = new Map();
return arr.filter(e => !seen.has(e) && seen.set(e, 1));
};
数组扁平
通过循环每个元素,如果元素是数组,递归调用扁平函数,并把返回结果 concat 到当前的结果数组中。
js
const flatten = arr => {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
res = res.concat(flatten(arr[i]));
} else {
res.push(arr[i]);
}
}
return res;
};
toString: 因为多层嵌套的数组调用 toString
方法会转换成逗号拼接的字符串。如[1,[2,[3,4]]]
会变成1,2,3,4。然后利用 split 分割下丢进返回结果数组。改方法只适用于都是数字的数组。
js
const flatten = arr => {
return arr
.toString()
.split(',')
.map(item => +item);
};
reduce: 数组的 reduce
会循环每个元素,并存储上次的计算结果,最终返回一个值。所以可以利用该函数进行递归的优化。
js
const flatten = arr => {
return arr.reduce((pre, e) => {
return pre.concat(Array.isArray(e) ? flatten(e) : e);
}, []);
};
es6扩展运算符: 因为[].concat([1,[2,[3,4]]])
会返回 [1,2,[3,4]]
。可见,每次调用会扁平一层数组,所以可以循环调用,直到数组不包含数组元素。
js
const flatten = arr => {
while (arr.some(e => Array.isArray(e))) {
arr = [].concat(...arr);
}
return arr;
};
实现 instanceof
js
function myInstancceOf(obj, Constr) {
let proto = obj.__proto__
while(proto) {
if(proto === Constr.prototype) return true
proto = proto.__proto__
}
return false
}
实现 new 运算符
js
function Foo(name) {
this.name = name
}
function myNew(Contr, ...args) {
const obj = Object.create(Contr.prototype)
const res = Foo.apply(obj, args)
return typeof res === 'object' ? res : obj
}
var foo = myNew(Foo, 'wozien')
console.log(foo)
实现 call, apply 和 bind
实现 call
js
Function.prototype.myCall = function(context) {
context = context || window;
// 这里的属性名应该利用唯一的key,防止覆盖原属性
// 可以用es6的symbol
context.fn = this;
// 获取参数
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
}
// args自动调用Array.toString方法
var result = eval('context.fn(' + args + ')');
delete context.fn;
return result;
}
实现 apply
:
js
Function.prototype.myApply = function(context, arr) {
context = context || window;
context.fn = this;
var result;
if (Object.prototype.toString.call(arr).slice(8, -1) !== 'Array') {
// 传的不是数组直接调用
result = context.fn();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')');
}
delete context.fn;
return result;
};
实现 bind
:
js
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new Error('Function.prototype.bind - what is trying to be bound is not callable');
}
var fn = this;
var arg1 = Array.prototype.slice.call(arguments, 1);
var Fnp = function() {};
var bindfn = function() {
var arg2 = Array.prototype.slice.call(arguments);
// 执行原函数,修改this
fn.apply(this instanceof fn ? this : context, arg1.concat(arg2));
};
// 新函数和原函数指向同一个原型
Fnp.prototype = fn.prototype;
bindfn.prototype = new Fnp();
return bindfn;
};
实现 EventEmitter
js
function EventEmitter() {
// 回调函数对象
this.cbs = {};
}
EventEmitter.prototype.addListener = function(type, listener) {
if (this.cbs[type]) {
this.cbs[type].push(listener);
} else {
this.cbs[type] = [listener];
}
};
EventEmitter.prototype.once = function(type, listener) {
// 处理监听函数,再调用后立即removeListener
const only = (...arg) => {
listener.apply(this, arg);
this.removeListener(type, listener);
};
// 这一步是为了能够在移除的时候找到对应的监听函数
only.origin = listener;
this.addListener(type, only);
};
EventEmitter.prototype.removeListener = function(type, listener) {
if (Array.isArray(this.cbs[type])) {
if (!listener) {
// 清空所有监听
delete this.cbs[type];
} else {
// 移除通过addListener或者once添加的回调
this.cbs[type] = this.cbs[type].filter(e => e !== listener && e.origin !== listener);
}
}
};
EventEmitter.prototype.emit = function(type, ...args) {
if (Array.isArray(this.cbs[type])) {
this.cbs[type].forEach(cb => {
cb.apply(this, args);
});
}
};