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用于判断构造函数是否存在函数原型上prototypeObject.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语言本身提供的一些预定义对象,比如MathDateString等。
常用的内置对象有
Math: 包含了数学相关的函数和常量。Date: 用于处理日期和时间。String: 用于处理字符串。Array: 用于处理数组。Object: 用于处理对象。
操作数组方法的有哪些
push(): 向数组的末尾添加一个或多个元素,并返回新的长度。pop():删除并返回数组的最后一个元素。shift():删除并返回数组的第一个元素。unshift(): 在数组的开头添加一个或多个元素,并返回新的长度。splice(): 向数组中添加/删除项目,并返回被删除的项目。slice(): 根据索引截取并返回一个新的数组,包含从开始索引到结束索引(不包括结束索引)的原数组的元素。concat(): 将两个数组进行合并返回一个新的数组,包含两个或多个数组的元素。map():创建一个新数组,新数组中的内容,回调函数中返回的结果!filter():创建一个新数组,返回一个结果为true的元素组成的数组。reduce(): 对数组中的元素进行累计操作,最终返回一个单一值。forEach(): 为数组中的每个元素调用一次提供的函数。isArray():判断一个变量是否为数组。some(): 检查数组中是否有元素满足回调函数的条件。every(): 检查数组中是否所有元素满足回调函数的条件。find():返回数组中第一个满足回调函数的元素。findIndex(): 返回数组中第一个满足回调函数的元素的索引。
哪些会改变原数组
pushpopshiftunshiftsplicereverse
事件委托
事件委托是指将子元素中的事件监听器添加到父元素上,当子元素触发事件时,会冒泡到父元素上,由父元素来统一处理事件。
基本数据类型 和 引用数据类型
数据类型主要分为基本数据类型和引用数据类型!
基本数据类型:stringnumberbooleannullundefinedsymbol!引用数据类型:objectarrayfunctiondateregexperrorarguments!
区别
基本数据类型: 存储在栈中,访问时通过值进行访问,比较时也是通过值进行比较的!引用数据类型: 存储在堆中,访问时是对象的引用地址,比较时是对象引用地址的比较!
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关键字类似。