前端常见编程题

    科技2022-07-12  151

    CSS篇

    垂直居中

    方式一

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>垂直居中</title> </head> <style> .wrapper { overflow: hidden; width: 1000px; height: 500px; background: #999; } .center { width: 18em; height: 10em; text-align: center; background-color: orange; color: #fff; margin: 50vh auto; transform: translateY(-50%); } </style> <body> <div class="wrapper"> <div class="center"> 基于视口的垂直居中<br /> 不要求原生有固定的宽高。<br /> 但是这种居中是在整个页面窗口内居中,不是基于父元素<br /> </div> </div> </body> </html>

    方式二

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>垂直居中</title> <style> .center { width: 18em; height: 10em; position: absolute; background: orange; text-align: center; top: 50%; left: 50%; margin-left: -9em; margin-top: -5em; /* 或者 transform: translate(-9em, -5em); */ } </style> </head> <body> <div class="center"> 要求原生有固定的宽高。<br /> position: absolute;<br /> top和left 为 50%;<br /> margin上为高的一半<br /> margin左为宽的一半<br /> </div> </body> </html>

    方式三

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>垂直居中</title> </head> <style> .center { width: 18em; height: 10em; text-align: center; background-color: orange; color: #fff; position: absolute; top: calc(50% - 5em); left: calc(50% - 9em); } </style> <body> <div class="center"> 要求原生有固定的宽高。<br/> position: absolute;<br/> top 为 calc(50% 剪 一半高) left 为 calc(50% 剪 一半宽) </div> </body> </html>

    方式四

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>垂直居中</title> </head> <style> .center { width: 18em; height: 10em; text-align: center; background-color: orange; color: #fff; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } </style> <body> <div class="center"> 不要求原生有固定的宽高。<br/> position: absolute;<br/> top和left 为 50%;<br/> transform: translate(-50%, -50%); </div> </body> </html>

    方式五

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>垂直居中</title> </head> <style> .wrapper { width: 1000px; height: 600px; background: #999; display: flex; } .center { width: 18em; height: 10em; text-align: center; background-color: orange; color: #fff; margin: auto; } </style> <body> <div class="wrapper"> <div class="center"> 使用flex居中<br/> 父元素 display: flex; <br/> 居中块: margin: auto; </div> </div> </body> </html>

    方式六

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>垂直居中</title> </head> <style> .wrapper { width: 1000px; height: 600px; background: #999; display: flex; justify-content: center; align-items: center; } .center { width: 18em; height: 10em; text-align: center; background-color: orange; color: #fff; } </style> <body> <div class="wrapper"> <div class="center"> 使用flex居中<br/> 父元素 display: flex; <br/> justify-content: center;<br/> align-items: center;<br/> </div> </div> </body> </html>

    水平居中

    方式一

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>水平居中</title> <style> .wrapper { text-align: center; height: 200px; background: orange; } .center { display: inline-block; width: 100px; height: 100px; } </style> </head> <body> <div class="wrapper"> <div class="center">如果需要居中的元素为常规流中 inline / inline-block 元素,为父元素设置 text-align: center;</div> </div> </body> </html>

    方式二

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>水平元素居中</title> </head> <style> .wrapper { width: 100%; height: 500px; text-align: center; /* 3 */ } .center { width: 500px; text-align: left; margin: 0 auto; background-color: orange; } </style> <body> <div class="wrapper"> <div class="center"> 父元素上设置 text-align: center;<br /> 居中元素上margin 为 auto。 </div> </div> </body> </html>

    方式三

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>水平元素居中</title> </head> <style> .wrapper { width: 80%; height: 500px; background: #888; position: relative; } .center { width: 500px; position: absolute; left: 50%; margin-left: -250px; background-color: orange; } </style> <body> <div class="wrapper"> <div class="center">如果元素positon: absolute; 那么 0)设置父元素postion: relative 1)为元素设置宽度,2)偏移量设置为 50%,3)偏移方向外边距设置为元素宽度一半乘以-1</div> </div> </body> </html>

    方式四

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>水平元素居中</title> </head> <style> .wrapper { width: 80%; height: 500px; background: #888; } .center { width: 500px; position: relative; left: 50%; margin-left: -250px; background-color: orange; } </style> <body> <div class="wrapper"> <div class="center">如果元素positon: relative。 那么 1)为元素设置宽度,2)偏移量设置为 50%,3)偏移方向外边距设置为元素宽度一半乘以-1</div> </div> </body> </html>

    布局

    绝对定位

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>绝对定位</title> <style> html, body { margin: 0; padding: 0; } * { margin: 0; padding: 0; } aside { position: absolute; width: 300px; min-height: 100px; } left { left: 0; background-color: red; } .right { right: 0; background-color: blue; } .center { position: absolute; left: 300px; right: 300px; background-color: orange; } </style> </head> <body> <aside class="left"></aside> <aside class="right"></aside> <main class="center"> <h1>绝对定位解决方案</h1> <p>左右区域分别postion:absolute,固定到左右两边</p> <p>中间区域postion:absolute;left:300px; right: 300px</p> <p>给总的宽度加一个min-width,不然缩小窗口会有毛病</p> </main> </body> </html>

    三栏-表格布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>表格布局</title> <style> html, body { margin: 0; padding: 0; } .wrapper { display: table; width: 100%; } .left, .main, .right { min-height: 100px; display: table-cell; } .left { width: 100px; background-color: red; } .main { background-color: orange; } .right { width: 200px; background-color: blue; } </style> </head> <body> <div class="wrapper"> <aside class="left"></aside> <main class="main"> <h1>表格布局</h1> <p>父元素display:table并且宽度为100%</p> <p>每一个子元素display:table-cell</p> <p>左右两侧添加宽度,中间不加宽度</p> </main> <aside class="right"></aside> </div> </body> </html>

    三栏-浮动方案

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>浮动方案</title> <style> html, body { padding: 0; margin: 0; } .left, .right, .main { height: 200px; } .left { float: left; width: 100px; background-color: blue; } .main { background-color: seagreen; width: 100%; } .right { float: right; width: 100px; background-color: red; } </style> </head> <body> <aside class="left"></aside> <aside class="right"></aside> <main class="center"> <h1>浮动解决方案</h1> <p>方法:left和right都写在center前面,并且分别左右浮动</p> <p>中间的这个div因为是块级元素,所以在水平方向按照他的包容快自动撑开</p> <p>简单,但是中心部分过长下面会溢出,然后文字就会跑到两边去。</p> </main> </body> </html>

    三栏-网格布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>网格布局</title> <style> html, body { padding: 0; margin: 0; } .wrapper { display: grid; width: 100%; grid-template-columns: 100px 1fr 100px; } .left { background-color: red; } .center { background-color: orange; } .right { background-color: blue; } </style> </head> <body> <div class="wrapper"> <aside class="left"></aside> <main class="center"> <h1>网格布局</h1> <p>父元素display:grid</p> </main> <aside class="right"></aside> </div> </body> </html>

    三栏-flex布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>flex布局</title> <style> html, body { padding: 0; margin: 0; } .wrapper { display: flex; height: 200px; } .left { width: 100px; background-color: seagreen; } .right { width: 200px; background-color: sienna; } .main { flex: 1; background-color: springgreen; } </style> </head> <body> <div class="wrapper"> <aside class="left"></aside> <main class="main"> <h1>flex布局</h1> </main> <aside class="right"></aside> </div> </body> </html>

    圣杯布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>圣杯布局</title> <style> html, body { padding: 0; margin: 0; } .left { width: 100px; background-color: springgreen; } .right { width: 200px; background-color: steelblue; } .main { width: 100%; background-color: red; } /* 关键代码 */ .left, .right, .main { float: left; position: relative; height: 200px; } .left { margin-left: -100%; left: -100px; } .container { padding-left: 100px; padding-right: 200px; } .right { margin-left: -200px; right: -200px; } </style> </head> <body> <div class="container"> <div class="main"></div> <div class="left"></div> <div class="right"></div> </div> </body> </html>

    双飞翼布局

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>双飞翼布局</title> <style> html, body { padding: 0; margin: 0; } .left, .right, .main { min-height: 200px; } .left { width: 200px; background-color: thistle; } .main { background: #999; width: 100%; } .right { width: 300px; background-color: violet; } /*关键代码*/ .left, .right, .main { float: left; } .main-inner { margin-left: 200px; margin-right: 300px; } .left { margin-left: -100%; } .right { margin-left: -300px; } </style> </head> <body> <div class="main"> <div class="main-inner">中心区</div> </div> <div class="left">left</div> <div class="right">right</div> </body> </html>

    JS篇

    闭包问题

    循环中赋值为引用的问题

    for (var i = 1; i < 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }

    解决方法有3种

    第一种,使用立即执行函数方式

    for (var i = 1; i < 5; i++) { (function(j){ setTimeout(function timer() { console.log(j) }, j * 1000) })(i) }

    第二种,使用ES6的let

    for (let i = 1; i < 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) }

    第三种,使用setTimeout的第三个参数

    for (var i = 1; i < 5; i++) { setTimeout(function timer(j) { console.log(j) }, i * 1000, i) }

    计数器

    实现一个foo函数 可以这么使用:

    a = foo(); b = foo(); c = foo(); // a === 1;b === 2;c === 3; foo.clear();d = foo(); //d === 1; function myIndex() { var index = 1; function foo(){ return index++; } foo.clear = function() { index = 1; } return foo; } var foo = myIndex();

    防抖节流

    防抖 debounce

    函数防抖就是在函数需要频繁触发的情况下,只有足够的空闲时间,才执行一次。

    典型应用

    百度搜索框在输入稍有停顿时才更新推荐热词。拖拽 function debounce(handler, delay){ delay = delay || 300; var timer = null; return function(){ var _self = this, _args = arguments; clearTimeout(timer); timer = setTimeout(function(){ handler.apply(_self, _args); }, delay); }

    为啥要记录this

    // 频繁触发时,清楚对应的定时器,然后再开一个定时器,delay秒后执行 function debounce(handler, delay){ delay = delay || 300; var timer = null; return function(){ var _self = this, _args = arguments; clearTimeout(timer); timer = setTimeout(function(){ handler.apply(_self, _args); }, delay); } } // 不希望被频繁调用的函数 function add(counterName) { console.log(counterName + ": " + this.index ++); } // 需要的上下文对象 let counter = { index: 0 } // 防抖的自增函数,绑定上下文对象counter let db_add = debounce(add, 10).bind(counter) // 每隔500ms频繁调用3次自增函数,但因为防抖的存在,这3次内只调用一次 setInterval(function() { db_add("someCounter1"); db_add("someCounter2"); db_add("someCounter3"); }, 500) /** * 预期效果: * * 每隔500ms,输出一个自增的数 * 即打印: someCounter3: 0 someCounter3: 1 someCounter3: 2 someCounter3: 3 */

    节流 throttle

    一个函数只有在大于执行周期时才执行,周期内调用不执行。好像水滴积攒到一定程度才会触发一次下落一样。

    典型应用:

    抢券时疯狂点击,既要限制次数,又要保证先点先发出请求窗口调整页面滚动 function throttle(fn,wait=300){ var lastTime = 0 return function(){ var that = this,args=arguments var nowTime = new Date().getTime() if((nowTime-lastTime)>wait){ fn.apply(that,args) lastTime = nowTime } } }

    观察者模式

    JS观察者模式

    观察者模式:观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。而js中最常见的观察者模式就是事件触发机制。

    先来个完整的

    class EventEmitter { constructor () { this.eventPool = { // 'eventName': [] } } listen(eventName, callback) { if(this.eventPool[eventName]) { if(this.eventPool[eventName].indexOf(callback) === -1) { this.eventPool[eventName].push(callback) } } else { this.eventPool[eventName] = [callback] } } // trigger是有参数的 trigger(eventName, ...args) { if(this.eventPool[eventName]) { this.eventPool[eventName].forEach(cb => cb(...args)) } } remove(eventName, callback) { if(this.eventPool[eventName]) { let cbIndex = this.eventPool[eventName].indexOf(callback) this.eventPool[eventName].splice(cbIndex, 1) } } once(eventName, callback) { this.listen(eventName, function _cb(...args) { callback(...args); this.remove(eventName, _cb) }) } }

    ES5/ES6实现观察者模式(自定义事件) - 简书

    先搭架子

    要有一个对象,存储着它自己的触发函数。而且这个对象的触发函数可能有很多种,比如一个onclick可能触发多个事件,那么handler的属性应该是一个数组,每个数组的值都是一个函数。 handler={ type1:[func1,func2...], type2:[func3,func4...], ... }

    现在这个对象的主体部分已经思考好了,现在就是要它‘动起来’,给它添加各种动作。 一个事件可能有哪些动作呢?

    add:添加事件某种类型的函数,remove: 移除某种类型的函数,fire:触发某种类型的函数,once:触发某种类型的函数,然后移除掉这个函数

    现在,自定义事件的架子已经搭建好了

    eventOb={ //函数储存 handler:{ type1:[func1,func2...], type2:[func2,func4...], ... }, //主要事件 add:function(){}, remove:function(){}, fire:function(){}, once:function(){}, }

    add

    添加一个事件监听,首先传入参数应该是 事件类型type,和触发函数 func,传入的时候检测有没有这个函数,有了就不重复添加。

    add:function (type,func) { //检测type是否存在 if(eventOb.handleFunc[type]){ //检测事件是否存在,不存在则添加 if(eventOb.handleFunc[type].indexOf(func)===-1){ eventOb.handleFunc[type].push(func); } } else{ eventOb.handleFunc[type]=[func]; } },

    remove

    remove有一个潜在的需求,就是如果你的事件不存在,它应该会报错。而这里不会报错,index在func不存在的时候是-1;这时候要报错。

    remove:function (type,func) { try{ let target = eventOb.handleFunc[type]; let index = target.indexOf(func); if(index===-1) throw error; target.splice(index,1); }catch (e){ console.error('别老想搞什么飞机,删除我有的东西!'); } },

    fire

    触发一个点击事件肯定是要触发它全部的函数,这里也是一样,所以只需要传入type,然后事件可能不存在,像上面一样处理。

    fire:function (type,func) { try{ let target = eventOb.handleFunc[type]; let count = target.length; for (var i = 0; i < count; i++) { //加()使立即执行 target[i](); } } catch (e){ console.error('别老想搞什么飞机,触发我有的东西!'); } },

    但会有问题,我只想触发并且删除某个事件怎么办,fire一下就全触发了呀。 所以fire的问题就显现出来了。我们还是要给它一个func,但是可选。

    fire:function (type,func) { try{ let target = eventOb.handleFunc[type]; if(arguments.length===1) { //不传func则全部触发 let count = target.length; for (var i = 0; i < count; i++) { target[i](); } }else{ //传func则触发func let index=target.indexOf(func); if(index===-1)throw error; func(); } //need some code }catch (e){ console.error('别老想搞什么飞机,触发我有的东西!'); //need some code } },

    once

    fire,然后remove

    once (event, callback) { this.fire(event, (...args) => { callback(...args); this.remove(event) }) }

    完整代码

    class eventObs { constructor(){ this.handleFunc={} } add(type,func){ if(this.handleFunc[type]){ if(this.handleFunc[type].indexOf(func)===-1){ this.handleFunc[type].push(func); } }else{ this.handleFunc[type]=[func]; } }; fire(type,func){ try{ if(arguments.length===1) { let target = this.handleFunc[type]; let count = target.length; for (var i = 0; i < count; i++) { target[i](); } }else{ let target = this.handleFunc[type]; let index=target.indexOf(func); if(index===-1)throw error; func(); } return true; }catch (e){ console.error('别老想搞什么飞机,触发我有的东西!'); return false; } }; remove(type,func){ try{ let target = this.handleFunc[type]; let index=target.indexOf(func); if(index===-1)throw error; target.splice(index,1); }catch (e){ console.error('别老想搞什么飞机,删除我有的东西!'); } }; once(type,func) { this.fire(type, func) ? this.remove(type, func) : null; } }

    尽早顺序打印Ajax请求

    /** * 接受一个URL数组做参数,并行请求,尽可能块的按照顺序打印内容 */ const urlList = [1, 2, 3, 4, 5] loadData(urlList) function fetchData(url, succCallback) { setTimeout(() => { succCallback('ok: ' + url); }, (Math.random() * 5 * 1000) >> 0); } function loadData(urlList) { let resArr = [], doneId = 0 for (let i = 0; i < urlList.length; i++) { fetchData(urlList[i], res => { console.log(`${i+1} is done`) resArr[i] = res outPutRes(resArr) }) } function outPutRes(resArr) { for (let i = doneId; i < resArr.length; i++) { if (resArr[i]) { console.log(resArr[i]); doneId++; } else { break; } } } }

    curry

    柯里化(英语:Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。

    实现一个add方法,使计算结果能够满足如下预期:

    add(1)(2)(3) = 6 add(1, 2)(3) = 10

    实现方法: 做一个闭包,返回一个函数,这个函数每次执行会改写闭包里面记录参数的数组。当这个函数判断参数个数够了,就去执行它。

    function curry(func) { // 存储已传入参数 let _args = [] // 做一个闭包 function _curry(...args) { // 把参数合并 _args = _args.concat(args) // 如果参数够了就执行 if (_args.length >= func.length) { const result = func(..._args) _args = [] return result; } // 继续返回此函数 else { return _curry } } return _curry } // 测试代码 function add1(a, b, c) { return a + b + c } let testAdd = curry(add1) console.log(testAdd(1)(2)(3)) console.log(testAdd(1, 2)(3)) console.log(testAdd(1)(2, 3))

    实现一个类型判断函数

    判断null判断基础类型使用Object.prototype.toString.call(target)来判断引用类型

    注意: 一定是使用call来调用,不然是判断的Object.prototype的类型 之所以要先判断是否为基本类型是因为:虽然Object.prototype.toString.call()能判断出某值是:number/string/boolean,但是其实在包装的时候是把他们先转成了对象然后再判断类型的。 但是JS中包装类型和原始类型还是有差别的,因为对一个包装类型来说,typeof的值是object

    /** * 类型判断 */ function getType(target) { //先处理最特殊的Null if(target === null) { return 'null'; } //判断是不是基础类型 const typeOfT = typeof target if(typeOfT !== 'object') { return typeOfT; } //肯定是引用类型了 const template = { "[object Object]": "object", "[object Array]" : "array", "[object Function]": "function", // 一些包装类型 "[object String]": "object - string", "[object Number]": "object - number", "[object Boolean]": "object - boolean" }; const typeStr = Object.prototype.toString.call(target); return template[typeStr]; }

    实现一个 sleep 函数

    比如 sleep(1000) 意味着等待1000毫秒,可从 Promise、Generator、Async/Await 等角度实现

    Promise

    const sleep = time =>{ new Promise((resolve) => { setTimeout(resolve, time) }) } sleep(1000).then(() => { console.log(1) })

    Generator

    function *sleep(time) { yield new Promise(resolve => { setTimeout(resolve, time) }) } sleep(1000).next().value.then(() => { console.log(1) })

    async

    async function sleep(time, func) { await new Promise(resolve => setTimeout(resolve, time)) return func() } sleep(1000, () => { console.log(1) })

    ES5

    function sleep(callback,time) { if(typeof callback === 'function') setTimeout(callback,time) } function output(){ console.log(1); } sleep(output,1000);

    异步编程

    promise与setTimeout 判断执行顺序

    promise和setTimeout都会将事件放入异步队列,但setTimeout即便是写0,也会有4ms的延迟

    console.log('begin'); setTimeout(() => { console.log('setTimeout 1'); Promise.resolve() .then(() => { console.log('promise 1'); setTimeout(() => { console.log('setTimeout2'); }); }) .then(() => { console.log('promise 2'); }); new Promise(resolve => { console.log('a'); resolve(); }).then(() => { console.log('b'); }); }, 0); console.log('end');

    答案

    begin end setTimeout 1 a promise 1 b promise 2 setTimeout2

    async函数的使用

    function repeat(func, times, wait) { } // 输入 const repeatFunc = repeat(alert, 4, 3000); // 输出 // 会alert4次 helloworld, 每次间隔3秒 repeatFunc('hellworld'); // 会alert4次 worldhellp, 每次间隔3秒 repeatFunc('worldhello')

    我自己的实现,没有成功。这种实现是setTimeout新建了两个,然而只清理了一个。

    function repeat(func, times, wait) { var timer = null; var count = 0; return function(...args) { timer = setInterval(function() { func.apply(null, args); count ++; console.log('count', count, "times", times) if( count >= times) { clearInterval(timer); } }, wait); } } // 输入 const repeatFunc = repeat(console.log, 4, 3000); // 输出 // 会alert4次 helloworld, 每次间隔3秒 repeatFunc('hellworld'); // 会alert4次 worldhellp, 每次间隔3秒 repeatFunc('worldhello');

    正确解法:使用 async/await来实现

    async function wait(seconds) { return new Promise((res) => { setTimeout(res, seconds); }); } function repeat(func, times, s) { return async function (...args) { for (let i = 0; i < times; i++) { func.apply(null, args); await wait(s); } }; } let log = console.log let repeatFunc = repeat(log,4,3000) repeatFunc('HelloWorld') repeatFunc('WorldHello')

    async执行练习

    await后面的才是异步的,之前都是同步的 async function async1() { console.log('async1 start'); // 2 await async2(); console.log('async1 end'); // 6 } async function async2() { console.log('async2'); // 3 } console.log('script start'); // 1 setTimeout(function() { console.log('setTimeout'); // 8 }, 0); async1(); new Promise(function(resolve) { console.log('promise1'); // 4 resolve(); }).then(function() { console.log('promise2'); // 7 }); console.log('script end'); // 5

    bind、apply实现

    自封装bind方法

    因为bind的使用方法是 某函数.bind(某对象,…剩余参数) 所以需要在Function.prototype 上进行编程 将传递的参数中的某对象和剩余参数使用apply的方式在一个回调函数中执行即可要在第一层获取到被绑定函数的this,因为要拿到那个函数用apply /** * 简单版本 */ Function.prototype.myBind = (that, ...args) => { const funcThis = this; return function(..._args) { return funcThis.apply(that, args.concat(_args)); } } Function.prototype.mybind = function(ctx) { var _this = this; var args = Array.prototype.slice.call(arguments, 1); return function() { return _this.apply(ctx, args.concat(args, Array.prototype.slice.call(arguments))) } } /** * 自封装bind方法 * @param {对象} target [被绑定的this对象, 之后的arguments就是被绑定传入参数] * @return {[function]} [返回一个新函数,这个函数就是被绑定了this的新函数] */ Function.prototype.myBind = function (target){ target = target || window; var self = this; var args = [].slice.call(arguments, 1); var temp = function(){}; var F = function() { var _args = [].slice.call(arguments, 0); return self.apply(this instanceof temp ? this: target, args.concat(_args)); } temp.prototype = this.prototype; //当函数是构造函数时,维护原型关系 F.prototype = new temp(); return F; }

    自封装一个apply

    首先要先原型上即 Function.prototype上编程需要拿到函数的引用, 在这里是 this让 传入对象.fn = this执行 传入对象.fn(传入参数)返回执行结果 Function.prototype.myApply = function(context) { if (typeof this !== 'function') { throw new TypeError('Error') } context = context || window context.fn = this let result // 处理参数和 call 有区别 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result }

    deepclone

    const typeMap = { '[object Array]': 'array', '[object Object]': 'object', '[object Function]': 'function', '[object Symbol]': 'symbol', '[object RegExp]': 'regexp' } function deepClone(target, map = new WeakMap()) { let cloneTarget let type = typeMap[getType(target)] if (type === 'symbol') { //处理symbol return Object(Symbol.prototype.valueOf.call(target)); } else if (type === 'function') { //处理function return cloneFunction(target) } else if (type === 'object' || type === 'array') { cloneTarget = getInit(target) } else { return target } //避免循环引用 if (map.get(target)) { return map.get(target) } else { map.set(target, cloneTarget) } //遍历 for (const key in target) { cloneTarget[key] = deepClone(target[key], map) } return cloneTarget function getInit(target) { const constructor = target.constructor return new constructor() } function getType(target) { return Object.prototype.toString.call(target) } function cloneFunction(func) { const bodyReg = /\{([\s\S]*)\}$/; const paramReg = /(?<=\().+(?=\)\s+{)/; const funcString = func.toString(); if (func.prototype) { console.log('普通函数'); const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); console.log(body) if (body) { console.log('匹配到函数体:', body[0]); if (param) { const paramArr = param[0].split(','); console.log('匹配到参数:', paramArr); return new Function(...paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); } } } const target = { //待处理正则 field1: 1, field2: undefined, field3: { child: 'child' }, field4: [2, 4, 8], field6: function (age, w) { console.log(age, w) }, field7: Symbol('www') }; target.target = target; let t = deepClone(target) // t.field3.child = '2' // console.log(target) // console.log(t) console.log(deepClone(target))

    flat

    //数组摊平为一维 let arr = [1, 2, [3, 4, 5, [6, 7], 8], 9, 10, [11, [12, 13]]] let result = [] function flat(arr) { for (let i = 0; i < arr.length; i++) { if (Array.isArray(arr[i])) { flat(arr[i]) } else { result.push(arr[i]) } } } flat(arr) console.log(result)

    jsonp

    //http://www.baidu.com?aa=11&callback=my_jsonp04349289664328899 var jsonp = function (url, param, callback) { //处理url地址,查找?,如果没有?这个变量就有一个"?",有?这个变量接收一个& var querystring = url.indexOf("?") == -1 ? "?" : "&"; //处理参数{xx:xx} for (var k in param) { querystring += k + "=" + param[k] + '&'; //?k=para[k] } //处理回调函数名 var random = Math.random().toString().replace(".", ""); var cbval = "my_jsonp" + random; var cb = "callback=" + cbval; querystring += cb; var script = document.createElement("script"); script.src = url + querystring; //把回调函数的名字附给window window[cbval] = function (param) { //这里执行回调的操作,用回调来处理参数 callback(param); //拿到了就删掉这个script document.body.removeChild(script); }; document.body.appendChild(script); } jsonp( "https://www.baidu.com", { aa: 11 }, function () { console.log(param); } );

    reduce

    reduce

    reduce函数第一个参数是累计值,如果有初始值,则total=初始值,cur=arr[0],否则,total=arr[0],cur=arr[1]每次返回的值当做下一次的初始值输入

    累加累乘

    function Accumulation(...vals) { return vals.reduce((t, v) => t + v, 0); } function Multiplication(...vals) { return vals.reduce((t, v) => t * v, 1); } Accumulation(1, 2, 3, 4, 5); // 15 Multiplication(1, 2, 3, 4, 5); // 120

    权重求和

    const scores = [{ score: 90, subject: "chinese", weight: 0.5 }, { score: 95, subject: "math", weight: 0.3 }, { score: 85, subject: "english", weight: 0.2 } ]; const result = scores.reduce((total, cur) => total + cur.score * cur.weight, 0)

    reverse

    let arr = [1, 2, 3, 4, 5] let result result = arr.reduceRight(function (total, cur) { return total.concat(cur) }, []) console.log(result);//[ 5, 4, 3, 2, 1 ]

    实现map

    let arr = [0, 2, 4, 6] let result let map = v => v * 2 //map result = arr.reduce((total, cur) => { return total.concat(map(cur)) }, []) console.log(result);

    扁平化

    let arr = [0, 1, [2, 3], [4, 5, [6, 7]], [8, [9, 10, [11, 12]]] ]; let result function flat(arr) { return arr.reduce((total, cur) => Array.isArray(cur) ? total.concat(flat(cur)) : total.concat(cur), []) } console.log(flat(arr)) //[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]

    去重

    let arr = [2, 1, 0, 3, 2, 1, 2]; let result function uniq(arr) { return arr.reduce((total, cur) => total.includes(cur) ? total : total.concat(cur), []) } console.log(uniq(arr)) // [2, 1, 0, 3]

    this指向

    this指向

    面试题目

    请分别写出下面题目的答案。

    function Foo() { getName = function() { console.log(1); }; return this; } Foo.getName = function() { console.log(2); }; Foo.prototype.getName = function() { console.log(3); }; var getName = function() { console.log(4); }; function getName() { console.log(5); } //请写出以下输出结果: Foo.getName(); //-> 2 Foo对象上的getName() ,这里不会是3,因为只有Foo的实例对象才会是3,Foo上面是没有3的 getName(); //-> 4 window上的getName,console.log(5)的那个函数提升后,在console.log(4)的那里被重新赋值 Foo().getName(); //-> 1 在Foo函数中,getName是全局的getName,覆盖后输出 1 getName(); //-> 1 window中getName(); new Foo.getName(); //-> 2 Foo后面不带括号而直接 '.',那么点的优先级会比new的高,所以把 Foo.getName 作为构造函数 new Foo().getName();//-> 3 此时是Foo的实例,原型上会有输出3这个方法

    箭头函数中的this 判断

    箭头函数里面的this是继承它作用域父级的this, 即声明箭头函数处的this

    let a = { b: function() { console.log(this) }, c: () => { console.log(this) } } a.b() // a a.c() // window let d = a.b d() // window

    this判断 下面输出为多少?

    var name1 = 1; function test() { let name1 = 'kin'; let a = { name1: 'jack', fn: () => { var name1 = 'black' console.log(this.name1) } } return a; } test().fn() // ?

    答案: 输出1

    因为fn处绑定的是箭头函数,箭头函数并不创建this,它只会从自己的作用域链的上一层继承this。这里它的上一层是test(),非严格模式下test中this值为window。

    如果在绑定fn的时候使用了function,那么答案会是 ‘jack’如果第一行的 var 改为了 let,那么答案会是 undefined, 因为let不会挂到window上

    最后

    行文至此,感谢阅读,如果您喜欢的话,可以帮忙点个like哟~

    欢迎转载,但要注明出处哟~​

    该文章首发自【全栈web之路】,web开发之路,诚邀您携手同行。

    Processed: 0.014, SQL: 8