JS基础大乱炖一锅出

JS基础大乱炖一锅出

image-20250218161653528

1、new操作符的实现原理

原理!就4步!

1、创建空对象

2、把新对象内部的[[Prototype]]指向构造函数的prototype

3、以新对象为this调用构造函数,初始化新对象

4、如果构造函数返回的是对象,直接返回。否则,返回创建的新对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 首先,咱们写一个构造函数
function Person(name) {
this.name = name;
}
// 再写一个函数,模拟new操作符的作用
function myNew(con, ...args) {
// 判断参数时不时函数类型
if (typeof con !== "function") {
console.error("con must be a function");
}
// 创建空对象
let newObj = {};
// 将空对象的原型指向构造函数prototype
newObj.__proto__ = con.prototype;
// 将构造函数的this指向新对象
let res = con.apply(newObj, args);
// 判断构造函数返回值是否为对象,如果是,则返回构造函数返回值,否则返回新对象
let flag = res && (typeof res === "object" || typeof res === "function");
return flag ? res : newObj;
}

// 测试效果

console.log(myNew(Person,'anan'))

2、map和Object的区别

map和object都能够存储键值对,那好奇的朋友就发问了,有什么区别呢?

有的。

1、键的类型

  • Map:什么类型都可以,字符串√ 数字√ 对象√函数√ NaN也行!并且严格区分,不会自动转换为字符串。

    1
    2
    3
    4
    5
    const map = new Map();
    map.set(1, "number one");
    map.set("1", "string one");
    console.log(map.get(1)); // "number one"
    console.log(map.get("1")); // "string one"
  • Object:只能是字符串或者Symbol,不是字符串的都会自动转换为字符串!

    1
    2
    3
    4
    const obj = {};
    obj[1] = "number one"; // 数字 1 会转换为字符串 "1"
    obj["1"] = "string one"; // 覆盖"1"的值
    console.log(obj[1]); // "string one"

2、键的顺序

  • Map:内部会保存插入顺序,遍历的时候也是按照插入顺序返回的
  • Object:ES6 规范中规定对象属性遍历顺序,但实际行为在不同情况下可能不一致

3、大小属性

  • Map:获取键值对数量使用map.size
  • Object:没有直接获取键值对数量的方法,可以使用Object.keys(obj).length

4、迭代方面

  • Map:可以迭代,使用for...of 循环、map.forEach() 或解构语法

    1
    2
    3
    4
    const map = new Map([["a", 1], ["b", 2]]);
    for (const [key, value] of map) {
    console.log(key, value);
    }
  • Object:本身是不可迭代的。需要转换成键数组、值数组或键值对数组。

    Object.keys()Object.values()Object.entries()

    1
    2
    3
    4
    const obj = { a: 1, b: 2 };
    for (const key of Object.keys(obj)) {
    console.log(key, obj[key]);
    }

5、应用场景

  • Map:更适合频繁增加和删除键值对的场景。没有原型链干扰,它不继承来自 Object.prototype 的属性。
  • Object:通常用于存储结构化数据或作为简单的字典,但可能会受到原型链的干扰(例如内置属性或方法)。

3、Map和WeakMap的区别

键的类型

  • Map:键可以是任意类型
  • WeakMap:键只能是对象!只能是对象!对象对象对象~

可枚举性

  • Map:可迭代,使用 for...ofmap.forEach() ,也有size属性
  • WeakMap:不可迭代!没有遍历方法!也没有size属性!不允许列举键值对,因为它的键是弱引用,随时都可能被回收~

使用场景

  • Map:频繁增加、删除、遍历键值对的场景、用于数据持久存储和操作,或者需要保证键的顺序。
  • WeakMap:存储与对象相关的元数据或私有数据。当希望在对象不再被使用时,该对象对应的相关数据能自动被清除,使用 WeakMap 可以有效防止内存泄漏。

4、JavaScript有哪些内置对象

“内置对象”是指 JavaScript 语言在其标准中预先定义并由 JavaScript 引擎自动提供的对象。

全局对象:存储全局可用的变量和函数。

在浏览器中,全局对象通常是 window(或 self,例如在 Web Worker 中)。

在 Node.js 中,全局对象通常是 global

为了统一跨平台访问,ES2020 引入了 globalThis,它提供了一个标准的方式来引用全局对象。

基本类型包装对象ObjectFunctionNumberStringBooleanSymbolBigInt 等。

集合和数组相关ArrayMapSetWeakMapWeakSet

工具对象Math(数学函数和常量)、JSON(解析和字符串化 JSON 数据)。

日期和正则DateRegExp

错误处理Error 及其派生类型(如 TypeErrorSyntaxError 等)。

二进制数据处理ArrayBufferDataView、各类 TypedArray

