前言
不论是寒冬还是暖冬,找工作之前都需要做好充足的准备,面试的时候才能做到游刃有余。此文是把我最近找工作准备的以及笔试面试中涉及到的手写题做一个总结。给自己,也给需要的同学。
手写题是比较好准备的一个环节,大部分公司考察的题也就那么多,大都不会超出范围。
本文是手写题系列的第二篇文章。
往期:
- “ 寒冬 ” 三年经验前端面试总结(含头条、百度、饿了么、滴滴等)
- “ 寒冬 ” 三年经验前端面试总结(含头条、百度、饿了么、滴滴等)之手写题(一)
实现 eventEmitter
观察者模式是我们工作中经常能接触到的一种设计模式。用过 jquery
的应该对这种设计模式都不陌生。eventEmitter
是 node
中的核心,主要方法包括on、emit、off、once
。
class EventEmitter {constructor(){this.events = {}
}
on(name,cb){if(!this.events[name]){this.events[name] = [cb];
}else{this.events[name].push(cb)
}
}
emit(name,...arg){if(this.events[name]){this.events[name].forEach(fn => {fn.call(this,...arg)
})
}
}
off(name,cb){if(this.events[name]){this.events[name] = this.events[name].filter(fn => {return fn != cb})
}
}
once(name,fn){var onlyOnce = () => {fn.apply(this,arguments);
this.off(name,onlyOnce)
}
this.on(name,onlyOnce);
return this;
}
}
实现继承
继承是一个万年不变的考点。从 ES5 到 ES6,有许多继承方法。专门看有关继承的文章,一般都会从最基础的 prototype
原型链继承 到 借用父类构造函数的 call
继承 到二者的结合说起。本文只给出终极方法,如果想了解其他方法的话,可以自行搜索。
// ES5
function Parent(name,age){
this.name = name;
this.age = age;
}
Parent.prototype.say = function(){console.log('I am' + this.name)
}
function Child(name, age, sex){Parent.call(this,name,age);
this.sex = sex;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// ES6
class Parent {constructor(name,age){
this.name = name;
this.age = age;
}
}
class Child extends Parents{constructor(name,age,sex){super(name,age);
this.sex = sex; // 必须先调用 super,才能使用 this
}
}
实现 instanceof
首先要了解 instanceof
实现的功能,instanceof
运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上。其实考察的也是继承。
function myInstanceof(left,right){
var proto = left.__proto__;
var protoType = right.prototype;
while(true){if(proto === null){return false}
if(proto == protoType){return true}
proto = proto.__proto__
}
}
new 的过程
当我们 new 一个对象的时候,具体执行的是什么?MDN
上给的说明如下:
- 创建一个空的简单
JavaScript
对象(即{}); - 链接该对象(即设置该对象的构造函数)到另一个对象;
- 将步骤 1 新创建的对象作为
this
的上下文; - 如果该函数没有返回对象,则返回
this
。
以 var child = new Parent()
为例:
function newParent(){var obj = {}; // 首先创建一个对象
obj.__proto__ = Parent.prototype; // 然后将该对象的__proto__属性指向构造函数的 protoType
var result = Parent.call(obj) // 执行构造函数的方法,将 obj 作为 this 传入
return typeof(result) == 'object' ? result : obj
}
lazyMan
原题如下:
实现一个 LazyMan,可以按照以下方式调用:
LazyMan("Hank")输出:
Hi! This is Hank!
LazyMan("Hank").sleep(10).eat("dinner")输出
Hi! This is Hank!
// 等待 10 秒..
Wake up after 10
Eat dinner~
LazyMan("Hank").eat("dinner").eat("supper")输出
Hi This is Hank!
Eat dinner~
Eat supper~
LazyMan("Hank").sleepFirst(5).eat("supper")输出
// 等待 5 秒
Wake up after 5
Hi This is Hank!
Eat supper
以此类推。
这道题主要考察的是链式调用、任务队列、流程控制等。关键是用手动调用 next 函数来进行下次事件的调用,类似 express
中间件和 vue-router
路由的执行过程。
function _LazyMan(name){
this.nama = name;
this.queue = [];
this.queue.push(() => {console.log("Hi! This is" + name + "!");
this.next();})
setTimeout(()=>{this.next()
},0)
}
_LazyMan.prototype.eat = function(name){this.queue.push(() =>{console.log("Eat" + name + "~");
this.next()})
return this;
}
_LazyMan.prototype.next = function(){var fn = this.queue.shift();
fn && fn();}
_LazyMan.prototype.sleep = function(time){this.queue.push(() =>{setTimeout(() => {console.log("Wake up after" + time + "s!");
this.next()},time * 1000)
})
return this;
}
_LazyMan.prototype.sleepFirst = function(time){this.queue.unshift(() =>{setTimeout(() => {console.log("Wake up after" + time + "s!");
this.next()},time * 1000)
})
return this;
}
function LazyMan(name){return new _LazyMan(name)
}
实现 jsonp
jsonp
的作用是跨域。原理是通过动态插入 script
标签来实现跨域,因为 script
脚本不受同源策略的限制。它由两部分组成:回调函数和数据。举例:
function handleResponse(response){alert("You’re at IP address" + response.ip + ", which is in" +response.city + "," + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script,document.body.firstChild);
}
根据上面的例子,下面来实现一个通用的 JSONP
函数
function jsonp(obj) {const {url,data} = obj;
if (!url) return
return new Promise((resolve, reject) => {const cbFn = `jsonp_${Date.now()}`
data.callback = cbFn
const head = document.querySelector('head')
const script = document.createElement('script')
const src = `${url}?${data2Url(data)}`
console.log('scr',src)
script.src = src
head.appendChild(script)
window[cbFn] = function(res) {res ? resolve(res) : reject('error')
head.removeChild(script)
window[cbFn] = null
}
})
}
function data2Url(data) {return Object.keys(data).reduce((acc, cur) => {acc.push(`${cur}=${data[cur]}`)
return acc
}, []).join('&')
}
// jsonp({url:'www.xxx.com',data:{a:1,b:2}})
函数 currying
函数柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术,是高阶函数的一种用法。比如求和函数add(1,2,3)
, 经过柯里化后变成add(1)(2)(3)
function currying(fn,...args){if(fn.length <= args.length){return fn(...args)
}
return function(...args1){return currying(fn,...args,...args1)
}
}
function add(a,b,c){return a + b + c}
add(1,2,3) // 6
var curryingAdd = currying(add);
curryingAdd(1)(2)(3) // 6
写在最后
有错误之处还请小伙伴们及时指出,以免误人子弟。想看往期内容,翻到页面最上面有链接~