几个常见的js手写题,你能写出来几道
创始人
2024-06-03 01:11:41
0

实现 new 过程:

要点:

  1. 函数第一个参数是构造函数
  2. 实例的__proto__指向构造函数的原型属性prototype
  3. 函数剩余参数要挂载到一个实例对象上
  4. 构造函数有返回值时,就返回这个返回值
const createObj = function () {let obj = {}let Constructor = [].shift.call(arguments) // 1obj.__proto__ = Constructor.prototype // 2let ret = Constructor.apply(obj, arguments) // 3return typeof ret === 'object' ? ret: obj // 4
}// 使用
const Fun = function (name) {this.name = name
}
Fun.prototype.getName = function() {alert(this.name)
}
let fun = createObj(Fun, 'gim')
fun.getName() // gim

值得注意的是,es6的class必须用new调用,否则会报错,如下:

class Fun {constructor(name) {this.name = name}getName() {alert(this.name)}
}
let fun = createObj(Fun, 'gim')
fun.getName() // Uncaught TypeError: Class constructor Fun cannot be invoked without 'new'

手写 call、apply 及 bind 函数

共同点:

  1. 第一个参数是要绑定的this
  2. 函数内部的 this 其实是要执行绑定的函数(因为三者都是点调用)

bind

这里实现简单版本(new 调用结果不一样)

  1. bind函数执行后,要返回一个原函数的拷贝
  2. 给返回函数内部的 fn 绑定传入的 context
Function.prototype.myBind = function(context, ...args) {if (typeof this !== 'function') throw 'caller must be a function'const fn = thisreturn function() {return fn.call(context, ...args, ...arguments)}
}

callapply 函数的实现其实都借助了点调用。利用第一个参数做个中转,调用完之后删除。

call

Function.prototype.myCall = function(context = windows, ...args) {context._fn = thisconst result = context._fn(...args)delete context._fnreturn result
}

apply

Function.prototype.myApply = function(context = windows, args) {context._fn = thisconst result = context._fn(args)delete context._fnreturn result
}

节流和防抖

刚开始接触这俩概念的时候傻傻分不清楚。

浏览器的一些事件,如:resize,scroll,keydown,keyup,keypress,mousemove等。这些事件触发频率太过频繁,绑定在这些事件上的回调函数会不停的被调用。会加重浏览器的负担,导致用户体验非常糟糕。

节流防抖主要是利用了闭包。

节流

节流函数来让函数每隔 n 毫秒触发一次。

