for循环中放定时器造成的异步问题
//问题复现
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
console.log(i)
//实际输出
5->5 5 5 5 5 //->代表间隔了1s
-
如果我们期待的输出是5 -.> 0,1,2,3,4 有几种改写方式呢?
//第一种、使用闭包中的IIFE(立即执行函数) for (var i = 0; i < 5; i++) { (function(j){ setTimeout(function() { console.log(i); }, 1000); })(i); } console.log(i) //第二种、用setTimeOut的参数 for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000,i); } console.log(i) //第三种、利用js中函数的传参是按值传递(把for和setTimeOut分离,将循环的值传进一个带定时器的函数) function output(i){ setTimeout(function() { console.log(i); }, 1000,i); } for(var i=0;i<5;i++){ output(i) } console.log(i) //第四种、利用es6中的let for (let i = 0; i < 5; i++) { (function(j){ setTimeout(function() { console.log(i); }, 1000); })(i); } console.log(i) //这里i要报错,因为i改为块级作用域,所以全局作用域不存在一个i
-
如果我们期待的输出是0->1->2->3->4->5,又该如何改写呢?
//方法一、暴力改写法,定时器改成逐渐递增1s的 for (var i = 0; i < 5; i++) { (function(j) { setTimeout(function() { console.log(j); }, 1000 * j); // 这里修改 0~4 的定时器时间 })(i); } setTimeout(function() { // 这里增加定时器,超时设置为 5 秒 console.log(i); }, 1000 * i); //方法二、promise+es6改写 (在所有异步操作都结束后再进行一个操作) let arr = [] let output = (i)=>new Promise((resolve,reject)=>{ setTimeout((i)=>{ console.log(i) resolve() },1000*i) }) for(var i=0;i<5;i++){ arr.push(output(i)) } Promise.all(arr).then(()=>{ setTimeout(()=>{ console.log(i) },1000) }) //方法三、手写一个sleep函数,利用async和await // 模拟其他语言中的 sleep,实际上可以是任何异步操作 const sleep = (timeountMS) => new Promise((resolve) => { setTimeout(resolve, timeountMS); }); (async () => { // 声明即执行的 async 函数表达式 for (var i = 0; i < 5; i++) { if (i > 0) { await sleep(1000); } console.log(new Date, i); } await sleep(1000); console.log(new Date, i); })();
几种手写sleep函数,以及可能遇到的问题
手写new函数(还可以继续更新)
目标有三:
1.获取原型中的属性
2.获取构造函数的私有属性
3.根据构造函数返回值的差异设置实例的返回值
Function.prototype.new = (cons,...args)=>{
if(typeof cons!= 'Function'){
throw 'this new function the first param must be a function'
}
let obj = Object.create(cons.prototype)
let res = cons.apply(obj,args)
if((typeof res==='Object'&& typeof res!=='null')||typeof res==='Function'){
return res
}
return obj
}
手写apply和call
目标有三
1.将函数放进新的目标this对象中
2.执行他
3.删除它
Function.prototype.call = function(context,...args){
var context = context||window
context.fn = this
let res = context.fn(...args)
delete context.fn
return res
}
Function.prototype.apply = function(context,args){
var context = context||window
context.fn = this
let res = context.fn(...args)
delete context.fn
return res
}
使用es6的解构语法,apply和call的区别只在传参的处理:…args/args
手写bind
目标有许多
1.返回一个函数
2.这个函数也可以作为构造函数,所以this指向也要相应的更改
3.构造函数和bind函数的传参要叠加在一起使用
Function.prototype.bind = function(context,...args){
if(typeof this != 'Function'){
throw new Error('Function.prototype.bind - what is trying to be bound is not callable')
}
var self = this
var fun =function(...arr){
self.apply(this instanceof self?this:context,[...args,...arr])
}
fun.prototype = Object.create(self.prototype)
return fun
}
new object()和object.create()的区别
new Object() 通过构造函数来创建对象, 添加的属性是在自身实例下。
Object.create() 创建对象的另一种方式,可以理解为继承一个对象, 添加的属性是在原型下。
使用到这个知识点的地方:
-
最优的继承方案:寄生式组合继承
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;
-
手写new 详见上文
-
手写bind 详见上文
类数组转普通数组的方法
- [].slice.call(arguments)
- Array.from()
- [].concat.apply([],arguments)
- […arguments]
js数组判断是否包含某一值
- arr.indexOf() 返回索引
- arr.includes() 返回布尔值
- arr.find(callback) 返回符合条件的第一个值
- arr.findIndex(callback) 返回符合条件的第一个值的下标
数组扁平化
arr.flat(Infinite)
JSON.Stringfy(arr).replace('/(\ [ | \ ] )/g','').split(',')
JSON.parse('[' + JSON.Stringfy(arr).replace('/(\ [ | \ ] )/g','') +']')
-
while(arr.some(Array.isArray)){ arr = [].concat( ...arr ); }
-
let result = [ ]; let fn = function(arr){ for(let i =0;i<arr.length;i++){ if(Array.isArray(arr[i])){ fn(arr[i]) } else{ result.push(arr[i]) } } }