异步编程Promise 等。

反射与代理ReflectProxy

5、常用的正则表达式有哪些

匹配数字:该字符串从开始到结束全部由一个或多个数字组成。

1
const reg = /^\d+$/ 

匹配英文:确保整个字符串只包含一个或多个英文字母

1
const reg = /^[a-zA-Z]+$/

匹配邮箱

1
const reg = /^[\w.-]+@[A-Za-z\d.-]+\.[A-Za-z]{2,}$/

匹配号码:

1
const reg = /^1[3-9]\d{9}$/

6、对JSON的理解

JSON 是一种文本格式,可以方便地在不同编程语言之间传输数据。

JSON 是 Web 开发中最常用的数据交换格式。

客户端与服务器之间通常使用 JSON 格式传输数据,因为它格式简单、易于解析并且与多种编程语言兼容。

严格的语法规则

  • 键必须用双引号括起来,单引号不合法。
  • 字符串也必须使用双引号。
  • JSON 不允许使用尾随逗号。

数据转换

  • 序列化
    将 JavaScript 对象转换为 JSON 字符串可以使用 JSON.stringify() 方法:

    1
    2
    3
    const obj = { name: "Alice", age: 25 };
    const jsonString = JSON.stringify(obj);
    console.log(jsonString); // 输出: '{"name":"Alice","age":25}'
  • 反序列化
    将 JSON 字符串解析为 JavaScript 对象可以使用 JSON.parse() 方法:

    1
    2
    3
    const jsonString = '{"name":"Alice","age":25}';
    const obj = JSON.parse(jsonString);
    console.log(obj.name); // 输出: "Alice"

7、JavaScript脚本延迟加载的方式有哪些

延迟加载(Lazy Loading) 是一种优化技术,目的是推迟非关键脚本的加载和执行,从而减少页面初始加载时间、提升首屏渲染速度,并降低不必要的资源消耗。

1、使用defer

加载时机:立即开始加载(下载)

执行时机:HTML文档解析完成后,DOMContentLoaded事件触发前,按出现顺序执行。

特点:不会阻塞页面的解析加载,保证脚本执行顺序和文档中出现的顺序一致。

1
<script src="script.js" defer></script>

2、使用async

加载时机:立即开始加载(下载)(异步加载)

执行时机:加载完成,就立即执行

特点:不保证顺序,取决于它们的加载速度,不会阻塞页面解析和渲染

1
<script src="script.js" async></script>

3、动态插入脚本 createElement

通过JS动态创建<script>标签,插入到DOM中,从而延迟加载脚本。

灵活,可以在特定条件或事件发生时加载脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function loadScript(url, callback) {
const script = document.createElement('script');
script.src = url;
script.onload = () => {
if (callback) callback();
};
document.head.appendChild(script);
}

// 例如,当用户点击按钮时加载脚本
document.getElementById('loadBtn').addEventListener('click', function() {
loadScript('script.js', function() {
console.log('Script loaded!');
});
});

**4、动态加载模块 import() **

使用 ES6 提供的动态 import() 方法,在需要时再异步加载模块。

返回一个 Promise,可以通过 .then()await 处理加载结果。

1
2
3
4
5
6
7
8
9
10
// 当需要加载某个模块时
import('./module.js')
.then((module) => {
// module 是加载的模块对象,包含所有导出的内容
module.doSomething(); // 调用模块中的函数
})
.catch((err) => {
// err 是模块加载失败时的错误对象
console.error('Error loading module:', err);
});

5、脚本置底

将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

8、JavaScript 类数组对象的定义

类数组对象:是对象,和数组具有类似的数组结构,但不是真正的Array对象。

  • 能够通过索引访问元素
  • 有length属性
  • 不一定继承Array.prototype

常见的类数组对象

arguments 对象、DOM 集合如NodeList HTMLCollection、字符串

1
2
3
4
5
6
7
8
9
10
11
function demo() {
// arguments 是一个类数组对象
console.log(arguments.length); // 输出传入参数的数量
console.log(arguments[0]); // 输出第一个参数
}
demo(10, 20, 30);

// NodeList 示例(在浏览器环境中)
const paragraphs = document.querySelectorAll('p'); // 返回 NodeList 类数组对象
console.log(paragraphs.length); // 输出页面中 <p> 标签的数量
console.log(paragraphs[0]); // 输出第一个 <p> 元素

类数组对象转换为数组呢

  • Array.from方法

    1
    const argsArray = Array.from(arguments);
  • 扩展运算符

    1
    const argsArray = [...arguments]
  • Array.prototype.slice

    1
    const argsArray = Array.prototype.slice.call(arguments);

9、数组有哪些原生方法

修改:push、pop、shift、unshift、splice、sort、reverse 、fill【影响原数组】

