Javascript面试题(二)

93 阅读12分钟

1, 简单谈一下 cookie ?

cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地,当下一次有同源的请求时,将保存的 cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie 一般可以存储 4k 大小的数据,并且只能够被同源的网页所共享访问。

服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条 cookie 包括了 5 个属性值 expires、domain、path、secure、HttpOnly。 其中 expires 指定了 cookie 失效的时间, domain 是域名、 path 是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。 secure 规定了 cookie 只能在确保安全的情况下传输, HttpOnly 规定了这个 cookie 只能被服务器访问,不能使用 js 脚本访问。

2 ES6 模块与 CommonJS 模块、AMD、CMD 的差异。

它们之间的主要区别有两个方面。

(1)第一个方面是在模块定义时对依赖的处理不同。AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇就近依赖,只有在用到某个模块的时候再去 require。

(2)第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD 在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。

3 ES6 模块与 CommonJS 模块

1.CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。 ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

2.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 CommonJS 模块就是对象,即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。 而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

4. requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何 缓存的?)

require.js 的核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数。

5. documen.write 和 innerHTML 的区别?

document.write 的内容会代替整个文档内容,会重写整个页面。

innerHTML 的内容只是替代指定元素的内容,只会重写页面中的部分内容。

6,innerHTML 与 outerHTML 的区别?

innerHTMLouterHTML 是操作 DOM 的两个常用属性, innerHTML表示一个元素的 内部 HTML 内容。 outerHTML 表示一个元素 自身及其内部 HTML 内容。

<div id="example">Hello, <b>world</b>!</div>
<script>
  const div = document.getElementById("example");
  console.log(div.innerHTML); // 输出: Hello, <b>world</b>!
  div.innerHTML = "New Content";
  console.log(div.outerHTML); // 输出: <div id="example">New Content</div>
</script>

7,如何编写高性能的 Javascript ?

1.使用位运算代替一些简单的四则运算。 2.避免使用过深的嵌套循环。 3.不要使用未定义的变量。 4.当需要多次访问数组长度时,可以用变量保存起来,避免每次都会去进行属性查找。

8.call() 和 .apply() 的区别?

它们的作用一模一样,区别仅在于传入参数的形式的不同。

call()

  • 参数形式: 参数逐个传递。

  • 语法: func.call(thisArg, arg1, arg2, ...)

  • 用法场景: 适用于参数数量明确且已知的情况。

apply()

  • 参数形式: 参数以数组或类似数组的形式传递。
  • 语法: func.apply(thisArg, [arg1, arg2, ...])
  • 用法场景: 适用于参数来源是数组或不确定参数数量的情况。

9,### JavaScript 类数组对象的定义?

类数组对象(Array-like Object)是一个看起来像数组,但不完全是数组的对象

  • 拥有类似数组的结构

    • 通常有数值索引(如 0, 1, 2)。
    • 可能有 length 属性,表示对象的元素数量。
  • 不是真正的数组

    • 它并没有数组的方法,比如 pushpopforEach 等。
  • 常见的类数组对象

    • arguments 对象:函数中的参数列表。
    • DOM 方法返回的 NodeListHTMLCollection
    • 自定义对象,只要满足类数组的特征。
// 一个类数组对象
const arrayLike = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};
console.log(arrayLike[0]); // 输出: 'a'
console.log(arrayLike.length); // 输出: 3

转换为真正数组的方法

const realArray = Array.from(arrayLike);
console.log(realArray); // 输出: ['a', 'b', 'c']

const realArray2 = [...arrayLike];
console.log(realArray2); // 输出: ['a', 'b', 'c']

10,数组和对象有哪些原生方法,列举一下?

以下是 JavaScript 中数组和对象的常用原生方法:


数组的原生方法

  1. 操作元素

    • push():向数组末尾添加元素。
    • pop():移除数组末尾的元素。
    • unshift():向数组开头添加元素。
    • shift():移除数组开头的元素。
    • splice(start, deleteCount, ...items):在指定位置添加/删除元素。
    • concat():合并多个数组。
  2. 查找与筛选

    • indexOf():查找元素第一次出现的索引。
    • lastIndexOf():查找元素最后一次出现的索引。
    • includes():判断数组是否包含某个元素。
    • find(callback):返回第一个满足条件的元素。
    • findIndex(callback):返回第一个满足条件的元素索引。
    • filter(callback):返回满足条件的所有元素组成的新数组。
  3. 遍历与变换

    • forEach(callback):遍历数组。
    • map(callback):创建新数组,元素为回调函数的返回值。
    • reduce(callback, initialValue):累加数组中的值。
    • reduceRight(callback, initialValue):从右向左累加值。
  4. 排序与反转

    • sort():排序数组(默认按字符串排序,可自定义比较函数)。
    • reverse():反转数组顺序。
  5. 分割与拼接

    • slice(start, end):返回数组的子数组。
    • join(separator):将数组元素拼接为字符串。
    • flat(depth):将多维数组扁平化。
    • flatMap(callback):映射并扁平化数组。
  6. 其他

    • Array.isArray():判断是否为数组。
    • length:返回数组的长度。

对象的原生方法

  1. 操作对象

    • Object.assign(target, ...sources):合并对象。
    • Object.create(proto, propertiesObject):创建新对象,指定原型。
    • Object.defineProperty(obj, prop, descriptor):定义属性。
  2. 获取对象信息

    • Object.keys(obj):返回对象所有可枚举属性的键。
    • Object.values(obj):返回对象所有可枚举属性的值。
    • Object.entries(obj):返回对象键值对的数组。
    • Object.getOwnPropertyNames(obj):返回对象自身所有属性的键。
    • Object.getOwnPropertyDescriptor(obj, prop):获取属性的描述符。
    • Object.hasOwn(obj, prop):判断对象是否有某个自身属性。
  3. 判断与比较

    • Object.is(value1, value2):判断两个值是否严格相等。
    • Object.hasOwnProperty(prop):判断是否有某个自身属性。
    • Object.prototype.toString():返回对象的字符串表示。
  4. 对象原型

    • Object.getPrototypeOf(obj):获取对象原型。
    • Object.setPrototypeOf(obj, proto):设置对象原型。
  5. 冻结与密封

    • Object.freeze(obj):冻结对象(不可修改)。
    • Object.isFrozen(obj):判断对象是否被冻结。
    • Object.seal(obj):密封对象(不可添加新属性)。
    • Object.isSealed(obj):判断对象是否被密封。

11,数组和对象有哪些原生方法,列举一下?

用一个固定值填充一个数组中从起始索引到终止索引内的全部元素

没错!以下是对 fill() 方法的详细说明和示例:

语法

array.fill(value, start, end)
  • value(必需):用来填充数组的值。
  • start(可选):开始填充的位置,默认值为 0
  • end(可选):停止填充的位置(不包括该索引),默认值为 array.length

特性

  1. 会直接修改原数组,并返回修改后的数组。
  2. startend 支持负值
    • 负值会从数组末尾开始计算索引,例如 -1 表示最后一个元素。

示例

  1. 基本用法
const arr = [1, 2, 3, 4, 5];
console.log(arr.fill(0)); // 输出: [0, 0, 0, 0, 0]
  1. 指定 startend
const arr = [1, 2, 3, 4, 5];
console.log(arr.fill(9, 1, 4)); // 输出: [1, 9, 9, 9, 5]
// 从索引 1 填充到索引 4(不包含 4)
  1. 使用负索引
const arr = [1, 2, 3, 4, 5];
console.log(arr.fill(7, -3, -1)); // 输出: [1, 2, 7, 7, 5]
// 等效于 fill(7, 2, 4)
  1. 填充空数组
const arr = new Array(5);
console.log(arr.fill(1)); // 输出: [1, 1, 1, 1, 1]

注意点

  • fill() 方法直接改变原数组,而不是返回新数组。
  • 如果数组是稀疏数组,fill() 会对每个索引赋值,变成密集数组。

12, [,,,] 的长度?

原因

在 JavaScript 中,数组最后的逗号是可忽略的。因此:

const arr = [,,,];

实际上等价于:

const arr = [undefined, undefined, undefined];

但如果写成 [...,],它等价于:

const arr = [undefined];

示例验证

console.log([,,,].length); // 输出: 3
console.log([...].length); // 输出: 1
console.log([...,,].length); // 输出: 1

总结:多余的逗号只影响数组的视觉呈现,不会增加数组的长度。

