前言

本文是面试系列篇的实现篇。笔者整理了面试过程中可能会遇到的手写实现,以及它的原理。这可以帮助面试者在笔试环节获得良好的加分。

正文
apply和call
apply和call的实现中,主要是利用了两个点:
根据传入的thisArgs去创建对象
在新创建的对象上定义当前函数,然后进行调用
apply和call的区别:
apply在指定对象后只接受一个参数
call在指定对象后可以接受多个参数
代码实现如下:
/**
* apply模拟实现
*/
Function.prototype.myApply = function(thisArg, arrArg) {
//#1 类型检测
if (typeof this !== 'function') {
throw new TypeError(`${this}.apply is not a function`);
}
//#2 arrArg和thisArg参数判断
if (arrArg === undefined || arrArg === null) {
arrArg = [];
}
if (thisArg === undefined || thisArg === null) {
thisArg = _self;
}

//#3 创建对象,函数赋值,调用对象方法
const obj = new Object(thisArg);
obj['_fn_'] = this;
var result = obj['_fn_'](...arrArg);
delete obj['_fn_'];
return result;
}

/**
* call模拟实现
*/
Function.prototype.myCall = function(thisArg) {
//#1 类型检测
if (typeof this !== 'function') {
throw new TypeError(`${this}.call is not a function`);
}

//#2 参数调整
const argumentsArr = [];
for (let i = 1; i < arguments.length; i++) {
argumentsArr.push(arguments[i]);
}
if (thisArg === undefined || thisArg === null) {
thisArg = _self;
}

//#3 创建对象,函数赋值,调用对象函数
const obj = new Object(thisArg);
var _fn = '_fn_';
obj[_fn] = this;
var result = obj[_fn](...argumentsArr);
delete obj[_fn];
return result;
}
复制代码
bind和softBind实现
bind和softBind的区别:
bind的绑定对象是固定的,softBind相当于是一种软绑定【如果this为空,或者指向全局对象时,才改变函数的绑定】
bind和apply & call的区别:
bind返回一个函数;apply和call是调用时执行的
代码实现如下:
/**
* bind实现
*/
Function.prototype.myBind = function(obj) {
//#1 类型校验
if (typeof this === 'function') {
throw new TypeError(`${this}.bind is not a function`);
}

//#2 取参
const args = Array.prototype.shift.call(arguments) || [];
const fn = this;

//#3 定义函数,调用apply改变this指向,同时改变新函数的原型
var bindFn = function() {
return this.apply(obj, [].concat(args, arguments));
}
bindFn.prototype = fn.prototype;
return bindFn;
}

