JavaScript面试手写系列:
1.防抖与节流
//防抖与节流
//定义:出发时间后在n秒内函数只能执行一次,如果在n秒内又触发了事件,则会重新计算函数执行时间。
//搜索框搜索输入,只需用户最后一次输入完,再发送请求
//手机号、邮箱验证输入检测onchange oninput事件
//窗口大小resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
var btn = document.getElementById('input');
btn.addEventListener('click', debounce(submit), false);
function submit(e) {
console.log(e)
}
function debounce(fn) {
var timer = null;
return function() {
if (timer) {
clearTimeout(timer)
};
timer = setTimeout(() => {
fn.apply(this, arguments)
}, 1000);
}
}
//如果有一个需求,需要我们第一次点击不存在延时而是立刻发送请求,往后的 点击才需要发送请求,应该怎么去变形呢?
function debounce(fn, timer) {
var t = null;
return function() {
var firstClick = !t;
if (t) {
clearTimeout(t);
}
if (firstClick) {
fn.apply(this, arguments);
}
t = setTimeout(() => {
t = null;
}, timer);
}
}
//节流:当持续触发事件时,保证间隔时间触发一次事件。
//懒加载 、滚动加载、加载更多或监听滚动条位置
//百度搜索框,搜索联想功能
//防止高频点击提交,防止表单重复提交
var btn = document.getElementById('input')
btn.addEventListener('click', throttle(submit, 2000), false)
function submit(e) {
console.log(e)
}
function throttle(fn, delay) {
var begin = 0;
return function() {
var cur = Date.now();
if (cur - begin > delay) {
fn.apply(this, arguments);
begin = cur;
}
}
}
2.深浅拷贝
对象:JSON.stringfy(无法赋值undefined和fn)、object.assign、扩展运算符(无法复制二级以上引用属性)
数组:JSON.stringfy(无法赋值undefined和fn)、arr.slice、arr.concat、扩展运算符(无法复制二级以上引用属性)
使用递归实现简单深拷贝:
function DeepClone(data) {
const newData = Array.isArray(data) ? [] : {};
for (let key in data) {
if (data[key] && typeof data[key] == 'object') {
newData[key] = DeepClone(data[key])
} else {
newData[key] = data[key]
}
}
return newData;
}
const arr1 = [1, 2, 3, 4, { a: 1, b: 2, c: 3 },
[1, 2, 3], undefined, { func: () => {} }
]
const arr2 = DeepClone(arr1)
arr2[0] = 666;
arr2[4].a = 666;
arr2[5][0] = 666;
console.log(arr1, arr2)
效果如下实现深拷贝:
3.数组去重、数组对象去重
-
数组去重可以写多少种?
1.ES6的set方法
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
function uniqueArr(array) {
var newArr = [];
//return Array.from(new Set(array))
//也可以用扩展运算符
return [...new Set(array)]
}
console.log(uniqueArr(arr))
2.双重for循环+标志位
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
function uniqueArr(array) {
var newArr = [];
// for (let i = 0; i < array.length; i++) {
// let isRepeat = false;
// for (let j = i + 1; j < array.length; j++) {
// if (array[i] == array[j]) {
// isRepeat = true;
// break;
// }
// }
// if (!isRepeat) {
// newArr.push(array[i]);
// }
// };
//有一种利用遍历新数组的变形,本质差不多
for (let i = 0; i < array.length; i++) {
let isRepeat = false;
for (let j = 0; j < newArr.length; j++) {
if (array[i] == newArr[j]) {
isRepeat = true;
break;
}
}
if (!isRepeat) {
newArr.push(array[i]);
}
};
return newArr;
}
console.log(uniqueArr(arr));
3. 新对象+新数组(利用对象的键名不能重复)
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
function uniqueArr(array) {
var newArr = [],
obj = {};
for (let i = 0; i < array.length; i++) {
if (!obj[array[i]]) {
obj[array[i]] = 1;
newArr.push(array[i])
console.log(obj)
} else {
obj[array[i]]++;
}
}
return newArr;
}
console.log(uniqueArr(arr))
4.reduce条件统计
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
function uniqueArr(array) {
let newArr = [];
newArr = array.sort((a, b) => a - b).reduce((pre, cur) => {
if (pre.length === 0 || pre[pre.length - 1] !== cur) {
pre.push(cur);
}
return pre;
}, [])
return newArr;
}
console.log(uniqueArr(arr));
5.新数组+indexOf
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
function uniqueArr(array) {
let newArr = [];
for (let i = 0; i < array.length; i++) {
if (newArr.indexOf(array[i]) == -1) {
newArr.push(array[i]);
}
}
return newArr;
}
console.log(uniqueArr(arr))
6.新数组+includes方法
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
function uniqueArr(array) {
let newArr = [];
for (let i = 0; i < array.length; i++) {
if (!newArr.includes(array[i])) {
newArr.push(array[i]);
}
}
return newArr;
}
console.log(uniqueArr(arr))
7.双指针思想+splice(改变原数组的方式)
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8]
function distinct(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--
}
}
}
return arr
}
console.log(distinct(arr))
8.forEach、filters、lastIndexOf都是可以的
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
function uniqueArr(array) {
let newArr = [];
array.forEach((item) => {
newArr.includes(item) ? '' : newArr.push(item)
});
return newArr;
}
console.log(uniqueArr(arr))
let arr = [1, 8, 5, 4, 21, 1, 2, 2, 5, 8];
function uniqueArr(array) {
let newArr = [];
newArr = array.filter((item) => {
return newArr.includes(item) ? '' : newArr.push(item)
});
return newArr;
}
console.log(uniqueArr(arr))
-
数组对象去重
1.reduce条件统计去重
let arr = [
{ id: 1, name: '周瑜' },
{ id: 3, name: '王昭君' },
{ id: 2, name: '亚瑟' },
{ id: 1, name: '小乔' },
{ id: 2, name: '大桥' },
{ id: 3, name: '韩信' }
];
let obj = {}
arr = arr.reduce((preVal, curVal) => {
if (!obj[curVal.id]) {
obj[curVal.id] = 1;
preVal.push(curVal);
console.log(preVal)
} else {
obj[curVal.id]++;
}
return preVal;
}, [])
console.log(arr)
2.利用原生js+空对象+数组
let arr = [
{ id: 1, name: '周瑜' },
{ id: 3, name: '王昭君' },
{ id: 2, name: '亚瑟' },
{ id: 1, name: '小乔' },
{ id: 2, name: '大桥' },
{ id: 3, name: '韩信' }
];
function uniqueArr(array, key) {
let newObj = {};
let newArr = [];
for (let i = 0; i < array.length; i++) {
let temp = array[i];
if (!newObj[temp[key]]) {
newObj[temp[key]] = newArr.push(temp);
}
}
return newArr;
}
console.log(uniqueArr(arr, 'id'))
3.利用原生js+标志位+空数组
let arr = [
{ id: 1, name: '周瑜' },
{ id: 3, name: '王昭君' },
{ id: 2, name: '亚瑟' },
{ id: 1, name: '小乔' },
{ id: 2, name: '大桥' },
{ id: 3, name: '韩信' }
];
function uniqueArr(array, key) {
let newArr = [];
for (let i = 0; i < array.length; i++) {
let isRepeat = false;
for (let j = i + 1; j < arr.length; j++) {
if (array[i][key] === array[j][key]) {
isRepeat = true;
break;
}
}
if (!isRepeat) {
newArr.push(array[i])
}
}
return newArr
}
console.log(uniqueArr(arr, 'id'))
4.利用双指针思想+splice方法
let arr = [
{ id: 1, name: '周瑜' },
{ id: 3, name: '王昭君' },
{ id: 2, name: '亚瑟' },
{ id: 1, name: '小乔' },
{ id: 2, name: '大桥' },
{ id: 3, name: '韩信' },
{ id: 3, name: 'zxg' }
];
function uniqueArr(array, key) {
for (let i = 0; i < array.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (array[i][key] === array[j][key]) {
array.splice(j, 1)
j--;
}
}
}
return array
}
console.log(uniqueArr(arr, 'id'))
4.数组扁平化
let arr = [1, [2, [3, [4, [5]]]]];
1.利用数组原生flat方法,要传入Infinity,因为你并不知道有多少层嵌套
// let newArr = arr.flat(Infinity);
// console.log(newArr)
2.利用JSON.parse、JSON.strinfy、正则
// let brr2 = JSON.parse("[" + JSON.stringify(arr).replace(/\[|\]/g, "") + "]");
// console.log(brr2);
3.reduce+递归,最传统的方法
function flatArr(array) {
let newArr = array.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatArr(cur) : cur);
}, [])
return newArr
}
console.log(flatArr(arr))
效果如下:
持续更新中......
5.如何模拟实现一个new的效果
(1)首先会创建一个 新的空对象 ,如obj
(2)obj对象会继承构造函数即Person的原型,即 obj.__proto===Person.prototype,这样obj就可以调用Person上的原型方法了。
(3)将Person的this指向obj,也就是将Person上的属性添加到新的obj对象上去,obj则可以调用Person中定义的属性了。
(4)如果Person构造函数没有返回对象,则返回新创建的对象(即this),否则返回return的对象。
<script>
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function() {
console.log('hello')
}
function myNew(cosntructor) {
if (typeof cosntructor !== "function") {
throw "myNew方法的第一个参数 必须是一个方法"
}
//改变原型
let newObj = Object.create(cosntructor.prototype);
//获取传入的参数
console.log(arguments)
let args = Array.from(arguments).slice(1);
//执行constructor函数,获取结果,并将属性添加到新对象newObj上
let result = cosntructor.apply(newObj, args);
//判断result类型,如果是object或者function类型,则直接返回结果
let originType = Object.prototype.toString.call(result); //获取内部属性值
let isObject = originType === '[object Object]';
let isFunction = originType === '[object Function]';
if (isObject || isFunction) {
return result;
} else {
//返回新对象
return newObj;
}
}
let obj = myNew(Person, 'zxg', 20)
console.log(obj)
</script>
6.如何模拟实现一个call、apply、bind的效果
//手写call
Function.prototype.myCall = function(context, ...args) {
if (typeof this !== 'function') {
throw ('type error')
};
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result
}
function foo() {
console.log('bar():', ...arguments, this); //打印参数
console.log(this)
return 'hello'
}
let obj = {
name: 'zxg',
age: 23,
}
let res = foo.myCall(obj, obj.name, obj.age)
let res1 = foo.call(obj,obj.name,obj.age)
console.log(res)
//手写apply
Function.prototype.myApply = function(context, args) {
if (typeof this !== 'function') {
throw ('type error')
};
context.fn = this;
let result = context.fn(...args);
delete context.fn;
return result
}
function foo() {
console.log('bar():', ...arguments, this); //打印参数
console.log(this)
return 'hello'
}
let obj = {
name: 'zxg',
age: 23,
}
let res = foo.myApply(obj, [obj.name, obj.age])
let res1 = foo.apply(obj, [obj.name, obj.age])
console.log(res);
console.log(res1);
//手写bind
Function.prototype.myBind = function(context, ...args) {
if (typeof this !== 'function') {
throw ('type error')
};
context.fn = this;
return function() {
// console.log(arguments)
// console.log(this)
let newArgs = Array.from(arguments)
let result = context.fn(...[...args, ...newArgs]);
delete context.fn;
return result
}
}
function foo() {
console.log('bar():', ...arguments, this); //打印参数
console.log(this)
return 'hello'
}
let obj = {
name: 'zxg',
age: 23,
}
let res = foo.myBind(obj, obj.name, obj.age)
let res1 = foo.bind(obj, obj.name, obj.age)
res(6, 8, 9, 10);
res1(8, 9, 12);
7.手写Object.create();思想:将传入的对象obj作为原型
function create(obj){
function F(){}
F.prototype = obj
return new F();
}
8.手写instanceof方法;
此运算符用来判断构造函数的prototype属性是否出现在了传入对象的原型链中的任何位置。
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = new Person('zxg', 23)
function myInstanceof(obj, className) {
let pointer = obj;
while (pointer) {
if (pointer === className.prototype) {
return true;
}
pointer = pointer.__proto__;
}
return false;
}
console.log(myInstanceof(person, Person))
9.手写promise(实现A+规范,872passing)
const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';
class Promise {
constructor(executor) {
console.log('我的promise');
this.status = PENDING;
this.value = null;
this.reason = null;
//定义数组将函数存放起来
this.resolveCallbacks = [];
this.rejectCallbacks = [];
try {
//将resolve,reject绑定在Promise实例上
executor(this.resolve.bind(this), this.reject.bind(this))
} catch (error) {
this.reject(error)
}
}
//放在原型上的resolve
resolve(value) {
if (this.status === PENDING) {
this.value = value;
this.status = RESOLVED;
this.resolveCallbacks.forEach(onFulfilled => {
onFulfilled()
})
}
}
//放在原型上的reject
reject(reason) {
if (this.status === PENDING) {
this.reason = reason;
this.status = REJECTED;
this.rejectCallbacks.forEach(onRejected => {
onRejected()
})
}
}
then(onFulfilled, onRejected) {
// onFulfilled(this.value);
// console.log('then的状态', this.status);
//默认处理
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
let promise2 = new Promise((resolve, reject) => {
if (this.status === PENDING) {
this.resolveCallbacks.push(() => {
setTimeout(() => {
// console.log('promise2', promise2)
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
});
});
this.rejectCallbacks.push(() => {
setTimeout(() => {
// console.log('promise2', promise2)
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
});
})
}
if (this.status === RESOLVED) {
//利用异步任务的宏任务拿到promise2
setTimeout(() => {
// console.log('promise2', promise2)
try {
let x = onFulfilled(this.value);
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
});
}
if (this.status === REJECTED) {
setTimeout(() => {
// console.log('promise2', promise2)
try {
let x = onRejected(this.reason);
resolvePromise(x, promise2, resolve, reject)
} catch (error) {
reject(error)
}
});
}
})
return promise2
}
}
function resolvePromise(x, promise2, resolve, reject) {
// console.log(1)
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
// console.log(2)
if (x && (typeof x === 'object' || typeof x === 'function')) {
let called;
try {
let then = x.then
if (typeof then === 'function') {
then.call(x, value => {
if (called) return;
called = true;
resolvePromise(value, promise2, resolve, reject);
}, reason => {
if (called) return;
called = true
reject(reason)
})
} else {
resolve(x)
}
} catch (error) {
if (called) return
called = true
reject(error)
}
} else {
resolve(x);
}
}
Promise.deferred = function() {
let deferred = {};
deferred.promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
})
return deferred;
}
module.exports = Promise;
参考了很多前辈手写promise的过程,感觉一点点根据原生promise的功能慢慢推慢慢实现是最容易理解的,慢慢调试,附上调试界面:
再加一张A+规范测试图: