面试题里的那些各种手写

30次阅读

共计 5865 个字符,预计需要花费 15 分钟才能阅读完成。

最近准备初级前端面试,发现有很多手写实现什么的,例如什么手写实现 bind,promise。手写 ajax,手写一些算法。翻阅了很多书籍和博客。
这里做一个总结改进,算是对我后面大概为期一个月找工作的准备。
手写实现 bind()

Function.prototype._bind = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBind = function () {
var bindArgs = Array.prototype.slice.call(arguments);
return self.apply(this instanceof fBind ? this : context, args.concat(bindArgs));
}
fBind.prototype = self.prototype&&Object.create(self.prototype)
return fBind;
}
简单的说明:

这里之所以传参的时候需要两个数组,是因为考虑到用 new 以构造函数的形式调用硬绑定函数的情况:this 这时是应该指向实例对象的。
这样子就需要继承之前函数的方法,fBind.prototype = self.prototype&&Object.create(self.prototype)

, 同时也可以用 Object.setPrototypeOf(fBind.prototype,self.prototype)。考虑到存在 undefined 的情况,前面加一个判断 self.prototype&&…..
关于 apply 的第一个参数,如果考虑到之前的情况,是不能传入 context 的,这需要做一个判断。
像是下面的情况
function Foo(price){

this.price =price
this.fn = ()=>{
console.log(‘hi fn’)
}
console.log(this.name)
}

Foo.prototype.sayMyName = function(){
console.log(this.name)
}
var Obj1 = {
name:’obj1′
}
var b =Foo._bind(Obj1)
b() //”obj1″
var c = new b(1000)//”i am c”
c.name = ‘i am c’
c.sayMyName()
这里的 this 的指向就是 c,它指向实例对象本身。
后面以这道题为引线面试官可能会追问:

什么是执行上下文
this 的判断
call,bind 的区别

手写一个函数实现斐波那契数列
首先拷一个阮神在他 es6 教程里的一个写法。
function* fibonacci() {
let [prev, curr] = [0, 1];
for (;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}

for (let n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
更精简的
const feibo= max=>{
let [a,b,i]= [0,1,1]
while(i++<=max) {
[a,b] = [b,a + b]
console.log(b)
}
return a
}
相对是非常简单的,感觉也不会多问啥,就不多说了。
手写一个简单的 ajax
let xhr = new XMLHttpRequest()

xhr.open(‘get’, url, true)

xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
console.log(‘ 请求完成 ’)
if(this.status >= 200 &&this.status<300){
conso.log(‘ 成功 ’)
}else{
consol.log(‘ 失败 ’)
}
}
}
xhr.onerror = function(e) {
console.log(‘ 连接失败 ’)
}
xhr.send()

大概是这么个意思就差不多了,顺势可能会问一些状态码和状态值的问题,或者直接问到关于 http 上面的问题。
原型继承
function inherit(supertype,subtype){
Object.setPrototypeOf(subtype.prototype,supertype.prototype)
subtype.prototype.constructor = subtype
}

function Car(name,power){
this.name=name
this.power = power
}

Car.prototype.showPower = function(){
console.log(this.power)
}

function Changan(price,name,power){
this.price = price
Car.call(this,name,power)
}

inherit(Car,Changan)

Changan.prototype.showName = function(){
console.log(this.name)
}

var star = new Changan(100000,’star’,500)

star.showPower()
防抖与节流
function debounce(fn,duration){
var timer
window.clearTimeout(timer)
timer = setTimeout(()=>{
fn.call(this)
},duration)
}
function throttle(fn,duration){
let canIRun
if(!canIRun)return
fn.call(this)
canIRun = false
setTimeout(()=>{
canIRun = true
},duration)
}

