- 每到跳槽的时候,总会遇到一些公司让手写几道面试题,从而看你对JavaScript掌握的程度
实现防抖函数
- 防抖函数原理:在事件被触发n秒后再执⾏回调,如果在这n秒内⼜被触发,则重新计时。
使用场景: 防⽌多次提交按钮,只执⾏最后提交的⼀次,表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似
const debounce = (fn, delay) => { let timer = null; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { fn.apply(this, args); }, delay); }; };
实现节流函数
- 节流函数原理:规定在⼀个单位时间内,只能触发⼀次函数。如果这个单位时间内触发多次函数,只有⼀次⽣效。
适⽤场景: 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动,缩放场景:监控浏览器resize.
const throttle = (fn, delay = 500) => { let flag = true; return (...args) => { if (!flag) return; flag = false; setTimeout(() => { fn.apply(this, args); flag = true; }, delay); }; };
深克隆
简易版
- 他⽆法实现对函数 、RegExp等特殊对象的克隆
- 会抛弃对象的constructor,所有的构造函数会指向Object
- 对象有循环引⽤,会报错
const newObj = JSON.parse(JSON.stringify(oldObj));
最终版本
- ⼀些特殊情况没有处理: 例如Buffer对象、Promise、Set、Map
const clone = (parent) => { const isType = (obj, type) => { if (typeof obj !== 'object') return false; const typeString = Object.prototype.toString.call(obj); let flag; switch (type) { case 'Array': flag = typeString === '[object Array]'; break; case 'Date': flag = typeString === '[object Date]'; break; case 'RegExp': flag = typeString === '[object RegExp]'; break; default: flag = false; } return flag; }; // 处理正则 const getRegExp = (re) => { var flags = ''; if (re.global) flags += 'g'; if (re.ignoreCase) flags += 'i'; if (re.multiline) flags += 'm'; return flags; }; // 维护两个储存循环引⽤的数组 const parents = []; const children = []; const _clone = (parent) => { if (parent === null) return null; if (typeof parent !== 'object') return parent; let child, proto; if (isType(parent, 'Array')) { // 对数组做特殊处理 child = []; } else if (isType(parent, 'RegExp')) { // 对正则对象做特殊处理 child = new RegExp(parent.source, getRegExp(parent)); if (parent.lastIndex) child.lastIndex = parent.lastIndex; } else if (isType(parent, 'Date')) { // 对Date对象做特殊处理 child = new Date(parent.getTime()); } else { // 处理对象原型 proto = Object.getPrototypeOf(parent); // 利⽤Object.create切断原型链 child = Object.create(proto); } // 处理循环引⽤ const index = parents.indexOf(parent); if (index != -1) { // 如果⽗数组存在本对象,说明之前已经被引⽤过,直接返回此对象 return children[index]; } parents.push(parent); children.push(child); for (let i in parent) { // 递 归 child[i] = _clone(parent[i]); } return child; }; return _clone(parent); };
实现Event
class EventEmeitter {
constructor() {
this._events = this._events || new Map(); // 储存事件/回调键值对
this._maxListeners = this._maxListeners || 10; // 设⽴监听上限
}
}
// 触发名为type的事件
EventEmeitter.prototype.emit = function (type, ...args) {
let handler;
// 从储存事件键值对的this._events中获取对应事件回调函数
handler = this._events.get(type);
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
return true; JS笔试题
134
};
// 监听名为type的事件
EventEmeitter.prototype.addListener = function (type, fn) {
// 将type事件以及对应的fn函数放⼊this._events中储存
if (!this._events.get(type)) {
this._events.set(type, fn);
}
};
// 触发名为type的事件
EventEmeitter.prototype.emit = function (type, ...args) {
let handler;
handler = this._events.get(type);
if (Array.isArray(handler)) {
// 如果是⼀个数组说明有多个监听者,需要依次此触发⾥⾯的函数
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args);
} else {
handler[i].call(this);
}
}
} else {
// 单个函数的情况我们直接触发即可
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
}
return true;
};
// 监听名为type的事件
EventEmeitter.prototype.addListener = function (type, fn) {
const handler = this._events.get(type); // 获取对应事件名称的函数清单
if (!handler) {
this._events.set(type, fn);
} else if (handler && typeof handler === "function") {
// 如果handler是函数说明只有⼀个监听者
this._events.set(type, [handler, fn]); // 多个监听者我们需要⽤数组储存
} else {
handler.push(fn); // 已经有多个监听者,那么直接往数组⾥push函数即可
}
};
EventEmeitter.prototype.removeListener = function (type, fn) {
const handler = this._events.get(type); // 获取对应事件名称的函数清
// 如果是函数,说明只被监听了⼀次
if (handler && typeof handler === "function") {
this._events.delete(type, fn);
} else {
let postion;
// 如果handler是数组,说明被监听多次要找到对应的函数
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
postion = i;
} else {
postion = -
1;
}
}
// 如果找到匹配的函数,从数组中清除
if (postion !== -1) {
// 找到数组对应的位置,直接清除此回调
handler.splice(postion, 1);
// 如果清除后只有⼀个函数,那么取消数组,以函数形式保存JS笔试题
135
if (handler.length === 1) {
this._events.set(type,
handler[0]);
}
} else {
return
this;
}
}
}
实现instanceOf
// 模 拟 instanceof
function instance_of(L, R) {
//L 表示左表达式,R 表示右表达式
var O = R.prototype; // 取 R 的显示原型
L = L.proto; // 取 L 的隐式原型
while (true) {
if (L === null) return false;
if (O === L)
// 这⾥重点:当 O 严格等于 L 时,返回 true
return true;
L = L.proto;
}
}
模拟new
new操作符做了这些事:
- 它创建了⼀个全新的对象
- 它会被执⾏[[Prototype]](也就是proto)链接
- 它使this指向新创建的对象
- 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
- 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调⽤将返回该对象引⽤
// objectFactory(name, 'cxk', '18') function objectFactory() { const obj = new Object(); const Constructor = [].shift.call(arguments); obj. proto = Constructor.prototype; const ret = Constructor.apply(obj, arguments); return typeof ret === "object" ? ret : obj; }
实现⼀个call
- 将函数设为对象的属性
- 指定this到函数并传⼊给定参数执⾏函数
如果不传⼊参数,默认指向为 window
Function.prototype.myCall = function (context) { //此处没有考虑context⾮object情况 context.fn = this; let args = []; for (let i = 1, len = arguments.length; i < len; i++) { args.push(arguments[i]); } context.fn(...args); let result = context.fn(...args); delete context.fn; return result; };
实现apply⽅法
apply原理与call很相似
Function.prototype.myapply = function (context, arr) { var context = Object(context) || window; context.fn = this; var result; if (!arr) { result = context.fn(); } else { var args = []; for (var i = 0, len = arr.length; i < len; i++) { args.push("arr[" + i + "]"); } result = eval("context.fn(" + args + ")"); } delete context.fn; return result; };
实现bind
- 返回⼀个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使⽤。故作为构造函数时应使得this失效,但是传⼊的参数依然有效
if (!Function.prototype.bind) { Function.prototype.bind = function (oThis) { if (typeof this !== 'function') { throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function () { }, fBound = function () { return fToBind.apply(this instanceof fBound ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }; }
模拟Object.create
- Object.create()⽅法创建⼀个新对象,使⽤现有的对象来提供新创建的对象的proto。
function create (proto) {
function F () { }
F.prototype = proto;
return new F();
}
实现类的继承
function Parent (name) {
this.parent = name
}
Parent.prototype.say = function () {
console.log(`${this.parent}: 你打篮球的样⼦像kunkun`)
}
function Child (name, parent) {
// 将⽗类的构造函数绑定在⼦类上
Parent.call(this, parent)
this.child = name
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function () {
console.log(`${this.parent}好,我是练习时⻓两年半的${this.child}`);
}
// 注意记得把⼦类的构造指向⼦类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样⼦像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时⻓两年半的cxk
实现JSON.parse
var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");
实现Promise
var PromisePolyfill = (function () {
// 和reject不同的是resolve需要尝试展开thenable对象
function tryToResolve (value) {
if (this === value) {
throw TypeError('Chaining cycle detected for promise!')
}
if (value !== null &&
(typeof value === 'object' || typeof value === 'function')) {
try {
var then = value.then
var thenAlreadyCalledOrThrow = false
if (typeof then === 'function') {
then.bind(value)(
function (value2) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
tryToResolve.bind(this, value2)()
}.bind(this),
function (reason2) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', reason2)()
}.bind(this)
)
} else {
resolveOrReject.bind(this, 'resolved', value)()
}
} catch (e) {
if (thenAlreadyCalledOrThrow) return
thenAlreadyCalledOrThrow = true
resolveOrReject.bind(this, 'rejected', e)()
}
} else {
resolveOrReject.bind(this, 'resolved', value)()
}
}
function resolveOrReject (status, data) {
if (this.status !== 'pending') return
this.status = status
this.data = data
if (status === 'resolved') {
for (var i = 0; i < this.resolveList.length; ++i) {
this.resolveList[i]()
}
} else {
for (i = 0; i < this.rejectList.length; ++i) {
this.rejectList[i]()
}
}
}
function Promise (executor) {
if (!(this instanceof Promise)) {
throw Error('Promise can not be called without new !')
}
if (typeof executor !== 'function') {
throw TypeError('Promise resolver ' + executor + ' is not a function')
}
this.status = 'pending'
this.resolveList = []
this.rejectList = []
try {
executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
} catch (e) {
resolveOrReject.bind(this, 'rejected', e)()
}
}
Promise.prototype.then = function (onFullfilled, onRejected) {
if (typeof onFullfilled !== 'function') {
onFullfilled = function (data) {
return data
}
}
if (typeof onRejected !== 'function') {
onRejected = function (reason) {
throw reason
}
}
var executor = function (resolve, reject) {
setTimeout(function () {
try {
var value = this.status === 'resolved'
? onFullfilled(this.data)
: onRejected(this.data)
resolve(value)
} catch (e) {
reject(e)
}
}.bind(this))
}
if (this.status !== 'pending') {
return new Promise(executor.bind(this))
} else {
// pending
return new Promise(function (resolve, reject) {
this.resolveList.push(executor.bind(this, resolve, reject))
this.rejectList.push(executor.bind(this, resolve, reject))
}.bind(this))
}
}
// for prmise A+ test
Promise.deferred = Promise.defer = function () {
var dfd = {}
dfd.promise = new Promise(function (resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
// for prmise A+ test
if (typeof module !== 'undefined') {
module.exports = Promise
}
return Promise
})()
PromisePolyfill.all = function (promises) {
return new Promise((resolve, reject) => {
const result = []
let cnt = 0
for (let i = 0; i < promises.length; ++i) {
promises[i].then(value => {
cnt++
result[i] = value
if (cnt === promises.length) resolve(result)
}, reject)
}
})
}
PromisePolyfill.race = function (promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; ++i) {
promises[i].then(resolve, reject)
}
})
}