一、用ES5实现数组的map方法
核心要点:
1.回调函数的参数有哪些,返回值如何处理。
2.不修改原来的数组。
Array.prototype.MyMap = function(fn, context){
var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了
var mappedArr = [];
for (var i = 0; i < arr.length; i++ ){
if(!arr.hasOwnProperty(i))continue;
mappedArr.push(fn.call(context, arr[i], i, this));
}
return mappedArr;
}
二、用ES5实现数组的reduce方法
核心要点:
1、初始值不传怎么处理
2、回调函数的参数有哪些,返回值如何处理。
Array.prototype.myReduce = function(fn, initialValue) {
var arr = Array.prototype.slice.call(this);
var res, startIndex;
res = initialValue ? initialValue : arr[0];
startIndex = initialValue ? 0 : 1;
for(var i = startIndex; i < arr.length; i++) {
res = fn.call(null, res, arr[i], i, this);
}
return res;
}
三、实现call/apply
思路: 利用this的上下文特性。
//实现apply只要把下一行中的...args换成args即可
Function.prototype.myCall = function(context, ...args) {
if(!context) context = window
let func = this
let fn = Symbol("fn");
context[fn] = func;
let res = context[fn](...args)//重点代码,利用this指向,相当于context.caller(...args)
delete context[caller]
return res
}
四、实现Object.create方法(常用)
function create(proto) {
function F() {};
F.prototype = proto;
return new F();
}
五、实现bind方法
核心要点:
1.对于普通函数,绑定this指向
2.对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function(context, ...args) {
let self = this;//谨记this表示调用bind的函数
let fBound = function() {
//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
return self.apply(this instanceof fBound ? this : context, args);
}
fBound.prototype = Object.create(this.prototype);//保证原函数的原型对象上的属性不丢失
return fBound;
}
大家平时说的手写bind,其实就这么简单:)
六、实现new关键字
核心要点:
- 创建一个全新的对象,这个对象的proto要指向构造函数的原型对象
- 执行构造函数
- 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象
function myNew(fn, ...args) {
let instance = Object.create(fn.prototype);
let res = fn.apply(instance, args);
return typeof res === 'object' ? res; instance;
}
七、实现instanceof的作用
核心要点:原型链的向上查找。
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left);
while(true) {
if(proto == null) return false;
if(pro == right.prototype) return true;
proto = Object.getPrototypeof(proto);
}
}
八、实现单例模式
核心要点: 用闭包和Proxy属性拦截
function proxy(func) {
let instance;
let handler = {
constructor(target, args) {
if(!instance) {
instance = Reflect.constructor(fun, args);
}
return instance;
}
}
return new Proxy(func, handler);
}
九、实现数组的flat
方式其实很多,之前我做过系统整理,有六种方法,请参考:
需求:多维数组=>一维数组
let ary = [1, [2, [3, [4, 5]]], 6];
let str = JSON.stringify(ary);
//第0中处理:直接的调用
arr_flat = arr.flat(Infinity);
//第一种处理
ary = str.replace(/(\[|\])/g, '').split(',');
//第二种处理
str = str.replace(/(\[\]))/g, '');
str = '[' + str + ']';
ary = JSON.parse(str);
//第三种处理:递归处理
let result = [];
let fn = function(ary) {
for(let i = 0; i < ary.length; i++) }{
let item = ary[i];
if (Array.isArray(ary[i])){
fn(item);
} else {
result.push(item);
}
}
}
//第四种处理:用 reduce 实现数组的 flat 方法
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
})
}
let ary = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(ary))
//第五种处理:扩展运算符
while (ary.some(Array.isArray)) {
ary = [].concat(...ary);
}
十、实现防抖功能
核心要点:
如果在定时器的时间范围内再次触发,则重新计时。
const debounce = (fn, delay) => {
let timer = null;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
十一、实现节流功能
核心要点:
如果在定时器的时间范围内再次触发,则不予理睬,等当前定时器完成,才能启动下一个定时器。
const throttle = (fn, delay = 500) => {
let flag = true;
return (...args) => {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
};
十二、用发布订阅模式实现EventEmit
基于"发布-订阅"的原生JS插件封装中的手写发布订阅部分。
十三、实现深拷贝
以下为简易版深拷贝,没有考虑循环引用的情况和Buffer、Promise、Set、Map的处理,如果一一实现,过于复杂,面试短时间写出来不太现实,如果有兴趣可以去这里深入实现:
const clone = parent => {
// 判断类型
const isType = (target, type) => `[object ${type}]` === Object.prototype.toString.call(target)
// 处理正则
const getRegExp = re => {
let flags = "";
if (re.global) flags += "g";
if (re.ignoreCase) flags += "i";
if (re.multiline) flags += "m";
return flags;
};
const _clone = parent => {
if (parent === null) return null;
if (typeof parent !== "object") return parent;
let child, proto;
if (isType(parent, "Array")) {
// 对数组做特殊处理
child = [];
} else if (isType(parent, "RegExp")) {
// 对正则对象做特殊处理
child = new RegExp(parent.source, getRegExp(parent));
if (parent.lastIndex) child.lastIndex = parent.lastIndex;
} else if (isType(parent, "Date")) {
// 对Date对象做特殊处理
child = new Date(parent.getTime());
} else {
// 处理对象原型
proto = Object.getPrototypeOf(parent);
// 利用Object.create切断原型链
child = Object.create(proto);
}
for (let i in parent) {
// 递归
child[i] = _clone(parent[i]);
}
return child;
};
return _clone(parent);
};
十四、实现Promise
重点难点,比较复杂,请参考我的另一篇步步拆解文章:
十五、使用ES5实现类的继承效果
也是重点知识,我之前做过详细拆解,有五个版本,如果每一版本都能说清楚,能够很好的体现自己对于原型链的理解,文章地址: