外观
外观
怡然
4084字约14分钟
面试JavaScript
2024-09-03
number
string
boolean
undefined
null
bigInt
symbol
object
除object为引用数据类型外,其他几种均属于基本数据类型
Map
,Set
,WeakMap
, 和WeakSet
是ES6(ECMAScript 2015)引入的内置对象,它们提供了不同的数据结构用于存储键值对或者元素集合。
Map
对象保存键值对,并且可以是任何类型的值(包括函数、对象、基本类型)。与对象字面量不同的是,Map
的值可以是任意类型的数据。Map
中的元素是有序的,元素按照插入顺序排序。WeakMap
类似于 Map
,但是它的键必须是对象,而且这些键是弱引用的。这意味着如果一个对象作为 WeakMap
的键并且这个对象没有其他引用了,那么它可以被垃圾回收。这使得 WeakMap
适合用来存储临时的数据关联。Set
是一个集合,它只包含唯一的值作为元素。Set
可以用来确保每个值都是唯一的,并且可以通过迭代器访问这些值。WeakSet
类似于 Set
,但是它的值必须是对象,并且这些值也是弱引用的。它主要用于存储一组对象,并允许在不阻止垃圾回收的情况下持有对这些对象的引用。相同点
Map
和Set
都是一种数据结构,可以作为存放内容的容器;Map
和Set
相较于普通的数组array
来说,查找效率更快;Map
和Set
都通过delete
方法来删除数据;Map
和Set
都通过has
方法来获取元素是否在集合中。不同点
Map
的值类似于一个二维数组,而Set
的值是一个伪数组(其实是一个对象,但是可以通过索引去访问);Map
更像是一个字典,存储的数据由key: value
的格式组成,而Set更像是一个集合,里面存储了一个个的值;Map
的键名不会重复,Set
则是值不会重复;Map
通过get
方法访问数据,Set
则只能通过遍历或者转成数组,再去访问;Map
通过set
方法去添加数据,Set
则是通过add
方法去添加数据。bigInt
和number
的区别Number
: JavaScript中的 Number
类型可以存储从 -1e308
到 1e308
之间的值(即大约从-10的308次方到10的308次方)。对于整数来说,Number
类型只能精确表示从 -2^53+1
到 2^53-1
之间的所有整数。BigInt
: BigInt
类型用于表示任意精度的整数。这意味着它可以准确地表示远远超过 Number
类型所能表示的最大值或最小值的整数。Number
: 普通数字直接量,如 123
或 3.14
。BigInt
: BigInt
数字直接量需要在整数后面加上 n
后缀,例如 1234567890123456789012345678901234567890n
,或者通过 BigInt()
函数创建,如 BigInt(123)
。Number
类型的操作,如果操作超出了 Number
能表示的范围,则会得到 Infinity
或 -Infinity
,或者由于精度丢失导致的结果不准确。BigInt
类型的操作则不会溢出,并且能够保持大整数的精确度。但是,BigInt
不支持任何非整数的数学运算,比如浮点运算或使用 %
(取模)运算符。Number
和 BigInt
在某些情况下混合使用时,如果无法进行类型转换来维持精度,将会抛出错误。例如,当你试图将一个 BigInt
和一个 Number
相加时,JavaScript
会抛出一个类型错误(TypeError),因为 BigInt
不能自动转换为 Number
,反之亦然。Number
类型通常比 BigInt
类型在处理速度上更快,因为大多数现代处理器对双精度浮点数有很好的支持。BigInt
占用更多的内存空间,并且可能在执行效率上不如 Number
类型,尤其是在处理不太大的数值时。if
条件语句中,BigInt
和Number
类似;Boolean
转换时,BigInt
和Number
类似;BigInt
类型的数据可以和Number
类型的数据进行比较,但是结果是宽松相等(==
才成立, ===
不行)的;BigInt
和Number
类似,可以使用+、-、*、/、%
等运算;BigIn
t可以和Number
类型的数据放在同一数组进行排序。BigInt
不能使用Math
对象:不能使用Math
对象的一些方法,比如Math.floor
、Math.ceil
等;Number
运算:不能和Number
类型的数据进行运算,必须要转换成同一类型才可以,但是转换的过程可能会精度丢失;BigInt
的数据不能使用JSON.stringify()
。typeof
:typeof null
的值是object
,这是由于在JavaScript
最初的实现中,js
的值是由表示类型的标签和实际数值表示的,而恰好object
的类型标签是0
,null
又代表空指针,在大多数平台下解析到的类型标签也是0
,所以typeof
的值也就变成了object
。instanceof
:所有的引用类型用instanceof Object
判断时,结果都为true
。toString()
:需要对返回值进行匹配。function myTypeof(data) {
return Object.prototype.toString
.call(data) // eg:[object Number]
.slice(8, -1) // 正序索引为八的字符到倒序索引为-1的字符
.toLowerCase(); // 转小写
}
JavaScript
中的this
this
是执行上下文中的一个属性,指向最后一次调用这个方法的对象。
this
指向全局对象。this
指向这个对象。new
调用时,函数执行前会新创建一个对象,this
指向这个新创建的对象。apply
、call
和bind
三个方法可以显式得指定调用函数的this
指向。 apply
方法接受两个参数,一个是this
绑定的对象,一个是参数数组。call
方法接收的第一个参数是this
绑定的对象,后面的其余参数是传入函数执行的参数。bind
方法通过传入一个对象,返回一个this
绑定了传入对象的新函数,这个函数的this
指向除了使用new
时会被改变,其他情况下都不会改变。function greet(phrase, time) {
console.log(phrase + ', ' + time + ' ' + this.name);
}
let person = {
name: 'Alice'
};
// 绑定this值,并预设参数
let morningGreet = greet.bind(person, 'Good morning', 'in the morning');
// 输出 "Good morning, in the morning Alice"
morningGreet();
this
:箭头函数不会创造自己的this
上下文,而是从外围的作用域中继承this
值,无论箭头函数在哪里被调用,它的this
指向始终与定义时所在的上下文相同。arguments
:箭头函数内部的arguments
对象实际上是一个由剩余参数操作符(...
)提供的数组,而普通函数的arguments
是一个类数组对象。new
操作符:箭头函数不能作为构造函数使用,因此不能使用new
关键字来调用,否则会报TypeError
的错。AMD和CommonJS、ES6模块是JavaScript模块化编程中三种主要的模块系统。
CommonJS:
module.exports
导出模块接口,并使用require
动态加载模块。// 模块文件:math.js
module.exports = {
add: function(x, y) {
return x + y;
}
};
// 主文件:index.js
const math = require('./math');
console.log(math.add(1, 2)); // 输出 "3"
AMD:
define
函数定义模块,模块定义是一个立即执行的函数表达式,可以接收依赖项作为参数。require
函数加载模块,通常需要一个模块加载器。// 定义一个模块
define(['dependency'], function(dependency) {
return {
doSomething: function() {
dependency.doSomethingElse();
}
};
});
// 加载模块
require(['myModule'], function(myModule) {
myModule.doSomething();
});
ES6 Modules:
import
和export
关键字来声明模块依赖关系和导出成员。这些导入和导出都是静态解析的,意味着编译器可以在编译阶段确定所有依赖关系。import()
表达式来异步加载模块。import
和export
不能在脚本的任何地方使用,只能在顶级作用域使用。// 模块文件:math.js
export const add = (x, y) => {
return x + y;
};
// 主文件:index.js
import { add } from './math';
console.log(add(1, 2)); // 输出 "3"
区别 | var | let | const |
---|---|---|---|
是否有块级作用域 | 否 | 是 | 是 |
是否存在变量提升 | 是 | 否 | 否 |
是否添加全局属性 | 是 | 否 | 否 |
能否重复声明变量 | 能 | 否 | 否 |
是否存在暂时性死区 | 否 | 是 | 是 |
是否必须设置初始值 | 否 | 否 | 是 |
能否改变指针指向 | 能 | 能 | 否 |
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log("Hello, my name is " + this.name);
};
// 使用 new 操作符创建对象
let alice = new Person('Alice', 30);
alice.sayHello(); // 输出 "Hello, my name is Alice"
// 模拟new操作符
function _new(Constructor, ...args) {
// 创建一个新的对象
const newObj = {};
// 将新对象的原型设置为构造函数的 prototype
newObj.__proto__ = Constructor.prototype;
// 绑定 this 到新对象,并执行构造函数
const result = Constructor.apply(newObj, args);
// 检查构造函数是否返回一个对象
return result instanceof Object ? result : newObj;
}
// 使用自定义的_new函数创建对象
let bob = _new(Person, 'Bob', 25);
bob.sayHello(); // 输出 "Hello, my name is Bob"
操作方法
push
:向数组末尾添加一个或多个元素,并返回新的长度。let arr = [1, 2, 3];
arr.push(4); // arr 现在是 [1, 2, 3, 4]
pop
:移除数组末尾的元素,并返回被移除的元素。let arr = [1, 2, 3];
arr.pop(); // 返回 3, arr 现在是 [1, 2]
shift
:移除数组开头的元素,并返回被移除的元素。let arr = [1, 2, 3];
arr.shift(); // 返回 1, arr 现在是 [2, 3]
unshift
:向数组开头添加一个或多个元素,并返回新的长度。let arr = [1, 2, 3];
arr.unshift(0); // arr 现在是 [0, 1, 2, 3]
splice
:用于添加或删除数组中的元素。let arr = [1, 2, 3, 4];
arr.splice(1, 2); // 删除索引1开始的两个元素,arr 现在是 [1, 4]
arr.splice(1, 0, 2, 3); // 在索引1处插入2, 3,arr 现在是 [1, 2, 3, 4]
concat
:将一个或多个数组合并为一个新数组,并返回该数组。let arr1 = [1, 2], arr2 = [3, 4];
let newArr = arr1.concat(arr2); // newArr 现在是 [1, 2, 3, 4]
slice
:返回数组的一部分,并不会修改原数组。let arr = [1, 2, 3, 4];
let newArr = arr.slice(1, 3); // newArr 现在是 [2, 3]
reverse
:反转数组中元素的顺序,并返回原数组。let arr = [1, 2, 3];
arr.reverse(); // arr 现在是 [3, 2, 1]
sort
:对数组元素进行排序,默认按照转换为字符串后的字典顺序。let arr = [3, 1, 2];
arr.sort(); // arr 现在是 [1, 2, 3] (默认按字符串比较)
arr.sort((a, b) => a - b); // 数字排序
fill
:用指定的值填充数组的一部分或全部。let arr = [1, 2, 3, 4];
arr.fill(0); // arr 现在是 [0, 0, 0, 0]
查询方法:
indexOf
/lastIndexOf
:返回数组中第一个匹配项的索引或最后一个匹配项的索引。let arr = [1, 2, 3, 2];
arr.indexOf(2); // 返回 1
arr.lastIndexOf(2); // 返回 3
includes
:检查数组是否包含某个元素。let arr = [1, 2, 3];
arr.includes(2); // 返回 true
迭代方法:
forEach
:对数组中的每个元素执行一个方法。let arr = [1, 2, 3];
arr.forEach(item => console.log(item));
map
:创建一个新数组,其结果是该数组中的每个元素执行方法的结果。let arr = [1, 2, 3];
let newArr = arr.map(item => item * 2); // newArr 现在是 [2, 4, 6]
filter
:创建一个新数组,其结果是该数组中满足条件的元素。let arr = [1, 2, 3, 4];
let even = arr.filter(item => item % 2 === 0); // even 现在是 [2, 4]
reduce
/reduceRight
:对数组中的每个元素执行方法,将其结果汇总为单个值。let arr = [1, 2, 3, 4];
let sum = arr.reduce((acc, cur) => acc + cur, 0); // sum 现在是 10
some
/every
:some
检查数组中是否有至少一个元素满足条件;every
检查数组中的所有元素是否都满足条件。let arr = [1, 2, 3, 4];
arr.some(item => item > 2); // 返回 true
arr.every(item => item < 5); // 返回 true
字符串转数组:
split
:用于将字符串分割成字符串数组,第一个参数是分隔符,可以是字符串或正则表达式。let str = "hello world";
let arr = str.split(" "); // ["hello", "world"]
...
let str = "hello";
let arr = [...str]; // ["h", "e", "l", "l", "o"]
Array.from
:用于将类数组对象或可迭代对象转换为数组。let str = "hello";
let arr = Array.from(str); // ["h", "e", "l", "l", "o"]
数组转字符串:
join
:用于将数组的所有元素连接成一个字符串,第一个参数是连接符,默认为空字符串。let arr = ["hello", "world"];
let str = arr.join(" "); // "hello world"
toString
:用于将数组转换为逗号分隔的字符串。let arr = ["hello", "world"];
let str = arr.toString(); // "hello,world"
// for in
const arr = ['a','b','c','d']
for(const index in arr) {
console.log(index)
}
// 打印结果:'0' '1' '2' '3',可以发现打印的是数组的下标,数组是特殊的对象,下标是数组对象身上的可枚举属性,打印的就是这个可枚举属性
// for of
for(const item of arr) {
console.log(item)
}
// 打印结果:'a' 'b' 'c' 'd',for of打印的就是数组里的每一项元素的值
总结:for of遍历键值对的值,for in 遍历键值对的键。