canvas

canvas

HTML5 <canvas> 元素是一个强大的绘图工具,允许开发者在网页上绘制图形、图像、动画等。以下是 <canvas> 的常用方法和属性的详细介绍。

1. 基本概念

<canvas> 是一个矩形区域,可以通过 JavaScript 的 CanvasRenderingContext2D 接口进行绘图操作。以下是基本的 HTML 和 JavaScript 初始化代码:

HTML

1
2
3
<canvas id="myCanvas" width="500" height="500">
您的浏览器不支持 Canvas。
</canvas>

JavaScript

1
2
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d'); // 获取 2D 渲染上下文

2. 常用属性

<canvas> 元素本身和 CanvasRenderingContext2D 对象都有一些重要的属性。

<canvas> 元素属性

  • widthheight:定义画布的宽度和高度(以像素为单位)。
    1
    <canvas width="500" height="500"></canvas>
  • getContext:获取绘图上下文(如 2dwebgl)。
    1
    const ctx = canvas.getContext('2d');

CanvasRenderingContext2D 属性

  • fillStyle:设置填充颜色或渐变。
    1
    2
    ctx.fillStyle = 'red';
    ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
  • strokeStyle:设置描边颜色或渐变。
    1
    ctx.strokeStyle = 'blue';
  • lineWidth:设置线条宽度。
    1
    ctx.lineWidth = 5;
  • font:设置文本字体。
    1
    ctx.font = '20px Arial';
  • textAligntextBaseline:设置文本对齐方式。
    1
    2
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
  • globalAlpha:设置全局透明度(范围为 0 到 1)。
    1
    ctx.globalAlpha = 0.5;
  • globalCompositeOperation:设置图形的合成方式(如 source-overdestination-over 等)。
    1
    ctx.globalCompositeOperation = 'xor';

3. 常用方法

CanvasRenderingContext2D 提供了丰富的绘图方法,可以绘制形状、路径、图像等。

绘制矩形

  • fillRect(x, y, width, height):绘制填充矩形。
    1
    2
    ctx.fillStyle = 'green';
    ctx.fillRect(10, 10, 100, 100);
  • strokeRect(x, y, width, height):绘制描边矩形。
    1
    2
    ctx.strokeStyle = 'black';
    ctx.strokeRect(10, 10, 100, 100);
  • clearRect(x, y, width, height):清除指定区域。
    1
    ctx.clearRect(10, 10, 100, 100);

绘制路径

  • beginPath():开始新的路径。
  • closePath():闭合路径。
  • moveTo(x, y):移动到指定点。
  • lineTo(x, y):绘制直线到指定点。
  • arc(x, y, radius, startAngle, endAngle, anticlockwise):绘制圆形或圆弧。
  • bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y):绘制贝塞尔曲线。
  • quadraticCurveTo(cpx, cpy, x, y):绘制二次贝塞尔曲线。
  • fill():填充路径。
  • stroke():描边路径。
    示例:
1
2
3
4
5
6
7
8
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 150);
ctx.arc(150, 150, 50, 0, Math.PI * 2);
ctx.fillStyle = 'yellow';
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();

绘制文本

  • fillText(text, x, y):绘制填充文本。
    1
    ctx.fillText('Hello, Canvas!', 10, 50);
  • strokeText(text, x, y):绘制描边文本。
    1
    ctx.strokeText('Hello, Canvas!', 10, 50);

绘制图像

  • drawImage(image, x, y):将图像绘制到画布上。
    1
    2
    3
    4
    5
    const img = new Image();
    img.src = 'image.jpg';
    img.onload = () => {
    ctx.drawImage(img, 10, 10);
    };

转换和变换

  • translate(x, y):平移画布。
    1
    ctx.translate(100, 100);
  • rotate(angle):旋转画布。
    1
    ctx.rotate(Math.PI / 4); // 旋转45度
  • scale(x, y):缩放画布。
    1
    ctx.scale(2, 2); // 放大2倍
  • save()restore():保存和恢复画布状态。
    1
    2
    3
    ctx.save();
    ctx.translate(100, 100);
    ctx.restore();

4. 事件交互

<canvas> 元素支持鼠标和触摸事件,可以实现交互功能。

示例:绘制鼠标轨迹

1
2
3
4
5
6
7
8
9
10
11
<canvas id="myCanvas" width="500" height="500"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ctx.fillRect(x, y, 5, 5);
});
</script>

5. 动画

通过 requestAnimationFrame 方法可以实现动画效果。

示例:移动的矩形

1
2
3
4
5
6
7
8
9
10
11
12
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 0;
let y = 0;
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布
ctx.fillRect(x, y, 50, 50); // 绘制矩形
x += 1; // 更新位置
y += 1;
requestAnimationFrame(animate); // 递归调用
}
animate();

6. 注意事项

  1. 性能优化:尽量减少 clearRect 和重绘操作。
  2. 分辨率适配:在高分辨率设备上,可能需要调整 canvas 的宽高以匹配显示效果。
  3. 兼容性<canvas> 在现代浏览器中广泛支持,但在旧版本浏览器中可能需要备用方案。
    通过这些方法和属性,你可以实现丰富的图形绘制和交互功能,满足大多数网页绘图需求。

地理位置

地理位置

在JavaScript中,可以通过HTML5的Geolocation API获取用户的地理位置信息,包括经度和纬度。以下是实现地理位置功能的常用方法和步骤:

1. 检查浏览器支持

在使用Geolocation API之前,需要检查浏览器是否支持该功能:

1
2
3
4
5
6
if ("geolocation" in navigator) {
// 浏览器支持 Geolocation
navigator.geolocation.getCurrentPosition(showPosition, showError);
} else {
alert("您的浏览器不支持地理位置功能。");
}

2. 获取当前位置

通过navigator.geolocation.getCurrentPosition()方法可以获取用户的当前位置。该方法接受两个回调函数:成功时调用的函数和失败时调用的函数。

成功回调

1
2
3
4
5
function showPosition(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
console.log("您的位置:纬度 " + latitude + ",经度 " + longitude);
}

错误回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function showError(error) {
switch (error.code) {
case error.PERMISSION_DENIED:
console.error("用户拒绝了位置请求。");
break;
case error.POSITION_UNAVAILABLE:
console.error("无法获取位置信息。");
break;
case error.TIMEOUT:
console.error("获取位置信息超时。");
break;
case error.UNKNOWN_ERROR:
console.error("未知错误。");
break;
}
}

3. 持续追踪位置

如果需要实时更新用户的位置,可以使用navigator.geolocation.watchPosition()方法:

1
2
3
4
5
6
7
let watchId = navigator.geolocation.watchPosition((position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
console.log("更新位置:纬度 " + latitude + ",经度 " + longitude);
});
// 停止追踪位置
navigator.geolocation.clearWatch(watchId);

4. 地图集成

获取地理位置后,可以将其与地图服务(如Google Maps API)结合使用。例如,可以通过点击事件跳转到地图应用:

1
2
3
4
5
6
7
8
document.getElementById("myButton").addEventListener("click", () => {
navigator.geolocation.getCurrentPosition((position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const mapLink = `https://www.google.com/maps?q=${latitude},${longitude}`;
window.open(mapLink, "_blank");
});
});

5. 注意事项

  • 用户权限:获取地理位置需要用户授权,用户可能会拒绝请求。
  • 安全上下文:从Chrome 50开始,Geolocation API仅在HTTPS环境下工作。
  • 错误处理:在获取位置时,应处理可能出现的错误,如超时或位置不可用。
    通过这些方法,可以实现基于地理位置的功能,如导航、地图集成和实时位置追踪。

js中prototype属性与继承

JS中prototype属性与继承

继承方法

继承是 Java 、 C++等基于类的语言中的一个术语。它的含义是子类的对象可以调用父类的属性和方法。基于对象的 ES 语言根本没有类的概念,当然也就不存在基于类的那种继承方式,但是,它可以通过 prototype 属性来达到类似于继承的效果。 prototype 是 ES 中 function 类型对象的一个特殊属性。每个 function 类型的对象都有 prototype 属性, prototype 属性的值是 object 类型的对象。在 FireBug 中可以看到 Object ( Object 本身是 function 类型)的 prototype 属性类型
function 对象中 prototype 属性对象的作用是这样的:在 function 对象创建出的 object 类型对象实例中可以直接调用 function 对象的 prototype 属性对象中的属性(包括方法属性),例如下面的例子 。

1
2
3
4
5
6
7
8
9
function Car(color, displacement) { 
this.color= color ;
this.displacement = displacement;
}
Car.prototype.logMessage = function(){
console.log(this.color,this.displacement);
}
var car = new Car("black","2.4T");
car.logMessage(); //black, 2.4T

这个例子中,给 Car 的 prototype 属性对象添加了 logMessage 方法,这样使用 Car 创建 的 car 对象就可以直接调用 logMessage 方法 。 虽然这里可以使用 car 调用 logMessage 方法, 但是 car 对象本身并不会添加这个方法,只是可以调用而已 。 function 创建的实例对象在调用属性时会首先在自己的属性中查找,如果找不到就会去 function 的 prototype 属性对象中查找 。 但是,创建的对象只是可以调用 prototype 中的属性 。 但是并不会实际拥有那些属性,也不可以对它们进行修改(修改操作会在实例对象中添加一个同名属性) 。 当创建的实例对象定义了同名的属性后就会覆盖 prototype 中的属性,但是原来 prototype 中的属性并不会发生变化,而且当创建出来的对象删除了添加的属性后,原来 prototype 中的属性还可以继续调用,请看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Car(color, displacement) { 
this.color= color ;
this.displacement = displacement;
}
Car.prototype.logMessage = function(){
console.log(this.color,this.displacement);
}
var car = new Car("black","2.4T");
car.logMessage(); //black, 2.4T
car.logMessage= function(){
console.log(this.color);
}
car.logMessage();
delete car.logMessage;
car.logMessage();

在这个例子中,使用 Car 直接创建的 car 对象并没有 logMessage 方法,所以第一次调 用 logMessage 方法时会调用 Car 的 prototype 属 性对象中的 logMessage 方 法, 然后给 car 定义了 logMessage 方法,这时再调用 logMessage 方法就会调用 car 自己的 logMessage 方法了 , 最后又删除了 car 的 logMessage 方法,此时调用 logMessage 方法就会再次调用 Car 的 prototype 属 性对象中的 logMessage 方法, 而且 Car 的 prototype 属性对象中的 logMessage 方法的内容也没有发生变化 。

多层继承

function 的 prototype 属性是 object 类型的属性对象,其本身可能也使用 function创建的对象,通过这种方法就可以实现多层继承,例如下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function log(msg) {
console.log(msg);
}
function Person() {}
Person.prototype.logPerson = function() {
log("person");
}
function Teacher() {
this.logTeacher = function() {
log("teacher");
}
}
Teacher.prototype = new Person();
Teacher.prototype.logPrototype = function() {
log("prototype");
}
var teacher = new Teacher();
teacher.logTeacher();
teacher.logPrototype();
teacher.logPerson();

这个例子中,因为 Teacher 的 prototype 属性是 Person 创建的实例对象,而使用 Teacher 创建出来的 teacher 对象可以调用 Teacher 的 prototype 属性对象的属性,所以 teacher 对象可以调用 Person 创建的实例对象的属性。又因为 Person 创建的实例对象可以调用 Person 的 prototype 属性对象中的属性,所以 teacher 对象也可以调用 Person 的 prototype 属性对象中的的logPerson 。另外,因为此程序给 Teacher 的 prototype 属性对象添加了 logPrototype 方法,所以 teacher 也可以调用 logPrototype 方法。最后的输出结果如下。

1
teacher prototype person

这种调用方法相当于基于类语言中的多层继承,Teacher 创建出来的 teacher 对象在调用属性时会首先在自己的属性中查找 , 如果找不到就会到 Teacher 的 prototype 属性对象的属性中查找,如果还找不到就会到 Person 的 prototype 属性对象的属性中查找,而 Teacher 的 prototype 又由两部分组成 ,一部分是用 Person 创建的 person 对象,另一部分是直接定义 的 logPrototype 方法 。

