前端面试小结(持续更新)

169 阅读28分钟

1. let,const

// let
1. 不存在变量提升,更符合规范更易理解。
2. 暂时性死区,块级作用域中先使用后let,会报错。
3. 变量名不能重复声明。
4. 块级作用域能保存let 变量,块级作用域外不能引用块级作用域内的变量。
5. 不会污染window的和覆盖window的属性
// const
1. 基础类型常量不能改变
2. 引用类型可以改变堆里面的内容,不改变栈的地址

2.全局变量得缺陷

// let比var的优势之一
1. var的变量覆盖window对象已有属性
2. var的变量污染window对象

3.解构赋值

用法: 
1.右边有几个括号,左边有几个括号
2.一一对应匹配,不写直接跳过。
3.除了undefined等于默认值,不然都等于右边的变量
4.对象的解构赋值先找到同名属性,然后赋值给该属性的value值
1. 解构数组,对象,已有系统对象,将等号右边的值赋值给左边的变量

4.var的预编译

步骤:(找形参变量声明再实参赋值,找函数声明再函数赋值)
1.创建一个AO对象
2.将形参,变量声明 作为ao对象的属性名,值为undefined
3.将实参赋值形参,变量先不赋值 
4.将函数声明 赋值函数体

5.模板字符

6.es6新增方法

1.字符串的新方法
// 下面三个函数均有第二个参数,表示开始搜索的位置
includes() // 判断是否包含
startsWith() //参数1:字符串
endsWith()

repeat()
//补全字符串长度
padStart  // 参数1:最大长度, 参数2:从开始位置填充的字符串
padEnd 

2.数组的新方法
Array.includes() // 是否包含
Array.from() //用于把类数组转为真正的数组 (部署了iterator接口都可以用Array.from转换为真数组)
Array.of()  // 传入元素,能够传一个元素,弥补new Array(3)一样转为长度
Array.find() // 将符合条件的第一个值返回
Array.findIndex() // 将符合条件的第一个值得下标返回

7.展开运算符...

1.拆包,将数组拆成多项
2.打包
3.可以对内部拥有symbol.iterator的 set,map,function* gem(){}都可以使用 ...

8.Symbol

1.Symbol可以做独一无二的对象私有属性
2.常量枚举
3.Symbol.iterator指向对象的默认遍历器的方法 例如 for of

9.Set

特性:
1.引用数据类型,与数组类似,但是不存在重复值
原理:
值的重复机制类似于 ===,但存在特殊情况 NaN 不会被重复
	例子 new Set().add(NaN).add(NaN) //  (1) {NaN}
		new Set().add(4).add('4') // (2) {4,'4'}
方法和属性:
1.add(value) // 添加
2.size //相当于length
3.delete(value) // 删除
4.has(value) //相当于includes
5.clear() // 清空
6.forEach((item,index)=>{}) // item 和 index 值是相同的

10.Map

特性:
1.引用数据类型
2. 对象作为键名 传输的是地址
	let obj = {a:1}
	let m = new Map([[obj,'a']])
    	m.get({a:1}) // undefined
		m.get(obj) // 'a'
3.需要一个统一的接口iteratore去处理数据部署,如set一样内部拥有iterator接口,可以使用for ... of
原理:
	为什么new Map([[],[]])能传二维数组
    因为内部用了数组的foreach方法和解构赋值
    Array.forEach(
        ([key,value])=>{
        map.set(key,value)
    })
方法和属性:
1. set(key,value) // key 可以是数组对象字符串等等
2. get(key)
3. new Map([[],[],[]]) //接收嵌套数组,第二层数组为[key,value]
作用:
1.灵活存储
	let errors = new Map([
        [404,'Not Found'],
        [500,'InterError']
    ])

11.iterator

产生原因:
1. Map Set 等数据结构 需要一个统一的接口去处理 iterator就是提供了这么一个机制
   一旦数据部署了iterator接口 就可以使用 for ... ofr 循环遍历
