前端面试手写篇
手写篇
1. 手写 instenceof
原生的
instanceof
1console.log([] instanceof Array) // true
2
3console.log('' instanceof Array) // false
手写myInstanceof
:
1function myInstanceof(left,right){
2
3 let proto = left.__proto__
4
5 let prototype = right.prototype
6
7 while(true){
8
9 if(proto === null)return false
10
11 if(proto === prototype)return true
12
13 proto = proto.__proto__
14
15 }
16}
17
18console.log(myInstanceof([],Array))// true
19
20console.log(myInstanceof('',Array))// false
实现原理:
通过不断的沿着原型链查找,如果找到顶端了即:
proto === null
,那么就说明没有找到,返回false,说明left
不是right
构造函数的实例如果找到隐式原型
proto
等于构造函数的原型prototype
,那么说明left
是right
构造函数的实例,返回true其它情况就是不断的改变
proto
,以便可以不断的往上查找
2. 手写 flat
原生示例:
1const arr1 = [1, 2, [3, 4]];
2arr1.flat();
3// [1, 2, 3, 4]
4
5const arr2 = [1, 2, [3, 4, [5, 6]]];
6arr2.flat();
7// [1, 2, 3, 4, [5, 6]]
8
9const arr3 = [1, 2, [3, 4, [5, 6]]];
10arr3.flat(2);
11// [1, 2, 3, 4, 5, 6]
12
13const arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
14arr4.flat(Infinity);
15// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
手写flatDeep:
1function flatDeep( arr, dep=1 ){
2 let ret = []
3
4 for(let i=0;i<arr.length;i++){
5
6 if(Array.isArray(arr[i])){
7
8 dep>0 ? (ret = ret.concat(flatter(arr[i],dep-1))):(ret.push(arr[i]))
9
10 }else{
11
12 ret.push(arr[i])
13 }
14 }
15
16 return ret
17}
实现原理:
第一个参数是数组,第二个是降维层级,
用for循环遍历这个数组,检测每一项
如果这项是不是数组则直接添加到
ret
结果数组里面否则根据降维层级判断,默认是降一维层级,当递归降维不满足
ret>0
,说明已经达到dep降维层数了,其它情况即ret.push(arr[i])
3. 手写 call
1Function.prototype.myCall = function(context){
2
3 context =(context === null || context === undefined) ? window : context
4
5 context.fn = this// 其实就等价于 obj.fn = function say(){} 当指向 context.fn 时,say里面的this 指向obj [关键]
6 //obj 此时变成 var obj = {name:'innerName',fn:function say(){console.log(this.name)}}
7
8 let args = [...arguments].slice(1) //截取第二个开始的所有参数
9 let result= context.fn(...args)//把执行的结果赋予result变量
10
11 delete context.fn //删除执行上下文上的属性 (还原)由var obj = {name:'innerName',fn:function say(){console.log(this.name)}}删除fn
12 return result
13}
14var name = 'outerName'
15var obj = {
16 name:'innerName'
17}
18function say(){
19 console.log(this.name)
20}
21say()//outerName 等价于 window.say this指向window
22say.myCall(obj)//innerName
实现原理:
函数的原型方法call 第一个参数是传入的执行上下文,后面传入的都是参数,以逗号隔开
当传入的是null或undefined是执行上下文是指向
window
,否使为传入的对象,然后再传入的对象身上添加fn
属性并把函数实例say函数赋值给fn,此时变成
var obj = {name:'innerName',fn:function say(){console.log(this.name)}}
此时context
就是obj对象啦,所有你执行context.fn(...args)
其实就是
obj.fn(...args)
,fn
其值是function say(){ console.log(this.name) }
,所以这个this
就变成obj
对象了然后就是结果赋值,对象还原
返回结果
4. 手写 apply
1Function.prototype.myApply = function(context){
2
3 context =(context === null || context === undefined) ? window : context
4
5 let result
6
7 context.fn = this
8
9 result = arguments[1] ? context.fn(...arguments[1]) : context.fn()
10
11 delete context.fn
12
13 return result
14}
同
myCall
实现原理大致相同,不同的是由于call
和apply
的传参方式不一样,我们需要额外的对第二个参数做判断,
apply
受参形式是数组,且再第二个参数位置,一:如果第二个参数存在,执行的时候就把第二个参数(数组形式)用扩展运算符打散后传入执行
二:如果第二个参数不存在,执行执行
其它就于
call
的实现一样
5. 手写 bind
1Function.prototype.myBind = function(context){
2
3 context =(context === null || context === undefined) ? window : context
4
5 let o = Object.create(context)
6
7 o.fn = this
8
9 let args = [...arguments].slice(1)
10
11 let fn= function(){
12
13 o.fn(...args)
14 }
15
16 return fn
17}
bind 的手写实现,与其它两个区别是返回一个函数,并没返回函数执行的结果,并且受参形式不受限制
实现原理:
通过
Object.create
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
,通过 中介对象o
来实现,来达到不影响传入的对象
6. 手写 new
new 一个函数的时候,会生成一个实例,该实例的隐式原型
__proto__
===该函数的prototype
原型对象在构造函数中this指向当前实例
最后再将实例对象返回
1function myNew(func){
2
3 //第一步 将函数的 prototype 指向 o 对象的__proto__
4 let o = Object.create(func.prototype)
5
6 //第二步 通过call改变 this的指向,使之指向 o
7 let ret = func.call(o)
8
9 //第三步 如果构造函数里面有返回对象,则返回这个对象,没有则返回 o 对象
10 return typeof ret === 'object' ? ret : o
11
12}
检测:
1function M(){}
2
3let m = myNew(M); // 等价于 new M 这里只是模拟
4console.log(m instanceof M); // instanceof 检测实例
5console.log(m instanceof Object);
6console.log(m.__proto__.constructor === M);