20道前端JS高频面试题附答案(2023)

目录

什么是闭包?

什么是函数柯里化?

什么是作用域链?如何理解作用域链?

如何创建一个对象?有哪些方法?

如何实现继承?有哪些方式?

解释JavaScript中的事件冒泡和事件捕获。

解释JavaScript中的this关键字。

解释JavaScript中的原型继承。

解释JavaScript中的事件委托。

什么是Generator函数?如何使用Generator函数?

什么是跨域请求?如何解决跨域请求问题?

如何测试对象的属性和值? 

undefined和null的区别是什么?

什么是同步和异步操作? 如何使用 JavaScript 进行异步编程?

如何避免回调地狱?

什么是高阶函数? 如何在 JavaScript 中实现高阶函数?

什么是模块化? 如何在 JavaScript 中使用模块化?

什么是解构赋值? 如何在 JavaScript 中使用解构赋值?

解释Javascript中的事件循环。

 解释JavaScript中的Promise对象。


  • 什么是变量提升?如何避免变量提升?

答:变量提升是指JavaScript在代码执行前会先将变量的声明进行提升到当前作用域的顶部。可以使用let或const等关键字来避免变量提升,因为这些关键字声明的变量不会被自动提升。

  • 什么是闭包?

答:闭包是指函数可以访问其外部上下文中定义的变量,即使这些变量在闭包函数执行时已经销毁。闭包可以创建私有变量并且可以在不同的函数调用中维持它们的值。在JavaScript中,每次函数调用都会创建一个新的执行上下文环境,闭包需要在其执行环境中创建一个持久化环境以保存其外部上下文的状态。闭包在JavaScript中有多种用途,例如实现函数柯里化、封装私有变量等。

举个例子,下面的函数返回一个内部函数,这个内部函数可以访问外部函数中的变量“count”,并且“count”可以被更新和持久化到闭包函数中。

function counter() {
  let count = 0;
  return function increment() {
    count++;
    console.log(count);
  }
}
var c = counter();
c(); // 输出1
c(); // 输出2
c(); // 输出3

  • 什么是函数柯里化?

答:函数柯里化是通过将一个多参数的函数转换成一个多个单参数函数的过程,例如将函数f(x,y)转换成函数f(x)(y)。这个过程可以用闭包来实现。柯里化的用途包括简化函数调用、提供默认参数值以及延迟求值等。

  • 什么是作用域链?如何理解作用域链?

答:作用域链是指JavaScript在查找变量的时候,先从当前作用域开始查找,如果没有找到则向上一级作用域查找,直到找到为止。理解作用域链可以帮助我们更好地理解变量的作用范围及可访问性。

例如,当我们在一个函数中访问一个变量时,JavaScript会先查找当前函数内的变量,如果没有找到则向上一级作用域查找,直到找到为止。如果一直找到全局作用域还没有找到,则会返回undefined。

  • 如何创建一个对象?有哪些方法?

答:可以使用对象字面量、构造函数、Object.create()等方式来创建对象。

1、使用对象字面量:

var obj = {
  name: 'Tom',
  age: 18
};

2、使用构造函数:

function Person(name, age) {
  this.name = name;
  this.age = age;
}
var person = new Person('Tom', 18);

3、使用Object.create():

var obj = Object.create({}, {
  name: {
    value: 'Tom'
  },
  age: {
    value: 18
  }
});

 

  • 如何实现继承?有哪些方式?

答:可以使用原型链、构造函数、组合继承、寄生组合继承等方式来实现继承。

1、使用原型链:

function Parent() {
  this.name = 'Tom';
}
function Child() {}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name); // 输出'Tom'

2、使用构造函数:

function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
var child = new Child();
console.log(child.name); // 输出'Tom'

3、使用组合继承:

function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
Child.prototype = new Parent();
var child = new Child();
console.log(child.name); // 输出'Tom'

使用寄生组合继承:

