高频手写题目

面试高频手写题目

1 实现防抖函数(debounce)

防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时

手写简化版:

// func是用户传入需要防抖的函数
// wait是等待时间
const debounce = (func, wait = 50) => {// 缓存一个定时器idlet timer = 0// 这里返回的函数是每次用户实际调用的防抖函数// 如果已经设定过定时器了就清空上一次的定时器// 开始一个新的定时器,延迟执行用户传入的方法return function(...args) {if (timer) clearTimeout(timer)timer = setTimeout(() => {func.apply(this, args)}, wait)}
}

适用场景:

按钮提交场景:防止多次提交按钮,只执行最后提交的一次 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

2 实现节流函数(throttle)

节流函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效

手写简版

// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 50) => {// 上一次执行该函数的时间let lastTime = 0return function(...args) {// 当前时间let now = +new Date()// 将当前时间和上一次执行函数时间对比// 如果差值大于设置的等待时间就执行函数if (now - lastTime > wait) {lastTime = nowfunc.apply(this, args)}}
}setInterval(throttle(() => {console.log(1)}, 500),1
)

适用场景:

  • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动

  • 缩放场景:监控浏览器resize

  • 动画场景:避免短时间内多次触发动画引起性能问题

3 深克隆(deepclone)

简单版:

const newObj = JSON.parse(JSON.stringify(oldObj));

局限性:

  • 他无法实现对函数 、RegExp等特殊对象的克隆

  • 会抛弃对象的constructor,所有的构造函数会指向Object

  • 对象有循环引用,会报错

面试够用版

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;
}

4 实现Event(event bus)

event bus既是node中各个模块的基石,又是前端组件通信的依赖手段之一,同时涉及了订阅-发布设计模式,是非常重要的基础

简单版:

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;
};// 监听名为type的事件
EventEmeitter.prototype.addListener = function(type, fn) {// 将type事件以及对应的fn函数放入this._events中储存if (!this._events.get(type)) {this._events.set(type, fn);}
};

面试版:

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;
};// 监听名为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);// 如果清除后只有一个函数,那么取消数组,以函数形式保存if (handler.length === 1) {this._events.set(type, handler[0]);}} else {return this;}}
};

5 实现instanceOf

核心要点:原型链的向上查找

function myInstanceof(left, right) {let proto = Object.getPrototypeOf(left);while(true) {if(proto == null) return false;if(proto == right.prototype) return true;proto = Object.getPrototypeof(proto);}
}

6 模拟new

new操作符做了这些事:

  • 创建一个全新的对象,这个对象的__proto__要指向构造函数的原型对象

  • 执行构造函数

  • 返回值为object类型则作为new方法的返回值返回,否则返回上述全新对象

function myNew(fn, ...args) {let instance = Object.create(fn.prototype);let res = fn.apply(instance, args);return typeof res === 'object' ? res: instance;
}

7 实现一个call

call做了什么:

  • 将函数设为对象的属性

  • 执行&删除这个函数

  • 指定this到函数并传入给定参数执行函数

  • 如果不传入参数,默认指向为 window

// 模拟 call bar.mycall(null);
//实现一个call方法:
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;
};

8 实现apply方法

思路: 利用this的上下文特性。

// 实现apply只要把下一行中的...args换成args即可
Function.prototype.myCall = function (context = window, ...args) {const func = this;const fn = Symbol('fn');context[fn] = func;const res = context[fn](...args);// 重点代码,利用this指向,相当于context.caller(...args)delete context[fn];return res;
};

9 实现bind

bind 的实现对比其他两个函数略微地复杂了一点,因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现

  • bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式

  • 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来,于是就有了这样的实现 args.concat(…arguments)

  • 最后来说通过 new 的方式,在之前的章节中我们学习过如何判断 this,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this

Function.prototype.myBind = function (context) {if (typeof this !== 'function') {throw new TypeError('Error')}const _this = thisconst args = [...arguments].slice(1)// 返回一个函数return function F() {// 因为返回了一个函数,我们可以 new F(),所以需要判断if (this instanceof F) {return new _this(...args, ...arguments)}return _this.apply(context, args.concat(...arguments))}
}

10 模拟Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的proto

