js十大手撕代码
前言
js中有很多API贼好用,省下了很多工夫,你知道它的原理吗?这篇文章对它们做一个总结。
正文
一、手撕instanceof
- instanceof的原理:通过判断
对象的原型
是否等于构造函数的原型
来进行类型判断 - 代码实现:
const myInstanceOf=(Left,Right)=>{
if(!Left){
return false
}
while(Left){
if(Left.__proto__===Right.prototype){
return true
}else{
Left=Left.__proto__
}
}
return false
}
//验证
console.log(myInstanceOf({},Array)); //false
二、手撕call,apply,bind
call,apply,bind是通过this的显示绑定
来修改函数的this指向
1. call
call的用法:a.call(b) -> 将a的this指向b
我们需要借助隐式绑定
规则来实现call,具体实现步骤
如下:
往要绑定的那个对象(b)上挂一个属性,值为需要被调用的那个函数名(a),在外层去调用函数。
function foo(x,y){
console.log(this.a,x+y);
}
const obj={
a:1
}
Function.prototype.myCall=function(context,...args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn=Symbol('fn') //使用Symbol尽可能降低myCall对其他的影响
context[fn]=this //this指向foo
const res=context[fn](...args) //解构,调用fn
delete context[fn] //不要忘了删除obj上的工具函数fn
return res //将结果返回
}
//验证
foo.myCall(obj,1,2) //1,3
2. apply
apply和call的本质区别就是接受的参数形式不同,call接收零散
的参数,而apply以数组
的方式接收参数,实现思路完全一样,代码如下:
function foo(x,y){
console.log(this.a,x+y);
}
const obj={
a:1
}
Function.prototype.myApply=function(context,args){
if(typeof this !== 'function') return new TypeError('is not a function')
const fn=Symbol('fn') //尽可能降低myCall对其他的影响
context[fn]=this
context[fn](...args)
delete context[fn]
}
//验证
foo.myApply(obj,[1,2]) //1,3
3. bind
bind和call,apply的区别是会返回一个新的函数
,接收零散
的参数
需要注意的是,官方bind
的操作是这样的:
- 当new了bind返回的函数时,相当于new了foo,且new的
参数
需作为实参
传给foo - foo的this.a
访问不到
obj中的a
function foo(x,y,z){
this.name='zt'
console.log(this.a,x+y+z);
}
const obj={
a:1
}
Function.prototype.myBind=function(context,...args){
if(typeof this !== 'function') return new TypeError('is not a function')
context=context||window
let _this=this
return function F(...arg){
//判断返回出去的F有没有被new,有就要把foo给到new出来的对象
if(this instanceof F){
return new _this(...args,...arg) //new一个foo
}
_this.apply(context,args.concat(arg)) //this是F的,_this是foo的 把foo的this指向obj用apply
}
}
//验证
const bar=foo.myBind(obj,1,2)
console.log(new bar(3)); //undefined 6 foo { name: 'zt' }
三、手撕深拷贝
这篇文章中详细记录了实现过程
【js手写】浅拷贝与深拷贝
四、手撕Promise
思路:
- 我们知道,promise是有
三种状态
的,分别是pending
(异步操作正在进行),fulfilled
(异步操作成功完成),rejected
(异步操作失败)。我们可以定义一个变量保存promise的状态。 - resolve和reject的实现:把
状态变更
,并把resolve或reject中的值保存
起来留给.then使用
。 - 要保证
实例对象
能访问.then
,必须将.then挂在构造函数的原型上
- .then接收
两个函数
作为参数,我们必须对所传参数进行判断
是否为函数,当状态为fulfilled时,onFulfilled函数触发
,并将前面resolve中的值传给
onFulfilled函数;状态为rejected时同理。 - 当在promise里放一个
异步函数
(例:setTimeout)包裹
resolve或reject函数时,它会被挂起,那么当执行到.then时,promise的状态仍然是pending
,故不能触发
.then中的回调函数。我们可以定义两个数组
分别存放.then中的两个回调函数
,将其分别在resolve和reject函数中调用
,这样保证了在resolve和reject函数触发时,.then中的回调函数即能触发。
代码如下:
const PENDING = 'pending'
const FULFILLED = 'fullfilled'
const REJECTED = 'rejected'
function myPromise(fn) {
this.state = PENDING
this.value = null
const that = this
that.resolvedCallbacks = []
that.rejectedCallbacks = []
function resolve(val) {
if (that.state == PENDING) {
that.state = FULFILLED
that.value = val
that.resolvedCallbacks.map((cb)=>{
cb(that.value)
})
}
}
function reject(val) {
if (that.state == PENDING) {
that.state = REJECTED
that.value = val
that.rejectedCallbacks.map((cb)=>{
cb(that.value)
})
}
}
try {
fn(resolve, reject)
} catch (error) {
reject(error)
}
}
myPromise.prototype.then = function (onFullfilled, onRejected) {
const that = this
onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : v => v
onRejected= typeof onRejected === 'function' ? onRejected : r => { throw r }
if(that.state===PENDING){
that.resolvedCallbacks.push(onFullfilled)
that.resolvedCallbacks.push(onRejected)
}
if (that.state === FULFILLED) {
onFullfilled(that.value)
}
if (that.state === REJECTED) {
onRejected(that.value)
}
}
//验证 ok ok
let p = new myPromise((resolve, reject) => {
// reject('fail')
resolve('ok')
})
p.then((res) => {
console.log(res,'ok');
}, (err) => {
console.log(err,'fail');
})
五、手撕防抖,节流
这篇文章中详细记录了实现过程
面试官:什么是防抖和节流?如何实现?应用场景?
六、手撕数组API
1. forEach()
思路:
- forEach()用于
数组的遍历
,参数接收一个回调函数
,回调函数中接收三个参数
,分别代表每一项的值、下标、数组本身。 - 要保证数组能访问到我们自己手写的API,必须将其挂到
数组的原型上
。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
//代码实现
Array.prototype.my_forEach = function (callback) {
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this)
}
}
//验证
arr.my_forEach((item, index, arr) => { //111 111
if (item.age === 18) {
item.age = 17
return
}
console.log('111');
})
2. map()
思路:
- map()也用于
数组的遍历
,与forEach不同的是,它会返回一个新数组
,这个新数组是map接收的回调函数
的返回值
。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_map=function(callback){
const res=[]
for(let i=0;i<this.length;i++){
res.push(callback(this[i],i,this))
}
return res
}
//验证
let newarr=arr.my_map((item,index,arr)=>{
if(item.age>18){
return item
}
})
console.log(newarr);
//[
// undefined,
// { name: 'aa', age: 19 },
// undefined,
// { name: 'cc', age: 21 }
//]
3. filter()
思路:
- filter()用于
筛选过滤
满足条件的元素,并返回一个新数组
。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_filter = function (callback) {
const res = []
for (let i = 0; i < this.length; i++) {
callback(this[i], i, this) && res.push(this[i])
}
return res
}
//验证
let newarr = arr.my_filter((item, index, arr) => {
return item.age > 18
})
console.log(newarr); [ { name: 'aa', age: 19 }, { name: 'cc', age: 21 } ]
4. reduce()
思路:
- reduce()用于将数组中所有元素按指定的规则进行
归并计算
,返回一个最终值
。 - reduce()接收
两个参数
:回调函数、初始值(可选)。 - 回调函数中接收
四个参数
:初始值 或 存储上一次回调函数的返回值、每一项的值、下标、数组本身。 - 若不提供初始值,则从
第二项开始
,并将第一个值
作为第一次执行的返回值
。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_reduce = function (callback,...arg) {
let pre,start=0
if(arg.length){
pre=arg[0]
}
else{
pre=this[0]
start=1
}
for (let i = start; i < this.length; i++) {
pre=callback(pre,this[i], i, this)
}
return pre
}
//验证
const sum = arr.my_reduce((pre, current, index, arr) => {
return pre+=current.age
},0)
console.log(sum); //76
5. fill()
思路:
- fill()用于
填充
一个数组的所有元素,它会影响原数组
,返回值为修改后
的原数组
。 - fill()接收
三个参数
:填充的值、起始位置(默认为0)、结束位置(默认为this.length-1)。 - 填充遵循
左闭右开
的原则 不提供
起始位置和结束位置时,默认填充整个数组
。
代码实现:
Array.prototype.my_fill = function (value,start,end) {
if(!start&&start!==0){
start=0
}
end=end||this.length
for(let i=start;i<end;i++){
this[i]=value
}
return this
}
//验证
const arr=new Array(7).my_fill('hh',null,3) //往数组的某个位置开始填充到哪个位置,左闭右开
console.log(arr); //[ 'hh', 'hh', 'hh', <4 empty items> ]
6. includes()
思路:
- includes()用于判断数组中是否
包含
某个元素,返回值为true 或 false
- includes()提供
第二个参数
,支持从指定位置开始
查找
代码实现:
const arr = ['a', 'b', 'c', 'd', 'e']
Array.prototype.my_includes = function (item,start) {
if(start<0){start+=this.length}
for (let i = start; i < this.length; i++) {
if(this[i]===item){
return true
}
}
return false
}
//验证
const flag = arr.my_includes('c',3) //查找的元素,从哪个下标开始查找
console.log(flag); //false
7. join()
思路:
- join()用于将数组中的
所有元素
以指定符号
连接成一个字符串
代码实现:
const arr = ['a', 'b', 'c']
Array.prototype.my_join = function (s = ',') {
let str = ''
for (let i = 0; i < this.length; i++) {
str += `${this[i]}${s}`
}
return str.slice(0, str.length - 1)
}
//验证
const str = arr.my_join(' ')
console.log(str); //a b c
8. find()
思路:
- find()用于返回数组中
第一个满足条件
的元素
,找不到返回undefined
- find()的参数为一个
回调函数
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_find = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return this[i]
}
}
return undefined
}
//验证
let j = arr.my_find((item, index, arr) => {
return item.age > 19
})
console.log(j); //{ name: 'cc', age: 21 }
9. findIndex()
思路:
- findIndex()用于返回数组中
第一个满足条件
的索引
,找不到返回-1
- findIndex()的参数为一个
回调函数
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_findIndex = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return i
}
}
return -1
}
let j = arr.my_findIndex((item, index, arr) => {
return item.age > 19
})
console.log(j); //3
10. some()
思路:
- some()用来检测数组中的
元素
是否满足
指定条件。 - 若
有一个
元素符合条件,则返回true
,且后面的元素不
会再检测。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_some = function (callback) {
for (let i = 0; i < this.length; i++) {
if(callback(this[i], i, this)){
return true
}
}
return false
}
//验证
const flag = arr.some((item, index, arr) => {
return item.age > 20
})
console.log(flag); //true
11. every()
思路:
- every() 用来检测
所有元素
是否都符合
指定条件。 - 若
有一个
不满足条件,则返回false
,后面的元素都不
会再执行。
代码实现:
const arr = [
{ name: 'zt', age: 18 },
{ name: 'aa', age: 19 },
{ name: 'bb', age: 18 },
{ name: 'cc', age: 21 },
]
Array.prototype.my_every = function (callback) {
for (let i = 0; i < this.length; i++) {
if(!callback(this[i], i, this)){
return false
}
}
return true
}
//验证
const flag = arr.my_every((item, index, arr) => {
return item.age > 16
})
console.log(flag); //true
七、数组去重
1. 双层for循环 + splice()
let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
j-- //删除后j向前走了一位,下标需要减一,避免少遍历一位
}
}
}
return arr
}
console.log(unique(arr)) //[ 1, '1', 2, 3 ]
2. 排序后做前后比较
let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
let res = []
let seen //记录上一次比较的值
let newarr=[...arr] //解构出来,开辟一个新数组
newarr.sort((a,b)=>a-b) //sort会影响原数组 n*logn
for (let i = 0; i < newarr.length; i++) {
if (newarr[i]!==seen) {
res.push(newarr[i])
}
seen=newarr[i]
}
return res
}
console.log(unique(arr)) //[ 1, '1', 2, 3 ]
3. 借助include
let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
function unique(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
if(!res.includes(arr[i])){
res.push(arr[i])
}
}
return res
}
console.log(unique(arr)) //[ 1, '1', 2, 3 ]
4. 借助set
let arr = [1, 1, '1', '1', 2, 2, 2, 3, 2]
const res1 = Array.from(new Set(arr));
console.log(res1); //[ 1, '1', 2, 3 ]
八、数组扁平化
1. 递归
let arr1 = [1, 2, [3, 4, [5],6]]
function flatter(arr) {
let len = arr.length
let result = []
for (let i = 0; i < len; i++) { //遍历数组每一项
if (Array.isArray(arr[i])) { //判断子项是否为数组并拼接起来
result=result.concat(flatter(arr[i]))//是则使用递归继续扁平化
}
else {
result.push(arr[i]) //不是则存入result
}
}
return result
}
console.log(flatter(arr1)) //[ 1, 2, 3, 4, 5, 6 ]
2. 借助reduce (本质也是递归)
let arr1 = [1, 2, [3, 4, [5],6]]
const flatter = arr => {
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, [])
}
console.log(flatter(arr1)) //[ 1, 2, 3, 4, 5, 6 ]
3. 借助正则
let arr1 = [1, 2, [3, 4, [5],6]]
const res = JSON.parse('[' + JSON.stringify(arr1).replace(/\[|\]/g, '') + ']');
console.log(res) //[ 1, 2, 3, 4, 5, 6 ]
九、函数柯里化
思路:
- 函数柯里化是只传递给函数
一部分参数
并调用
它,让它返回一个函数
去处理剩下的参数
。 - 传入的参数
大于等于
原始函数fn的参数个数,则直接执行
该函数,小于
则继续对当前函数进行柯里化
,返回一个接受所有参数
(当前参数和剩余参数) 的函数
代码实现:
const my_curry = (fn, ...args) =>
args.length >= fn.length
? fn(...args)
: (...args1) => curry(fn, ...args, ...args1);
function adder(x, y, z) {
return x + y + z;
}
const add = my_curry(adder);
console.log(add(1, 2, 3)); //6
console.log(add(1)(2)(3)); //6
console.log(add(1, 2)(3)); //6
console.log(add(1)(2, 3)); //6
十、new方法
思路:
- new方法主要分为四步:
(1) 创建一个新对象
(2) 将构造函数中的this
指向该对象
(3) 执行构造函数中的代码(为这个新对象添加属性
)
(4)返回新对象
function _new(obj, ...rest){
// 基于obj的原型创建一个新的对象
const newObj = Object.create(obj.prototype);
// 添加属性到新创建的newObj上, 并获取obj函数执行的结果.
const result = obj.apply(newObj, rest);
// 如果执行结果有返回值并且是一个对象, 返回执行的结果, 否则, 返回新创建的对象
return typeof result === 'object' ? result : newObj;
}
结
总结不易,
作者:zt_ever
来源:juejin.cn/post/7253260410664419389
动动手指给个赞吧!💗来源:juejin.cn/post/7253260410664419389