前端杂学录(一)

记录看到的前端知识

1.constletvar 是 JavaScript 中用于声明变量的三种方式,它们之间有一些重要的区别和用法。

1. var

  • 作用域var 声明的变量是函数作用域或全局作用域。在块级作用域(如 iffor 等)中声明的 var 变量在外部仍然可访问。
  • 提升var 变量会被提升到作用域的顶部,但值是 undefined,直到赋值语句执行。
console.log(x); // 输出: undefined
var x = 5;
console.log(x); // 输出: 5

if (true) {
    var y = 10;
}
console.log(y); // 输出: 10 (y 在外部可访问)

2. let

  • 作用域let 声明的变量是块级作用域,只在其所在的块内可访问。
  • 提升let 变量也会被提升,但在声明之前无法访问,会导致 ReferenceError
// console.log(z); // 会报错: Cannot access 'z' before initialization
let z = 5;
console.log(z); // 输出: 5

if (true) {
    let a = 10;
    console.log(a); // 输出: 10
}
// console.log(a); // 会报错: a is not defined

 3. const

  • 作用域const 的作用域与 let 相同,也是块级作用域。
  • 赋值const 用于声明常量,必须在声明时初始化,并且一旦赋值后不能被重新赋值。对象和数组的内容可以修改,但引用不能改变。
const b = 5;
// b = 10; // 会报错: Assignment to constant variable.

const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许修改对象的属性
console.log(obj.name); // 输出: Bob

// const arr = [1, 2, 3];
// arr = [4, 5, 6]; // 会报错: Assignment to constant variable.
// arr.push(4); // 允许修改数组内容
console.log(arr); // 输出: [1, 2, 3, 4]

2.  continue用于跳过本次循环,执行下一次循序。break用于彻底退出整个循环 

3.JavaScript规定了几种语言类型

  • 原始类型(Primitive Types)

    • Undefined:一个未定义的值。
    • Null:表示“无”或“空”值。
    • Boolean:布尔值,只有两个取值:truefalse
    • Number:数字类型,包括整数和浮点数。
    • BigInt:用于表示大于 Number.MAX_SAFE_INTEGER 的整数。
    • String:字符串类型,用于表示文本。
    • Symbol:用于创建独一无二的标识符。
  • 引用类型(Reference Types)

    • Object:用于表示对象,包括数组、函数、日期等。

4.js中,数组的那个方法可以得到数组的元素和下标

在 JavaScript 中,Array.prototype.forEach() 方法可以同时获取数组的元素和下标。它接受一个回调函数,回调函数有三个参数:元素值(value)、索引(index)和整个数组(array)。

也可以使用 Array.prototype.map()Array.prototype.entries() 方法来获得元素和下标。entries() 方法返回一个包含数组键值对的迭代器

5.JavaScript对象的底层数据结构是什么

JavaScript 对象的底层数据结构是基于**哈希表(Hash Table)**的实现。对象在 JavaScript 中是一种键值对集合,它的属性(键)可以是字符串或 Symbol,而属性的值可以是任意类型的数据。

详细解释:

  1. 键值对存储: JavaScript 对象的每个属性都由键(Key)和对应的值(Value)组成。键通常是字符串(也可以是 Symbol),值可以是任何数据类型。在对象内部,键会被转换为某种唯一标识符,以方便快速查找。

  2. 哈希表结构: 对象的底层采用了类似哈希表的数据结构。当我们向对象添加键值对时,JavaScript 引擎会通过一个哈希函数将键映射到对象的内部存储位置。通过这种方式,JavaScript 可以在常数时间复杂度内(O(1))完成对对象属性的查找、添加和删除。

  3. 属性查找: 当访问对象属性时,JavaScript 引擎会使用哈希函数来计算键对应的位置,然后直接查找到该属性的值。这种查找过程非常高效。

  4. 隐藏类和属性优化(JIT优化): 虽然 JavaScript 对象的基本实现是基于哈希表,但现代 JavaScript 引擎(如 V8 引擎)使用了一些优化技术,提升对象访问性能。例如,JavaScript 引擎可能会为对象创建隐藏类(Hidden Class)或转为类数组结构来加速常用对象的访问。对于对象的不同属性组合,JavaScript 引擎可能会动态生成不同的类结构,以加速相同结构的对象的处理。

对象存储的扩展:

  • 普通对象(Plain Object):使用哈希表存储键值对,键为字符串或 Symbol
  • 数组(Array):数组实际上也是一种对象,但它的键是数字索引,底层可能会采用不同的优化方式(如稀疏数组处理)。
  • MapSet:这两种数据结构也是基于哈希表实现的,但它们比普通对象支持更多的数据类型作为键。

6.JavaScript 中的 MapSet

JavaScript 提供了 MapSet,它们是高级的数据结构,内部使用哈希表来实现,允许高效地存储、查找和删除数据。

Map

Map 是一个键值对的集合,它允许任何类型的键(包括对象、数组等),与传统对象不同,Map 的键不仅限于字符串或 Symbol 类型。