特性:
1.Iterator接口部署在数据结构的Symbol.iterator属性
2.一个数据只要有Symbol.Iterator属性 就可以任务可以用for...of遍历循环
3.Symbol.Iterator属性本质是一个函数 执行这个函数 会返回一个指针对象
for...of原理:
1.创建一个指针对象,指向数据的初始位置 iterator就是一个指针对象
2.第一次调用指针对象的next() 可以将指针指向第一个成员
3. ...
4. 直到结束
5.每一次调用指针对象的时候会返回value指, done是布尔值 用来标识遍历是否完成
迭代器封装:
function myIterator(array) {
    let nextIndex = 0
    return { //指针对象:Symbol.iterator()的执行结果
        next: function() {
            return nextIndex >= array.length ? 
        	{
                value: array[nextIndex++],
                done: false
            }:
            {
                value: undefined,
                done: true
            }
        }
    }
}

12.函数的一些新特性

1. 参数初始化变为 function(x,y=1){...},相比较function(x,y){y = y || '1'} 传递0的时候不会出现传参无效
2. 参数不能用let,const重复声明否则会报错 function(x){let x =1} //报错重复声明
3. 实参传整个对象,形参用解构去拿到想要的值, 比如将表单的所有数据作为实参,将{user,pass}作为解构形参作为params                   
4. 函数的(x=y)括号内的形参相当于在全局或者外层函数定义一个块级作用域并用let去声明变量
	let y =1
	{
        let x = y  // 1
    }       
    等价于
    let y = 1
    functin(x=y) {
        //let y = 2
        console.log(x,y)
    }

13.严格模式

1. this 指向的是undefined
2. 不能使用arguments和...
3. 不能使用形参默认值初始化
4.不能使用解构赋值

14.箭头函数

1. 没有参数或者多个参数,参数需要用()包裹
2. 一个参数的时候,()可以省略
3. 多于一条语句我们要使用{},return不可省略
4. 一条语句可以直接返回,return 不可省略,但是直接返回对象时候需要加()如: ()=>({name:'erdip'})
5. 箭头函数不能做构造函数 不能new
6. 箭头函数不能使用arguments
7. 不能当作generator函数,不能加yield命令

15.class

用法:
class Some {
    
}
class Person extends Some{ // 继承
    static name = "erduo" // 静态属性 实例获取不到 new Person().name 为undefinded
	age = 19 //实例属性
	#count = 1  // 私有属性
	constructor() {
        
    }
	static say () {
        return 'hello'
    }
	add() { // 父类原型的方法
        this.#count ++
        return this.#count
    }
	#add2() { // 私有方法
    	this.#count ++
        return this.#count
	}
    addResult() {
        this.#add2()
    }
}
特性:
1. 静态属性和方法是类本身的东西,得用类名调用Person.name Person.say()
2. 私有属性不能被外部使用
3. class默认为严格模式,所以class作用域this为undefined
4. 继承必须要在constructor中调用super,并且只能在constructor中使用super()
5. 不调用super就得不到this,因为子类自己的this对象 必须通过父类的构造函数生成
6. super指向父类的原型,被执行时候this指向子类实例,
7. super能调用父类的方法
8. 赋值时super指向子类this, 不赋值时指向父类原型,在静态方法中指向父类
9. 在子类静态方法中通过super调用父类方法时方法内部的this指向子类,而不是子类实例

16.闭包

//闭包的原理
函数里面嵌套函数,并存在内部变量的引用不销毁
深层:执行期对变量存储作用域链
1.js的某些属性是访问不到的 如[[scope]]域
2.js引擎内部用的 0:函数自身的AO 1:其他函数的AO,2:GO
3.[[scope]]保存的是函数作用域
4.外层函数执行完后指向AO和GO被解除,内层函数的指向仍然时相同的AO,GO对象,这是闭包存在的原因:作用域链共享

17.promise

定义:
1. promise有三种状态,分别是待定,成功,失败。(pending, resolve, reject),内部执行类似setTimeout的异步函数,用来避免回调地狱
2. 用then的两个回调来接收成功和失败的状态,或者用catch接收失败状态,then里面还可以return 一个promise
3.resolve内嵌套resolve,则用then输出只会输出最里层resolve的内容;
如:resolve(Promise.resolve(3)).then(res=>console.log(res))  //3
// 方法
4.Promise.all()的使用技巧: 如果有多个promise进行异步操作,并且只有单一参数不同,我们只需要进行稍稍改造就能使用这个返回一个存在不同的数组.并利用promise.all进行使用
	a.例子:
    // 改造每个promise
    const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
      return getJSON('/post/' + id + ".json"); // 返回新数组
    });
	// promise使用该新数组
    Promise.all(promises).then(function (posts) {
      // ...
    }).catch(function(reason){
      // ...
    });	