使用 Prototype 时的注意事项

  • todo
    在 function 的 prototype 属性对象中默认存在一个名为 constructor 的属性 。 这个属性默认指向 function 方法自身,例如上节例子中 Person 的 prototype 属性对象的 constructor 属性就指向了 Person 。 但是,Teacher 的 prototype 由于被赋予了新的值,因此它的 constructor 属性就不存在(使用 Person 创建的 person 对象自身并没有 constructor 属性) 。 这时,如果调用 teacher.constructor 就 返回 Person 函数 (因为最后会沿着 prototype 找到 person 的 prototype 属性对象的 constructor 属性) 。 为了可以使用 constructor 属性得到正确的构造函数,可以手 动给 Teacher 的 prototype 属性对象的 constructor 属性赋值为 Teacher ,代码如下所示 。
1
2
```
使用 prototype 时应注意以下三点 。 一是, prototype 是属于 function 类型对象的属性 , prototype 自身的属性可以被 function 创建的 object 类型的实例对象使用,但是 object 类型的实例对象自身并没有 prototype 属性 。 二是,如果要给 function 对象的 prototype 属性赋 予新的值并且又要添加新的属性,则需 要先赋予新值,然后再添加新的属性,否则在赋值时,会将原先添加的属性覆盖掉,例如下 面的代码 。
1
在上述代码中,在执行最后一行代码 teacher.logPrototype ()的时候会报错,这是因为 给 Teacher 的 prototype 属性对象添加了 logPrototype 属性方法后,又将 prototype 赋值为new Person (),而新的 prototype 中并没有 logPrototype 方法,所以调用就会出错,也就 是说 logPrototype 被新的对象覆盖 。 也 nction 创建的对象在调用属性时是实时按 prototype 链依次查找的,而不是将 prototype 中的属性关联到创建的对象本身,因此创建完对象后,再修改 function 的 prototype 也会影响 到创建的对象的调用,例如下面的例子 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这里的 log 方法是在 teacher 对象已经创建完成后添加的,但是在 teacher 对象中仍然可以使用,也就是说 prototype 中的属性是动态查询的。另外,使用 prototype 除了可以实现继承之外,还可以节约内存,因为无论使用 function 创建多少对象,它们所指向的 prototype 对象在内存中都只有一份。但是,使用 prototype 中的属性比直接使用对象中定义的属性在执行效率上理论来说会低一些。
## 原型链
JavaScript 中的原型链是一种实现继承的机制。每个 JavaScript 对象都有一个内部属性,称为 `Prototype`,通常可以通过 `__proto__` 属性或 `Object.getPrototypeOf()` 方法来访问。这个属性指向另一个对象,即该对象的原型。原型链就是通过这种原型关系连接起来的一系列对象。
### 原型链的基本概念
1. **原型对象**:每个函数都有一个 `prototype` 属性,该属性是一个对象,称为原型对象。当你创建一个函数的实例时,这个实例的 `Prototype` 属性会指向该函数的 `prototype` 属性所指向的对象。
2. **属性查找**:当你尝试访问一个对象的属性或方法时,JavaScript 引擎会首先在该对象本身上查找。如果找不到,就会沿着原型链向上查找,直到找到该属性或到达原型链的末端(即 `null`)。
3. **原型链的末端**:原型链的末端是 `Object.prototype`,它的 `Prototype` 属性是 `null`。所有原型链最终都会指向 `Object.prototype`。
### 示例
```javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}!`);
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
console.log(person1.sayHello); // 输出: [Function: sayHello]
console.log(person1.__proto__ === Person.prototype); // true
console.log(person1.__proto__.__proto__ === Object.prototype); // true
console.log(person1.__proto__.__proto__.__proto__); // null

在这个例子中:

  • Person 是一个构造函数。
  • Person.prototype 是一个对象,它有一个 sayHello 方法。
  • person1person2Person 的实例。它们的 Prototype 指向 Person.prototype
  • Person.prototypePrototype 指向 Object.prototype
  • Object.prototypePrototypenull,这是原型链的末端。

原型链的优缺点

  • 优点
    • 实现了代码的复用,多个实例可以共享原型对象上的属性和方法。
    • 提供了一种继承机制,可以通过原型链来实现对象之间的继承关系。
  • 缺点
    • 过长的原型链会导致性能问题,因为属性查找需要沿着原型链逐级向上查找。
    • 如果原型链上的对象被修改,可能会影响所有继承该原型的对象,这可能导致一些难以追踪的错误。

注意事项

  • 避免修改 Object.prototype:修改 Object.prototype 会影响所有对象,因为所有对象的原型链最终都会指向 Object.prototype
  • 使用 Object.create():可以更清晰地创建对象及其原型关系,例如 const obj = Object.create(proto),其中 proto 是新对象的原型。
    通过理解原型链的工作原理,可以更好地进行面向对象编程和对象继承的设计。

对象自身的属性和prototype里的属性有什么区别

在 JavaScript 中,对象自身的属性和 prototype 里的属性有以下主要区别:

一、定义位置不同

  • 对象自身的属性:直接定义在对象实例上。例如:
    1
    2
    3
    4
    let obj = {
    name: "Kimi",
    age: 20
    };
    这里 nameage 就是对象 obj 自身的属性。
  • prototype 里的属性:定义在对象的原型上。在 JavaScript 中,每个函数都有一个 prototype 属性,指向一个对象,这个对象被称为函数的原型对象。当通过构造函数创建对象实例时,实例对象的内部属性 __proto__ 会指向构造函数的 prototype。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }
    Person.prototype.sayHello = function() {
    console.log("Hello");
    };
    let person1 = new Person("Kimi", 20);
    这里 sayHello 方法是定义在 Personprototype 上的,person1 实例通过 __proto__ 链可以访问到 sayHello 方法。

二、访问方式不同

  • 对象自身的属性:可以直接通过对象实例访问。例如:
    1
    2
    console.log(obj.name); // 输出 Kimi
    console.log(obj.age); // 输出 20
  • prototype 里的属性:通过对象实例访问时,如果实例上没有该属性,会沿着原型链向上查找。例如:
    1
    2
    console.log(person1.sayHello); // 输出 ƒ () { console.log("Hello"); }
    person1.sayHello(); // 控制台输出 Hello
    这里 person1 实例本身没有 sayHello 方法,但是它可以通过原型链找到 Person.prototype 上的 sayHello 方法并调用。

三、内存占用不同

  • 对象自身的属性:每个对象实例都有自己的属性副本。如果创建多个对象实例,每个实例都会有自己的属性占用内存。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    let obj1 = {
    name: "Kimi",
    age: 20
    };
    let obj2 = {
    name: "Moonshot",
    age: 30
    };
    obj1obj2 各自占用内存来存储 nameage 属性。
  • prototype 里的属性:所有通过同一个构造函数创建的实例共享原型上的属性。这意味着无论创建多少个实例,原型上的属性只有一份,节省内存。例如:
    1
    let person2 = new Person("Moonshot", 30);
    person1person2 都可以通过原型链访问到 Person.prototype 上的 sayHello 方法,而 sayHello 方法在内存中只有一份。

四、修改属性的影响不同

  • 对象自身的属性:修改一个实例的自身属性,不会影响其他实例。例如:
    1
    2
    obj1.name = "Kimi Moonshot";
    console.log(obj2.name); // 输出 Moonshot,obj2 的 name 属性不受影响
  • prototype 里的属性:修改原型上的属性,会影响所有通过该构造函数创建的实例。例如:
    1
    2
    3
    Person.prototype.age = 25;
    console.log(person1.age); // 输出 25
    console.log(person2.age); // 输出 25
    但是,如果实例自身已经存在同名属性,则修改原型上的属性不会影响该实例的自身属性。例如:
    1
    2
    3
    4
    person1.age = 22;
    Person.prototype.age = 30;
    console.log(person1.age); // 输出 22,因为 person1 自身有 age 属性
    console.log(person2.age); // 输出 30

五、应用场景不同

  • 对象自身的属性:适用于存储每个实例独有的数据。例如,每个 Person 实例的 nameage 属性都是不同的,应该定义在实例自身上。
  • prototype 里的属性:适用于存储多个实例共享的方法或数据。例如,所有 Person 实例都可以使用 sayHello 方法,这个方法就应该定义在 Personprototype 上,以实现代码复用和节省内存。

多媒体

多媒体

HTML5 提供了多种方法来处理多媒体内容,主要包括使用 <video><audio> 标签嵌入和控制音视频内容,以及通过 JavaScript 进行高级操作。以下是详细介绍:

1. <video><audio> 标签

HTML5 引入了 <video><audio> 标签,用于在网页中嵌入视频和音频内容,而无需依赖第三方插件。

<video> 标签

  • 基本用法
    1
    2
    3
    4
    5
    <video width="640" height="360" controls>
    <source src="movie.mp4" type="video/mp4">
    <source src="movie.webm" type="video/webm">
    您的浏览器不支持 HTML5 视频。
    </video>
  • 常用属性
    • controls:显示播放控件。
    • autoplay:页面加载后自动播放。
    • loop:循环播放。
    • muted:静音播放。
    • poster:视频加载前显示的图像。
    • preload:预加载策略(autometadatanone)。

<audio> 标签

  • 基本用法
    1
    2
    3
    4
    5
    <audio controls>
    <source src="audio.mp3" type="audio/mpeg">
    <source src="audio.ogg" type="audio/ogg">
    您的浏览器不支持 HTML5 音频。
    </audio>
  • 常用属性
    • controls:显示播放控件。
    • autoplay:自动播放。
    • loop:循环播放。
    • muted:静音播放。

2. JavaScript 控制多媒体

通过 JavaScript,可以实现更复杂的多媒体控制。

视频播放控制

1
2
3
4
5
6
const video = document.getElementById('myVideo');
video.play(); // 播放视频
video.pause(); // 暂停视频
video.volume = 0.5; // 设置音量(0.0 到 1.0)
video.currentTime = 10; // 跳转到第 10 秒
video.playbackRate = 2; // 设置播放速度(0.5 到 2.0)

事件监听

1
2
3
4
5
6
video.addEventListener('loadedmetadata', () => {
console.log(`视频时长: ${video.duration}秒`);
});
video.addEventListener('timeupdate', () => {
console.log(`播放进度: ${(video.currentTime / video.duration) * 100}%`);
});

3. 字幕和媒体轨道

HTML5 支持通过 <track> 标签添加字幕、章节等。

1
2
3
4
5
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="subtitles" src="subs_zh.vtt" srclang="zh" label="中文" default>
<track kind="subtitles" src="subs_en.vtt" srclang="en" label="English">
</video>

4. 其他多媒体方法

  • <iframe>:用于嵌入外部内容(如视频、地图等)。
  • <embed><object>:用于嵌入其他类型的多媒体内容。

5. 浏览器兼容性与格式支持

  • 视频格式:推荐使用 MP4(H.264 编码)和 WebM(VP8/VP9 编码)。
  • 音频格式:常用 MP3 和 Ogg。
    通过这些方法,HTML5 提供了强大的多媒体处理能力,能够满足大多数网页开发需求。

拖拽

拖拽

html

HTML5 Drag and Drop API 提供了一种强大的方式,允许用户通过鼠标或触摸屏拖动网页中的元素,并将其放置到其他位置。以下是实现拖拽功能的基本步骤和关键事件的介绍:

1. 基本概念

HTML5 拖拽 API 主要由两部分组成:

  • 拖拽源(Drag Source):用户开始拖动的元素。
  • 拖拽目标(Drop Target):用户希望放置被拖动元素的位置。

2. 启用拖拽功能

要使一个元素可拖拽,需要设置其 draggable 属性为 true

1
<div id="dragElement" draggable="true">拖拽我!</div>

3. 关键事件

实现拖拽功能需要监听以下事件:

  • dragstart:拖拽开始时触发。通常用于存储拖拽数据。
    1
    2
    3
    document.getElementById('dragElement').addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', e.target.id); // 设置拖拽数据
    });
  • dragover:拖拽元素在目标区域上方移动时触发。必须调用 e.preventDefault(),否则默认行为会阻止放置操作。
    1
    2
    3
    document.getElementById('dropArea').addEventListener('dragover', (e) => {
    e.preventDefault();
    });
  • drop:拖拽元素被放置时触发。在这里处理放置逻辑。
    1
    2
    3
    4
    5
    6
    document.getElementById('dropArea').addEventListener('drop', (e) => {
    e.preventDefault();
    const data = e.dataTransfer.getData('text/plain'); // 获取拖拽数据
    const draggedElement = document.getElementById(data);
    e.target.appendChild(draggedElement); // 将拖拽元素添加到目标区域
    });

4. 高级用法

自定义拖拽效果

  • 设置拖拽数据:通过 dataTransfer 对象传递拖拽数据。
    1
    e.dataTransfer.setData('text/plain', '自定义数据');
  • 提供视觉反馈:在拖拽过程中改变目标区域的样式。
    1
    2
    3
    4
    5
    6
    7
    dropArea.addEventListener('dragover', (e) => {
    e.preventDefault();
    dropArea.style.backgroundColor = 'lightgreen'; // 提供反馈
    });
    dropArea.addEventListener('dragleave', () => {
    dropArea.style.backgroundColor = ''; // 恢复背景色
    });
  • 拖拽文件:支持文件上传功能。
    1
    2
    3
    4
    5
    dropArea.addEventListener('drop', (e) => {
    e.preventDefault();
    const files = e.dataTransfer.files; // 获取拖拽的文件
    console.log(files);
    });

5. 应用场景

  • 图片与文本拖拽:实现内容的动态交换和调整。
  • 文件上传:允许用户将文件拖拽到网页中进行上传。
  • 自定义列表排序:通过拖拽重新排序列表元素。

6. 注意事项

  • 浏览器兼容性:HTML5 拖拽 API 在现代浏览器中广泛支持,但在旧版本浏览器中可能存在兼容性问题。
  • 事件监听:确保在目标区域监听 dragoverdrop 事件,并调用 preventDefault()
    通过这些步骤和事件,你可以实现丰富的拖拽交互功能,提升网页的用户体验。

canvas

在HTML5 <canvas> 中实现拖拽功能的核心在于监听鼠标事件(如 mousedownmousemovemouseup),并通过这些事件更新画布上的元素位置。以下是实现Canvas拖拽功能的完整步骤和代码示例:

1. HTML结构

首先,在页面中添加一个 <canvas> 元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas 拖拽示例</title>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="800" height="600"></canvas>
<script src="script.js"></script>
</body>
</html>

2. JavaScript实现

script.js 文件中,通过监听鼠标事件实现拖拽逻辑。

初始化画布和绘制图形

1
2
3
4
5
6
7
8
9
10
11
12
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let isDragging = false; // 是否正在拖拽
let startX, startY; // 鼠标初始位置
let offsetX = 0, offsetY = 0; // 元素偏移量
// 绘制一个可拖拽的矩形
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布
ctx.fillStyle = 'red';
ctx.fillRect(100 - offsetX, 100 - offsetY, 100, 100); // 绘制矩形
}
draw(); // 初始绘制

监听鼠标事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
canvas.addEventListener('mousedown', (e) => {
isDragging = true; // 开始拖拽
startX = e.offsetX; // 记录鼠标初始位置
startY = e.offsetY;
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return; // 如果没有拖拽,则不处理
offsetX += e.offsetX - startX; // 更新偏移量
offsetY += e.offsetY - startY;
startX = e.offsetX; // 更新鼠标位置
startY = e.offsetY;
draw(); // 重新绘制
});
canvas.addEventListener('mouseup', () => {
isDragging = false; // 结束拖拽
});

3. 完整代码

将上述代码整合后,即可实现一个简单的Canvas拖拽功能。

HTML

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas 拖拽示例</title>
<style>
canvas {
border: 1px solid black;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let isDragging = false;
let startX, startY;
let offsetX = 0, offsetY = 0;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'red';
ctx.fillRect(100 - offsetX, 100 - offsetY, 100, 100);
}
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.offsetX;
startY = e.offsetY;
});
canvas.addEventListener('mousemove', (e) => {
if (!isDragging) return;
offsetX += e.offsetX - startX;
offsetY += e.offsetY - startY;
startX = e.offsetX;
startY = e.offsetY;
draw();
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
draw();
</script>
</body>
</html>

4. 优化与拓展

  • 性能优化:在 mousemove 事件中,可以使用 requestAnimationFrame 来优化重绘性能。
  • 支持多元素拖拽:可以通过数组存储多个元素,并在 mousedown 事件中判断鼠标点击的位置是否在某个元素内。
  • 触摸事件支持:为了支持移动设备,可以为 <canvas> 添加触摸事件监听。
    通过上述代码,你可以在HTML5 <canvas> 中实现一个简单的拖拽功能,并根据需求进行进一步的优化和拓展。

00 js对象

js对象

菜鸟 JavaScript 对象

JavaScript 的全局属性和函数

js的全局属性:

  • Infinity
  • NAN
  • undefined
    js的全局函数:
  • decodeURI()
  • decodeURIcomponent()
  • encodeURI
  • encodeURIcomponent()
  • escape()
  • eval()
  • isFinite()
  • isNAN()
  • Number()
  • parseFloat()
  • parseInt()
  • String()
  • unescape()
  • Bigint()

JavaScript 中对象的分类

在 JavaScript 中,对象可以按照多种方式进行分类,以下是一些常见的分类方式:

按照对象的来源分类

  • 内置对象
    • 全局对象Object 是 JavaScript 中所有对象的原型对象,它定义了所有对象的基本属性和方法。例如,Object.prototype.toString() 方法可以用于获取对象的类型信息。Function 对象用于创建函数,它是所有函数的构造函数。Array 对象用于创建数组,它提供了许多数组操作的方法,如 push()pop()map() 等。String 对象用于创建字符串,它有许多字符串处理的方法,如 charAt()indexOf()split() 等。Number 对象用于创建数字,它有一些数学运算的方法和常量,如 Number.MAX_VALUENumber.isNaN() 等。Boolean 对象用于创建布尔值,它有一些布尔运算的方法,如 Boolean.toString() 等。
    • 其他内置对象Math 对象提供了数学运算的方法和常量,如 Math.PIMath.sin()Math.random() 等。Date 对象用于创建日期和时间,它有许多日期和时间处理的方法,如 getDate()setFullYear()toLocaleString() 等。RegExp 对象用于创建正则表达式,它用于字符串的匹配、查找和替换等操作,如 test()exec() 等方法。
  • 宿主对象
    • 在浏览器环境中,宿主对象是由浏览器提供的,用于操作浏览器窗口、文档等的 API。例如,window 对象是浏览器窗口的顶层对象,它包含了许多属性和方法,如 alert()confirm()setTimeout() 等。document 对象用于操作 HTML 文档,它有许多属性和方法,如 getElementById()getElementsByTagName()createElement() 等。navigator 对象提供了浏览器的信息,如浏览器的名称、版本等。history 对象用于操作浏览器的历史记录,如 back()forward()pushState() 等方法。
    • 在 Node.js 环境中,宿主对象是由 Node.js 提供的,用于操作文件系统、网络等的 API。例如,fs 模块提供了文件系统操作的 API,如 fs.readFile()fs.writeFile()fs.readdir() 等。http 模块用于创建 HTTP 服务器和客户端,如 http.createServer()http.request() 等方法。path 模块用于处理和转换文件路径,如 path.join()path.resolve()path.basename() 等方法。
  • 自定义对象
    • 通过对象字面量创建:可以直接使用花括号 {} 创建一个对象,并在其中定义属性和方法。例如,let person = {name: '张三', age: 20, sayHello: function() {console.log('你好,我是' + this.name);}},这种方式创建的对象结构简单,适合创建单个对象。
    • 通过构造函数创建:可以定义一个构造函数,然后使用 new 关键字创建对象实例。例如,function Person(name, age) {this.name = name; this.age = age; this.sayHello = function() {console.log('你好,我是' + this.name);};},然后通过 let p1 = new Person('张三', 20) 创建对象。这种方式可以创建多个具有相同结构的对象实例,便于实现对象的复用。
    • 通过工厂函数创建:工厂函数是一种创建对象的函数,它内部使用对象字面量或其他方式创建对象,并返回创建的对象。例如,function createPerson(name, age) {let person = {name: name, age: age}; person.sayHello = function() {console.log('你好,我是' + this.name);}; return person;},然后通过 let p1 = createPerson('张三', 20) 创建对象。这种方式可以隐藏对象的创建细节,提供统一的创建接口。

按照对象的属性和方法分类

  • 普通对象
    • 这种对象主要包含一些普通的属性和方法,用于存储数据和实现一些简单的功能。例如,let book = {title: 'JavaScript高级程序设计', author: '尼古拉斯·泽卡斯', pages: 300, getInfo: function() {return this.title + ' - ' + this.author + ' - ' + this.pages + '页';}},这个对象主要包含书籍的标题、作者、页数等属性,以及一个获取书籍信息的方法。
  • 函数对象
    • 在 JavaScript 中,函数也是对象,可以像普通对象一样拥有属性和方法。例如,function add(a, b) {return a + b;},这个函数对象本身可以添加属性和方法,如 add.count = 0; add.incrementCount = function() {this.count++;};。同时,函数对象还可以作为构造函数创建其他对象,如上面提到的通过构造函数创建对象实例的方式。
  • 数组对象
    • 数组是一种特殊类型的对象,用于存储有序的数据集合。数组对象具有许多特有的属性和方法,如 length 属性表示数组的长度,push() 方法用于向数组末尾添加元素,pop() 方法用于移除数组末尾的元素,shift() 方法用于移除数组开头的元素,unshift() 方法用于向数组开头添加元素,sort() 方法用于对数组元素进行排序等。例如,let arr = [1, 2, 3]; arr.push(4); console.log(arr); // 输出 [1, 2, 3, 4]

按照对象的用途分类

  • 数据对象
    • 主要用于存储和管理数据,这些数据可以是简单的值,也可以是复杂的数据结构。例如,let user = {id: 1, username: 'zhangsan', password: '123456', roles: ['admin', 'user']},这个对象用于存储用户的信息,包括用户 ID、用户名、密码和角色等数据。
  • 工具对象
    • 提供一些工具方法,用于实现特定的功能,如数据处理、格式转换、验证等。例如,let utils = {formatDate(date) {return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();}, validateEmail(email) {return /\S+@\S+\.\S+/.test(email);}},这个对象提供了格式化日期和验证电子邮件地址的工具方法。
  • 控制对象
    • 用于控制程序的流程、状态等。例如,在一个游戏程序中,可以有一个控制对象用于控制游戏的开始、暂停、结束等状态,如 let gameController = {start() {console.log('游戏开始');}, pause() {console.log('游戏暂停');}, end() {console.log('游戏结束');}}

JavaScript 的内置对象

JavaScript 的内置对象是预定义的对象,提供了各种实用的方法和属性,用于执行常见的任务和操作。这些内置对象可以分为几类,包括全局对象、函数对象、数组对象、日期对象、正则表达式对象、错误对象、数学对象、JSON 对象等。以下是一些常见的内置对象及其用法:

1. 全局对象(Global Objects)

全局对象是 globalThis,在浏览器中是 window,在 Node.js 中是 global。全局对象提供了全局变量和函数的访问。

1
2
console.log(globalThis); // Window 或 global
console.log(window === globalThis); // true (在浏览器中)

2. Object 对象

Object 是 JavaScript 中所有对象的基类,提供了许多用于操作对象的方法和属性。

1
2
3
4
5
6
7
const obj = {
name: 'Alice',
age: 30
};
console.log(Object.keys(obj)); // ['name', 'age']
console.log(Object.values(obj)); // ['Alice', 30]
console.log(Object.entries(obj)); // [['name', 'Alice'], ['age', 30]]

3. Function 对象

Function 是所有函数的构造函数,提供了创建和操作函数的方法。

1
2
const greet = new Function('name', 'console.log(`Hello, ${name}!`);');
greet('Alice'); // Hello, Alice!

4. Array 对象

Array 用于表示有序的集合,提供了许多用于操作数组的方法。

1
2
3
4
const arr = [1, 2, 3, 4, 5];
console.log(arr.length); // 5
console.log(arr.map(x => x * 2)); // [2, 4, 6, 8, 10]
console.log(arr.filter(x => x % 2 === 0)); // [2, 4]

5. Date 对象

Date 用于表示日期和时间,提供了多种方法来操作日期和时间。

1
2
3
4
5
const now = new Date();
console.log(now.toISOString()); // 2024-10-01T12:00:00.000Z
console.log(now.getFullYear()); // 2024
console.log(now.getMonth()); // 9 (0-11 表示 1-12 月)
console.log(now.getDate()); // 1

6. RegExp 对象

RegExp 用于表示正则表达式,提供了用于字符串匹配、替换等操作的方法。

1
2
3
const regex = /hello/i;
console.log(regex.test('Hello World')); // true
console.log('Hello World'.match(regex)); // ['Hello']

7. Error 对象

Error 用于表示错误信息,提供了多种错误类型,如 SyntaxErrorReferenceErrorTypeError 等。

1
2
3
4
5
6
try {
throw new Error('Something went wrong');
} catch (e) {
console.log(e.message); // Something went wrong
console.log(e instanceof Error); // true
}

8. Math 对象

Math 提供了基本的数学常量和函数,用于执行数学运算。

1
2
3
console.log(Math.PI); // 3.141592653589793
console.log(Math.sqrt(16)); // 4
console.log(Math.random()); // 0 到 1 之间的随机数

9. JSON 对象

JSON 提供了用于解析和序列化 JSON 数据的方法。

1
2
3
4
5
const obj = { name: 'Alice', age: 30 };
const json = JSON.stringify(obj);
console.log(json); // '{"name":"Alice","age":30}'
const parsedObj = JSON.parse(json);
console.log(parsedObj); // { name: 'Alice', age: 30 }

10. MapSet 对象

MapSet 提供了集合和映射的功能,用于存储键值对和唯一值。

1
2
3
4
5
6
7
const myMap = new Map();
myMap.set('key1', 'value1');
console.log(myMap.get('key1')); // value1
const mySet = new Set();
mySet.add(1);
mySet.add(2);
console.log(mySet.has(1)); // true

11. Symbol 对象

Symbol 用于创建唯一的、不可变的值,常用于对象属性的键,以确保属性名的唯一性。

1
2
const mySymbol = Symbol('mySymbol');
console.log(mySymbol.toString()); // Symbol(mySymbol)

12. Promise 对象

Promise 用于表示异步操作的最终完成或失败,提供了处理异步操作的方法。

1
2
3
4
5
6
const promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Done!'), 1000);
});
promise.then(result => {
console.log(result); // Done!
});

总结

JavaScript 的内置对象提供了丰富的功能,用于执行各种常见的任务和操作。理解这些内置对象及其方法和属性,可以帮助你更高效地编写和管理 JavaScript 代码。通过合理使用这些内置对象,可以提高代码的可读性和可维护性。

JavaScript 的宿主对象

在 JavaScript 中,宿主对象(Host Objects)是由 JavaScript 运行环境(如浏览器或 Node.js)提供的对象,它们扩展了 JavaScript 语言的核心功能。宿主对象的具体实现和行为取决于运行环境,因此不同环境中的宿主对象可能会有所不同。以下是一些常见的宿主对象及其用法:

1. 浏览器环境中的宿主对象

在浏览器环境中,宿主对象主要由浏览器提供,用于操作 DOM、处理浏览器事件、管理浏览器窗口等。

1.1 window 对象

window 对象是浏览器的全局对象,代表浏览器窗口,提供了访问和操作浏览器窗口的方法和属性。

1
2
3
4
5
console.log(window.innerWidth); // 浏览器窗口的宽度
console.log(window.innerHeight); // 浏览器窗口的高度
window.alert('Hello, World!'); // 弹出警告框
window.confirm('Are you sure?'); // 弹出确认框
window.prompt('Enter your name:'); // 弹出输入框

1.2 document 对象

document 对象代表当前加载的 HTML 文档,提供了访问和操作文档的方法和属性。

1
2
3
4
5
6
7
8
const title = document.title; // 获取文档标题
document.title = 'New Title'; // 设置文档标题
const body = document.body; // 获取文档的 body 元素
const head = document.head; // 获取文档的 head 元素
const element = document.getElementById('myElement'); // 通过 ID 获取元素
const elements = document.getElementsByClassName('myClass'); // 通过类名获取元素
const elements = document.getElementsByTagName('div'); // 通过标签名获取元素
const elements = document.querySelectorAll('.myClass'); // 通过 CSS 选择器获取元素

1.3 location 对象

location 对象提供了访问和操作当前 URL 的方法和属性。

1
2
3
4
5
6
console.log(window.location.href); // 获取当前 URL
console.log(window.location.pathname); // 获取当前路径
console.log(window.location.search); // 获取查询字符串
console.log(window.location.hash); // 获取锚点
window.location.href = 'https://example.com'; // 跳转到新 URL
window.location.reload(); // 重新加载当前页面

1.4 navigator 对象

navigator 对象提供了访问浏览器信息的方法和属性。

1
2
3
console.log(navigator.userAgent); // 获取用户代理字符串
console.log(navigator.platform); // 获取操作系统平台
console.log(navigator.language); // 获取浏览器语言

1.5 history 对象

history 对象提供了访问和操作浏览器历史记录的方法和属性。

1
2
3
4
5
6
console.log(history.length); // 获取历史记录的长度
history.back(); // 返回上一页
history.forward(); // 前进到下一页
history.go(-1); // 返回上一页
history.pushState({ state: 'new' }, 'New Title', 'new-url.html'); // 添加新历史记录
history.replaceState({ state: 'new' }, 'New Title', 'new-url.html'); // 替换当前历史记录

2. Node.js 环境中的宿主对象

在 Node.js 环境中,宿主对象主要由 Node.js 提供,用于文件系统操作、网络通信、模块管理等。

2.1 global 对象

global 对象是 Node.js 的全局对象,类似于浏览器中的 window 对象,提供了全局变量和函数的访问。

1
2
console.log(global.process); // 获取 process 对象
console.log(global.Buffer); // 获取 Buffer 对象

2.2 process 对象

process 对象提供了访问和操作当前 Node.js 进程的方法和属性。

1
2
3
4
5
console.log(process.version); // 获取 Node.js 版本
console.log(process.platform); // 获取操作系统平台
console.log(process.cwd()); // 获取当前工作目录
console.log(process.env); // 获取环境变量
process.exit(0); // 退出当前进程

2.3 require 函数

require 函数用于加载模块,是 Node.js 模块系统的核心。

1
2
3
const fs = require('fs'); // 加载文件系统模块
const http = require('http'); // 加载 HTTP 模块
const myModule = require('./myModule'); // 加载自定义模块

2.4 moduleexports 对象

moduleexports 对象用于定义和导出模块。

1
2
3
4
5
6
7
8
// myModule.js
const myFunction = () => {
console.log('Hello, World!');
};
exports.myFunction = myFunction;
// main.js
const myModule = require('./myModule');
myModule.myFunction(); // Hello, World!

2.5 Buffer 对象

Buffer 对象用于处理二进制数据,是 Node.js 中处理文件和网络数据的核心。

1
2
const buffer = Buffer.from('Hello, World!', 'utf-8');
console.log(buffer.toString('utf-8')); // Hello, World!

3. 其他环境中的宿主对象

除了浏览器和 Node.js,其他 JavaScript 运行环境(如 Web Workers、Service Workers、Deno 等)也会提供特定的宿主对象。

3.1 Web Workers

Web Workers 允许在后台线程中运行 JavaScript 代码,提供了 WorkerSharedWorker 对象。

1
2
3
4
5
6
7
8
9
10
11
// 主线程
const worker = new Worker('worker.js');
worker.onmessage = (event) => {
console.log('Message from worker:', event.data);
};
worker.postMessage('Hello, Worker!');
// worker.js
self.onmessage = (event) => {
console.log('Message from main thread:', event.data);
self.postMessage('Hello, Main Thread!');
};

3.2 Service Workers

Service Workers 是一种在浏览器后台运行的脚本,可以拦截和处理网络请求,提供离线支持和推送通知等功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 注册 Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(() => {
console.log('Service Worker registered');
})
.catch(() => {
console.error('Service Worker registration failed');
});
}
// service-worker.js
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

总结

宿主对象是由 JavaScript 运行环境提供的对象,用于扩展 JavaScript 语言的核心功能。不同环境中的宿主对象会有所不同,但它们都提供了丰富的功能,用于操作 DOM、处理浏览器事件、管理文件系统、进行网络通信等。理解这些宿主对象及其方法和属性,可以帮助你更有效地编写和管理 JavaScript 代码,适应不同的运行环境。

function对象和object对象的区别

  1. 从原型链角度
    • 在JavaScript中,Function 对象是所有函数的构造函数。而 Object 对象是所有对象的根构造函数。从原型链角度来看,Function 的原型(Function.prototype)是一个空函数(即 function(){}),这个空函数的原型(Function.prototype.prototype)指向 Object.prototype。也就是说,Function 对象的原型链上有一个指向 Object.prototype 的链接。这使得函数(由 Function 构造)也继承了 Object 对象的一些基本属性和方法,比如 toString()valueOf() 等。
    • 例如,当你创建一个函数function myFunc(){};时,myFunc是一个Function实例,同时由于原型链的关系,它也是Object的“后代”。你可以调用myFunc.toString(),它会返回该函数的字符串形式,这是因为toString()方法是从Object.prototype上继承来的。
  2. 从实例化角度
    • Object对象可以用来创建普通的对象。例如,let obj = new Object();会创建一个空对象。这个对象的原型是Object.prototype
    • Function对象是用来创建函数的。当你使用function myFunc(){};或者let myFunc = new Function('console.log("Hello");');创建函数时,myFunc是一个函数对象。函数对象也是对象的一种,所以它也可以用Object对象的方法来操作。比如你可以使用Object.keys(myFunc)来获取函数对象的可枚举属性名(虽然函数对象通常没有可枚举的自身属性)。
  3. 从类型角度
    • 在JavaScript中,函数也是对象。从类型判断的角度,使用typeof运算符对函数进行判断会返回"function",而对普通对象(由Object构造)进行判断会返回"object"。但是,由于函数是特殊的对象,使用instanceof运算符时,函数实例(即函数对象)既是Function的实例,也是Object的实例。例如:
      1
      2
      3
      function myFunc(){}
      console.log(myFunc instanceof Function); // true
      console.log(myFunc instanceof Object); // true
      这说明函数对象在类型上同时具有FunctionObject的特征。
  4. 从方法和属性共享角度
    • Object对象定义了一些通用的方法和属性,这些方法和属性可以被所有对象(包括函数对象)使用。例如Object.defineProperty()方法可以用来定义对象的属性,无论是普通对象还是函数对象都可以用它来精确地控制属性的特性(如可写性、可枚举性等)。
    • Function对象自身也有一些特定的方法和属性,如Function.prototype.apply()Function.prototype.call()等,这些方法主要用于函数的调用控制,可以让函数在不同的上下文中执行。虽然这些方法是Function特有的,但由于函数也是对象,它们在某种程度上也参与了对象行为的扩展,使得函数对象在调用方式上更加灵活。

JavaScript 内置对象和宿主对象的区别

JavaScript 内置对象和宿主对象有以下主要区别:

定义来源不同

  • 内置对象:是由 JavaScript 语言本身定义和提供的,是 ECMAScript 规范中规定的对象。它们在 JavaScript 引擎初始化时就已经存在,是 JavaScript 语言的核心组成部分,无论在何种 JavaScript 运行环境中(如浏览器、Node.js 等)都会提供这些内置对象。例如,ObjectArrayStringNumberBooleanFunctionMathDateRegExp 等都是内置对象。
  • 宿主对象:是由 JavaScript 运行环境提供的,不同的运行环境会有不同的宿主对象。在浏览器环境中,宿主对象是由浏览器厂商实现的,用于操作浏览器窗口、文档、历史记录等的 API,如 windowdocumentnavigatorhistory 等。在 Node.js 环境中,宿主对象是由 Node.js 团队提供的,用于操作文件系统、网络、模块等的 API,如 fshttppathmodule 等。

提供的功能侧重点不同

  • 内置对象:主要提供 JavaScript 语言层面的基本功能和数据结构操作。例如,Object 对象提供了对象的基本操作方法,如 Object.create()Object.defineProperty() 等,用于创建对象、定义属性等;Array 对象提供了数组的各种操作方法,如 push()pop()map()filter() 等,用于添加、删除、遍历、转换数组元素;String 对象提供了字符串的处理方法,如 charAt()indexOf()split()replace() 等,用于获取字符、查找子串、分割字符串、替换字符等;Math 对象提供了数学运算的方法和常量,如 Math.sin()Math.cos()Math.PI 等,用于进行数学计算;Date 对象提供了日期和时间的操作方法,如 getDate()setFullYear()toLocaleString() 等,用于获取和设置日期时间、格式化日期时间等。
  • 宿主对象:主要提供与运行环境相关的特定功能。在浏览器环境中,宿主对象的功能侧重于操作网页内容和浏览器行为,如 window 对象提供了弹出对话框、操作窗口大小和位置、管理定时器等方法;document 对象提供了操作 HTML 元素、获取元素信息、创建和修改元素等方法;navigator 对象提供了获取浏览器信息、检测设备特性等方法;history 对象提供了操作浏览器历史记录的方法。在 Node.js 环境中,宿主对象的功能侧重于服务器端的文件操作、网络通信、模块管理等,如 fs 模块提供了读写文件、操作文件系统目录等方法;http 模块提供了创建 HTTP 服务器和客户端、处理 HTTP 请求和响应等方法;path 模块提供了处理文件路径的方法;module 模块提供了模块加载和管理的方法。

兼容性和规范性不同

  • 内置对象:由于是 ECMAScript 规范中定义的,所以在不同的 JavaScript 运行环境中,内置对象的行为和提供的方法相对一致,具有较好的兼容性。开发者在编写代码时,可以放心地使用内置对象提供的标准方法,不用担心在不同的环境中会出现较大的差异。例如,Array.prototype.map() 方法在所有现代浏览器和 Node.js 环境中都可以正常使用,用于对数组元素进行遍历和转换。
  • 宿主对象:不同运行环境提供的宿主对象可能会存在差异。在浏览器环境中,不同浏览器厂商实现的宿主对象可能会有一些细微的差别,虽然现代浏览器大多遵循 W3C 等相关规范,但在一些边缘功能或新特性支持上可能会有所不同。例如,某些较新的 CSS 属性或 HTML5 API 在不同浏览器中的支持程度可能会有差异。在 Node.js 环境中,不同版本的 Node.js 也可能对宿主对象的某些方法或模块的支持有所变化,而且 Node.js 的宿主对象与浏览器环境中的宿主对象完全不同,这是由于它们运行环境的侧重点不同所导致的。

生命周期和作用域不同

  • 内置对象:内置对象在 JavaScript 程序的整个生命周期中都可用,它们是全局可用的,可以在任何地方通过相应的名称访问和使用。例如,可以在函数内部、模块内部或全局作用域中直接使用 ArrayString 等内置对象创建实例或调用方法。
  • 宿主对象:宿主对象的生命周期和作用域通常与运行环境的具体上下文有关。在浏览器环境中,宿主对象如 windowdocument 等与浏览器窗口和文档页面的生命周期紧密相关,当页面加载时可用,当页面关闭或刷新时可能会被销毁或重新初始化。在 Node.js 环境中,宿主对象如 fshttp 等模块在模块被加载时可用,在整个 Node.js 应用程序的生命周期中都可以使用,但它们的作用域通常限制在模块内部或通过模块导出的方式在其他模块中使用。

内置的全局函数

JavaScript 提供了一些内置的全局函数,这些函数在全局作用域中可以直接使用,它们为开发者提供了很多便捷的功能。以下是一些常见的 JavaScript 内置全局函数:

一、解析和格式化数字的函数

(一)parseInt(string, radix)

  • 功能:将一个字符串解析成一个整数。
  • 参数
    • string:要解析的字符串。
    • radix(可选):表示字符串的基数(进制),取值范围是 2 到 36。如果不指定该参数,解析时会根据字符串的格式来判断其基数。
  • 返回值:解析后的整数。如果字符串开头不是有效的数字格式,则返回 NaN(Not - a - Number)。
  • 示例
    1
    2
    3
    4
    5
    console.log(parseInt("123")); // 输出 123
    console.log(parseInt("0x10")); // 输出 16,因为以 0x 开头,默认是十六进制
    console.log(parseInt("12.34")); // 输出 12,小数点后的部分会被忽略
    console.log(parseInt("abc")); // 输出 NaN
    console.log(parseInt("1010", 2)); // 输出 10,因为是二进制

(二)parseFloat(string)

  • 功能:将一个字符串解析成一个浮点数。
  • 参数string - 要解析的字符串。
  • 返回值:解析后的浮点数。如果字符串开头不是有效的浮点数格式,则返回 NaN
  • 示例
    1
    2
    3
    4
    console.log(parseFloat("123.45")); // 输出 123.45
    console.log(parseFloat("123")); // 输出 123
    console.log(parseFloat("abc")); // 输出 NaN
    console.log(parseFloat("123.45.67")); // 输出 123.45,第二个小数点后的部分会被忽略

二、编码和解码 URI 的函数

(一)encodeURI(uri)

  • 功能:对 URI(统一资源标识符)进行编码。它会将 URI 中的一些特殊字符(如空格、中文字符等)转换为 % 加上两位十六进制数的形式,以确保 URI 在传输过程中的正确性。
  • 参数uri - 要编码的 URI 字符串。
  • 返回值:编码后的 URI 字符串。
  • 示例
    1
    2
    console.log(encodeURI("http://example.com/search?q=JavaScript教程")); 
    // 输出 http://example.com/search?q=JavaScript%E6%95%99%E7%A8%8B

(二)decodeURI(encodedURI)

  • 功能:对编码后的 URI 进行解码,将 % 加上两位十六进制数的形式还原为原来的字符。
  • 参数encodedURI - 要解码的编码后的 URI 字符串。
  • 返回值:解码后的 URI 字符串。
  • 示例
    1
    2
    console.log(decodeURI("http://example.com/search?q=JavaScript%E6%95%99%E7%A8%8B")); 
    // 输出 http://example.com/search?q=JavaScript教程

(三)encodeURIComponent(uriComponent)

  • 功能:对 URI 组件进行编码。它比 encodeURI 编码更严格,会将更多的字符(如 /? 等)进行编码。这在编码 URI 的查询字符串参数等组件时非常有用。
  • 参数uriComponent - 要编码的 URI 组件字符串。
  • 返回值:编码后的 URI 组件字符串。
  • 示例
    1
    2
    console.log(encodeURIComponent("http://example.com/path?query=abc")); 
    // 输出 http%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dabc

(四)decodeURIComponent(encodedURIComponent)

  • 功能:对编码后的 URI 组件进行解码。
  • 参数encodedURIComponent - 要解码的编码后的 URI 组件字符串。
  • 返回值:解码后的 URI 组件字符串。
  • 示例
    1
    2
    console.log(decodeURIComponent("http%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dabc")); 
    // 输出 http://example.com/path?query=abc

三、类型转换函数

(一)Number(value)

  • 功能:将一个值转换为数字类型。
  • 参数value - 要转换的值。
  • 返回值:转换后的数字。如果转换失败,则返回 NaN
  • 示例
    1
    2
    3
    4
    5
    console.log(Number("123")); // 输出 123
    console.log(Number("12.34")); // 输出 12.34
    console.log(Number("abc")); // 输出 NaN
    console.log(Number(true)); // 输出 1
    console.log(Number(false)); // 输出 0

(二)String(value)

  • 功能:将一个值转换为字符串类型。
  • 参数value - 要转换的值。
  • 返回值:转换后的字符串。
  • 示例
    1
    2
    3
    4
    5
    6
    console.log(String(123)); // 输出 "123"
    console.log(String(12.34)); // 输出 "12.34"
    console.log(String(true)); // 输出 "true"
    console.log(String(false)); // 输出 "false"
    console.log(String(null)); // 输出 "null"
    console.log(String(undefined)); // 输出 "undefined"

(三)Boolean(value)

  • 功能:将一个值转换为布尔类型。
  • 参数value - 要转换的值。
  • 返回值:转换后的布尔值。在 JavaScript 中,false0-00n(BigInt 类型的 0)、""(空字符串)、nullundefinedNaN 被认为是“假值”,其他值被认为是“真值”。
  • 示例
    1
    2
    3
    4
    5
    6
    console.log(Boolean(123)); // 输出 true
    console.log(Boolean("")); // 输出 false
    console.log(Boolean("abc")); // 输出 true
    console.log(Boolean(null)); // 输出 false
    console.log(Boolean(undefined)); // 输出 false
    console.log(Boolean(NaN)); // 输出 false

四、其他常用全局函数

(一)eval(x)

  • 功能:计算字符串 x 中的 JavaScript 代码,并执行它。
  • 参数x - 一个字符串,包含要执行的 JavaScript 代码。
  • 返回值:执行代码后的返回值。如果没有返回值,则返回 undefined
  • 示例
    1
    2
    3
    4
    let result = eval("1 + 2");
    console.log(result); // 输出 3
    eval("console.log('Hello from eval');");
    // 控制台输出 Hello from eval
  • 注意eval 函数非常危险,因为它会执行任意的代码。如果执行的代码来自不可信的源,可能会导致安全漏洞,如跨站脚本攻击(XSS)等。在现代 JavaScript 开发中,通常不推荐使用 eval

(二)isFinite(number)

  • 功能:判断一个值是否是有限的数字。
  • 参数number - 要判断的值。
  • 返回值:如果是有限的数字,返回 true;否则返回 false。对于 NaNInfinity-Infinity,返回 false
  • 示例
    1
    2
    3
    4
    console.log(isFinite(123)); // 输出 true
    console.log(isFinite(Infinity)); // 输出 false
    console.log(isFinite(NaN)); // 输出 false
    console.log(isFinite("123")); // 输出 false,因为参数不是数字类型

(三)isNaN(number)

  • 功能:判断一个值是否是 NaN
  • 参数number - 要判断的值。
  • 返回值:如果是 NaN,返回 true;否则返回 false。需要注意的是,由于 NaN 有一些特殊的性质(如 NaN !== NaN),这个函数在判断是否为 NaN 时很有用。
  • 示例
    1
    2
    3
    console.log(isNaN(NaN)); // 输出 true
    console.log(isNaN(123)); // 输出 false
    console.log(isNaN("abc")); // 输出 true,因为 "abc" 转换为

原型链

原型链

原型链

原型链是 JavaScript 中实现继承的主要方式之一。在 JavaScript 中,每个对象都有一个内部属性,称为 Prototype(在大多数现代浏览器中可以通过 __proto__ 属性访问),这个属性引用了另一个对象。这个被引用的对象被称为原对象的“原型”。通过这种方式,对象可以继承和共享属性和方法。

原型链的基本概念

  1. 原型对象
    • 每个 JavaScript 对象都有一个原型对象,除了 Object.prototype,它是所有对象的最终原型。
  2. 构造函数的原型属性
    • 每个函数在创建时,都有一个 prototype 属性,它是一个对象,包含可以由通过该构造函数创建的对象继承的属性和方法。
  3. 对象的原型访问
    • 可以通过对象的 __proto__ 属性或者 Object.getPrototypeOf(obj) 方法访问一个对象的原型。
  4. 继承
    • 当尝试访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的末端。

原型链的工作方式

当访问一个对象的属性时,JavaScript 引擎会首先在该对象自身上查找,如果找不到,就会查找该对象的原型,如果还是找不到,就继续查找原型的原型,这个过程会一直持续到找到属性为止,或者到达原型链的末端(Object.prototype 的原型是 null)。

示例代码

1
2
3
4
5
6
7
8
9
10
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
return "Hello, I'm " + this.name;
};
var person1 = new Person("Kimi");
console.log(person1.sayHello()); // "Hello, I'm Kimi"
console.log(person1.hasOwnProperty('name')); // true
console.log(person1.hasOwnProperty('sayHello')); // false

在这个例子中,person1 对象通过构造函数 Person 创建,它拥有自己的属性 name,但是 sayHello 方法是继承自 Person.prototype 的。

原型链的问题

  1. 性能问题
    • 原型链过长可能会导致性能问题,因为属性查找需要遍历整个链。
  2. 共享问题
    • 所有对象共享原型上的数据,如果修改了原型上的属性,会影响所有继承自该原型的对象。
  3. 继承链的不可变性
    • 一旦原型链被创建,就很难改变,这限制了继承结构的灵活性。
      原型链是 JavaScript 中实现继承的一种方式,但它并不是唯一的方式。随着 ES6 的引入,类(class)和模块(module)提供了更现代和更灵活的方式来实现继承和代码复用。

几个等式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name) {
this.name = name;
}
Person.prototype.getName = function(){
console.log(this.name);
}
let obj = new Person("clerk")
console.log(obj.constructor);
console.log(obj.__proto__);
console.log(Person.prototype);
console.log(Object.getPrototypeOf(obj));
console.log(Object.getPrototypeOf(obj) === Person.prototype);
console.log(Object.getPrototypeOf(obj) === obj.__proto__);
console.log(Person.prototype === obj.__proto__);
console.log(obj.constructor === Person);
console.log(obj instanceof Person);
console.log(Person.prototype.isPrototypeOf(obj));

JavaScript 中的 constructor 属性

在 JavaScript 中,constructor 属性是每个对象都具有的一个属性,它指向创建该对象的构造函数。这个属性在对象的原型上定义,因此所有通过同一个构造函数创建的对象都会共享同一个 constructor 属性。

1. constructor 属性的基本用法

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const obj = new Object();
console.log(obj.constructor === Object); // true
const arr = new Array();
console.log(arr.constructor === Array); // true
const num = new Number(10);
console.log(num.constructor === Number); // true
const str = new String("Hello");
console.log(str.constructor === String); // true
const bool = new Boolean(true);
console.log(bool.constructor === Boolean); // true
const func = new Function();
console.log(func.constructor === Function); // true
const date = new Date();
console.log(date.constructor === Date); // true
const regex = new RegExp("pattern");
console.log(regex.constructor === RegExp); // true
const error = new Error("message");
console.log(error.constructor === Error); // true

2. 自定义构造函数的 constructor 属性

当你定义自己的构造函数时,通过该构造函数创建的对象也会有一个 constructor 属性,指向该构造函数。

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}
const person1 = new Person('Alice', 30);
console.log(person1.constructor === Person); // true
const person2 = new Person('Bob', 25);
console.log(person2.constructor === Person); // true

3. 修改原型链时的 constructor 属性

当你修改对象的原型时,constructor 属性可能会被覆盖。为了保持正确的 constructor 引用,你可能需要手动修复它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Person(name, age) {
this.name = name;
this.age = age;
}
// 修改原型
Person.prototype = {
greet: function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
};
const person1 = new Person('Alice', 30);
console.log(person1.constructor === Person); // false
console.log(person1.constructor === Object); // true
// 修复 constructor 属性
Person.prototype.constructor = Person;
const person2 = new Person('Bob', 25);
console.log(person2.constructor === Person); // true

4. 使用 constructor 属性创建新对象

constructor 属性可以用于创建新对象,特别是当你不确定对象的类型时,可以动态地创建相同类型的新对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person1 = new Person('Alice', 30);
const person2 = new person1.constructor('Bob', 25);
console.log(person2.name); // Bob
console.log(person2.age); // 25
person2.greet(); // Hello, my name is Bob and I am 25 years old.

5. constructor 属性在继承中的使用

在继承中,constructor 属性可以帮助你理解对象的构造层次结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Animal(name) {
this.name = name;
}
function Dog(name, breed) {
Animal.call(this, name);
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
const myDog = new Dog('Buddy', 'Golden Retriever');
console.log(myDog.constructor === Dog); // true
console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true

总结

constructor 属性是一个指向创建对象的构造函数的引用,它在对象的原型上定义。通过 constructor 属性,可以动态地创建新对象,修复原型链中的构造函数引用,以及在继承中理解对象的层次结构。理解 constructor 属性的用法和行为,可以帮助你更有效地管理和操作 JavaScript 对象。

实例

  • Son.prototype=Father.prototype, 所以Father.prototype 被覆盖了
1
2
3
4
5
6
7
8
9
10
11
12
13
function Father(age){
this.age = age
}
function Son(age){
Father.call(this);
}
Son.prototype = Father.prototype;
Father.prototype.getAge = function(){console.log(40);}
Son.prototype.getAge = function(){console.log(18);}
var father = new Father(40);
var son = new Son(18);
son.getAge();
father.getAge();
  • 直接量没有 prototype,typeof 优先级低
1
2
3
4
console.log(typeof ''.prototype);//''.prototype undefined
console.log(typeof ''.__proto__); //object
console.log(typeof ''.__proto__ === typeof ''.prototype);
console.log(typeof ''.__proto__ === typeof ''.prototype); //false
  • 对象a自身具有属性x,属性值为2,同时其原型对象上也有属性x,属性值为1;对象b在初始化时,也是自身具有属性x,属性值为3,同时其原型对象为函数A的实例,同样具有属性x,由于没有传参,其属性x的值为undefined。当使用delete b.x时,对象b自身的x属性被删除,但是其原型对象上的x属性不会被删除。另外,根据对象属性查找的作用域链规则,访问对象属性时,会先查找对象自身的属性,如果不存在,才会继续在其原型对象上进行查找,故a.x的返回结果为2,b.x的返回结果为undefined
1
2
3
4
5
6
7
8
9
10
function A(x){
this.x = x;
}
A.prototype.x = 1;
function B(x){
this.x = x;
}
B.prototype = new A();
var a = new A(2), b = new B(3);
delete b.x;

哪些对象有prototype属性

在JavaScript中,几乎所有函数对象都有prototype属性,但普通对象(即通过字面量或Object.create创建的对象)通常没有prototype属性。下面详细解释这一点。

函数对象的prototype属性

  1. 函数对象
    • 在JavaScript中,函数也是对象,称为函数对象。每个函数对象都有一个prototype属性。
    • prototype属性是一个对象,用于定义该函数作为构造函数时,其创建的实例对象的原型。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      function Person(name) {
      this.name = name;
      }
      Person.prototype.sayHello = function() {
      console.log('Hello, ' + this.name);
      };
      const person1 = new Person('Alice');
      person1.sayHello(); // 输出: Hello, Alice
      console.log(person1.__proto__ === Person.prototype); // true
  2. 内置构造函数
    • 内置构造函数(如ArrayObjectFunction等)也有prototype属性。
    • 例如:
      1
      2
      3
      console.log(Array.prototype); // Array(0) []
      console.log(Object.prototype); // {}
      console.log(Function.prototype); // function () {}

普通对象的__proto__属性

  1. 普通对象
    • 普通对象(即通过字面量或Object.create创建的对象)没有prototype属性,但它们有一个内部属性Prototype,可以通过__proto__属性或Object.getPrototypeOf方法访问。
    • 例如:
      1
      2
      3
      4
      5
      6
      const obj = { a: 1 };
      console.log(obj.__proto__); // {}
      console.log(Object.getPrototypeOf(obj)); // {}
      const obj2 = Object.create({ b: 2 });
      console.log(obj2.__proto__); // { b: 2 }
      console.log(Object.getPrototypeOf(obj2)); // { b: 2 }
  2. 实例对象
    • 通过构造函数创建的实例对象,其__proto__属性指向构造函数的prototype属性。
    • 例如:
      1
      2
      3
      4
      5
      6
      7
      8
      function Person(name) {
      this.name = name;
      }
      Person.prototype.sayHello = function() {
      console.log('Hello, ' + this.name);
      };
      const person1 = new Person('Alice');
      console.log(person1.__proto__ === Person.prototype); // true

总结

  • 函数对象:所有函数对象都有prototype属性,用于定义该函数作为构造函数时,其创建的实例对象的原型。
  • 普通对象:普通对象没有prototype属性,但有一个内部属性Prototype,可以通过__proto__属性或Object.getPrototypeOf方法访问。
  • 实例对象:通过构造函数创建的实例对象,其__proto__属性指向构造函数的prototype属性。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 函数对象
function MyFunction() {}
console.log(MyFunction.prototype); // {}
// 内置构造函数
console.log(Array.prototype); // Array(0) []
console.log(Object.prototype); // {}
console.log(Function.prototype); // function () {}
// 普通对象
const obj = { a: 1 };
console.log(obj.__proto__); // {}
console.log(Object.getPrototypeOf(obj)); // {}
// 实例对象
const person = new MyFunction();
console.log(person.__proto__ === MyFunction.prototype); // true

通过这些示例,可以清楚地看到哪些对象有prototype属性,哪些对象有__proto__属性。

Array

Array 对象

Array 里有什么

1
2
3
console.log(Object.getOwnPropertyNames(Array))
console.log(Object.getOwnPropertyNames(Array.prototype))
console.log(Object.getOwnPropertyNames(new Array()))
1
2
3
(7) ['length', 'name', 'prototype', 'isArray', 'from', 'fromAsync', 'of']
(40) ['length', 'constructor', 'at', 'concat', 'copyWithin', 'fill', 'find', 'findIndex', 'findLast', 'findLastIndex', 'lastIndexOf', 'pop', 'push', 'reverse', 'shift', 'unshift', 'slice', 'sort', 'splice', 'includes', 'indexOf', 'join', 'keys', 'entries', 'values', 'forEach', 'filter', 'flat', 'flatMap', 'map', 'every', 'some', 'reduce', 'reduceRight', 'toReversed', 'toSorted', 'toSpliced', 'with', 'toLocaleString', 'toString']
['length']

!Array.png

数组元素添加和删除 5

  • push
  • pop
  • unshift
  • shift
  • splice
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var a = [1,2,3];
console.log(a.__proto__ === Array.prototype);
console.log(a.constructor === Array);
var b = [13,14,15]
// 数组元素添加和删除 5个
a.push(4,5,6) //[1, 2, 3, 4, 5, 6]
var newLength = a.push(...b)// [1, 2, 3, 4, 5, 6, 13, 14, 15],9
var items = a.pop() // [1, 2, 3, 4, 5, 6, 13, 14],15
a.unshift(7,8,9)// [7, 8, 9, 1, 2, 3, 4, 5, 6, 13, 14]
a.shift()// [8, 9, 1, 2, 3, 4, 5, 6, 13, 14]
console.log(a);
//定位删除添加
//`array.splice(start, deleteCount, item1, item2, ...)`
let arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // 从索引 2 开始删除 1 个元素,arr 现在是 [1, 2, 4, 5]
arr.splice(2, 0, 'a', 'b'); // 从索引 2 开始添加 'a' 和 'b',arr 现在是 [1, 2, 'a', 'b', 4, 5]

数组元素查找 5

  • indexOf 查找数组中第一个与给定元素相等的元素的索引,如果不存在则返回-1
  • lastIndexOf 查找数组中最后一个与给定元素相等的元素的索引,如果不存在则返回-1
  • find 查找数组中第一个满足提供的测试函数的元素的值,如果没有找到则返回undefined
  • findIndex 查找数组中第一个满足提供的测试函数的元素的索引,如果没有找到则返回-1
  • findLastIndex
1
2
3
4
5
let arr = [1, 2, 3, 4, 2];
let index = arr.indexOf(3); // 2
let index2 = arr.lastIndexOf(2); // 4
let result = arr.find(element => element > 2); // 3
let result2 = arr.findIndex(element => element > 2); // 2

遍历数组 5

  • forEach对数组中的每个元素执行一次给定的函数
  • map创建一个新数组,其元素是调用一次提供的函数后的返回值
  • filter创建一个新数组,包含通过所提供函数实现的测试的所有元素
  • reduce对数组中的所有元素执行一个由左到右的归并操作,将其归并为单个返回值
  • reduceRight对数组中的所有元素执行一个由右到左的归并操作,将其归并为单个返回值
1
2
3
4
5
6
let arr = [1, 2, 3, 4, 5];
arr.forEach(element => console.log(element));// 1 2 3 4 5
let mapArr = arr.map(element => element * element); // [1, 4, 9, 16, 25]
let evenNumbers = arr.filter(element => element % 2 === 0); // [2, 4]
let sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); //15
let sumRight = arr.reduceRight((accumulator, currentValue) => accumulator + currentValue, 0); // 15

排序 2

  • sort 对数组的元素进行原地排序,并返回数组
    • 返回一个小于 0 的值,则 a 会被排序到 b 之前
    • 返回一个大于 0 的值,则 a 会被排序到 b 之后
  • reverse对数组的元素进行原地反转,并返回数组
1
2
3
4
5
6
7
8
9
10
let arr = [3, 1, 4, 1, 5, 9, 2, 6];
arr.sort((a, b) => {
return a - b
});
console.log(arr);//[1, 1, 2, 3, 4, 5, 6, 9]
arr.sort((a, b) => {
return b - a
});
console.log(arr);//[9, 6, 5, 4, 3, 2, 1, 1]
console.log(arr.reverse());//[1, 1, 2, 3, 4, 5, 6, 9]

拷贝合并 2

  • slice() - 创建一个新数组,包含从开始到结束(不包括结束)的数组中的元素
  • concat() - 创建一个新数组,包含原数组和一个或多个其他数组或值的组合
1
2
3
4
5
let arr = [1, 2, 3, 4, 5];
let subArray = arr.slice(1, 3); // subArray 是 [2, 3]
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let newArr = arr1.concat(arr2); // newArr 是 [1, 2, 3, 4, 5, 6]

其他 6

  • join 将数组的所有元素连接成一个字符串,并返回这个字符串
  • includes 检查数组是否包含某个值,如果是则返回 true,否则返回 false
  • every 测试数组中的所有元素是否都通过了提供的函数测试,如果是则返回 true,否则返回 false
  • some 测试数组中的至少一个元素是否通过了提供的函数测试,如果是则返回 true,否则返回 false
  • fill 用一个固定值填充数组的从起始索引到终止索引内的元素
  • copyWithin 浅复制

常规 4

  • toString
  • toLocaleString
  • constructor
  • length

Js里有没有不改变原数组的排序方法

在JavaScript中,如果你想要对数组进行排序而不改变原数组,可以使用以下几种方法:

  1. 使用slice()方法
    slice()方法可以返回一个新的数组对象,这个对象是一个由 beginend(不包括 end)的原数组的浅拷贝。然后你可以对新数组使用 sort() 方法进行排序。
    1
    2
    3
    const arr = [3, 1, 4, 1, 5, 9];
    const sortedArr = arr.slice().sort((a, b) => a - b);
    // sortedArr 现在是 [1, 1, 3, 4, 5, 9],而 arr 保持不变
  2. 使用Array.from()方法
    Array.from()方法创建一个新数组实例,然后你可以对这个新数组进行排序。
    1
    2
    3
    const arr = [3, 1, 4, 1, 5, 9];
    const sortedArr = Array.from(arr).sort((a, b) => a - b);
    // sortedArr 现在是 [1, 1, 3, 4, 5, 9],而 arr 保持不变
  3. 使用扩展运算符(Spread Operator)
    扩展运算符...可以用来复制数组中的元素到一个新数组中,然后对这个新数组进行排序。
    1
    2
    3
    const arr = [3, 1, 4, 1, 5, 9];
    const sortedArr = [...arr].sort((a, b) => a - b);
    // sortedArr 现在是 [1, 1, 3, 4, 5, 9],而 arr 保持不变
  4. 使用map()方法
    如果你需要对数组中的每个元素进行某种操作后再排序,可以使用map()方法创建一个新数组,然后排序。
    1
    2
    3
    const arr = [3, 1, 4, 1, 5, 9];
    const sortedArr = arr.map(a => a * 1).sort((a, b) => a - b);
    // sortedArr 现在是 [1, 1, 3, 4, 5, 9],而 arr 保持不变
    以上方法都可以在不改变原数组的情况下对数组进行排序。选择哪一种方法取决于你的具体需求和代码风格。

DS

JavaScript 的 Array(数组)是一种用于存储有序数据集合的对象,支持多种数据类型元素,并提供丰富的内置方法用于操作数据。以下是 JavaScript 数组的详细介绍:

1. 数组的创建

1.1 字面量形式

1
2
const arr1 = [];          // 空数组
const arr2 = [1, "a", {}]; // 包含不同数据类型的元素

1.2 构造函数

1
2
3
const arr3 = new Array();         // 空数组
const arr4 = new Array(5); // 长度为5的空数组(稀疏数组)
const arr5 = new Array(1, 2, 3); // 等同于 [1, 2, 3]

2. 基本特性

  • 动态长度:数组的 length 属性会自动调整。
    1
    2
    3
    const arr = [1, 2];
    arr.length = 5; // 变为 [1, 2, empty × 3]
    arr.length = 1; // 截断为 [1]
  • 稀疏数组:数组可以有“空位”(empty),如 [1, , 3]。某些方法会跳过空位。
  • 类型灵活:可存储任意数据类型(包括其他数组或对象)。

3. 常用方法

3.1 增删元素

方法 描述 是否修改原数组
push() 末尾添加元素,返回新长度 ✔️
pop() 移除并返回最后一个元素 ✔️
unshift() 开头添加元素,返回新长度 ✔️
shift() 移除并返回第一个元素 ✔️
splice() 添加/删除任意位置的元素 ✔️
示例
1
2
3
const arr = [1, 2];
arr.push(3); // [1, 2, 3]
arr.splice(1, 1); // 删除索引1的元素 → [1, 3]

3.2 遍历与转换

方法 描述 返回值
forEach() 遍历数组,无返回值 undefined
map() 对每个元素执行函数,返回新数组 新数组
filter() 返回符合条件元素的新数组 新数组
reduce() 累积计算结果(如求和) 单个值
join() 将数组转为字符串(默认逗号分隔) 字符串
示例
1
2
3
[1, 2, 3].map(x => x * 2);          // [2, 4, 6]
[1, 2, 3].filter(x => x > 1); // [2, 3]
[1, 2, 3].reduce((sum, x) => sum + x, 0); // 6

3.3 查找与判断

方法 描述
indexOf() 返回元素首次出现的索引(无则返回-1)
includes() 判断是否包含某元素(支持 NaN
find() 返回第一个符合条件的元素(无则返回undefined
some()/every() 检查是否有/所有元素满足条件
示例
1
2
[1, 2, NaN].includes(NaN);    // true
[1, 2, 3].find(x => x > 1); // 2

3.4 排序与反转

方法 描述 是否修改原数组
sort() 排序(默认按字符串Unicode) ✔️
reverse() 反转数组顺序 ✔️
示例
1
[3, 1, 2].sort((a, b) => a - b);  // [1, 2, 3]

4. 高级操作

4.1 展开运算符 (...)

合并数组或展开元素:

1
2
const arr1 = [1, 2];
const arr2 = [...arr1, 3]; // [1, 2, 3]

4.2 扁平化数组

  • flat():扁平化嵌套数组。
  • flatMap():结合 map()flat()
1
2
[1, [2, 3]].flat();         // [1, 2, 3]
[1, 2].flatMap(x => [x, x*2]); // [1, 2, 2, 4]

5. 静态方法

方法 描述
Array.isArray() 判断是否为数组(优于 typeof
Array.from() 将类数组(如 arguments)转为数组
Array.of() 创建包含任意参数的新数组
示例
1
2
3
Array.isArray([]);          // true
Array.from("hello"); // ['h', 'e', 'l', 'l', 'o']
Array.of(5); // [5](与 new Array(5) 不同)

6. 性能与注意点

  • 避免密集操作shift()/unshift() 时间复杂度为 O(n),大数据量时慎用。
  • 稀疏数组:空位可能导致 map() 等方法跳过某些索引。
  • 浅拷贝:方法如 slice() 返回浅拷贝,嵌套对象会共享引用。

总结

JavaScript 数组是灵活且功能强大的数据结构,适合处理有序集合。通过结合高阶函数(如 mapfilter)和 ES6+ 特性(展开运算符、flatMap),可以高效处理复杂数据操作。

数组去重

在 JavaScript 中,有多种方法可以对数组进行去重,以下是几种常见的方法:

1. 使用 Set 对象

Set 对象是一个集合,它只允许存储唯一的值。利用这一特性可以很方便地实现数组去重。

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

这种方法简洁高效,是目前最常用的数组去重方式之一,但它会改变原数组的顺序,且不适用于对象数组等复杂数据类型的去重。

2. 使用 filter 方法结合 indexOf

通过 filter 方法遍历数组,利用 indexOf 方法判断当前元素在数组中的第一次出现的位置是否与当前索引相同,如果相同则保留,否则说明是重复元素,不保留。

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

这种方法也不会改变原数组的顺序,但效率相对较低,因为 indexOf 方法会在每次遍历时都对数组进行一次查找。

3. 使用 filter 方法结合 includes

和上面的方法类似,也是通过 filter 方法遍历数组,不过这次是利用 includes 方法判断当前元素是否在数组的前部分(即当前索引之前的部分)出现过,如果出现过则不保留,否则保留。

1
2
3
const arr = [1, 2, 2, 3, 4, 4, 5];
const uniqueArr = arr.filter((item, index) => !arr.slice(0, index).includes(item));
console.log(uniqueArr); // [1, 2, 3, 4, 5]

这种方法同样不会改变原数组的顺序,但效率也不高,因为每次调用 includes 方法都会对数组的前部分进行遍历。

4. 使用对象或 Map 来记录

创建一个空对象或 Map,遍历数组,将数组中的每个元素作为键(或 Map 的键),值可以是任意值,由于对象或 Map 的键是唯一的,这样就可以实现去重。最后再将对象或 Map 的键转换为数组即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用对象
const arr = [1, 2, 2, 3, 4, 4, 5];
const obj = {};
arr.forEach(item => {
obj[item] = true;
});
const uniqueArr = Object.keys(obj).map(item => Number(item)); // 如果数组是数字类型,需要转换回数字
console.log(uniqueArr); // [1, 2, 3, 4, 5]
// 使用 Map
const arr = [1, 2, 2, 3, 4, 4, 5];
const map = new Map();
arr.forEach(item => {
map.set(item, true);
});
const uniqueArr = Array.from(map.keys());
console.log(uniqueArr); // [1, 2, 3, 4, 5]

这种方法效率较高,且可以适用于对象数组等复杂数据类型的去重,但需要额外的转换步骤来获取去重后的数组。

5. 双层循环比较

使用两层循环,外层循环遍历数组的每个元素,内层循环从当前元素的下一个位置开始遍历,比较两个元素是否相等,如果相等则将内层循环中的元素从数组中删除。

1
2
3
4
5
6
7
8
9
10
const arr = [1, 2, 2, 3, 4, 4, 5];
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--; // 因为删除了一个元素,所以需要将 j 减 1
}
}
}
console.log(arr); // [1, 2, 3, 4, 5]

这种方法可以原地修改数组,但效率较低,时间复杂度为 O(n^2),且在删除元素时需要注意索引的变化。

6. 使用 reduce 方法

利用 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); // [1, 2, 3, 4, 5]

这种方法不会改变原数组的顺序,但效率也不高,因为每次调用 includes 方法都会对新数组进行遍历。

Tips

  • forEach(), filter(), reduce(), every() 和some()都会跳过空位。
  • map()会跳过空位,但会保留这个值
  • js数组和对象相互转化

map 和 filter 的区别

JavaScript 中的 Array.prototype.mapArray.prototype.filter 都是数组的高阶函数,它们都用于遍历数组并对数组中的每个元素执行某种操作,但它们的目的和返回结果有所不同。

map 方法

  • 定义map 方法创建一个新数组,其元素是调用一次提供的函数后的返回值。
  • 语法array.map(function(currentValue, index, arr), thisArg)
    • currentValue:当前正在处理的元素。
    • index(可选):当前正在处理的元素的索引。
    • arr(可选):调用 map 方法的数组。
    • thisArg(可选):执行回调时使用的 this 值。
  • 特点
    • map 方法会遍历数组中的每个元素,并对每个元素执行提供的回调函数。
    • 回调函数的返回值会组成一个新数组,新数组的长度与原数组相同。
    • map 不会改变原数组,它返回的是一个新数组。
    • 如果回调函数中包含异步操作,map 返回的新数组中将包含异步操作的结果,这可能是一个 Promise 数组。
  • 示例
    1
    2
    3
    const numbers = [1, 2, 3, 4];
    const squares = numbers.map(num => num * num);
    console.log(squares); // [1, 4, 9, 16]

filter 方法

  • 定义filter 方法创建一个新数组,包含通过所提供函数实现的测试的所有元素。
  • 语法array.filter(function(currentValue, index, arr), thisArg)
    • 参数与 map 方法相同。
  • 特点
    • filter 方法会遍历数组中的每个元素,并对每个元素执行提供的回调函数。
    • 回调函数应该返回一个布尔值,如果返回 true,则当前元素会被包含在新数组中;如果返回 false,则当前元素不会被包含在新数组中。
    • filter 不会改变原数组,它返回的是一个新数组,新数组的长度可能小于等于原数组的长度。
    • filter 方法可以用于数组的筛选操作,比如筛选出符合条件的元素。
  • 示例
    1
    2
    3
    const numbers = [1, 2, 3, 4, 5];
    const evenNumbers = numbers.filter(num => num % 2 === 0);
    console.log(evenNumbers); // [2, 4]

区别

  • 目的不同
    • map 的目的是对数组中的每个元素进行某种操作,然后返回一个新数组,新数组的元素是操作后的结果。
    • filter 的目的是筛选出数组中符合条件的元素,然后返回一个新数组,新数组的元素是符合条件的原数组中的元素。
  • 返回值不同
    • map 返回的新数组的长度与原数组相同,因为每个元素都会被处理并返回一个结果。
    • filter 返回的新数组的长度可能小于等于原数组的长度,因为只有符合条件的元素才会被包含在新数组中。
  • 回调函数的返回值不同
    • map 的回调函数的返回值会组成新数组的元素。
    • filter 的回调函数的返回值是一个布尔值,用于决定当前元素是否包含在新数组中。

使用场景

  • map
    • 当需要对数组中的每个元素进行某种转换操作时,比如将数组中的每个数字乘以 2、将字符串数组转换为大写等。
    • 当需要基于原数组生成一个新数组,且新数组的元素与原数组的元素一一对应时。
  • filter
    • 当需要从数组中筛选出符合条件的元素时,比如筛选出数组中的偶数、筛选出数组中大于某个值的元素等。
    • 当需要对数组进行条件过滤,生成一个新数组,且新数组的元素是原数组中满足特定条件的元素时。

所有方法

JavaScript 数组提供了许多内置方法,用于操作和处理数组。以下是一些常用的数组方法,按功能分类介绍:

1. 添加和删除元素

push() - 向数组末尾添加一个或多个元素,并返回新长度

1
2
let arr = [1, 2, 3];
arr.push(4); // arr 现在是 [1, 2, 3, 4]

pop() - 删除数组末尾的元素,并返回被删除的元素

1
2
let arr = [1, 2, 3];
let lastElement = arr.pop(); // lastElement 是 3,arr 现在是 [1, 2]

shift() - 删除数组开头的元素,并返回被删除的元素

1
2
let arr = [1, 2, 3];
let firstElement = arr.shift(); // firstElement 是 1,arr 现在是 [2, 3]

unshift() - 向数组开头添加一个或多个元素,并返回新长度

1
2
let arr = [1, 2, 3];
arr.unshift(0); // arr 现在是 [0, 1, 2, 3]

splice() - 从数组中添加或删除元素

  • 删除元素:array.splice(start, deleteCount)
  • 添加元素:array.splice(start, deleteCount, item1, item2, ...)
1
2
3
let arr = [1, 2, 3, 4, 5];
arr.splice(2, 1); // 从索引 2 开始删除 1 个元素,arr 现在是 [1, 2, 4, 5]
arr.splice(2, 0, 'a', 'b'); // 从索引 2 开始添加 'a' 和 'b',arr 现在是 [1, 2, 'a', 'b', 4, 5]

2. 查找元素

indexOf() - 返回数组中第一个匹配元素的索引,如果没有找到则返回 -1

1
2
let arr = [1, 2, 3, 4, 5];
let index = arr.indexOf(3); // index 是 2

lastIndexOf() - 返回数组中最后一个匹配元素的索引,如果没有找到则返回 -1

1
2
let arr = [1, 2, 3, 4, 3, 5];
let lastIndex = arr.lastIndexOf(3); // lastIndex 是 4

find() - 返回数组中第一个匹配条件的元素,如果没有找到则返回 undefined

1
2
let arr = [1, 2, 3, 4, 5];
let found = arr.find(element => element > 3); // found 是 4

findIndex() - 返回数组中第一个匹配条件的元素的索引,如果没有找到则返回 -1

1
2
let arr = [1, 2, 3, 4, 5];
let foundIndex = arr.findIndex(element => element > 3); // foundIndex 是 3

3. 遍历数组

forEach() - 对数组中的每个元素执行一次给定的函数

1
2
3
let arr = [1, 2, 3, 4, 5];
arr.forEach(element => console.log(element));
// 输出:1 2 3 4 5

map() - 创建一个新数组,其元素是调用一次提供的函数后的返回值

1
2
let arr = [1, 2, 3, 4, 5];
let squares = arr.map(element => element * element); // squares 是 [1, 4, 9, 16, 25]

filter() - 创建一个新数组,包含通过所提供函数实现的测试的所有元素

1
2
let arr = [1, 2, 3, 4, 5];
let evenNumbers = arr.filter(element => element % 2 === 0); // evenNumbers 是 [2, 4]

reduce() - 对数组中的所有元素执行一个由左到右的归并操作,将其归并为单个返回值

1
2
let arr = [1, 2, 3, 4, 5];
let sum = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); // sum 是 15

reduceRight() - 对数组中的所有元素执行一个由右到左的归并操作,将其归并为单个返回值

1
2
let arr = [1, 2, 3, 4, 5];
let sum = arr.reduceRight((accumulator, currentValue) => accumulator + currentValue, 0); // sum 是 15

4. 排序和反转

sort() - 对数组的元素进行原地排序,并返回数组

1
2
let arr = [3, 1, 4, 1, 5, 9, 2, 6];
arr.sort(); // arr 现在是 [1, 1, 2, 3, 4, 5, 6, 9]

reverse() - 对数组的元素进行原地反转,并返回数组

1
2
let arr = [1, 2, 3, 4, 5];
arr.reverse(); // arr 现在是 [5, 4, 3, 2, 1]

5. 拷贝和合并

slice() - 创建一个新数组,包含从开始到结束(不包括结束)的数组中的元素

1
2
let arr = [1, 2, 3, 4, 5];
let subArray = arr.slice(1, 3); // subArray 是 [2, 3]

concat() - 创建一个新数组,包含原数组和一个或多个其他数组或值的组合

1
2
3
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let newArr = arr1.concat(arr2); // newArr 是 [1, 2, 3, 4, 5, 6]

6. 其他方法

join() - 将数组的所有元素连接成一个字符串,并返回这个字符串

1
2
let arr = [1, 2, 3, 4, 5];
let str = arr.join(', '); // str 是 "1, 2, 3, 4, 5"

includes() - 检查数组是否包含某个值,如果是则返回 true,否则返回 false

1
2
let arr = [1, 2, 3, 4, 5];
let contains = arr.includes(3); // contains 是 true

every() - 测试数组中的所有元素是否都通过了提供的函数测试,如果是则返回 true,否则返回 false

1
2
let arr = [1, 2, 3, 4, 5];
let allPositive = arr.every(element => element > 0); // allPositive 是 true

some() - 测试数组中的至少一个元素是否通过了提供的函数测试,如果是则返回 true,否则返回 false

1
2
let arr = [1, 2, 3, 4, 5];
let hasPositive = arr.some(element => element > 0); // hasPositive 是 true

fill() - 用一个固定值填充数组的从起始索引到终止索引内的元素

1
2
let arr = [1, 2, 3, 4, 5];
arr.fill(0, 2, 4); // arr 现在是 [1, 2, 0, 0, 5]

copyWithin() - 浅复制数组的一部分到同一数组中的另一个位置,并返回它,不修改其大小

1
2
let arr = [1, 2, 3, 4, 5];
arr.copyWithin(0, 3); // arr 现在是 [4, 5, 3, 4, 5]

总结

JavaScript 数组提供了丰富的方法来操作和处理数组,这些方法可以满足大多数常见的需求。通过合理使用这些方法,可以更高效地编写和管理数组相关的代码。

伪数组

伪数组(类数组对象)是指具有某些数组特征但又不是真正的数组对象的数据结构。以下是关于伪数组的详细介绍:

一、伪数组的特点

  1. 具有length属性
    • 伪数组通常具有一个length属性,表示其包含的元素数量。例如,arguments对象是一个常见的伪数组,它在函数中用于访问函数的参数。每个arguments对象都有一个length属性,表示传入函数的参数个数。
      1
      2
      3
      4
      5
      6
      7
      function myFunction() {
      console.log(arguments.length); // 输出传入的参数个数
      for (let i = 0; i < arguments.length; i++) {
      console.log(arguments[i]); // 访问每个参数
      }
      }
      myFunction(1, 'a', true); // 输出 3,然后依次输出 1、'a'、true
  2. 元素可通过索引访问
    • 伪数组的元素可以通过索引(从0开始)进行访问,就像数组一样。例如,arguments[0]可以访问第一个参数,arguments[1]可以访问第二个参数等。
  3. 不是真正的数组实例
    • 伪数组不是真正的数组对象,它不能直接使用数组的原型方法,如pushpopmapforEach等。例如,arguments.push()会报错,因为arguments对象没有push方法。
      1
      2
      3
      function myFunction() {
      // arguments.push(4); // TypeError: arguments.push is not a function
      }

二、常见的伪数组

  1. arguments对象
    • arguments对象是函数的内置对象,用于访问函数的参数。它是一个伪数组,具有length属性,可以通过索引访问参数。
  2. DOM集合
    • 许多DOM操作返回的结果也是伪数组。例如,document.getElementsByTagNamedocument.getElementsByClassNamedocument.querySelectorAll等方法返回的都是伪数组对象。
      1
      2
      3
      4
      5
      let divs = document.getElementsByTagName('div');
      console.log(divs.length); // 输出页面中div元素的数量
      for (let i = 0; i < divs.length; i++) {
      console.log(divs[i]); // 访问每个div元素
      }
      这些DOM集合对象也具有length属性,并且可以通过索引访问每个元素,但它们不是真正的数组实例。

三、如何将伪数组转换为真正的数组

  1. 使用Array.prototype.slice方法
    • 可以使用Array.prototype.slice方法将伪数组转换为真正的数组。slice方法可以接受一个伪数组作为this上下文,并返回一个真正的数组。
      1
      2
      3
      4
      5
      6
      7
      function myFunction() {
      let args = Array.prototype.slice.call(arguments);
      console.log(args); // 输出一个真正的数组
      args.push(4); // 可以使用数组方法
      console.log(args); // 输出 [1, 'a', true, 4]
      }
      myFunction(1, 'a', true);
  2. 使用扩展运算符(ES6)
    • 在ES6中,可以使用扩展运算符(...)更简洁地将伪数组转换为真正的数组。
      1
      2
      3
      4
      5
      6
      7
      function myFunction() {
      let args = [...arguments];
      console.log(args); // 输出一个真正的数组
      args.pop(); // 可以使用数组方法
      console.log(args); // 输出 [1, 'a']
      }
      myFunction(1, 'a', true);
  3. 使用Array.from方法(ES6)
    • Array.from方法可以将伪数组转换为真正的数组。它接受一个类数组对象或可迭代对象,并返回一个真正的数组。
      1
      2
      3
      4
      5
      6
      function myFunction() {
      let args = Array.from(arguments);
      console.log(args); // 输出一个真正的数组
      args.forEach(item => console.log(item)); // 可以使用数组方法
      }
      myFunction(1, 'a', true);

通过以上方法,可以将伪数组转换为真正的数组,从而能够使用数组的原型方法进行操作。

Js数组和对象相互转化

在JavaScript中,数组和对象之间的相互转换是常见的操作。以下是一些基本的方法来实现这种转换:

数组转对象

  1. 使用reduce方法
    将数组转换为对象,其中数组的每个元素应该是一个包含键和值的子数组。
    1
    2
    3
    4
    5
    6
    7
    8
    let array = ['key1', 'value1', 'key2', 'value2'];
    let obj = array.reduce((acc, curr, index, arr) => {
    if (index % 2 === 0) {
    acc[arr[index]] = arr[index + 1];
    }
    return acc;
    }, {});
    console.log(obj); // { key1: 'value1', key2: 'value2' }
  2. 使用Object.assignObject.fromEntries方法(需要迭代器):
    如果你的数组是键值对的形式,可以使用Object.fromEntries方法。
    1
    2
    3
    let array = [['key1', 'value1'], ['key2', 'value2']];
    let obj = Object.fromEntries(array);
    console.log(obj); // { key1: 'value1', key2: 'value2' }

对象转数组

  1. 使用Object.keysObject.valuesObject.entries方法
    将对象的键、值或键值对转换为数组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let obj = { key1: 'value1', key2: 'value2' };
    // 转换为键数组
    let keys = Object.keys(obj);
    console.log(keys); // ['key1', 'key2']
    // 转换为值数组
    let values = Object.values(obj);
    console.log(values); // ['value1', 'value2']
    // 转换为键值对数组
    let entries = Object.entries(obj);
    console.log(entries); // [['key1', 'value1'], ['key2', 'value2']]
  2. 使用for...in循环
    通过遍历对象的属性来创建数组。
    1
    2
    3
    4
    5
    6
    7
    8
    let obj = { key1: 'value1', key2: 'value2' };
    let array = [];
    for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
    array.push([key, obj[key]]);
    }
    }
    console.log(array); // [['key1', 'value1'], ['key2', 'value2']]
  3. 使用Object.assign方法
    将对象转换为数组,首先将对象转换为JSON字符串,然后再将字符串转换为数组。
    1
    2
    3
    let obj = { key1: 'value1', key2: 'value2' };
    let array = JSON.parse(JSON.stringify(obj));
    console.log(array); // {"key1":"value1","key2":"value2"},注意这是一个对象,不是数组
    请注意,最后一个方法实际上并没有将对象转换为数组,而是转换成了一个JSON字符串,然后再将这个字符串解析为一个对象。如果你需要一个真正的数组,你可能需要使用其他方法,比如Object.valuesObject.entries