暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

70 个经典的 JS 面试题(五)

前端新世界 2022-02-16
1094
喜欢就关注我们吧


本文翻译自:
https://dev.to/macmacky/70-javascript-interview-questions-5gfi

PS:)文章有点长,有耐心看完的请点个赞,或者留言给点建议;没空看的可以先收藏。

前面我们分享了 70 个 JavaScript 面试题的前四个部分,没有阅读过的朋友可以先看看:

接下来我们分享第五部分,这部分主要涉及很多ES6的新功能,箭头函数、模板字面量等等。

41. ES6或ECMAScript 2015中有哪些新功能?

  • 箭头函数
  • 模板字符串
  • 增强对象文字
  • 对象解构
  • Promises
  • 生成器
  • 模组
  • 符号
  • 代理
  • Sets
  • 默认功能参数
  • Rest和Spread
  • 使用let
    const
    的块作用域

42. var,let和const关键字有什么区别?

var
关键字声明的变量是函数作用域的。这意味着即使我们在块内声明变量,也可以在整个函数访问该变量。

function giveMeX(showX{
  if (showX) {
    var x = 5;
  }
  return x;
}

console.log(giveMeX(false));
console.log(giveMeX(true));

第一个console.log
语句打印undefined
,而第二个打印5
。我们可以访问x
变量,是因为它被提升到了函数作用域的顶部。所以代码可以解析为这样:

function giveMeX(showX{
  var x; // has a default value of undefined
  if (showX) {
    x = 5;
  }
  return x;
}

如果你想知道为什么程序在第一个console.log
语句中打印undefined
,请记住,声明的变量没有初始值的话,其默认值为undefined

let
const
关键字声明的变量是块级作用域的。这意味着变量只能在被声明的那个块{}
中访问。

function giveMeX(showX{
  if (showX) {
    let x = 5;
  }
  return x;
}


function giveMeY(showY{
  if (showY) {
    let y = 5;
  }
  return y;
}

如果我们传入参数false
调用此函数,则会抛出Reference Error
,因为我们无法访问该块外部的x
y
变量,并且这些变量不会被提升。

let
const
之间也有区别,我们可以使用let
分配新的值,但不能使用const
,但是const
是可变的。这意味着如果我们分配给const
的值是一个对象,那么我们只能更改对象属性的值,但不能重新分配新的值给此变量。

43. 何为箭头函数?

箭头函数是在JavaScript中创建函数的新方法。用箭头函数创建函数花费的时间很少,并且语法比函数表达式更简洁,因为我们在创建函数时省略了function
关键字。

//ES5 Version
var getCurrentDate = function (){
  return new Date();
}

//ES6 Version
const getCurrentDate = () => new Date();

在此示例中,ES5版本中,分别需要function(){}
声明和return
关键字以创建函数和返回值。在箭头函数版本中,我们只需要括号()
,且无需return
语句——因为如果只返回一个表达式或值,则箭头函数会隐式返回。

//ES5 Version
function greet(name{
  return 'Hello ' + name + '!';
}

//ES6 Version
const greet = (name) => `Hello ${name}`;
const greet2 = name => `Hello ${name}`;

箭头函数中的参数可以与函数表达式和函数声明相同。如果箭头函数中只有一个参数,那么我们可以省略括号,所以上面这两种写法是等效的。

const getArgs = () => arguments

const getArgs2 = (...rest) => rest

箭头函数无权访问arguments
对象。因此,调用第一个getArgs
函数将引发错误。相反,我们可以使用rest
参数来获取箭头函数中传递的所有参数。

const data = {
  result0,
  nums: [12345],
  computeResult() {
    // "this" here refers to the "data" object
    const addAll = () => {
      // arrow functions "copies" the "this" value of 
      // the lexical enclosing function
      return this.nums.reduce((total, cur) => total + cur, 0)
    };
    this.result = addAll();
  }
};

箭头函数没有自己的this
值。它捕获和获取的是闭包函数的this
值,或者在此示例中,addAll
函数复制executeResult
方法的this
值,并且如果我们在全局作用域内声明箭头函数,则this
的值将为window
对象。

44. 什么是类?

类是用JavaScript编写构造函数的一个新方法。它是使用构造函数的语法糖,且仍然在底层使用原型和基于原型的继承。

//ES5 Version
function Person(firstName, lastName, age, address{
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.address = address;
}

Person.self = function ({
    return this;
}

Person.prototype.toString = function ({
    return "[object Person]";
}

Person.prototype.getFullName = function ({
    return this.firstName + " " + this.lastName;
}

//ES6 Version
class Person {
    constructor(firstName, lastName, age, address) {
        this.lastName = lastName;
        this.firstName = firstName;
        this.age = age;
        this.address = address;
    }

    static self() {
        return this;
    }

    toString() {
        return "[object Person]";
    }

    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

重写方法并从另一个类继承。

//ES5 Version
Employee.prototype = Object.create(Person.prototype);

function Employee(firstName, lastName, age, address, jobTitle, yearStarted{
  Person.call(this, firstName, lastName, age, address);
  this.jobTitle = jobTitle;
  this.yearStarted = yearStarted;
}

Employee.prototype.describe = function ({
  return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
}

Employee.prototype.toString = function ({
  return "[object Employee]";
}

//ES6 Version
class Employee extends Person //Inherits from "Person" class
  constructor(firstName, lastName, age, address, jobTitle, yearStarted) {
    super(firstName, lastName, age, address);
    this.jobTitle = jobTitle;
    this.yearStarted = yearStarted;
  }

  describe() {
    return `I am ${this.getFullName()} and I have a position of ${this.jobTitle} and I started at ${this.yearStarted}`;
  }

  toString() { // Overriding the "toString" method of "Person"
    return "[object Employee]";
  }
}

那么我们怎么知道程序在底层使用原型呢?

class Something {

}

function AnotherSomething({

}
const as = new AnotherSomething();
const s = new Something();

console.log(typeof Something); // logs "function"
console.log(typeof AnotherSomething); // logs "function"
console.log(as.toString()); // logs "[object Object]"
console.log(as.toString()); // logs "[object Object]"
console.log(as.toString === Object.prototype.toString);
console.log(s.toString === Object.prototype.toString);
// both logs return true indicating that we are still using 
// prototypes under the hoods because the Object.prototype is
// the last part of the Prototype Chain and "Something"
// and "AnotherSomething" both inherit from Object.prototype

45. 什么是模板字面量?

模板字面量是使用JavaScript创建字符串的新方法。我们可以使用反引号或后引号实现模板字面量。

//ES5 Version
var greet = 'Hi I\'m Mark';

//ES6 Version
let greet = `Hi I'm Mark`;

在ES5版本中,我们需要使用 \
转义 '
以转义该符号的正常功能,而在这种情况下,即完成该字符串值。在模板字面量中,我们不需要这样做。

//ES5 Version
var lastWords = '\n'
  + '   I  \n'
  + '   Am  \n'
  + 'Iron Man \n';


//ES6 Version
let lastWords = `
    I
    Am
  Iron Man   
`
;

在ES5版本中,我们需要添加\n
以在字符串中添加新的行。而在模板字面量中,我们也不需要这样做。

//ES5 Version
function greet(name{
  return 'Hello ' + name + '!';
}


//ES6 Version
const greet = name => {
  return `Hello ${name} !`;
}

在ES5版本中,如果需要在字符串中添加表达式或值,则需要使用+
或字符串串联运算符。在模板字面量中,我们可以使用${expr}
嵌入一个表达式,这使其比ES5版本更整洁。

46. 什么是对象解构?

对象解构是一种从对象或数组中获取或提取值的新方法。

假设我们有一个这样的对象。

const employee = {
  firstName"Marko",
  lastName"Polo",
  position"Software Developer",
  yearHired2017
};

从对象获取属性的老方法是我们创建一个与对象属性同名的变量。这种方式很麻烦,因为我们要为每个属性都创建一个新变量。假设我们有一个具有许多属性的大型对象,那么使用这种方法来提取属性的方法将会令人厌烦。

var firstName = employee.firstName;
var lastName = employee.lastName;
var position = employee.position;
var yearHired = employee.yearHired;

如果我们使用对象解构,代码就会干净多了,并且比老的方法所需要的时间少一些。对象解构的语法是,如果要在对象中获取属性,则使用{}
,在{}
中,指定需要提取的属性,而如果要从数组中获取数据,则使用[]

let { firstName, lastName, position, yearHired } = employee;

如果要更改需要提取的变量名,则使用propertyName:newName
语法。在此示例中,fName
变量的值将保留firstName
属性的值,而lName
变量将保留lastName
属性的值。

let { firstName: fName, lastName: lName, position, yearHired } = employee;

在解构时我们也可以使用默认值。在此示例中,如果firstName
属性在对象中包含值undefined
,则当我们解构时,firstName
变量将保留默认值”Mark
”。

let { firstName = "Mark"lastName: lName, position, yearHired } = employee;

47. 什么是ES6模块?

模块使得我们可以将代码库拆分为多个文件,以实现更高的可维护性——可以避免我们将所有代码都放在一个大文件中。在ES6支持模块之前,曾有两种非常流行的模块系统用于支持JavaScript中的代码可维护性。

  • CommonJS-Node.js
  • AMD(Asynchronous Module Definition异步模块定义)—浏览器

基本上,使用模块的语法很简单,import
用于从另一个文件或多个功能或多个值中获取功能,而export
用于从一个文件或多个功能或多个值中导出功能。

在文件中导出功能或Named Exports

使用ES5(CommonJS)

// Using ES5 CommonJS - helpers.js
exports.isNull = function (val{
  return val === null;
}

exports.isUndefined = function (val{
  return val === undefined;
}

exports.isNullOrUndefined = function (val{
  return exports.isNull(val) || exports.isUndefined(val);
}

使用ES6模块

// Using ES6 Modules - helpers.js
export function isNull(val){
  return val === null;
}

export function isUndefined(val{
  return val === undefined;
}

export function isNullOrUndefined(val{
  return isNull(val) || isUndefined(val);
}

在另一个文件中导入功能

// Using ES5 (CommonJS) - index.js
const helpers = require('./helpers.js'); // helpers is an object
const isNull = helpers.isNull;
const isUndefined = helpers.isUndefined;
const isNullOrUndefined = helpers.isNullOrUndefined;

// or if your environment supports Destructuring
const { isNull, isUndefined, isNullOrUndefined } = require('./helpers.js');

// ES6 Modules - index.js
import * as helpers from './helpers.js'// helpers is an object

// or 

import { isNull, isUndefined, isNullOrUndefined as isValid } from './helpers.js';

// using "as" for renaming named exports

在一个文件中导出单个功能或Default Exports

使用ES5(CommonJS)

// Using ES5 (CommonJS) - index.js
class Helpers {
  static isNull(val) {
    return val === null;
  }

  static isUndefined(val) {
    return val === undefined;
  }

  static isNullOrUndefined(val) {
    return this.isNull(val) || this.isUndefined(val);
  }
}

module.exports = Helpers;

使用ES6模块

// Using ES6 Modules - helpers.js
class Helpers {
  static isNull(val) {
    return val === null;
  }

  static isUndefined(val) {
    return val === undefined;
  }

  static isNullOrUndefined(val) {
    return this.isNull(val) || this.isUndefined(val);
  }
}

export default Helpers

从另一个文件导入单个功能

使用ES5(CommonJS)

// Using ES5 (CommonJS) - index.js
const Helpers = require('./helpers.js'); 
console.log(Helpers.isNull(null));

使用ES6模块

import Helpers from '.helpers.js'
console.log(Helpers.isNull(null));

这是使用ES6模块的基础。有关模块的内容就不多说了,因为这是一个广泛的话题,再扯开去我怕大家嫌文章太长不肯继续看下去了。

48. 什么是Set对象,以及其工作原理?

Set对象是一个ES6功能,可用于存储唯一值、基元或对象引用。集合中的值只能出现一次。使用SameValueZero
算法来检查集合对象中是否存在某个值。

我们可以使用Set构造函数创建Set实例,并且可以选择性地传递Iterable
用作初始值。

const set1 = new Set();
const set2 = new Set(["a","b","c","d","d","e"]);

我们可以使用add
方法将一个新的值添加到Set实例中,并且由于add
返回Set对象,因此我们可以链接add
调用。如果Set对象中已经存在了这个值,则不会再次添加该值。

set2.add("f");
set2.add("g").add("h").add("i").add("j").add("k").add("k");
// the last "k" will not be added to the set object because it already exists

我们可以使用delete
方法从Set实例中删除一个值,如果Set对象中已经存在这个值,则此方法返回一个true
,同样道理false
则表明该值不存在。

set2.delete("k"// returns true because "k" exists in the set object
set2.delete("z"// returns false because "z" does not exists in the set object

我们可以使用has
方法来检查Set实例中是否存在某个特定值。

set2.has("a"// returns true because "a" exists in the set object
set2.has("z"// returns false because "z" does not exists in the set object

我们可以使用size
属性获取Set实例的长度。

set2.size // returns 10

我们可以使用clear
方法来删除或移除Set实例中的所有元素。

set2.clear(); // clears the set data

我们可以使用Set对象删除数组中的重复元素。

const numbers = [12345667885];
const uniqueNums = [...new Set(numbers)]; // has a value of [1,2,3,4,5,6,7,8]

49. 什么是回调函数?

回调函数是将在以后的某个时间点被调用的函数。

const btnAdd = document.getElementById('btnAdd');

btnAdd.addEventListener('click'function clickCallback(e{
    // do something useless
});

在此示例中,我们等待ID为btnAdd
的元素中的click event
,如果clicked
,则将执行clickCallback
函数。回调函数添加了一些功能到某些数据或事件。数组中的reduce
filter
map
方法期望将回调函数作为参数。如果对回调打比方就是,当你call某个人时,如果对方没有应答,那么你留下一条消息希望对方callback。Call某人或留下消息的行为就是事件或数据,而callback就是你期望之后发生的行为。

50. 什么是Promises?

Promises是JavaScript中处理异步操作的一种方法。它代表异步操作的值。在使用回调之前,我们用Promises来解决执行和处理异步代码的问题。

fs.readFile('somefile.txt'function (e, data{
  if (e) {
    console.log(e);
  }
  console.log(data);
});

如果我们在回调内部有另一个异步操作,则此方法会出现问题。代码将变得混乱且不可读。此时的代码被称为“回调地狱”。

//Callback Hell yucksss
fs.readFile('somefile.txt'function (e, data{
  //your code here
  fs.readdir('directory'function (e, files{
    //your code here
    fs.mkdir('directory'function (e{
      //your code here
    })
  })
})

如果我们在此代码中使用promises,则代码会更具可读性、更易于理解和维护。

promReadFile('file/path')
  .then(data => {
    return promReaddir('directory');
  })
  .then(data => {
    return promMkdir('directory');
  })
  .catch(e => {
    console.log(e);
  })

Promises有4个不同的状态。

  • Pending——promises的初始状态。由于操作尚未完成,因此尚未得知promises的结果。
  • Fulfilled——异步操作已完成,并成功生成结果值。
  • Rejected——异步操作已失败,并给出失败原因。
  • Settled——说明promises已处于Fulfilled状态或Rejected状态两者之一。

Promise构造函数有两个参数,分别是函数resolve和reject。

如果异步操作已完成且没有错误,则调用resolve
函数来解决Promise,而如果发生错误,则调用reject
函数并将错误或原因传递给它。

我们可以使用.then
方法来访问fulfilled promise的结果,我们可以用.catch
方法中捕获错误。我们可以在.then
方法中链接多个异步promise操作,因为.then
方法返回Promise,就像上面的示例一样。

const myPromiseAsync = (...args) => {
  return new Promise((resolve, reject) => {
    doSomeAsync(...args, (error, data) => {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    })
  })
}

myPromiseAsync()
  .then(result => {
    console.log(result);
  })
  .catch(reason => {
    console.log(reason);
  })

我们可以创建一个辅助函数,让该函数将带有回调的异步操作转换为Promise。它的工作原理类似于从节点核心模块util promisify
实用程序函数。

const toPromise = (asyncFuncWithCallback) => {
  return (...args) => {
    return new Promise((res, rej) => {
      asyncFuncWithCallback(...args, (e, result) => {
        return e ? rej(e) : res(result);
      });
    });
  }
}

const promReadFile = toPromise(fs.readFile);

promReadFile('file/path')
  .then((data) => {
    console.log(data);
  })
  .catch(e => console.log(e));

(文本完)

每日分享前端插件干货,欢迎关注!

点赞和在看就是最大的支持❤️

文章转载自前端新世界,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论