/**
* softBind的实现
*/
Function.prototype.softBind = function(obj) {
//#1 类型校验
if (typeof this === 'function') {
throw new TypeError(`${this}.softBind is not a function`);
}

//#2 取参
const args = Array.prototype.shift.call(arguments) || [];
const fn = this;

//#3 定义函数,调用apply,此处注意对this的判断,改变新函数的原型
var softBindFn = function() {
return fn.apply((!this || this === (window || global) ? obj : this), [].concat(args, arguments));
}
softBindFn.prototype = fn.prototype;
return softBindFn;
}
复制代码
new的实现
new实现的过程:
创建一个新的对象
将新对象的隐式引用(proto)指向构造函数的原型
通过apply调用构造函数,给这个对象赋值
最后判断返回的对象是否是一个对象,然后结果
代码实现如下:
/**
* new模拟实现
*/
function myNew() {
//#1 创建一个空对象
let obj = Object.create({});
//#2 取出构造函数
let Constructor = Array.prototype.unshift.call(arguments);

//#3 改变新对象的隐式指向
obj._proto_ = Constructor.prototype;

//#4 调用构造函数,返回结果
ret = Constructor.apply(obj, arguments);

//#5 对结果进行判断
return ret instanceof Object ? ret : obj;
}
复制代码
instanceof的实现
instance的实现:
取出隐式引用(proto),然后和原型进行比较
依次往上循环,直到__proto__为null时
代码实现如下:
/**
* 实现instanceof
*/
function instanceFn(L, R) {
//#1 取出R的原型
const O = R.prototype;

//#2 取L的隐式指向,依次在原型链上判断
L = L._proto_;
while(true) {
if (L === null) {
//#3 当L为空时,返回false
return false;
} else {
//#4 当O与L相等是,说明继承于R
if (O === L) {
return true;
}
L = L._proto_;
}
}
}
复制代码
ES6的extend实现
ES6中的extend实现的原理:
将子构造函数的原型指向父构造函数的实例
然后改变原型中的构造函数(constructor)指向子构造函数
然后将子构造函数的隐式指向(proto)指向父构造函数
代码实现如下:
/**
* extend模拟实现
*/
function _inherits(subType, superType) {
//#1 创建对象,增强对象,指定对象
subType.prototype = Object.create(superType && superType.prototype, {
constructor: {
value: subType,
writable: true,
configurable: true,
enumerable: false
}
});

//#2 给子构造函数的隐式指向父构造函数
if (superType) {
Object.setPrototypeOf ? Object.setPrototypeOf(subType, superType) : subType._proto_ = superType;
}
}
复制代码
深拷贝和浅拷贝的实现
浅拷贝,就是只进行一层拷贝:
把对象的key取出来,然通过hasOwnProperty判断,赋值
深拷贝,就是无限层级的拷贝:
通过Map的形式,解决循环引用,如果对象存在,则直接返回
通过getOwnPropertySymbols的方法,解决Symbol值的拷贝
循环递归,拷贝对象属性
代码实现如下:
/**
* 浅拷贝
*/
function shallowCopy(obj) {
//#1 类型校验
if (obj !== 'object') return obj;

const target = {};

//#2 遍历对象的key
for (let key in obj) {
//#3 判断key是否在对象上
if (obj.hasOwnProperty(key)) {
target[key] === obj[key];
}
}
return target;
}
/**
* 深拷贝
*/
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
function deepCopy(source, hash = new WeakMap()) {
//#1 类型校验
if (!isObject(source)) return source;
//#2 解决循环引用
if (hash.get(source)) return hash.get(source);
//#3 创建新对象,存入hash中
const target = Array.isArray(source) ? [] : {};
hash.set(source, target);

//#4 Symbol拷贝
const symbolArr = Object.getOwnPropertySymbols(source);
if (symbolArr.length) {
symbolArr.forEach(key => {
if (isObject(source[key])) {
target[key] = deepCopy(source[key], hash);
} else {
target[key] = source[key]
}
})
}
//#5 遍历对象key值
for (let key in source) {
//#6 判断是否是对象的key
if (source.hasOwnProperty(key)) {
//#7 判断属性类型
if (isObject(source[key])) {
target[key] = deepCopy(source[key], hash);
} else {
target[key] = source[key];
}
}
}
return target;
}
复制代码
防抖和节流的实现
节流:事件在一段时间内只会被触发一次,多次触发,只有一次生效
防抖:在事件触发n秒后再被调用,如果在这n秒内又被触发,则重新计时
代码实现如下:
/**
* 节流
*/
function throttle(fn, delay) {
//#1 定义上次触发时间
let last = 0;
return function() {
//#2 取参
const args = arguments;
const now = +new Date();

//#3 判断是否超过之前的时间
if (now < last + delay) {
last = now;
fn.apply(this, args);
}
};
}
/**
* 防抖
*/
function debounce(fn, delay) {
let timer;
return function(...args) {
//#1 判断定时器是否存在,清除定时器
if (timer) clearTimeout(timer);

//#2 重新调用setTimeout
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
复制代码
Promise的all和race实现
Promise.all:谁跑得慢,以谁为准,如果有一个失败,就返回失败结果
Promise.race:谁跑得快,以谁为准
代码实现如下:
/**
* all模拟实现
*/
Promise1.all = function (promises) {
//#1 计时器 & 结果数组
let count = 0;
const res = [];

//#2 传值函数,将结果放入结果数组
function processData (index, data, resolve) {
res[index] = data;
count++;
if (count === promises.length) {
resolve(res);
}
}

//#3 返回一个Promise
return new Promise((resolve, reject) => {
for (let j = 0; j < promises.length; j++) {
//#4 遍历promises数组,调用每个promise,将结果放入结果数组
promises[j].then(data => {
processData(j, data, resolve);
}, reject);
}
});
}
/**
* race模拟实现
*/
Promise1.race = function (promises) {
//#1 返回一个Promise
return new Promise((resolve, reject) => {
//#2 遍历promises数组,谁先返回,就用谁
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}