生成新数组:concat、slice、flat、flatMap、join(生成的字符串)

迭代与处理:forEach(返回 undefined,可以主动修改原数组)、map(返回新数组)、filter、reduce、every、some、find、findIndex

搜索:indexOf、lastIndexOf、includes

迭代器相关:keys、values、entries

10、Unicode、UTF-8、UTF-16、UTF-32的区别

Unicode:国际标准,抽象的字符集,世界上几乎所有的文字和符号都分配了唯一的数字标识代码点)。旨在取代各种字符编码系统,实现全球字符的统一表示。

Unicode 的代码点范围从 U+0000U+10FFFF

UTF是为了实现Unicode字符集的存储和传输的一系列编码方案,它们都把Unicode的代码点转换为一系列字节,但是具体方式和效率不同。

UTF-8

  • 可变长度(1~4 字节)。
  • 向后兼容 ASCII,节省英文文本空间,广泛应用于网络和文件存储。

UTF-16

  • 可变长度(2 或 4 字节)。
  • 大多数字符用 2 字节表示,超出基本平面的字符用 4 字节。
  • 被 Windows 和 JavaScript 等使用。

UTF-32

  • 固定长度(4 字节)。
  • 编码简单,但内存占用大,使用较少。

11、常见的位运算符有哪些?其计算规则是什么

位运算符:直接操作数字的二进制位。

JavaScript 中的数字是以 64 位浮点数(IEEE 754 格式)存储的,但在进行位运算时,它们会被暂时转换为 32 位有符号整数。位运算符通常比算术运算符更快,因为它们直接操作内存中的二进制位。

**按位与&**:对应的二进制位都是1,则该位结果是1,否则是0

1
2
3
// 二进制: 5 -> 0101, 3 -> 0011
// 0101 & 0011 = 0001 (十进制 1)
console.log(5 & 3); // 输出 1

按位或|:对应的二进制位只要有1,则该位结果是1,否则是0

1
2
3
// 二进制: 5 -> 0101, 3 -> 0011
// 0101 | 0011 = 0111 (十进制 7)
console.log(5 | 3); // 输出 7

按位异或^:对应的二进制位,只有两个操作数都不同,则该位结果是1,否则是0

1
2
3
// 二进制: 5 -> 0101, 3 -> 0011
// 0101 ^ 0011 = 0110 (十进制 6)
console.log(5 ^ 3); // 输出 6

**按位非~**:对每一位取反(对 32 位整数取反后再解释成带符号数)

1
2
3
// 5 的二进制(32 位)大致为 ...00000101
// ~5 = ...11111010,解释为 -6
console.log(~5); // 输出 -6

**左移运算符<<**:二进制左移指定的位数,补0

1
2
// 5 -> 0101,左移 1 位变为 1010,即十进制 10
console.log(5 << 1); // 输出 10

**有符号右移运算符>>**:二进制右移指定的位数,左侧根据原来的符号补(正数补0,负数补1)

1
2
3
4
5
6
// 5 -> 0101,右移 1 位变为 0010,即十进制 2
console.log(5 >> 1); // 输出 2

// -8 的 32 位表示为 11111111 11111111 11111111 11111000,
// 右移 2 位后为 11111111 11111111 11111111 11111110,即十进制 -2
console.log(-8 >> 2); // 输出 -2

**无符号右移运算符>>>**:将一个数的二进制位向右移动指定的位数,左侧始终补 0,不考虑符号位,

1
2
3
4
5
6
// 5 -> 0101,右移 1 位,左侧补 0,结果为 0010,即十进制 2
console.log(5 >>> 1); // 输出 2

// 对于负数,如 -1 的 32 位表示为 0xFFFFFFFF,
// 无符号右移 1 位后为 0x7FFFFFFF,即十进制 2147483647
console.log(-1 >>> 1); // 输出 2147483647

12、为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组

arguments是类数组,它被设计成一个简单的对象,仅提供数字索引和 length 属性。

在许多使用场景中,开发者只需要读取参数数量和逐个访问参数,而不需要数组提供的高级方法,因此将其设计为类数组就足够了。

传统的for循环

