实现 new 过程:
要点:
- 函数第一个参数是构造函数
- 实例的__proto__指向构造函数的原型属性prototype
- 函数残余参数要挂载到一个实例对象上
- 构造函数有返回值时,就返回这个返回值
const createObj = function () { let obj = {} let Constructor = [].shift.call(arguments) // 1 obj.__proto__ = Constructor.prototype // 2 let ret = Constructor.apply(obj, arguments) // 3 return 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 函数
共同点:
- 第一个参数是要绑定的this
- 函数外部的 this 其实是要执行绑定的函数(因为三者都是点调用)
bind
这里实现简略版本(new 调用后果不一样)
- bind函数执行后,要返回一个原函数的拷贝
- 给返回函数外部的 fn 绑定传入的 context
Function.prototype.myBind = function(context, ...args) { if (typeof this !== 'function') throw 'caller must be a function' const fn = this return function() { return fn.call(context, ...args, ...arguments) }}
call
、apply
函数的实现其实都借助了点调用。利用第一个参数做个直达,调用完之后删除。
call
Function.prototype.myCall = function(context = windows, ...args) { context._fn = this const result = context._fn(...args) delete context._fn return result}
apply
Function.prototype.myApply = function(context = windows, args) { context._fn = this const result = context._fn(args) delete context._fn return result}
节流和防抖
刚开始接触这俩概念的时候傻傻分不清楚。
浏览器的一些事件,如:resize,scroll,keydown,keyup,keypress,mousemove等。这些事件触发频率太过频繁,绑定在这些事件上的回调函数会不停的被调用。会减轻浏览器的累赘,导致用户体验十分蹩脚。
节流防抖次要是利用了闭包。
节流
节流函数来让函数每隔 n 毫秒触发一次。
// 节流function throttle (f, wait = 200) { let last = 0 return function (...args) { // 以下 外部匿名函数 均是指这个匿名函数 let now = Date.now() if (now - last > wait) { last = now f.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 = 0 return 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.length return 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
要点:
- 三种状态的扭转:
pending
fulfilled
rejected
resolve()
reject()
函数的实现- 关键点
then
链式调用的实现
class MyPromise { constructor(fn) { this.status = 'pending' this.value = null this.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 = res this.resolvedFns.forEach(fn => { fn(res) }) }) } _reject(res) { setTimeout(() => { this.status = 'rejected' this.value = res this.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.resolvedFns
和this.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.observers for (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() // 创立观察者1let observer_2 = new Observer()let sub = new Subject() // 创立主体sub.add(observer_1) // 增加观察者1sub.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] } } }}