前端面试 —— JavaScript基础篇

  在准备前端面试的过程中,发现前端的知识体系涉及的内容很广很多,在网上搜罗整理了一些前端面试的问题及答案,一方面是为了面试,另一方面是对很多基础和常用知识的复习。本篇是JavaScript基础篇
  传送门:
  前端面试 —— React篇

问题汇总

JS中基本数据类型和引用类型有哪些?

  JS中基本类型(值类型)有Number、Boolean、String、Undefined、Null、Symbol(ES6);引用类型有Object、Array、Function、Date、RegExp…

JS中基本数据类型和引用类型在内存上的区别?

  基本数据类型是存储在栈内存中的简单数据段,其变量和数据以及存储空间是一一对应的;而引用类型是存储在堆内存中的对象,通常来说可以多个引用类型变量指向同一存储空间块

null和undefined的区别?

  null表示的是一个空指针,也就是“无”的对象,转为数值是0;而undefined表示的是“无”的原始值,转为数值是NaN。当声明的变量还未被初始化时,默认值时undefined,而null通常来说是空的对象

如何判断一个对象是否为空对象?

  a. 利用for…in遍历对象属性,如果for…in执行,则非空对象;否则空对象
  b. 利用JSON.stringify()将检测对象转换成JSON字符串,如:JSON.stringify(target) === ‘{}’
  c. 利用ES6新增的Object.keys()方法,如果目标是空对象,该方法会返回一个空数组

原型规则和原型链?

  a. 所有的引用类型(数组、对象、函数),都具有对象特性,即可自由扩展属性(除了”null”以外)
  b. 所有的引用类型(数组、对象、函数),都有一个__proto__(隐式原型)属性,属性值是一个普通的对象
  c. 所有的函数都有一个prototype(显式原型)属性,属性值是一个普通的对象
  d. 所有的引用类型(数组、对象、函数),__proto__属性指向(===)它的构造函数的prototype属性
  e. 当试图得到一个对象(引用类型)的某个属性或方法时,如果这个对象本身没有,那么会去proto(对应构造函数的prototype)中去寻找;如果依然不存在,则会去构造函数上一层的prototype继续查找,直到找到目标属性/方法,或者到该条链路径的顶点,这条查找的链路径就是原型链

typeof、instanceof和valueOf区别?

  typeof操作符返回字符串,表示未经求值的操作数的类型,返回值为number、boolean、string、undefined、object、function、symbol(ES6)
  A instanceof B用来判断A是否是构造函数B的一个实例或者其子类的一个实例对象
  valueOf其实就是返回对象的原始值

instanceof的原理?

  判断某个构造函数的显式原型(prototype)属性所指向的对象是否存在于要检测对象的原型链上

new操作符的原理?

  a. 生成一个新的对象,并将this指向这个对象
  b. 执行构造函数代码,并根据传入参数对this的相应属性进行赋值
  c. 默认将this对象返回(因此无需显式调用return语句),并赋值给对应的实例变量

原型链的封装和继承?

  原型链的封装和继承其实就是将实例具有共同属性和方法绑定在其构造函数的prototype上,这样可以有效地避免在生成实例时,重复开辟很多相同属性的内存空间,转而用原型链继承的方式传递给实例的proto属性,因此实例可以使用这些属性和方法

正则表达式?

  考察正则一般会根据要求写出相应表达式 - 练习传送门

阻止冒泡的方法?阻止默认事件的方法?

  阻止冒泡:考虑浏览器的兼容性问题,通常是将event.stopPropagation()和event.cancelBubble = true组合使用。
  阻止默认事件:同样地,event.preventDefault()和event.returnValue = false;

JS的垃圾回收机制(GC)?

  最常用的方法是“引用计数”:语言引擎有一张“引用表”,保存了内存中所有资源(通常是各种值)的引用次数。当一个值的引用次数为0,表示这个值不再使用了,然后可以将这块内存释放。也有特殊情况存在,就是值不再需要了,但是引用数却不是0,因此GC无法释放这块内存,导致内存泄漏
  第二种是标记清除,当变量进入执行环境标记为“进入环境”,当变量离开执行环境时标记为“离开环境”,标记为“进入环境”的是不能被回收的,而“离开环境”可以被回收
  详细请见https://juejin.im/post/5b684f30f265da0f9f4e87cf

如何判断一个变量是Array类型?

  首先,typeof操作符是不能区分对象(object)和数组(array)的,都会返回”object”,因此可用以下三种方法来判断Array类型:
  a. Array.isArray(arr)
  b. arr instanceof Array
  c. Object.prototype.toString.call(arr) === ‘[object Array]’

谈一谈闭包?

  闭包就是能够读取其他函数内部变量的函数,在JS中,只有函数内部的子函数才能读取其内部变量,因此闭包简单说也可以是定义在函数内部的函数。本质上,闭包是将函数内部和外部连接起来的桥梁;当某个函数的内部子函数访问该函数的内部变量时,这个子函数就构成了闭包。闭包可以用来封装变量,收敛权限。