1
2
3
4
5
6
function demo() {
for (let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}
demo(1, 2, 3); // 分别输出 1, 2, 3

for ..of循环

1
2
3
4
5
6
function demo() {
for (const arg of arguments) {
console.log(arg);
}
}
demo('a', 'b', 'c');

转换为真正的数组

使用 Array.from()

1
2
3
4
5
function demo() {
const argsArray = Array.from(arguments);
argsArray.forEach(arg => console.log(arg));
}
demo('x', 'y', 'z');

使用扩展运算符:

1
2
3
4
5
function demo() {
const argsArray = [...arguments];
argsArray.forEach(arg => console.log(arg));
}
demo(10, 20, 30);

使用 Array.prototype.slice.call()

1
2
3
4
5
function demo() {
const argsArray = Array.prototype.slice.call(arguments);
argsArray.forEach(arg => console.log(arg));
}
demo('hello', 'world');

13、什么是 DOM 和 BOM?

DOM:文档对象模型 – Document Object Model

是一种标准的编程接口,代表整个文档的结构,是一个树形结构,用来表示HTML文档的结构和内容。它把页面解析为一个树状结构,每个HTML元素、属性、文本都被视为一个节点。允许脚本动态访问和修改页面的内容、结构、样式。提供丰富的 API 以实现动态交互和更新页面内容。

  • 元素选择

    document.getElementById(id)

    document.getElementsByClassName(className) ———– HTMLCollection 动态

    document.getElementsByTagName(tagName) ———– HTMLCollection 动态

    document.querySelector(selector)

    document.querySelectorAll(selector) ——– NodeList 静态

  • 节点操作

    document.createElement(tagName)

    element.appendChild(node)

    element.insertBefore(newNode, referenceNode)

    element.removeChild(node)

    element.replaceChild(newNode, oldNode)

    element.cloneNode(deep)

  • 内容和属性操作

    element.innerHTML

    element.textContent

    element.setAttribute(name, value)

    element.getAttribute(name)

    element.removeAttribute(name)

  • 样式与类操作

    element.style

    element.classList.add()/remove()/toggle()/contains()

  • 事件处理

    element.addEventListener(event, handler, options)

    element.removeEventListener(event, handler, options)

    element.dispatchEvent(event)

  • DOM 遍历

    node.parentNode / node.parentElement

    node.childNodes / element.children

    node.firstChild / node.lastChild

    node.nextSibling / node.previousSibling

BOM:浏览器对象模型 – Browser Object Model

是和浏览器交互的对象和接口,主要用来访问和控制浏览器窗口和环境,而不是页面内容本身。允许开发者操作浏览器窗口、导航、历史记录、屏幕信息等。包括 windownavigatorlocationhistoryscreen 等对象。不同于 DOM,它不规定文档内容的结构,而是与浏览器环境(例如窗口、框架、地址栏等)相关。

  • window 对象

    window.alert(message)

    window.confirm(message)

    window.prompt(message, default)

    window.open(url, name, specs)

    window.close()

    window.setTimeout(callback, delay)

    window.setInterval(callback, delay)

    window.clearTimeout(id) / window.clearInterval(id)

  • location 对象

    location.href

    location.assign(url)

    location.replace(url)

    location.reload()

  • history 对象

    history.back()

    history.forward()

    history.go(delta)

  • navigator 对象

    navigator.userAgent

    navigator.language

    navigator.onLine

    navigator.geolocation

  • screen 对象

    screen.width、screen.height

    screen.availWidth、screen.availHeight

14、对AJAX的理解,实现一个AJAX请求

AJAX(Asynchronous JavaScript and XML)是一种技术,不用重新加载整个页面,能够与服务器交换数据,来更新部分网页的技术。网页可以实现异步加载数据,从而改善用户体验。虽然名字里有XML,但是现代AJAX请求通常使用JSON格式的数据进行交互。

  • 异步通信:在后台与服务器通信,不会阻塞用户界面或者刷新页面。
  • 数据格式:最初是XML,现在是JSON。
  • 浏览器支持:可以使用XMLHttpRequest对象或者fetch API。

实现一个 AJAX 请求(使用 XMLHttpRequest)

1、创建XMLHttpRequest对象

2、配置请求

3、设置状态变化监听

4、发送请求

5、回调处理

1
2
3
4
5
6
7
8
var xhr = new XMLHttpRequest();
xhr.open("GET", "data.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();

fetch怎么使用

xhr需要手动检查readyStatestatus判断请求是否成功,fetch是基于Promise的,语法更简洁,便于链式调用和错误捕获。

1
2
3
4
5
6
7
8
9
10
fetch('data.json', {
method: 'POST', // 指定请求方法为 POST
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

使用 Promise 封装 AJAX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ajaxGet(url){
return new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest()
xhr.open("GET",url,true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
resolve(xhr.responseText)
}else{
reject(new Error('fail'))
}
}
xhr.onerror = function(){
reject(new Error('网络错误'))
}

xhr.send()
})
}

xhr.readyState:0,1,2,3,4

15、JavaScript为什么要进行变量提升

变量提升:代码执行前,解释器会先扫描整个作用域(函数作用域或者全局作用域),将变量和函数的声明都提升到作用域的最前面。这里的提升针对的是声明部分,不是赋值。

为什么会有变量提升

JS在执行代码前,会先经过一个编译阶段。在这个阶段,解释器会给每个变量和函数创建内存空间,并且建立作用域链。在 ES5 及之前的版本中,JavaScript 只有函数级作用域(没有块级作用域)。为了保持作用域的一致性,变量声明都会被提升到函数或全局的最顶部,这样可以确保在整个函数体内都能访问到这些变量(虽然如果在使用前没有赋值,则值为 undefined)。变量提升是 JavaScript 早期设计的一部分,它帮助解释器在运行时对代码结构有一个整体的认识。虽然这可能会让初学者感到困惑,但它是语言实现的一种选择,同时也为后续的改进(比如 ES6 中的 letconst,它们也存在提升但有“暂时性死区”)打下了基础。

16、ES6模块与CommonJS模块有什么异同

它们都是JS里面的模块化系统,用于实现代码的分离、组织和重用。

ES Module:是ECMAScript2015(ES6)引入的官方模块系统,已经是JS标准的一部分。模块的依赖关系在编译时就能确定。语法是通过export导出变量或者函数,通过import引入其他模块。

1
2
3
4
5
6
7
8
// 导出
export const value = 42;
export function greet() {
console.log('Hello');
}

// 导入
import { value, greet } from './module.js';

CommonJS:是Node.js中使用的模块系统,适合服务器端环境。模块在加载时采用同步加载,在运行时解析依赖,适合于服务器环境的同步 I/O 操作。语法是通过module.exports或者exports导出。通过require引入模块。

1
2
3
4
5
6
7
8
9
10
11
12
// 导出
module.exports = {
value: 42,
greet: function() {
console.log('Hello');
}
};

// 导入
const module = require('./module.js');
console.log(module.value);
module.greet();

它们的差异:

  • 语法差异

    • es6模块:使用exportexport default 关键字导出,import导入。

      1
      2
      3
      4
      5
      // 命名导出
      export const foo = 'bar';
      export function func() { ... };
      //默认导出 只能有一个
      export default function() { ... };
      1
      2
      3
      4
      //导入命名导出  解构语法
      import { foo, func } from './module.js';
      //导入默认导出
      import myFunc from './module.js';
    • CommonJS:使用module.exports或者exports导出,require导入。

      exportsmodule.exports的简写,只能用于添加属性。

      1
      2
      3
      4
      5
      // 导出 只能有一个
      module.exports = { value: 42 };
      // 导出多个命名成员
      exports.foo = "Hello";
      exports.bar = "World";
      1
      const moduleA = require('./moduleA');
  • 加载和执行的时机

    es6:静态加载,编译时就确定模块依赖关系,可以进行静态分析。支持异步加载。

    commonjs:同步加载,动态加载,require() 可出现在代码任意位置。在require调用时,会立即加载并执行对应模块。适用服务器端,因为文件IO操作通常是同步的。

  • 作用域与变量绑定

    es6:实时绑定。导出的是值的引用,模块内部变化会同步到导入方。当模块内部的值变化时,导入的模块可以感知变化。自动采用严格模式,提高代码健壮性。导入的是实时的引用(live binding),看到的是原始变量的当前状态;这种引用是只读的(不能整体重赋值),但如果值是对象,修改内部属性仍然可行。

    commonjs:模块内部的变量是私有的,通过require的引入其实是模块导出对象的拷贝。一旦加载完成,再改变内部变量,不会反映在其他模块中导入的值上。导入时类似“浅拷贝”:拿到的是一个快照,如果整个对象被重新赋值,导入的绑定不会改变,但内部属性修改仍然共享。

  • 循环依赖的处理

    es6:依赖关系在编译阶段就已确定,采用实时绑定,可以在模块间动态同步状态。
    但要注意,在模块尚未完全初始化前使用导入的变量可能会出现问题,因此也需要谨慎设计模块间的调用顺序。

    commonjs:模块加载是同步的,缓存机制可能导致在循环依赖中获取到部分初始化的导出对象。
    解决循环依赖的关键在于重新组织代码结构或延迟加载。

共同点

  • 目的一致:两者都用于将代码分割成独立的模块,实现职责分离和代码重用。
  • 对象属性修改:无论采用哪种模块系统,如果模块导出的是一个对象,导入后都可以修改这个对象内部的属性值(前提是对象本身没有被重新赋值)。

17、常见的DOM操作有哪些

获取元素

通过ID:document.getElementById

通过标签名: document.getElementsByTagName

通过类名:document.getElementsByClassName

通过css选择器:document.querySelectordocument.querySelectorAll

创建元素document.createElement

插入元素

​ 父元素末尾插入:parentElement.appendChild

​ 指定元素前插入:parentElement.insertBefore(newElement, referenceElement)

删除元素

​ 删除子元素:parentElement.removeChild

​ 直接删除元素:element.remove()

修改元素:

​ 修改元素内容:element.textContentelement.innerHTML

​ 修改元素属性:element.setAttributeelement.getAttributeelement.removeAttribute

​ 修改样式:element.style.element.classList.addelement.classList.removeelement.classList.toggleelement.classList.contains

遍历元素:

​ 获取父元素: element.parentElement

​ 获取子元素:element.childrenelement.firstElementChildelement.lastElementChild

​ 获取兄弟元素:element.nextElementSiblingelement.previousElementSibling

事件操作

​ 绑定事件:element.addEventListener

​ 移除事件:element.removeEventListener

18、 use strict是什么

use strict是JS里面的一条指令,启动严格模式。能帮助写出更健壮、更安全的代码。

1
"use strict";

可以在脚本的最开始添加,或者在函数内部启用。

特点:

  • 捕获错误:比如使用未声明的变量会报错 // ReferenceError: a is not defined

    在非严格模式下,直接给未声明的变量赋值,会自动创建一个全局变量;

  • 普通函数调用,this 关键字的绑定:

非严格模式,默认指向全局对象(window)

严格模式,则为undefined。迫使开发者在使用 this 时更加明确其绑定对象,减少潜在的错误。

  • 禁止重复的参数名和对象属性

  • 禁止删除变量、函数或参数

  • 禁用 with 语句(with 语句会改变作用域链,容易引起混乱和难以调试的问题)

  • 部分标识符被视为保留字,不能用作变量名或函数名。例如public

19、如何判断一个对象是否属于某个类

1、使用instance of运算符

检查对象的原型链里面是否包含某个构造函数的prototype对象。

instance of 的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function myInstanceof(object, constructor) {
if (typeof object !== "object" || object === null) {
return false;
}

let proto = Object.getPrototypeOf(object);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}

return false;
}
1
console.log(person instanceof Person);

