JavaScript基础知识要点

判断object为空

常用方法:
Object.keys(obj).length === 0
JSON.stringify(obj)===’{}
for in 判断
以上方法都是不太严谨,因为处理不了 const obj ={[Symbol(‘a’)]:1 }. 这种情况

更严谨的方法:Reflect.ownKeys(obj).length ===0

强制类型转换、隐式类型转换

强制类型转换

1
2
3
4
5
6
// 使用 let 声明变量 num 并将字符串 "42" 转换为数字
let num = Number("42");
// 使用 let 声明变量 str 并将数字 123 转换为字符串
let str = String(123);
// 使用 let 声明变量 bool 并将数字 0 转换为布尔值
let bool = Boolean(0);

隐式类型转换

1
2
3
4
5
6
7
8
9
10
11
// 将数字 10 和字符串 "Hello" 拼接
var result = 10 + "Hello";
console.log(result);

// 比较 true 和 1,并将结果赋值给变量
var isTrueEqualOne = true == 1;
console.log(isTrueEqualOne);

// 比较 false 和 0,并将结果赋值给变量
var isFalseEqualZero = false == 0;
console.log(isFalseEqualZero);

==和===的区别

“==”,先隐式类型转换,再判断值是否相等
“===”,直接判断 类型 +值 是否相等

1
2
3
4
5
6
7
8
9
10
11
const a = {
i: 1,
valueOf: function() {
return this.i++;
}
};

// 利用宽松相等的隐式类型转换特性
if (a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}

javascript 的数据类型

基本数据类型:
a.Number(数字):表示数值,包括整数和浮点数。
b.String(字符串):表示文本数据,使用引号(单引号或双引号)括起来。

​ c.Boolean(布尔值):表示逻辑值,即true(真)或false(假)
​ d.Nul(空):表示一个空值或没有值的对象。
​ e.Undefined(未定义):表示一个未被赋值的变量的值。
​ f.Symbol(符号):表示唯一的标识符。
复杂数据类型(也被称为引用类型):
​ a.Object(对象):表示复杂数据结构,可以包含键值对的集合
​ b.Array(数组):表示有序的集合,可以包含任意类型的数据。c.Function(函数):表示可执行的代码块,
在 ECMAScript 2020(ES11)规范中正式被添加 BigInt 数据类型。用于对“大整数”的表示和操作。
​ a.结尾用n表示:100000n/200n
基础类型存放于栈,变量记录原始值;引用类型存放堆,变量记录地址

javascript 变量在内存中的堆栈存储

基础类型会存放于栈,引用类型会存放在堆

1
2
3
4
5
6
7
8
function modifyObj(obj) {
obj.m = 50;
console.log(obj.m); // 输出: 50
}

const originalObj = { m: 30 };
modifyObj(originalObj);
console.log(originalObj.m); // 输出: 50

1.当执行 consto={ m:30 } 时,相当于在堆内存开辟一块空间,存储{m:30 },同时利用变量 。 记录该堆内存地址,o存放于栈。

2.接着执行 fn(o)会把 。记录的地址值作为实参传递到方法 fn 中,同时记录在 obj 副本变量中(注意:JS 的传参都是值传递)

3.再下来执行 obj={m:50 },相当于重新开辟了一个堆内存空间存储{m:50}, 同时把地址记录到 obj 中。

4.然后执行 console.log(obj.m)会根据 obj 记录的地址2,找到{m:50},所以输出50。

5.最后同理,执行console.log(0.m)会根据o记录的地址1,找到{m: 30}, 所以会输出30

JS 单线程设计的目的

javascript 是浏览器的脚本语言,主要用途是进行页面的一系列交互操作以及用户互动,多线程编程通常会引发竞态条件、死锁和资源竞争等问题。如果以多线程的方式进行浏览器操作,则可能出现不可预测的冲突。假设有两个线程同时操作同一个 DOM 元素,线程1要求浏览器修改 DOM 内容,而线程2却要求删除 DOM,浏览器就疑惑,无法决定采用哪个线程的操作。所以 JavaScript的单线程设计很好的简化了这类并发问题,避免了因多线程而引发的竞态条件、死锁和资源竞争等问题。当然,如果在开发中确切需要到异步场景,javascript 也有众多的异步队列来帮助我们实现,也就是我们熟知的事件循环,微任务队列,宏任务队列。如果真的需要开辟一个新线程处理逻辑,也可以通过 webworker 实现。

判断 javascript 的数据类型

typeof 操作符: 可以用来确定一个值的基本数据类型,返回一个表示数据类型的字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 检测数字类型
console.log(typeof 42); // 输出: "number"
// 42 是一个数值,所以 typeof 返回 "number"

// 检测字符串类型
console.log(typeof "Hello"); // 输出: "string"
// "Hello" 是一个字符串,因此 typeof 返回 "string"

// 检测布尔类型
console.log(typeof true); // 输出: "boolean"
// true 是布尔值,所以 typeof 返回 "boolean"