b.例子: 有then的promise
    const databasePromise = connectDatabase();

    const booksPromise = databasePromise
      .then(findAllBooks);

    const userPromise = databasePromise
      .then(getCurrentUser);

    Promise.all([
      booksPromise,
      userPromise
    ])
    .then(([books, user]) => pickTopRecommendations(books, user));

18.generator

1.是一种异步解决方案。
2.能够暂缓保存状态值。
特征:
1.用funciton*声明
2.yield存值,执行第一次返回迭代器对象,接下来执行nex土方法,会返回{value,done}
3.yield表达式只能出现在generator,yield表达式和其他表达式连用,yield需要加括号
4. let res = function* a(){}();
       res === res[symbol.generator] // true
5. yield 默认返回undefined
6. next方法可以带一个参数这个参数会被当做上一个yield的返回值,如果想第一个yield也有返回值
   必须使用嵌套函数假象表示res.next()一次返回res,再外部再执行时候就是第一个yield
7. for of 循环 genreator() 只会返回到yield最后一个 return不会返回
8. res.throw('错误')可以通过try catch 去函数内部或外部捕获
9. 外部可以使用res.return('res')去返回,后续的res.next()不生效
10. for i of res() {yield i} 等价于 yield* res()
11. 
	function* g(){
        this.a = "a"
    } // 直接 let res = g() res.a 是undefied
	 // 改写 let res = g.call(g.prototype) res.a 是"a"
作用:
1. for(let i of g()) {
    console.log(i) // 所有yield的内容
}
2. 数据传输
function request(url) {
    axios.get(url).then(res=>{ //res指向yield的结果
        result.next(res) //从第二个next开始调用 res从后台返回,并且 res传递给
    	//逻辑代码
    })
}

function* gen(){
    // 此处yield指向第一个
     let yield1 = yield request() //res1
    console.log(yield1) //结果为第二个next的内容
    //第二个yield
    let yield request() // res2
}
    let result = gen()
    result.next() //执行第一个next res从后台返回

19.proxy

特性:
1. 类似于中介,是一个对象
2.参数1:target 包装目标对象
  参数2:handel 代理操作对象用于get和set拦截
3. 拦截器
 a. getter,setter,deleteProperty:
  		参数1: target
  		参数2: property 
         参数3: value
         参数4:proxy对象
  b. apply拦截函数的调用
  let proxy = new Proxy(function(){},{
      apply: function(){
          cnsole.log("你被拦截了")
      }
  })
  proxy() //你被拦截了
  c. has拦截能够拦截 in 例如: 'name' in proxy //即使有name属性你也可以再处理函数中拦截 reutnr false

20.reflect

作用:
1.判断对象是否有某属性
Reflect.has(obj, 'name')
2. 获取对象上的某属性
Reflect.get(obj, 'name')
3.

扩展

对象1继承对象二的原型
let obj1 = {}
let obj2 = {name:'obj2'}
Object.setPrototype(obj1, obj2)
obj1.__proto__ = obj2

试题

1. 循环数组,用数组解构赋值,分别打印出第二层的值
const items = [
    ['name','张飞'],
    ['title','长坂坡一声吼']
]
items.forEach(([key,value])=>{
    console.log(a,b)
})

2.浅拷贝和深拷贝的相同点和不同点,哪些方法是浅拷贝
相同:
浅拷贝和深拷贝的生成的对象第一层指向的堆地址都和原地址不一样
不同:
浅拷贝的内层引用型数据的改变会引起原址的堆的内容改变
深拷贝的内层引用型数据的改变不会引起原址的堆的内容改变
扩展:
复制时两个对象或者数组他们的值和地址都相同
浅拷贝的方法:
arr.concat(),arr.slice(0),[...arr],{...obj} obj.assign({},obj)

3. 数组去重
用includes() find() findIndex()
方式1function uniduqe(arr) {
    let arr2 = []  
    arr.forEach((item,index) =>{
        if (!arr2.includes(item)) {
            arr2.push(item)
        }
    })
    return arr2
}
方式2function uniduqe(arr) {
    let arr2 = []
    arr.forEach((item,index) =>{
        if (!arr2.find(function(v){
            return item == v
        })) {
            arr2.push(item)
        }
    })
    return arr2
}
function uniduqe(arr) {
    let arr2 = []
    arr.forEach((item,index) =>{
        if (arr2.findIndex(function(v){
            return item == v
        }) == -1) {
            arr2.push(item)
        }
    })
    return arr2
}
4.怎么证明是一个数组,对象,Set类型,map类型等
数组:
	arr instanceof Array
	{}.toString.call(arr).slice(8,-1)
	Array.isArray(arr)