用法:
  • 创建一个 Map
const map = new Map();

添加键值对

map.set('name', 'Alice'); // 键是字符串
map.set(1, 'Number 1');    // 键是数字
map.set({id: 2}, 'Object'); // 键是对象

获取值

console.log(map.get('name')); // 输出: Alice

删除键值对

map.delete('name');

检查键是否存在

console.log(map.has(1)); // 输出: true

遍历 Map

map.forEach((value, key) => {
  console.log(key, value);
});

// 或者用 for...of
for (const [key, value] of map) {
  console.log(key, value);
}

Map 的特性

  • 保留键值对插入的顺序。
  • 键可以是任何类型,而不仅限于字符串。
Set

Set 是一个存储唯一值的集合,类似于数组,但不允许重复的元素。Set 内部也是通过哈希表实现的,因此查找和删除操作效率很高。

用法:
  • 创建一个 Set
    const set = new Set();
    

    添加值

    set.add(1);
    set.add(5);
    set.add(1); // 重复的值会被忽略
    

    检查值是否存在

    console.log(set.has(1)); // 输出: true
    console.log(set.has(3)); // 输出: false
    

    删除值

    set.delete(5);
    

    遍历 Set

    set.forEach(value => {
      console.log(value);
    });
    
    // 或者用 for...of
    for (const value of set) {
      console.log(value);
    }
    

    Set 转换为数组

    const array = [...set]; // 使用扩展运算符
    

    Set 的特性

  • 值是唯一的,不能重复。
  • 不保证元素插入的顺序(ES6 规范保证了顺序的保留,但旧版本浏览器可能不支持)

对比 Map 和普通对象

