1 判断null
typeof null的结果为object
如何判断一个数是否为null:
1、nums===null
2、isNan()
2 instanceof原理
用于检测某个实例对象是否是这个构造函数的实例
function instance_of(obj1,obj2){
let proto=obj.__proto__
let prototype=obj2.prototype
let queue=[proto]
while(queue.length){
let temp=queue.shift()
if(temp===null)return false
if(temp===prototype)return true
queue.push(temp.__proto__)
}
}
3 类型转换
(1)强制转换
- undefined、null、false、NaN、‘’、0、-0都会转换为false
- 其他所有值都转为true,包括所有对象
将undefined转换为数字结果为NaN,字符串如果内容为数字或进制值就正常转换,否则为NaN
(2)隐式转换
将对象转换为基本类型:
- 调用symbol.toPrimitive,转成功就结束
- 调用valueOf,转成功就结束
- 调用toString,转成功就结束
- 报错
[] == ![] 结果为true
undefinednull 结果为true
nullnull 结果为true
4 new操作符
new操作符执行步骤可分为以下几步:
- 生成一个新对象
- 将对象的原型连接到构造函数的原型上,并绑定this
- 判断返回值,如果没有返回对象,则返回创建的对象
function myNew(fn,...args){
let obj=Object.create(fn.prototype)
let res=fn.call(obj,...args)
return res && typeof res==='object'? res:obj
}
5 什么是作用域?什么是作用域链?
作用域是某些特定部分中变量,函数和对象的可访问性
当js需要查找某个变量的值时,它会从当前作用域查找,如果没有再去上级作用域中查找,直到查到全局作用域,这样一个过程形成的链条就叫作用域链
6 原型
- 所有对象都有一个属性_proto_指向一个对象,也就是原型
- 每个对象的原型都可以通过constructor找到构造函数,构造函数可以通过prototype找到原型
- 每个对象都有其原型对象,这样形成一个关联一个、层层相互依赖的 从属关系,这就是原型链,原型链后面的对象可以使用前面的对象的属性和方法
7 继承
(1)原型链继承
function parent(){
this.age=32
this.arr=[1,2]
}
function child(){
this.name='xie'
}
child.prototype=new parent()
let s1=new child()
let s2=new child()
s1.arr.push(3)
缺点:虽然父类的方法和属性能够访问,但是原型属性是共享的
(2)构造函数继承
function parent(){
this.age=32
this.arr=[1,2]
}
parent.prototype.sex='boy'
function child(){
parent.call(this)
}
let s=new child()
缺点:无法继承父类的原型属性和方法
(3)类继承
class person{
constructor(name){
this.name=name
}
}
class child extends person{
constructor(name){
super(name)
}
}
const s=new child('xie')
通过原型实现的继承和class实现的继承有什么不同之处?
1、getPrototypeOf结果不同
2、this创造顺序不同,ES5的继承是先创建子类实例对象的this,然后再将父类的方法添加到this上,ES6的继承先将父类实例对象的属性加到this上,然在再在子类中修改this
3、class子类实例的构建,基于父类实例,只有super方法才能调用父类实例
8 深拷贝和浅拷贝
(1)浅拷贝:两个对象第一层的引用不相同
- Object.assign(target,source)可实现浅拷贝
- 扩展运算符:b={…a}
(2)深拷贝:两个对象内部所有引用都不相同
- JSON.parse(JSON.stringfy(object))
function deeoclone(source){
//判断是否为基本数据类型
const isPrimitive=(value)=>{
return /Number|Boolean|String|Null|Undefined|Symbol|/.test(Object.prototype.toString.call(value))
}
}
const checkType=(value)=>{
return Object.prototype.toString.call(value)
}
let res=null
if(isPrimitive(source)){
res=source
}else if(checkType(source)==='[object Date]'){
res=new Date(source)
}else if(checkType(source)==='[object RegExp]'){
res=new RegExp(source)
}else if(checkType(source)==='[object Set]'){
res=new Set()
for(let v of source){
res.add(deepclone(v))
}
}else if(checkType(source)==='[object Map]'){
res=new Map()
for(let [key,value] of source.entires()){
res.set(key,deepclone(value))
}
}else if(checkType(source)==='[object Array]'){
source.forEach((value,index)=>{
res[index]=deepclone(value)
})
}else if(checkType(source)==='[object Object]'){
res={}
Object.entires(source).forEach(([key,value])=>{
res[key]=deepclone(value)
})
}
return res
9 手写promise.all和promise.race
function myAll(_promises){
return new Promise((resolve,reject)=>{
const promises=Array.from(_promises)
const len=promises.length
const res=[]
let count=0
for(let i=0;i<len;i++){
Promise.resolve(promises[i]).then(o=>{
res[i]=o
count++
if(count===len){
resolve(res)
}
}).catch(e=>reject(e))
}
})
}
function myRace(_promises){
return new Promise((resolve,reject)=>{
let promises=Array.from(_promises)
promises.forEach(p=>{
Promise.resolve(p).then(o=>resolve(o)).catch(e=>reject(e))
})
})
}
10 promise all错误处理
11 promise all若其中一个失败了,如何让其他成功的请求返回
let p1=Promise.resolve(1)
let p2=Promise.resolve(2)
let p3=Promise.resolve(3)
let p4=Promise.resolve(4)
let p5=Promise.reject(5)
const arr=[p1,p2,p3,p4,p5]
let all=Promise.all(arr.map(p=>p.catch(e=>console.log(e))))
all.then(res=>console.log(res)).catch(e=>console.log(e))
12 promise的串行实现
function runPromise(_promises){
return _promises.reduce((prev,next)=>{
prev.then(()=>next())
},Promise.resolve())
}
async function runPromise(_promises){
for(let p of _promises){
await p()
}
}
13 await的优缺点
优点:在处理then的调用时,能够更清晰准确的写出代码
缺点:滥用await会导致性能问题,因为await会阻塞代码,导致代码失去了并发性
14 什么是事件循环?微任务和宏任务
事件循环是浏览器解决js单线程运行不会阻塞的一种机制。由于js为单线程的,前一个任务执行完后才能执行下一个任务。任务可分为同步任务和异步任务。
事件循环的流程为:
- 整体script开始执行,将代码分为同步任务和异步任务
- 同步任务进入主线程依次执行
- 异步任务又分为微任务和宏任务
- 微任务和宏任务进入事件表中并注册相应的回调函数,每当指定的事件完成,事件表会将这个函数移到事件队列中
- 当主线程执行完毕,主线程为空时,会检查微任务的事件队列,如果有则全部执行,如果没有则执行下一个宏任务
微任务:promise的then方法
宏任务:setTimeout回调、setInternal回调、IO、postMessage
15 模块化
commonJS
module.exports={
a:1
}
exports.a=1
var module=require('./a.js')
module.a
commonJs和ESM的区别:
- commonJS支持动态导入(require),ESM使用import异步加载
- commonJS输出的是值拷贝,ESM输出的是值的引用
- commonJS是运行时加载,ESM是编译时输出接口
16 垃圾回收
(1)清楚标记
- 垃圾回收器给内存中存储的所有变量加上标记
- 去除环境中的变量和被环境中的变量引用变量的标记
- 剩下还被标记的变量就是需要清楚的变量
- 垃圾回收器销毁带标记的值并回收它们占用的内存空间
(2)引用计数:跟踪每个值被引用的次数,回收引用次数为0的变量所占用的内存
缺点:当存在循环引用时,会导致内存泄漏
17 0.1+0.2!==0.3
原因:浮点数用而二进制表示的时候是无穷的,因为精度问题,两个浮点数相加会造成截断丢失精度
解决办法:先求出两个浮点数的最大小数长度x,在将两个数分别乘以10的x次方幂,相加再除以10的x次方幂