对象:
	obj instanceof Object
	{}.toString.call(obj).slice(8,-1)
set:
	set instanceof Set
	{}.toString.call(set).slice(8,-1)
map: 
	map instanceof Map
	{}.toString.call(map).slice(8,-1)
转数组: 
	Array.from(set) // Array.from将拥有迭代器的类数组展位数组
	[...set]	
5.set 和 展开运算符实现数组的交集,并集和差集
let arr1 = [1,2,3]
let arr2 = [2,3,4]
并集:
	[...new Set(...arr1,...arr2)]
交集:
	function jiaoji (arr1,arr2) {
        let arr2Set = new Set(arr2)
        arr1.forEach(item => {
            if (!arr2Set.has(item)) {
                arr2Set.delete(item)
            }
        })
        return Array.from(arr2Set)
    }
差集:
function chaji (arr1,arr2) {
        let arr2Set = new Set(arr2)
        arr1.forEach(item => {
            if (arr2Set.has(item)) {
                 arr2Set.delete(item)
            } else {
                 arr2Set.add(item)
            }
        })
    	return [...arr2Set]
    }
6.

1576684831645

7. Symbol.iterator迭代器对 Map, Set, String的运用
let map = new Map([['name','erduo'],['age',18]])
let tool = map[Symbol.iterator]()
console.log(tool.next())

let arr = [1,2,3,4]
let tool = arr[Symbol.iterator]()
console.log(tool.next())

let str = '1234'
let tool = str[Symbol.iterator]()
console.log(tool.next())
8.对象没有Iterator可以手动部署
let obj = {
    data: [1,2,3,5],
    [Symbol.iterator](){
        let index = 0
        let _this = this
        return {
            next() {
                if (index <= _this.data.length) {
                    return {
                        value: _this.data[index++],
                        done: false
                    }
                } else {
                    return {
                        value: undefined,
                        done: true,
                    }
                }
            }
        }
    }
}
8.promise面试题
console.log('hello')
let promise = new Promise((res,rej)=>{
    console.log('promise') // 不在异步中等同于立即执行
    res()
})

promise.then(()=>{
    console.log('resolve')
}).catch(()=>{
    console.log('reject')
})
console.log('hello2')
//执行结果: hello promise hello2 resolve
8.promise常用情况
function getJson(url) {
    return new Promise((resolve,reject) =>{
        var xhr = new XMLHttoRequest()
        xhr.open('get',url,true)
        xhr.setHeader('Content-type','application-/json;charset=utf-8')
        xhr.send()
        xhr.onreadystatechange = function(){
            if (this.readyState !== 4) return
            if (this.status === 200) {
                resolve(res.response)
            }else {
			   reject(new Error(this.statusText))
            }
        }
    })
}
getJson('http://127.0.0.1:3000/name').then(
	res=>{
        console.log(res)
        const id = res.name[2].id // 获取到某id
        return getJson(`http://127.0.0.1:3000/age?=${id}`).then(
        	res=>console.log (res)
        ).catch(
        	err=>console.log(err)
        )
    }
)
9.防抖(debounce),规定大于等于定时器时间间隔则执行,小于规定时间频繁触发事件,则之前的事件不会执行.被定时器清除,重新计时,(频繁触发不会执行,只执行最后一次)
  场景:输入框监听输入事件字符超多时候用防抖去联想
function debounce (fn,delay = 2000) {
    let timeId ;
    return function () {
        let args = arguments
        let _self = this
        if (timeId) clearInterval(timeId)
        timeId = setTimeout(()=>{
            fn.apply(_self,args)
        })
    }
}
window.onresize =  debounce(( n )=>{
        document.documentElement.style.width = window.innerWidth /n
        document.documentElment.style.height = window.innerHeight/n
    },300)(2)
  

input.oninput = debounce(()=>{
        console.log(111)
},300)(2)


10.节流(throttle),就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
	(频繁触发时,到达指定时间搓执行一次)