还有类class的情况

1
2
3
4
5
6
7
8
class Animal {
constructor(species) {
this.species = species;
}
}

const animal = new Animal('dog');
console.log(animal instanceof Animal); // 输出: true

2、检查原型链

1
console.log(Person.prototype.isPrototypeOf(person));

3、比较constructor属性

每个对象都有一个constructor属性,指向创建对象的构造函数。

但这种方法可能会受到构造函数属性被修改的影响,所以不如 instanceof 稳定。

1
console.log(person.constructor === Person); // 输出: true

20、强类型语言和弱类型语言的区别

强类型语言:变量和表达式都有严格的类型限制,不同类型之间不能随意混用或者隐式转换,所有的类型转换必须明确的进行。这种严格性可以在编译时或者运行时捕获错误。

弱类型语言:允许自动隐式转换。代码更简洁,不需要频繁显式声明类型、转换类型。比如数值与字符串的相加。

21、解释性语言和编译型语言的区别

编译语言:(C、C++、Go )

需要通过编译器把源代码转换为可以直接执行的机器码。

可执行文件在操作系统上直接运行,不需要额外的中间解释步骤,运行速度通常较快。

1
源代码 --> [编译器] --> 机器码(可执行文件)

优点:

  • 执行效率高:编译后的机器码直接运行,省去了运行时解释的开销。

  • 提前发现错误:编译器在编译阶段会进行语法、类型等检查,能在运行前捕获部分错误。

