前端面试题汇总(二)

1.JavaScript判断变量的类型的方法

JavaScript有4种方法判断变量的类型,分别是typeofinstanceofObject.prototype.toString.call()(对象原型链判断方法)、constructor (用于引用数据类型)
typeof:常用于判断基本数据类型,对于引用数据类型除了function返回’function‘,其余全部返回’object’。
instanceof:主要用于区分引用数据类型,检测方法是检测的类型在当前实例的原型链上,用其检测出来的结果都是true,不太适合用于简单数据类型的检测,检测过程繁琐且对于简单数据类型中的undefined, null, symbol检测不出来。
constructor:用于检测引用数据类型,检测方法是获取实例的构造函数判断和某个类是否相同,如果相同就说明该数据是符合那个数据类型的,这种方法不会把原型链上的其他类也加入进来,避免了原型链的干扰。 Object.prototype.toString.call():适用于所有类型的判断检测,检测方法是Object.prototype.toString.call(数据) 返回的是该数据类型的字符串。

2.数组去重的方法

1.使用Set数据结构:将数组转换为Set,Set会自动去除重复元素,然后再转换为数组。

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

2.使用filter+indexOf方法:通过filter方法遍历数组,返回只包含第一次出现的元素的新数组。

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]

3.使用reduce+includes方法:通过reduce方法遍历数组,将元素添加到一个新数组中,但只添加第一次出现的元素。

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

4.使用Map数据结构:Map数据结构去重数组的原理是利用Map对象的键值对特性,其中键是唯一的,可以帮助我们去除重复的元素。我们可以遍历数组,将数组元素作为Map的键,然后将Map的键转换为数组,即可得到去重后的数组。

const arr = [1, 2, 3, 2, 4, 5, 3];

const uniqueArr = Array.from(new Map(arr.map(item => [item, item])).values());

console.log(uniqueArr); // [1, 2, 3, 4, 5]

3.伪数组和数组的区别

伪数组它的类型不是Array,而是Object,而数组类型是Array。可以使用的length属性查看长度,也可以使用[index]获取某个元素,但是不能使用数组的其他方法,也不能改变长度,可以使用for in方法遍历。

伪数组的常见场景:
函数参数对象(arguments):在函数内部,可以通过arguments对象获取传入的所有参数,它具有类似数组的结构,但并非真正的数组。
DOM元素集合:通过querySelectorAll等方法获取的DOM元素集合是伪数组,它们具有类似数组的结构,但不是真正的数组。

将伪数组转换为数组可以通过以下几种方法
1.使用Array.from()方法:Array.from()方法可以将类数组对象或可迭代对象转换为真正的数组。

let pseudoArray = document.querySelectorAll('.example'); // 伪数组
let realArray = Array.from(pseudoArray); // 将伪数组转换为真正的数组

2.使用Array.prototype.slice.call()方法:通过调用Array.prototype.slice.call()方法,可以将伪数组转换为数组。

let pseudoArray = document.querySelectorAll('.example'); // 伪数组
let realArray = Array.prototype.slice.call(pseudoArray); // 将伪数组转换为真正的数组

3.使用展开运算符(Spread Operator):展开运算符可以将可迭代对象展开为数组。

let pseudoArray = document.querySelectorAll('.example'); // 伪数组
let realArray = [...pseudoArray]; // 将伪数组转换为真正的数组

4.箭头函数

箭头函数相当于匿名函数,简化了函数定义。箭头函数有两种写法,当函数体是单条语句的时候可以省略{}和return。另一种是包含多条语句,不可以省略{}和return。
箭头函数的特点:
1.箭头函数没有自己的this,它会捕获其所在上下文的this值,因此可以解决this指向问题。
2.箭头函数不能使用arguments对象,但可以使用rest参数(…)来获取所有参数。
3.箭头函数不能作为构造函数使用,不能使用new关键字调用。
4.箭头函数没有prototype属性和super

5.事件扩展运算符的应用场景

展开语法(Spread syntax), 可以在函数调用/数组构造时, 将数组表达式或者string在语法层面展开;还可以在构造字面量对象时, 将对象表达式按key-value的方式展开
1.合并数组,合并对象
2.复制数组,复制对象
3.将字符串转换为字符数组
4.将函数参数转换为数组
5.构造字面量对象时,进行浅克隆或者属性拷贝

6.什么是闭包

一个函数和词法环境的引用捆绑在一起,这样的组合就是闭包(closure)。一般就是一个函数A,return其内部的函数B,被return出去的B函数能够在外部访问A函数内部的变量,这时候就形成了一个B函数的变量背包,A函数执行结束后这个变量背包也不会被销毁,并且这个变量背包在A函数外部只能通过B函数访问。
闭包形成的原理:作用域链,当前作用域可以访问上级作用域中的变量 闭包解决的问题:能够让函数作用域中的变量在函数执行结束之后不被销毁,同时也能在函数外部可以访问函数内部的局部变量
闭包带来的问题:由于垃圾回收器不会将闭包中变量销毁,于是就造成了内存泄露,内存泄露积累多了就容易导致内存溢出
闭包的应用:能够模仿块级作用域,能够实现柯里化,在构造函数中定义特权方法、Vue中数据响应式Observer中使用闭包等。

7.JS变量提升

变量提升是指JS的变量和函数声明会在代码编译期,提升到代码的最前面。 变量提升成立的前提是使用Var关键字进行声明的变量,并且变量提升的时候只有声明被提升,赋值并不会被提升,同时函数的声明提升会比变量的提升优先。 变量提升的结果,==可以在变量初始化之前访问该变量,返回的是undefined。==在函数声明前可以调用该函数。使用let和const声明的变量是创建提升,形成暂时性死区,在初始化之前访问let和const创建的变量会报错。