// 检测未定义类型
console.log(typeof undefined); // 输出: "undefined"
// 当变量未赋值或者函数没有返回值时会是 undefined 类型,这里直接检测 undefined,typeof 返回 "undefined"

// 检测 null 类型
console.log(typeof null); // 输出: "object"
// 这是 JavaScript 语言的一个历史遗留问题,按照预期 null 应该是原始值类型,但 typeof null 返回 "object",是一个常见的误解

// 检测数组类型
console.log(typeof [1, 2, 3]); // 输出: "object"
// 在 JavaScript 中,数组是对象的一种特殊形式,所以 typeof 对数组检测返回 "object"

// 检测对象类型
console.log(typeof { key: "value" }); // 输出: "object"
// { key: "value" } 是一个普通的对象字面量,typeof 返回 "object"

// 检测函数类型
console.log(typeof function () { }); // 输出: "function"
// 函数在 JavaScript 中是一等公民,有自己独特的类型,typeof 对函数检测返回 "function"

注意,typeof nul1 返回”object”是历史遗留问题,不是很准确。

Object.prototype.tostring:用于获取更详细的数据类型信息。

1
2
3
4
5
6
7
8
console.log(Object.prototype.toString.call(42)); // "[object Number]"
console.log(Object.prototype.toString.call("Hello")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call([1, 2, 3])); // "[object Array]"
console.log(Object.prototype.toString.call({ key: "value" })); // "[object Object]"
console.log(Object.prototype.toString.call(function () {})); // "[object Function]"

**instanceof 操作符:**用于检查对象是否属于某个类的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个普通对象
var obj = {};
console.log(obj instanceof Object); // true

// 定义一个数组
var arr = [];
console.log(arr instanceof Array); // true

// 定义一个构造函数
function Person() {}
// 使用构造函数创建一个实例
var person = new Person();
console.log(person instanceof Person); // true

**Array.isArray:**用于检查一个对象是否是数组

1
2
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray("Hello")); // false

ES 每个版本引入内容

ECMAScript是一种用于编写 JavaScript 的标准化脚本语言。下面是每个版本的一些重要特性和区别

ES6(ECMAScript 2015)

  • 引入了 letconst 关键字,用于声明块级作用域的变量。
  • 引入了箭头函数(arrow functions)。
  • 添加了模板字符串(template strings)。
  • 引入了解构赋值(destructuring assignment)。
  • 引入了类和模块(classes and modules)。
  • 引入了 Promise

ES7(ECMAScript 2016)

  • 引入了 Array.prototype.includes() 方法,用于检查数组是否包含特定元素。
  • 引入了指数操作符(exponentiation operator)。

ES8(ECMAScript 2017)

  • 引入了异步函数(async/await)。
  • 添加了 Object.values()Object.entries() 方法,用于遍历对象的值和键值对。
  • 引入了字符串填充方法(string padding)。

ES9(ECMAScript 2018)

  • 引入了异步迭代器(asynchronous iterators)。
  • 添加了 Promise.finally() 方法,用于指定无论 Promise 状态如何都会执行的回调函数。
  • 引入了对象的扩展运算符(object spread)。

ES10(ECMAScript 2019)

  • 引入了 Array.prototype.flat()Array.prototype.flatMap() 方法,用于处理嵌套数组。
  • 添加了 String.prototype.trimStart()String.prototype.trimEnd() 方法,用于去除字符串开头和结尾的空格。
  • 引入了动态导入(dynamic imports)。

ES11(ECMAScript 2020)

  • 引入了可选链操作符(optional chaining)。
  • 添加了空值合并操作符(nullish coalescing)。
  • 引入了 BigInt 类型,用于处理超出 Number 类型范围的整数。

let 声明变量的特性

块级作用域

1
2
3
4
5
for (var i = 0; i < 10; ++i) {
setTimeout(() => {
console.log(i);
}, 1000);
}

1 秒后输出 10 个 10,循环体变量i会渗透到循环体外部,所以在 setTimeout1 秒 的过程中,i的值实质变成了 10,因此会在 1秒后输出 10 个 10。

1
2
3
4
5
for (let i = 0; i < 10; ++i) {
setTimeout(() => {
console.log(i);
}, 1000);
}

变会 let 定义之后,问题会消失,正常在1秒后,输出 0-9,因为let 是块级作用域,仅局限于循环体内部。

1
2
3
4
5
6
7
for (var i = 0; i < 10; ++i) {
(function (index) {
setTimeout(() => {
console.log(index);
}, 1000);
})(i);
}

如果用 var 定义,可通过在循环体内添加一个立即执行函数,把迭代变量的作用域保护起来

暂时性死区(temporal dead zone)

在 let 声明之前的执行瞬间被称为“暂时性死区”,此阶段引用任何后面声明的变量会抛出 ReferenceError 错误

同级作用域下不能重复声明

全局声明会挂到 Script作用域下,不会挂在 window