缺点:

  • 编译时间:大规模项目可能需要较长的编译时间。
  • 平台依赖性:生成的机器码通常与平台相关,不同操作系统和硬件需要不同版本的可执行文件。
  • 调试不够灵活:编译后的代码和源代码之间的映射可能不直观,调试时需要借助符号表等辅助工具。

解释语言(Python、JavaScript)

在运行时由解释器逐行读取源代码,即时执行。将每一行代码翻译成机器码后马上执行。

不需要事先生成独立的可执行文件。执行速度可能较编译型语言慢一些。

1
源代码 --> [解释器] --> 执行(逐行解释)

优点:

  • 跨平台性好:只要有相应平台的解释器,源代码可以直接运行,不依赖平台特定的机器码。
  • 开发调试便捷:无需编译,可以快速修改代码并立即看到效果,适合快速原型开发。
  • 动态性高:支持动态类型、动态绑定等特性,使得语言更灵活。

缺点:

  • 执行效率较低:每次运行都需要解析源代码,存在额外的解释开销。
  • 错误发现较晚:某些错误只能在运行时才会暴露,可能影响程序稳定性。

混合模式
现代语言和执行环境往往采用编译与解释相结合的方式,例如:

  • Java:先将源代码编译成平台无关的字节码,再由 Java 虚拟机(JVM)解释执行或采用即时编译(JIT)技术将热点代码编译成本地机器码。
  • JavaScript:传统上是解释执行,但现代引擎(如 V8、SpiderMonkey)使用 JIT 技术提高执行效率。一旦检测到某部分代码频繁执行,引擎就会将其编译成机器码,提高后续执行速度。

