知识目录
- 简答题
- 一、请说出下列最终的执行结果,并解释为什么?
- 二、请说出下列最终的执行结果
- 三、 结合ES6新语法,用最简单的方式找出数组中的最小值
- 四、请详细说明 var、 let 、const、三种声明变量的方式之间具体差别
- 五、请说出下列代码最终输出的结果,并解释为什么?
- 六、 简述 symbol 类型的用途
- 七、 说说什么是浅拷贝,什么是深拷贝?
- 八、 请简述 Typescript 与 Javascript 之间的关系。
- 九、 请谈谈你所认为的Typescript 优缺点。
- 十、描述引用计数的工作原理和优缺点
- 十一、 描述标记整理算法的工作流程
- 十二、描述V8中新生代存储区垃圾回收的流程
- 十三、 描述增量标记算法在何时使用及工作原理
简答题
一、请说出下列最终的执行结果,并解释为什么?
var a = []
for(var i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 打印结果: 10
答案: 此题用var 定义变量 i 相当于
var a = []
var i;
for(i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]()
由于此时的 i 是全局变量,当循环运行完成时 i 已经变成了10,那么在windows 下打印 i 就会是10;
如果此题变成
var a = []
for(let i = 0; i < 10; i++) {
a[i] = function() {
console.log(i)
}
}
a[6]() // 打印结果: 6
二、请说出下列最终的执行结果
var tmp = 123;
if (true) {
console.log(tmp)
let tmp;
}
// Uncaught ReferenceError: tmp is not defined
答案:此时打印会报错 tmp is not defined, 因为 let 不会让变量提升,只能先定义在使用,而且 let 定义的变量会形成一个局部作用域,外面也是拿不到的
三、 结合ES6新语法,用最简单的方式找出数组中的最小值
// var arr = [12,34,32,89,4]
// console.log(Math.min(...arr))
四、请详细说明 var、 let 、const、三种声明变量的方式之间具体差别
答案:
- var 全局声明变量,存在声明提升;
- let 可全局声明变量,也可局部声明变量,不存在声明提升;
- const 常量声明,不存在声明提升,并且定义后不可改变(严格来说:基本数据类型不可以改变值,引用数据类型仅可改变其类型)
var | let | const | |
---|---|---|---|
语法 | var a = 10 | let a = 10 | const a = 10 |
提升 | 声明提升, 使用undefined初始化 | 声明不提升 | 声明不提升 |
作用域 | 全局作用域 | 块级作用域 | 块级作用域 |
初始化 | 可以仅声明不初始化 | 可以仅声明不初始化 | 必须在声明时初始化 |
重复声明 | 可以 | 不可以 | 不可以 |
多次赋值 | 可以 | 可以 | 基本数据类型不可以,引用数据类型尽可改变其值 |
声明前访问 | 可以 | 不可以 | 不可以 |
五、请说出下列代码最终输出的结果,并解释为什么?
var a= 10;
var obj = {
a: 20,
fn () {
setTimeout(() => {
console.log(this.a);
})
}
}
obj.fn() // 20
答案: 控制台打印20,原因: 谁调用 this 就指向谁
六、 简述 symbol 类型的用途
答案: symbol 是一种全新的基本数据类型。定义方法 :直接使用 Symbol() 创建新的symbol类型,并可以用一个可选的字符串作为其描述。
const sym1 = symbol()
const sym2 = symbol()
每个从 Symbol() 返回的 symbol 值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。
借助symbol值是唯一的,我们还可以给对象创建似有成员
```javascript
const name = Symbol()
const person = {
[name]: '张三',
say(){
console.log(this[name])
}
}
// let naMe = person[Symbol()] //undefined
person.say()//张三
由于不能直接调用对象中的symbol属性,所以创建了所谓的私有成员。
总体来说,目前symbol的主要作用就是为对象添加独一无二的属性标识符;
七、 说说什么是浅拷贝,什么是深拷贝?
答案:
- 浅拷贝: 是对内存地址的复制,如果是引用类型,则是让目标对象的指针和源对象指向同一个内存空间,也可以理解为拷贝的是指针。
结合下面代码理解:
// 浅拷贝
let a = {name: 'zhangsan'}
let b = a
console.log('b',b); // b {name: "zhangsan"}
delete b.name
console.log(b,b.name); //{} undefined
console.log(a,a.name); //{} undefined
- 深拷贝: 是拷贝对象的具体内容或者为拷贝的是内存地址。拷贝结束完成后,两个对象虽然值是一样的,但是两者互不影响。
结合下面代码理解:
var a = {name: 'zhangsan'}
var b = JSON.parse(JSON.stringify(a))
console.log(b,b.name); // {name: "zhangsan"} "zhangsan"
delete a.name
console.log(b,b.name); // {name: "zhangsan"} "zhangsan"
console.log(a,a.name); // {} undefined
//深拷贝的另一种方法
var a = {name: 'zhangsan'}
var b = Object.assign({},a)
console.log('b',b); //b {name: "zhangsan"}
b.name = 'lisi'
console.log(a,a.name); //{name: "zhangsan"} "zhangsan"
console.log(b,b.name); //{name: "lisi"} "lisi"
由此可见,对于引用类型而言,浅拷贝只是拷贝了“指针”,而深拷贝则是开辟了另一块内存空间。
八、 请简述 Typescript 与 Javascript 之间的关系。
答案:有人说TypeScript 是JavaScript 的一个超集,这种说法也不为过。因为Typescript可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。
九、 请谈谈你所认为的Typescript 优缺点。
TypeScript 优点
- 增强代码的可读性和可维护性,强类型的系统相当于最好的文档,在编译时即可发现大部分的错误,增强编辑器的功能。
- 包容性,js文件可以直接改成 ts 文件,不定义类型可自动推论类型,可以定义几乎一切类型,ts 编译报错时也可以生成 js 文件,兼容第三方库,即使不是用ts编写的
- 有活跃的社区,大多数的第三方库都可提供给 ts 的类型定义文件,完全支持 es6 规范
Typescript 缺点
- 增加学习成本,需要理解接口(Interfaces)和泛型(Generics),类(class),枚举类型(Enums)
- 短期项目增加开发成本,增加类型定义,但减少后期维护成本
- ts 集成到构建流程需要一定的工作量
- 目前和有些库结合时不是很完美
十、描述引用计数的工作原理和优缺点
引用计数算法核心思想: 设置引用数,判断当前引用数是否为0
当引用关系改变时修改引用数字,当引用数字为0时空间立即被回收
引用计数的优点:
-
发现垃圾时立即回收
只要发现被引用计数为0时立即回收内存 -
最大限度减少程序暂停
只要程序在运行时就要去消耗内存,而引用计数算法,只要发现某一块内存的引用数为0时就会立即回收,这样大大降低了内存爆满现象,从而减少程序卡顿暂停的说法。
引用计数的缺点:
- 无法回收循环引用的对象
十一、 描述标记整理算法的工作流程
标记整理算法核心思想:分标记、整理和清除三个阶段完成
- 遍历所有对象找到并标记活动(可达)对象
- 遍历所有对象,将活动对象整理为连续内存,再将无标记对象(即将被清除的对象整理为连续内存)整理到一起,成为连续内存。
- 清除无标记对象
配合图例理解
十二、描述V8中新生代存储区垃圾回收的流程
首先结合图例看一下 V8中内存的分配
其实 V8将内存一分为二,左侧白色区域为新生代存储区,右侧红色区域为老生代存储区;
新生代存储区用于存储新生代对象,新生代对象指的是存活时间较短的对象(局部作用域下的变量对象)。新生代存储区大小一般为32M【64位系统】 | 16M【32位系统】。
了解完什么是新生代对象,我们就说一下新生代对象回收是如何实现的
- 回收过程采用复制算法 + 标记整理算法
- 新生代内存区分为两个等大小空间
- 使用空间为From, 空闲空间为To
- 活动对象存储于From 空间
- 标记整理后将活动对象拷贝至To
- From 与 To 交换空间 完成释放
总的来说,新生代对象空间也是一分为二的,新生代对象回收过程采用复制算法 + 标记整理算法,当活动区(From)标记整理完成之后将活动对象复制到 To 空间,之后完全释放From 空间,再将From 与 To 空间交换,原来的From变成To,原来的To变成From ,完成空间释放和回收操作。
十三、 描述增量标记算法在何时使用及工作原理
标记增量,就是把一整段的垃圾回收操作,拆分成多个小步,整合着完成垃圾回收,从而去替代一口气做完的垃圾回收操作。这样的好处主要就是垃圾回收和程序执行是交替着进行工作,这样时间消耗更合理一些。
可参照图例
当程序执行到一个阶段,由于是在老生代区域操作会由循环遍历对象进行标记,然后程序会继续执行,接着再进行标记,直到程序把垃圾对象都标记完,然后进行清除,当然这不是一次标记完成的。
当老生代区域内存达到1.5G 时V8就会触发最大的垃圾回收即非增量垃圾回收,时间也不会超过1s 钟,所以看似程序停顿很多次,其实不影响用户使用感官的。