JavaScript 基础
1、基本数据类型:
1.1 基本数据类型:
Number(数值):表示数字,包括整数和浮点数。例如:5、3.14。
String(字符串):表示文本数据,由字符组成。用引号(单引号或双引号)括起来。例如:“Hello”、‘World’。
Boolean(布尔):表示逻辑值,只有两个可能的值:true(真)和false(假)。
Undefined(未定义):表示变量声明但未初始化的值。当访问未初始化的变量时,其值为undefined。
Null(空):表示一个空值或者空对象引用。
Symbol(符号):在ES6中引入的新数据类型,表示唯一且不可变的值。
除了以上的基本数据类型,JavaScript还有一个复杂数据类型:
Object(对象):表示一个键值对的集合,用于存储多个值。可以是原始类型的值或其他对象的引用。
这些基本数据类型在 JavaScript 中都是不可变的(immutable),即它们的值在创建后不能被改变。
1.2、基本类型和引用类型
在JavaScript中,数据类型可以分为基本类型和引用类型。
基本类型(Primitive Types)是指存储简单数据值的类型,它们是不可改变的(immutable)。JavaScript有以下基本类型:
Number(数值):表示数字,包括整数和浮点数。
String(字符串):表示文本数据。
Boolean(布尔):表示逻辑值,true或false。
Undefined(未定义):表示变量声明但未初始化的值。
Null(空):表示一个空值或者空对象引用。
引用类型(Reference Types)是指存储对象的类型,它们是可变的(mutable),并且可以包含多个值。JavaScript中的引用类型包括:
Object(对象):表示键值对的集合,可以存储多个值。
Array(数组):是对象的特殊类型,用于存储有序的数据集合。
Function(函数):是对象的特殊类型,可以执行一些操作。
基本类型的值直接存储在变量中,而引用类型的值则是存储在堆内存中,变量中存储的是对堆内存中的引用。因此,当复制基本类型的值时,会创建一个新的值。而复制引用类型的值时,实际上只是复制了对同一个对象的引用。
总结起来,基本类型在赋值时会复制值本身,而引用类型在赋值时会复制对对象的引用。
1.3.判断一个变量的数据类型
**instanceof:**可以用来检查一个对象是否属于某个特定类或其子类。它是通过检查对象的原型链来确定的,如果对象的原型链中存在指定类或其子类,则返回true,否则返回false。它可以判断引用类型的具体类型,但不能判断基本类型。
**Object.prototype.toString.call():**可以用来获取一个对象的内部属性[[Class]],从而判断其类型。这种方法可以判断基本类型和引用类型,返回的结果是一个字符串,格式为"[object 类型]",其中类型表示对象的具体类型。可以判断基本类型和引用类型的具体类型,但需要通过调用方法来获取类型信息。
typeof():主要用于判断基本类型,如字符串、数字、布尔值等。对于引用类型,除了函数以外,typeof()只能返回"object",无法具体判断其类型
var value = 42;
var type = Object.prototype.toString.call(value);
console.log(type); // 输出 "[object Number]"
2、 事件冒泡和事件捕获
事件冒泡(Event Bubbling)和事件捕获(Event Capturing)是指在处理嵌套元素上的事件时,事件在DOM树中传播的两种不同方式。
事件冒泡是指当一个元素触发了某个事件时,该事件将从最内层的元素开始向父级元素逐级传播,直到达到最外层的祖先元素。这意味着如果你在一个嵌套的元素上注册了事件处理程序,事件将首先触发嵌套元素的处理函数,然后再触发外层元素的处理函数。事件冒泡是默认的事件传播方式。
事件捕获则是相反的过程,事件从最外层的元素开始传播,逐级向内层元素传播,直到触发实际触发事件的元素。对于一个嵌套结构的元素,事件捕获的过程会先触发外层元素的处理函数,然后再触发内层元素的处理函数。
事件冒泡的用途:
方便的事件委托:通过在父元素上监听事件,利用事件冒泡的特性来处理子元素的事件。这样可以减少事件处理程序的数量,提高性能。
动态添加事件:对于动态创建的元素,可以将事件处理程序绑定在它们的父元素上,利用事件冒泡来处理事件。
简化代码结构:通过事件冒泡,可以在父元素上统一处理多个子元素的相似事件,避免重复代码。
事件捕获的用途:
预处理事件:可以在事件传播到目标元素之前拦截和处理事件。适用于需要在事件到达目标元素之前进行一些初始化或预处理操作的场景。
更精细的事件控制:如果需要在事件传播的早期阶段就停止事件传播或阻止默认行为,可以使用事件捕获阶段来实现。
深层次的事件处理:如果需要在嵌套较深的元素结构中进行事件处理,可以利用事件捕获来更精确地控制事件的传播路径。
综合来说,事件冒泡适合用于简化事件处理、提高性能和实现事件委托等场景,而事件捕获则适合在事件传播的早期阶段进行一些特殊处理或控制事件传播路径。
阻止事件冒泡或默认行为
可以使用event.stopPropagation()方法来阻止事件冒泡,使用event.preventDefault()方法来阻止默认行为。
3. 什么是原型链?如何利用原型链实现继承?
原型链是 JavaScript 中用于实现对象之间继承关系的机制。每个对象都有一个指向另一个对象的引用,这个被引用的对象就是该对象的原型(prototype)。而被引用对象的原型又可以有自己的原型,于是就形成了一个链式结构,即原型链。
在 JavaScript 中,每个对象(除了 null)都有一个原型,并且继承了原型对象的属性和方法。当我们试图访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到相应的属性或方法或者到达原型链的顶端(Object.prototype),如果还是没有找到,则返回 undefined。
利用原型链实现继承的过程可以通过以下步骤来实现:
创建父类(超类)的构造函数,并定义其属性和方法。
创建子类的构造函数,同时将父类的实例作为子类的原型。这样子类就能够继承父类的属性和方法。
可以在子类的构造函数中添加子类特有的属性和方法,也可以重写父类的方法来实现多态。
下面是一个简单的示例来说明如何利用原型链实现继承:
// 定义父类构造函数
function Animal(name) {
this.name = name;
}
// 定义父类的方法
Animal.prototype.sayName = function() {
console.log('My name is ' + this.name);
};
// 定义子类构造函数
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数,继承父类的属性
this.breed = breed;
}
// 将父类的实例作为子类的原型
Dog.prototype = Object.create(Animal.prototype);
// 添加子类的方法
Dog.prototype.bark = function() {
console.log('Woof!');
};
// 创建子类实例
var myDog = new Dog('Buddy', 'Golden Retriever');
myDog.sayName(); // 输出 "My name is Buddy"
myDog.bark(); // 输出 "Woof!"
我们通过原型链的方式实现了子类 Dog 对父类 Animal 的继承。子类 Dog 继承了父类 Animal 的属性和方法,并且还添加了自己的方法。这样就实现了对象之间的继承关系。
图文解析:
-
显示原型
显示原型就是利用prototype属性查找原型,只是这个是函数类型数据的属性。 -
隐式原型
隐式原型是利用__proto__属性查找原型,这个属性指向当前对象的构造函数的原型对象,这个属性是对象类型数据的属性,所以可以在实例对象上面使用:
原型链:
如果某个对象查找属性,自己和原型对象上都没有,那就会继续往原型对象的原型对象上去找,这个例子里就是Object.prototype,这里就是查找的终点站了,在这里找不到,就没有更上一层了(null里面啥也没有),直接返回undefined。
可以看出,整个查找过程都是顺着__proto__属性,一步一步往上查找,形成了像链条一样的结构,这个结构,就是原型链。所以,原型链也叫作隐式原型链。
正是因为这个原因,我们在创建对象、数组、函数等等数据的时候,都自带一些属性和方法,这些属性和方法是在它们的原型上面保存着,所以它们自创建起就可以直接使用那些属性和方法。
原型和原型链详细深度讲解,好文章
4、遍历对象中的属性
在 JavaScript 中,可以使用不同的方法来遍历一个对象的属性。以下是几种常见的遍历对象属性的方法:
for…in循环:使用for…in循环可以遍历对象的所有可枚举属性,并访问每个属性。
const obj1 = {
name: "qq",
age: "12",
school: "fudan",
};
// for in循环
for (let key in obj1) {
if (obj1.hasOwnProperty(key)) {
// 为了避免遍历到原型链上的属性,通常会在循环体内加上 hasOwnProperty 方法的判断。
console.log(key + ":" + obj1[key]); // name: 11, age: 12, school: fudan
}
}
Object.getOwnPropertyNames():使用Object.getOwnPropertyNames()方法可以获取对象的所有属性名,并返回一个包含属性名的数组。通过遍历这个数组,可以访问对象的属性。
// Object.getOwnPropertyNames() 方法:
Object.getOwnPropertyNames(obj1).forEach((key) => {
console.log(key + ":" + obj1[key]); // name: 11, age: 12, school: fudan
});
console.log( " Object.getOwnPropertyNames(obj1)", Object.getOwnPropertyNames(obj1)); // ['name', 'age', 'school']
Object.keys() 方法:
Object.keys() 方法会返回一个包含对象自身的所有可枚举属性的数组,然后可以使用 forEach 方法遍历这个数组。
// object.kyes()方法
Object.keys(obj1).forEach((key) => {
console.log(key + ":" + obj1[key]); // name: 11, age: 12, school: fudan
});
console.log("Object.keys(obj1)", Object.keys(obj1)); // ['name', 'age', 'school']
Object.entries() ES8+
方法会返回一个包含对象自身的所有可枚举属性键值对的数组,然后可以使用 forEach 方法遍历这个数组。
// Object.entries() 方法(ES8+):
Object.entries(obj1).forEach(([key, value]) => {
console.log(key + ": " + value); // Object.entries() 方法会返回一个包含对象自身的所有可枚举属性键值对的数组,然后可以使用 forEach 方法遍历这个数组。
});
console.log(' Object.entries(obj1)', Object.entries(obj1)) // [["name", "qq"],["age", "12"],["school", "fudan"]]
5、判断一个变量是否为数组
在 JavaScript 中,有几种方法可以判断一个变量是否为数组。以下是常用的几种方法:
Array.isArray() 方法:
使用 Array.isArray() 方法是最可靠和推荐的方法来判断一个变量是否为数组。
const arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
Array.isArray() 方法会返回 true 如果传入的参数是一个数组,否则返回 false。
instanceof 操作符:
使用 instanceof 操作符也可以判断一个变量是否为数组,但不如 Array.isArray() 方法准确。
const arr = [1, 2, 3];
console.log(arr instanceof Array); // true
instanceof 操作符检查原型链,如果变量是指定类型的实例,则返回 true,否则返回 false。
Object.prototype.toString.call() 方法:
const arr = [1, 2, 3];
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
这种方法通过调用 Object.prototype.toString 方法并传入要检查的变量,然后比较返回的字符串是否为 [object Array] 来判断变量是否为数组。
以上是几种常用的方法来判断一个变量是否为数组。在实际使用中,建议优先使用 Array.isArray() 方法进行判断,因为它更直观和可靠
6、常用数组方法有哪些?
push():向数组末尾添加一个或多个元素,并返回新的长度。
pop():删除并返回数组的最后一个元素。
shift():删除并返回数组的第一个元素,同时将所有其他元素下标减 1。
unshift():向数组的开头添加一个或多个元素,并返回新的长度。
concat():用于合并两个或多个数组,不会修改现有数组,而是返回一个新数组。
slice():从已有的数组中返回选定的元素。
splice():向/从数组中添加/删除项目,然后返回被删除的项目。
indexOf():返回数组中某个元素第一次出现的索引,如果不存在则返回 -1。
lastIndexOf():返回数组中某个元素最后一次出现的索引,如果不存在则返回 -1。
forEach():为数组中的每个元素执行提供的函数。
map():创建一个新数组,其结果是该数组中的每个元素调用一个提供的函数后返回的值。
filter():创建一个新数组,包含通过所提供函数实现的测试的所有元素。
reduce():对数组中的每个元素执行一个提供的函数,将其结果汇总为单个返回值。
find():返回数组中满足提供的测试函数的第一个元素的值。
findIndex():返回数组中满足提供的测试函数的第一个元素的索引。
some():返回数组中是否至少有一个元素通过了所提供函数的测试。
every():返回数组中的所有元素是否都通过了所提供函数的测试。
7、 遍历数组的方法
**for 循环:**使用传统的 for 循环可以遍历数组,通过索引访问数组元素。
const arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
forEach() 方法:forEach() 方法是数组提供的一种遍历方法,它接受一个回调函数作为参数,在数组的每个元素上执行该回调函数。
const arr = [1, 2, 3];
arr.forEach((element) => {
console.log(element);
});
for…of 循环:for…of 循环是 ES6 引入的新语法,它可以用来遍历可迭代对象,包括数组。
const arr = [1, 2, 3];
for (const element of arr) {
console.log(element);
}
map() 方法:map() 方法可以遍历数组,并将每个元素传递给回调函数进行处理,返回一个新的数组。
const arr = [1, 2, 3];
const newArr = arr.map((element) => {
return element * 2;
});
console.log(newArr);
filter() 方法:filter() 方法可以遍历数组,并根据回调函数的返回值筛选出满足条件的元素,返回一个新的数组。
const arr = [1, 2, 3, 4, 5];
const filteredArr = arr.filter((element) => {
return element % 2 === 0;
});
console.log(filteredArr);
8、ajax请求的几种方法?
XMLHttpRequest对象:XMLHttpRequest是原生的JavaScript对象,它提供了一种在后台与服务器进行数据交换的方法。通过创建一个XMLHttpRequest对象,可以发送异步请求并接收服务器返回的数据。
Fetch API:Fetch API是一种现代的Web API,它提供了一种更简洁和强大的方式来进行网络请求。它使用Promise对象来处理异步操作,并提供了更灵活的请求和响应处理方式。
jQuery.ajax()方法:如果你使用jQuery库,可以使用其提供的ajax()方法来发送异步请求。这个方法封装了XMLHttpRequest对象,并提供了简化的语法和更高级的功能,如自动处理数据格式、跨域请求等。
Axios库:Axios是一个流行的第三方库,用于发送HTTP请求。它提供了简单易用的API,并支持Promise对象和拦截器等功能。Axios可以在浏览器和Node.js环境中使用。
axios请求
Axios 是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node.js 中使用。
1)首先,需要在项目中安装axios。可以使用npm或者yarn进行安装。
npm install axios
2)在需要发送请求的文件中,引入axios模块。
const axios = require('axios');
// 或者在浏览器中使用 script 标签引入 Axios,无需导入
3)使用axios的方法发送请求,常见的方法有get、post、put、delete等。
// get 请求
axios.get(url)
.then((response) => {
// 请求成功,处理响应数据
console.log(response.data);
})
.catch((error) => {
// 请求失败,处理错误信息
console.error(error);
});
可以通过配置选项来设置请求的URL、请求头、请求体等信息。
使用then方法处理请求成功的响应,使用catch方法处理请求失败的情况。
9、post请求与get请求的区别?
1) 参数传递方式:
GET请求:GET请求的参数会暴露在URL中,可以被缓存、浏览器历史记录等获取到。
POST请求:参数通过请求体传递,不会暴露在URL中,参数通过请求体以键值对的形式传递。
2)参数长度限制:
GET请求:由于参数附加在URL中,URL长度有限制,不同浏览器和服务器对URL长度的限制不同,一般为几千个字符。
POST请求:由于参数通过请求体传递,没有URL长度限制,但是服务器和应用程序可能会对请求体的大小进行限制。
3)安全性:
GET请求:参数暴露在URL中,容易被拦截、修改或泄露,不适合传递敏感信息。
POST请求:参数不暴露在URL中,相对安全,适合传递敏感信息。
4)缓存:
GET请求:由于参数暴露在URL中,可以被浏览器缓存,下次相同的请求可以直接使用缓存结果,提高性能。
POST请求:由于参数不暴露在URL中,不会被浏览器缓存。
5)幂等性:
GET请求:对于相同的请求,多次GET请求不会对服务器产生副作用,即不会改变服务器状态。
POST请求:对于相同的请求,多次POST请求可能会对服务器产生副作用,即可能改变服务器状态。
关键区别总结:
GET 请求用于获取资源,参数附加在 URL 中,可以被缓存,参数较少且不敏感。
POST 请求用于提交数据,参数包含在请求体中,不会被缓存,参数较多或敏感。
10、Promise
在 JavaScript 中,Promise 是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成(或失败)及其结果的承诺。使用 Promise 可以更优雅地处理回调地狱(callback hell)和异步代码。
一个 Promise 可以处于以下三种状态之一:
Pending(进行中):初始状态,既不是成功状态,也不是失败状态。
Fulfilled(已成功):表示操作成功完成。
Rejected(已失败):表示操作失败。
创建一个 Promise 的基本语法如下:
const myPromise = new Promise((resolve, reject) => {
// 异步操作,例如请求数据、定时器等
if (/* 异步操作成功 */) {
resolve("Success"); // 调用 resolve 表示 Promise 成功
} else {
reject("Error"); // 调用 reject 表示 Promise 失败
}
});
Promise 可以通过 then() 方法来处理成功状态和失败状态:
myPromise.then((result) => {
console.log("Promise resolved: " + result); // 处理成功状态
}).catch((error) => {
console.log("Promise rejected: " + error); // 处理失败状态
});
当 Promise 被 resolve(成功)时,会调用 then() 中的回调函数;当 Promise 被 reject(失败)时,会调用 catch() 中的回调函数。
此外,Promise 还支持链式调用,可以通过返回另一个 Promise 实现连续的异步操作:
myPromise.then((result) => {
return doSomethingElse(result);
}).then((newResult) => {
console.log("Chained Promise resolved: " + newResult);
}).catch((error) => {
console.log("Error in promise chain: " + error);
});
通过使用 Promise,可以更清晰地编写和管理异步代码,避免回调地狱,提高代码可读性和可维护性。
11、async和await用法
async:async关键字用于定义一个函数为异步函数。异步函数在执行过程中可以暂停并等待异步操作的完成,然后恢复执行。在函数声明前加上async关键字,就可以将该函数定义为异步函数。
await:await关键字用于等待一个Promise对象的解析结果。在异步函数内部,可以使用await关键字来暂停函数的执行,直到Promise对象的状态变为resolved(解析完成)或rejected(解析失败)。await关键字后面跟着一个Promise对象,表示等待该Promise对象的解析结果。
区别:
async关键字用于定义一个函数为异步函数,而await关键字用于等待一个Promise对象的解析结果。
async函数内部可以包含多个await语句,每个await语句都会等待前一个await语句的Promise对象解析完成。
await只能在异步函数内部使用,而不能在普通函数中使用。