在准备前端实习面试时,掌握一些JavaScript手写函数是非常重要的。这些手写函数包括:数组去重、防抖和节流函数、深拷贝、Promise实现、排序算法(如快速排序)、call、apply、bind函数的实现、实现一个简单的EventEmitter(事件发布/订阅模式)、AJAX请求封装等。这些函数不仅能帮助面试者在面试中脱颖而出,显示出自己的编程能力和对JavaScript深入理解的程度,同时也是前端开发中常见的功能实现,有实际的应用价值。
以深拷贝为例,它在处理复杂的数据类型转换时尤为重要,尤其是对象或数组类型。深拷贝确保了原始数据的结构和内容被完整无缺地复制到新的变量中,而不是仅仅复制引用或指针。这对于防止后续操作影响到原始数据极其关键,尤其在处理大型、复杂的前端项目时,经常需要操作和管理大量的状态和数据。
一、数组去重
数组去重是前端开发中常见的需求,特别是在处理数据展示时,我们需要确保数据的唯一性。
-
方法一:使用Set
JavaScript的ES6中引入了Set对象,它可以自动去重复的值。
function unique(arr) {
return [...new Set(arr)];
}
-
方法二:双重循环去重
通过外层循环选定比较元素,内层循环与之比较,相同则删去。
function unique(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; // 删除元素后,后面的元素会前移,所以j要减一
}
}
}
return arr;
}
二、防抖和节流函数
在前端开发中,防抖和节流函数是优化页面性能,特别是提高用户体验的重要手段。
-
防抖(Debounce)
防抖的核心思想是在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
function debounce(func, wAIt) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(this, arguments);
}, wait);
};
}
-
节流(Throttle)
节流的核心思想是在一定时间间隔内只执行一次回调。
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
三、深拷贝
实现一个深拷贝函数对于前端开发者来说是一项基本技能,尤其在处理复杂对象时。
-
JSON方法
最简单的深拷贝实现方式就是使用JSON.stringify和JSON.parse,但这种方法有局限性,比如无法复制函数。
function deepClone(obj) {
return JSON.parse(JSON.stringify(obj));
}
-
递归拷贝
递归方法可以完整复制一个对象,包括其内部的复杂结构。
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj; // Null情况
if (obj instanceof Date) return new Date(obj); // Date对象
if (obj instanceof RegExp) return new RegExp(obj); // RegExp对象
if (typeof obj !== "object") return obj; // 基本类型直接返回
if (hash.has(obj)) return hash.get(obj); // 解决循环引用问题
let cloneObj = new obj.constructor;
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}
四、Promise实现
Promise是现代JavaScript编程中不可或缺的一部分,了解其背后的原理对于前端开发者来说非常重要。
- Promise构造函数的简单实现
一步一步实现Promise功能,首先从构造函数开始。
class MyPromise {
constructor(executor) {
// 初始状态为pending
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
// 成功态的处理函数队列
this.onFulfilledCallbacks = [];
// 失败态的处理函数队列
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(func => func());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(func => func());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
// 省略then方法实现...
}
五、排序算法(如快速排序)
掌握基本的排序算法对于前端开发者来说同样重要,其中快速排序是最常用的排序算法之一。
- 快速排序的实现
快速排序使用分治法的策略来把一个序列分为较小和较大的两个子序列,然后递归地排序两个子序列。
function quickSort(arr) {
if (arr.length <= 1) { return arr; }
let pivotIndex = Math.floor(arr.length / 2);
let pivot = arr.splice(pivotIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
if(arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
六、CALL、APPLY、BIND函数的实现
这三个函数是改变函数执行时的上下文环境(即this指向)的关键方法,在JavaScript编程中十分重要。
-
CALL的实现
函数的call方法可以改变函数的this指向,并立即执行该函数。
Function.prototype.myCall = function(context = window, ...args) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
-
APPLY的实现
Apply的实现与Call类似,区别在于Apply接受一个参数数组,而不是一组参数列表。
Function.prototype.myApply = function(context = window, args = []) {
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
-
BIND的实现
Bind方法创建一个新的函数,在Bind被调用时,这个新函数的this被指定为Bind的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
const _this = this;
return function F() {
// 考虑到用new来调用绑定函数的情况
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
}
};
七、实现一个简单的EventEmitter(事件发布/订阅模式)
EventEmitter(事件发布/订阅模式)是Node.js中一个重要且常用的模式,它允许我们订阅一个事件,并在这个事件被触发时执行回调。
- EventEmitter的基本实现
实现EventEmitter,核心就是管理一个事件注册表。
class EventEmitter {
constructor() {
this.events = {};
}
on(eventName, callback) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(callback);
}
emit(eventName, ...args) {
if (this.events[eventName]) {
this.events[eventName].forEach(callback => callback.apply(this, args));
}
}
off(eventName, callback) {
if (this.events[eventName]) {
this.events[eventName] = this.events[eventName].filter(cb => cb !== callback);
}
}
}
八、AJAX请求封装
AJAX请求是前端与后端交互的基础,封装一个通用的AJAX请求函数对于前端开发来说非常重要。
- 基本的AJAX封装
使用原生的XMLHttpRequest对象来实现基本的AJAX请求。
function ajax(url, method = 'GET', data = null, success, fail) {
let xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300) {
success && success(xhr.responseText);
} else {
fail && fail(xhr.statusText);
}
}
};
if (method === 'POST') {
xhr.setRequestHeader('Content-type', 'application/json');
xhr.send(JSON.stringify(data));
} else {
xhr.send(null);
}
}
掌握以上提及的JavaScript手写函数将大大增强前端实习面试者的竞争力,并为今后进入前端开发岗位打下坚实的基础。
相关问答FAQs:
1. 互联网公司前端实习面试常考的JavaScript手写函数有哪些?
在互联网公司前端实习面试中,面试官常常会要求面试者手写一些常见的JavaScript函数,以考察他们对JavaScript语言的熟练程度。以下是一些互联网公司前端实习面试中常考的JavaScript手写函数:
- 数组去重函数:面试者需要写一个函数,将给定数组中的重复元素去除,返回一个去重后的新数组。
- 数组扁平化函数:面试者需要写一个函数,将给定的多维数组扁平化为一维数组,返回一个扁平化后的新数组。
- 数组排序函数:面试者需要写一个函数,将给定数组进行排序,并返回一个排好序的新数组。
- 实现EventEmitter类:面试者需要实现一个简单的EventEmitter类,使得可以通过该类实例进行事件的触发和监听。
2. 如何手写一个数组去重函数?
要手写一个数组去重函数,可以使用以下方法:
- 使用Set:使用ES6中的Set数据结构,由于Set中的元素不能重复,可以将数组转化为Set,然后再将Set转化回数组,即可去重。代码示例:
function uniqueArray(arr) {
return Array.from(new Set(arr));
}
- 使用对象属性:遍历数组,将数组中的每个元素作为对象的属性,并给对应属性赋值为真,如果出现重复的元素,则属性已经存在,此时删除重复元素。代码示例:
function uniqueArray(arr) {
var obj = {};
for (var i = 0; i < arr.length; i++) {
if (!obj[arr[i]]) {
obj[arr[i]] = true;
} else {
arr.splice(i, 1);
i--;
}
}
return arr;
}
3. 怎样实现一个简单的EventEmitter类?
要实现一个简单的EventEmitter类,可以使用以下方法:
class EventEmitter {
constructor() {
this.events = {}; // 存储事件及其对应的监听函数
}
on(eventName, listener) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(listener);
}
emit(eventName, ...args) {
const listeners = this.events[eventName];
if (listeners) {
listeners.forEach(listener => {
listener.apply(null, args);
});
}
}
off(eventName, listener) {
const listeners = this.events[eventName];
if (listeners) {
// 移除匹配的监听函数
this.events[eventName] = listeners.filter(l => l !== listener);
}
}
}
使用该EventEmitter类,可以进行事件的触发和监听。可以通过on方法监听事件,通过emit方法触发事件,通过off方法移除监听函数。