一、JavaScript
一、[‘1’, ‘2’, ‘3’].map(parseInt) ?
[1,NaN,NaN]
parseInt(string, radix),字符串转十进制整数
string
:必需。要被解析的字符串。radix
: 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
可以理解为 radix 是表明 string 的进制的,最终是要转为 10 进制数的tips:注意第一个参数一定是字符串,如果是number类型可能会发生不可预期错误,例如:
1
2
3 > parseInt(01232, 10); // 666
> parseInt('01232', 10); // 1232
>
解析的时候,parseInt会认为 string
参数是 radix
进制的,比如:parseInt(3,4)
,这里会认为“3”是 4进制的,所以当遇到这种情况:parseInt(3,2)
, 会认为这里的“3”是二进制的,那么3的二进制是3吗?不是啊,是 “11”啊 ,所以会返回NaN
二、防抖和节流
下面我都用的 函数属性timer作为记录定时器的变量,其实也可以在函数内声明一个变量
1. 防抖实现
触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间,
比如窗口缩放事件 window.resize1
2
3
4
5
6
7
8
9function debounce(fn,time){
return function(){
clearTimeout(fn.timer)
fn.timer=setTimeout(()=>{
fn.apply(this, arguments)
},time)
}
}
`
问:为啥用apply?
答:改变函数this指向
问:为啥要改变fn的this指向?如果不apply这个this又指向谁?要是不改有啥影响?
卒~
解析:
首先要明白一件事情,this的指向问题,
this 的指向只是与如何调用这个方法有关,看几个例子
1 | a={ |
看完上面例子,你就知道了setTimeout里如果用箭头函数,那么this是当前作用于,但是 fn()
这样执行的时候,里面的this指向全局,所以要给这个fn绑定当前执行环境的this,以避免一些因为作用域问题引起bug
2. 节流实现
高频事件触发,但在n秒内只会执行一次1
2
3
4
5
6
7
8
9function debounce(fn,time){
return function(){
if(fn.timer)return
fn.timer=setTimeout(()=>{
fn.apply(this, arguments)
clearTimeout(fn.timer)
},time)
}
}
三、介绍下 Set、Map、WeakSet 和 WeakMap 的区别?
四、深度优先遍历和广度优先遍历
1 | <div class="parent"> |
1. 深度优先遍历
1 | function deepTraversal(node) { |
2. 广度优先遍历
1 | function widthTraversal(node) { |
五、深拷贝函数
1 | function deepCopy(obj) { |
六、事件循环
js 中的栈
和堆
分别存储值类型和引用类型执行栈
和上面的栈意义不同,
当一个脚本第一次执行的时候,js 引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,然后从头开始执行。如果当前执行的是一个方法,那么 js 会向执行栈中添加这个方法的执行环境,然后进入这个执行环境继续执行其中的代码。当这个执行环境中的代码 执行完毕并返回结果后,js 会退出这个执行环境并把这个执行环境销毁,回到上一个方法的执行环境。。这个过程反复进行,直到执行栈中的代码全部执行完毕。
事件循环
js 引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起(浏览器去执行
),继续执行执行栈中的其他任务。当一个异步事件返回结果后(注意是异步有了返回结果后,将回调函数添加到事件队列
),js 会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕
(这也是为什么setTimeout 时间不会非常精确的原因
), 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”
的原因。
callback queue
:事件队列webAPIs
:异步操作(浏览器执行)stack
:执行栈
七、宏任务和微任务
异步事件也分宏任务和微任务,他们执行顺序的优先级不同宏任务
:setTimeout、setInterval、I/O、UI 渲染微任务
:Promise、MutaionObserver
同一次事件循环中微任务比宏任务优先级高
执行顺序
执行队列空闲=>检查微任务队列=>检查宏任务队列
1 | setTimeout(function () { |
执行过程
script 进入主线程开始执行同步代码,将异步操作交给浏览器执行,所以先将 setTimeout 挂起,然后打印 2 ,遇到了 promise 则挂起,此时任务队列空闲(这时异步操作都已经执行完加入了事件队列),那么去检查事件队列,先检查微任务队列,好,发现了 promise 的 then 回调,执行 ,打印 3,微任务队列空了,检查宏任务队列 打印 1 ,此时执行栈为空 ,退出
比较特殊的例子
1 | async function async1() { |
async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行
也就是 await async2()后面(下面)的部分是放在了promise(微任务),async1 转换一下就是这样:1
2
3
4
5
6async function async1(){
console.log('1');
return new Promise(resolve=>resolve(async2())).then(e=>{
console.log('2')
})
}
可以看到 async2() 实在promise的executor(执行器)里同步执行的,所以是
主线程 4 , async 1 ,async2 , 将console.log(2) 加入微任务, 主线程的 console.log(5)
执行栈已经空闲,查看事件队列(优先微任务,然后宏任务) 输出2
练习题,写出执行结果考点:就是事件循环、宏任务、微任务的执行顺序,上面说的弄明白了,就很容易了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
八、for 循环每隔 1 秒输出对应数值
因为以前面试遇到过,所以就记录下来
- async、await
1 | async function a() { |
- 闭包,这种方式的缺陷就是第一次其实是有间隔时间的,而上面是马上输出的
1 | function b() { |
- let/const,原理是每次循环 let 或 const 形成了自己的局部作用域
1 | function b() { |
九、编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组
arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
- 方法一
1 | [...new Set(arr.flat(Infinity))].sort((a, b) => a - b); |
- 方法二
1 | function flat(arr) { |
十、new 的时候发生了啥 || 实现一个 new
1 | /* |
十一、检测 js 类型
这个问题都快要问烂了,一般都是说 Object.prototype.toString.call(参数) ,可是为什么要用这种方式?
先列出来我知道的几种方式
- typeof
缺点:不能判断出来数组,对象 , undefined,null 因为它们都返回 object - 参数.constructor.name
缺点:不能检测 undefined,null - Object.prototype.toString.call(参数)
这种方式很全面,但是为什么要用 Object 的原型?
Array ,function 继承了 Object,但重写了 toSting,可以验证一下
1 | delete Array.prototype.toString > true; |
十二、模板编译
解析模板
1 | let template = ` |
解析函数
1 | function compile(template) { |
十三、补全字符串
1 | // 如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全 |
十四、链表
单链表
链表组成
- 节点
- 链表
1 | // 节点类 |
附加
附加
二、CSS
一、什么是包含块?怎么确定包含块?
一个元素的大小和位置要依据一个矩形框来设置,这个矩形框就是包含块
1. 确定包含块
一个元素的包含块完全依赖于这个元素的position属性
- 如果 postion 属性为 static/relative,包含块就是他最近的
祖先块元素
的内容区的边缘组成的(注意是块元素哦)
下面几条注意了,是内边距的外边缘(边框的内边缘),而不是内容区域边缘了,上面的是内容区边缘 - 如果 position 属性为 absolute,包含块就是由它的最近的 position 的值不是 static (也就是值为 fixed, absolute, relative 或 sticky)的祖先元素的
内边距区的外边缘
组成。 - 如果 position 属性是 fixed,包含块是 viewport(浏览器中就是 window,可视区域)
- 如果 position 属性是 absolute/fixed,包含块也可能是由满足以下条件的最近父级元素的
内边距区的外边缘
组成的- transform 或 perspective 属性(不为 none)
- 剩下几条都是些浏览器不怎么兼容的属性,略
2. 根据包含块定位
像 margin/position:absolute 这类的元素,如果设置百分比,则会根据它的包含块的大小来计算定位
ps: margin 不管是 margin-top 还是 margin-left 等都是根据 父元素宽度来计算的
css 的offsetParent 指的是祖先元素中已经定位(position/transfrom/perspective)的元素,如果没有就是 body
css 的parentNode 指的是父级元素
二、块级格式化上下文 BFC
BFC 隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素
创建 BFC:
- 元素
- 浮动元素(float 不是 none)
- 绝对定位元素(position 取值为 absolute 或 fixed)
- display 取值为 inline-block,table-cell, table-caption,flex, inline-flex 之一的元素
- overflow 不是 visible 的元素
比如:
1 | <div id="box" style="margin-top:10px"> |
这种情况实际上会发生外边距重叠(浏览器试一下就明白),实际我想要不让它们重叠,也就是 box 里的元素不要影响到外面或者外面的元素属性不要影响到里面,那么给 box 创建一个 BFC 即可
三、块级元素的垂直居中
1 | <div class="f"> |
- 子元素必须固定大小
1 | .f { |
- 子元素宽高没有限制
1 | .f { |
- 子元素宽高没有限制
1 | .f { |
- 子元素要固定大小
1 | .f { |
- 子元素宽高没有限制
1 | .f { |
- 子元素宽高没有限制
1 | .f { |