function Parent() {
  this.name = 'Tom';
}
function Child() {
  Parent.call(this);
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child = new Child();
console.log(child.name); // 输出'Tom'

 

  • 解释JavaScript中的事件冒泡和事件捕获。

答:事件冒泡和事件捕获都是基于DOM元素之间嵌套的关系。事件捕获是从最外层元素向下传递事件,直到到达触发事件的元素。而事件冒泡则是从触发事件的元素向上冒泡到最外层元素。在JavaScript中,事件处理程序依赖于事件冒泡和事件捕获。默认情况下,事件处理程序被绑定到冒泡阶段。

<div id="outer">
  <div id="inner">Click me!</div>
</div>

var outer = document.getElementById('outer');
var inner = document.getElementById('inner');
outer.addEventListener('click', function() {
  console.log('Outer clicked');
}, true);
inner.addEventListener('click', function() {
  console.log('Inner clicked');
});

在这个例子中,我们给外层元素和内层元素都添加了一个点击事件监听器,并且外层元素的监听器是在捕获阶段执行的。当我们在内层元素上点击时,控制台会依次输出“Outer clicked”和“Inner clicked”。这是因为外层元素的监听器在事件捕获阶段执行,而内层元素的监听器在事件冒泡阶段执行。

  • 解释JavaScript中的this关键字。

答:在JavaScript中,this关键字表示当前执行上下文环境的所有者。在全局上下文环境中,this指向的是window对象。在函数中,this的值取决于函数的调用方式。

如果函数是作为一个方法调用的,this就指向这个方法所属的对象。

var person = {
  name: 'John',
  greet: function() {
    console.log('Hello, my name is ' + this.name);
  }
}
person.greet(); // 输出:Hello, my name is John

如果函数是作为函数调用的,this就指向全局上下文环境。

function sayName() {
  console.log(this.name);
}
sayName(); // 输出:undefined

在严格模式下,函数的this值为undefined。

'use strict';
function sayName() {
  console.log(this.name);
}
sayName(); // 抛出错误:Cannot read property 'name' of undefined

  • 解释JavaScript中的原型继承。

 答:JavaScript中的原型继承是通过在对象的原型链上继承其他对象的属性和方法来实现的。每个JavaScript对象都有一个原型对象,它定义了一个对象的属性和方法。如果一个属性或方法不存在于当前对象,引擎会沿着原型链向上查找直到找到为止。这个过程一直持续到Object.prototype,它是所有JavaScript对象的祖先。在原型链上,一个对象可以继承多个对象的属性和方法,并且可以动态地添加和删除属性和方法。

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

Person.prototype.greet = function() {
  console.log('Hello, my name is ' + this.name);
}

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

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

var s = new Student('John');
s.greet(); // 输出:Hello, my name is John

在这个例子中,我们定义了一个Person构造函数,它有一个属性和一个方法。然后我们定义了一个Student构造函数,它继承了Person的方法,并且重写了Person的属性。最后,我们创建了一个Student实例,并调用了它的greet方法。这个方法在Person的原型对象上定义,但可以被Student对象继承和重写。

  • 解释JavaScript中的事件委托。

答:事件委托是一种通过将事件处理程序绑定到父元素来提高性能和代码可维护性的技术。当我们在一个父元素上绑定事件处理程序时,所有子元素也会继承这个处理程序。在事件捕获或冒泡阶段,我们可以使用event.target属性来确定哪个子元素触发了事件,并根据需要执行相应的操作。

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li>Item 4</li>
</ul>


var list = document.getElementById('myList');
list.addEventListener('click', function(event) {
  if (event.target.tagName === 'LI') {
    console.log(event.target.textContent);
  }
});

在这个例子中,我们为父元素绑定了一个点击事件处理程序,并且在事件处理程序内部判断了发生事件的目标元素是否是一个li元素。如果是,我们就输出这个元素的文本内容。这个技术可以提高性能,因为只有一个事件处理程序需要绑定,而不是为每个子元素都绑定一个处理程序。这样做也使代码更加可维护,因为我们不需要处理每个子元素的变动。

  • 什么是Generator函数?如何使用Generator函数?

答:Generator函数是一种异步编程的解决方案,在处理异步任务时可以更清晰、更具可读性。

Generator函数通过yield语句来定义状态,通过next()方法来进行状态转移。

示例代码:

function* generator() {
  var result = yield new Promise(function(resolve) {
    setTimeout(function() {
      resolve('success');
    }, 1000);
  });
  console.log(result);
}
var gen = generator();
var promise = gen.next().value;
promise.then(function(result) {
  gen.next(result);
});

  • 什么是跨域请求?如何解决跨域请求问题?

答:跨域请求是指由于浏览器的安全限制,不允许在一个域名下的JavaScript代码访问另一个域名下的资源。

解决跨域请求问题可以通过jsonp、CORS等方式。

1、使用jsonp:

function handleResponse(data) {
  console.log(data);
}
var script = document.createElement('script');
script.src = 'http://example.com/data?callback=handleResponse';
document.body.appendChild(script);

2、使用CORS:

在服务端设置Access-Control-Allow-Origin头:

Access-Control-Allow-Origin: *

在客户端发送跨域请求,可以使用XMLHttpRequest对象,并设置withCredentials属性为true。

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://example.com/data');
xhr.onload = function() {
  console.log(xhr.responseText);
}
xhr.send();

  • 如何测试对象的属性和值? 

答:可以使用JavaScript的in运算符来检查一个对象是否具有某个属性,或者使用Object.hasOwnProperty()方法来检查一个对象是否具有自身属性。可以使用对象的属性值来进行测试并进行逻辑比较。也可以使用typeof运算符来检查一个对象的属性是否具有某个类型。

  • undefined和null的区别是什么?

答:undefined表示一个变量已声明但未初始化,或者不存在该变量。null表示一个变量已经定义,但值为null。另外,在JavaScript中undefined是一个全局变量,而null是一个关键字。

  • 什么是同步和异步操作? 如何使用 JavaScript 进行异步编程?

答:同步操作是指一个在主线程中执行的操作,如果该操作需要一些时间才能返回结果,主线程就会被阻塞,直到该操作完成并返回结果。而异步操作是指一个在主线程之外执行的操作,当该操作完成后,它会通知主线程,并运行相应的回调函数。

JavaScript 中的异步编程通常使用回调函数来实现。可以使用 setTimeout()、setInterval()、XMLHttpRequest 等函数来启动异步操作,并将一个回调函数传递给这些函数。当异步操作完成时,该函数将被异步地调用,并包含异步操作的结果作为参数。

  • 如何避免回调地狱?

答:回调地狱指的是代码嵌套过深,导致代码可读性差和维护难度大。为了避免回调地狱,可以使用 Promise 或 async/await 语法。

使用 Promise 可以更好地管理异步操作,并消除回调函数的嵌套。而 async/await 语法可以将异步操作表现为同步代码,从而更加自然且易于理解。

  • 什么是高阶函数? 如何在 JavaScript 中实现高阶函数?

答:高阶函数是指可以接受一个或多个函数作为参数,并返回一个新的函数的函数。高阶函数通常用于实现抽象和复用,可以将函数的特定行为抽象出来,并将其作为参数传递给其他函数。

在 JavaScript 中,可以使用函数的参数和返回值来实现高阶函数。例如,可以使用 Array.prototype.map() 方法来实现一个高阶函数,该函数接受一个函数作为参数,并将该函数应用于数组的每个元素。

  • 什么是模块化? 如何在 JavaScript 中使用模块化?

答:模块化是指将代码分解为独立的模块,并通过导出和导入来组合这些模块的过程。JavaScript 中的模块化可以使用 CommonJS、AMD 或 ES6 模块等规范来实现。

在 JavaScript 中,可以使用 CommonJS 规范来实现模块化。在 Node.js 中,可以使用 require() 导入模块,使用 exports 关键字或 module.exports 导出模块。在浏览器中,可以使用 Browserify 或 Webpack 等打包工具来实现 CommonJS 模块化。

  • 什么是解构赋值? 如何在 JavaScript 中使用解构赋值?

答:解构赋值是一种快捷的访问和修改对象和数组的方式。解构赋值可以将对象中的属性或数组中的元素分配给变量,并且可以在一行代码中实现多个分配。

在 JavaScript 中,可以使用解构赋值来访问和修改变量。例如,以下代码中的解构赋值将数组中的元素分配给三个变量:

const [a, b, c] = [1, 2, 3];
console.log(a); // 输出 1
console.log(b); // 输出 2
console.log(c); // 输出 3

 

  • 解释Javascript中的事件循环。

答:之前有详细写过篇章。一文带你了解JavaScript的事件循环

  •  解释JavaScript中的Promise对象。

 答:我之前有写过详细的篇章,一文读懂Promise

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值