前端常见编程题
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)
})
}
}
先搭架子
- 要有一个对象,存储着它自己的触发函数。而且这个对象的触发函数可能有很多种,比如一个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函数第一个参数是累计值,如果有初始值,则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哟~
欢迎转载,但要注明出处哟~