《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
一、本文面试题目录
1. JavaScript的数据类型有哪些?
JavaScript有七种基本数据类型:Number
(数字)、String
(字符串)、Boolean
(布尔值)、Null
(空值)、Undefined
(未定义)、Symbol
(符号)、BigInt
(大整数),还有一种引用数据类型Object
(对象),数组、函数等都属于对象类型。示例代码如下:
let num = 10; // Number
let str = "hello"; // String
let bool = true; // Boolean
let n = null; // Null
let u; // Undefined
let sym = Symbol('unique'); // Symbol
let big = 12345678901234567890n; // BigInt
let obj = {name: 'John'}; // Object
2. typeof null 的结果是什么?为什么?
typeof null
的结果是'object'
。这是JavaScript早期设计的一个缺陷,当初JavaScript采用32位系统,null
表示为全0的地址,而对象在底层存储也是类似的内存地址形式,所以typeof
判断时将null
误判为object
。
3. 如何判断一个变量是否为数组?
可以使用以下几种方法:
Array.isArray(arr)
:这是最推荐的方法,它会直接返回一个布尔值,表示参数是否为数组。Object.prototype.toString.call(arr) === '[object Array]'
:通过调用Object
原型上的toString
方法,能准确判断数据类型。arr instanceof Array
:判断变量是否是Array
构造函数的实例,但在跨框架或跨窗口环境下可能不准确。
示例代码:
let arr = [1, 2, 3];
console.log(Array.isArray(arr)); // true
console.log(Object.prototype.toString.call(arr) === '[object Array]'); // true
console.log(arr instanceof Array); // true
4. == 和 === 有什么区别?
==
会在比较时进行类型转换,只判断值是否相等。例如'1' == 1
会将字符串'1'
转换为数字1
,然后比较,结果为true
。而===
不会进行类型转换,要求值和类型都必须相等。如'1' === 1
结果为false
,因为类型不同。
5. 什么是闭包?请举例说明。
闭包是函数和其词法作用域的组合。它允许函数访问其外部函数作用域中的变量,即使外部函数已经执行完毕。例如:
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const counter = outer();
counter(); // 输出1
counter(); // 输出2
在这个例子中,inner
函数就是一个闭包,它可以访问outer
函数作用域中的count
变量。
6. 什么是作用域链?
当访问一个变量时,JavaScript会从当前作用域开始查找,如果找不到,就会向上一级作用域查找,直到找到该变量或者到达全局作用域,这样形成的链式查找结构就是作用域链。例如:
let globalVar = 'global';
function outer() {
let outerVar = 'outer';
function inner() {
let innerVar = 'inner';
console.log(globalVar); // 从当前作用域找不到,向上在全局作用域找到
console.log(outerVar); // 从当前作用域找不到,向上在外层函数作用域找到
console.log(innerVar); // 在当前作用域找到
}
inner();
}
outer();
7. 什么是原型和原型链?
每个对象都有一个__proto__
属性(现代浏览器中),它指向其构造函数的prototype
对象。原型链是由多个对象的原型依次连接形成的链条,当访问对象的某个属性或方法时,如果对象自身没有,就会沿着原型链向上查找,直到找到为止或者到达null
。例如:
function Person(name) {
this.name = name;
}
Person.prototype.sayName = function() {
console.log(this.name);
};
let person = new Person('Alice');
person.sayName(); // 从person对象自身找不到sayName方法,沿原型链在Person.prototype中找到
8. 什么是 this?它是如何绑定的?
this
是JavaScript中的一个关键字,它的值取决于函数的调用方式。具体如下:
- 普通函数调用时,在非严格模式下,
this
指向全局对象(浏览器中是window
),严格模式下为undefined
。 - 作为对象的方法调用时,
this
指向该对象。 - 使用
new
关键字调用构造函数时,this
指向新创建的实例对象。 - 箭头函数没有自己的
this
,它会继承外层作用域的this
。 - 使用
call
、apply
、bind
方法可以显式绑定this
。
示例代码:
function normalFunc() {
console.log(this);
}
normalFunc(); // 非严格模式下指向window
let obj = {
method: function() {
console.log(this);
}
};
obj.method(); // 指向obj
function Constructor() {
this.name = 'constructor';
}
let instance = new Constructor(); // this指向instance
let arrowFunc = () => {
console.log(this);
};
arrowFunc(); // 继承外层的this,若在全局作用域,非严格模式下指向window
function func() {
console.log(this.name);
}
let anotherObj = {name: 'another'};
func.call(anotherObj); // this指向anotherObj
9. var、let、const 有何区别?
var
是函数作用域,在函数内部声明的var
变量在整个函数内都可访问。它存在变量提升现象,会被提升到作用域顶部,且可以重复声明。例如:
function test() {
console.log(x); // 输出undefined,var存在变量提升
var x = 10;
}
test();
let
是块作用域,只在声明它的代码块内有效。它也存在变量提升,但会形成暂时性死区,在声明之前访问会报错,且不允许重复声明。例如:
{
let y = 20;
console.log(y); // 输出20
}
console.log(y); // 报错,y不在此作用域内
let z = 30;
// let z = 40; // 报错,不能重复声明
const
也是块作用域,用于声明常量,一旦声明就不能重新赋值,声明时必须初始化。例如:
const PI = 3.14;
// PI = 3.15; // 报错,不能重新赋值
10. new 操作符的原理是什么?
new
操作符主要做了以下几件事:
- 创建一个空对象。
- 将空对象的
__proto__
属性指向构造函数的prototype
属性。 - 将构造函数中的
this
绑定到新创建的对象上,执行构造函数中的代码。 - 如果构造函数没有返回非原始类型的值,则返回新创建的对象。
示例代码:
function Person(name) {
this.name = name;
}
let person = new Person('Bob');
console.log(person.name); // Bob
11. 手写 call、apply、bind 简化版
call
方法:
Function.prototype.myCall = function(ctx, ...args) {
ctx = ctx || window;
ctx.fn = this;
const result = ctx.fn(...args);
delete ctx.fn;
return result;
};
function add(a, b) {
return a + b;
}
let obj = {name: 'obj'};
let sum = add.myCall(obj, 3, 5);
console.log(sum); // 8
apply
方法:
Function.prototype.myApply = function(ctx, args) {
ctx = ctx || window;
ctx.fn = this;
const result = ctx.fn(...(args || []));
delete ctx.fn;
return result;
};
let sum2 = add.myApply(obj, [4, 6]);
console.log(sum2); // 10
bind
方法:
Function.prototype.myBind = function(ctx, ...args) {
const self = this;
return function(...innerArgs) {
return self.apply(ctx, [...args,...innerArgs]);
};
};
let boundAdd = add.myBind(obj, 2);
let sum3 = boundAdd(8);
console.log(sum3); // 10
12. 常见数组方法有哪些?
- 遍历类:
forEach
(遍历数组,执行回调函数,无返回值)、map
(遍历数组,返回新数组,回调函数返回值组成新数组)、filter
(过滤数组,返回符合条件的元素组成的新数组)、reduce
(将数组缩减为单个值,通过回调函数累加等操作)。 - 查找类:
find
(查找第一个符合条件的元素)、includes
(判断数组是否包含某个元素)、indexOf
(查找元素在数组中的索引,不存在返回-1)。 - 变异类:
push
(向数组末尾添加元素,返回新长度)、pop
(删除数组末尾元素,返回删除的元素)、splice
(删除、插入或替换数组元素,返回删除的元素数组)、sort
(对数组排序,会修改原数组)。 - 不变类:
slice
(截取数组部分元素,返回新数组,不修改原数组)、concat
(合并数组,返回新数组,不修改原数组)。
示例代码:
let arr = [1, 2, 3, 4, 5];
// forEach
arr.forEach(item => console.log(item));
// map
let newArr = arr.map(item => item * 2);
// filter
let filteredArr = arr.filter(item => item > 2);
// reduce
let sum = arr.reduce((acc, cur) => acc + cur, 0);
// find
let found = arr.find(item => item === 3);
// includes
let has = arr.includes(4);
// indexOf
let index = arr.indexOf(2);
// push
let length = arr.push(6);
// pop
let popped = arr.pop();
// splice
let removed = arr.splice(1, 2);
// sort
arr.sort((a, b) => a - b);
// slice
let slicedArr = arr.slice(0, 2);
// concat
let combinedArr = arr.concat([7, 8]);
13. 如何实现数组去重?
可以使用Set
数据结构或filter
方法等。示例如下:
- 使用
Set
:
let arr = [1, 2, 2, 3, 3, 4];
let uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3, 4]
- 使用
filter
:
let arr2 = [1, 2, 2, 3, 3, 4];
let uniqueArr2 = arr2.filter((item, index) => arr2.indexOf(item) === index);
console.log(uniqueArr2); // [1, 2, 3, 4]
14. JS如何判断变量是对象?
可以使用typeof
结合null
判断,或者Object.prototype.toString.call
方法。示例代码:
let obj = {key: 'value'};
let num = 10;
// 方法一
console.log(typeof obj === 'object' && obj!== null); // true
console.log(typeof num === 'object' && num!== null); // false
// 方法二
console.log(Object.prototype.toString.call(obj) === '[object Object]'); // true
console.log(Object.prototype.toString.call(num) === '[object Object]'); // false
15. JSON.stringify 中不能序列化哪些?
JSON.stringify
不能序列化undefined
、function
、Symbol
以及包含循环引用的对象。示例代码:
let obj = {
prop1: undefined,
prop2: function() {console.log('function')},
prop3: Symbol('sym'),
prop4: {}
};
obj.prop4.circular = obj.prop4; // 循环引用
console.log(JSON.stringify(obj));
// 输出{"prop4": {"circular": "[Circular]"}},忽略了undefined、function、Symbol,循环引用会特殊处理