1
2
3
4
5
6
7
8
9
function getList() {
var _list = [1, 2, 3, 4];
return function(index) {
console.log(_list[index]);
}
}

var getElem = getList();
getElem(1);

  闭包的三个特性:1. 函数嵌套函数;2. 内部函数使用外部函数的参数和变量;3. 外部的参数和变量不会被GC回收
  闭包的缺点:1. 常驻内存,增加了内存使用量;2. 使用不当造成内存泄漏

call、apply、bind区别?

  call和apply都是将函数的this对象绑定到传入的第一个参数对象上,而bind是返回一个新函数,然后将this设置为第一个参数,并接受额外参数;call和apply的区别是参数接收形式,apply是参数数组,call是参数列表

this的使用场景?

  this要在执行时才能确认值,定义时无法确认:a. 作为构造函数执行; b. 作为对象属性执行; c. 作为普通函数执行; d. call、apply、bind; e. 箭头函数中的this(ES6)

创建对象的多种方式?

  a. 工厂模式:定义一个函数,在函数内部创建Object对象,然后增加需要的属性(值/函数),返回这个对象。缺点:对象无法识别,因为所有实例都指向一个原型
  b. 构造函数模式:利用构造函数来创建对象。优点:实例可以为一个特定类型,缺点:耗内存,每个实例,都需要创建所有属性和方法。
  c. 原型模式:利用原型链,在构造函数的prototype上加入需要的方法和属性。优点:节省内存,不用反复创建方法,缺点:所有属性和方法都是共享的,无法初始化参数
  d. 组合模式:结合b和c,共享的属性和方法使用原型模式,然后利用构造函数模式初始化独有参数。优点:该共享共享,该私有私有,最广泛的创建方式

实现继承的多种方式和优缺点?

  a. 原型链继承:类型的所有属性都被实例共享;2. 创建子类实例的时候,无法向父类传参。
  b. 构造函数继承(经典继承):在子类型中调用父类型的构造函数并用call改变this指向,优点:避免了类型的属性被所有实例共享;2. 可以向父类型传递参数;缺点:耗内存,新建一个实例,就会新创建所有的方法和属性,不管是否是相同的。
  c. 组合继承:a和b混用。缺点是会调用父构造函数两次
  d. 原型式继承:模拟Object.create()的实现,将传入参数作为对象的原型。缺点:共享
  e. 寄生式继承:创建一个仅用于封装继承过程的函数,函数内部以某种形式增强对象,最后返回对象。跟b缺点一样
  f. 寄生组合式继承:将组合继承的两次父构造函数调用,减少为一次
  详细请见https://juejin.im/post/5d28374951882564ca686ef2

匿名函数的应用场景?

  a. 闭包; b. 自执行函数(匿名函数); c. 回调函数

attribute和property的区别?

  attribute是指HTML元素上的属性,使用setAttribute()和getAttribute()来设置和获取;而property是JS对象的属性(获取的DOM节点在JS中是一个对象),设置和获取与常规JS对象属性无异

window.onload和document.DOMContentLoaded两个事件的区别?

  window.onload在页面的全部资源(包括样式表、图片、音频、子框架…)加载完后执行;而DOMContentLoaded是在HTML文档加载和解析后执行,即DOM渲染完执行,不需要等待其他资源加载
  DOM完整解析过程https://www.jianshu.com/p/1a8a7e698447

== 和 === 的区别?

  == 是弱相等,会先进行强制类型转换,然后进行值比较;=== 是严格相等,会比较两个变量的类型和值

[], [] === [], [] == []?

  空数组[]在JS中也是一个object,因此用在判断条件时会转换成true;但是任意值和布尔值比较时,都会将两边的值转换成Number: [] -> 0, false -> 0, true -> 1
  [] == [] 和 [] === [] 都是false,因为 [] 属于引用类型,引用类型的比较是需要比较两个引用值在内存中是否指向同一对象,两个空数组互不相关,因此都是false

undefined == undefined, undefined === undefined?

  undefined == undefined, undefined === undefined 都是true,因为undefined在JS是基本类型,也就是值类型,且只有一个值,所以无论是值和类型都是相等的。

JS的作用域有几种?{}是不是作用域?

  通常来说,作用域被分为全局作用域和局部作用域,全局作用域就是在代码块之外的部分,而局部作用域在ES6之前是只有函数作用域一种的,而且在函数作用域中声明变量时,必须使用var关键字,否则会因为变量提升成为全局变量。ES6,引入了let和const,拥有块级作用域(局部作用域)了,因此{}代码块是作用域(ES6),而ES5中只有{}是函数的时候才算做作用域