变量提升 & 函数提升(优先级)

1
2
3
4
5
6
7
8
// 这里输出的是函数 s,因为函数声明提升优先级高于变量声明
console.log(s);
// 变量声明,但赋值操作不会提升
var s = 2;
// 函数声明
function s() {
console.log(s);
}

var 在会变量提升
优先级:函数提升 >变量提升

null和undefined 的区别

undefined

当声明了一个变量但未初始化它时,它的值为undefined

当访问对象属性或数组元素中不存在的属性或索引时,也会返回undefined。

当函数没有返回值时,默认返回 undefined

如果函数的参数没有传递或没有被提供值,函数内的对应参数的值为undefined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 变量声明但未赋值
let x;
console.log(x); // undefined

// 定义一个空对象
const obj = {};
console.log(obj.property); // undefined

// 定义一个无返回值的函数
function exampleFunc() {}
console.log(exampleFunc()); // undefined

// 定义一个加法函数
function add(a, b) {
return a + b;
}
console.log(add(2)); // NaN

null

null 是一个特殊的关键字,表示一个空对象指针。

它通常用于显式地指示一个变量或属性的值是空的,null 是一个赋值的操作,用来表示”没有值”或“空”

null 通常需要开发人员主动分配给变量,而不是自动分配的默认值。

是原型链的顶层:所有对象都继承自 0bject 原型对象,0bject 原型对象的原型是 null

用 setTimeout 来实现倒计时,与setInterval 的区别?

1
2
3
4
5
6
7
8
9
10
const countDown = (count) => {
setTimeout(() => {
count--;
console.log(count);
if (count > 0) {
countDown(count);
}
}, 1000);
};
countDown(10);
1
2
3
4
5
6
7
8
9
let count = 10;
let timer = setInterval(() => {
count--;
console.log(count);
if (count <= 0) {
clearInterval(timer);
timer = null;
}
}, 1000);

**setTimeout:**每隔一秒生成一个任务,等待一秒后执行,执行完成后,再生成下一个任务,等待一秒后执行如此循环,所以左边任务间的间隔保证是1秒。
**setInterval:**无视执行时间,每隔一秒往任务队列添加一个任务,等待一秒后执行,这样会导致任务执行间隔小于1秒,甚至任务堆积。

setInterval 中当任务执行时间大于任务间隔时间,会导致消费赶不上生产

常用的 console 方法有哪些,JS 调试方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 普通打印
console.log('a');

// 按级别打印
console.error('a');
console.warn('a');
console.info('a');
console.debug('a');

// 占位符打印
console.log('%o a', { a: 1 });
console.log('%s a', 'xx');
console.log('%d d', 123);

// 打印任何对象,一般用于打印DOM节点
console.dir(document.body);

// 打印表格
console.table({ a: 1, b: 2 });

// 计数
for (let i = 0; i < 10; ++i) {
console.count('a');
}

// 分组
console.group('group1');
console.log('a');
console.group('group2');
console.log('b');
console.groupEnd('group2');
console.groupEnd('group1');

// 计时
console.time('a');
const now = Date.now();
while (Date.now() - now < 1000) { }
console.timeEnd('a');

// 断言
console.assert(1 === 2, 'error');

// 调用栈
function a() {
console.trace();
}
function b() {
a();
}
b();

// 内存占用
console.log(console.memory);

数组去重的方法

Set 只允许存储唯一的值,可以将数组转换为Set,然后再将Set转换回数组以去重。

1
2
3
const arr = [12, 2, 3, 4, 4, 5];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr);

利用 filter方法来遍历数组,只保留第一次出现的元素

1
2
3
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.filter((value, index, self) => self.indexOf(value) === index);
console.log(uniqueArr);

使用 reduce 方法逐个遍历数组元素,构建一个新的数组,只添加第一次出现的元素。

1
2
3
4
5
6
7
8
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
console.log(uniqueArr);

使用 indexof 方法 ,遍历数组,对于每个元素,检査其在数组中的索引,如果第一次出现,则添加到新数组

1
2
3
4
5
6
7
8
const arr = [1, 2, 2, 3, 5];
const uniqueArr = [];
arr.forEach((value) => {
if (uniqueArr.indexOf(value) === -1) {
uniqueArr.push(value);
}
});
console.log(uniqueArr);

使用 includes 方法:类似于 index0f 方法,只不过使用 includes 来检査元素是否已存在于新数组。

1
2
3
4
5
6
7
8
const arr = [1, 2, 2, 3, 5];
const uniqueArr = [];
arr.forEach((value) => {
if (!uniqueArr.includes(value)) {
uniqueArr.push(value);
}
});
console.log(uniqueArr);

Js 数组常见操作方式及方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
// 定义一个示例数组
const list = [1, 2, 3, 4, 5];

// 1. 遍历方法

// 1.1 for 循环
console.log('使用 for 循环遍历数组:');
for (let i = 0; i < list.length; ++i) {
console.log(list[i]);
}

