前端面试题(js篇)

本文详细阐述了JavaScript的组成部分,包括ECMAScript、DOM和BOM,介绍了JavaScript的内置对象及其功能,讨论了数据类型检测、闭包、内存泄漏、事件委托、基本与引用数据类型的区别以及原型链的工作原理。同时,讲解了new操作符在创建对象实例中的作用。
摘要由CSDN通过智能技术生成

1.JS由哪三部分组成?

JavaScript 主要由以下三部分组成:

核心(ECMAScript):

ECMAScript 是 JavaScript 语言的标准和核心。它定义了语言的语法、类型、语句、关键字、保留字、操作符、对象以及它们的行为。这部分是所有 JavaScript 实现的基础,无论在什么平台上。

文档对象模型(DOM):

文档对象模型(DOM)是一个跨平台和语言独立的接口,允许程序和脚本动态地访问和更新文档的内容、结构和样式。在 Web 浏览器中,JavaScript 通过 DOM 提供的 API 与 HTML 文档进行交互,例如创建、添加或修改 HTML 元素和属性。

浏览器对象模型(BOM):

浏览器对象模型(BOM)允许 JavaScript 与浏览器进行交互,而不是文档的内容。例如,通过 BOM,开发者可以控制浏览器窗口、读取和设置浏览器的导航历史、进行异步通信等。BOM 提供了很多对象,用于访问浏览器功能,但这些功能并没有标准的规范,因此在不同浏览器中的实现可能会有所不同。

2.JS有哪些内置对象?

JavaScript 提供了许多内置对象,这些对象提供了基本的语言功能和工具,使得开发者可以执行各种常见的编程任务。这里是一些最常用的 JavaScript 内置对象:

全局对象:

Global:这不是一个真正的对象,而是全局命名空间的一个伪对象,它的属性和方法可以在代码的任何地方访问。

基本对象:

Object:所有 JavaScript 对象的基类。

Function:所有 JavaScript 函数的基类。

Boolean:表示布尔值(true 或 false)的对象。

Symbol:表示独一无二的标识符的数据类型。

错误对象:

Error:基本错误对象。

TypeError、RangeError、ReferenceError 等:具体的错误类型对象。

数字和日期对象:

Number:表示数值的对象。

Math:提供数学运算功能和常数的对象。

Date:提供日期和时间功能的对象。

字符串处理对象:

String:表示和操作字符串的对象。

RegExp:表示正则表达式的对象。

集合类型对象:

Array:表示数组的对象。

Map:表示键值对集合的对象。

Set:表示值的集合的对象。

WeakMap 和 WeakSet:分别为 Map 和 Set 的弱引用版本。

结构化数据对象:

JSON:用于解析和字符串化 JSON 数据的对象。

控制抽象对象:

Promise:用于异步编程的对象。

Generator:使得函数可以暂停执行和恢复执行的对象。

反射和代理对象:

Reflect:提供了操作对象的底层 API。

3.操作数组的方法有哪些?

JavaScript 提供了丰富的数组操作方法,使得处理和操作数组变得非常方便和高效。下面是一些最常用的数组方法:

添加或删除元素

push(): 在数组的末尾添加一个或多个元素,并返回新的长度。

pop(): 移除数组的最后一个元素,并返回那个元素。

shift(): 移除数组的第一个元素,并返回那个元素。

unshift(): 在数组的开始添加一个或多个元素,并返回新的长度。

splice(): 通过删除现有元素和/或添加新元素来更改数组的内容。

迭代元素

forEach(): 对数组的每个元素执行一次提供的函数。

map(): 创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后的返回值。

filter(): 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

reduce(): 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

reduceRight(): 与 reduce() 类似,但是从数组的末尾向前工作。

查找元素

indexOf(): 返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。

lastIndexOf(): 返回在数组中可以找到一个给定元素的最后一个索引,如果不存在,则返回-1。

find(): 返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。

findIndex(): 返回数组中满足提供的测试函数的第一个元素的索引,否则返回 -1。

测试元素

every(): 测试一个数组内的所有元素是否都能通过某个指定函数的测试。

some(): 测试数组中的某些元素是否通过由提供的函数实现的测试。

排序和反转

sort(): 对数组的元素进行排序,并返回数组。

reverse(): 颠倒数组中元素的顺序。

其他实用方法

concat(): 用于合并两个或多个数组。

join(): 将数组中的所有元素连接成一个字符串并返回这个字符串。

slice(): 返回一个新的数组对象,这一对象是一个由开始到结束(不包括结束)选择的数组的一部分浅拷贝。

