【牛客网】前端面试手撕题讲解

最近在准备金三银四,刚好看到有一些常见的前端手写原理题目,遂记录下解题的思路(不是全部,好几道我也不会啊);
在这里插入图片描述

地址: 20道前端面试手撕题

下面正文开始

FDE1 事件委托

在这里插入图片描述

描述

请补全JavaScript代码,要求如下:

  1. 给"ul"标签添加点击事件
  2. 当点击某"li"标签时,该标签内容拼接".“符号。如:某"li"标签被点击时,该标签内容为”…"
    注意:
  3. 必须使用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 快速排序

纯数字的数组从小到大的排序
注意:

  1. 数组元素仅包含数字
  2. 请优先使用快速排序方法

我好像是用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 实现浅拷贝一个对象

注意:

  1. 参数可能包含函数、正则、日期、ES6新对象

浅拷贝对象那不直接Object.assign不就好了吗


const _shallowClone = target => {
	return Object.assign({}, target)
}

FDE16 实现一个简单的深拷贝

注意:

  1. 参数对象和参数对象的每个数据项的数据类型范围仅在数组、普通对象({})、基本数据类型中]
  2. 无需考虑循环引用问题

不需要考虑那些引用啥的 这么简单 那我直接JSON.parse(JSON.stringify(obj))不就OK啦

const _sampleDeepClone = target => {
	return JSON.parse(JSON.stringify(target))
}

如果不允许使用json这个取巧的办法那只能递归来实现了 太简单了就略了

FDE17 复杂的深拷贝

注意:

  1. 需要考虑函数、正则、日期、ES6新对象
  2. 需要考虑循环引用问题

复杂的深拷贝,现在基本都用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"构造函数。要求如下:

  1. 给"Human"构造函数的原型上添加"getName"函数,该函数返回调用该函数对象的"name"属性
  2. 给"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"类实现发布订阅模式。
注意:

  1. 同一名称事件可能有多个不同的执行函数
  2. 通过"on"函数添加事件
  3. 通过"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"类实现观察者模式。要求如下:

  1. 被观察者构造函数需要包含"name"属性和"state"属性且"state"初始值为"走路"
  2. 被观察者创建"setObserver"函数用于保存观察者们
  3. 被观察者创建"setState"函数用于设置该观察者"state"并且通知所有观察者
  4. 观察者创建"update"函数用于被观察者进行消息通知,该函数需要打印(console.log)数据,数据格式为:小明正在走路。其中"小明"为被观察者的"name"属性,"走路"为被观察者的"state"属性
    注意:
  5. "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}`)
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值