// 1.2 for...in 循环
console.log('\n使用 for...in 循环遍历数组:');
for (const key in list) {
console.log(list[key]);
}

// 1.3 for...of 循环
console.log('\n使用 for...of 循环遍历数组:');
for (const item of list) {
console.log(item);
}

// 1.4 forEach 方法
console.log('\n使用 forEach 方法遍历数组:');
list.forEach(item => {
console.log(item);
});

// 1.5 map 方法
console.log('\n使用 map 方法对数组元素进行转换:');
const newList = list.map(item => item * 2);
console.log('原数组:', list);
console.log('转换后的新数组:', newList);

// 2. 逻辑判断方法

// 2.1 every 方法
console.log('\n使用 every 方法判断数组元素是否都大于 0:');
const allGreaterThanZero = list.every(item => item > 0);
console.log('数组元素是否都大于 0:', allGreaterThanZero);

// 2.2 some 方法
console.log('\n使用 some 方法判断数组中是否有偶数:');
const hasEvenNumber = list.some(item => item % 2 === 0);
console.log('数组中是否有偶数:', hasEvenNumber);

// 3. 过滤方法

// 3.1 filter 方法
console.log('\n使用 filter 方法筛选出数组中的偶数:');
const evenNumbers = list.filter(item => item % 2 === 0);
console.log('数组中的偶数:', evenNumbers);

// 4. 查找方法

// 4.1 indexOf 方法
console.log('\n使用 indexOf 方法查找元素 3 的位置:');
const index = list.indexOf(3);
console.log('元素 3 的位置:', index);

// 4.2 lastIndexOf 方法
const listWithDuplicates = [1, 2, 3, 4, 3, 5];
console.log('\n使用 lastIndexOf 方法查找元素 3 的最后位置:');
const lastIndex = listWithDuplicates.lastIndexOf(3);
console.log('元素 3 的最后位置:', lastIndex);

// 4.3 includes 方法
console.log('\n使用 includes 方法判断数组中是否包含元素 3:');
const hasThree = list.includes(3);
console.log('数组中是否包含元素 3:', hasThree);

// 4.4 find 方法
console.log('\n使用 find 方法查找数组中的第一个偶数:');
const firstEven = list.find(item => item % 2 === 0);
console.log('数组中的第一个偶数:', firstEven);

// 4.5 findIndex 方法
console.log('\n使用 findIndex 方法查找数组中第一个偶数的索引:');
const firstEvenIndex = list.findIndex(item => item % 2 === 0);
console.log('数组中第一个偶数的索引:', firstEvenIndex);

JS 数组 reduce 方法的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 数组求和
const sumResult = [1, 2, 3].reduce((pre, cur) => pre + cur);
console.log('数组求和结果:', sumResult);

// 找最大值
const maxResult = [1, 2, 3, 2, 1].reduce((pre, cur) => Math.max(pre, cur));
console.log('数组中的最大值:', maxResult);

// 数组去重
const uniqueResultList = [1, 2, 3, 2, 1].reduce((preList, cur) => {
if (preList.indexOf(cur) === -1) {
preList.push(cur);
}
return preList;
}, []);
console.log('去重后的数组:', uniqueResultList);

// 归类
const dataList = [
{ name: 'aa', country: 'china' },
{ name: 'bb', country: 'china' },
{ name: 'cc', country: 'USA' },
{ name: 'dd', country: 'EN' }
];
const resultObj = dataList.reduce((preObj, cur) => {
const { country } = cur;
if (!preObj[country]) {
preObj[country] = [];
}
preObj[country].push(cur);
return preObj;
}, {});
console.log('归类后的对象:', resultObj);

// 字符串反转
const str = 'hello world';
const resultStr = Array.from(str).reduce((pre, cur) => `${cur}${pre}`, '');
console.log('反转后的字符串:', resultStr);

如何遍历对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 使用 for...in 遍历对象属性
const obj1 = { a: 1, b: 2, c: 3 };
for (let key in obj1) {
console.log(key, obj1[key]);
}

// 使用 Object.keys() 遍历对象属性
const obj2 = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj2);
keys.forEach(key => {
console.log(key, obj2[key]);
});

// 使用 Object.entries() 遍历对象属性
const obj3 = { a: 1, b: 2, c: 3 };
const entries = Object.entries(obj3);
entries.forEach(([key, value]) => {
console.log(key, value);
});

// 使用 Reflect.ownKeys() 遍历对象属性
const obj4 = { a: 1, b: 2, c: 3 };
Reflect.ownKeys(obj4).forEach(key => {
console.log(key, obj4[key]);
});

创建函数的几种方式

**函数声明(Function Declaration):**使用 function 关键字定义函数,可以在任何位置声明并使用,函数
声明提升(hoisting),所以可以在声明之前调用函数。

1
2
3
4
function sayHello() {
console.log("Hello, world!");
}
sayHello(); // 调用函数