// 节流
function throttle (f, wait = 200) {let last = 0return function (...args) { // 以下 内部匿名函数 均是指这个匿名函数let now = Date.now()if (now - last > wait) {last = nowf.apply(this, args) // 注意此时 f 函数的 this 被绑定成了内部匿名函数的 this,这是很有用的}}
}
// 未节流
input.onkeyup = funciton () {$.ajax(url, this.value)
}
// 节流
input.onkeyup = throttle(function () { // throttle() 返回内部匿名函数,所以 input 被绑定到了内部匿名函数的 this 上$.ajax(url, this.value) // 注意这个 this 在执行时被 apply 到了内部匿名函数上的 this ,也就是 input
})

防抖

防抖函数让函数在 n 毫秒内只触发最后一次。

// 防抖
function debounce (f, wait = 200) {let timer = 0return function (...args) {clearTimeout(timer)timer = setTimeout(() => {f.apply(this, args)}, wait)}
}
// 未防抖
input.onkeyup = funciton () {$.ajax(url, this.value)
}
// 防抖
input.onkeyup = debounce(function () { // debounce() 返回内部匿名函数,所以 input 被绑定到了内部匿名函数的 this 上$.ajax(url, this.value) // 注意这个 this 在执行时被 apply 到了内部匿名函数上的 this ,也就是 input
})

参考 前端进阶面试题详细解答

柯里化函数

柯里化可以利用函数和不同的参数构成功能更加专一的函数。

柯里化其实就是利用闭包的技术将函数和参数一次次缓存起来,等到参数凑够了就执行函数。

function curry(fn, ...rest) {const length = fn.lengthreturn function() {const args = [...rest, ...arguments]if (args.length < length) {return curry.call(this, fn, ...args)} else {return fn.apply(this, args)}}
}
function add(m, n) {return m + n
}
const add5 = curry(add, 5)

Promise

要点:

  1. 三种状态的改变:pending fulfilled rejected
  2. resolve() reject() 函数的实现
  3. 关键点 then 链式调用的实现
class MyPromise {constructor(fn) {this.status = 'pending'this.value = nullthis.resolve = this._resolve.bind(this)this.reject = this._reject.bind(this)this.resolvedFns = []this.rejectedFns = []try {fn(this.resolve, this.reject)} catch (e) {this.catch(e)}}_resolve(res) {setTimeout(() => {this.status = 'fulfilled'this.value = resthis.resolvedFns.forEach(fn => {fn(res)})})}_reject(res) {setTimeout(() => {this.status = 'rejected'this.value = resthis.rejectedFns.forEach(fn => {fn(res)})})}then(resolvedFn, rejecetedFn) {return new MyPromise(function(resolve, reject) {this.resolveFns.push(function(value) {try {const res = resolvedFn(value)if (res instanceof MyPromise) {res.then(resolve, reject)} else {resolve(res)}} catch (err) {reject(err)}})this.rejectedFns.push(function(value){try {const res = rejectedFn(value)if (res instanceof MyPromise) {res.then(resolve, reject)} else {reject(res)}} catch (err) {reject(err)}})})}catch(rejectedFn) {return this.then(null, rejectedFn)}
}

this.resolvedFnsthis.rejectedFns中存放着 then 函数的参数的处理逻辑,待 Promise 操作有了结果就会执行。

then函数返回一个Promise实现链式调用。

其实面试的时候主要靠死记硬背,因为有一次 20 分钟让我写 5 个实现(包括promise),,,谁给你思考的时间。。。

深拷贝

乞丐版的

function deepCopy(obj) {//判断是否是简单数据类型,if (typeof obj == "object") {//复杂数据类型var result = obj.constructor == Array ? [] : {};for (let i in obj) {result[i] = typeof obj[i] == "object" ? deepCopy(obj[i]) : obj[i];}} else {//简单数据类型 直接 == 赋值var result = obj;}return result;
}

观察者模式和发布订阅模式

观察者模式观察者Observer和主体Subject都比较清晰,而发布订阅模式的发布和订阅都由一个调度中心来处理,发布者和订阅者界限模糊。

观察者模式存在耦合,主体中存储的是观察者实例,而 notify 方法遍历时调用了观察者的 update 方法。而发布订阅模式是完全解耦的,因为调度中心中存的直接就是逻辑处理函数。

要点:都要实现添加/删除/派发更新三个事件。

观察者模式

class Subject {constructor() {this.observers = []}add(observer) {this.observers.push(observer)this.observers = [...new Set(this.observers)]}notify(...args) {this.observers.forEach(observer => observer.update(...args))}remove(observer) {let observers = this.observersfor (let i = 0, len = observers.length; i < len; i++) {if (observers[i] === observer) observers.splice(i, 1)}}
}class Observer {update(...args) {console.log(...args)}
}let observer_1 = new Observer() // 创建观察者1
let observer_2 = new Observer()
let sub = new Subject() // 创建主体
sub.add(observer_1) // 添加观察者1
sub.add(observer_2)
sub.notify('I changed !')

发布订阅模式

这里使用了还在提案阶段的 class 的私有属性 #handlers,但是主流浏览器已支持。

class Event {// 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)#handlers = {}// 事件添加方法,参数有事件名和事件方法addEventListener(type, handler) {// 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器if (!(type in this.#handlers)) {this.#handlers[type] = []}// 将事件存入this.#handlers[type].push(handler)}// 触发事件两个参数(事件名,参数)dispatchEvent(type, ...params) {// 若没有注册该事件则抛出错误if (!(type in this.#handlers)) {return new Error('未注册该事件')}// 便利触发this.#handlers[type].forEach(handler => {handler(...params)})}// 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)removeEventListener(type, handler) {// 无效事件抛出if (!(type in this.#handlers)) {return new Error('无效事件')}if (!handler) {// 直接移除事件delete this.#handlers[type]} else {const idx = this.#handlers[type].findIndex(ele => ele === handler)// 抛出异常事件if (idx === -1) {return new Error('无该绑定事件')}// 移除事件this.#handlers[type].splice(idx, 1)if (this.#handlers[type].length === 0) {delete this.#handlers[type]}}}
}

相关内容

热门资讯

安卓系统录音怎么不能用,排查与... 最近是不是发现你的安卓手机录音功能突然失灵了?别急,别急,让我来给你一探究竟,帮你找出录音不能用的小...
安卓系统查看闪照,闪照生成技术... 你有没有遇到过这种情况:手机里突然弹出一个闪照,瞬间心跳加速,好奇心爆棚,但又担心错过重要信息。别急...
拓实安卓系统驱动下载,轻松实现... 你有没有遇到过手机系统崩溃,然后发现需要下载驱动才能让安卓手机重新焕发生机的情况?别急,今天就来给你...
安卓网上火车订票系统,便捷出行... 你有没有想过,在这个信息爆炸的时代,订火车票竟然也能变得如此轻松愉快?没错,我要跟你聊聊的就是这个神...
安卓系统连接hcair导航,畅... 你有没有想过,你的安卓手机竟然能和HCAir导航这么高科技的东西无缝连接呢?没错,就是那种在驾驶时帮...
安卓如何用苹果系统拍照,安卓设... 你有没有想过,即使你的手机是安卓的,也能享受到苹果系统拍照的乐趣呢?没错,就是那种拍出高清、色彩鲜艳...
怎么开启安卓原生系统,开启纯净... 你有没有想过,你的安卓手机其实隐藏着许多未被发掘的潜能?没错,就是那个我们每天不离手的安卓原生系统!...
安卓系统安装中国象棋 你有没有想过,在手机上安装一款经典的中国象棋游戏,随时随地来一场智慧的较量呢?安卓系统可是个不错的选...
老手机刷机安卓系统,安卓系统刷... 你那台老手机是不是已经有点儿力不从心啦?别急,今天就来给你支个招——老手机刷机,升级安卓系统,让它重...
安卓系统历届的名称,从“糖果”... 你有没有发现,每次打开你的安卓手机,系统界面上的名称总在悄悄地变化?这可不是简单的改个名字那么简单,...
安卓系统的谷歌卸载不了 你是不是也遇到了这个问题?安卓系统的谷歌应用卸载不了,是不是让你头疼得要命?别急,今天就来给你详细解...
鸿蒙系统兼容安卓手表吗,鸿蒙系... 最近手机圈可是热闹非凡呢!鸿蒙系统横空出世,让无数科技爱好者为之疯狂。不过,有人开始好奇了,鸿蒙系统...
安卓系统的移动加密软件,安全守... 你知道吗?在这个信息爆炸的时代,保护个人隐私变得尤为重要。而手机,作为我们日常生活中不可或缺的伙伴,...
最低安卓系统淘汰时间,揭秘最低... 你知道吗?在科技飞速发展的今天,手机更新换代的速度简直就像坐上了火箭!这不,最近有个话题在数码圈里炒...
安卓系统什么框架好用,探索最佳... 你有没有想过,你的安卓手机里那些应用,是怎么运行得那么顺畅的呢?其实,这背后可是有“大功臣”的——那...
平板安卓系统和ios,安卓与i... 你有没有发现,现在身边的朋友几乎人手一台平板电脑呢?无论是追剧、办公还是游戏,平板电脑都成了我们生活...
安卓的定位系统叫什么,GPS导... 你有没有想过,你的手机是怎么知道你在哪儿的呢?是不是觉得这事儿很神奇?其实,这背后有一个强大的技术支...
老式电视安卓系统升级,解锁智能... 你有没有发现,家里的老式电视突然变得聪明起来啦?没错,就是那个陪伴我们多年的老伙伴,它竟然悄悄地升级...
长安车载系统升级安卓 你有没有发现,最近你的长安车好像变得聪明多了?没错,就是那个车载系统,它悄悄地进行了安卓升级,简直就...
安卓系统损坏标志图,故障原因与... 手机突然间闹起了脾气,屏幕上那个安卓系统损坏的标志图,简直就像是个不速之客,突然闯进了你的生活。别急...