&emsp;&emsp;&emsp;&emsp;鼠标不断点击触发,mousedown(单位时间内只触发一次)
&emsp;&emsp;&emsp;&emsp;监听滚动事件,比如是否滑到底部自动加载更多,用
&emsp;&emsp;&emsp;&emsp;if((scrolltop+clientHeight) >= scrollHeight) // 滚动高度+区域高度> 滚		动条对应内容的高度
    {
        throttle(fn, 300)(...);
    }
 /*
* @param: 节流函数
* @fn: 需要执行的业务函数
* @delay: 每次函数执行时的间隔时间
* */
function throttle(fn,delay){
   let _fn = fn; // 业务逻辑函数
   let timer = null // 定时器
   let isFirst = true // 第一次标识
   return function () {
       let _args= arguments // 业务逻辑函数的参数
       let _me  = this // 预存调用对象
       console.log("me",this)
       if (isFirst) {
           isFirst = flase
           _self.apply(_me,args)
           isFirst = false
           return 
       }
       if (timer) { // 用于判断是否已经开启定时器
           return false;
       }
       timer = setTimeout(()=>{ //开启新的定时器
           if (timer) clearTimeout(timer); // 500s后清除定时器,防止定时器累计
           console.log("定时器被激活")
           _self.apply(_me,_args) // 调用一次业务函数
           timer = null // 清空本次定时器开启标志
       },delay||500))
   }
}
		//节流抽离
		let fn  = throttle((count)=>{console.log("count:",count++)},1000)

		//节流效果绑定在某个元素上
		document.onclick = function () {
			fn(1)

		}
11.bind的封装
if (!Function.prototype.bind) {
        	Function.prototype.bind = function () {
                var _fn = this
        		var _self = [].shift.call(arguments)
                var _args = arguments
                return function () {
                    _fn.apply(_self, [..._args,...arguments])
				}
			}
        }
        //逻辑函数
        let accumulate = function () {
            console.log(obj.count++)
		}
		//被执行对象
		let obj = { count: 0}
		let objAccumulate = accumulate.bind(obj)
        objAccumulate()
12.window.requestAnimationFrame的兼容性写法
window._requestAnimationFrame = (function(){
    return window.requestAinmationFrame ||
           window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame || 
           function(callback) {
        		window.setTimeout(callback, 1000/60)
    	  }
})
13. 用reduce实现map函数
14. //目的:map每一个元素执行一遍回调函数,然后得到的新元素放在一起组成一个新的数组返回
	if (!Array.prototype.map) {
      Array.prototype.map = function(fn, fnThis) {
          let _self = this
          let _fn = fn
          return _self.reduce((pre,cur,index,_self) => {
                pre.push(fn.call(fnThis,cur,index,_self))
                return pre
          },[])
      }
    }
14.数组乱序
new Array(100).fill(1).map((item, key)=> key).sort(()=>0.5 -Math.random())
15. for infor of 的区别
a. for of 需要拥有迭代器才能使用Symbol.iterator,对象不能使用
b. for of 获取的是属性值,元素值
c. for in 不仅遍历属性,还会遍历方法
d. for in 获取的是key或者下表值,可枚举属性和原型属方法
16. 继承的集中方式
a.组合继承(原型链 + call构造函数)
function Person (age){
    this.name = 'erduo'
    this.hobby = [5,2,0]
    this.age = age
    
}
Person.prototype.getName = function() {
    return this.name
}
function Child(age){
    Person.call(this)
    this.age = age
}
Child.prototype = new Person()
let child = new Child()
// 优点:
child.hobby.push('1') // 不会改变父构造函数的引用类型数据
// 缺点:
new Person() , Person.call(this) // 使用了两次构造函数

b.寄生组合继承(原型链寄生在中间商, 再new中间商和call父构造函数 )
function Person(name){
    this.name = name
}

function Middle() {}
Middle.prototype = Person.prototype

function Child() {
    Person.call(this)
}
Child.prototype = new Middle()

c. es6Class
class Child extends Person {
    constructor(){
        super()
    }
} 
let child new Child()
17.利用proxy改变this指向
18.分时函数
/*
* 当我们对大量数据进行处理的时候, 比如将一个数组里面的大量数据渲染出dom节点, 如果直接for循环实现
* 会在短时间内给浏览器的渲染引擎造成很大的压力,很有可能会造成假死, 卡顿等情况
* 所以我们开发这个分时函数, 目的就是为了使得大量的数据可以分时分段渲染, 这样, 性能就会更好一些
* @start: 渲染函数
* @array: 需要处理的数据
* @fn   : 处理数据的函数
* @num : 单次处理数据个数的步长
* @delay : 每隔多长时间处理一次数据
* */