22、什么是尾调用,使用尾调用有什么好处?

尾调用:在函数地方最后一步直接调用另一个函数(或者自身)。

递归调用是函数最后一步,不需要额外计算。

1
2
3
4
function tailCallExample(x, y) {
// 在这里没有其他操作,直接返回 f(x, y) 的结果
return f(x, y);
}

好处:

  • 减少调用栈的使用:可以复用当前函数的调用帧,避免调用栈不断增长。
  • 提高性能:由于不需要为每一次递归调用都分配和销毁调用帧,尾调用优化能减少内存分配和回收的开销,提升程序执行效率。
  • 支持无限递归:论上无限深的递归(例如遍历一个非常大的数据结构),尾调用优化使得这种递归成为可能,因为不会导致栈溢出。

调用栈(Call Stack):管理函数调用的内存区域,以“栈”(后进先出)的结构管理所有正在执行的函数调用。

调用帧(Stack Frame):每当一个函数被调用时,程序会为该函数创建一个“调用帧”。这个帧保存了函数执行时所需的所有信息:函数参数、局部变量、返回程序地址。

调用帧确保当函数调用嵌套(包括递归)时,每个函数都有自己的执行环境。函数结束时,它对应的调用帧会从调用栈中移除,从而恢复到之前的状态。

当一个函数调用另一个函数时,就会创建新的调用帧。如果函数递归调用自己而没有进行尾调用优化,每次递归都需要分配新的帧。随着递归深度增大,调用栈上的帧也会越来越多,这可能导致内存不足(栈溢出)。

尾调用优化的核心思想是:

如果函数的最后一步是调用另一个函数(或自身),当前函数调用结束后不再需要做其他操作,那么就可以不创建新的帧,而是复用当前的帧。

23、for…in和for…of的区别

image-20250218153544246

遍历对象属性:如果使用for..in,需要结合hasOwnProperty过滤继承属性!

1
2
3
4
5
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key, obj[key]);
}
}

遍历数组和其他可迭代对象:推荐使用 for...of,因为它更直观,且不会受到对象属性遍历时的顺序问题影响。普通的对象用for..of遍历是会报错的,如果需要遍历的对象是类数组对象,用Array.from转成数组即可。

24、 ajax、axios、fetch的区别

AJAX (XMLHttpRequest)

浏览器内置的 XMLHttpRequest 对象来实现前后端数据交互。基于回调函数。

原生支持:几乎所有浏览器都支持 XMLHttpRequest

非 Promise 化:默认 API 基于回调,代码结构较为繁琐,错误处理不够直观。

1
2
3
4
5
6
7
8
9
10
11
12
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data',true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('成功:', xhr.responseText);
} else {
console.error('请求出错:', xhr.status);
}
}
};
xhr.send();

Fetch

ES6 后新增的浏览器原生 API,采用 Promise 方式封装 HTTP 请求。

基于 Promise:使用 Promise 链式调用,方便处理异步请求和错误。

更简洁的语法:写法直观,不需要处理复杂的状态机(如 readyState)。

错误处理需要注意:默认只有网络错误会导致 Promise 被 reject,对于 HTTP 错误(如 404、500)不会自动 reject,需要手动判断 response.ok

浏览器兼容性:现代浏览器支持较好,但老版本浏览器可能需要 polyfill。

1
2
3
4
5
6
7
8
9
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error('HTTP error, status = ' + response.status);
}
return response.json();
})
.then(data => console.log('成功:', data))
.catch(error => console.error('请求出错:', error));

Axios

基于 Promise 的第三方 HTTP 请求库

基于 Promise:与 Fetch 类似,代码风格现代且易于理解

自动转换 JSON:响应数据默认会自动转换成 JSON 对象,无需手动调用 .json()

拦截器支持:可以在请求和响应时进行预处理(如添加 token、统一处理错误)。

更丰富的配置:支持请求超时、取消请求、配置 baseURL、并发请求等。

兼容性:封装了底层实现,适用于大部分浏览器和 Node.js 环境。