flat(): 用于将嵌套的数组“拉平”,变成一维的数组。

flatMap(): 先使用映射函数映射每个元素,然后将结果压缩成一个新数组。

这些方法可以单独使用或者结合使用,以实现复杂的数组操作和数据处理。

4.JS对数据类的检测方式有哪些?

在 JavaScript 中,检测数据类型主要涉及到各种数据,如基本数据类型(如字符串、数字等)和对象类型(如数组、函数等)。下面是一些常用的数据类型检测方法:

typeof 操作符

typeof 是一个一元操作符,用来返回一个表示操作数数据类型的字符串。它适用于基本类型的检测,但对于对象类型,特别是区分数组和对象,typeof 可能就不太适用了。

console.log(typeof "hello");  // "string"
console.log(typeof 42);       // "number"
console.log(typeof true);     // "boolean"
console.log(typeof undefined);// "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof function() {}); // "function"
console.log(typeof {});            // "object"
console.log(typeof []);            // "object"
console.log(typeof null);          // "object"

instanceof 操作符

instanceof 检测构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。这对于自定义类型或内置类型对象非常有用,但不适用于基本数据类型。

console.log([] instanceof Array);           // true
console.log({} instanceof Object);          // true
console.log(function() {} instanceof Function); // true

Object.prototype.toString.call()

Object.prototype.toString.call() 是一种更可靠的类型检测方法,能够区分不同的对象类型,如数组、日期等。

console.log(Object.prototype.toString.call([]));       // "[object Array]"
console.log(Object.prototype.toString.call(new Date())); // "[object Date]"
console.log(Object.prototype.toString.call({}));       // "[object Object]"

Array.isArray()

特别为数组设计的检测方法,用来确定传递的值是否是一个数组。这是检测数组的最可靠方式。

console.log(Array.isArray([]));  // true
console.log(Array.isArray({}));  // false

构造函数名称 constructor

通过检查对象的 constructor 属性来确定对象的类型。但这种方法的可靠性取决于 constructor 属性没有被改变。

console.log(([]).constructor === Array);       // true
console.log(({}).constructor === Object);      // true
console.log((function() {}).constructor === Function); // true

5.说一下闭包,闭包有什么特点?

在JavaScript中,闭包是一个函数和其被创建时的词法环境的组合。这个特性使得一个函数即使在其外部作用域被执行时,仍然能访问到它定义时的作用域中的变量。闭包是JavaScript中非常强大的一个特性,可以用于多种用途,如数据隐藏和封装、创建模块或函数库等。

示例:使用闭包来封装数据

下面是一个利用闭包来封装数据,使其不能直接从外部被访问或修改的例子:

function createBankAccount(initialBalance) {
    let balance = initialBalance;  // balance是一个通过闭包保护的私有变量

    return {
        deposit: function(amount) {
            balance += amount;  // 内部函数可以访问外部函数的balance变量
            return balance;
        },
        withdraw: function(amount) {
            if (amount <= balance) {
                balance -= amount;
                return balance;
            } else {
                return 'Insufficient funds';
            }
        },
        getBalance: function() {
            return balance;  // 提供了一个公共的方法来查看余额
        }
    };
}

const account = createBankAccount(100);
console.log(account.getBalance());  // 输出: 100
account.deposit(50);
console.log(account.getBalance());  // 输出: 150
console.log(account.withdraw(20));  // 输出: 130

在这个例子中,函数createBankAccount创建了一个具有存款、取款和查询余额功能的对象。这个对象中的方法都可以访问定义它们的函数作用域中的balance变量。这个变量对于外部代码是不可见的,只能通过这三个方法进行操作,有效地隐藏了实现细节并防止了外部直接修改余额。

闭包的典型用途

  1. 数据封装:正如上面的银行账户示例所示,闭包可以用于封装私有数据,提供操作这些数据的公共方法。
  2. 模块化代码:闭包可以用来创建模块,封装一组具有共同功能的方法,只暴露必要的API给外部使用,其余细节保持私有。
  3. 函数工厂:闭包可以用来创建设置特定参数的特定功能的函数。
  4. 记忆功能:闭包可以用来缓存数据,例如在递归计算中缓存中间结果,提高效率。

6.前端的内存泄漏怎么理解?

前端的内存泄漏指的是在Web应用程序中,由于不正确的内存管理导致内存占用持续增加,直到达到浏览器或设备内存的极限,从而影响应用程序的性能甚至导致崩溃。内存泄漏通常是由于未正确释放不再需要的内存空间而导致的。

