Skip to content

常见问题

多层的list嵌套的情况的优化如何处理?

  1. flat打平
  2. 使用reduce

常见代码优化有哪些?

双目运算 > 三元表达式 > if else

浏览器引擎

内核以Blink为例

浏览器引擎 = 排版引擎 = 渲染引擎,解决的是页面渲染和布局的问题

引擎的作用:解析JS,转换为二进制,到CPU执行

layout engine
		·浏览器内核有哪些?
			Gecko								(早期被网景和火狐使用)
			Trident								(微软开发,IE4-IE11使用;目前的Edge已经转向Blink)
			Webkit(小程序架构)					(苹果基于KHTML开发,开源,用于Safari,谷歌之前也有用)
				WebCore:负责解析、布局、渲染
				JsCore:解析、执行JS代码
			Blink								(Webkit的分支,谷歌开发,用于Chrome、Edge、Opera等)
			
js engine
		·Js引擎有哪些?
			Spider Monkey		(Brendan Eich开发)
			Chakra 				(微软,IE引擎)
			JavaScriptCore		(苹果开发,也是小程序引擎)
			V8					(谷歌开发)
V8
            C++编写、高性能
            可解析js、WebAssembly
            用于Chrome、Node
            多环境可运行(Win7、Mac10+、x64、IA-32、ARM、MIPS的Linux)
            可嵌入到任何C++程序

V8中的进程种类

  • 1 主进程 Broswer
  • 1 GPU进程 GPU
  • 1 网络进程 Network
  • N 渲染进程 Renderer
  • N 插件进程 Plugin

