JavaScript相关面试题
本文最后更新于 2024-11-04,文章内容可能已经过时。
JavaScript
浏览器工作原理
浏览器由两部分组成:
浏览器内核
,也称为渲染引擎
,渲染引擎主要负责html 页面结构布局和样式渲染
!浏览器引擎
,也是js引擎
主要负责js解析和执行
!- 当用户向服务器
发送url请求
时,浏览器会进行 dns 解析
,获取 ip 地址
,然后向服务器发送请求
,获取html结构内容
,然后进行html结构解析和样式渲染
!
浏览器渲染过程:
html
和style
样式解析,分别生成dom tree
和style 样式规则
,之后会生成渲染树
进行布局
和绘制
最终显示渲染内容
!
js引擎
:js 引擎
主要负责js 代码解析和执行
,会将js源码
通过词法解析 和 语法分析
生成抽象语法树ast
然后在转换成字节码bytecode
最后进行代码执行
!
JS执行上下文
执行上下有两种,一种是
全局执行上下文
, 一种是函数执行上下文
!
全局执行上下文
: 在代码解析的时候会将全局变量和函数存放到全局上下文GO对象内中
!函数执行上下文
:函数执行
的时候会创建一个新的执行上下文AO对象
,并将函数的参数和变量存放到函数上下文中
!
全局执行上下文
: 全局代码执行前,创建一个全局执行上下文,将全局代码中的变量和函数声明添加到全局执行上下文中。函数执行上下文
: 函数执行前,创建一个函数执行上下文,将函数的参数和变量声明添加到函数执行上下文中。执行栈
: 执行栈是JS引擎
的内存管理单元,用于存储执行上下文
,先进后出
。作用域
: 函数执行上下文创建
后,会创建一个作用域链
,作用域链指向
函数执行上下文和全局执行上下文。this
:this
指针在函数执行过程中,会根据函数的调用方式,指向不同的对象。原型
:原型
是JavaScript
中所有对象
的基础
,每个函数
都有一个原型
,原型
可以理解为函数的模板
,实例对象
会继承原型
上的属性
和方法
。
作用域与作用域链
作用域
作用域主要分为
局部作用域
和全局作用域
,通常是指变量的可访问范围
。
局部作用域
: 指在函数
或块
中定义的变量
,只能在该函数或块中访问
。全局作用域
: 指在函数外部定义的变量
,可以在整个程序范围内
访问。
作用域链
当我们试图在
函数内部访问一个变量时
,若函数体内没有该变量
,则会向上级作用域进行查找
,直到找到该变量或到达全局作用域为止
。
内存管理
垃圾回收机制
垃圾回收机制是指
自动释放不再使用的内存
,主要分为手动回收
和自动回收
两种。
手动回收
: 程序员手动调用垃圾回收函数
进行回收。自动回收
: 程序运行时,自动检测
并回收不再使用的内存
。
内存泄漏
内存泄漏
指程序运行过程中
由于对象引用的存在导致无法被回收
因此内存会占用过多
,导致系统无法正常运行
,甚至崩溃
。
全局变量
: 全局变量在程序运行过程中
一直存在,导致内存泄漏。监听器
: 监听器未及时移除
,导致内存泄漏。临时变量
: 临时变量未及时清除
,导致内存泄漏。闭包
: 闭包未及时释放
,导致内存泄漏。
闭包
- 闭包是指
内部函数访问外部函数自由变量时形成的一个闭包空间
,当外部函数执行完被销毁
时依然可访问外部函数所定义的变量
!原因:
内部函数作用域指向了父级作用域
,导致父级作用域的变量无法被释放
!
this指向
this
关键字通常用在函数内部
,用来指定当前对象的引用
。
this
是动态绑定
,根据调用方式
,this
会指向不同的对象。
this
绑定分为四种
:默认绑定
隐式绑定
显示绑定
new 绑定
!
默认绑定
:全局作用域
下的函数,this
指向window
对象。
隐式绑定
: 函数作为对象的方法调用
,this
指向该对象
。
显示绑定
: 通过apply()
、call()
、bind()
方法,this
指向第一个参数
。
new 绑定
:new
关键字,将函数
的this
绑定到新创建的对象上
。
new 关键字
new
关键字用来创建对象
,并执行构造函数
,返回一个实例对象
。
new
关键字的过程:- 创建一个
空对象
; - 将
空对象的隐式原型
被赋值与构造函数的原型
; - 将
函数中的this绑定到新创建的空对象上
; - 执行函数体
- 如果函数体内没有返回值时,会返回
this
对象本身
- 创建一个
原型链
原型链
是JavaScript
中对象
和函数
之间一种引用关系
,它通过原型链
实现对象之间的属性继承
。
当试图在一个对象上访问一个属性时,这个属性不存在则会去对象的原型上查找,如果原型上也不存在则会继续查找原型的原型,直到找到原型链的顶端返回为null时
!`
原型链
的顶端
是Object.prototype
,Object.prototype
的原型
指向null
。构造函数
的原型
指向实例对象
,实例对象
的原型
指向构造函数的原型
。实例对象
的属性
和方法
都来源于原型链
。
apply()、call()、bind()方法
apply()
和call()
方法是函数
的方法
,用于改变函数
的this
指向,apply()
和call()
的区别在于参数传递
。
apply()
:apply()
方法接收两个参数,第一个参数是this
要指向的对象,第二个参数是数组
,数组中的元素作为函数的参数。call()
:call()
方法接收多个参数,第一个参数是this
要指向的对象,后面的参数作为函数的参数。bind()
:bind()
方法接收多个参数,第一个参数是this
要指向的对象,后面的参数作为函数的参数,返回一个新函数
,新函数的this
指向第一个参数。
JS 中是如何实现继承的
原型链继承
: 将子类的原型
指向父类的构造函数
,这样子类就可以继承父类的属性和方法
。
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child('Tom', 18);
console.log(child1.name); // Tom
console.log(child1.age); // 18
构造函数继承
:子类构造函数中调用父类构造函数
,这样子类就可以继承父类的属性和方法
。
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
var child1 = new Child('Tom', 18);
console.log(child1.name); // Tom
console.log(child1.age); // 18
组合继承
: 组合继承是将原型链继承和构造函数继承的一种组合,通过将父类的实例作为子类的原型,这样子类就可以继承父类的属性和方法。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('Tom', 18);
child1.sayName(); // Tom
寄生组合继承
: 寄生组合继承是通过组合继承
的方式来实现的,但是在组合继承的基础上
添加了对原型的继承
。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log(this.name);
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child1 = new Child('Tom', 18);
child1.sayName(); // Tom
数组去重的方式
indexOf()
: 遍历数组,判断当前元素是否在新数组中存在,不存在则添加。
function unique(arr) {
var newArr = [];
for (var i = 0; i < arr.length; i++) {
if (newArr.indexOf(arr[i]) === -1) {
newArr.push(arr[i]);
}
}
return newArr;
}
filter()
: 遍历数组,判断当前元素值的索引位置,indexOf()
只会返回第一次出现
的索引位置!
function unique(arr) {
return arr.filter(function(item, index, self) {
return self.indexOf(item) === index;
});
}
new Set()
:Set
是一个ES6
新增的数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的值。
const unique = arr => [...new Set(arr)];
类型判断
typeof
:typeof
用于判断基本数据类型
,返回值为string
。instanceof
:instanceof
用于判断构造函数是否存在函数原型上prototype
Object.prototype.toString.call()
:Object.prototype.toString.call()
方法可以获取对象类型
,返回值为string
。Object.prototype.isPrototypeOf()
:Object.prototype.isPrototypeOf()
方法可以判断一个对象是否存在另一个对象的原型链上
,返回值为boolean
。contructor
:("abc").constructor === String
:constructor
属性可以获取对象的构造函数
。
JavaScript由哪三部分组成
ECMAScript
:ECMA
是javascript
实现的一种标准
,定义了js基本语法
和基本对象
。DOM(文档对象模型)
: 在HTML
中,所有的元素都是DOM文档
中的一部分!BOM(浏览器对象模型)
:BOM
是一组包含对浏览器操作的api
,比如:本地存储
定时器
导航
location
等相关api。
JS中有哪些内置对象
内置对象
是指JavaScript
语言本身提供的一些预定义对象
,比如Math
Date
String
等。
常用的内置对象有
Math
: 包含了数学相关的函数
和常量
。Date
: 用于处理日期和时间
。String
: 用于处理字符串
。Array
: 用于处理数组
。Object
: 用于处理对象
。
操作数组方法的有哪些
push()
: 向数组的末尾添加
一个或多个元素
,并返回新的长度
。pop()
:删除
并返回数组的最后一个元素
。shift()
:删除
并返回数组的第一个元素
。unshift()
: 在数组的开头
添加一个或多个元素
,并返回新的长度
。splice()
: 向数组中添加/删除项目
,并返回被删除的项目
。slice()
: 根据索引截取并返回一个新的数组
,包含从开始索引到结束索引(不包括结束索引)
的原数组的元素。concat()
: 将两个数组进行合并返回一个新的数组
,包含两个或多个数组的元素
。map()
:创建
一个新数组
,新数组中的内容
,回调函数中返回的结果
!filter()
:创建
一个新数组
,返回一个结果为true
的元素组成的数组。reduce()
: 对数组中的元素进行累计操作,最终返回一个单一值。forEach()
: 为数组中的每个元素调用一次提供的函数。isArray()
:判断一个变量是否为数组。some()
: 检查数组中是否有元素
满足回调函数
的条件。every()
: 检查数组中是否所有元素
满足回调函数
的条件。find()
:返回数组中第一个满足回调函数
的元素。findIndex()
: 返回数组中第一个满足回调函数
的元素的索引
。
哪些会改变原数组
push
pop
shift
unshift
splice
reverse
事件委托
事件委托
是指将子元素中的事件监听器添加到父元素上
,当子元素触发事件
时,会冒泡到父元素上
,由父元素来统一处理事件
。
基本数据类型 和 引用数据类型
数据类型
主要分为基本数据类型
和引用数据类型
!
基本数据类型
:string
number
boolean
null
undefined
symbol
!引用数据类型
:object
array
function
date
regexp
error
arguments
!
区别
基本数据类型
: 存储在栈
中,访问时通过值进行访问
,比较时也是通过值进行比较
的!引用数据类型
: 存储在堆
中,访问时是对象的引用地址
,比较时是对象引用地址
的比较!
ES6
ES6
新特性
let
和const
:let
和const
是用来声明变量的,属于块级作用域
,let
可以重新赋值,const
不能重复赋值。模板字符串
:模板字符串
是ES6
新增的字符串语法,可以直接拼接字符串
,${}
可以嵌入表达式
。解构赋值
:解构赋值
是ES6
新增的语法,可以直接解构对象和数组
。箭头函数
:箭头函数
是ES6
新增的语法,可以简化函数声明
,this
指向外部函数
。类
:类
是ES6
新增的语法,可以定义类
,类
可以继承
。模块
:模块
是ES6
新增的语法,可以定义模块
,模块
可以导出
和导入
。Promise
:Promise
是ES6
新增的语法,可以异步编程
。async/await
:async/await
是ES6
新增的语法,可以简化异步编程
。Reflect
:Reflect
是ES6
新增的语法,可以操作对象
。Proxy
:Proxy
是ES6
新增的语法,可以代理对象
。Generator
:Generator
是ES6
新增的语法,可以生成器
。Symbol
:Symbol
是ES6
新增的语法,可以定义唯一标识符
。
let
和 const
let
和const
都是用来声明变量的关键字
,两者都是属于块级作用域范畴
!
let
:let
可以重新赋值
,但无法在声明变量前访问
!const
:cosnt
是常量
,声明变量时,必须有初始值
,且不能再次赋值
。
let
和 const
以及 var
的区别
let
和const
以及var
关键字都是用来声明变量
的一种方式,除了声明变量以外,也是有点差别的!
var:
是函数作用域
,存在变量提升
,容易污染变量
,并且可以重复定义变量值
,后面会覆盖前面已声明过的变量
!let:
是块级作用域
,不存在变量提升,不能在声明变量前进行访问,存在暂时性死区
,不能重复定义变量
!const:
是块级作用域
,与let
的区别是,const是常量
声明,声明变量时必须有初始值
,且值一旦被赋值,则无法不能被修改
!
箭头函数
箭头函数
: 没有自己的this 指向
, 也没有arguments 对象
,且无法使用new关键字
当作构造函数去使用
,也没有自己的prototype
原型以及contructor 构造函数
!
模块的理解和作用
- 在模块出现之前,业务
代码比较臃肿
,不好维护
,且变量命名冲突
等问题。模块的出现,可以解决这些问题。- 模块可以帮我们实现
代码拆分
,命名空间
,以及代码复用
。- 目前流行的模块化有
CommonJS
和ES6 Module
等。
CommonJS
和 ES6 Module
的区别
CommonJS
是Node.js
的模块化规范,ES6 Module
是ECMAScript
的模块化规范。CommonJS
模块化主要是同步加载
,ES6 Module
模块化主要是异步加载
。CommonJS
使用module.export
导出模块,ES6 Module
使用export
导出模块。CommonJS
使用require
引入模块,ES6 Module
使用import
引入模块。
新增的数据类型
Symbol
:Symbol
是ES6
新增的数据类型,Symbol
是全局唯一
的,Symbol
可以定义唯一标识符
。BigInt
:BigInt
是ES9
新增的数据类型,BigInt
是大整数
,BigInt
可以表示任意精度的整数
。
Set
和 Map
Set
是ES6
新增的数据存储结构
,Set
是集合
,Set
可以存储任何类型的唯一值
,Set
可以去重
。Map
是ES6
新增的数据存储结构
,Map
是键值对
,Map
可以存储任意类型的值
,Map
可以通过键获取值
。
Set
与Array
的区别:Set
是无序集合
,Set
中的元素是唯一的,且不能重复
。Array
是有序集合
,Array
中的元素是可以重复的。
Map
与Object
的区别:Map
键可以是不同类型
普通对象
的键只能是字符串
即使不是字符串,也会默认转换为字符串
!
数组去重
和 数组排序
数组去重
:数组去重
是指去除数组中的重复元素
,数组去重
的方法有Set
、filter
、includes
、indexOf
等。数组排序
:数组排序
是指对数组中的元素进行排序
,数组排序
的方法有sort
、reverse
等。
Promise
的理解
promise
是异步任务处理
,当异步任务处理结束
后,会返回一个承诺
,就是确定
或者是驳回
!primise
有三种状态:pending
、fulfilled
、rejected
!
pending
: 初始状态,表示异步操作
正在进行中。fulfilled
: 异步操作成功
完成。rejected
: 异步操作失败
完成。
async/await
的理解
async/await
是ES6
新增的语法,可以简化异步编程
。
async
:async
是声明一个异步函数
,async
函数返回一个promise
对象。await
:await
是暂停
异步函数的执行,await
后面可以跟promise
对象,await
后面的promise
对象状态
变成fulfilled
后,才会继续执行
。
Reflect
的理解
Reflect
是ES6
新增的语法,可以操作对象
,是Object对象的一种规范化
! 常配合Proxy代理对象
一起使用!`
Reflect.get()
:Reflect.get()
方法是获取对象属性
,与Object.get()
方法类似。Reflect.set()
:Reflect.set()
方法是设置对象属性
,与Object.set()
方法类似。Reflect.has()
:Reflect.has()
方法是判断对象是否有属性
,与Object.has()
方法类似。Reflect.deleteProperty()
:Reflect.deleteProperty()
方法是删除对象属性
,与delete
关键字类似。