13 ECMAScript6 怎么写 class,为什么会出现 class 这种东西?

引入了 class 语法,提供了更清晰、更接近面向对象语言的方式来定义类。

14如何判断一个对象是否属于某个类?

15 Ajax 是什么? 如何创建一个 Ajax?

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。

// 创建 XMLHttpRequest 对象
const xhr = new XMLHttpRequest();

// 配置请求方式和 URL
xhr.open('GET', 'https://api.example.com/data', true); // true 表示异步

// 监听响应
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && xhr.status === 200) { // 请求成功
    console.log(xhr.responseText); // 输出响应数据
  }
};

// 发送请求
xhr.send();

16,谈一谈浏览器的缓存机制?

浏览器的缓存机制

浏览器的缓存机制是为了提升网页加载速度、减少网络请求和服务器压力的一种技术。它通过将已访问的资源(如 HTML、CSS、JS、图片等)存储在本地,避免重复请求,提高用户体验。


缓存的分类

  1. 强缓存

    • 不向服务器发送请求,直接使用本地缓存。
    • 通过 HTTP 响应头控制,如 ExpiresCache-Control
    • 特点
      • 命中强缓存时,返回状态码为 200(from disk cache 或 memory cache)
  2. 协商缓存

    • 浏览器向服务器发送请求,服务器根据资源是否修改决定是否使用缓存。
    • 通过 HTTP 响应头 Last-Modified / If-Modified-SinceETag / If-None-Match 实现。
    • 特点
      • 如果资源未修改,返回状态码 304(Not Modified),浏览器继续使用本地缓存。

缓存的 HTTP 头

1. 强缓存控制

  • Expires(HTTP/1.0):指定资源过期时间(绝对时间,受客户端时间影响)。

    Expires: Fri, 20 Nov 2024 10:00:00 GMT
    
  • Cache-Control(HTTP/1.1):更灵活的缓存控制(优先级高于 Expires)。

    • 常见指令:
      • max-age=3600:缓存的有效时间(秒)。
      • no-cache:需要协商缓存验证。
      • no-store:不使用缓存,每次都请求服务器。
      • public:资源可以被客户端和代理服务器缓存。
      • private:资源仅能被客户端缓存。

2. 协商缓存控制

  • Last-ModifiedIf-Modified-Since

    • Last-Modified:资源最后一次修改的时间。
    • If-Modified-Since:浏览器发送的头,表示上次的修改时间。
    Last-Modified: Fri, 20 Nov 2024 09:00:00 GMT
    
    • 缺点:
      • 修改时间不够精确(只能精确到秒)。
      • 文件内容未变,但修改时间更新,导致缓存失效。
  • ETagIf-None-Match

    • ETag:资源的唯一标识符(通常是文件的哈希值)。
    • If-None-Match:浏览器发送的头,用于校验 ETag 是否匹配。
    ETag: "5d8c72a5edda5-18a"
    

缓存的流程

  1. 浏览器请求资源,首先检查本地缓存:

    • 如果命中 强缓存,直接使用本地资源,不发请求。
    • 如果未命中强缓存,发送请求到服务器。
  2. 服务器收到请求:

    • 根据 协商缓存 验证资源是否修改。
    • 如果未修改,返回 304,浏览器使用本地缓存。
    • 如果已修改,返回新的资源和状态码 200

缓存的优先级

强缓存 > 协商缓存 > 不缓存。


常见场景和优化策略

  1. 静态资源缓存

    • 对静态资源(如图片、CSS、JS)使用长时间强缓存(Cache-Control: max-age=31536000)。
    • 通过文件名哈希值实现版本控制(如 style.abc123.css)。
  2. 动态资源缓存

    • 动态数据使用短时间缓存或禁用缓存(Cache-Control: no-store)。
  3. 清理缓存

    • 为了解决缓存更新问题,可以结合 ETag 或修改资源 URL(如增加时间戳或版本号)。

总结

浏览器缓存机制通过 强缓存协商缓存 来减少不必要的网络请求,提升性能。开发中应合理利用缓存头来提高页面加载速度,同时确保资源的实时性和正确性。

17 事件是什么?IE 与火狐的事件机制有什么区别? 如何阻止冒泡?

18 三种事件模型是什么?

19 事件委托是什么?

20