promise
一、基础应用
let promise = new Promise((resolve,reject)=>{console.log('Promise')})//then方法返回一个新的promise实例,可以链式调用then//Promise.prototype.then()//Promise.prototype.catch();promise.then((res)=>{console.log("resolve成功后执行的函数")},(err)=>{console.log("reject失败后执行的函数")}).then(()=>{},()=>{}).catch(()=>{ //捕获rejected/then方法指定的回调函数 的异常})//Promise.prototype.finally() //不管Promise对象最后状态如何,都会执行,一般用于释放一些资源let requst = (time,id)=>{return new Promise((resolve,reject)=>{setTimeout(()=>{resolve(`第${id}个请求${time/1000}秒`)},time)})}let p1 = requst(3000,1)let p2 = requst(2000,2)Promise.all([p1,p2].then((res)=>{//整合数据console.log(res)}).catch(err)=>{console.log(err)})//Promise.race(),哪个结果获取的快,就返回哪个结果,不管成功还是失败Promise.race([p1,p2].then(()=>{}))//超时取消//2020.6.11补充function test(bool){if(bool){return new Promise((resolve,reject)=>{//操作resolve(30) //将返回的数字转换为一个promise对象,以便调用then方法})}else{return Promise.reject(new Error('ss'))//也可以返回一个值,resolve和reject 属于Promise上的静态方法//return Promise.resolve(30)}}test(0).then((val)=>{console.log(val)},(err)=>{console.log(err)})//用 catch 来捕获 reject改变promise状态的错误//不要用 throw Error去捕获
2019.09.15补充
Promise解决回调地狱问题得例子
function getData(path){return new Promise((resolve,reject)=>{var xhr = new XMLHttpRequest();xhr.open('get','http://localhost:3000/' + path);xhr.send(null);xhr.onreadystatechange = function() {if(xhr.readyState != 4) return;if(xhr.readyState == 4 && xhr.status == 200) {// 获取后台数据var ret = xhr.responseText;// 成功的情况resolve(ret);} else {// 失败的情况reject('服务器错误');}}})}getData(path1).then(res=>{console.log(res);return getData(path2)}).then(res=>{console.log(res);return getData(path3)}).then(res=>{console.log(res);}).catch(err=>{console.log(err)}).finally(res=>{console.log('完成')})
async 和 await
//伪代码async function test() {// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式// 如果有依赖性的话,其实就是解决回调地狱的例子了await fetch(url)await fetch(url1)await fetch(url2)}//例子let a = 0let b = async () => {a = a + await 10console.log('2', a) // -> '2' 10 ,因为b()比a++先执行,所以这里a还是0}b()a++console.log('1', a) //-> '1' 1。这里是同步代码,先执行
二、规范(promiseA+)与实现
规范
1、3种状态:
1.1 pending:可改变,可以通过resolve(value),reject(reason)改变成最终态
1.2 fulfilled:最终态,不可改变
1.3 rejected:最终态,不可改变
2、then
promise.then(onFulfilled,onRejected)
2.1 参数规范:只能是函数,如果不是函数,应该被忽略
2.2 promise的状态变成fulfilled时,应该调用onFulfilled,参数是value,并且只能调用一次
2.3 onRejected同理
2.4 onFulfilled和onRejected应该是在微任务阶段执行
2.4.1 queueMicrotack实现微任务的调用
2.5 then方法可以被链式调用(返回一个promise),并且所有的onFulfilled和onRejected回调需要按照.then的顺序执行,用一个数组存
2.6 返回值。then应该返回一个promise
const promise2 = promise.then(onFulfilled,onRejected)
2.6.1 onFulfilled或者onRejected执行结果为x,调用resolvePromise(作用是递归解析x)
2.6.2 onFulfilled或者onRejected执行过程种抛出了异常e,promise2需要被reject
2.6.3 如果onFulfilled不是一个函数,promise2以promise1的value触发fulfilled,值穿透
2.6.4 如果onRejected不是一个函数,promise2以promise1的then触发fulfilled,值穿透
2.7 resolvePromise
resolvePromise(promise2,x,resolve,reject)
2.7.1 如果promise2和x相等, reject typeError
2.7.2 如果x是一个promise
如果x是pending,promise必须要在pending状态,直到x状态变更
如果x是fulfilled, value -> fulfilled
如果x是rejected,reason -> rejected
2.7.3 如果x是一个Object\Function
去获取 const then = x.then,
如果获取这个then报错,直接reject reason
如果then是一个函数,then.call(x,resolvePromiseFn,rejectPromiseFn): 用上面的方式获取后,可能指针已经改变了,所以要重新绑定指针
resolvePromiseFn 的入参是y , 执行resolvePromise(promise2,y,resolvePromiseFn,rejectPromiseFn)???
then 如果跑出异常,resolvePromiseFn,rejectPromiseFn 还未执行完,就reject(reason)
实现
1、是一个构造函数或者class
2、定义3种状态
3、初始化状态pending
4、resolve 和 reject 方法
4.1 改变状态
4.2 入参分别是value,reason
5、实例化时入参的处理
5.1 入参是一个函数,接受resolve,reject两个参数
5.2 初始化实例的时候就要同步去执行这个函数,如果有报错,要reject,利用try catch
6、then 方法
6.1 接收两个参数,onFulfilled,onRejected
6.2 两个参数应该是函数,如果不是函数,就忽略
6.3 根据当前promise的状态,调用不同的函数
6.4 首先要拿到所有的回调,新建2个数组,存储成功和失败的回调,如果还是pending,就存入数组
6.5 在status发生变化的时候,执行回调,用到getter sertter,监听status变化,然后做对应的操作,对应遍历执行成功和失败回调,(value要在status改变之前改变)
7、then的返回值
7.1
里面有 resolve方法,reject方法,多个回调的时候怎么处理?用一个数组存储,为了解决 以下调用方式let p1 = Promise.resolve()p1.then();p1.then();p1.then();p1.then();const PENDING = 'pending'const FULFILLED = 'fulfilled'const REJECTED = 'rejected'class MPromise{RESOLVECALLBACL = [];REJECTEDCALLBACK = [];_status = PENDINGconstructor(fn){this.status = PENDING; //初始状态this.value = null;this.reason = null;try{fn(this.resolve.bind(this),this.reject.bind(this))//为什么要绑定?如果传入的不是箭头函数,this会丢失}catch(e){console.log('出错拉~~~~~~~~~~~~~~~~~~~')this.reject(e)}}get status(){//定义一个私有变量,禁止套娃return this._status}set status(newStatus){this._status = newStatus//、 执行回调switch(newStatus){case FULFILLED:this.RESOLVECALLBACL.forEach(callback=>{callback(this.value)})breakcase REJECTED:this.REJECTEDCALLBACK.forEach(callback=>{callback(this.reason)})break}}resolve(value){if(this.status === PENDING){//这里一定是先改变值,再去改变状态,因为 set 拦截里需要用到值this.value = valuethis.status = FULFILLED}}reject(reason){if(this.status === PENDING){this.reason = reason;this.status = REJECTED}}then(onFulfilled,onRejected){ //判断是否是函数,如果不是,需要直接返回value(值透传)const realOnFuifilled = this.isFunction(onFulfilled)?onFulfilled:value=>valueconst realOnRejected = this.isFunction(onRejected)?onRejected:reason=>{throw reason}//返回一个promise//定义微任务//数组存回调const promise2 = new MPromise((resolve,reject)=>{const fulfilledMicrotask = ()=>{queueMicrotask(()=>{try{const x = realOnFuifilled(this.value)this.resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}})}const rejectedMicrotask = ()=>{queueMicrotask(()=>{try{const x = realOnRejected(this.reason)this.resolvePromise(promise2,x,resolve,reject)}catch(e){reject(e)}})}switch(this.status){case FULFILLED:{fulfilledMicrotask()break}case REJECTED:{rejectedMicrotask()break}case PENDING:{this.RESOLVECALLBACL.push(fulfilledMicrotask)this.REJECTEDCALLBACK.push(rejectedMicrotask)break}}})return promise2}isFunction(fn){return typeof fn ==="function"}resolvePromise(promise2,x,resolve,reject){if(promise2 === x){return reject(new TypeError('The promise and the return value are the same'))}if(x instanceof MPromise){//如果x是一个promise,需要再次解析y,(可以理解为递归)queueMicrotask(()=>{x.then(y=>{this.resolvePromise(promise2,y,resolve,reject)},reject)})}else if(typeof x ==="object" || this.isFunction(x)){if(x=== null){return resolve(x)}let then = null//这里规范里定义要捕获这个错误try{then = x.then}catch(e){reject(e)}if(this.isFunction(then)){let called = false //设置一个标志位,只调用一次try{then.call(x,y=>{if(called) return;called = truethis.resolvePromise(promise2,y,resolve,reject)},r=>{if(called) returncalled = truereject(r)})}catch(e){if(called) return;reject(e)}}else{resolve(x)}}else {resolve(x)}}catch(onRejected){return this.then(null,onRejected)}static resolve(value){if(value instanceof MPromise){return value}return new MPromise(resolve=>{resolve(value)})}static reject(reason){return new MPromise((resolve,reject)=>{reject(reason)})}static race(promiseList){ //哪个执行的快就返回哪个,其他的执行完但是不会返回了return new MPromise((resolve,reject)=>{let len = promiseList.length;for(var i = 0;i<len;i++){MPromise.resolve(promiseList[i]) //先转成promise,防止传入的不是promise导致报错.then(value=>{return resolve(value)},reason=>{return reject(reason)})}})}}MPromise.resolve().then(() => {console.log(0);return MPromise.resolve(4); //返回值是promise ,会创建2次微任务}).then((res) => {console.log(res)})MPromise.resolve().then(() => {console.log(1);}).then(() => {console.log(2);}).then(() => {console.log(3);}).then(() => {console.log(5);}).then(() =>{console.log(6);})//备注 :::static all(promiseList){ //一个失败全部都失败return new MPromise((resolve,reject)=>{let len = promiseList.length;let res = []let counter = 0;if(len === 0) return resolve()for(var i = 0; i <len; i++){MPromise.resolve(promiseList[i]).then((value)=>{res[i] = valuecounter++if (counter === len) { //所有的promise执行完毕return resolve(res)}},(reason)=>{return reject(reason)})}})}}//Promise.allSettled的实现function PromiseAllSettled(promiseArray){return new Promise((resolve,reject)=>{if(!Array.isArray(promiseArray)){return reject(new TypeError('参数必须是一个数组'))}let counter = 0;const promiseNum = promiseArray.length;const resultArray = []for(let i=0; i<promiseNum;i++){Promise.resolve(promiseArray[i]).then((value)=>{resultArray[i] = {status:'fulfilled',value}}).catch((reason)=>{resultArray[i] = {status:'rejected',reason}}).finally(()=>{counter++;if(counter===promiseNum){resolve(resultArray)}})}})}
参考链接 :https://zhuanlan.zhihu.com/p/264944183
https://juejin.cn/post/6953452438300917790#heading-0
三、promise与微任务、宏任务,vue的 nextTikc原理,以及浏览器V8串联
四、promise,async await对于错误的处理以及缺点
定时器相关知识
一、requestAnimationFrame 实现setInterval
function setInterval(callback, interval) {let timerconst now = Date.nowlet startTime = now()let endTime = startTimeconst loop = () => {timer = window.requestAnimationFrame(loop)endTime = now()if (endTime - startTime >= interval) {startTime = endTime = now()callback(timer)}}timer = window.requestAnimationFrame(loop)return timer}let a = 0;setInterval(timer => {console.log(1)a++if (a === 3) cancelAnimationFrame(timer)}, 1000)
二、定时器应用(防抖和节流)
再学js之 es6-es10
一、环境准备
1、安装node,推荐12.2.0
2、npx命令 npm版本>5.6
3、初始化项目:npx es10-cli create projectName
4、启动项目 npm start
如安装失败
npm install es10-cli -g
es10-cli create projectName
二、es6
—-全局作用域,函数作用域,块级作用域,动态作用域this,根据调用方式不同,this指向也不同(详见https://www.yuque.com/tudouxianbaozi/lvh04f/uz26r7,顺便复习一下)
1、let const
function test(){var a = 3;//会提升成这样:var bif(a === 3){var b = 4;//这里如果换成 es6 的let ,就不会有变量提升,b只是在这个{}块里访问得到,下面的b就不能访问成功console.log('abc')}else{console.log('abcd')}console.log(b) // 这里能打印成功,因为es5没有块级作用域,而且还会变量提升return a + 4 //2、闭包(实现外部拿到内部的变量)}console.log(test())//利用闭包function test(){var a = 3;function test2(){var b = 4;return a+b;}return test2}var t = test();t();
1、let 声明的变量具有块级作用域
2、let声明的变量在全局作用域|window上找不到
3、let 声明的变量不能被重复定义
4、不会变量提升
5、const定义的是常量,不允许再修改
6、const不能先声明后赋值
//会不会报错??//es5Object.defineProperty() 声明常量
2、Array
1、for in ,array.every,for,forEach,数组对象的遍历方式,es5
2、for of,遍历自定义的数据,es6的
//伪数组到数组的转换,如arguments,nodeListlet args = [].slice.call(arguments)//es6已经废弃了arguments//es6的做法let args = Array.from(arguments){0:'a',1:'b',length:2} //伪数组let arr = Array.from({length:5},()=>1)//es6生成数组的方法ler arr1 = Array.of(6)let arr2 = Array(9).fill(7)//fill方法可以用来改变数组的某一项值,达到 splice的效果//如何给数据结构自定义遍历//手写实现array 的flat方法,flat方法的参数表示打平的深度function flatDeep(arr,d = 1){if(d>0){arr.reduce((res,val)=>{ //reduce(回调参数(累计结果,当前处理的值),初始值)if(Array.isArray(val)){res = res.concat(flatDeep(val,d-1))}esle{res = res.concat(val)}return res},[])}else{return arr.slice()}}Array.incluedes()Array.from(伪数组\真数组,回调函数map)//一个伪数组转成真数组的方式1、[...arguments]2、Array.from()3、Array.prototype.slice.call(arguments)
3、类 class
monkey.constructor.protptype.eat = Animal.prototype.eat//es5是怎么做到属性保护(get 和set)let Animal = function(type){this.type = type;}Animal.prototype.eat = function(){Animal.walk();//调用静态方法console.log('eat food')}Animal.walk = function(){console.log('walking')}let dog = new Animal('dog');dog.eat()//es6class Animal{constructor(type){this.type = type;}eat(){Animal.walk()console.log('eat')}static walk(){ //静态console.log('walk')}}let dog = new Animal('dog')dog.eat()
4、继承
//es5的继承,请复习//es6的继承
5、函数不确定参数,函数默认值
//es5处理//arguments,call,apply,Array.prototypefunction sum(...nums){let num = 0;}function sum(base,...nums){}function sum(x,y=3,z=x+y){}let data = [1,2,3]sum(...data)
6、箭头函数
let sum =(x,y,z)=>({x:x,y:y,z:z})console.log(sum(1,2,3))//箭头函数的this//如何用箭头函数来实现一个数组排序的问题?//箭头函数对this的处理还有什么妙用?const fn = (value) => ({})//不能用作构造函数1、会改变this指向,指向new出来的那个实例,但是箭头函数的this是在定义的时候就决定了的
7、Set
let s = new Set()//add ,delete,has,clear,size//keys,values,entries//s.forEach(item=>console.log(item))//for of
8、Map
let map = new Map([[1,2],[3,4]]);map.set(1,3)map.set(3,4)map.set(1,2)map.delete(1)//删除某一个map.clear() //全部删除map.sizemap.has(1) //查的是索引值map.get()map.keys();map.valuesmap.entries()map.forEach((v,k)=>console.log('值':v +','+'键':k))for (let [key,value] of map){console.log(key ,value)}//map 的key值可以是任意的,包括函数//map 性能优于object
9、object
//1.属性简写//object拷贝Object.assign()//浅拷贝实现浅拷贝shallowCopy 用 for in{...a,...b}实现 Object.keys()实现 Object.values()Object.entries()Object.getOwnPropertyNamesObject.getOwnPropertyDescriptorsObject.freeze(obj) //引用类型的常量属性不能被修改,会报错
10、正则
const s = 'aaa_aa_a';const r1 = /a+/g;; //^ $const r2 = /a+/y //y修饰符,粘连,每次匹配从上一次的结束位置开始,连续匹配//es6种处理中文字符的方法//加上U修饰符,es6都习惯加上u修饰符console.log(/\u{61}/u.test('a'))
10、字符串
function Price(string,type){console.log(string[0])console.log(type)}let shoTxt = Price`您此次的${'retail'}`//还能这样用!!!!//面试题:如何实现模板字符串的render函数const year = '2021'const month = '10'const day = '24'const template = '${year}-${month}-${day}'const context = {year,month,day}const str = render(template)(context)console.log(str) //2021-10-24//解答, 高阶函数+正则function render(template){return function(context){return template.replace(/\$\{(.*?)\}/g,(match,key)=>context[key])}}//正则真的很重要!!!!
11、解构赋值
a、右边是一个可遍历的对象,左边 [],如 let [ f, t ] = [1,2]
//数组的解构赋值let user= {name:'s',surname:'t'};[user.name,user.surname] = [1,2];for(let [key,val] of Object.entries(user)){console.log(`${key}:${val}`)}//Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组//对象的解构赋值let options = {title:'rt',size:{width:100,height:200},items:['cake','dount'],extra:true}//let {size,...last} = optionslet {title,size:{width,height},items:[,item2]} = options//原理:针对可迭代对象Iterator,通过遍历按顺序获取对应的值进行赋值symbol.iteratorIterator是?Iterator是一种接口,interface,为各种不一样的数据解构提供统一的访问机制任何数据解构,只要有这个Symbol.iterator,都可以进行遍历,然后按照顺序解构出数据,表明是一个可迭代对象for of 相当于一个遍历器,遍历的时候,去寻找Iterator有什么作用?为各种不同的数据解构提供统一的访问接口数据解构按照顺序处理for of 可以进行遍历可迭代对象是什么?可迭代对象存在两种协议:可迭代协议 和迭代器协议可迭代协议:对象必须实现interator方法,对象或者原型上必须有个Smybol.interator:()=>迭代器协议迭代器协议:必须实现一个next方法,next方法返回一个对象,包含done,valuefunction generateInterator(array){let nextIndex = 0;return {next:()=>{return nextIndex<array.length?{done:false,value:array[nextIndex++]}:{done:true,value:undefined}}}}//调用const interator = generateInterator([1,2,3])console.log(interator.next())console.log(interator.next())console.log(interator.next())console.log(interator.next())for of 和for in 的区别1.使用上,for in输出下标(对象输出key),for of 输出的item2.for in 还会遍历原型上面的属性,需要用hasOwnProperty判断一下,可以被break中断,,不适合遍历数组3.for of 可以break中断,forEach 没有break ,可以throw error,然后try catch应用场景1、数组传参2、结合函数参数的初始值const c =({a,b,c=1})=>{}3、返回值取值4、变量交换let a = 1,b =2[b,a] = [a,b]5、json处理解构取JSON.perse(json)6、ajax返回值通过解构取
12、promise
//补充手写 promise源码//返回所有promise的状态和结果Promise.allSettled
13、Reflect
Reflect对象不能使用new
Math.floor.apply(null,[3.76])Reflect.apply(Math.floor,null,[3.76])let price = 101.9console.log(Reflect.apply(price>100?Math.ceil:Math.floor,null,[price]))let d = Reflect.construct(Date,[]) //注意:这里第二个参数为空数组,等于以下代码let d1 = new Date()let student = {};let s = Reflect.defineProperty(student,'name',{value:'rt'});let s1 = Object.defineProperty(student,'name',{value:'rt'});Reflect.deleteProperty(obj,'属性名')Reflect.get(obj,'属性名')Reflect.get([1,23,4],'索引')//尽量使用Reflect,以后挂载到Object上的方法会逐渐废除?优化了 js中一些不规范的行为比如说命令式的删除某一个对象上的属性 delete obj.属性名
14、proxy
//场景一,只读 用 Proxy//场景二,校验//场景三,还原后端的数据//场景四,生成随机Id,不能被修改,每次实例ID生成的不一样class Component {constructor (){this.proxy = new Proxy({id: Math.random().toString(36).slice(-8)},{})}get id (){return this.proxy.id}}//每次实例随机生成的ID,不一样let com = new Component()let com2 = new Component()for(let = 0; i< 10,i++){console.log(com.id,com2.id)}com.id = 'abc' //不生效,不能被修改//撤销代理let 0 = {price:190,name:'34dffgb'}let d = Proxy.revocable(o,{get(target,key){if(key ==="price"){return target[key]+=20}else{return target[key]}}})setTimeout(()=>{d.revoke();//撤销setTimeout(()=>{console.log(d.proxy.price)})},1000)
15、generator
- 迭代器 Iterator的概念:es6引入的一种新的遍历机制,同时也是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口
- 每个迭代器对象都有一个next()方法,每次调用都返回一个当前结果对象。当前结果对象有两个属性
value:当前属性的值
done:用于判断是否遍历结束,当没有更多可返回的数据时,返回true
//一、可以自定义遍历器let authors = {allAuthors:{fiction:['qwe','weer','rrtt','rtyy','yui','xcd'],scienceFiction:['sdvv','axd','ss','hj','','dff','cdd','mjh','cvv','bnn'],fantasy:['hj','dd','gh','jk','rt','sbnv']},adress:[]}//如果是 es5遍历let r = [];for(let [k,v] of Object.entries(author.allAuthors)){r = r.concat(v)}//定义遍历器,,还是和 业务 耦合呀????authors[Symbol.iterator] = function(){ //此函数没有入参let allAuthors = this.allAuthors;let keys = Reflect.ownKeys(allAuthors);let values = [];return {next(){ //必须有if(!values.length){if(keys.length){values = allAuthors[keys[0]]keys.shift()}}return { //必须有done:!values.length,value:values.shift()}}}}//这个迭代器用generator来替代authors[Symbol.iterator] = function * (){let allAuthors = this.allAuthors;let keys = Reflect.ownKeys(allAuthors);let values = [];while(1){if(!values.length){if(keys.length){values = allAuthors[keys[0]];keys.shift()yield values.shift()}else{return false}}else {yield values.shilft()}}}let r1 = []for(let r of authors){r1= r1.concat(r)}//1、可迭代协议,如果一个对象上包含以Symbol.iterator为key的方法,说明这个对象可迭代//2、迭代器协议,必须返回一个对象,对象必须包含next方法,next方法必须返回一个包含done和value的对象function * gen(){let valval = yield [1,2,3]//yield 后面可以跟一个可遍历的对象,也可以跟一个generotor对象//val = yield * [1,2,3]console.log(val)}let l = gen()//next可以传值,用于修改内部的数据console.log(l.next())//提前终止console.log(l.return(100))//抛出异常,可以通过try catch捕获l.throw(new Error('ss'))console.log(l.next())//年会抽奖案例//可以控制抽奖的节奏function * getPrize(first=1,second=3,third=5){let allPepole = ['qw','we','er','rt','ty','yu','ui','io','op','pa','sa','sd','df','fg','gh','hj','jk','kl','la','lz']let count = 0let randomwhile(1){if(count < first+second+third){random = Math.floor(Math.random()*allPepole.length);yield allPepole[random]count ++allPepole.splice(random,1)}else{return false}}}let p = getPrize();console.log(p.next().value)console.log(p.next().value)console.log(p.next().value)console.log(p.next().value)console.log(p.next().value)console.log(p.next().value)function * count(x=1){while(1){if(x % 3 === 0){yield x}x++}}let n = count()console.log(n.next().value)//用generator实现一个裴波那数列
- 实现自动执行的generator ,Co库
- 特征 1、每当执行完一条yield语句后函数就会自动停止执行,直到再次调用next()
2、yield关键字只可以在生成器内部使用
3、可以通过函数 表达式来创建生成器,但是不能使用箭头函数
function longTimeFn(time){return new Promise(resolve=>{setTimeout(()=>{resolve(time)},time)})}function asyncFunc(generator){const iterator = generator();let n = iterator.nextconst next = (data)=>{const {value,done} = iterator.next(data) //感觉就像递归调用next方法if(done) return;value.then(data=>{next(data)})}next()}asyncFunc(function* (){let data = yield longTimeFn(2000)console.log(data)data = yield longTimeFn(3000)console.log(data)})
三、es7
1、es7中判断数组中一个元素是否存在
let arr = [1,2,3,4,5,6,7]console.log(arr.includes(7))
2、数学乘方的简写
console.log(Math.pow(2,5))//es7console.log(2 ** 5)
四、es8
1、比promise更优雅的处理异步的方式
//async关键字会自动返回一个promise对象async function test(){let promise = new Promise((resolve,reject)=>{setTimeout(()=>{resolve('now is done')},1000)})console.log(await promise) //控制宏任务先执行console.log(await Promise.resolve(40)) //先执行console.log(20)return Promise.resolve(4)}test().then((val)=>{console.log(val)})
2、object快速遍历的方法
let grade = {'lilei' : 96,'han' : 99}//遍历keysconsole.log(Object.keys(grade))console.log(Object.keys(grade).filter(item => item === 'lilei'))//遍历valuesconsole.log(Object.values(grade))console.log(Object.values(grade).filter(item => item > 96))//object.entries:把对象变成可遍历的对象;(方法返回一个给定对象自身可枚举属性的键值对数组)let result = []for (let [k, v] of Object.entries(grade)) {console.log(k, v)result.push(k)}console.log(result)
3、String补白的方式
for (let i=1;i<4522;i+=1000){console.log(i.toString().padStart(4,'*&')) //前面补全}for (let i=1;i<4522;i+=1000){console.log(i.toString().padEnd(4,'*&'))//后面补全}
4、获取对象的描述符
const data = {age:'23',name:'hailei',legs:4,}//等同于 Reflcet.definePropertyObject.defineProperty(data,'legs',{enumerable:false})console.log(Object.keys(data))//获取所有属性的描述符//Reflect.getOwnPropertyDescriptor(target, propertyKey)===Object.getOwnPropertyDescriptor(target, propertyKey)
五、es9
1、for await of ,对异步集合的遍历,,可以用来实现网约车的异步流程
function Gen(time){return new Promise((resolve,reject)=>{setTimeout(()=>{resolve(time),time})})}//老的语法可以这样控制,但是不完美async function test(){let arr = [Gen(2000),Gen(100),Gen(3000)]for(let item of arr){console.log(Date.now(),await item.then(console.log))}}//使用 for await ofasync function test(){let arr = [Gen(2000),Gen(100),Gen(3000)]for await (let item of arr){console.log(Date.now,item)}}//自定义数据结构的异步遍历器let obj = {count:0,Gen(time){return new Promise((resolve,reject)=>{setTimeout(()=>{resolve({done:false,value:time}) //要遵循迭代器协议},time)})},[Symbol.asyncIterator](){let self = this;return {next(){self.count++if(self.count < 4){return self.Gen(Math.radom()*1000)}else{return Promise.resolve({done:true,value:''})}}}}}async function test(){for await (let item of obj){console.log(Date.now(),item)}}
2、升级版的promise
function Gen(time){return new Promise((resolve,reject)=>{setTimeout(()=>{if(time < 500){reject(time)}else{resolve(time)}},time)})}Gen(Math.random()*1000).then(val=>console.log(val,'resolve')).catch(err=>console.log(err,'reject')).finally(()=>{console.log('finish')})
3、Object 的 Rest 和Spread
//可以用来合并对象const obj1 = {a:1,b:2}const obj2 = {c:3,d:4}const obj3 = {e:5,f:8}const obj = {...obj1,...obj2,...obj3,n:7}console.log(obj)obj1.a = 34;console.log(obj1,obj)const input = {a:1,b:2,c:3,d:4}let {a,b,...rest} = input;console.log(a,b,rest)
4、dotAll,命名分组捕获、后行断言
//需要匹配 . 以及行终止符,就需要加 s 的修饰符,需要匹配//匹配4个字节的utf16字符,需要加U修饰符console.log(/foo.bar/us.test('foo\nbar'))//判断是否启用dotAll模式const re = /foo.bar/sconsole.log(re.dotAll)console.log(re.flags)(2)命名分组捕获const t = '2019-04-09'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)console.log(t)console.log(t.groups.year)(3)后行断言let test = 'hello world'console.log(test.match(/hello(?=\sworld)/))console.log(test.match(/(?<=hello\s)world/))console.log(test.match(/(?<!hellr\s)world/)) // !表示不等于
六、es10
1、JSON.stringify,修正0xD800-0xDFFF之间字符显示的bug
2、新增扁平化数组,参数为深度,指定深度的打平
let arr = [1,[2,[4,3,[6,7,[8]]]]];console.log(arr.flat(4))
3、数组增加flatMap
4、字符串增加的API
//去掉字符串首尾空格let s = ' foo 'console.log(s.trimStart()) //trimStart = trimLeftconsole.log(s.trimEnd()) //trimEnd = trimRightconsole.log(s.trim()) //去掉全部s.match(正则)s.matchAll(正则)
5、新增object 的API,fromEntries
const arr = [['a',1],['b',2]]const obj = Object.fromEntries(arr)console.log(obj)const test = {'a':1,'b':2,'c':3,'sd':5}let res = Object.fromEntries(Object.entries(test).filter(([key,val])=>key.length===1));console.log(res)//应用一、提取url中的参数let url3 = "https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&rsv_pq=acb2887700009bd5&rsv_t=4b0dalrwbS8EVGgEzFz57Bv%2BtOH4PY3P6AoZRV6xYfwao8HSYZrTuaXYNRc&rqlang=cn&rsv_enter=0&rsv_dl=tb&rsv_btype=t"let r = url3.split('?')[1]let query = Object.fromEntries(new URLSearchParams(r))console.log(query)//应用二、交换属性和值function foo(obj) {return Object.fromEntries(Object.entries(obj).map(([key, value]) => [value, key]))}console.table({ name: 'oli', age: '12' })console.table(foo({ name: 'oli', age: '12' }))//应用三、过滤属性function foo(obj, ...keys) {return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)))}console.table(foo({ name: 'oli', age: '12' }, 'name'))//应用四、处理表格数据let arr = [{name: 'oli',age: 12}, {name: 'troy',age: 14}]obj = Object.fromEntries(arr.map(({name, age}) => [name, age]))console.table(obj)//try catch能省略(e)try{}catch{}
6、新增的一种数据类型 bigint
const a = 11nconsole.log(typeof a)
七、Vue 结合新语法的实战
知识点1、vue自定义指令
2、Mock 数据,利用http-server启动本地服务
3、async\await异步处理
4、箭头函数,解构赋值等
5、利用proxy保存原始接口请求回来的原始数据(排序后还原)
6、自定义遍历器处理接口返回数据
八、环境构建
1、webpack配置,常用的loader,插件
2、babel工作原理,es6 的code => AST =》AST(es5的) =》 es5 的code
3、eslint 使用,自动修复需要编辑器的配置:安装linter 和 linter-eslint ,设置里开启 lint-onchange/Fix errors on save
es6实战
class 写tab 和newList 组件
//节流 :丢弃//防抖 : 等待//装饰器的节流,,时间戳,定时器
九、前端知识结构整理
一、思维导图
前端知识结构整理(新).xmind
前端知识架构整理.xmind
二、面试重点知识点导图
十、模块化(AMD,CMD,CommonJS ,ES6)
一、模块化演进过程:
1.文件划分的方式 污染全局作用域 命名冲突 无法管理模块依赖关系
2.命名空间方式 在第一个阶段的基础上 将每个模块只暴露一个全局对象 所有的变量都挂载到这个全局对象上
3.IIFE 立即执行函数 为模块提供私有空间,闭包0
4.esm
问题
commonJs怎么解决循环引用问题?
如果a 依赖了b,同步执行到b,如果b又引用了a,就只会拿到a的执行结果,如果没有就undefind,不会再回到a了;待执行完b,再继续执行 a后面的
try catch 的 catch可以实现块级作用域,面试居然被问到过 let ,{} ,?????
| AMD异步加载(require.js) | CMD(sea.js) | CommonJS,服务端规范,nodejs采用 | es6 使用: |
|
|---|---|---|---|---|
特点 |
1、异步,允许指定回调函数 2、 define([加载的资源],回调函数) 3、使用起来比较复杂 4、模块的js文件请求频繁 5、先加载依赖 实现原理:运行require.js后,会动态在body元素尾部添加需要的js文件, |
插入sciprt节点,并且加载+运行其js文件
1. // 建一个node节点, script标签
1. var node = document.createElement(‘script’)
1. node.type = ‘text/javascript’
1. node.src = ‘3.js’
1.
- // 将script节点插入dom中
1. document.body.appendChild(node)
| 1、按需加载
2、define(function(){
var a = require(‘2.js’)
console.log()
})
3、碰到require(‘2.js’) 就立即执行2.js | 1、同步的,所以不适用浏览器,会阻塞
2、一个文件就是一个模块,有单独的作用域
3、通过module.exports导出成员
4、通过require函数载入模块
5、CommonJS 模块输出的是一个值的拷贝,一旦输出一个值,模块内部的变化就影响不到这个值
6、CommonJS 模块加载的顺序,按照其在代码中出现的顺序
7、由于 CommonJS 是同步加载模块的,在服务器端,文件都是保存在硬盘上,所以同步加载没有问题,但是对于浏览器端,需要将文件从服务器端请求过来,那么同步加载就不适用了,所以,CommonJS 是不适用于浏览器端的。
8、ommonJS 模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。要想让模块再次运行,必须清除缓存
9、const exports= module.exports; exports只是module.exports的快捷方式
导出方式:module.export ={
a:1,b:2,test:’100’
}
或者 export.a = 1
export.b = 2
export.test = ‘100’
不能直接给你export 赋值,不然断了和module.export的联系,,调用模块就访问不到exports对象及其属性 | 1、自动采用严格模式,忽略use strict
2、每个ESM模块都是单独的私有作用域
3、ESM是通过CORS去请求外部JS模块的:
4、ESM中的script标签会延迟执行脚本,在普通的js后面执行
5、ES6 模块是动态引用,引用类型属性被改变会相互影响
6、导出的并不是成员的值 而是内存地址 内部发生改变外部也会改变,外部导入的是只读成员不能修改
7、import不是解构导出对象
用法:export const name=”aa”
export funciton foo(){}
export class Person{}
或者导出多个变量
export {
name,foo,Person
}
import{name}from’./modules.js’
as 关键字
export {name as alias}
import{ name as alias}from’./modules.js’
default
export default name
导入关键字
importas param from’./modules.js’
param 是个对象
同时接受 default 和普通变量
// 默认值在前,普通变量在后,用逗号隔开,语法可以正常使用import defaultParam,{normalParam …}from’./modules.js’
或者
import defaultParam,*as nomalParam from’./modules.js’
动态导入
import(‘path’).then((param)=>{// param 是导入的成员变量}) |
二、代码实现补充
- 如何知道依赖的哪些模块。解析require关键字 ```javascript //AMD大致原理 * 依赖前置 提前加载并读取 var node = document.createElement(‘script’) node.type = ‘text/javascript’ node.src = ‘1.js’
// 给该节点添加onload事件,标签上onload,这里是load,见事件那里的知识点 // 1.js 加载完后onload的事件 node.addEventListener(‘load’, function(evt) { // 开始加载 2.js var node2 = document.createElement(‘script’) node2.type = ‘text/javascript’ node2.src = ‘2.js’ // 插入 2.js script 节点,所以2.js先执行 document.body.appendChild(node2) }) // 插入 1.js script 节点 document.body.appendChild(node) //如何判断 所有的依赖都加载完了,递归
CMD大致原理 :```javascript// sea.js *** 依赖就近 **提前读取文件,但在需要的时候再加载const modules = {};const exports = {};sj = {};const toUrl = (dep) => {const p = location.pathname;return p.slice(0, p.lastIndexOf('/')) + '/' + dep + '.js';}const getDepsFromFn = (fn) => {let matches = [];// require('a ')//1. (?:require\() -> require( -> (?:) 非捕获性分组//2. (?:['"]) -> require('//3. ([^'"]+) -> a -> 避免回溯 -> 回溯 状态机let reg = /(?:require\()(?:['"])([^'"]+)/g; // todolet r = null;while((r = reg.exec(fn.toString())) !== null) {reg.lastIndexmatches.push(r[1])}return matches}const __load = (url) => {return new Promise((resolve, reject) => {const head = document.getElementsByTagName('head')[0];const node = document.createElement('script');node.type = 'text/javascript';node.src = url;node.async = true;node.onload = resolve;node.onerror = reject;head.appendChild(node)})}// 依赖呢?// 提取依赖: 1. 正则表达式 2. 状态机define = (id, factory) => {const url = toUrl(id);const deps = getDepsFromFn(factory); //此函数是从define(id,callback) 的 callback 中提取require的文件路径,转成一个数组, 如 ['a','b']if (!modules[id]) { //添加到modules 中debuggermodules[id] = { url, id, factory, deps } //deps :依赖, factory === callback回调, id :define('main',()=>{}) 中的'main' ***路径*** ,url :拼全的路径}}const __exports = (id) => exports[id] || (exports[id] = {});const __module = this;// 这里面才是加载模块的地方const __require = (id) => {debuggerreturn __load(toUrl(id)).then(() => { //加载依赖的模块adebugger// 加载之后const { factory, deps } = modules[id];debuggerif (!deps || deps.length === 0) {factory(__require, __exports(id), __module);return __exports(id);}return sj.use(deps, factory);})}sj.use = (mods, callback) => { //入口,比如 sj.use('main') ,mods = Array.isArray(mods) ? mods : [mods];return new Promise((resolve, reject) => {Promise.all(mods.map(mod => { //就去加载main ,main 里又有了 define ,就先去执行definedebuggerreturn __load(toUrl(mod)).then(() => { //debuggerconst { factory } = modules[mod];debuggerreturn factory(__require, __exports(mod), __module) //拿到 main 中的factory === callback,正式去加载main})})).then(resolve, reject)}).then(instances => callback && callback(...instances))}
JSONP原理
利用了 script标签 src 加载 js 文件不受跨域限制的特性
- 动态的创建了一个script 标签,加载一个js文件,src = “服务端请求地址+callback=callback”
- 服务端获取这个callback,然后再把需要获取的数据装在里面
前端请求完这个js后,自动执行callback回调,在回调里就能拿到数据
function callback(data){console.log(data) //这里就是后台 装入的数据}
JQ 的jsonp实现
window.onload = function(){jsonp({url:"",jsonp:'callback',data:{a:1,b:2,c:3},success:function(res){//成功的回调},fail:function(){},timeout:3000})}(function(global){function jsonp(options){let url = options.urllet cbName = options.jsonp //设置传递给后台的函数名let data = options.datalet success = options.successlet fail = options.faillet timeout = options.timeout || 3000let head = document.getElementsByTagName('head')[0]let script = document.createElement('script')data['callback'] = cbNamelet str = ""for(let i in data){str+= i+'='+ data[i] +"&"}console.log(str)str = str.slice(0,str.length-1)script.src = url.indexOf('?')>-1?url +'&'+ str:url+'?'+strscript.onerror = function(){fail && fail({msg:'出错啦'})}global[cbName] = function(res){head.removeChild(script)global[cbName] = null//todo 清除超时定时器success &&success(res)script.timer&&clearTimeout(script.timer)}head.appendChild(script)script.timer = setTimeout(()=>{head.removeChild(script)global[cbName] = nullfail && fail({msg:'超市啦'})},timeout)}})(this)
十一、浏览器事件模型
1、stopProgation :阻止事件传播(不论是捕获还是冒泡)。
2、阻止默认行为:e.preventDefault
封装一个没有兼容问题的addEvent
事件委托+ 伪数组(获取的node节点数组)
3、浏览器原生请求
xhr.upload.onprogress //原生的上传的进度
xhr.timeout = 3000
fentch:默认不带cookie
错误不会reject出去
不支持设置超时
可以中止fentch,signal:new Abort
封装一个ajax工具函数,处理对于异步函数的超时处理
function test(asyncFn,options){}function asyncFn(){return new Promise((resolve,reject)=>{});}const asyncFnWithTimeout = test(asyncFn)asyncFnWithTimeout()
十二、js对象及原型链
1.1对象创建方式
对象字面量创建
工厂模式 :无法正确识别对象的类型(实例.constructor)
构造函数:缺点:浪费内存,每个实例中都会存一份属性和对象
原型:将方法挂在到原型上面去,内存中只会存一份
静态属性:绑定再构造函数的属性 Fn.xx
1.2new 关键字做了什么
1、创建一个继承至构造函数的新对象
2、把这个新对象的proto指向构造函数的prototype
3、把this指向该对象
4、返回该对象
4.1如果构造函数没有返回值或者返回值是基础类型,那就返回this
4.2如果构造函数的返回值是引用类型,就返回该对象
function Player(name){this.name = name}function objectFactory(){let o = new Object();let FunctionConstructor = [].shift.call(arguments);o.__proto__ = FunctionConstructor.prototypelet resObj = FunctionConstructor.apply(o,arguments)return typeof resObj ==='object'?resObj:o}let p1 = objectFactory(Player,'rt')
1.3原型链
一条由proto和prototype组成的链条,表述对象关系的链条
function DogFactory(){}console.log(DogFactory.__proto__ === Function.prototype)console.log(DogFactory.prototype.__proto__ === Object.prototype)//demo 二let O = {a:1,b:2}let b = {}b.__proto__ = O
- 最好不要直接操作proto,因为1、是隐藏属性,不是标准定义的,2、会造成性能问题:v8源码中对原型的实现做了很多的优化,比如通过隐藏类优化了很多原有的对象结构,所以直接修改proto会直接破坏已经优化的结构,造成性能问题
- 可以使用Object.getPrototypeOf ,Object.setPrototypeOf ,或则Object.create ,或者 es6的extends
1.4继承
1.4.1原型继承
function Parent(){this.name = 'parentName'}Parent.prototype.getName = function(){console.log(this.name)}function Child(){}Child.prototype = new Parent();//因为Child.prototype被覆盖,constructor指向不正确了,指向了ParentChild.prototype.constructor = Child;//问题1、如果属性是引用类型,那么new 出来多个实例会共享,修改 了一个就会修改全部2、new Child的时候,无法传参
1.4.2 构造函数继承
function Parent(name,age){this.name = name;this.age = agethis.say = function(){consle.log('eat------'+ name)}}function Child(id){this.id = idParent.apply(this,Array.from(arguments).slice(1))}//问题,实例之间各自创建了方法,造成了内存浪费
1.4.3 组合继承
function Parent(name,age){this.name = name;this.age = age}Parent.prototype.say = function(){console.log('say')}function Child(id){this.id = idParent.apply(this,Array.from(arguments).slice(1))//等价于//Parent.apply(this,[].slice.call(arguments,1))//Parent.apply(this,Array.prototype.slice.call(arguments,1))//Parent.apply(this,[...arguments].slice(1))}Child.prototype = new Parent()child.prototype.constructor = Child//缺点 :调用了2次Parent构造函数
1.4.4 寄生组合继承
function Parent(name,age){this.name = name;this.age = age}Parent.prototype.say = function(){console.log('say')}function Child(id){this.id = idParent.apply(this,Array.from(arguments).slice(1))}//Child.prototype = new Parent()Child.prototype = Object.create(Parent.prototype)//等价于let MidFunction = function (){}MidFuntion.prototype = Parent.prototypeChild.prototype = new MidFunction()child.prototype.constructor = Child
1.4.5 class 继承
class Parent{constructor(name,age){this.name = name;this.age = age;}say(){console.log('say-----')}}class Child extends Parent{constructor() {super();}}
十三、babel
//插件实现
十四、this
函数调用方式和this
默认绑定(函数直接调用)
- let 定义的变量不会被绑定到全局,它有自己的块级作用域
- var 定义的变量在非严格模式下会被绑定到window上
- setTimeout/setInterval 调用的代码运行在与所在函数完全分离的执行环境上,默认指向window
let obj = {fun:function(){}}setTimeout(obj.fun,2000)//此时this 指向的是window
隐式绑定(对象属性访问调用)
隐式绑定的this指向的是调用堆栈的上一级(.前面一个) ```javascript //案列
<a name="o1Eoh"></a>#### 显示绑定(call,apply,bind)```javascriptfunciton fn(){console.log(this)}fn.bind(1).bind(2)//可以绑定基本类型?因为有装箱操作----Number(1)//多次绑定,只看第一次bind
实现bind
function myBind(){}
//箭头函数没有this,所以没有比较优先级的意义
function foo(){console.log(this.a)}var a = 2;(function (){"use strict"foo()})()//输出2var name = "the window"var object = {name:'my object',getName:function(){return this.name}}object.getName() //my object(object.getName)() //my object 前面这个()不会没有作用(object.getName = object.getName)() //发生了赋值,丢失了this指向,the window(object.getName,object.getName)() //the window发生了赋值,运算操作等,都会丢失this指向//骚气的题,完全搞不懂function a(x){this.x = x;return this}var x = a(5)var y = a(6)console.log(x.x)console.log(y.x)
作用域和闭包
储存空间和上下文
了解一下 v8中 数组的实现
递归和尾调用优化(返回一个)
闭包:函数拥有对其词法作用域的访问,哪怕是在当前作用域之外执行
逃逸分析,gc
闭包的数据存在堆内存
node 中的gc ,浏览器中的gc ,新生代,老生代
面
1、什么是原型和原型链?
答:(js高程中的概念):每一个js对象(除null)在创建的时候,就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型中继承属性
每个js对象(除了null),都具有一个属性,叫做proto,这个属性会指向该对象的原型;
原型链:一条由proto 和prototype组成的表示对象关系的链条
为什么要设计原型?
节省内存;当我们使用构造函数实例化一个实例,里面公用的方法或者属性可以放在构造函数的prototype上(组合继承),这样多个实例的方法\属性指向的是同一个(共享一个内存地址)
2、谈谈js中的this?
答:指代函数当前的运行环境,由于函数可以在不同的运行环境中执行,所以需要有一种机制,能在函数内部获得当前的运行环境。this的设计目的,就是在函数体内部,指代函数当前的运行环境;
是js中的动态作用域机制;
js中 this的绑定分为4中,1是默认绑定,就是函数的直接调用。2是隐式绑定,比如对象属性访问调用。3是显示绑定,比如call,apply,bind 4是构造函数绑定,通过new 出来的实例,this始终指向这个实例
3、结合作用域谈一下闭包?扩展垃圾回收机制
变量环境(函数声明和var变量声明提升,在编译阶段就被存在的内存中),词法环境(let 和const声明的变量在词法环境中)
调用栈:管理函数调用关系的一种数据结构,来管理执行上下文
全局执行上下文
函数执行上下文
作用域就是变量与函数的可访问范围,