function timeChunk(fn,delay,arr,num) {
			let _fn = fn
			let timer = null
			function start() { //预存一个条数限制渲染函数
				for(let i = 0; i < Math.min(arr.length, num); i++){
					_fn.call(this, arr.shift()) //执行并传递本次需渲染的数据
					console.log("执行了一次渲染函数" )
				}
			}
			return function () { //分情况执行
				//只有一种情况:无限执行到数据为空
				timer = setInterval(()=>{ // 渲染函数定时执行
					arr.length ? start() : clearInterval(timer)

				})
			}
		}

		/*
        * 业务模拟函数
        * */

		let ary = [];
		for (let i = 1; i <= 1005; i++) {
			ary.push(i);
		};

		let renderFriendList = timeChunk (function (data) { //函数抽离
			let div = document.createElement('div');
			div.innerHTML = data;
			document.body.appendChild(div);
		}, 16.7, ary, 8);

		renderFriendList(); // 开始渲染

19.知道什么是事件委托吗
   a. 事件委托,就是利用冒泡原理,将事件加在父元素上。
   b. 传统的事件绑定需要绑定多次,事件委托一次绑定多个监听
      传统的事件绑定新增子元素要手动绑定,事件委托会自动绑定
      传统的事件绑定定义多个函数,事件委托只需一个函数占用内存更少
 20. 你了解promise吗?
     promise是一种异步回调嵌套的解决方案。相比较传统的异步回调,写法更加直观和优雅,代码容易理解维护。
	自带特性:
 		 1. 拥有三种状态,fulfiled,rejected,pendding
	     
		2. 无论是否用then,promise内部代码都会执行,异步结果通过resolve和reject传递
         3. resolve和reject内部可以嵌套promise,最终转变状态由最内层的状态决定
		4.resolve和reject的被执行,后续代码依然执行,而且先于then。
		(这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。改进方法是直接return resolve()结果,就不会出现该意外)
        
         5. 异步函数的结果被then或catch捕获
         6. then返回值作为promise被另一个then接收,能够链式调用
         7. catch捕获的错误不会影响promise外部的代码执行
         8. finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作(
      finally源码
Promise.prototype.finally = function (callback) {
   let P = this.constructor;
   return this.then(
    value  => P.resolve(callback()).then(() => 	value),
    reason => P.resolve(callback()).then(() => { throw reason })
             );
         };// 无论怎样都会返回当前promise的resolve和reject结果
)
         9.Promise.all方法用于将多个promise实例,包装成一个新的Promise实例。
(
     const p = Promise.all([p1, p2, p3]);
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
(3Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
)
         10.Promise.allSettled方法用于将多个promise实例,包装成一个新的Promise实例。该方法返回的新的 Promise 实例,一旦结束,状态 总是 fulfilled。
         (1)可用于确保所有请求是否都传递完毕,all方法只能确保全部为fulfilled
	    (2)可用于与await配合使用,并且可根据status过滤出两种状态
(
const promises = [ fetch('index.html'),fetch('https://does-not- exist/') ];
const results = await Promise.allSettled(promises);

// 过滤出成功的请求
const successfulPromises = results.filter(
    p => p.status === 'fulfilled'
);

// 过滤出失败的请求,并输出原因
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);
)
		11.Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);
上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

		12.Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案 。Promise.any()跟Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

21.window的onload事件和domcontentloaded执行顺序
	documentcontentloaded在dom树构建完成后执行,所以他先执行
	window.onload在图片和脚本构建完成后执行,所以他后执行
22.你之前遇到过跨域问题吗?是怎么解决的。
   遇见过,跨域问题就是不满足同源政策导致的,也就是协议,域名,端口不一致,我们只需将其配置一致。
    1. jsonp进行解决跨域, 我们可以在前端中先定义一个接收后端数据的函数。然后再dom中添加一个script标签,并且在src属性中发送一个请求并携带一个回调函数。后台解析并执行该回调函数。前台即可获得对应的参数和执行结果。
    2. cors跨域,在node的express中我们需要对请求头进行配置,req.setHeader设置Access-Control-Allow-Origin * 
    3. 构造函数返回值为对象和非对象时,new出来的是什么
	  若返回的是一个对象,则new的结果为该对象
      若返回的是一个数字或字符串,则new的结果为该实例化对象