**函数表达式(Function Expression):**将函数赋值给变量或属性,函数表达式的名称是可选的,与函数声明不同,函数表达式不会提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个函数表达式并赋值给变量 sayHi
var sayHi = function() {
console.log("Hi there!");
};
// 调用函数
sayHi();

// 匿名函数表达式,将其赋值给变量 greet
var greet = function(name) {
console.log("Hello, " + name);
};
// 调用函数并传入参数
greet('Alice');

**箭头函数(Arrow Function):**箭头函数是ES6引入的一种函数声明方式,它具有更短的语法和词法作用域
箭头函数没有自己的 this ,它继承自外围作用域。

1
2
const add = (a, b) => a + b;
console.log(add(2, 3));

匿名函数(Anonymous Function):函数没有名字,通常用于回调函数或临时函数。

1
2
3
setTimeout(function() {
console.log("This is an anonymous function.");
}, 1000);

创建对象的几种方式

**对象字面量(0bject Literal):**使用大括号{}创建对象,可以在大括号内定义对象的属性和方法。

1
2
3
4
5
6
7
8
9
10
var person = {
name: "Alice",
age: 30,
sayHello: function() {
console.log("Hello!");
}
};

// 调用对象的方法
person.sayHello();

**构造函数(Constructor Function):**使用构造函数创建对象,通过new关键字调用以创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
// 定义构造函数 Person
function Person(name, age) {
this.name = name;
this.age = age;
}

// 使用构造函数创建对象
var person1 = new Person("Alice", 30);

// 访问并打印对象的属性
console.log(person1.name);
console.log(person1.age);

**0bject.create()方法:**使用 0bject.create()方法创建对象,可以指定对象的原型

1
2
3
4
var person = Object.create(null); // 创建一个空对象
person.name = "Alice";
person.age = 30;
console.log(person);

**工厂函数(Factory Function):**使用工厂函数创建对象,工厂函数是一个返回新对象的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个函数 createPerson,用于创建一个表示人的对象
function createPerson(name, age) {
// 返回一个包含 name 和 age 属性的对象
return {
name: name,
age: age
};
}

// 调用 createPerson 函数创建一个对象实例 person1
var person1 = createPerson("Alice", 30);
// 打印 person1 对象,方便查看结果
console.log(person1);

**类(ES6中引入的类):**使用类定义对象,类是一种对象构造器的语法糖

1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个名为 Person 的类
class Person {
// 构造函数,用于初始化类的实例
constructor(name, age) {
this.name = name;
this.age = age;
}
}

// 使用 Person 类创建一个实例对象 person1
var person1 = new Person("Alice", 30);
// 打印 person1 对象的信息,方便查看实例的属性
console.log(person1);

宿主对象、内置对象、原生对象

宿主对象(Host Objects):
宿主对象是由宿主环境(通常是浏览器或Node.js)提供的对象。它们不属于]avaScript的核心,而是根据运行环境提供的功能而存在。宿主对象可以包括
浏览器环境中的 window、document、XMLHttpRequest
Node.js环境中的 global、process等。
宿主对象的定义和行为取决于宿主环境,因此它们可能在不同的环境中有不同的特性

内置对象(Built-in 0bjects):
内置对象是JavaScript语言本身提供的对象,它们包含在]avaScript的标准规范中。这些对象包括全局对象、数学对象、日期对象、正则表达式对象等。内置对象可以直接在任何]avaScript环境中使用,无需额外导入或引入。例如,全局对象 Math 用于数学计算,日期对象 Date 用于日期和时间操作。

原生对象(Native Objects)
原生对象是JavaScript语言的一部分,但它们不是内置对象。原生对象是通过构造函数或字面量方式创建的对象,例如数组、字符串、函数、对象等。这些对象可以通过]avaScript代码自定义,它们通常是开发人员用来构建应用程序的基本构建块。

区分对象和数组

语法区别:
数组使用方括号[]来定义,元素之间使用逗号分隔。

​ 对象使用花括号 {}来定义,每个属性由键值对组成,键和值之间使用冒号分隔,键和值之间使用逗号 分0隔。

方法和属性区别:
数组具有一系列方法和属性,用于操作和查询元素,例如push()、pop()、length 等。

​ 对象没有数组的方法,但它们有属性,可以通过属性名称访问值。

访问区别:
数组的元素可以通过数字索引(从 0开始)来访问。

​ 对象的属性名可以是字符串或符号,可以包含任何字符。

用途区别:
数组通常用于存储一系列有序的值,可以通过索引访问。

​ 0对象通常用于表示实体或实体的属性,每个属性都有一个唯一的名称。

什么是类数组(伪数组),如何将其转化为真实的数组?

类数组(或伪数组)是一种类似数组的对象,它们具有类似数组的结构,即具有数字案引和 length 属性,但不具有数组对象上的方法和功能

常见的类数组:

