最近在准备金三银四,刚好看到有一些常见的前端手写原理题目,遂记录下解题的思路(不是全部,好几道我也不会啊);
地址: 20道前端面试手撕题
下面正文开始
FDE1 事件委托
描述
请补全JavaScript代码,要求如下:
- 给"ul"标签添加点击事件
- 当点击某"li"标签时,该标签内容拼接".“符号。如:某"li"标签被点击时,该标签内容为”…"
注意: - 必须使用DOM0级标准事件(onclick)
dom0级事件只有两个,一个是直接在dom上绑定onclick事件,另一个是获取dom元素并绑定onclick;
const ul = document.getElementsByTagName('ul')[0];
ul.onclick = function(e) {
e.target.innerText += e.target.innerText
}
FDE2 数组去重
数组去重方法太多了 这里略过吧 需要的可以看看之前的博客地址
FDE3 合法的URL
这里需要用到正则校验 正则我也不会(我是真的学不会),略过;
FDE4 快速排序
纯数字的数组从小到大的排序
注意:
- 数组元素仅包含数字
- 请优先使用快速排序方法
我好像是用sort直接排序的 没用快速
const _quickSort = array => {
// 补全代码
return array.sort((a, b) => a - b)
}
FDE5 全排列
没思路 后续再安排 略过
FDE6 instanceof
请补全JavaScript代码,要求以Boolean的形式返回第一个实例参数是否在第二个函数参数的原型链上。
直接用instanceof判断下
const _instanceof = (target, Fn) => {
// 补全代码
return target instanceof Fn
}
FDE7 手写array.map
map是执行一个函数,遍历数组并执行回调方法,回调函数第一个参数是当前遍历的值,第二个是索引,第三个是原数组。
直接for循环遍历来实现map
Array.prototype._map = function(cb) {
const list = [];
if (this.length) {
for(let i = 0; i < this.length; i++) {
list.push(cb(this[i], i, this))
}
}
return list
}
FDE8 手写filter
思路同上,不过filter是根据传入的条件来返回对应的值;
Array.prototype._filter = function(cb) {
const list = [];
if (this.length) {
for(let i = 0; i < this.length; i++) {
if (cb(this[i], i, this)) list.push(cb(this[i], i, this))
}
}
return list
}
FDE9 手写reduce
array.reduce也同上,遍历并累计每一次的值,并返回;
tips: 这几个手写遍历我都是当纯数字的数组来实现的
Array.prototype._reduce = function(cb, initValue = 0) {
if (!this.length) return initValue
// 先判断有没有默认值,没有则直接拿数组的第一项数据
let cur = initValue || this[0];
// 有默认值的话那就从索引0开始遍历,没有则从索引1开始遍历
const start = initValue ? 0 : 1;
for(let i = start; i < this.length; i++) {
cur += cb(cur, this[i], i, this)
}
return cur
}
FDE10 实现Object.create
object.create是创建一个新对象并绑定到这个对象的原型上;
const _objectCreate = proto => {
// 补全代码
const obj = {};
obj.__proto__ = proto;
return obj
}
FDE11 手写call
call apply bind 用来改变this指向的方法
Function.prototype._call = function(target, ...ctx) {
// 通过隐式绑定来指定this
target.fn = this;
const result = target.fn(...ctx);
delete target.fn;
return result
}
// apply方法同理 只不过第二个参数是数组
FDE12 手写bind
bind方法不是立即就执行的 它有一个收集参数的过程,类似函数柯里化;所以它应该是return了一个函数
Function.prototype._bind = function(target, ...ctx) {
const fn = this;
return function _fn(...arg) {
// 判断this当前指向,如果在_fn上则通过new 显示绑定把它指向fn
if (this instanceof _fn) return new fn(...ctx, ...arg)
// 如果不在_fn上则通过call或者apply来改变并指向传入的第一个参数
else return fn.call(target, ...ctx, ...arg)
}
}
FDE13 手写new
首先得先明白new做了什么;
创建一个新对象并指向对象的原型,改变this指向这个新对象;
const _new = function() {
// 获取第一个参数
const Constructor = [].shift.call(arguments);
const obj = {};
obj.__proto__ = Constructor.prototype;
Constructor.call(obj);
return obj
}
FDE14 手写Object.freeze
object.freeze是冻结一个对象使其无法更改数据,需要用到Object.seal和Object.defineProperty;
Object.seal是封闭一个对象使其无法新增属性并将现有的数据标记为不可配置;
Object.defineProperty是vue2那个数据拦截代理
const _objectFreeze = object => {
// 先判断是不是对象
if (object instanceof Object) {
Object.seal(object);
// for in遍历对象
for(let key in object) {
// 判断值是不是一个对象 需不需要做深层遍历
if (object[key] instanceof Object) _objectFreeze(object[key])
Object.defineProperty(object, key, {
// 将写设置为false即可无法更改值
writable: false
})
}
}
}
FDE15 实现浅拷贝一个对象
注意:
- 参数可能包含函数、正则、日期、ES6新对象
浅拷贝对象那不直接Object.assign不就好了吗
const _shallowClone = target => {
return Object.assign({}, target)
}
FDE16 实现一个简单的深拷贝
注意:
- 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中]
- 无需考虑循环引用问题
不需要考虑那些引用啥的 这么简单 那我直接JSON.parse(JSON.stringify(obj))不就OK啦
const _sampleDeepClone = target => {
return JSON.parse(JSON.stringify(target))
}
如果不允许使用json这个取巧的办法那只能递归来实现了 太简单了就略了
FDE17 复杂的深拷贝
注意:
- 需要考虑函数、正则、日期、ES6新对象
- 需要考虑循环引用问题
复杂的深拷贝,现在基本都用es6的 new Map()来写简单一点;Map是类似对象的一种结构,它的key可以是对象之类的引用类型。需要注意的是题中有指明要考虑到正则/日期等这种无法遍历的类型,所以要考虑下这种情况要怎么处理。
const _completeDeepClone = (target, map = new WeakMap()) => {
// 基础类型和函数
if (!target || typeof target !== 'object') return target
// 日期类型
if (target instanceof Date) return new Date(target)
// 正则
if (target instanceof RegExp) return new RegExp(target)
// map中存在 直接返回
if (map.get(target)) return map.get(target)
// 创建引用的实例类型
let cloneConstr = new target.constructor;
// 存入到map中
map.set(target, cloneConstr);
// 遍历引用类型并一一复制
for(let key in target) {
if (target.hasOwnProperty(key)) {
cloneConstr[key] = _completeDeepClone(target[key], map)
}
}
return cloneConstr
}
优化了深拷贝方法
FDE18 实现寄生式组合继承
描述
请补全JavaScript代码,要求通过寄生组合式继承使"Chinese"构造函数继承于"Human"构造函数。要求如下:
- 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
- 给"Chinese"构造函数的原型上添加"getAge"函数,该函数返回调用该函数对象的"age"属性
寄生式组合继承之前做过笔记,点这里看
function Human(name) {
this.name = name
this.kingdom = 'animal'
this.color = ['yellow', 'white', 'brown', 'black']
}
function Chinese(name,age) {
this.color = 'yellow';
Human.call(this, ...arguments)
}
Human.prototype.getName = function() {
return this.name
}
Chinese.prototype.getAge = function() {
return this.age
}
Chinese.prototype = Object.create(Human.prototype);
Chinese.prototype.constructor = Chinese
剩下19-20,大概是跟vue2那个Object.defineProperty的get set逻辑一样,我也不废啊 待我研究研究吧
FDE19 发布订阅模式
研究了下发布订阅模式 发现实现起来其实还挺简单的,就是为啥牛客网的测试用例通过不了啊??
我丢… 牛客网里js的编译环境一定要在constructor里初始化吗
描述
请补全JavaScript代码,完成"EventEmitter"类实现发布订阅模式。
注意:
- 同一名称事件可能有多个不同的执行函数
- 通过"on"函数添加事件
- 通过"emit"函数触发事件
class EventEmitter {
// 补全代码
// events = {};
constructor() {
this.events = {}
}
on(event, cb) {
if (this.events.hasOwnProperty(event) && Array.isArray(this.events[event])) {
this.events[event].push(cb)
} else {
this.events[event] = [cb]
}
}
// 只执行一次的话
once(event, cb) {
const that = this;
function handler(arg) {
cb.call(that, arg);
that.off(event)
}
this.on(event, handler)
}
// 移除发布订阅
off(event) {
if (this.events.hasOwnProperty(event)) delete this.events[event]
}
emit(event, data = null) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(data))
} else {
alert('请预先发布该事件!!')
}
}
}
FDE 20 观察者模式
描述
请补全JavaScript代码,完成"Observer"、"Observerd"类实现观察者模式。要求如下:
- 被观察者构造函数需要包含"name"属性和"state"属性且"state"初始值为"走路"
- 被观察者创建"setObserver"函数用于保存观察者们
- 被观察者创建"setState"函数用于设置该观察者"state"并且通知所有观察者
- 观察者创建"update"函数用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性
注意: - "Observer"为观察者,"Observerd"为被观察者
观察者模式,可以理解成在拍卖场里的拍卖物和竞拍者。在我理解看来 就是说 多个个观察者 一个被观察者(主题对象);当被观察者里面变量更新了,观察者们能够检测到并触发相关的方法;
类似vue/react中的生命周期函数,vue/react实例就是被观察者,它们的生命周期就是观察者,当实例进行到某一个状态时,生命周期会观察到并自动触发;
需要注意的是 被观察者(主题)只能有一个,但是观察者可以有无数个,它们是一对多的关系
class Observerd {
constructor(name) {
this.name = name;
this.state = '走路';
this.observers = []
}
// 保存观察者
setObserver(observe) {
this.observers.push(observe)
}
// 更新状态
setState(state) {
this.state = state;
// 通知
this.notifyAllObservers()
}
// 通知观察者
notifyAllObservers() {
this.observers.forEach(item => {
item.update(this.name, this.state)
})
}
}
class Observer {
update(name, state) {
console.log(`${name}正在${state}`)
}
}