// 模拟 Object.createfunction create(proto) {function F() {}F.prototype = proto;return new F();
}

11 实现类的继承-简版

类的继承在几年前是重点内容,有n种继承方式各有优劣,es6普及后越来越不重要,那么多种写法有点『回字有四样写法』的意思,如果还想深入理解的去看红宝书即可,我们目前只实现一种最理想的继承方式。

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
}/** 1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
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: 你打篮球的样子像kunkunvar child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

12 ES5实现继承的那些事-详细

第一种方式是借助call实现继承

function Parent1(){this.name = 'parent1';
}
function Child1(){Parent1.call(this);this.type = 'child1'
}
console.log(new Child1);

这样写的时候子类虽然能够拿到父类的属性值,但是问题是父类中一旦存在方法那么子类无法继承。那么引出下面的方法

第二种方式借助原型链实现继承:

  function Parent2() {this.name = 'parent2';this.play = [1, 2, 3]}function Child2() {this.type = 'child2';}Child2.prototype = new Parent2();console.log(new Child2());

看似没有问题,父类的方法和属性都能够访问,但实际上有一个潜在的不足。举个例子:

  var s1 = new Child2();var s2 = new Child2();s1.play.push(4);console.log(s1.play, s2.play); // [1,2,3,4] [1,2,3,4]

明明我只改变了s1的play属性,为什么s2也跟着变了呢?很简单,因为两个实例使用的是同一个原型对象

第三种方式:将前两种组合:

function Parent3 () {this.name = 'parent3';this.play = [1, 2, 3];}function Child3() {Parent3.call(this);this.type = 'child3';}Child3.prototype = new Parent3();var s3 = new Child3();var s4 = new Child3();s3.play.push(4);console.log(s3.play, s4.play); // [1,2,3,4] [1,2,3]

之前的问题都得以解决。但是这里又徒增了一个新问题,那就是Parent3的构造函数会多执行了一次(Child3.prototype = new Parent3();)。这是我们不愿看到的。那么如何解决这个问题?

第四种方式: 组合继承的优化1

  function Parent4 () {this.name = 'parent4';this.play = [1, 2, 3];}function Child4() {Parent4.call(this);this.type = 'child4';}Child4.prototype = Parent4.prototype;

这里让将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类属性和方法均能访问,但是我们来测试一下

  var s3 = new Child4();var s4 = new Child4();console.log(s3)

子类实例的构造函数是Parent4,显然这是不对的,应该是Child4。

第五种方式(最推荐使用):优化2

  function Parent5 () {this.name = 'parent5';this.play = [1, 2, 3];}function Child5() {Parent5.call(this);this.type = 'child5';}Child5.prototype = Object.create(Parent5.prototype);Child5.prototype.constructor = Child5;

这是最推荐的一种方式,接近完美的继承。

13 实现一个JSON.stringify

JSON.stringify(value[, replacer [, space]]):
  • Boolean | Number| String类型会自动转换成对应的原始值。

  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。

  • 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略

  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略

function jsonStringify(obj) {let type = typeof obj;if (type !== "object") {if (/string|undefined|function/.test(type)) {obj = '"' + obj + '"';}return String(obj);} else {let json = []let arr = Array.isArray(obj)for (let k in obj) {let v = obj[k];let type = typeof v;if (/string|undefined|function/.test(type)) {v = '"' + v + '"';} else if (type === "object") {v = jsonStringify(v);}json.push((arr ? "" : '"' + k + '":') + String(v));}return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")}
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"

14 实现一个JSON.parse

JSON.parse(text[, reviver])

用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)

第一种:直接调用 eval

function jsonParse(opt) {return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}

避免在不必要的情况下使用 evaleval() 是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行JS代码,有XSS漏洞。

如果你只想记这个方法,就得对参数json做校验。

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (rx_one.test(json.replace(rx_two, "@").replace(rx_three, "]").replace(rx_four, ""))
) {var obj = eval("(" +json + ")");
}

第二种:Function

核心:Function与eval有相同的字符串参数特性

var func = new Function(arg1, arg2, ..., functionBody);

在转换JSON的实际应用中,只需要这么做

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();

evalFunction都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用

15 Promise的简单实现

// 使用
var promise = new Promise((resolve,reject) => {if (操作成功) {resolve(value)} else {reject(error)}
})
promise.then(function (value) {// success
},function (value) {// failure
})
function myPromise(constructor) {let self = this;self.status = "pending"   // 定义状态改变前的初始状态self.value = undefined;   // 定义状态为resolved的时候的状态self.reason = undefined;  // 定义状态为rejected的时候的状态function resolve(value) {if(self.status === "pending") {self.value = value;self.status = "resolved";}}function reject(reason) {if(self.status === "pending") {self.reason = reason;self.status = "rejected";}}// 捕获构造异常try {constructor(resolve,reject);} catch(e) {reject(e);}
}
// 添加 then 方法
myPromise.prototype.then = function(onFullfilled,onRejected) {let self = this;switch(self.status) {case "resolved":onFullfilled(self.value);break;case "rejected":onRejected(self.reason);break;default:       }
}var p = new myPromise(function(resolve,reject) {resolve(1)
});
p.then(function(x) {console.log(x) // 1
})

16 解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型city: '北京', // 中文需解码enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中let paramsObj = {};// 将 params 存到对象中paramsArr.forEach(param => {if (/=/.test(param)) { // 处理有 value 的参数let [key, val] = param.split('='); // 分割 key 和 valueval = decodeURIComponent(val); // 解码val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值paramsObj[key] = [].concat(paramsObj[key], val);} else { // 如果对象没有这个 key,创建 key 并设置值paramsObj[key] = val;}} else { // 处理没有 value 的参数paramsObj[param] = true;}})return paramsObj;
}

17 模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {name: '姓名',age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则if (reg.test(template)) { // 判断模板里是否有模板字符串const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段template = template.replace(reg, data[name]); // 将第一个模板字符串渲染return render(template, data); // 递归的渲染并返回渲染后的结构}return template; // 如果模板没有模板字符串直接返回
}

18 转化为驼峰命名

var s1 = "get-element-by-id"// 转化为 getElementByIdvar f = function(s) {return s.replace(/-\w/g, function(x) {return x.slice(1).toUpperCase();})
}

19 查找字符串中出现最多的字符和个数

例: abbcccddddd -> 字符最多的是d,出现了5次

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';// 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){num = $0.length;char = $1;        }
});
console.log(`字符最多的是${char},出现了${num}次`);

20 字符串查找

请使用最基本的遍历来实现判断字符串 a 是否被包含在字符串 b 中,并返回第一次出现的位置(找不到返回 -1)。

a='34';b='1234567'; // 返回 2
a='35';b='1234567'; // 返回 -1
a='355';b='12354355'; // 返回 5
isContain(a,b);
function isContain(a, b) {for (let i in b) {if (a[0] === b[i]) {let tmp = true;for (let j in a) {if (a[j] !== b[~~i + ~~j]) {tmp = false;}}if (tmp) {return i;}}}return -1;
}

21 实现千位分隔符

// 保留三位小数
parseToMoney(1234.56); // return '1,234.56'
parseToMoney(123456789); // return '123,456,789'
parseToMoney(1087654.321); // return '1,087,654.321'
function parseToMoney(num) {num = parseFloat(num.toFixed(3));let [integer, decimal] = String.prototype.split.call(num, '.');integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');return integer + '.' + (decimal ? decimal : '');
}

22 判断是否是电话号码

function isPhone(tel) {var regx = /^1[34578]\d{9}$/;return regx.test(tel);
}

23 验证是否是邮箱

function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;return regx.test(email);
}

24 验证是否是身份证

function isCardNo(number) {var regx = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;return regx.test(number);
}

25 用ES5实现数组的map方法

  • 回调函数的参数有哪些,返回值如何处理

  • 不修改原来的数组

Array.prototype.MyMap = function(fn, context){var arr = Array.prototype.slice.call(this);//由于是ES5所以就不用...展开符了var mappedArr = [];for (var i = 0; i < arr.length; i++ ){mappedArr.push(fn.call(context, arr[i], i, this));}return mappedArr;
}

26 用ES5实现数组的reduce方法

  • 初始值不传怎么处理

  • 回调函数的参数有哪些,返回值如何处理。

Array.prototype.myReduce = function(fn, initialValue) {var arr = Array.prototype.slice.call(this);var res, startIndex;res = initialValue ? initialValue : arr[0];startIndex = initialValue ? 0 : 1;for(var i = startIndex; i < arr.length; i++) {res = fn.call(null, res, arr[i], i, this);}return res;
}
  • 对于普通函数,绑定this指向

  • 对于构造函数,要保证原函数的原型对象上的属性不能丢失

Function.prototype.bind = function(context, ...args) {let self = this;//谨记this表示调用bind的函数let fBound = function() {//this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)return self.apply(this instanceof fBound ? this : context || window, args.concat(Array.prototype.slice.call(arguments)));}fBound.prototype = Object.create(this.prototype);//保证原函数的原型对象上的属性不丢失return fBound;
}

大家平时说的手写bind,其实就这么简单

27 实现单例模式

核心要点: 用闭包和Proxy属性拦截

function proxy(func) {let instance;let handler = {constructor(target, args) {if(!instance) {instance = Reflect.constructor(fun, args);}return instance;}}return new Proxy(func, handler);
}

28 实现数组的flat

需求:多维数组=>一维数组

let ary = [1, [2, [3, [4, 5]]], 6];
let str = JSON.stringify(ary);
//第0种处理:直接的调用
arr_flat = arr.flat(Infinity);
//第一种处理
ary = str.replace(/(\[|\])/g, '').split(',');
//第二种处理
str = str.replace(/(\[\]))/g, '');
str = '[' + str + ']';
ary = JSON.parse(str);
//第三种处理:递归处理
let result = [];
let fn = function(ary) {for(let i = 0; i < ary.length; i++) }{let item = ary[i];if (Array.isArray(ary[i])){fn(item);} else {result.push(item);}}
}
//第四种处理:用 reduce 实现数组的 flat 方法
function flatten(ary) {return ary.reduce((pre, cur) => {return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);}, []);
}
let ary = [1, 2, [3, 4], [5, [6, 7]]]
console.log(flatten(ary))
//第五种处理:扩展运算符
while (ary.some(Array.isArray)) {ary = [].concat(...ary);
}

29 请实现一个 add 函数,满足以下功能

add(1);          // 1
add(1)(2);      // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add() {let args = [].slice.call(arguments);let fn = function(){let fn_args = [].slice.call(arguments)return add.apply(null,args.concat(fn_args))}fn.toString = function(){return args.reduce((a,b)=>a+b)}return fn
}

30 实现一个 sleep 函数,比如 sleep(1000) 意味着等待1000毫秒

const sleep = (time) => {return new Promise(resolve => setTimeout(resolve, time))
}sleep(1000).then(() => {// 这里写你的骚操作
})

31 实现 (5).add(3).minus(2) 功能

例: 5 + 3 - 2,结果为 6

Number.prototype.add = function(n) {return this.valueOf() + n;
};
Number.prototype.minus = function(n) {return this.valueOf() - n;
};

32 给定两个数组,写一个方法来计算它们的交集

例如:给定 nums1 = [1, 2, 2, 1],nums2 = [2, 2],返回 [2, 2]。

function union (arr1, arr2) {return arr1.filter(item => {return arr2.indexOf(item) > - 1;})
}const a = [1, 2, 2, 1];const b = [2, 3, 2];console.log(union(a, b)); // [2, 2]

33 实现一个JS函数柯里化

是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术

通用版

function curry(fn, args) {var length = fn.length;var args = args || [];return function(){let newArgs = args.concat(Array.prototype.slice.call(arguments));if (newArgs.length < length) {return curry.call(this,fn,newArgs);}else{return fn.apply(this,newArgs);}}
}function multiFn(a, b, c) {return a * b * c;
}var multi = curry(multiFn);multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4)

ES6写法

const curry = (fn, arr = []) => (...args) => (arg => arg.length === fn.length? fn(...arg): curry(fn, arg)
)([...arr, ...args])let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) //返回10
curryTest(1,2)(4)(3) //返回10
curryTest(1,2)(3,4) //返回10

34 实现一个双向绑定

defineProperty 版本

// 数据
const data = {text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {// 数据变化 --> 修改视图set(newVal) {input.value = newVal;span.innerHTML = newVal;}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {data.text = e.target.value;
});

proxy 版本

// 数据
const data = {text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {set(target, key, value) {target[key] = value;// 数据变化 --> 修改视图input.value = value;span.innerHTML = value;return value;}
};
const proxy = new Proxy(data, handler);// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {proxy.text = e.target.value;
});

35 Array.isArray 实现

Array.myIsArray = function(o) {return Object.prototype.toString.call(Object(o)) === '[object Array]';
};console.log(Array.myIsArray([])); // true

36 对象数组如何去重

根据每个对象的某一个具体属性来进行去重

const responseList = [{ id: 1, a: 1 },{ id: 2, a: 2 },{ id: 3, a: 3 },{ id: 1, a: 4 },
];
const result = responseList.reduce((acc, cur) => {const ids = acc.map(item => item.id);return ids.includes(cur.id) ? acc : [...acc, cur];
}, []);
console.log(result); // -> [ { id: 1, a: 1}, {id: 2, a: 2}, {id: 3, a: 3} ]

37 实现一个函数判断数据类型

function getType(obj) {if (obj === null) return String(obj);return typeof obj === 'object' ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase(): typeof obj;
}// 调用
getType(null); // -> null
getType(undefined); // -> undefined
getType({}); // -> object
getType([]); // -> array
getType(123); // -> number
getType(true); // -> boolean
getType('123'); // -> string
getType(/123/); // -> regexp
getType(new Date()); // -> date

38 查找字符串中出现最多的字符和个数

// 例: abbcccddddd -> 字符最多的是d,出现了5次let str = "abcabcabcbbccccc";
let num = 0;
let char = '';// 使其按照一定的次序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){num = $0.length;char = $1;        }
});
console.log(`字符最多的是${char},出现了${num}次`);

39 数组去重问题

首先:我知道多少种去重方式

双层 for 循环

function distinct(arr) {for (let i=0, len=arr.length; i<len; i++) {for (let j=i+1; j<len; j++) {if (arr[i] == arr[j]) {arr.splice(j, 1);// splice 会改变数组长度,所以要将数组长度 len 和下标 j 减一len--;j--;}}}return arr;
}

思想: 双重 for 循环是比较笨拙的方法,它实现的原理很简单:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行比对,如果不重复则添加到新数组中,最后返回新数组;因为它的时间复杂度是O(n^2),如果数组长度很大,效率会很低

Array.filter() 加 indexOf/includes

function distinct(a, b) {let arr = a.concat(b);return arr.filter((item, index)=> {//return arr.indexOf(item) === indexreturn arr.includes(item)})
}

思想: 利用indexOf检测元素在数组中第一次出现的位置是否和元素现在的位置相等,如果不等则说明该元素是重复元素

ES6 中的 Set 去重

function distinct(array) {return Array.from(new Set(array));
}

思想: ES6 提供了新的数据结构 Set,Set 结构的一个特性就是成员值都是唯一的,没有重复的值。

reduce 实现对象数组去重复

var resources = [{ name: "张三", age: "18" },{ name: "张三", age: "19" },{ name: "张三", age: "20" },{ name: "李四", age: "19" },{ name: "王五", age: "20" },{ name: "赵六", age: "21" }
]
var temp = {};
resources = resources.reduce((prev, curv) => {// 如果临时对象中有这个名字,什么都不做if (temp[curv.name]) {}else {// 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中temp[curv.name] = true;prev.push(curv);}return prev
}, []);
console.log("结果", resources);

这种方法是利用高阶函数 reduce 进行去重, 这里只需要注意initialValue得放一个空数组[],不然没法push

前端面试高频手写题目相关推荐

  1. 前端面试高频手写代码题

    前端面试高频手写代码题 一.实现一个解析URL参数的方法 方法一:String和Array的相关API 方法二: Web API 提供的 URL 方法三:正则表达式+string.replace方法 ...

  2. 前端面试:手写代码JS实现字符串反转

    前端萌新面试:手写代码JS实现字符串反转 前言 因为做前年小红书的前端校招面试题,发现出现好几道关于字符串对象和数组对象的题目,说难不难,但突然要写的话一时想不起来,这不想着做个小总结. 首先明白字符 ...

  3. 前端面试之手写代码篇

    手写代码 1.手写instanceof方法 2.手写new操作符 3.手写Promise.all() 4.手写防抖函数 5.手写节流函数 6.手写call.apply函数 7.手写bind函数 8.封 ...

  4. 银行前端面试高频基础问题——var、let和const到底有哪些区别?讲不清楚当场发感谢信!?

    银行前端面试高频基础问题--var.let和const到底有哪些区别?讲不清楚当场发感谢信!? 面试官:知道const.let和var吧,说说他们的区别吧 我:- - 前言 可以说这是银行我们面试遇到 ...

  5. Chat Top10 | 给面试官手写一个 Nacos,多少 K?

    每周推荐的最新 Chat Top10 没有固定主题,仅仅是编辑部参考多方评分和反馈挑选出来的好文章,不一定适合你的口味,建议小心食用- 我们一起看下第三期 Chat Top10 都有哪些内容 ???? ...

  6. 前端面试高频考点,ES6知识点汇总!!!

    前端面试高频考点,ES6知识点汇总!!! ⛳️大家好,我是王同学,今天给大家分享的是ES6面试的高频考点,文章没有一点套路,只有满满的干货 ⛳️如果对你有帮助就给我点个赞吧,这样我们就互不相欠了 ⛳️ ...

  7. JS 面试问题: 手写 new

    JS 面试问题: 手写 new

  8. web前端面试高频考点——Vue原理(理解MVVM模型、深度/监听data变化、监听数组变化、深入了解虚拟DOM)

    系列文章目录 内容 参考链接 Vue基本使用 Vue的基本使用(一文掌握Vue最基础的知识点) Vue通信和高级特性 Vue组件间的通信及高级特性(多种组件间的通信.自定义v-model.nextTi ...

  9. 前端高频手写面试题总结

    实现字符串的repeat方法 输入字符串s,以及其重复的次数,输出重复的结果,例如输入abc,2,输出abcabc. function repeat(s, n) {return (new Array( ...

最新文章

  1. Cisco交换机解决网络蠕虫病毒***问题
  2. Openstack组件部署 — Nova_安装和配置Controller Node
  3. matlab建模实例运筹学,matlab数学建模实例与编程教程
  4. 你了解 Java 的 jstat 命令吗?
  5. vim插件推荐之auto-pairs
  6. oracle数据库优化--基本概念
  7. Oracle学习:新建表空间
  8. dubbo启动服务启动报错.UnsatisfiedDependencyException: Error creating bean with name '***': Un
  9. 电脑编程教学_东莞沙田mastercam编程学习怎么收费
  10. IOS提交审核 错误Missing Push Notification Entitlement
  11. OBS Windows10 1909版本黑屏问题解决方案
  12. 打通语言理论和统计 NLP 两个世界,Transformers/GNNs 架构能做到吗?
  13. java pipeline 实现_Docker+Jenkins+Pipeline实现持续集成(二)java项目构建
  14. linux on android 项目,好累,终于完成了 Android-on-Yeeloong 项目的搭建
  15. java的编译原理_Javac编译原理 - Martiny的个人空间 - OSCHINA - 中文开源技术交流社区...
  16. std::setw的坑
  17. 11.什么是Heuristic
  18. CSS:CSS的外联样式
  19. 如何写好一份数据分析报告?
  20. USB总线转串口芯片:沁恒CH340

热门文章

  1. Netty+SpringBoot搭建游戏服务器(带控制台客户端)
  2. 多模态:指代图像分割
  3. 网购秒杀系统架构设计
  4. ​怎么把图片无损放大?分享一个图片无损放大小妙招
  5. 【有限元仿真】ABAQUS人工能量-ALLVD
  6. 充电2.0: 逆向无线充电的转变
  7. 高并发分布式下生成全局ID的几种方法
  8. LCD MIPI解析
  9. thymeleaf中th:field和th:value的区别
  10. 如何刻录光盘镜像文件?