​ 函数内部的 arguments对象
​ DOM 元素列表(例如通过querySelectorAll获取的元素集合)
​ 一些内置方法(如 getElementsByTagName 返回的集合)

类数组转化为真实的数组方法:

Array.from()方法:

1
2
3
4
// 获取所有类名为 my-elements 的 DOM 元素,结果存储在 nodeList 中
const nodeList = document.querySelectorAll('.my-elements');
// 将 NodeList 对象转换为普通数组,存储在 arrayFromNodeList 中
const arrayFromNodeList = Array.from(nodeList);

Array.prototype.slice.call()方法:

1
2
3
4
// 获取所有类名为 my-elements 的 DOM 元素,结果存储在 nodeList 中
const nodeList = document.querySelectorAll('.my-elements');
// 将 NodeList 对象转换为普通数组,存储在 arrayFromNodeList 中
const arrayFromNodeList = Array.prototype.slice.call(nodeList);

Spread 运算符:

1
2
3
4
// 获取所有类名为 my-elements 的 DOM 元素
const nodeList = document.querySelectorAll('.my-elements');
// 使用展开语法将 NodeList 转换为数组
const arrayFromNodeList = [...nodeList];

作用域链

作用域链是 JavaScript 中用于查找变量的一种机制,它是由一系列嵌套的作用域对象构成的链式结构,每个作用域对象包含了在该作用域中声明的变量以及对外部作用域的引用,目的是确定在给定的执行上下文中如何查找变量。当您引用一个变量时,JavaScript 引擎会首先在当前作用域对象中查找该变量,如果找不到,它会沿着作用域链向上查找,直到找到该变量或达到全局作用域,如果变量在全局作用域中也找不到,将抛出一个引用错误。

作用域链的形成方法:
1.在函数内部,会创建一个新的作用域对象,包含了函数的参数、局部变量以及对外部作用域的引用。
2.如果在函数内部嵌套了其他函数,那么每个内部函数都会创建自己的作用域对象,形成一个链。
3.这个链条会一直延伸到全局作用域。

作用域链如何延长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function makeCounter() {
var count = 0;
return function () {
count++;
return count;
};
}

var counter1 = makeCounter();
var counter2 = makeCounter();

console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1

DOM 节点的 Attribute 和 Property 区别

Attribute(属性):
Attribute 是 HTML 元素在文档中的属性,它们通常在 HTML 中定义,并被存储在 HTML 元素的开始标签中。
Attribute 可以包含在 HTML中,如《div id=”myDiv”class=”container”>中的 id 和 class 。
Attribute 始终是字符串值,无论它们在 HTML 中是什么数据类型。
通过 getAttribute()方法可以访问元素的属性值,例如element.getAttribute(“id”)。
Property(属性):
Property 是 DOM 元素对象的属性,它们通常表示了 HTML 元素在文档中的状态和属性。
Property 的名称通常对应于 HTML 元素的属性名称,但不总是相同(有时有所不同)。
Property 的值可以是不同的数据类型,取决于属性的类型。
通过访问 DOM 元素对象的属性,可以直接操作和修改元素的状态,例如 element.id 或element.className 。
总结:
Attribute 是 HTML 标记中的属性,它们以字符串形式存储在 HTML 元素的标记中。
Property 是 DOM 元素对象的属性,它们表示了元素在文档中的状态和属性,可以是不同的数据类型 Attribute 始终是字符串,而 Property 的数据类型可以更广泛。
通常,Property 的名称与 Attribute 的名称相同,但不总是一致。

​ id 和 class 是 Attribute,它们以字符串形式存储在 HTML 标记中。
​ id 和 className 是 Property,它们是 DOM 元素对象的属性,可以直接访问和操作。

DOM 结构操作创建、添加、移除、移动、复制、查找节点

创建节点:

1
2
3
4
5
6
7
8
// 创建新元素节点
const newElement = document.createElement("div");

// 创建文本节点
const textNode = document.createTextNode("Hello, World");

// 创建文档片段
const fragment = document.createDocumentFragment();

添加节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建新元素节点
const newElement = document.createElement("div");

// 假设已经获取到了父元素和参考元素
// 你需要根据实际情况替换为正确的选择器逻辑
const parentElement = document.getElementById('parent');
const referenceElement = document.getElementById('reference');

// 添加为子节点
parentElement.appendChild(newElement);

// 在参考节点之前插入
parentElement.insertBefore(newElement, referenceElement);

移除节点

1
2
3
4
5
6
7
// 假设已经获取到了父元素和子元素
// 你需要根据实际情况替换为正确的选择器逻辑
const parentElement = document.getElementById('parent');
const childElement = document.getElementById('child');

// 从父节点中移除子节点
parentElement.removeChild(childElement);

移动节点

1
2
3
4
5
6
7
// 假设我们已经获取到新的父元素和要移动的子元素
// 这里你可以根据实际情况修改选择器,通过不同方式获取元素
const newParentElement = document.getElementById('newParent');
const childElement = document.getElementById('child');

