JavaScript 基础
一、数据类型
1. JavaScript 有哪些数据类型?
JavaScript 有以下数据类型:
原始类型(Primitive Types):
number- 数字string- 字符串boolean- 布尔值undefined- 未定义null- 空值symbol- 符号(ES6)bigint- 大整数(ES2020)
引用类型(Reference Types):
object- 对象(包括数组、函数、日期等)
2. null 和 undefined 的区别?
- undefined:表示变量声明了但未赋值,或者函数没有返回值
- null:表示空值,通常用于明确表示变量为空
javascript
let a; // undefined
let b = null; // null
typeof undefined // "undefined"
typeof null // "object" (历史遗留问题)3. 类型检测的方式有哪些?
方式一:typeof
javascript
typeof 123 // "number"
typeof "hello" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" (bug)
typeof [] // "object"
typeof {} // "object"
typeof function(){} // "function"方式二:instanceof
javascript
[] instanceof Array // true
{} instanceof Object // true
new Date() instanceof Date // true方式三:Object.prototype.toString
javascript
Object.prototype.toString.call(123) // "[object Number]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(null) // "[object Null]"方式四:Array.isArray()
javascript
Array.isArray([]) // true
Array.isArray({}) // false二、作用域和闭包
1. 什么是作用域?
作用域(Scope)决定了变量和函数的可访问性。
作用域类型:
- 全局作用域:在整个程序中都可访问
- 函数作用域:只在函数内部可访问
- 块作用域:使用
let和const声明的变量具有块作用域
javascript
// 全局作用域
var globalVar = "global";
function test() {
// 函数作用域
var functionVar = "function";
if (true) {
// 块作用域
let blockVar = "block";
const blockConst = "block";
}
}2. 什么是闭包?
闭包(Closure)是指函数可以访问其外部作用域中的变量,即使外部函数已经执行完毕。
javascript
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3闭包的应用:
- 数据私有化
- 模块化
- 函数柯里化
3. var、let、const 的区别?
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数作用域 | 块作用域 | 块作用域 |
| 变量提升 | 是 | 是(TDZ) | 是(TDZ) |
| 重复声明 | 允许 | 不允许 | 不允许 |
| 修改值 | 可以 | 可以 | 不可以 |
javascript
// var
console.log(a); // undefined
var a = 1;
// let/const
console.log(b); // ReferenceError
let b = 2;三、this 指向
1. this 的指向规则
普通函数:
- 默认指向全局对象(浏览器中是
window,严格模式下是undefined) - 作为对象方法调用时指向该对象
- 使用
call/apply/bind可以改变指向
javascript
// 默认绑定
function foo() {
console.log(this);
}
foo(); // window (非严格模式)
// 隐式绑定
const obj = {
name: "obj",
foo: function() {
console.log(this.name);
}
};
obj.foo(); // "obj"
// 显式绑定
foo.call({name: "call"}); // {name: "call"}箭头函数:
- 箭头函数没有自己的
this,会继承外层作用域的this - 无法通过
call/apply/bind改变this指向
javascript
const obj = {
name: "obj",
foo: function() {
// 普通函数
console.log(this.name); // "obj"
const arrow = () => {
// 箭头函数继承外层 this
console.log(this.name); // "obj"
};
arrow();
}
};2. call、apply、bind 的区别?
相同点: 都可以改变函数的 this 指向
不同点:
- call:立即执行,参数逐个传递
- apply:立即执行,参数以数组形式传递
- bind:返回新函数,不立即执行
javascript
function greet(greeting, punctuation) {
console.log(greeting + this.name + punctuation);
}
const obj = { name: "Alice" };
// call
greet.call(obj, "Hello, ", "!"); // "Hello, Alice!"
// apply
greet.apply(obj, ["Hi, ", "."]); // "Hi, Alice."
// bind
const bound = greet.bind(obj, "Hey, ");
bound("!!"); // "Hey, Alice!!"四、原型和继承
1. 原型链是什么?
每个对象都有一个指向其原型对象的内部链接,通过这个链接可以访问原型对象的属性和方法。多个对象通过原型链接形成原型链。
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log("Hello, " + this.name);
};
const person = new Person("Alice");
person.sayHello(); // "Hello, Alice"
// 原型链查找:person -> Person.prototype -> Object.prototype -> null2. 实现继承的方式有哪些?
方式一:原型链继承
javascript
function Parent() {
this.name = "Parent";
}
Parent.prototype.sayHello = function() {
console.log("Hello from " + this.name);
};
function Child() {}
Child.prototype = new Parent();
const child = new Child();方式二:构造函数继承
javascript
function Parent(name) {
this.name = name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}方式三:组合继承
javascript
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log("Hello from " + this.name);
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child;方式四:ES6 Class 继承
javascript
class Parent {
constructor(name) {
this.name = name;
}
sayHello() {
console.log("Hello from " + this.name);
}
}
class Child extends Parent {
constructor(name, age) {
super(name);
this.age = age;
}
}五、异步编程
1. Promise 的原理和使用
Promise 的三种状态:
pending(等待中)fulfilled(已成功)rejected(已失败)
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("成功");
// reject("失败");
}, 1000);
});
promise
.then(result => {
console.log(result);
})
.catch(error => {
console.error(error);
});Promise 的常用方法:
Promise.all():所有 Promise 都成功时返回结果数组Promise.race():返回最先完成的 PromisePromise.allSettled():等待所有 Promise 完成(无论成功或失败)
2. async/await 的使用
async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。
javascript
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}3. 事件循环(Event Loop)
JavaScript 是单线程的,通过事件循环机制处理异步任务。
执行顺序:
- 同步代码
- 微任务(Promise.then、queueMicrotask)
- 宏任务(setTimeout、setInterval、I/O)
javascript
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
// 输出:1, 4, 3, 2六、数组方法
1. 常用的数组方法
遍历方法:
forEach():遍历数组,不返回新数组map():遍历数组,返回新数组filter():过滤数组,返回满足条件的元素reduce():累积计算,返回一个值
javascript
const arr = [1, 2, 3, 4, 5];
// map
const doubled = arr.map(x => x * 2); // [2, 4, 6, 8, 10]
// filter
const evens = arr.filter(x => x % 2 === 0); // [2, 4]
// reduce
const sum = arr.reduce((acc, x) => acc + x, 0); // 15查找方法:
find():查找第一个满足条件的元素findIndex():查找第一个满足条件的元素索引some():判断是否有元素满足条件every():判断是否所有元素都满足条件
javascript
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const user = users.find(u => u.id === 2); // { id: 2, name: "Bob" }
const hasBob = users.some(u => u.name === "Bob"); // true七、ES6+ 新特性
1. 解构赋值
javascript
// 数组解构
const [a, b, c] = [1, 2, 3];
// 对象解构
const { name, age } = { name: "Alice", age: 20 };
// 函数参数解构
function greet({ name, age }) {
console.log(`${name} is ${age} years old`);
}2. 展开运算符
javascript
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5]
// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1, c: 3 }; // { a: 1, b: 2, c: 3 }
// 函数参数展开
function sum(...args) {
return args.reduce((a, b) => a + b, 0);
}3. 模板字符串
javascript
const name = "Alice";
const age = 20;
const message = `Hello, my name is ${name} and I'm ${age} years old.`;八、常见面试题
1. 深拷贝和浅拷贝的区别?
浅拷贝:只复制对象的第一层属性
javascript
const obj = { a: 1, b: { c: 2 } };
const shallow = Object.assign({}, obj);
shallow.b.c = 3;
console.log(obj.b.c); // 3 (原对象也被修改)深拷贝:完全复制对象的所有层级
javascript
// 使用 JSON(有局限性,不能处理函数、undefined 等)
const deep1 = JSON.parse(JSON.stringify(obj));
// 递归实现
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof Array) return obj.map(item => deepClone(item));
if (typeof obj === 'object') {
const cloned = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}
}2. 防抖和节流的区别?
防抖(Debounce):在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时
javascript
function debounce(func, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => func.apply(this, args), delay);
};
}节流(Throttle):规定在一个单位时间内,只能触发一次函数
javascript
function throttle(func, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
func.apply(this, args);
lastTime = now;
}
};
}3. 数组去重的方法?
javascript
const arr = [1, 2, 2, 3, 3, 4];
// 方法1:Set
const unique1 = [...new Set(arr)];
// 方法2:filter + indexOf
const unique2 = arr.filter((item, index) => arr.indexOf(item) === index);
// 方法3:reduce
const unique3 = arr.reduce((acc, item) => {
if (!acc.includes(item)) acc.push(item);
return acc;
}, []);