在前端开发中,常见的导致内存泄漏的情况包括:

  1. 未释放事件监听器:如果在DOM元素上绑定了事件监听器,但是在元素被移除或销毁之前没有及时移除这些监听器,就会导致内存泄漏。因为事件监听器仍然保留了对元素的引用,即使元素被移除,它也不会被垃圾回收。

  2. 循环引用:当对象之间存在相互引用,而且这些引用形成了一个循环,即使这些对象已经不再被应用程序所需要,它们也不会被垃圾回收。这种情况下,需要手动断开循环引用,或者使用弱引用(WeakMap、WeakSet)来避免内存泄漏。

  3. 定时器和异步操作:未清除的定时器或异步操作(如Promise、事件监听器等)会阻止相关的作用域被垃圾回收,导致内存泄漏。特别是在单页应用程序(SPA)中,定时器和异步操作的数量可能会快速增加,因此需要谨慎管理它们的生命周期。

  4. 闭包:当闭包中引用了外部作用域的变量,并且这些闭包长期存在,而且外部作用域中的变量无法被释放,就会导致内存泄漏。

为了避免内存泄漏,开发人员应该注意以下几点:

  • 及时释放不再需要的引用,包括事件监听器、定时器、异步操作等。
  • 避免创建不必要的全局变量,使用局部变量和合适的作用域来限制变量的生命周期。
  • 使用工具和性能分析器来检测和解决内存泄漏问题,如Chrome开发者工具的Memory面板。
  • 注意循环引用和闭包,避免它们导致的内存泄漏。

7.事件委托是什么?

事件委托是一种常见的前端开发技术,也称为事件代理。它利用事件冒泡的机制,将事件处理程序绑定到父元素上,而不是直接绑定到子元素。当子元素触发了特定类型的事件时,事件会冒泡到父元素,父元素上绑定的事件处理程序会被调用。

事件委托的基本原理是利用了DOM事件的冒泡机制。当子元素触发一个事件时,这个事件会一级一级地向上传播,直到传播到根元素(通常是<html><body>),期间每个祖先元素都有机会捕获并处理这个事件。

通过在父元素上绑定事件处理程序,可以利用这个冒泡机制来实现事件委托。父元素可以根据触发事件的子元素来决定如何处理事件,从而减少了事件处理程序的数量,提高了性能和代码的简洁性。

事件委托的优点包括:

  1. 减少事件处理程序的数量:只需要在父元素上绑定一个事件处理程序,而不是在每个子元素上都绑定一个,可以大大减少代码量。

  2. 动态添加或移除子元素时不需要重新绑定事件:由于事件是绑定在父元素上的,所以如果动态添加或移除了子元素,无需重新绑定事件,事件处理程序仍然会生效。

  3. 节省内存和提高性能:减少了事件处理程序的数量,可以降低内存占用,并提高页面的响应速度。

下面是一个简单的示例,演示了如何使用事件委托来处理子元素的点击事件:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Event Delegation Example</title>
<style>
    ul {
        list-style-type: none;
    }
    li {
        padding: 10px;
        border: 1px solid #ccc;
        margin-bottom: 5px;
        cursor: pointer;
    }
</style>
</head>
<body>

<ul id="parent-list">
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
</ul>

<script>
document.getElementById('parent-list').addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
        console.log('You clicked on ' + event.target.textContent);
    }
});
</script>

</body>
</html>

在这个示例中,点击父元素<ul>中的任何一个子元素<li>,事件都会被委托到父元素上处理。通过判断event.target的标签名,可以确定点击的是哪个子元素,并作出相应的处理。

8.基本数据类型和引用数据类型的区别?

基本数据类型(Primitive Data Types)和引用数据类型(Reference Data Types)是编程语言中常见的两种数据类型,它们之间有一些重要的区别。

基本数据类型(Primitive Data Types):

  1. 存储方式:基本数据类型的值直接存储在变量所分配的内存空间中,每个变量都有一个固定大小的内存区域来存储其值。

  2. 拷贝方式:基本数据类型的赋值是值传递的,即将原始值直接复制到新的变量中。修改一个变量的值不会影响到另一个变量。

  3. 不可变性:基本数据类型的值是不可变的,一旦赋值后,其值无法被改变。

  4. 原始数据类型:JavaScript中的基本数据类型包括:numberstringbooleannullundefinedsymbol(ES6新增)。