// 移动节点到新位置
newParentElement.appendChild(childElement);

复制节点

1
2
const originalNode = document.getElementById('yourOriginalNodeId'); // 这里要根据实际情况替换ID
const clone = originalNode.cloneNode(true);

查找结点

1
2
3
4
5
6
7
8
9
// 通过 id 查找元素
const elementById = document.getElementById("myElement");

// 使用 CSS 选择器查找元素
const elementBySelector = document.querySelector(".myClass");

// 使用节点遍历方法查找节点
const parentElement = document.getElementById('parent'); // 假设先获取到父元素,这里要根据实际情况修改id
const firstChild = parentElement.firstElementChild;

DOM 的事件模型

事件对象(Event Object)):事件对象是一个包含有关事件的信息的对象。它包括事件的类型、目标元素鼠标位置、按下的键等信息。事件处理程序可以访问事件对象来了解事件的详细信息。

事件类型(Event Type): 事件类型指定了发生的事件的种类,例如点击事件(click)、鼠标移动事件(mousemove)、键盘按下事件(keydown)等。
事件目标(Event Target): 事件目标是触发事件的元素,事件将在目标元素上执行事件处理程序。

事件冒泡和事件捕获(Event Bubbling and Event Capturing): 事件可以在 DOM 树中冒泡或捕获。事件冒泡从目标元素开始,逐级向上传播到根元素;事件捕获从根元素开始,逐级向下捕获到目标元素。

事件监听器(Event Listener): 事件监听器是函数,用于处理特定类型的事件。它可以附加到元素,以便在事件发生时执行。通常使用 addEventListener 方法来添加事件监听器

事件处理程序(Event Handler): 事件处理程序是函数,负责处理特定事件类型的事件。事件监听器通常会调用事件处理程序。

事件委托(Event Delegation): 事件委托是一种技术,其中一个父元素上的事件监听器处理该元素的所有子元素上发生的事件。这减少了事件监听器的数量,提高了性能。

取消事件(Preventing Default): 事件处理程序可以取消事件的默认行为,例如在链接上阻止默认的点击跳转行为。这可以通过调用事件对象的preventDefault方法来实现。

停止事件传播(Stopping Propagation): 事件处理程序可以停止事件的传播,防止事件继续冒泡或捕获。
这可以通过调用事件对象的 stopPropagation或 stopImmediatePropagation方法来实现。

事件三要素

1.事件源(Event Source): 事件源是事件的发出者或触发者,它是产生事件的对象或元素,事件源通常是用户与页面交互的元素,如按钮、链接、输入框等。
2.事件类型(Event Type): 事件类型是指事件的种类或类型,描述了事件是什么样的行为或操作,不同的事件类型包括点击事件(c1ick)、鼠标移动事件(mousemove)、键盘按下事件(keydown )、表单提交事件(submit)等。
事件处理程序(Event Handler): 事件处理程序是事件触发后要执行的代码块或函数,它定义了当事件发生3时要执行的操作。事件处理程序通常由开发人员编写,用于响应事件并执行相应的逻辑。

这三要素一起构成了事件的基本信息。当用户与页面交互时,事件源会触发特定类型的事件,然后事件处理程序会捕获并处理事件,执行相关的操作。

事件委托

事件委托是一种常见的 JavaScript 编程技巧,它的核心思想是将事件处理程序附加到一个祖先元素上,而不是直接附加到每个子元素上,当事件在子元素上冒泡时,祖先元素捕获事件并根据事件目标来确定如何处理事件。
1.性能优势: 事件委托可以减少事件处理程序的数量,特别是在大型文档中,因为您只需为一个祖先元素添加一个事件处理程序。这降低了内存消耗和提高了性能,因为不必为每个子元素都绑定事件。
2.动态元素: 事件委托适用于动态生成的元素,因为无需为新添加的元素单独绑定事件,而是在祖先元素上继续使用相同的事件处理程序。
3.代码简洁性: 通过将事件处理逻辑集中在祖先元素上,代码更加简洁和可维护,因为您不需要为每个子元素编了写相似的事件处理代码。
4.处理多个事件类型: 通过在祖先元素上处理多个事件类型,可以实现更多的灵活性。例如,您可以在祖先元素上处理点击事件、鼠标移动事件和键盘事件,而不必为每个事件类型创建单独的事件处理程序。

示例:假设您有一个无序列表(《u1>)中的多个列表项(《1i>),您希望在点击任何列表项时执行某些操作。
您可以使用事件委托来处理这些点击事件,而不必为每个列表项单独添加事件处理程序。

1
2
3
4
5
6
7
const ulElement = document.querySelector("ul");
ulElement.addEventListener("click", function (event) {
if (event.target.tagName === "LI") {
// 在这里执行点击列表项时的操作
console.log("点击了列表项: " + event.target.textContent);
}
});

在上述示例中,事件委托将点击事件处理程序附加到了<u1》元素上,并使用 event.target 来确定被点击的列表项。这种方法使得单个事件处理程序能够处理整个列表的点击事件。