DOM事件绑定的几种方式(尤其是DOM0和DOM2)?

  a. DOM元素中直接绑定;b. 在JS代码中使用属性绑定(DOM0级绑定);c. 绑定事件监听函数(DOM2级绑定)
  DOM0级事件处理程序(属性绑定,兼容性好):将一个函数赋值给一个事件处理程序属性。特点:简单,跨浏览器。例如:btn.onclick = function() {xxx}, btn.onclick = null
  DOM2级事件处理程序(函数绑定,兼容性不好):使用addEventListener()和removeEventListener()来绑定和解绑事件的,可以处理多个事件处理程序,并按照顺序触发,移除事件和绑定事件传入的参数相同,绑定事件时使用匿名函数的话将无法移除
  DOM0级事件会覆盖,DOM2不会覆盖,会依次执行,DOM0级和DOM2级可以共存,不会相互覆盖

DOM事件中target和currentTarget的区别?

  事件中target是一个触发事件的对象的引用,当事件处理程序在事件的冒泡或捕获阶段被调用;而currentTarget指的是当事件遍历DOM时,标识事件的当前目标
  简言之,e.target指向触发事件监听的对象;e.currentTarget指向添加事件监听的对象。利用这两个特性,可以实现事件代理

JS的事件流模型?

  事件流描述的是从页面中接受事件的顺序
  冒泡事件流:从事件开始的具体元素,一层层向上传播直到window
  捕获事件流:捕获的顺序与冒泡刚好相反,是在事件到达目标之前捕获它,而最具体的节点是最后才接收到事件的
  DOM事件流:DOM2级规定的事件流包括三个阶段 - 事件捕获阶段、处于目标阶段、事件冒泡阶段。即使DOM2级规定在捕获阶段不会涉及事件目标,但是由于浏览器支持,所以有两个机会在目标对象上操作事件

普通函数和构造函数的区别?

  a. 构造函数使用new关键字调用,而普通函数不需要
  b. 构造函数内部可以使用this关键字,普通函数内部不建议使用this关键字,因为这时候this指向window全局对象,会无意间增加一些全局变量或函数
  c. 构造函数首字母建议大写;普通函数首字母建议小写
  d. 构造函数默认会返回this对象,不用显式return,而普通函数要想返回值需要return

给定一个元素获取其相对于视图窗口的坐标?

  获取视口坐标可以通过调用元素的getBoundingClientRect()方法,方法返回的一个有left、right、top、bottom属性的对象,分别表示元素四个位置相对于视口的坐标。该方法返回的坐标包含元素的内边距和边框,不包含外边距。

JS如何实现重载和多态?

  因为JS本身不支持重载的,所以有:a. 根据arguments个数实现重载,arguments检测传参的个数,然后在执行不同的方式;b. 检测数据类型实现重载,根据传参的数据类型,调用不同的方式,用typeof检测
  多态:利用构造函数的prototype属性,来实现多态,代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
function makeSound(animal) {
animal.sound();
}

function Duck() {}
function Chicken() {}
Duck.prototype.sound = function() {console.log('gagaga');}
Chicken.prototype.sound = function() {console.log('gegege');}

var d = new Duck();
var c = new Chicken();
makeSound(d);
makeSound(c);

内存泄漏的原因和场景?

  内存泄漏就是不再被需要的内存,由于某种原因,无法释放
  a. 全局变量造成内存泄漏
  b. 闭包造成内存泄漏
  c. 未销毁的定时器和回调函数造成内存泄漏
  d. DOM引用造成内存泄漏

JS中的事件循环?

  事件循环机制 = JS异步执行机制

for…in和forEach的使用场景?

  forEach循环缺点就是中途无法跳出,但是在多数情况下是遍历数组的首选
  for…in遍历数组的缺点:
  a. 数组键名通常是数字,而for…in是以字符串作为键名的,如果使用数组的index参与了运算,可能会得到预期不符的结果
  b. for…in不仅会遍历数组的键名,还会遍历其他手动添加的键,甚至原型链上的键
  c. 某些情况下,for…in循环会以任意顺序遍历键名
  总之,for…in适合遍历对象,而非数组

手指点击触控屏是什么事件?

  触摸事件:touchstart, touchmove, touchend (最常用的前三个), touchcancel

文章引用和推荐

  1. 面试中会遇到的正则题 来自呆头呆脑丶的掘金
  2. javascript垃圾回收机制 来自李赫feixuan的掘金
  3. JavaScript之继承的多种方式和优缺点 来自老詹啊的掘金
  4. window.onload和DOMContentLoaded 的区别 来自初入前端的小菜鸟的简书
  5. 面试之万能答案:事件循环 来自蔓越莓的掘金
  6. 移动端手势库设计与实践 来自连城的掘金
  7. 你真的理解事件冒泡和事件捕获吗? 来自Coderfei的掘金
------------------------ The End ------------------------

本文标题:前端面试 —— JavaScript基础篇

文章作者:Lu, Ruihui

发布时间:2020年07月23日 - 18:21:52

最后更新:2021年04月13日 - 19:07:59

原始链接:https://github.com/cs-lurh0826/cs-lurh0826.github.io/Interview-Questions/frontend-interview-js/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

一花一世界,一叶一追寻