JavaScript 基础 细节

148 阅读5分钟
var a = [];
for(var i = 0; i < 10; i++) {
  a[i] = function() {
    console.log(i)
  }
}
a[6]()
  • 最终执行结果是 10
  • 变量 i 是由 var 声明的,其所在的作用域是全局作用域,所以循环结束之后,i 的值为 10
  • 后续通过执行全局下生成的函数 a[6],由于当前函数执行时创建的私有执行上下文没有变量i,其向作用域链中的上一层作用域即全局作用域寻找 i, 所以获得的值 为 10  
var tmp = 123;
if (true) {
  console.log(tmp);
  let tmp;
}
  • 最终执行结果为 Uncaught ReferenceError: Cannot access 'tmp' before initialization at <anonymous>
  • 由于在if 语句的块作用域中 存在使用 let 声明的 tmp, 此时,在这个块级作用域中存在 暂存性死区,所以当在 变量tmp 声明定义之前访问 变量tmp时,尽管全局作用域中存在 tmp 这个变量,其也是访问不到全局作用域的 tmp, 而是报错
  • 可以认为在if 语句块中,tmp已经在词法环境中被创建了(存在提升,但不同于 var 提升的同时被初始化为 undefined),但是还没有到达它的初始化(这是语句本身的一部分)这个区间就是暂存性死区

 

  结合ES6语法,用最简单的方式找出数组中的最小值

var arr = [12, 34, 32, 89, 4]
let min = arr.sort((a, b) => a - b)[0]

 

请详细说明var、let、const三种声明变量的方式之间的具体差别

  • var 声明提升,初始化为 undefined
  • let 仅声明提升,未初始化
  • const 仅声明提升,未初始化
  • var 作用于全局作用域或者函数作用域
  • let 作用于块级作用域
  • const 必须在声明时初始化
  • var 可以仅声明不初始化
  • let 可以仅声明不初始化
  • const 作用于块级作用域
  • var 可以重复定义
  • let 不可以重复定义
  • const 不可以重复定义
  • var 可以多次赋值
  • let 可以多次赋值
  • const 基本数据类型不可以重新赋值,引用数据类型仅可以改变值
  • var 可以在声明前访问
  • let 不可以在声明前访问
  • const 不可以在声明前访问

请说出下列代码最终输出结果,并解释为什么?

var a = 10;
var obj = {
  a: 20,
  fn() {
    setTimeout(() => {
      console.log(this.a)
    })
  }
}
obj.fn()
  • 最终输出 20
  • obj调用fn函数时,fn中的 this 指向 obj,定时器为箭头函数,this指向最近的函数 this 指向,所以 this 也指向 obj,所以输出 20

 

简述Symbol类型的用途

  • Symbol 最主要的作用就是为对象添加独一无二的属性名
  • 定义私有属性,外部无法进行访问,只能通过类中的方法进行访问。

 

说说什么是浅拷贝,什么是深拷贝?

  • 浅拷贝:只拷贝第一层的原始类型值,和第一层的引用类型地址。
  • 深拷贝:拷贝所有的属性值,以及属性地址指向的值的内存空间,拷贝的对象和被拷贝对象互不影响

 

请简述TypeScript与JavaScript之间的关系?

  • TypeScriptJavaScript 的超集
  • JavaScript + ES6 + 类型系统 = TypeScript (最终编译成 JavaScript)
  • 任何一种 JavaScript 运行环境都支持使用 TypeScript 

 

请谈谈你所认为的typescript优缺点

优点

  • 增强代码的可读性和可维护性
  • 在编译时即可发现大部分的错误,增强编辑器的功能
  • 包容性,js文件可以直接改成 ts 文件,不定义类型可以使用 TypeScript 隐式类型推断,可以定义几乎一切类型,ts 编译报错时也可以生成 js 文件,兼容第三方库,即使不是用ts编写的
  • 有活跃的社区,大多数的第三方库都可提供给 ts 的类型定义文件,完全支持 es6 规范

缺点

  • 短期增加开发成本,增加类型定义,但减少维护成本
  • ts 集成到构建流程需要一定的工作量
  • 和有些库结合时不是很完美(需要自己手动输出类型定义文件)

描述引用计数的工作原理和优缺点

引用计数

引用计数的核心思想就是设置引用次数,判断当前引用次数是否为0,当引用数字为 0 立即回收

优点

  • 发现垃圾立即回收
  • 最大限度减少程序的暂停

缺点

  • 无法回收循环引用的对象
  • 时间开销大(监控对象的修改耗时)
  • 需要维护表来存储引用数,如果引用数过多,则会带来一定的损耗  

 

描述标记整理算法的工作流程

  • 标记整理算法可以看作标记清除的增强,分为3个阶段:标记、清除、整理
  • 标记阶段,collectormutator 根对象开始进行遍历,从根上可以访问到的对象都打上一个标识,将其记录为 可达对象
  • 清除阶段,collector 对堆内存从头到尾进行线性的遍历,如果没有标记为可达对象,则将对象的地址进行移动,使其在地址上连续,然后回收,避免空间碎片化

 

描述V8中新生代存储区垃圾回收的流程

  • 新生代内存区分为两个等大小空间
  • 使用空间为 From, 空闲空间为 To
  • 活动对象存储于 From 空间中
  • 标记整理后将活动对象拷贝至 To
  • 最后 FromTo 交换空间完成释放

 

描述增量标记算法在何时使用及工作原理

  • 程序执行的过程中,不进行垃圾回收
  • 当GC要工作的时候,程序会停下来,首先遍历对象进行标记
  • 但是这个标记和标记清除的标记有所不同,增量标记算法中的标记的过程并不是连贯的
  • 它会将标记的过程拆分为多个小的过程分开进行标记
  • 这样子就不会因为一次性标记太多可达对象造成js 执行卡顿
  • 虽然这样子程序执行和垃圾回收存在多次交替工作,但是 V8引擎 达到最大垃圾存储的约1.5G时,采用非增量标记的垃圾回收形式,时间也没有超过一秒钟
  • 所以这个过程中的交替间断时间是合理的,而且将原本需要停止很长的一段时间,拆分成很小的时间块,这样子对用户更加友好