23.typeofinstanceof的区别
	typeof 只能判断是否为引用了类型,不能判断是哪种引用类型,能够判断非引用类型具体为哪一种类型
	instancof 判断是否继承于某个具体的的类
24.任务队列

js的一大特点是单线程,即同一个时间只能做一件事,若不是单线程则导致复杂问题,比如一个线程删除一个节点,而另一个线程要操作该节点,浏览器不知以哪个线程为准。

	单线程意味着任务需要排队,如果前一个任务耗时长,那么就会阻塞后续任务的执行。为此js出现了同步和异步任务,二者都需要在主线程执行栈中执行;其中异步任务需要进入任务队列(task queue)进行排队,其具体运行机制如下:

规律:主线程,同步任务,任务队列,异步任务
    (1)同步任务在主线程上执行,形成一个执行栈
    (2)js会将主线程执行栈中的异步任务置于任务队列排队
    (3)一旦主线程执行栈同步任务执行完毕处于空闲状态时,就会将任务队列中任务入栈开始执行
异步任务:放在任务队列的宏任务和微任务
	macrotask(宏任务) 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括:
    script(整体代码)
    setTimeout、setInterval、setImmediate
    I/O、UI交互事件
	postMessage、MessageChannel
	microtask(微任务)可以理解为在macrotask任务 执行后,页面渲染前立即执行的任务。触发microtask任务的操作包括:
    Promise.then
    MutationObserver
    process.nextTick(Node环境)
Event loop:
	在主线程执行栈空闲的情况下,从任务队列中读取任务入执行栈执行,这个过程是循环不断进行的,所以又称Event loop(事件循环)。
    macrotasks>microtasks>microtasks回调
25.数组扁平化处理
	//数组则拆解再连接非数组则连接
function flatten(arr) {
   return  arr.reduce((pre, cur) =>{
        return pre.concat(Array.isArray(cur) ? flatten(cur) : cur)
    },[])
}
26.辣鸡汇收机制
	1.引用计数算法: 当变量被引用次数加1,解除引用次数减1,次数为0会被回收,存在循环引用的缺点,即变量已删除,次数仍然不为1,不会被回收
	2.标记清除算法: 当有效内存空间被耗尽的时候,就会停止整个程序,然后进行标记,将栈区的可以访问的对象标记为存活对象;遍历栈区,将不能访问的对象清除
27.浏览器渲染的机制
	1.处理html构建dom树
	2.处理css构建css样式表
	3.将dom树和css样式表合并成渲染树
	4.利用渲染树,计算节点位置进行渲染
28.为什么脚本代码不要写再头部
	1. 因为脚本代码会造成css的加载阻塞,知道脚本代码加载完毕,才会继续执行css构建
29.重绘(Repaint)和回流(Reflow)
	重绘:当节点需要更改外观而不会影响布局的,比如改变 color、background-color、visibility等就叫称为重绘
	回流:布局或者几何属性需要改变 就称为回流。
	注意: 回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
	会导致回流的操作:
        页面首次渲染
        浏览器窗口大小发生改变
        元素尺寸或位置发生改变
        元素内容变化(文字数量或图片大小等等)
        元素字体大小变化
        添加或者删除可见的DOM元素
        激活CSS伪类(例如::hover)
        查询某些属性或调用某些方法
	一些常用且会导致回流的属性和方法:
        clientWidth、clientHeight、clientTop、clientLeft
        offsetWidth、offsetHeight、offsetTop、offsetLeft
        scrollWidth、scrollHeight、scrollTop、scrollLeft
        scrollIntoView()、scrollIntoViewIfNeeded()
        getComputedStyle()
        getBoundingClientRect()
        scrollTo()
   避免回流的方法:
   		避免使用table布局。
        避免使用CSS表达式(例如:calc())。
30.XSS和CSRF
	XSS:是一种代码注入攻击,利用恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等
	XSS的类型:
		1.反射型:攻击者将带有恶意代码的URL发送到后端,用户打开网站后,后端取出恶意代码拼接在HTML,浏览器解析并执行恶意代码,进行窃取用户信息或者攻击用户。
		2.DOM型:用户将带有恶意代码的URL打开,前端 JavaScript 取出 URL 中的恶意代码并执行,进行窃取用户信息或攻击用户。
		3.存储型: 攻击者将恶意代码提交到后端数据库,用户打开网站,后端取出恶意代码拼接在html上,浏览器解析并执行恶意代码,对用户进行信息窃取或攻击用户。
	防御XSS的方法:
		1.httpOnly: 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。
		2.输入过滤: 一般是用于对于输入格式的检查,例如:邮箱,电话号码,用户名,密码……等,按照规定的格式输入。不仅仅是前端负责,后端也要做相同的过滤检查。因为攻击者完全可以绕过正常的输入流程,直接利用相关接口向服务器发送设置。
		3.改成纯前端渲染,把代码和数据分隔开。
	CSRF:利用已登录用户的用户凭证,绕过后台验证,对网站进行攻击或某项操作
	

