前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
前言
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' }
三、手撕深拷贝
四、手撕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; }
结
总结不易,动动手指给个赞吧!💗
前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库