Js基础
#
1. 值类型和引用类型的区别- 值类型都是存贮在栈中,存的是值,值类型占用的空间较少
- 值类型是没有函数调用的 undefined.toString(),会直接报错
- 但是'1'.toString(),这个时候1,已经被强转成了对象类型
- 0.1 + 0.2 !== 0.3
- null 的 typeof 判断为object,因为null在js初始的时候是000000,js是以 前三位来判断是否是对象类型,null为全0,当时的性能优化,现在已经改了,但是这个bug就一直留下来了。
- 引用类型是堆存贮的,存的是内存地址,引用类型可能很大
- 值类型:string,number,boolean,symbol,undefined,null
- 引用类型:object(array,function,object)
#
2. typeof能判断的类型- typeof可以判断除了null的值类型
- 所以用instanceof判断是比较好的选择(instanceof内部机制是通过
原型链
来判断的) - 用instancheof判断原始类型
- Object.prototype.toString.call()
- 判断数据类型,用obj原型上的toString方法
#
3. 类型转换原始值 | 转换目标 | 结果 |
---|---|---|
number | 布尔值 | 除了0,-0,NaN,都为true |
string | 布尔值 | 除了‘’ 都是true |
undefined、null | 布尔值 | false |
引用类型 | 布尔值 | true |
number | 字符串 | ‘1’ |
boolean、函数、Symbol | 字符串 | true |
数组 | 字符串 | [1,2] => 1,2 |
对象 | 字符串 | "[object Object]" |
string | 数字 | ‘1’ => 1 'a' => NaN |
数组 | 数字 | [] => 0, [1] => 1, 其他:NaN |
null | 数字 | 0 |
除了数组的引用类型 | 数字 | NaN |
Symbol | 数字 | 抛错 |
- 对象在转换的时候调用内部的[[ToPrimitive]]函数
- 如果是原始类型不需要转换
- 调用valueOf(),对象类型
- 调用toString(),字符串类型
- 都没有返回报错
#
4. 四则运算符+
如果接字符串会对字符串进行拼接- 如果是数字会直接进行相加
'a' + + 'b'
结果是aNaN
第二个b会被转为数字格式 - 除了
+
的其他运算符,其他类型都会被转为数字
#
5. 比较运算符- 字符串通过unicode比较
- 对象通过toPrimitive转换后比较
#
6. 构造函数- 用new关键字调用的函数会创建一个新对象
- 空对象被设置为函数的上下文this
- 可以在对象通过this添加属性和方法
- 不用写return,新构造的对象就是函数的返回值
- 如果写了return,且返回值是一个对象类型的话,函数会丢弃上下文this的属性和方法,直接返回return的那个对象
- 如果写了return,返回值非对象类型,构造函数会忽略该返回值,构造新的对象出来。
#
7. this- 函数的调用方式有四种(对应四种this的取值)
- 直接调用foo() —> window
- 当做对象的方法调用obj.foo() -> 指向对象
- 构造函数调用new foo() -> 指向当前实例,就不会在改变了bind无效
- call apply方法调用 -> this指向 call 和 apply 的第一个参数,连续调用只认第一个
#
8. 拷贝- 浅拷贝
- 深拷贝
- 简易版深拷贝
#
9. 原型 和 继承 new 操作符实现- 每一个对象身上都一个原型,原型上的
constructor
属性指向,当前的对象。 - 该原型对象创建出来的,实例对象的
constructor
属性同样指向该原型对象 - 一般不会改变
constructor
属性。instanceof
是通过原型链来判断是否是当前的引用。
- 继承 ES5 (寄生组合继承)
- 继承ES6,class是语法糖,本质就是函数
- new操作符
#
10. 闭包- 当作用域消失之后,依旧可以使用变量,闭包使函数更高效,但是会消耗一定的内存成本
- 私有变量
- 回调函数
#
11. 变量 和 变量提升- 函数声明提升,箭头函数和变量命名的函数都不会有声明提升
- var变量存在变量提升,let 和 const没有,所以会出现暂时性死区
- const不能声明的变量不可以重新复制 let可以
#
12. 模块化- IIFE
- 用闭包环境创建私有变量
- 如果包扩展了之后,导致几个闭包环境的,私有变量不能共享,就会有引入先后的问题
- AMD
- 主要提供给浏览器端使用,解决包模块的顺序调用
- 异步加载模块,避免阻塞
- 在同一个文件中可以定义多个模块
- UMD (可以同时支持AMD 和 commonJs)
- CommonJS
- 主要提供给Js宿主环境,node
- 直接引入文件,不用做异步处理,比较受服务端node的环境
- 语法module.exports 导出 require 导入,使用较为简单
- ES Module
- CommonJS支持动态导入;ES Module不支持
- CommonJS是同步导入,用于服务端;ES Module异步导入,用于浏览器,不会阻碍进程
- CommonJS是值拷贝,要更新要重新导入;ES Module引用拷贝,同步更新值
#
13. getter setter Proxy- 创建getter setter的方式 字面量,class,defineProperty
- 用处: 处理记录日志,数据验证,属性值变化检测
- Proxy
- 记录代理日志
- 用Proxy包裹进行函数性能测试
- 使用代理属性自动填充
- 给数组添加负数取值
#
14. filter, map, reduce- filter
- map
- reduce
- 根据数组对象中某个key的分类
- 二维数组变成一维数组
- 计算数组里面的每一项有多少个
#
15. Event Loop事件循环的两个基本原则
- 一次处理一个任务
- 一个任务开始后直到运行完成,不会被其他任务中断
微任务和宏任务
- 单次循环迭代中,最多处理一个宏任务(其余的在队列中等待)
- 微任务在一次循环内都会被处理掉
- 单次循环的微任务处理完成之后,会检查是否需要更新UI渲染。如果需要就更新。
- 此时第一次循环结束,开启第二次循环。
注意
- 两个任务队列是独立于事件循环的,事件的监听和添加任务是独立于事件循环的,保证了在循环过程中是可以添加时间到循环中的,不然在循环的时候用户如果触发了动作,就会被忽略,从而加不到队列当中。
- 在执行任务的时候,就不可能中断了
- 微任务在渲染前完成
- 浏览器每秒尝试渲染60次,所以一帧大概是16ms,如果是几百ms都是察觉不到的,但是如果任务的执行时间过长,浏览器可能会终止任务。
常见的eventLoop面试题
- 下面内容如何输出,浏览器是否会进行变色?
- 输出:1,3,2 浏览器为黄色。因为promise.then是微任务在选渲染之前执行不会变色;
- 不点击按钮和点击按钮的执行循环有什么区别?
- 不点击按钮的时候,相当于普通的函数调用。所以是按照微任务方式执行的和下面的代码是一个意思。
- 当点击按钮的时候,click就是浏览器事件,事件是宏任务,所以每一个宏任务是分开执行的。在自己的宏任务中处理当前的微任务。所以输出结果是。
listener1 micro task1 listener2 micro task2
- 下面代码的输出结果是什么?
- 第一次事件循环扫描所有代码,然后发现有微任务,和宏任务。把任务分别放到微任务【Promise1】,宏任务【setTimeout1】。
- 第二次循环:微任务要在浏览器渲染之前执行,所以微任务先执行了。输出 Promise1,在微任务中发现了宏任务,把宏任务放到宏队列中 现在微任务【】,宏任务【setTimeout1,setTimeout2】,依次执行宏任务,输出setTimeout1,在宏任务中发现了微任务,把微任务加入微任务队列中。现在微任务【Promise2】,宏任务【setTimeout2】,在本次事件循环结束之前要清空所有微任务,所以微任务执行,输出Promise2。现在微任务【】,宏任务【setTimeout2】
- 第三次循环:队列中只剩下一个宏任务了。输出setTimeout2
- 最后结果输出
Promise1 setTimeout1 Promise2 setTimeout2
- 下面代码输出的结果是什么?
- 1 6 2 3 8 7 4 5
#
16. 事件委托事件冒泡
- 点击的元素会从小到大,依次触发
事件捕获
- 点击的元素会从大到小,依次触发
事件执行的过程
- 如果设置了捕获会先捕获 -> 到达冒泡元素 -> 开始向上进行冒泡
- 浏览器的默认执行冒泡的
- 如果给body身上绑定了两个时间,同时冒泡和捕获,会按照注册顺序执行,而不是先捕获在冒泡
事件委托
- 点击子元素,冒泡到父元素。通过拿到子元素的事件对象,触发动作
- 当父元素作为事件处理器的注册元素的时候,这个时候this指向的是父元素。点击父亲中的一个子元素,这个时候父亲事件对象event.target,不是指向父元素的,也就是说和this并不是同一个对象,而是指向当前点击的子元素。就是这样完成了事件代理。
- 优点:节省内存
#
17. call,apply,bindcall
apply
bind
#
18. 跨域浏览器协议、域名、端口有一个不同就是跨域,Ajax请求会失败,为了安全考虑 CSRF攻击:利用用户的登录状态发起恶意请求
- JSONP
- 利用
<script>
标签没有跨域限制的漏洞 - 简单,兼容性好,但是只能用于get请求
- CORS
- 需要浏览器和后端同时支持
- 需要服务端设置请求头:Access-Control-Allow-Origin
- document.domain
- 只能用于二级域名相同的情况下,比如a.test.com, b.test.com
- 只需要给页面添加document.domain = test.com 表示二级域名都相同可以实现跨域。
- postMessage(待补充)
- 用于获取嵌入页面的第三方页面数据
#
19. 储存特性 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
数据储存大小 | 4k | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在header中,对于请求性能有影响 | 不参与 | 不参与 | 不参与 |
- service Worker
- 用来实现缓存功能
- 使用时保证传输必须是HTTPS
- 注册 -> 监听install事件 -> 判断缓存中是否存在,存在就取缓存中的,不存在就重新请求
#
20. 浏览器缓存机制存储位置
- Service Worker
- 可以自由控制缓存哪些文件,匹配缓存,读取缓存。
- 如果没有命中请求回来,浏览器依旧显示Service Worker
- Memory Cache
- 内存缓存,高效,但持续时间短
- 页面关闭就会失效
- 一般不会存贮大文件 和 频繁读取的文件
- Disk Cache
- 硬盘缓存,读取速度慢一些,但是可以存储的空间大
- 根据header请求头来判断那些资源需要缓存
缓存策略
- 强缓存
- Expries
- Http/1 根据绝对时间判断是否需要缓存
- 受限于本地时间,本地时间修改会影响该缓存
- Cache-control
- Http/1.1 优先级高于Expries,根据相对时间来判断是否需要缓存
- 可以设置多种指令
max-age=30
等
- 协商缓存
- Last-Modified
- 协商缓存成功返回304状态码
- 表示当前的文件最新修改时间
- If-Modified-Since,通过对比请求头的时间,来判断是否需要进行请求
- 缺点:1. 时间过期文件没有修改依旧会请求 2. 以秒计时,如果当前文件更改在1秒内,服务器同样不会更新
- Etag
- 用算法计算当前文件是否有修改过,类似每一个文件有一个对应的id,修改过id会改变
- 通过If-None-Match对比后端存入的值,看是否需要请求操作
- 优先级高于Last-Modified
启发式缓存:如果没有设置缓存策略,浏览器会自己在响应头中用Date - Last-Modified值的10%作为缓存时间
往返缓存:浏览器为了在用户页面间执行前进后退操作时拥有更加流畅体验的一种策略。该策略具体表现为,当用户前往新页面时,将当前页面的浏览器DOM状态保存到
bfcache
中;当用户点击后退按钮的时候,将页面直接从bfcache
中加载,节省了网络请求的时间。
#
TODO- 性能优化
- 安全
- http
- 设计模式
- 数据结构与算法
- webpack vite
- ssr
- node
- git
- react源码fiber