简而言之:网站过分相信用户

    CSRF的类型:
        1.get类型:在img的src中写入请求链接,用户点击会进行信息请求
        2.post类型:访问页面后表单自动提交到某页面
        3.链接类型:a标签进行诱导用户访问
    防御:
        1.进行token认证
    
28.三次握手 针对tcp连接
	客户端向服务端发送一个网络包,服务端接收到得知,客户端能正常发送请求,自己也能正常接收请求。
    服务端向客户端发送一个网络包,客户端接收后得知,服务端能正常接收和发送请求,自己也能正常发送和接收请求。
    客户端向服务端发送一个网络包,服务端接收后得知,客户端能正常接收和发送请求,自己也能正常发送和接收请求。
29.四次挥手(客户端和服务端都可以断开连接)
	第一次挥手:发送端发送一个fin报文,代表自己断开连接
    第二次挥手:接收端响应ack报文,并在自己处理完报文后发送一个fin报文
    第三次挥手:发送端接收到ack报文后,并且接收端的fin报文,然后向接收端发送一个ack报文,进入等待状态,等待对方接收到ack报文,断开连接
    第四次挥手:接收端接收到ack报文,断开连接
30.mvc,mvvm,mvp模式
m: modle层用于封装和业务逻辑相关的数据以及对数据的处理方法。
v: view层用于显示model层数据和响应用户操作
c: control层作为模型和视图之间的纽带,用于view层用户事件传递数据给model层,同时用于model层传递最新数据给view层。
方式:
	所有方式都是单向通信
缺点:
	耦合度高,view和control看似分离,确是紧密结合

m: model层用于封装和业务逻辑相关的数据以及对数据的处理方法。
v: view层用于显示model层数据和响应用户操作
p: presenter作为view和model层之间的中间人,并且mvp中的view层不能直接使用model层,必须通过presenter提供的接口,让presenter更新model,再用观察这模式更新view。
方式:
	各部分之间都是双向通信

m: model层用于封装和业务逻辑相关的数据以及对数据的处理方法,但在mvvm中只关心数据,不关心其他行为。
v: view层用于显示model层数据和和响应用户操作,在mvvm中利用模板语法来声明式地将数据渲染成dom,当model层更新时,通过数据绑定更新到view层。
vm: viewModel层类似于中间人,当model数据更新时,viewmodel会对view层进行数据更新,当view层对数据进行操作时viewModel会通知model层进行绑定数据的更新。
方式:
	双向绑定
实现数据绑定方式:
	数据劫持 (Vue)
	脏值检查 (旧版Angular)
优点:
	前后端分离,减轻开发人员的任务,便于测试
    
31.eventloop
一句概括:当js主线程中的执行栈任务被执行完毕,会读取任务队列中的异步任务去执行,其中任务队列放的是异步任务中处理完的回调函数,分为宏任务和微任务,当宏任务执行完毕会去执行微任务,一直循环到任务结束。
宏任务(MacroTask)
	script(整体代码)、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)、I/O、UI Rendering。

微任务(MicroTask)
	Process.nextTick(Node独有)、PromiseObject.observe(废弃)、MutationObserver(具体使用方式查看这里)
32:为什么JavaScript是单线程?
	假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
33.TCP和UDP的区别
(1)TCP是面向连接的,udp是无连接的即发送数据前不需要先建立链接。
(2)TCP提供数据可靠传输。,无差错,不丢失,不重复;UDP尽最大努力交付,即不保证可靠交付。 并且因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换。
(3)TCP是面向字节流,UDP面向报文,并且网络出现拥塞不会使得发送速率降低(因此会出现丢包,对实时的应用比如IP电话和视频会议等)。
(4)TCP只能是11的,UDP支持11,1对多。
(5)TCP的首部较大为20字节,而UDP只有8字节。
。