数组去重
我一般就用这两种,大部分情况都能应付了。
[…new Set(array)]
//hash
function unique(array) {
const object = {}
array.map(number=>{
object[number] = true
})
return Object.keys(object).map(//…….)
}// 大概是这样的意思,写法根据数组的不同可能会有所改变
深拷贝
应该是面试里面手写 xxx 出现频率最高的题了,无论是笔试还是面试。总是让你手写实现深拷贝函数。
事实上,js 并不能做到真正完全的标准的深拷贝
所以不管你写什么样的深拷贝函数都会有不适用的地方,这取决于使用的场景和拷贝的对象,如果面试官在这上面钻研比较深的话,是很难做到完美的。
既然这样就写个将就一点的深拷贝吧,面向面试的那种。
function deepClone(item) {
return result;
}

首先在类型判断上做一个选择,一般情况来说,用 new 创建的实例对象用 typeof 判断会出问题的,相比之下 instanceof 也不靠谱。这里面相对比较靠谱的 Object.prototype.toString.call(item)。(这个其实也不兼容到全部情况和性能要求,但是面向面试代码可读性会高一点)。
type = Object.prototype.toString.call(item).slice(8,this.length-1),
//[object String],[object Array],[object Object]

函数的拷贝,这里不能使用 bind,会改变 this 指向或影响后续使用 call 调用该拷贝的函数,大部分情况是不必要的,这里就直接赋值吧。

于是这里可以把基本数据类型和 Function 放一起。
fk= [‘String’,’Number’,’Boolean’,’Function’].indexOf(type)>-1

dom 对象的拷贝: result = item.cloneNode(true);

忽略正则

Date,[object Object], [object Array] 放到后面的判断
let other = {// 需要递归或者其他的操作
Array() {
result = []
item.forEach((child, index)=>{
hash.set(item, result);
result[index] = deepClone(child,hash)
})
},
Date(){
result = new Date(item)
},
Object(){
result = {}
Reflect.ownKeys(item).forEach(child=>{
hash.set(item, result);
result[child] = deepClone(item[child],hash)
})
}
}
other[type]()

这样子是不是相对清晰一些了,应付一般的情况应该差不多了,但是没考虑循环引用。
这里给个思路是使用 ES6 的 WeakMap, 不知道的兄弟可以看看阮神的 ES6 博客,为防止爆栈,我把循环引用直接扔给它,完美拷贝。就相当于
var wm = new WeakMap()

var obj = {
name:null
}
obj.name = obj
wm.set(obj,wm.get(obj))
console.log(wm)
现在就需要在开头检查一下循环引用,然后直接返回 WeakMap 对象键名为 item 参数对象的值 所以最终代码就是
function deepClone(item,hash = new WeakMap()) {
if (!item) return item
if (hash.has(item))return hash.get(item); // 检查循环引用
var result,
type = Object.prototype.toString.call(item).slice(8,this.length-1),
fk= [‘String’,’Number’,’Boolean’,’Function’].indexOf(type)>-1

if(fk){
result = item;// 直接赋值
}else if(item.nodeType && typeof item.cloneNode === “function”){
result = item.cloneNode(true); // 是否是 dom 对象
}else{

let other = {// 需要递归或者其他的操作
Array() {
result = []
item.forEach((child, index)=>{
hash.set(item, result);
result[index] = deepClone(child,hash)
})
},
Date(){
result = new Date(item)
},
Object(){
result = {}
Reflect.ownKeys(item).forEach(child=>{
hash.set(item, result);
result[child] = deepClone(item[child],hash)
})
}
}
other[type]()
}
return result;
}
意思就大概是这个意思,当然深拷贝的方法有很多,甚至不一定用到递归。面试官总会有找茬的地方的。我觉得我写的这个还是满足我现在找工作的级别要求的。
然后是我用来测试的对象
var obj1 = {
name:’obj1′,
one : {
a:new Date(),
b:new String(‘1-2’),
c:new Array([‘this’,’is’,1,{a:23}]),
d: function () {
if(true){
return ‘d’
}
},
e:new Number(15),
f:new Boolean(true)
},
two(x){
console.log(x+’ ‘+this.name)
},
three : [
{
a:’this is a’,
b:document.body,
c:{
a:[1,2,3,4,5,[13,[3],true],10],
b:{
a:{
a:[1,2,3]
},
b:2
}
}
},
],
four:[1,2]
}
obj1.name=obj1
obj1.four[3] = obj1
var copy = deepClone(obj1)

console.log(copy)
copy.two.call(window,’hi’)
## new
简单说下大概是这么一个过程

创建一个空对象
执行传入的构造函数,执行过程中对 this 操作就是对 这个空对象 进行操作。
返回这个空对象

模拟需要考虑的问题

是一个空对象, 这里面的写法 obj 原型链是没有上一级的,即不存在与其他任何对象之间的联系,虽然在这里面没多少区别:var obj = Object.create(null),

this 指向这个空对象:let rt = Constructor.apply(obj, arguments);

可以访问构造函数的原型链,Object.setPrototypeOf(obj, Constructor.prototype);

如果构造函数有返回值,并且是一个对象,那么实例对象拿到的就是这个对象 (应该只是值,并不是引用)。所以这里要做个判断 return typeof rt === ‘object’ ? rt : obj; 最终的代码

function _new(){
var obj = Object.create(null),
Constructor = [].shift.call(arguments);
Object.setPrototypeOf(obj, Constructor.prototype);
let rt = Constructor.apply(obj, arguments);
return rt instanceof Object ? rt : obj;
}
<br/> <br/>
快速排序
快排 : 代码精简了一点
function quickSort(arr){
if(arr.length<=1)return arr
var index = Math.floor(arr.length/2),
number = arr.splice(index,1)[0],
left = [],
right = [];
arr.forEach(item=>{
item<=number?left.push(item):right.push(item)
})
return quickSort(left).concat([number],quickSort(right))
}
这期间会不断更新并修改,这里面的手写实现您如果有更好的写法或者新的思路,也希望可以说明交流。最后谢谢大佬些的观看。

正文完
 0