目录前言1. 实现一个 compose 函数2. 实现一个 pipe 函数3. 实现一个 forEach 函数4. 实现一个 map 函数5. 实现一个 filter 函数6. 自定
之前有陆续写过一些 javascript
手写题的文章,但是时间有点久了而且感觉写的太散了,于是我把之前的文章都删了,用这一篇文章做一个汇总,面试前看看 就酱~
对于手写题的理解:
手写题其实对面试者的要求会高一点,因为不仅要考我们对于 api 的使用情况,还考我们对原理的理解,而且还要求写出来。所以我这里的建议是:
forEach
的实现,很多人都不知道 forEach
第一个参数 callbackFn
其实是有三个参数的,另外 forEach
还有一个可选的 thisArg
参数。compose
是组合的意思,它的作用故名思议就是将多个函数组合起来调用。
我们可以把 compose
理解为了方便我们连续执行方法,把自己调用传值的过程封装了起来,我们只需要给 compose
函数我们要执行哪些方法,他会自动的执行。
实现:
const add1 = (num) => {
return num + 1;
};
const mul5 = (num) => {
return num * 5;
};
const sub8 = (num) => {
return num - 8;
};
const compose = function (...args) {
return function (num) {
return args.reduceRight((res, cb) => cb(res), num);
};
};
console.log(compose(sub8, mul5, add1)(1)); // 2
上面的例子,我要将一个数加1乘5再减8,可以一个一个函数调用,但是那样会很麻烦,使用 compose
会简单很多。
compose
在函数式编程非常常见,Vue
中就有很多地方使用 compose
,学会它有助于我们阅读源码。
注意 compose
是从右往左的顺序执行函数的,下面说的 pipe
函数 是从左往右的顺序执行函数。 pipe
和 compose
都是函数式编程中的基本概念,能让代码变得更好~~
pipe
函数和 compose
函数功能一样,只不过是从左往右执行顺序,只用将上面 reduceRight
方法改为 reduce
即可
const add1 = (num) => {
return num + 1;
};
const mul5 = (num) => {
return num * 5;
};
const sub8 = (num) => {
return num - 8;
};
const compose = function (...args) {
return function (num) {
return args.reduce((res, cb) => cb(res), num);
};
};
console.log(compose(sub8, mul5, add1)(1)); // -34
forEach()
方法能对数组的每个元素执行一次给定的函数。
需要注意的点有,
Array
的原型上callBackFn
, thisArg
两个参数callBackFn
是一个函数, 且具有三个参数undefined
Array.prototype.myForEach = function (callBackFn, thisArg) {
if (typeof callBackFn !== "function") {
throw new Error("callBackFn must be function");
}
thisArg = thisArg || this;
const len = thisArg.length;
for (let i = 0; i < len; i++) {
callBackFn.call(thisArg, thisArg[i], i, thisArg);
}
};
map()
方法创建一个新数组,这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成。
需要注意的点有,
Array
的原型上callBackFn
, thisArg
两个参数callBackFn
是一个函数, 且具有三个参数Array.prototype.myMap = function (callbackFn, thisArg) {
if (typeof callbackFn !== "function") {
throw new Error("callbackFn must be function");
}
const arr = [];
thisArg = thisArg || this;
const len = thisArg.length;
for (let i = 0; i < len; i++) {
arr.push(callbackFn.call(thisArg, thisArg[i], i, thisArg));
}
return arr;
};
filter()
方法创建给定数组一部分的浅拷贝,其包含通过所提供函数实现的测试的所有元素。
需要注意的点有,
Array
的原型上callBackFn
, thisArg
两个参数callBackFn
是一个函数, 且具有三个参数Array.prototype.myFilter = function (callbackFn, thisArg) {
if (typeof callbackFn !== "function") {
throw new Error("must be function");
}
const len = this.length;
thisArg = thisArg || this
const _newArr = [];
for (let i = 0; i < len; i++) {
if (callbackFn.call(thisArg, thisArg[i], i, thisArg)) {
if (typeof thisArg[i] === "object") {
_newArr.push(Object.create(thisArg[i]));
} else {
_newArr.push(thisArg[i]);
}
}
}
return _newArr;
};
这个其实跟 filter
挺像的,只不过一个是在数组中过滤元素,一个是在对象中过滤属性。
需要注意的点有,
Object
的原型上callBackFn
, thisArg
两个参数callBackFn
是一个函数, 且具有三个参数Object.prototype.filterProperty = function (callbackFn, thisArg) {
if (typeof callbackFn !== "function") {
throw new Error("must be function");
}
thisArg = thisArg || this;
const propArray = [];
for (let prop in thisArg) {
if (callbackFn.call(thisArg, prop, thisArg[prop], thisArg)) {
propArray.push(prop);
}
}
return propArray;
};
bind()
方法创建一个新的函数,在 bind()
被调用时,这个新函数的 this
被指定为 bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
需要注意的点有,
Function
的原型上thisArgs
传递的任何值都需转换为对象bind
中输入的原函数所需的参数需要在返回函数中能接上,意思就是下面两种方式都要支持foo.bind(obj,1,2)(3)
foo.bind(obj,1,2,3)
Function.prototype.myBind = function (thisArgs, ...args1) {
thisArgs = Object(thisArgs)
const _self = this;
// const args1 = Array.prototype.slice.call(arguments, 1);
return function (...args2) {
// const args2 = Array.prototype.slice.call(arguments, 1);
return _self.apply(thisArgs, args1.concat(args2));
};
};
call()
方法使用一个指定的 this
值和单独给出的一个或多个参数来调用一个函数。
需要注意的点有,
Function
的原型上thisArgs
和 原函数所需的其他参数thisArgs
是否为 undefined
, 如果为 undefined
要他赋值全局的对象。Function.prototype.myCall = function (thisArg, ...args) {
if (thisArg) {
thisArg = Object(thisArg);
} else {
thisArg = typeof window !== "undefined" ? window : global;
}
thisArg._fn = this;
const result = thisArg._fn(...args);
delete thisArg._fn;
return result;
};
apply()
方法调用一个具有给定 this
值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。
Function.prototype.myApply = function (thisArg, args) {
if (thisArg) {
thisArg = Object(thisArg);
} else {
thisArg = typeof window !== "undefined" ? window : global;
}
let result;
if (!args) {
result = thisArg._fn();
} else {
result = thisArg._fn(...args);
}
delete thisArg._fn;
return result;
};
:::note{title="备注"} 虽然这个函数的语法与 call()
几乎相同,但根本区别在于,call()
接受一个参数列表,而 apply()
接受一个参数的单数组。 :::
举个例子
下面有两个函数 foo
和 bar
,他们两个调用的结果都相同, 1 + 2 + 3 + 4 = 10,但是调用的方式却不同。
function foo(a,b,c,d) {
return a + b + c + d
}
function bar(a) {
return function(b) {
return function(c) {
return function(d) {
return a + b + c + d
}
}
}
foo(1,2,3,4) // 10
bar(1)(2)(3)(4) // 10
将函数foo变成bar函数的过程就叫柯里化
上面的 bar
函数还可以简写成
const bar = a => b => c => d => a + b + c + d
我们把上面的例子改一下 现在我们需要对函数参数做一些操作,a = a +2
, b = b * 2
, c = -c
如果全都写在一个函数中是下面这样的
function add (a,b,c) {
a = a + 2
b = b * 2
c = -c
return a + b + c
}
而柯里化后
function add(a,b,c) {
a = a + 2
return function(b,c) {
b = b * 2
return function(c) {
c = -c
return a + b + c
}
}
}
很明显,柯里化后的函数单一性更强了,比如在最外层函数的逻辑就是对a进行操作,第二层函数就是对b进行操作,最内层就是对c进行操作
这是柯里化的第一个好处:更具有单一性
我们简化一下第一个例子,现在需要一个加法函数
function add(a,b){
return a + b
}
add(5,1)
add(5,2)
add(5,3)
add(5,4)
可以发现每次都是5加上另一个数字,每次都要传5其实是没必要的
function add(a,b) {
// 复用的逻辑
console.log("+",a)
return function(b) {
return a + b
}
}
const add5 = add(5)
add5(2)
add5(3)
add5(5)
可以看到在外层的函数中的代码被复用了,也可以说是定制化了一个加5的函数
(终于到了手写了^_^)
function currying(fn) {
function curried(...args) {
}
return curried
}
function currying(fn) {
function curried(...args) {
// 判断当前已接收的参数个数是否与fn函数一致
// 1.当已经传入的参数数量 大于等于 需要的参数时,就执行函数
if(args.length >= fn.length) {
return fn.apply(this, args)
} else {
// 2.没达到个数时,需要返回一个新的函数,继续来接受参数
return function curried2(...args2) {
// 接收到参数后,需要递归调用curried来检查函数的个数是否达到
return curried.apply(this, [...args, ...args2])
}
}
}
return curried
}
function add (a,b,c,d) {
return a + b + c + d
}
const curryingFn = currying(add);
console.log(add(1, 2, 3, 4)); // 10
console.log(curryingFn(1)(2)(3)(4)); // 10
console.log(curryingFn(1, 2)(3)(4)); // 10
console.log(curryingFn(1, 2, 3)(4)); // 10
console.log(curryingFn(1, 2, 3, 4)); // 10
这题我会带大家先认识一下防抖和节流,然后在一步一步的实现一下,会有很多个版本,一步一步完善,但其实在实际的时候能基本实现v2版本就差不多了。
场景:在实际开发中,常常会碰到点击一个按钮请求网络接口的场景,这时用户如果因为手抖多点了几下按钮,就会出现短时间内多次请求接口的情况,实际上这会造成性能的消耗,我们其实只需要监听最后一次的按钮,但是我们并不知道哪一次会是最后一次,就需要做个延时触发的操作,比如这次点击之后的300毫秒内没再点击就视为最后一次。这就是防抖函数使用的场景
总结防抖函数的逻辑
场景:开发中我们会有这样的需求,在鼠标移动的时候做一些监听的逻辑比如发送网络请求,但是我们知道 document.onmousemove
监听鼠标移动事件触发频率是很高的,我们希望按照一定的频率触发,比如3秒请求一次。不管中间document.onmousemove
监听到多少次只执行一次。这就是节流函数的使用场景
总结节流函数的逻辑
const debounceElement = document.getElementById("debounce");
const handleClick = function (e) {
console.log("点击了一次");
};
// debounce防抖函数
function debounce(fn, delay) {
// 定一个定时器对象,保存上一次的定时器
let timer = null
// 真正执行的函数
function _debounce() {
// 取消上一次的定时器
if (timer) {
clearTimeout(timer);
}
// 延迟执行
timer = setTimeout(() => {
fn()
}, delay);
}
return _debounce;
}
debounceElement.onclick = debounce(handleClick, 300);
上面 handleClick
函数有两个问题,一个是 this
指向的是 window
,但其实应该指向 debounceElement
,还一个问题就是是无法传递传递参数。
优化:
const debounceElement = document.getElementById("debounce");
const handleClick = function (e) {
console.log("点击了一次", e, this);
};
function debounce(fn, delay) {
let timer = null;
function _debounce(...args) {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args) // 改变this指向 传递参数
}, delay);
}
return _debounce;
}
debounceElement.onclick = debounce(handleClick, 300);
有些时候我们想点击按钮的第一次就立即执行,该怎么做呢?
优化:
const debounceElement = document.getElementById("debounce");
const handleClick = function (e) {
console.log("点击了一次", e, this);
};
// 添加一个immediate参数 选择是否立即调用
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false; // 是否调用过
function _debounce(...args) {
if (timer) {
clearTimeout(timer);
}
// 如果是第一次调用 立即执行
if (immediate && !isInvoke) {
fn.apply(this.args);
isInvoke = true;
} else {
// 如果不是第一次调用 延迟执行 执行完重置isInvoke
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
}, delay);
}
}
return _debounce;
}
debounceElement.onclick = debounce(handleClick, 300, true);
有些时候我们设置延迟时间很长,在这段时间内想取消之前点击按钮的事件该怎么做呢?
优化:
const debounceElement = document.getElementById("debounce");
const cancelElemetnt = document.getElementById("cancel");
const handleClick = function (e) {
console.log("点击了一次", e, this);
};
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false;
function _debounce(...args) {
if (timer) {
clearTimeout(timer);
}
if (immediate && !isInvoke) {
fn.apply(this.args);
isInvoke = true;
} else {
timer = setTimeout(() => {
fn.apply(this, args);
isInvoke = false;
}, delay);
}
}
// 在_debounce新增一个cancel方法 用来取消定时器
_debounce.cancel = function () {
clearTimeout(timer);
timer = null;
};
return _debounce;
}
const debonceClick = debounce(handleClick, 5000, false);
debounceElement.onclick = debonceClick;
cancelElemetnt.onclick = function () {
console.log("取消了事件");
debonceClick.cancel();
};
最后一个问题,上面 handleClick
如果有返回值我们应该怎么接收到呢
优化:用Promise回调
const debounceElement = document.getElementById("debounce");
const cancelElemetnt = document.getElementById("cancel");
const handleClick = function (e) {
console.log("点击了一次", e, this);
return "handleClick返回值";
};
function debounce(fn, delay, immediate = false) {
let timer = null;
let isInvoke = false;
function _debounce(...args) {
return new Promise((resolve, reject) => {
if (timer) clearTimeout(timer);
if (immediate && !isInvoke) {
try {
const result = fn.apply(this, args);
isInvoke = true;
resolve(result); // 正确的回调
} catch (err) {
reject(err); // 错误的回调
}
} else {
timer = setTimeout(() => {
try {
const result = fn.apply(this, args);
isInvoke = false;
resolve(result); // 正确的回调
} catch (err) {
reject(err); // 错误的回调
}
}, delay);
}
});
}
_debounce.cancel = function () {
clearTimeout(timer);
timer = null;
};
return _debounce;
}
const debonceClick = debounce(handleClick, 300, true);
// 创建一个debonceCallBack用于测试返回的值
const debonceCallBack = function (...args) {
debonceClick.apply(this, args).then((res) => {
console.log({ res });
});
};
debounceElement.onclick = debonceCallBack;
cancelElemetnt.onclick = () => {
console.log("取消了事件");
debonceClick.cancel();
};
这里说一下最主要的逻辑,只要 这次监听鼠标移动事件处触发的时间减去上次触发的时间大于我们设置的间隔就执行想要执行的操作就行了
nowTime−lastTime>intervalnowTime - lastTime > intervalnowTime−lastTime>interval
nowTime
:这次监听鼠标移动事件处触发的时间
lastTime
:监听鼠标移动事件处触发的时间
interval
:我们设置的间隔
const handleMove = () => {
console.log("监听了一次鼠标移动事件");
};
const throttle = function (fn, interval) {
// 记录当前事件触发的时间
let nowTime;
// 记录上次触发的时间
let lastTime = 0;
// 事件触发时,真正执行的函数
function _throttle() {
// 获取当前触发的时间
nowTime = new Date().getTime();
// 当前触发时间减去上次触发时间大于设定间隔
if (nowTime - lastTime > interval) {
fn();
lastTime = nowTime;
}
}
return _throttle;
};
document.onmousemove = throttle(handleMove, 1000);
和防抖一样,上面的代码也会有 this
指向问题 以及 参数传递
优化:
const handleMove = (e) => {
console.log("监听了一次鼠标移动事件", e, this);
};
const throttle = function (fn, interval) {
let nowTime;
let lastTime = 0;
function _throttle(...args) {
nowTime = new Date().getTime();
if (nowTime - lastTime > interval) {
fn.apply(this, args);
lastTime = nowTime;
}
}
return _throttle;
};
document.onmousemove = throttle(handleMove, 1000);
上面的函数第一次默认是立即触发的,如果我们想自己设定第一次是否立即触发该怎么做呢?
优化:
const handleMove = (e) => {
console.log("监听了一次鼠标移动事件", e, this);
};
const throttle = function (fn, interval, leading = true) {
let nowTime;
let lastTime = 0;
function _throttle(...args) {
nowTime = new Date().getTime();
// leading为flase表示不希望立即执行函数
// lastTime为0表示函数没执行过
if (!leading && lastTime === 0) {
lastTime = nowTime;
}
if (nowTime - lastTime > interval) {
fn.apply(this, args);
lastTime = nowTime;
}
}
return _throttle;
};
document.onmousemove = throttle(handleMove, 3000, false);
如果最后一次监听的移动事件与上一次执行的时间不到设定的时间间隔,函数是不会执行的,但是有时我们希望无论到没到设定的时间间隔都能执行函数,该怎么做呢?
我们的逻辑是:因为我们不知道哪一次会是最后一次,所以每次都设置一个定时器,定时器的时间间隔是距离下一次执行函数的时间;然后在每次进来都清除上次的定时器。这样就能保证如果这一次是最后一次,那么等到下一次执行函数的时候就必定会执行最后一次设定的定时器。
const handleMove = (e) => {
console.log("监听了一次鼠标移动事件", e, this);
};
// trailing用来选择最后一次是否执行
const throttle = function (fn,interval,leading = true,trailing = false) {
let nowTime;
let lastTime = 0;
let timer;
function _throttle(...args) {
nowTime = new Date().getTime();
// leading为flase表示不希望立即执行函数
// lastTime为0表示函数没执行过
if (!leading && lastTime === 0) {
lastTime = nowTime;
}
if (timer) {
clearTimeout(timer);
timer = null;
}
if (nowTime - lastTime >= interval) {
fn.apply(this, args);
lastTime = nowTime;
return;
}
// 如果选择了最后一次执行 就设置一个定时器
if (trailing && !timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
lastTime = 0;
}, interval - (nowTime - lastTime));
}
}
return _throttle;
};
document.onmousemove = throttle(handleMove, 3000, true, true);
这题会考察面试者对边界情况的考虑,比如深拷贝的对象会有很多类型,你是不是都考虑到了呢?
我的建议是最好能实现到最终版本。一般会考察的重点就是看你有没有解决循环引用的问题。
还有一个问题是
// 深拷贝函数,参数是一个待拷贝的对象
function deepClone(originValue) {
// 如果传入的是null或者不是对象类型, 那么直接返回
if(originValue == null || typeof originValue !== 'object') {
return originValue
}
// 创建一个新对象,递归拷贝属性
const newObj = {}
for(const key in originValue) {
// 不拷贝原型上的
if(originValue.hasOwnProterty(key)) {
newObj[key] = deepClone(originValue[key])
}
}
return newObj
}
测试代码
const obj = {
name: 'zhangsan',
address: {
city: 'hangzhou'
}
}
const newObj = deepClone(obj)
obj.address.city = 'beijing'
console.log(newObj) // { name: 'zhangsan', address: { city: 'hangzhou' } }
可以看到obj.address.city改成了'beijing'但是newObj.address.city还是hangzhou。说明已经对属性是对象类型作了深拷贝
function deepClone(originValue) {
if(originValue == null || typeof originValue !== 'object') {
return originValue
}
// 判断传入的是数组还是对象
const newObj = Array.isArray(originValue) ? [] : {}
for(const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj
}
测试代码
const obj = {
name: 'zhangsan',
address: {
city: 'hangzhou'
},
friends: ['zhangsan', 'lisi']
}
const newObj = deepClone(obj)
obj.address.city = 'beijing'
obj.friends[0] = 'wangwu'
console.log(newObj)
// {
// name: 'zhangsan',
// address: { city: 'hangzhou' },
// friends: [ 'zhangsan', 'lisi' ]
//}
可以看到obj.friends[0]改成了'wangwu'但是newObj.friends[0]还是zhangsan。说明已经对属性是数组类型作了深拷贝
函数一般认为只使用来进行代码的复用,不需要进行深拷贝,但若实现会一下更好。
function deepClone(originValue) {
// 判断是否是函数类型
if (originValue instanceof Function) {
// 将函数转成字符串
let str = originValue.toString()
// 截取函数体内容字符串
let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}"))
// 利用截取函数体内容的字符串和函数的构造器重新生成函数并返回
return new Function(subStr)
}
if(originValue == null || typeof originValue !== 'object') {
return originValue
}
// 判断传入的是数组还是对象
const newObj = Array.isArray(originValue) ? [] : {}
for(const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj
}
测试
const obj = {
foo: function () {
console.log("function");
},
};
const newObj = deepClone(obj);
console.log(obj.foo === newObj.foo); // false
function deepClone(originValue) {
// 判断是否是函数类型
if (originValue instanceof Function) {
// 将函数转成字符串
let str = originValue.toString()
// 截取函数体内容字符串
let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}"))
// 利用截取函数体内容的字符串和函数的构造器重新生成函数并返回
return new Function(subStr)
}
// 判断是否是Date类型
if(originValue instanceof Date) {
return new Date(originValue.getTime())
}
// 判断是否是Set类型(浅拷贝)
if(originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是Map类型(浅拷贝)
if(originValue instanceof Map) {
return new Map([...originValue])
}
if(originValue == null && typeof originValue !== 'object') {
return originValue
}
const newObj = Array.isArray(originValue) ? [] : {}
for(const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
return newObj
}
测试
const obj = {
name: 'zhangsan',
address: {
city: 'hangzhou'
},
friends: ['zhangsan', 'lisi'],
set: new Set([1,2,3]),
map: new Map([['aaa',111], ['bbb', '222']]),
createTime: new Date()
}
const newObj = deepClone(obj)
console.log(newObj.set === obj.set) // false
console.log(newObj.map === obj.map) // false
console.log(newObj.createTime === obj.createTime) // false
function deepClone(originValue) {
// 判断是否是函数类型
if (originValue instanceof Function) {
// 将函数转成字符串
let str = originValue.toString()
// 截取函数体内容字符串
let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}"))
// 利用截取函数体内容的字符串和函数的构造器重新生成函数并返回
return new Function(subStr)
}
// 判断是否是Date类型
if(originValue instanceof Date) {
return new Date(originValue.getTime())
}
// 判断是否是Set类型(浅拷贝)
if(originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是Map类型(浅拷贝)
if(originValue instanceof Map) {
return new Map([...originValue])
}
// 判断是否是Smybol类型
if(typeof originValue === 'symbol') {
return Symbol(originValue.description)
}
if(originValue == null && typeof originValue !== 'object') {
return originValue
}
const newObj = Array.isArray(originValue) ? [] : {}
for(const key in originValue) {
newObj[key] = deepClone(originValue[key])
}
// 对key是Symbol做处理
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for(const key of symbolKeys) {
newObj[key] = deepClone(originValue[key])
}
return newObj
}
测试
const s1 = Symbol('aaa')
const s2 = Symbol('bbb')
const obj = {
name: 'zhangsan',
address: {
city: 'hangzhou'
},
friends: ['zhangsan', 'lisi'],
set: new Set([1,2,3]),
map: new Map([['aaa',111], ['bbb', '222']]),
createTime: new Date(),
eating: function() {
console.log(this.name + ' is eating')
},
s1: s1,
[s2]: {a: 1}
}
const newObj = deepClone(obj)
console.log(obj.s1 === newObj.s1) // flase
console.log(obj[s2] === newObj[s2]) // false
// 参数中设置一个WeakMap用来保存
function deepClone(originValue, map = new WeakMap()) {
// 判断是否是函数类型
if (originValue instanceof Function) {
// 将函数转成字符串
let str = originValue.toString()
// 截取函数体内容字符串
let subStr = str.substring(str.indexOf("{"), 1, str.lastIndexOf("}"))
// 利用截取函数体内容的字符串和函数的构造器重新生成函数并返回
return new Function(subStr)
}
// 判断是否是Date类型
if(originValue instanceof Date) {
return new Date(originValue.getTime())
}
// 判断是否是Set类型(浅拷贝)
if(originValue instanceof Set) {
return new Set([...originValue])
}
// 判断是否是Map类型(浅拷贝)
if(originValue instanceof Map) {
return new Map([...originValue])
}
// 判断是否是Smybol类型
if(typeof originValue === 'symbol') {
return Symbol(originValue.description)
}
if(originValue == null && typeof originValue !== 'object') {
return originValue
}
// 判断之前是否存过,如果有则直接获取返回
if(map.has(originValue)) {
return map.get(originValue)
}
const newObj = Array.isArray(originValue) ? [] : {}
// 创建的newObj放到map里
map.set(originValue, newObj)
for(const key in originValue) {
newObj[key] = deepClone(originValue[key], map)
}
// 对key是Symbol做处理
const symbolKeys = Object.getOwnPropertySymbols(originValue)
for(const key of symbolKeys) {
newObj[key] = deepClone(originValue[key], map)
}
return newObj
}
测试
const s1 = Symbol('aaa')
const s2 = Symbol('bbb')
const obj = {
name: 'zhangsan',
address: {
city: 'hangzhou'
},
friends: ['zhangsan', 'lisi'],
set: new Set([1,2,3]),
map: new Map([['aaa',111], ['bbb', '222']]),
createTime: new Date(),
eating: function() {
console.log(this.name + ' is eating')
},
s1: s1,
[s2]: {a: 1}
}
obj.info= obj
const newObj = deepClone(obj)
console.log(newObj)
创建一个空对象obj
obj
this
上obj
对象
function _new(TargetClass, ...args) {
// 1. 创建空对象,并将新对象的__proto__属性指向构造函数的prototype
const obj = Object.create(TargetClass.prototype)
// 2. 将这个新对象绑定到函数的this上,执行构造函数
const ret =TargetClass.apply(obj, args)
return typeof ret === "object" ? ret : obj;
}
// 测试
function Foo(name) {
this.name = name;
}
Foo.prototype.sayHello = function () {
console.log("this is " + this.name);
};
const f = _new(Foo, "hello");
f.sayHello(); // this is hello
console.log(f); // Foo { name: 'hello' }
// 父类: 公共属性和方法
function Person() {
this.name = "yjw"
}
// 父类定义一个吃的方法
Person.prototype.eating = function() {
console.log(this.name + ' is eating')
}
// 子类: 特有属性和方法
function Student() {
this.sno = '001'
}
// Student的原型对象指向一个Person的实例对象per
const per = new Person()
Student.prototype = per
// 子类定义一个学习的方法
Student.prototype.studying = function() {
console.log(this.name + ' is studying')
}
const stu = new Student()
console.log(stu)
console.log(stu.name) // stu对象中没有name属性 会去他的原型对象per上找 per对象上有name属性
stu.eating() // stu对象中没有eating方法 会去他的原型对象per上找per对象上也没eating方法 再往上去per的原型对象上找 per的原型对象上有eating方法
stu.studying()
Person
传递参数,因为这个对象是一次性创建的(没办法定制化)// 父类: 公共属性和方法
function Person(name) {
this.name = name
}
// 父类定义一个吃的方法
Person.prototype.eating = function() {
console.log(this.name + ' is eating')
}
// 子类: 特有属性和方法
function Student(name, sno) {
// 借用了父类的构造函数
Person.call(this, name)
this.sno = sno
}
// Student的原型对象指向一个Person的实例对象per
const per = new Person()
Student.prototype = per
// 子类定义一个学习的方法
Student.prototype.studying = function() {
console.log(this.name + ' is studying')
}
借用构造函数继承解决了上面的三个问题。但还是不够完美
// 父类: 公共属性和方法
function Person(name) {
this.name = name
}
// 父类定义一个吃的方法
Person.prototype.eating = function() {
console.log(this.name + ' is eating')
}
// 子类: 特有属性和方法
function Student(name, sno) {
// 借用了父类的构造函数
Person.call(this, name)
this.sno = sno
}
Student.prototype = Object.create(Person.prototype) // 原型式继承 不用new Person()多调用父类构造函数了
Object.defineProperty(Student.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: Student
}) // 改构造函数名称
// 子类定义一个学习的方法
Student.prototype.studying = function() {
console.log(this.name + ' is studying')
}
// 上面的 Object.create(Person.prototype) 也可以写成
Object.setPrototypeOf(Student.prototype, Person.prototype)
// 也可以写成
function object(o) {
function F() {}
F.prototype = o
return new F()
}
Student.prototype = object(Person.prototyp)
class MyPromise {
constructor(executor) {
// Promise 三种状态 分别是pending(进行中)、fulfilled(已成功)和rejected(已失败)
this.status = 'pending'
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if(this.status === 'pending') {
// 调用resolve 状态改为fulfilled
this.status = 'fulfilled'
// 保存resolve回调的参数
this.value = value
}
}
const reject = (reason) => {
if(this.status === 'pending') {
// reject 状态改为rejected
this.status = 'rejected'
// 保存reject回调的参数
this.reason = reason
}
}
// 传入的回调函数是会直接执行的
executor(resolve,reject)
}
}
class MyPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
const resolve = (value) => {
if(this.status === 'pending') {
// 延迟调用(微任务)
queueMicrotask(() => {
this.status = 'fulfilled'
this.value = value
this.onFulfilled && this.onFulfilled(this.value)
}, 0)
}
}
const reject = (reason) => {
if(this.status === 'pending') {
// 延迟调用(微任务)
queueMicrotask(() => {
this.status = 'rejected'
this.reason = reason
this.onRejected && this.onRejected(this.reason)
}, 0)
}
}
executor(resolve,reject)
}
then(onFulfilled, onRejected) {
onFulfilled && this.onFulfilled = onFulfilled
onRejected && this.onRejected = onRejected
}
}
const promise = new MyPromise((resolve, reject) => {
resolve('resolve')
reject('reject')
})
promise.then(res => {
console.log({res})
}, err => {
console.log({err})
})
上面 then
方法还有几个点需要优化
then
方法中的回调函数保存到数组中)then
方法中返回 Promise
)then
方法在 resolve
已经执行后再执行,目前 then
中的方法不能调用 (解决方法:then
方法中做判断,如果调用的时候状态已经确定下来,直接调用)setTimeout(() => {
promise.then(res =>{
console.log({res})
})
}, 10000)
// 封装一个函数
const execFunctionWithCatchError = (exeFn, value, resolve, reject) => {
try {
const result = exeFn(value)
resolve(result)
} catch(err) {
reject(err)
}
}
class MyPromise {
constructor(executor) {
this.status = 'pending'
this.value = undefined
this.reason = undefined
this.onFulfilledFns = []
this.onRejectFns = []
const resolve = (value) => {
if(this.status === 'pending') {
queueMicrotask(() => {
if(this.status !== 'pending') return
this.status = 'fulfilled'
this.value = value
this.onFulfilledFns.forEach(fn => {
fn && fn(this.value)
})
}, 0)
}
}
const reject = (reason) => {
if(this.status === 'pending') {
queueMicrotask(() => {
if(this.status !== 'pending') return
this.status = 'rejected'
this.reason = reason
this.onRejectFns.forEach(fn => {
fn && fn(this.reason)
})
}, 0)
}
}
try {
executor(resolve,reject)
} catch(err) {
reject(err)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// 如果在then调用的时候状态已经确定下来,直接调用
if(this.status === 'fulfilled' && onFulfilled) {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
}
// 如果在then调用的时候状态已经确定下来,直接调用
if(this.status === 'rejected' && onRejected) {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
}
if(this.status === 'pending') {
// 将成功的回调和失败的回调都放到数组中
if(onFulfilled) this.onFulfilledFns.push(() => {
execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
})
if(onRejected) this.onRejectFns.push(() => {
execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
})
}
})
}
}
catch(onRejected) {
return this.then(undefined, onRejected)
}
then(onFulfilled, onRejected) {
// 当onRejected为空时 我们手动抛出一个错误
onRejected = onRejected || (err => { throw err })
return new return new MyPromise((resolve, reject) => {
...
})
}
finally(onFinally) {
// 不管成功和失败都调用onFinally
this.then(onFinally, onFinally)
}
then(onFulfilled, onRejected) {
// 当onRejected为空时 我们手动抛出一个错误
onRejected = onRejected || (err => { throw err })
// 当onFulfilled为空时 将上一个promise的value传下去
onFulfilled = onFulfilled || (value => value)
return new return new MyPromise((resolve, reject) => {
...
})
}
static resolve(value) {
return new MyPromise((resolve) => resolve(value))
}
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason))
}
all
: 参数是一个 promises数组 返回一个 promise
在所有 promise
执行成功后返回所有 promise
结果的一个数组 有一个promise
失败就 reject
不管有没有 promise
rejected
,都返回所有 promise
结果
static all(promises) {
return new MyPromise((resolve, reject) => {
const values = []
promises.forEach(promise => {
promise.then(res => {
values.push(res)
if(values.length === promises.length) {
resolve(values)
}
}, err => {
reject(err)
})
})
})
}
static allSettled(promises) {
return new MyPromise((resolve, reject) => {
const result = []
promises.forEach(promise => {
promise.then(res => {
result.push({state: 'resolved', value: res})
if(result.length === promises.length) {
resolve(result)
}
}, err => {
result.push({state: 'rejected', reason: err})
if(result.length === promises.length) {
resolve(result)
}
})
})
})
}
promise
,只要一个 promise
有结果 立刻返回(竞赛)static race(promises) {
return new MyPromise((resolve, reject) => {
promises.forEach(promise => {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
})
})
}
static any(promises) {
return new MyPromise((resolve, reject) => {
const reasons = []
promises.forEach(promise => {
promise.then(res => {
resolve(res)
}, err => {
reasons.push(err)
if(reasons.length === promises.length) {
reject(reasons)
}
})
})
})
}
resolve
、reject
回调resolve
执行微任务队列:改变状态、获取value、then传入执行成功回调reject
执行微任务队列:改变状态、获取reason、then传入执行失败回调onFulfilled
、onRejected
为空给默认值Promise
resovle/reject
支持链式调用、promise
状态是否确定 确定的话 onFufilled/onRejected
直接执行push(() => { 执行onFulfilled/onRejected 直接执行代码 })
到此这篇关于JavaScript常见手写题超全汇总的文章就介绍到这了,更多相关js常见手写题内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: JavaScript常见手写题超全汇总
本文链接: https://lsjlt.com/news/176299.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-12
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0