引用数据类型(Reference Data Types):

  1. 存储方式:引用数据类型的值存储在内存中的堆内存中,变量存储的是一个引用(内存地址),指向实际的数据。

  2. 拷贝方式:引用数据类型的赋值是引用传递的,即将原始值的引用复制到新的变量中。因此,两个变量实际上引用了同一个对象,修改一个变量的值会影响到另一个变量。

  3. 可变性:引用数据类型的值是可变的,因为变量存储的是对象的引用,可以修改对象的属性或内容。

  4. 引用数据类型:JavaScript中的引用数据类型包括:objectarrayfunction等,以及ES6中的MapSet等。

总结区别:

  • 基本数据类型的值是存储在栈内存中的,而引用数据类型的值是存储在堆内存中的。
  • 基本数据类型的赋值是复制值,而引用数据类型的赋值是复制引用。
  • 基本数据类型的值是不可变的,而引用数据类型的值是可变的。

9.说一下原型链

原型链(Prototype Chain)是JavaScript中一个重要的概念,它描述了对象之间的继承关系和原型对象的链式结构。每个对象都有一个原型(prototype),并且可以通过原型链来访问其原型对象的属性和方法。

原型链的基本原理:

  1. 每个对象都有一个原型(prototype):除了基本数据类型的对象外,JavaScript中的每个对象(包括函数)都有一个原型对象。可以通过__proto__属性来访问对象的原型。

  2. 对象之间通过原型链连接:当访问对象的属性或方法时,如果当前对象没有找到对应的属性或方法,它会沿着原型链向上查找,直到找到或者到达原型链的顶端(Object.prototype)。

  3. 原型链的顶端是Object.prototype:所有对象的原型链的顶端都指向Object.prototypeObject.prototype是JavaScript中的基础对象,包含了一些通用的方法,如toString()valueOf()等。

  4. 继承和属性访问:当访问对象的属性或方法时,如果对象本身没有该属性或方法,则会沿着原型链向上查找,直到找到或者到达原型链的顶端。如果在原型链上的某个原型对象中找到了对应的属性或方法,那么就会返回该属性或方法,否则返回undefined

代码示例:

// 创建一个对象 obj,它的原型是 Object.prototype
const obj = {};

// obj 继承了 Object.prototype 中的属性和方法
obj.toString();  // 调用 Object.prototype 上的 toString 方法

// 创建一个函数 func,它的原型是 Function.prototype
function func() {}

// func 继承了 Function.prototype 中的属性和方法
func.call();  // 调用 Function.prototype 上的 call 方法

// 所有对象的原型链的顶端都指向 Object.prototype
console.log(obj.__proto__ === Object.prototype);  // 输出: true
console.log(func.__proto__ === Function.prototype);  // 输出: true

// Object.prototype 的原型是 null,表示原型链的顶端
console.log(Object.prototype.__proto__ === null);  // 输出: true

在这个示例中,obj对象和func函数都继承了它们各自原型对象中的属性和方法,如果在对象或函数自身找不到对应的属性或方法,就会沿着原型链向上查找,直到找到或者到达原型链的顶端。

10.new操作符具体做了什么?

new 操作符用于创建一个新的对象实例,它执行了以下几个步骤:

  1. 创建一个新的空对象new 操作符首先创建一个新的空对象,这个对象将成为新创建的实例。

  2. 将新对象的原型(__proto__)链接到构造函数的原型对象new 操作符将新创建的对象的原型属性(__proto__)链接到构造函数的原型对象上。这样,新创建的对象就可以访问构造函数原型对象上的属性和方法。

  3. 将构造函数的作用域赋给新对象(绑定 this:通过 applycall 方法或者 bind 方法,将构造函数的作用域绑定到新创建的对象上,使得构造函数中的 this 关键字指向新创建的对象。

  4. 执行构造函数的代码块:此时,新对象已经创建并且 this 指向了这个新对象,然后执行构造函数中的代码块。构造函数中的属性和方法可以被添加到新对象上。

  5. 返回新对象:如果构造函数没有显式地返回一个对象,则 new 操作符会隐式地返回新创建的对象实例。如果构造函数中显式地返回了一个对象,则 new 操作符返回该对象。

代码示例:

// 定义一个构造函数 Person
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 使用 new 操作符创建一个新的对象实例
const person1 = new Person('Alice', 30);

console.log(person1.name);  // 输出: Alice
console.log(person1.age);   // 输出: 30

在这个示例中,new Person('Alice', 30) 创建了一个新的对象实例 person1new 操作符创建了一个新的空对象,并将构造函数 Person 的作用域绑定到这个新对象上,然后执行构造函数中的代码块,将属性 nameage 添加到新对象上,最后返回这个新对象。

  • 22
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爪洼守门员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值