1
2
3
4
5
6
7
axios.get('https://api.example.com/data')
.then(response => {
console.log('成功:', response.data);
})
.catch(error => {
console.error('请求出错:', error);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
// 添加请求拦截器
axios.interceptors.request.use(config => {
// 在请求发送前处理,比如添加认证 token
config.headers.Authorization = 'Bearer YOUR_TOKEN';
return config;
}, error => Promise.reject(error));

// 添加响应拦截器
axios.interceptors.response.use(response => response, error => {
// 统一错误处理
console.error('响应错误:', error);
return Promise.reject(error);
});

25、addEventListener()方法的参数和使用

这是一个向DOM元素添加事件监听器的方法~!

当指定的事件发生时,会执行回调函数。

1
element.addEventListener(type, listener, options);

type(必选):字符串,指定要监听的事件类型,如 "click""mouseover""keydown" 等。

1
2
// 监听点击事件
element.addEventListener("click", callback);

listener(必选):当事件发生时被调用的回调函数,函数要接收一个事件对象作为参数。

1
2
3
4
function handleClick(event) {
console.log("元素被点击", event);
}
element.addEventListener("click", handleClick);

options(可选):

旧版用法(布尔值 useCapture):一个布尔值,指定是否在捕获阶段调用事件处理程序。如果为 true,则在事件捕获阶段调用;默认为 false,在冒泡阶段调用。

现代用法(options 对象)

是一个对象,可以配置多个选项:

  • capture:布尔值,和 useCapture 功能一样,指定事件在捕获阶段触发。
  • once:布尔值,如果为 true,监听器在第一次触发后会自动移除。
  • passive:布尔值,表示监听器永远不会调用 preventDefault(),有助于提高滚动性能。
1
2
3
4
5
element.addEventListener("click", handleClick, {
capture: false, // 在冒泡阶段触发
once: true, // 只执行一次
passive: true // 提示不会调用 preventDefault()
});

可以使用 removeEventListener 方法移除监听器

事件传播阶段

事件在 DOM 树中传播分为三个阶段:捕获阶段、目标阶段和冒泡阶段。

通过设置 capture 选项(或旧的 useCapture 参数),可以指定事件处理程序在捕获阶段或冒泡阶段被调用。

三个阶段在做什么

捕获阶段:当一个事件发生时(例如点击一个按钮),浏览器会从 document根节点开始,沿着 DOM 树从最外层的元素逐级传递事件,直到到达事件的目标元素。监听器需设置 capture: true 才能在此阶段触发。

目标对象:当事件到达目标元素时,进入目标阶段。在这一阶段,目标元素本身既处于捕获阶段的末尾,也处于冒泡阶段的开始。所有监听器均会触发。

冒泡阶段:事件在目标阶段处理完毕后,会从目标元素开始,沿着 DOM 树向上逐级传递回根元素(即 document),这个过程称为冒泡阶段。这个阶段,事件会依次触发父级元素上注册的监听器。

事件类型

  1. 鼠标事件
    click:鼠标单击事件。
    dblclick:鼠标双击事件。
    mousedown:鼠标按下事件。
    mouseup:鼠标释放事件。
    mousemove:鼠标移动事件。
    mouseover / mouseout:鼠标进入/离开元素事件。
    mouseenter / mouseleave:类似于 mouseover/mouseout,但不冒泡。
    contextmenu:右键菜单事件。
  2. 键盘事件
    keydown:按键按下时触发。
    keyup:按键释放时触发。
    keypress:按键产生字符时触发(现已逐渐废弃)。
  3. 表单及输入事件
    submit:表单提交事件。
    change:表单元素的值发生变化时触发(下拉列表、复选框、文本框等)。
    input:输入框内容发生变化时触发。
    focus:元素获得焦点时触发。
    blur:元素失去焦点时触发。
  4. 窗口/文档事件
    load:页面或图像等资源加载完成时触发。
    unload:页面卸载时触发。
    beforeunload:在页面卸载之前触发(常用于提示用户)。
    resize:窗口尺寸改变时触发。
    scroll:滚动条滚动时触发。
    DOMContentLoaded:初始的 HTML 文档被完全加载和解析完成后触发。
  5. 触摸事件(移动端)
    touchstart:触摸开始时触发。
    touchmove:触摸移动时触发。
    touchend:触摸结束时触发。
    touchcancel:触摸因某种原因中断时触发。
  6. 指针事件(支持鼠标、触控、笔输入)
    pointerdown、pointerup、pointermove、pointercancel 等。
  7. 拖拽事件
    drag、dragstart、dragenter、dragover、dragleave、drop、dragend。
  8. 动画与过渡事件
    animationstart、animationend、animationiteration。
    transitionend。
  9. 媒体事件
    play、pause、ended:视频或音频播放控制。
    timeupdate:媒体播放位置更新时触发。
    volumechange:音量变化时触发。
    canplay:足够的数据已经加载,可以开始播放时触发。
    error:媒体加载或播放过程中发生错误时触发。
  10. 其他事件
    error:加载资源(图片、脚本等)失败时触发。
    wheel:鼠标滚轮滚动事件。