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 = 0
let b = async () => {
a = a + await 10
console.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 = PENDING
constructor(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)
})
break
case REJECTED:
this.REJECTEDCALLBACK.forEach(callback=>{
callback(this.reason)
})
break
}
}
resolve(value){
if(this.status === PENDING){
//这里一定是先改变值,再去改变状态,因为 set 拦截里需要用到值
this.value = value
this.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=>value
const 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 = true
this.resolvePromise(promise2,y,resolve,reject)
},r=>{
if(called) return
called = true
reject(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] = value
counter++
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 timer
const now = Date.now
let startTime = now()
let endTime = startTime
const 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 b
if(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不能先声明后赋值
//会不会报错??
//es5
Object.defineProperty() 声明常量
2、Array
1、for in ,array.every,for,forEach,数组对象的遍历方式,es5
2、for of,遍历自定义的数据,es6的
//伪数组到数组的转换,如arguments,nodeList
let 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()
//es6
class 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.prototype
function 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.size
map.has(1) //查的是索引值
map.get()
map.keys();
map.values
map.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.getOwnPropertyNames
Object.getOwnPropertyDescriptors
Object.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} = options
let {title,size:{width,height},items:[,item2]} = options
//原理:针对可迭代对象Iterator,通过遍历按顺序获取对应的值进行赋值
symbol.iterator
Iterator是?
Iterator是一种接口,interface,为各种不一样的数据解构提供统一的访问机制
任何数据解构,只要有这个Symbol.iterator,都可以进行遍历,然后按照顺序解构出数据,表明是一个可迭代对象
for of 相当于一个遍历器,遍历的时候,去寻找Iterator
有什么作用?
为各种不同的数据解构提供统一的访问接口
数据解构按照顺序处理
for of 可以进行遍历
可迭代对象是什么?
可迭代对象存在两种协议:可迭代协议 和迭代器协议
可迭代协议:对象必须实现interator方法,对象或者原型上必须有个Smybol.interator:()=>迭代器协议
迭代器协议:必须实现一个next方法,next方法返回一个对象,包含done,value
function 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 输出的item
2.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.9
console.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 val
val = 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 = 0
let random
while(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.next
const 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))
//es7
console.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
}
//遍历keys
console.log(Object.keys(grade))
console.log(Object.keys(grade).filter(item => item === 'lilei'))
//遍历values
console.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.defineProperty
Object.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 of
async 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/s
console.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 = trimLeft
console.log(s.trimEnd()) //trimEnd = trimRight
console.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 = 11n
console.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; // todo
let r = null;
while((r = reg.exec(fn.toString())) !== null) {
reg.lastIndex
matches.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 中
debugger
modules[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) => {
debugger
return __load(toUrl(id)).then(() => { //加载依赖的模块a
debugger
// 加载之后
const { factory, deps } = modules[id];
debugger
if (!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 ,就先去执行define
debugger
return __load(toUrl(mod)).then(() => { //
debugger
const { factory } = modules[mod];
debugger
return 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.url
let cbName = options.jsonp //设置传递给后台的函数名
let data = options.data
let success = options.success
let fail = options.fail
let timeout = options.timeout || 3000
let head = document.getElementsByTagName('head')[0]
let script = document.createElement('script')
data['callback'] = cbName
let 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+'?'+str
script.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] = null
fail && 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.prototype
let 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指向不正确了,指向了Parent
Child.prototype.constructor = Child;
//问题1、如果属性是引用类型,那么new 出来多个实例会共享,修改 了一个就会修改全部
2、new Child的时候,无法传参
1.4.2 构造函数继承
function Parent(name,age){
this.name = name;
this.age = age
this.say = function(){
consle.log('eat------'+ name)
}
}
function Child(id){
this.id = id
Parent.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 = id
Parent.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 = id
Parent.apply(this,Array.from(arguments).slice(1))
}
//Child.prototype = new Parent()
Child.prototype = Object.create(Parent.prototype)
//等价于
let MidFunction = function (){}
MidFuntion.prototype = Parent.prototype
Child.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)
```javascript
funciton 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()
})()
//输出2
var 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声明的变量在词法环境中)
调用栈:管理函数调用关系的一种数据结构,来管理执行上下文
全局执行上下文
函数执行上下文
作用域就是变量与函数的可访问范围,