V8处理JS的过程

  1. 通过词法和语法分析,解析JS,转换为AST,用于解析器解析(Ignition)

    Blink JS源码->Stream语法转换->scaner词法分析->tokens->AST->PreParse (Recursion)(函数声明和函数调用分开,函数调用时进行全解析

  2. Ignition解析,以适应不同的CPU指令集,转换为对应的Byte

    中途对可优化部分标记,通过TurboFun对其直接转为二进制

HTML解析遇到JS时

JS代码通过JS引擎执行,遇到JS后优先解析JS

HTML->HTML parse + DOM => DOM Tree
CSS -> CSS parse => Style Rules
接下来进行attachment
(重排发生在这一阶段) DOM Tree + StyleRule -> Render Tree

(重绘发生在这一阶段) Render Tree + Layout => Paint => Render && display

内存管理

说说V8的内存机制?

V8的对象分配基于堆进行

空间大小限制:
	64位:1.4G
	32位:0.7G

区域不同:
	新生代
	老生代

V8为什么会限制内存使用的大小?

V8中,js对象通过堆内存进行分配,V8对堆内存可申请的大小做了限制
堆内存又分为新生代内存和老生代内存,其总和值就是v8的最大内存——64位是1.4G,32位是0.7G

表层原因:V8设计之处为浏览器服务,没有大内存场景的需要
深层原因:v8垃圾回收机制——垃圾回收的过程会使得js线程暂停

说说新生代和老生代的不同

GC算法不同
	新生代:以Scavenge算法为主
	老生代:以Mark-Sweep && Mark-Compact为主

子空间不同:
	新生代:基于Scavenge算法, 其内存分为From空间和To空间;即新生代的内存块由两个semi space构成
	老生代:一整块大内存

新生代内存如何变为老生代内存?这两种不同类型的内存如何联通?

两者是一个线性的关系
    内存申请->新生代->老生代->GC回收

由新生代到老生代的逻辑,称为晋升
具体规则,满足任意一条即可晋升:
	1. semi space From 对象是否经历过scavengeGC
	2. semi space To 占用空间超过25%

具体讲讲scavenge算法?

scavenge的核心是Cheney算法,其本质上是一种复制
流程:
	1. 将堆内存一分为二,分别为From空间和To空间,共两个semi space
	2. 只有一个被启用(From),另一个闲置(To)
	3. 分配对象时,先在From中分配
	4. GC时的机制:先检查From空间的存活对象,将其转移至To空间中;施放From中的非激活对象

	* 第四步完成后,其内容实际上发生了转变,所以本质上是一种复制

缺点:
	堆内存一分为二

老生代的算法讲一下?

老生代中的对象,基本为高频使用的对象,对其使用scavenge并不合适

老生代对象特点:
	1. 声明周期长
	2. 存活对象多

算法:
	1. 标记清除 Mark-Sweep
		两个阶段:标记阶段 & 清除阶段
		原理:遍历堆内存中的对象,标记出存活对象,清除非存活对象
		问题:内存碎片
		
	2. 标记整理 Mark-Compact
		出现原因:解决标记清除带来的内存碎片问题

标记清除真的合适在老生代内存中进行GC吗?

无论是scavenge、Mark-Sweep、Mark-Compact,其在执行时都会进行全停顿,阻塞逻辑执行

针对老生代中对象的特点,全堆垃圾回收(full垃圾回收)的标记、清理、整理等动作造成的停顿带来的影响会很大

所以产生了增量标记,通过将任务拆分为小阶段,初始化-标记-整理,减少停顿时间

V8逐渐转移到增量式处理,而非一次性处理,包括延迟清理(Lazy Sweep),增量式整理(Incremental Compact),后续也会引入并行处理

如何理解V8的垃圾回收和性能之间的关系?

V8的垃圾回收特点和JavaScript在单线程上的执行情况,垃圾回收是影响性能的因素之一

高性能需要让垃圾回收尽量少地进行,尤其是全堆垃圾回收。

如果内存已经超过1.4G了还想申请更多的内存如何做?

使用Buffer

1.4G只是V8的极限,不是Node的极限
Buffer申请内存不归V8管,受V8GC限制的主要是heap memory

讲一讲内存泄露?

垃圾堆叠——内存泄露本质上就是该被GC回收的内存没有被回收,成为了常驻内存的对象,即老生代对象

常驻内存的对象有哪些?

闭包作用域内的引用
全局变量引用

这两者都无法被直接垃圾回收

如何查看内存情况?

查看内存有两种,分别是查看Node进程内存以及查看OS内存
process.memoryUsage()
os.totalmem && os.freemem

讲一讲delete?除了delete之外,还有什么能达到同样的效果?

delete释放常驻内存的对象,在后续的垃圾回中能够被自动回收

delete的弊端:
	影响V8的性能

其它的方法——赋值删除
	通过赋值为undefined或者null来进行,避免了v8的性能影响的同时,实现同样的效果

repaint & reflow

重绘与重排-repaint & reflow

  • repaint: 重绘——绘制,针对外观
  • reflow: 重拍——排列,针对DOM

GC

  • 引用计数

    循环引用问题
  • 标记清除

    基于Root Object,对每个Root Object进行定期遍历并查找没有标记的对象,对其回收;Root Object的指定避免了循环引用

    V8 GC基于标记清除

标签语法

tag1: for(let i = 1; i <= 10; i++) {
            tag2: for (let t = 1; t <= 10; t++) {
                if(t % 2 ==0) {
                    console.log(i,t)
                }
                if(i + t > 16) {
                    break tag2;
                }
            }
        }

WebWorker

创造独立的二级子环境,不能与DOM等依赖单线程交互的API操作,但可以与父环境交换信息

特点

  • 底层以实际线程执行
  • 并行
  • 可用SharedArrayBuffer在多个环境共享信息
  • 没有window的概念,全局对象为WokerGlobalScope,以self关键字暴露
  • 创建性能开销大
  • 内存占用大

分类

  • 专用型:只能被创建它的页面使用

  • 共享型

    • 可以被多个不同的上下文使用,包括不同的页面
    • 与创建共享工作者线程的脚本同源的脚本,都可以向共享工作者线程发送消息或从中接收消息。
  • 服务型:拦截、重定向和修改页面发出的请求

琐碎知识点

valueOf返回原始值,如没有原始值则返回本身
toString返回字符串表示 (模板字符串、字符串拼接时会自动调用toString)
    [] {}的valueOf值为本身
    [] {}的toString值为"" [Object, Object]

URL一般格式
	protocol//host:port/path?search_condition

计算精度
    const a = 0.1
    const b = 0.2
    const c = 0.3
    b - a == a // true
    c - b == a // false
        0.1和0.2在转换为二进制时存在无限循环。观察可以看到实质上都是1100的无限循环,而0.1是三个0,0.2是两个0,就会导致最后一位四舍五入进位丢失精度。
		0.1在计算机中二进制表示为0.0001 1001 1001 ...
		0.2在计算机中二进制表示为0.0011 0011 0011 ...


parseFloat
	~('109283lsakdjf') 会自动处理前n个数字输出
	~('0xA') === 0     字符串,同上
	~('1214.213.1.41') 只解析第一个正确的浮点数格式 //1214.213
	~('000930424.241') 自动删除最前方的0 // 930424.241

isNaN
    isNaN(NaN) // true
    isNaN('...') // true
    isNaN('10') //false
    

数组
	数组创建可以直接一连串逗号 [,,,,,],几个逗号长度为几
	
正则
	[0-9-]和[0-9\-]是一样的,都会匹配到-
	
BOM
	location.host  			如果是80端口,默认不显示;其它的均显示host:port
	location.hostname		只显示host
	location.pathname		返回当前页面路径和文件名

原生JS禁用
	设置disabled attribute