特性Map普通对象 (Object)
键类型任意类型(对象、函数、基本类型)只能是字符串或 Symbol
键的顺序保留插入顺序无保证(但大部分浏览器保留顺序)
高效性通过哈希表实现,查找和删除速度快查找和删除速度稍慢
可迭代性原生支持迭代(for...of不支持原生迭代,需手动处理

7.js中还有那些方法的参数是固定的

1. 数组方法(Array Methods)

forEach()
  • 参数顺序: value, index, array
  • 用于遍历数组中的每一个元素。
map()
  • 参数顺序: value, index, array
  • 用于对数组中的每个元素执行函数并返回新数组。
filter()
  • 参数顺序: value, index, array
  • 用于筛选数组中的元素,返回满足条件的元素组成的新数组。
reduce()
  • 参数顺序: accumulator, currentValue, currentIndex, array
  • 用于将数组中的元素汇总为单个值。
some() / every()
  • 参数顺序: value, index, array
  • some():检查数组中是否有任意元素满足条件。
  • every():检查数组中是否所有元素都满足条件。
find() / findIndex()
  • 参数顺序: value, index, array
  • find():返回第一个满足条件的元素。
  • findIndex():返回第一个满足条件的元素的索引。

2. Object 方法

Object.entries()
  • 返回[key, value] 数组的迭代器。
  • 将对象的键值对转换为数组,每个元素是 [key, value]
Object.keys()
  • 返回:对象的键组成的数组
Object.values()
  • 返回:对象的值组成的数组。

3. MapSet 方法

map.forEach()
  • 参数顺序: value, key, map
  • 遍历 Map 中的每个键值对。
set.forEach()
  • 参数顺序: value, valueAgain, set
  • 遍历 Set 中的每个元素,valuevalueAgain 是相同的,因为 Set 没有键,只有值。

4. Promise 方法

Promise.then()
  • 参数顺序: onFulfilled, onRejected
  • 用于处理 Promise 成功和失败的回调。
Promise.catch()
  • 参数顺序: onRejected
  • 用于处理 Promise 的失败回调。
Promise.finally()
  • 参数顺序: callback
  • 无论 Promise 是成功还是失败,都会调用 finally 的回调。

5. 事件监听器(Event Listener)

addEventListener()
  • 参数顺序: event, callback, options
  • 用于为 DOM 元素添加事件监听器。

6. 定时器方法

setTimeout()
  • 参数顺序: callback, delay, ...args
  • 在指定的延迟后执行回调函数。

 setInterval()

  • 参数顺序: callback, delay, ...args
  • 以固定的时间间隔重复执行回调函数。

 8.手动实现一个简单的 Symbol

let idCounter = 0;

function MySymbol(description) {
  const uniqueId = `@@Symbol(${description})_${idCounter++}`;
  return uniqueId;
}

const sym1 = MySymbol('test');
const sym2 = MySymbol('test');

console.log(sym1 === sym2); // false, 它们是不同的唯一标识符
console.log(sym1); // @@Symbol(test)_0
console.log(sym2); // @@Symbol(test)_1
分析:
  1. 唯一性:通过维护一个计数器 idCounter,每次调用 MySymbol() 时都会返回一个不同的唯一字符串,即使描述符相同也不会重复。
  2. 描述符:我们可以为每个 Symbol 提供一个可选的描述符,类似于 JavaScript 的 Symbol

9.JavaScript中的变量在内存中的具体存储形式

1. 内存的基本结构

JavaScript 的内存模型可以分为两个区域:

  • 栈内存(Stack):用于存储简单的、大小固定的数据(如原始类型和函数调用的上下文)。
  • 堆内存(Heap):用于存储复杂的数据(如对象、数组),这些数据大小不固定,会动态分配内存。

2. 原始类型的存储

原始类型(Primitive Types)的数据直接存储在栈内存中。它们包括:

  • Number(数字)
  • String(字符串)
  • Boolean(布尔)
  • Null(空值)
  • Undefined(未定义)
  • Symbol(符号)
  • BigInt(大整数)

栈内存是一种高效的内存结构,分配和释放速度非常快。对于这些原始类型,变量直接保存它们的值

特点:
  • 存储在栈内存中的原始值是不可变的(尤其是 String 类型,虽然它表现为可以改变,但每次操作都会返回一个新的字符串)。
  • 变量直接保存数据的值,访问时直接返回值,操作非常快速。

3. 引用类型的存储

引用类型(Reference Types)包括:

  • Object(对象)
  • Array(数组)
  • Function(函数)
  • 其他如 DateRegExp 等复杂数据类型

这些数据类型的值在堆内存中存储。变量本身并不直接存储值,而是存储一个引用,这个引用指向存储在堆内存中的数据。

特点:
  • 引用类型的数据存储在堆中,变量保存的是对堆内存的引用
  • 访问对象、数组等引用类型时,实际上是通过栈中的引用去查找堆中的数据。
  • 当多个变量引用同一个对象时,它们指向的都是堆内存中的同一块地址。

4. 内存分配与垃圾回收

JavaScript 是一种自动管理内存的语言,这意味着开发者不需要手动分配或释放内存。JavaScript 引擎使用垃圾回收机制来管理内存的释放。

内存分配
  • 原始类型:直接在栈中分配空间,因为它们的大小是固定的,存取速度也很快。
  • 引用类型:需要在堆中分配内存,堆内存的分配是动态的,适合存储复杂和大小不固定的数据结构。
垃圾回收

JavaScript 的垃圾回收机制(如常用的标记清除法)会定期检查哪些对象不再被引用,然后释放其占用的内存。

  • 当一个变量不再引用任何堆内存中的对象时,该对象会被标记为“可回收”。
  • 当垃圾回收器运行时,它会清理这些不再使用的对象,释放内存。

5. 原始类型与引用类型的区别

特性原始类型引用类型
存储位置栈内存堆内存
变量存储的内容直接存储值存储堆内存地址的引用
值的大小固定(相对较小)动态(大小可变)
拷贝行为复制值复制引用
访问速度较慢

6. 示例:拷贝行为

原始类型的拷贝:

原始类型的变量是值的拷贝,两个变量之间不会相互影响。

引用类型的拷贝:

引用类型的变量是引用的拷贝,两个变量指向同一个对象,修改其中一个会影响另一个。

7. 内存泄漏

尽管 JavaScript 有垃圾回收机制,但某些情况下可能会出现内存泄漏。常见的内存泄漏原因包括:

  • 全局变量:全局变量不会被垃圾回收器回收,可能会导致内存长期占用。
  • 闭包:如果闭包中保存了对外部变量的引用,可能会导致这些变量无法被释放。
  • DOM 引用:删除 DOM 节点时,如果仍然有 JavaScript 引用指向该节点,它就不会被回收。

总结

  • 原始类型:存储在栈内存中,值是直接存储的,数据大小固定,访问速度快。
  • 引用类型:存储在堆内存中,变量保存的是对数据的引用,数据大小动态变化,适合复杂的数据结构。
  • 垃圾回收:JavaScript 自动管理内存,使用垃圾回收器清理不再使用的对象,以防止内存泄漏。

10.基本类型对应的内置对象,以及他们之间的装箱拆箱操作

1. 基本类型与对应的内置对象

JavaScript 中有 6 种基本类型,每种基本类型都有其对应的内置对象:

基本类型对应的内置对象
StringString
NumberNumber
BooleanBoolean
SymbolSymbol
BigIntBigInt
Null
Undefined
说明:
  • NullUndefined 没有对应的内置对象,因此它们不能通过装箱操作转化为对象。
  • 其他基本类型都有内置对象,允许我们调用某些方法,如 StringlengthNumbertoFixed() 等。

2. 装箱(Boxing)

装箱是指将一个基本类型转换为其对应的对象类型。这是 JavaScript 引擎在幕后自动进行的,因此开发者不需要显式地进行转换。

例子:自动装箱

当你对一个基本类型调用方法时,JavaScript 引擎会自动将基本类型“装箱”为其对应的包装对象:

const str = "hello";
console.log(str.length);  // 5

在这段代码中,str 是一个基本的 String 类型,但我们调用了 str.length。JavaScript 在幕后自动将 str 装箱为 new String("hello"),然后在这个包装对象上查找 length 属性。

装箱过程大致如下:

const str = "hello";
const tempStrObj = new String(str); // 临时将基本类型转换为对象
console.log(tempStrObj.length);     // 获取 length 属性
tempStrObj = null;                  // 然后销毁临时对象

同样的,数字和布尔值也会在调用方法时自动装箱:

const num = 42;
console.log(num.toFixed(2));  // 输出: "42.00"

const bool = true;
console.log(bool.toString()); // 输出: "true"

3. 拆箱(Unboxing)

拆箱是指将一个包装对象转换为它的基本类型值。当你将对象用于需要基本类型的上下文中时,JavaScript 会自动执行拆箱操作。

例子:自动拆箱
const numObj = new Number(123);
console.log(numObj + 1);  // 124

在这段代码中,numObj 是一个 Number 对象,但当它与 1 相加时,JavaScript 自动将其拆箱为基本的 Number 类型,即 123,然后进行加法运算。

4. 手动装箱和拆箱

虽然 JavaScript 会自动进行装箱和拆箱,但你也可以手动创建包装对象。

手动装箱

你可以通过使用内置对象的构造函数手动创建包装对象:

const strObj = new String("hello");
const numObj = new Number(123);
const boolObj = new Boolean(false);

console.log(typeof strObj); // "object"
console.log(typeof numObj); // "object"
console.log(typeof boolObj); // "object"

需要注意的是,手动创建的包装对象是对象类型,而不是基本类型。

手动拆箱

包装对象可以通过 .valueOf() 方法或隐式上下文被拆箱为基本类型:

const strObj = new String("hello");
const numObj = new Number(123);

console.log(strObj.valueOf()); // "hello"(拆箱为基本类型字符串)
console.log(numObj.valueOf()); // 123(拆箱为基本类型数字)

console.log(typeof strObj.valueOf()); // "string"
console.log(typeof numObj.valueOf()); // "number"

5. 装箱与拆箱的应用场景

装箱和拆箱通常是在 JavaScript 引擎内部自动处理的,开发者通常不需要手动处理这些转换。但是理解它们在以下场景中很有帮助:

  • 基本类型方法调用:基本类型不能直接调用方法,但 JavaScript 会自动装箱以允许你这样做,例如字符串的 .length 属性或数字的 .toFixed() 方法。

  • 操作包装对象:如果你手动创建了包装对象(如 new String()),要小心它的行为与基本类型不同。例如,比较对象和基本类型时可能产生意外结果。

6. 包装对象的陷阱

使用包装对象时,有一些潜在的陷阱需要注意:

1. new Boolean() 的问题

当你使用 new Boolean(false) 创建一个 Boolean 对象时,尽管值是 false,对象的本质依然为 true,因为对象在 JavaScript 中总是被视为 true

2. 引用比较

包装对象和基本类型在比较时会出现不同的行为。包装对象是引用类型,因此它们的比较会检查是否是相同的引用,而不是值相同。

7. 总结

  • 装箱(Boxing):将基本类型转换为包装对象,以便能够像对象一样调用方法(如 toString()length)。这个过程通常是自动的。
  • 拆箱(Unboxing):将包装对象转换回基本类型。通常在需要基本类型的上下文中自动发生(如运算操作或显式调用 .valueOf())。
  • 避免手动创建包装对象:一般情况下,你不需要手动使用包装对象构造函数(如 new Number()new String()),因为它们可能会导致混淆和不必要的复杂性。

11.理解值类型和引用类型

1. 值类型(Primitive Types)

值类型是指简单的数据类型,直接存储其值。这些类型的特点是:

  • 存储位置:值类型的变量直接在栈内存中存储数据。
  • 不可变性:值类型的数据是不可变的,任何对其的修改都会生成一个新的值,而不是改变原来的值。
  • 比较行为:当比较两个值类型时,会比较它们的实际值。
常见的值类型
  • Number:数字类型(如 13.14
  • String:字符串类型(如 "hello"
  • Boolean:布尔类型(truefalse
  • Null:表示空值
  • Undefined:表示未定义的值
  • Symbol:唯一的标识符
  • BigInt:表示大整数

2. 引用类型(Reference Types)

引用类型是指复杂的数据类型,存储的是对内存中实际数据的引用。这些类型的特点是:

  • 存储位置:引用类型的变量在栈内存中存储的是一个指向堆内存的引用(地址),而实际的数据存储在堆内存中。
  • 可变性:引用类型的数据是可变的,修改对象的属性会影响所有指向该对象的引用。
  • 比较行为:当比较两个引用类型时,比较的是它们的引用(地址),而不是实际值。
常见的引用类型
  • Object:对象类型(如 { key: 'value' }
  • Array:数组类型(如 [1, 2, 3]
  • Function:函数也是对象(如 function() {}

3. 值类型与引用类型的对比

特性值类型引用类型
存储方式直接存储值存储对象的引用(地址)
存储位置栈内存堆内存
复制行为复制值复制引用(指向同一内存地址)
可变性不可变可变(可以修改对象的属性)
比较行为比较实际值比较内存地址(引用)

4. 注意事项

  • 自动装箱与拆箱:在 JavaScript 中,值类型可以自动转换为对应的对象(装箱),如使用方法时自动将字符串转换为 String 对象,然后使用后自动拆箱回值。
  • 类型判断:使用 typeof 可以判断基本类型,但对引用类型(如数组、对象)可能返回 "object",需用 Array.isArray()instanceof 进行进一步判

12.null和undefined的区别

1. 定义

  • null

    • 表示“无”或“空值”,通常用于指示缺失的对象。
    • 是一个赋值类型,可以显式地将变量设置为 null
    • 类型是 object(这是一个语言设计上的历史遗留问题)。
  • undefined

    • 表示“未定义”,通常用于表示一个变量声明了但未赋值,或者一个对象没有相应的属性。
    • 是 JavaScript 的默认值,任何未初始化的变量或函数没有返回值时会自动成为 undefined
    • 类型是 undefined

2. 用法示例

let a;               // 声明了但未赋值,默认为 undefined
console.log(a);     // 输出: undefined

let b = null;       // 明确赋值为 null
console.log(b);     // 输出: null

3. 比较

  • 使用 == 比较:

    console.log(null == undefined); // 输出: true
    
    • 在非严格比较中,nullundefined 被视为相等。
  • 使用 === 比较:

console.log(null === undefined); // 输出: false

在严格比较中,二者类型不同,因此不相等。

4. 使用场景

  • null
    • 用于指示对象的缺失,通常在 API 设计中作为参数或返回值,表示“没有对象”。
  • undefined
    • 通常用于变量未初始化、函数没有返回值或者访问对象不存在的属性时。

13.至少可以说出三种判断JavaScript数据类型的方式,以及他们的优缺点,如何准确的判断数组类型

1. typeof 操作符

优点
  • 简单易用,能够快速判断基本数据类型(如 stringnumberbooleanundefinedfunction)。
缺点
  • 对于对象和数组,typeof 仅返回 "object",无法区分。
  • 对于 null 也会返回 "object",这是一个历史遗留问题。

2. instanceof 操作符

优点
  • 可以判断对象是否是某个构造函数的实例,适用于数组、对象、函数等。
  • 能够准确判断数组类型。
缺点
  • 对于跨 iframe 或不同执行环境的对象实例,可能会出现问题。
  • 只能判断对象的直接原型链,不适用于基本类型。

3. Object.prototype.toString.call()

优点
  • 可以准确判断任何数据类型,包括数组、正则表达式、日期等。
  • 结果比较一致,避免了 typeofinstanceof 的局限。
缺点
  • 使用稍显复杂,不够直观。
  • 需要调用方法,语法较长。

如何准确判断数组类型

最准确的判断数组类型的方法是使用 Array.isArray()Object.prototype.toString.call()

4. constructor 属性

优点
  • 直接通过对象的构造函数进行判断,语法相对简单。
缺点
  • 对于重写了 constructor 属性的对象,可能会产生误判。
  • 只适用于判断对象的构造函数,对于基本数据类型不适用。

5. 使用 instanceof 判断 nullundefined

优点
  • 简单且有效,能快速判断变量是否为对象。
缺点
  • nullundefined 的判断需要额外处理,因为 null 不会被视为对象。

6. 使用 typeofArray.isArray() 的组合

优点
  • 结合了两种方法的优点,可以在判断是对象的同时确认是否为数组。
缺点
  • 仍然需要注意 null 的情况,因为 typeof null 也是 object

7. JSON.stringify() 方法

优点
  • 可以用来判断 nullundefined,返回的字符串比较简单。
缺点
  • 这种方法不够直接,也可能导致误解,因为它只适用于特定情况。

8. 自定义类型判断函数

可以创建一个自定义函数,综合以上方法来判断数据类型:

function getType(variable) {
    if (variable === null) return 'null';
    if (Array.isArray(variable)) return 'array';
    return typeof variable; // 其他类型
}

console.log(getType([1, 2, 3]));  // 输出: "array"
console.log(getType(null));        // 输出: "null"
console.log(getType(42));          // 输出: "number"

14.可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用

1. 运算符的使用

  • 算术运算:当涉及不同类型的值时,JavaScript 会尝试将其转换为数字。

console.log('5' - 2); // 输出: 3(字符串 '5' 转换为数字)
console.log('5' + 2); // 输出: '52'(数字 2 转换为字符串)

比较运算:在使用 == 时,JavaScript 会进行类型转换。

console.log(0 == false); // 输出: true
console.log('' == false); // 输出: true

2. 条件判断

在条件语句中,所有值都会被转换为布尔值:

if ('') { // 空字符串被视为 false
  console.log('This will not run');
}

3. 函数参数

如果函数参数期望特定类型,而传入了不同类型,JavaScript 会进行转换:

function add(a, b) {
  return a + b;
}
console.log(add('5', 2)); // 输出: '52'(隐式转换为字符串)

隐式转换原则

  1. 优先转换为数字:在算术运算中,字符串会转换为数字。
  2. 相等比较时:使用 == 进行比较时,会尝试转换为相同类型。

如何避免或巧妙应用

  1. 使用严格相等(===:始终使用 ===!== 进行比较,以避免意外的类型转换。

  2. 明确类型转换:在需要时使用显式转换,确保数据类型符合预期。

  3. 使用 parseIntparseFloat:对于字符串到数字的转换,使用这些函数来确保转换方式。

  4. 避免空值的使用:尽量避免使用 nullundefined 作为比较或运算的参与者,确保变量有明确的值。

  5. 清晰的函数参数:在函数中使用参数类型检查,确保传入的值符合预期类型。

15.出现小数精度丢失的原因,JavaScript可以存储的最大数字、最大安全数字,JavaScript处理大数字的方法、避免精度丢失的方法

console.log(0.1 + 0.2); // 输出: 0.30000000000000004

这是因为 0.1 和 0.2 在二进制中不能精确表示,导致计算结果出现误差。

JavaScript 可以存储的最大数字

  • 最大数字Number.MAX_VALUE,约为 1.7976931348623157e+308
  • 最小数字Number.MIN_VALUE,约为 5e-324(接近于零,但不等于零)。

最大安全数字

  • 最大安全整数Number.MAX_SAFE_INTEGER,值为 9007199254740991(即 253−12^{53} - 1253−1)。
  • 最小安全整数Number.MIN_SAFE_INTEGER,值为 -9007199254740991

在这个范围内的整数可以准确地表示,而超过这个范围的整数可能会出现精度丢失。

JavaScript 处理大数字的方法

  1. 使用 BigInt:这是 ES2020 引入的一种新数据类型,可以表示任意大小的整数。

  2. 使用第三方库:可以使用如 decimal.jsbignumber.js 等库来处理高精度的小数和大数字。

避免精度丢失的方法

  1. 整数运算:尽量将小数转换为整数进行计算。例如,将所有金额以分为单位处理

  2. 使用 toFixed():虽然不能完全解决精度丢失,但可以控制小数点后位数

  3. 使用 Math.round():在进行浮点数运算后进行四舍五入

  4. 使用 BigDecimal 类型:如果你的应用需要极高的精度,考虑使用 BigDecimal 类型(如通过库实现)。

16.理解原型设计模式以及JavaScript中的原型规则

关键点
  • 原型对象:对象可以从其他对象继承属性和方法。
  • 对象的共享:通过原型共享方法和属性,节省内存。

JavaScript 中的原型规则

JavaScript 使用原型链来实现继承和对象的共享。这些规则可以总结为以下几点:

  1. 每个对象都有一个原型

    • 当你创建一个对象时,它会自动继承自 Object.prototype,或者其构造函数的 prototype 属性。
    • 可以通过 Object.getPrototypeOf(obj)obj.__proto__ 获取对象的原型。
  2. 原型链

    • 当访问对象的属性或方法时,JavaScript 会首先检查对象自身是否有该属性,如果没有,则沿着原型链向上查找。
    • 如果原型链上的对象也没有该属性,最终会查找到 null
  3. 构造函数与原型

    • 通过构造函数创建对象时,可以在构造函数的 prototype 属性上定义共享的方法和属性。
function Person(name) {
    this.name = name;
}

Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name}`);
};

const alice = new Person('Alice');
alice.greet(); // 输出: Hello, my name is Alice

        4. 原型属性的覆盖

  • 如果在对象中定义了与原型中同名的属性,优先访问对象自身的属性,原型中的属性被遮蔽。
  • 5.instanceof 运算符

  • 用于检查对象是否是某个构造函数的实例,底层是通过原型链实现的

17.理解JavaScript的作用域和作用域链

1. 全局作用域
  • 在 JavaScript 中,任何在全局上下文中声明的变量或函数都属于全局作用域。这些变量和函数可以在代码的任何位置访问。
  • 在浏览器中,全局作用域是 window 对象。
2. 局部作用域
  • 局部作用域是指在函数内部声明的变量或函数。这些变量只能在该函数内部访问。
  • 每个函数都有自己的作用域,局部作用域会优先于全局作用域。

作用域链

作用域链是一个机制,用于确定在特定上下文中变量的可访问性。每当代码执行时,JavaScript 引擎会创建一个执行上下文,并维护一个作用域链。

1. 链的结构
  • 当代码在某个执行上下文中运行时,作用域链会从当前作用域向上查找,直到找到变量或到达全局作用域。
  • 这意味着,如果在当前作用域中找不到某个变量,JavaScript 会在其父作用域中继续查找。
    let outerVar = 'I am outside';
    
    function outerFunction() {
        let innerVar = 'I am inside';
    
        function innerFunction() {
            console.log(innerVar); // 可以访问
            console.log(outerVar); // 可以访问
        }
    
        innerFunction();
    }
    
    outerFunction(); // 输出: I am inside \n I am outside
    

    2. 闭包

  • 当内部函数访问外部函数的变量时,就形成了闭包。闭包可以使得外部函数的变量在内部函数中保持状态,即使外部函数已经返回。
    function makeCounter() {
        let count = 0; // 局部变量
    
        return function() {
            count++; // 访问外部变量
            return count;
        };
    }
    
    const counter = makeCounter();
    console.log(counter()); // 输出: 1
    console.log(counter()); // 输出: 2
    

    18.instanceof typeof 的底层实现原理,手动实现一个instanceof typeof

instanceoftypeof 的底层实现原理

1. typeof

typeof 是一个操作符,用于检查变量的类型。它返回一个表示类型的字符串。

  • 实现原理
    • typeof 会根据变量的内部类型来返回相应的字符串。例如,原始类型(如 stringnumberbooleanundefined)和对象(如数组、函数、对象等)都有特定的表示方式。
      typeof 'hello'; // "string"
      typeof 42;      // "number"
      typeof true;    // "boolean"
      typeof undefined; // "undefined"
      typeof {};      // "object"
      typeof [];      // "object"
      typeof function() {}; // "function"
      
      2. instanceof

      instanceof 是一个操作符,用于检查对象是否是某个构造函数的实例。它通过查找对象的原型链来实现。

    • 实现原理
      • instanceof 会检查对象的 __proto__ 属性是否指向构造函数的 prototype 属性。如果找到了匹配,则返回 true,否则返回 false
        function Person() {}
        const alice = new Person();
        
        console.log(alice instanceof Person); // true
        console.log(alice instanceof Object);  // true
        

        手动实现 typeofinstanceof

        1. 手动实现 typeof

        可以通过 Object.prototype.toString.call() 来模拟 typeof 的部分功能:

        function customTypeof(value) {
            if (value === null) return 'null'; // 特殊情况
            return typeof value === 'object' ? 
                Object.prototype.toString.call(value).slice(8, -1).toLowerCase() : 
                typeof value;
        }
        
        console.log(customTypeof('hello')); // "string"
        console.log(customTypeof(42));      // "number"
        console.log(customTypeof(null));     // "null"
        console.log(customTypeof([]));       // "array"
        console.log(customTypeof({}));       // "object"
        console.log(customTypeof(function() {})); // "function"
        
        2. 手动实现 instanceof

        可以通过一个简单的函数来实现 instanceof 的功能:

        function customInstanceof(instance, constructor) {
            if (typeof instance !== 'object' || instance === null) return false;
            let prototype = constructor.prototype;
        
            // 检查原型链
            while (instance) {
                if (instance === prototype) return true;
                instance = Object.getPrototypeOf(instance);
            }
            return false;
        }
        
        function Person() {}
        const alice = new Person();
        
        console.log(customInstanceof(alice, Person)); // true
        console.log(customInstanceof(alice, Object));  // true
        console.log(customInstanceof({}, Person));      // false
        

        19.实现继承的几种方式以及他们的优缺点

1. 原型链继承

通过将子类的原型指向父类的实例,实现继承。

function Parent() {
    this.name = 'Parent';
}

Parent.prototype.sayHello = function() {
    console.log(`Hello from ${this.name}`);
};

function Child() {
    this.name = 'Child';
}

Child.prototype = new Parent();

const child = new Child();
child.sayHello(); // 输出: Hello from Parent

2. 构造函数继承

在子类构造函数中调用父类构造函数,使用 callapply

function Parent(name) {
    this.name = name || 'Parent';
}

function Child(name) {
    Parent.call(this, name); // 传递参数
}

const child = new Child('Child');
console.log(child.name); // 输出: Child

3. 组合继承

结合原型链继承和构造函数继承的优点。

function Parent(name) {
    this.name = name || 'Parent';
}

Parent.prototype.sayHello = function() {
    console.log(`Hello from ${this.name}`);
};

function Child(name) {
    Parent.call(this, name); // 传递参数
}

Child.prototype = Object.create(Parent.prototype); // 继承原型
Child.prototype.constructor = Child;

const child = new Child('Child');
child.sayHello(); // 输出: Hello from Child

4.寄生式继承

在构造函数内部创建一个新对象,并将父类的方法添加到这个新对象上。

function createChild(parent) {
    const child = Object.create(parent);
    child.sayHello = function() {
        console.log(`Hello from ${this.name}`);
    };
    return child;
}

const parent = { name: 'Parent' };
const child = createChild(parent);
child.name = 'Child';
child.sayHello(); // 输出: Hello from Child

5. ES6 的 class 继承

使用 ES6 的 class 语法来实现继承。

class Parent {
    constructor(name) {
        this.name = name || 'Parent';
    }

    sayHello() {
        console.log(`Hello from ${this.name}`);
    }
}

class Child extends Parent {
    constructor(name) {
        super(name); // 调用父类构造函数
    }
}

const child = new Child('Child');
child.sayHello(); // 输出: Hello from Child

20.在 JavaScript 中,数组的某些方法会改变原数组(即就地修改),而其他方法则不会改变原数组,而是返回一个新数组。

改变原数组的方法

  1. push()

    • 向数组末尾添加一个或多个元素
    • const arr = [1, 2, 3];
      arr.push(4); // arr 变为 [1, 2, 3, 4]
      
  2. pop()

    • 从数组末尾删除一个元素,并返回该元素
    • const arr = [1, 2, 3];
      arr.pop(); // arr 变为 [1, 2]
      
  3. shift()

    • 从数组开头删除一个元素,并返回该元素
    • const arr = [1, 2, 3];
      arr.shift(); // arr 变为 [2, 3]
      
  4. unshift()

    • 向数组开头添加一个或多个元素
    • const arr = [1, 2, 3];
      arr.unshift(0); // arr 变为 [0, 1, 2, 3]
      
  5. splice()

    • 从数组中添加或删除元素
    • const arr = [1, 2, 3];
      arr.splice(1, 1); // arr 变为 [1, 3],删除索引1的元素
      
  6. sort()

    • 对数组进行排序
    • const arr = [3, 1, 2];
      arr.sort(); // arr 变为 [1, 2, 3]
      
  7. reverse()

    • 反转数组的元素顺序
    • const arr = [1, 2, 3];
      arr.reverse(); // arr 变为 [3, 2, 1]
      

不改变原数组的方法

  1. map()

    • 创建一个新数组,包含对原数组每个元素调用函数后的结果
    • const arr = [1, 2, 3];
      const newArr = arr.map(x => x * 2); // newArr 为 [2, 4, 6]
      
  2. filter()

    • 创建一个新数组,包含所有通过测试的元素
    • const arr = [1, 2, 3, 4];
      const evenArr = arr.filter(x => x % 2 === 0); // evenArr 为 [2, 4]
      
  3. reduce()

    • 对数组中的每个元素执行一个 reducer 函数,并返回单个值
    • const arr = [1, 2, 3, 4];
      const sum = arr.reduce((acc, x) => acc + x, 0); // sum 为 10
      
  4. slice()

    • 返回数组的一个片段,生成一个新数组,原数组不变
    • const arr = [1, 2, 3, 4];
      const newArr = arr.slice(1, 3); // newArr 为 [2, 3]
      
  5. concat()

    • 合并两个或多个数组,返回新数组,原数组不变
    • const arr1 = [1, 2];
      const arr2 = [3, 4];
      const newArr = arr1.concat(arr2); // newArr 为 [1, 2, 3, 4]
      
  6. find()

    • 返回数组中第一个满足条件的元素,不改变原数组
    • const arr = [1, 2, 3, 4];
      const found = arr.find(x => x > 2); // found 为 3
      
  7. some()

    • 测试数组中是否至少有一个元素通过测试,不改变原数组
    • const arr = [1, 2, 3];
      const hasEven = arr.some(x => x % 2 === 0); // hasEven 为 true
      
  8. every()

    • 测试数组中的所有元素是否都通过测试,不改变原数组
    • const arr = [2, 4, 6];
      const allEven = arr.every(x => x % 2 === 0); // allEven 为 true
      

总结

  • 改变原数组的方法push()pop()shift()unshift()splice()sort()reverse()
  • 不改变原数组的方法map()filter()reduce()slice()concat()find()some()every()

嗨!很高兴回答你关于Python的NumPy库的问题。NumPy是一个强大的科学计算库,提供了高性能的数组对象和各种功能丰富的数学函数。 首先,你可以使用以下命令安装NumPy库: ```python pip install numpy ``` 一旦安装完成,你可以在Python脚本中导入NumPy库: ```python import numpy as np ``` 现在让我们来看一些NumPy的常用功能。 1. 创建NumPy数组: - 使用`np.array()`函数从Python列表或元组创建数组。 - 使用`np.zeros()`创建一个元素全为0的数组。 - 使用`np.ones()`创建一个元素全为1的数组。 - 使用`np.random`模块生成随机数组。 2. 数组操作: - 通过索引访问和修改数组的元素。 - 使用切片操作提取子数组。 - 使用数组的形状、大小和维度等属性。 3. 数学函数: - NumPy提供了丰富的数学函数,例如平方根(`np.sqrt()`)、指数函数(`np.exp()`)、对数函数(`np.log()`)等。 - 通过在数组上应用这些函数,可以进行元素级别的数学操作。 4. 数组运算: - NumPy支持基本的数组运算,如加法、减法、乘法和除法。 - 这些运算可以在两个数组之间进行,也可以在数组和标量之间进行。 5. 线性代数: - NumPy提供了许多线性代数操作的函数,如矩阵乘法(`np.dot()`)、矩阵求逆(`np.linalg.inv()`)、特征值和特征向量(`np.linalg.eig()`)等。 这只是NumPy库的一小部分功能,但对于进行科学计算和数据分析来说非常重要。你可以参考NumPy官方文档以了解更多详细信息:https://numpy.org/doc/ 希望这些信息能帮助你开始学习NumPy库!如果还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值