JavaScript 动画和 CSS3 动画有什么区别?

实现方式:
JavaScript 动画: JavaScript 动画是通过编写 ]avaScript 代码来操作 DOM 元素的样式和属性,从而实现动画效果。您可以使用 setTimeout、setInterval 或现代的动画库(如 GreenSock Animation Platform)来创建 JavaScript 动画,
CSS3 动画: CSS3 动画是使用 CSS3 的动画属性和关键帧动画来定义和控制动画效果。您可以通过在 CSS 中定义关键帧和过渡效果来创建 CSS3 动画。
性能:
JavaScript 动画可以在更复杂的动画场景下提供更多的控制和灵活性,但性能取决于代码的JavaScript动画:质量。不合理的 JavaScript 动画可能导致性能问题,因为它们通常需要大量的计算。
CSS3 动画: CSS3 动画通常更具性能优势,因为浏览器可以使用硬件加速来处理它们,而不需要 JavaScript 的运行时计算。CSS3 动画通常更流畅和高效,特别是在简单的过渡效果中。
适用场景:
JavaScript 动画: 适用于需要更多控制和互动性的场景,例如游戏、用户交互和需要基于条件的动画JavaScript 动画可以响应用户输入,并在运行时根据条件调整动画。

​ CSS3 动画: 适用于简单的过渡效果、页面加载动画、滑动效果、渐变等。CSS3 动画是为了更好的性能和可维护性而设计的,适合许多常见的动画需求。
可维护性:
​ JavaScript 动画: JavaScript 动画可能需要更多的代码和维护工作,尤其是对于复杂的动画效果。它们通常需要手动处理动画的每一帧。
​ CSS3 动画: CSS3 动画通常更容易维护,因为它们将动画效果与样式分开,可以在样式表中轻松修改动画的属性和参数。

获取元素位置

getBoundingClientRect()方法:

1
2
3
4
5
6
const element = document.getElementById("myElement");
const rect = element.getBoundingClientRect();
console.log("元素左上角的X坐标: " + rect.left);
console.log("元素左上角的Y坐标: " + rect.top);
console.log("元素右下角的X坐标: " + rect.right);
console.log("元素右下角的Y坐标: " + rect.bottom);

offsetTop 和 offsetLeft 属性:

1
2
3
const element = document.getElementById("myElement");
console.log("元素的上边缘的偏移量: " + element.offsetTop);
console.log("元素的左边缘的偏移量: " + element.offsetLeft);

pageX 和 pageY 属性:

1
2
3
4
element.addEventListener("mousemove", function (event) {
console.log("鼠标的X坐标: " + event.pageX);
console.log("鼠标的Y坐标: " + event.pageY);
});

clientX和 clientY 属性:

1
2
3
4
element.addEventListener("mousemove", function (event) {
console.log("鼠标在视口中的X坐标: " + event.clientX);
console.log("鼠标在视口中的Y坐标: " + event.clientY);
});

document.write和innerHTML的区别

输出位置:
document.write: document.write 方法将内容直接写入到页面的当前位置,它会覆盖已存在的内容。如果它在页面加载后调用,它会覆盖整个页面内容,因此通常不建议在文档加载后使用它。
innerHTML: innerHTML 是 DOM 元素的属性,可以用来设置或获取元素的 HTML 内容。它可以用于特定元素,而不会覆盖整个页面。
2.用法:
document.write:通常用于在页面加载过程中动态生成 HTML 内容。它是一种旧的、不太推荐的方法0因为它可能导致页面结构混乱,不易维护。
innerHTML:通常用于通过 ]avaScrint 动态更改特定元素的内容。它更加灵活,允许您以更精确的方式操作 DOM.
3.DOM 操作:
document.write:不是 DOM 操作,它仅用于输出文本到页面0
innerHTML:是 DOM 操作,允许您操作特定元素的内容,包括添加、删除和替换元素的 HTML 内容。0

mouseover和mouseenter的区别

触发时机:
mouseover :当鼠标指针从一个元素的外部进入到元素的范围内时触发该事件。它会在进入元素内部时触0发一次,然后在鼠标在元素内部(有子元素)移动时会多次触发。
mouseenter:当鼠标指针从一个元素的外部进入到元素的范围内时触发该事件。不同于

​ mouseenter 只在第一次进入元素内部时触发一次,之后鼠标在元素内部移动不会再次触发。

冒泡:
mouseover会冒泡,也就是说当鼠标进入子元素时,父元素的mouseover事件也会被触发。

​ mouseenter不会冒泡,只有在真正进入指定元素时触发。

应用场景:
mouseover更常用于需要监听鼠标进入和离开元素的情况,特别是当需要处理子元素的情况

​ mouseenter 更常用于只需要在鼠标第一次进入元素时触发事件的情况,通常用于菜单、工具提示等需要0忽略子元素的场景。