8.this指向(普通函数、箭头函数)

普通函数:
1.普通函数的 this 指向是动态的,取决于函数被调用时的情况。
2.如果普通函数作为对象的方法调用,this 会指向调用该函数的对象
3.如果普通函数作为普通函数直接调用,而不是作为对象的方法,this 将指向全局对象(浏览器环境下是 window 对象)
4.如果使用严格模式,普通函数作为普通函数调用时,this 将是 undefined。
箭头函数:
1.箭头函数没有自己的 this 值,它会捕获外层作用域的 this 值

9.call apply bind的作用和区别

1.call:
call() 方法调用一个函数,其具有一个指定的 this 值和分别地提供的参数(参数列表)。
call() 方法可以接受一个或多个参数,第一个参数是要设置为函数执行上下文的对象,之后的参数是传递给函数的参数。
使用 call() 方法会立即执行函数

function sayHello() {
  console.log('Hello, ' + this.name);
}

const person = { name: 'Alice' };
sayHello.call(person); // 输出:Hello, Alice

2.apply:
apply() 方法调用一个函数,其具有一个指定的 this 值和以数组形式提供的参数。
apply() 方法接受一个参数,第一个参数是要设置为函数执行上下文的对象,第二个参数是一个数组,包含要传递给函数的参数。
使用 apply() 方法会立即执行函数。

function sayHello(greeting) {
  console.log(greeting + ', ' + this.name);
}

const person = { name: 'Bob' };
sayHello.apply(person, ['Hi']); // 输出:Hi, Bob

3.bind:
bind() 方法创建一个新的函数,将指定的对象设置为函数执行时的 this 值。
bind() 方法不会立即执行函数,而是返回一个新函数,可以稍后执行。
bind() 方法可以传递参数,并在新函数执行时继续传递参数。

function sayHello() {
  console.log('Hello, ' + this.name);
}
const person = { name: 'Charlie' };
const sayHelloToCharlie = sayHello.bind(person);
sayHelloToCharlie(); // 输出:Hello, Charlie

10.js继承的方法和优缺点

1.原型链继承:
原型链继承是 JavaScript 中最简单的继承方式,通过让子类的原型指向父类的实例来实现继承。
优点:
实现简单,易于理解。
缺点:
子类会继承父类原型对象上的属性和方法,可能导致原型链过长。
子类无法向父类传递参数。
子类对原型对象的引用会被共享,可能导致一个实例修改原型对象的属性影响所有实例。

function Parent() {
  this.name = "Parent";
}

Parent.prototype.sayHello = function() {
  console.log("Hello, I am " + this.name);
}

function Child() {}

Child.prototype = new Parent();

const child = new Child();
child.sayHello(); // 输出:Hello, I am Parent

2.构造函数继承(借用构造函数 / 经典继承):
构造函数继承通过在子类构造函数中调用父类构造函数来实现继承。
优点:
可以向父类传递参数。
避免了原型链继承中原型对象属性共享的问题。
缺点:
子类无法继承父类原型对象上的方法。
每个实例都会有自己的一份父类属性的副本,可能造成内存浪费。

function Parent(name) {
  this.name = name;
}

function Child(name) {
  Parent.call(this, name);
}

const child = new Child("Child");
console.log(child.name); // 输出:Child

3.组合继承:
组合继承结合了原型链继承和构造函数继承的优点,既可以继承父类原型对象上的属性和方法,又可以向父类传递参数。
优点:
继承属性和方法,避免原型链继承中原型对象属性共享的问题。
可以向父类传递参数。
缺点:
调用了两次父类构造函数,可能造成性能上的影响。

function Parent(name) {
  this.name = name;
}

Parent.prototype.sayHello = function() {
  console.log("Hello, I am " + this.name);
}

function Child(name) {
  Parent.call(this, name);
}

Child.prototype = new Parent();

const child = new Child("Child");
child.sayHello(); // 输出:Hello, I am Child

4.原型式继承:
原型式继承利用一个临时构造函数创建一个对象,并将这个对象作为另一个对象的原型。
优点:
简单易懂。
缺点:
多个实例会共享引用相同的对象,可能导致意外修改。

const parent = {
  name: "Parent",
  sayHello: function() {
    console.log("Hello, I am " + this.name);
  }
};

const child = Object.create(parent);
child.name = "Child";

child.sayHello(); // 输出:Hello, I am Child

5.寄生式继承:
寄生式继承是在原型式继承的基础上增加了一些额外的功能。
优点:
封装了创建对象的过程,可以添加一些额外的功能。
缺点:
和原型式继承一样,多个实例会共享引用相同的对象。

function createChild(parent) {
  const child = Object.create(parent);
  child.name = "Child";
  return child;
}

const parent = {
  name: "Parent",
  sayHello: function() {
    console.log("Hello, I am " + this.name);
  }
};

const child = createChild(parent);
child.sayHello(); // 输出:Hello, I am Child

6.寄生组合式继承(最佳实践):
寄生组合式继承是组合继承的一种优化,通过借用构造函数来继承属性,通过原型链来继承方法。这种方式避免了调用两次父类构造函数的性能问题。
优点:
继承属性和方法,避免了属性共享和调用两次父类构造函数的问题。
缺点:
相对于其他方式,代码相对复杂一些。

function Parent(name) {
  this.name = name;
}

Parent.prototype.sayHello = function() {
  console.log("Hello, I am " + this.name);
}

function Child(name) {
  Parent.call(this, name);
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child = new Child("Child");
child.sayHello(); // 输出:Hello, I am Child
  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值