- 几道手写面试题,考验对js掌握的熟练程度
随机打乱数组
shuffle(array) {
if (!Array.isArray(array)) {
return array;
}
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]
}
return array;
},
手动实现 Arrar.prototype.map方法
- map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。
function map(arr,mapCallback) {
首先检测传递的参数是否正确
if(!Arrar.isArray(arr) || !arr.length || typeof mapCallback !== 'function') {
return []
} else {
let result = [];
for(let i = 0,len = arr.length;i<len;i++) {
result.push(mapCallback(arr[i],i,arr))
}
return result
}
}
手动实现Array.prototype.filter方法
filter()
方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
function filter(arr,filterCallback) {
if(!Array.isArray(arr) || !arr.length || typeof filterCallback !== 'function') {
return []
} else {
let result = [];
for (let i=0,len=arr.length;i<len;i++) {
if(filterCallback(arr[i],i,arr)) {
result.push(arr[i])
}
}
return result
}
}
JS的节流和防抖
- 函数防抖 是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
- 防抖: 在一段时间内,事件只会最后触发一次。
节流: 事件,按照一段时间的间隔来进行触发。
// 函数防抖的实现 function debounce(fn, wait) { var timer = null; return function() { var context = this, args = arguments; // 如果此时存在定时器的话,则取消之前的定时器重新记时 if (timer) { clearTimeout(timer); timer = null; } // 设置定时器,使事件间隔指定事件后执行 timer = setTimeout(() => { fn.apply(context, args); }, wait); }; }
函数节流 是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
// 函数节流的实现; function throttle(fn, delay) { var preTime = Date.now(); return function() { var context = this, args = arguments, nowTime = Date.now(); // 如果两次时间间隔超过了指定时间,则执行函数。 if (nowTime - preTime >= delay) { preTime = Date.now(); return fn.apply(context, args); } }; }
多维数组处理一维数组
// 多维数组处理一维数据
export const flatten = (arr) => {
while (arr.some((item) => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
例如:
arr = [1,2,3,[22,33,44,[55,66,77,[88,99,100]]]]
flatten(arr) => [1, 2, 3, 22, 33, 44, 55, 66, 77, 88, 99, 100]
手写call bind apply
- call bind apply的作用都是可以进行修改this指向
- call 和 apply的区别在于参数传递的不同
- bind 区别在于最后会返回一个函数。
// call
Function.prototype.MyCall = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
// 使用Symbol 来确定唯一
const fnSym = Symbol()
//模拟对象的this指向
context[fnSym] = this
// 获取参数
const args = [...arguments].slice(1)
//绑定参数 并执行函数
const result = context[fnSym](...args)
//清除定义的this
delete context[fnSym]
// 返回结果
return result
}
// call 如果能明白的话 apply其实就是改一下参数的问题
// apply
Function.prototype.MyApply = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
// 使用Symbol 来确定唯一
const fnSym = Symbol()
//模拟对象的this指向
context[fnSym] = this
// 获取参数
const args = [...arguments][1]
//绑定参数 并执行函数 由于apply 传入的是一个数组 所以需要解构
const result = arguments.length > 1 ? context[fnSym](...args) : context[fnSym]()
//清除定义的this
delete context[fnSym]
// 返回结果 //清除定义的this
return result
}
// bind
Function.prototype.MyBind = function (context) {
if (typeof this !== "function") {
throw new Error('type error')
}
if (context === null || context === undefined) {
// 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
context = window
} else {
// 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
context = Object(context)
}
//模拟对象的this指向
const self = this
// 获取参数
const args = [...arguments].slice(1)
// 最后返回一个函数 并绑定 this 要考虑到使用new去调用,并且bind是可以传参的
return function Fn(...newFnArgs) {
if (this instanceof Fn) {
return new self(...args, ...newFnArgs)
}
return self.apply(context, [...args, ...newFnArgs])
}
}
手写实现继承
/**
* es6之前 寄生组合继承
*/
{
function Parent(name) {
this.name = name
this.arr = [1, 2, 3]
}
Parent.prototype.say = () => {
console.log('Hi');
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
// 核心代码 通过Object.create创建新对象 子类 和 父类就会隔离
// Object.create:创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
}
/**
* es6继承 使用关键字class
*/
{
class Parent {
constructor(name) {
this.name = name
this.arr = [1, 2, 3]
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
}
}
手写数组的 flat
- 原理就是,先在内部生成一个新数组,遍历原来的数组,当原数组内 存在数组并且层级deep大于等于1时进行递归, 如果不满足这个条件就可以直接push数据到新数组去
- 递归同时要先把层级减少, 然后通过 concat 链接递归出来的数组
- 最终返回这个数组就可以了
const flat = function (arr, deep = 1) {
// 声明一个新数组
let result = []
arr.forEach(item => {
if (Array.isArray(item) && deep > 0) {
// 层级递减
// deep-- 来自评论区的大佬指正:deep - 1
// 使用concat链接数组
result = result.concat(flat(item, deep - 1))
} else {
result.push(item)
}
})
return result
}