JavaScript每日面试题

闭包的理解

通过一个例子来理解闭包:

function outerFunction({
  let outerVariable = "I am from outer function";

  function innerFunction({
    console.log(outerVariable);
  }

  return innerFunction;
}

const closureFunction = outerFunction();
closureFunction(); // 输出 "I am from outer function"

在这个例子中,innerFunction 是一个闭包,因为它被定义在 outerFunction 内部,并且可以访问 outerFunction 中声明的变量 outerVariable。当 outerFunction 被调用时,它返回 innerFunction,然后我们将返回的函数赋给 closureFunction。最后,调用 closureFunction 时,它仍然能够访问 outerVariable,尽管 outerFunction 已经执行完毕。

闭包的主要特点有:

  1. 访问外部变量: 闭包可以访问其外部函数的变量,即使外部函数已经执行完毕。

  2. 保持词法作用域: 闭包会保持它被创建时的词法作用域,这意味着它可以访问其定义时所在的作用域中的变量。

  3. 存储状态: 闭包可以用于创建私有变量,因为它们可以在函数执行后继续存储状态。

闭包在实际开发中有许多用途,例如:

  • 封装私有变量: 使用闭包可以创建具有私有状态的对象。

  • 实现模块化: 通过闭包可以模拟私有和公共成员,实现一定程度的封装和模块化。

  • 处理异步操作: 在异步操作中,闭包可以用于捕获异步回调中的状态。

需要注意的是,过度使用闭包可能导致内存泄漏,因为闭包会保持对其包含作用域的引用。确保及时释放不再需要的闭包是很重要的。

原型和原型链

JavaScript 中的原型(prototype)和原型链(prototype chain)是理解对象和继承的关键概念。让我们逐步解释这两个概念:

1. 原型(Prototype):

每个 JavaScript 对象都有一个原型,它是一个指向另一个对象的引用。对象可以通过原型继承属性和方法。当你尝试访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会在对象的原型上查找。

示例:

// 创建一个对象
let myObject = {
  name"John",
  sayHellofunction({
    console.log("Hello, " + this.name + "!");
  }
};

// 使用原型创建另一个对象
let anotherObject = Object.create(myObject);
anotherObject.age = 25;

// 原型链:anotherObject -> myObject
anotherObject.sayHello(); // 输出 "Hello, John!"

在这个例子中,anotherObject 继承了 myObject 的属性和方法。原型链的形成使得 anotherObject 可以访问 myObjectsayHello 方法。

2. 原型链(Prototype Chain):

原型链是由对象的原型构成的链式结构。当试图访问一个对象的属性或方法时,JavaScript 引擎首先查找对象本身是否有该属性或方法。如果没有,它就会继续在对象的原型上查找,以此类推,直到找到或者到达原型链的末尾(Object.prototype)。

示例:

// 创建一个构造函数
function Person(name{
  this.name = name;
}

// 在构造函数的原型上添加方法
Person.prototype.sayHello = function({
  console.log("Hello, " + this.name + "!");
};

// 创建一个 Person 对象
let person1 = new Person("Alice");

// 原型链:person1 -> Person.prototype -> Object.prototype
person1.sayHello(); // 输出 "Hello, Alice!"

在这个例子中,Person.prototype 是一个包含 sayHello 方法的对象,而 person1 是通过 Person 构造函数创建的对象。当调用 person1.sayHello() 时,JavaScript 引擎会沿着原型链找到 sayHello 方法。

export 和 export default的区别

在 JavaScript 模块中,exportexport default 是两种导出方式,用于将模块中的功能暴露给其他模块。

1. export:

  • export 用于导出模块中的变量、函数、类等。
  • 一个模块可以有多个 export 语句。
  • 导入时需要使用相应的名称来引入。

示例:

// module.js
export const variable1 = 10;
export function add(a, b{
  return a + b;
}
// anotherModule.js
import { variable1, add } from './module';
console.log(variable1); // 输出 10
console.log(add(57));  // 输出 12

2. export default:

  • export default 用于导出模块中的默认功能。
  • 一个模块只能有一个 export default
  • 导入时可以使用任意名称,因为它是默认导出的。

示例:

// module.js
const variable1 = 10;
function add(a, b{
  return a + b;
}

export default { variable1, add };
// anotherModule.js
import myModule from './module';
console.log(myModule.variable1); // 输出 10
console.log(myModule.add(57));  // 输出 12

区别总结:

  • 使用 export 导出的内容需要使用相应的名称进行引入,而 export default 导出的内容可以使用任意名称。
  • 一个模块可以有多个 export,但只能有一个 export default
  • 在导入时,export 导出的内容需要用花括号 {} 包裹,而 export default 导出的内容则不需要。

箭头函数和普通函数的区别

箭头函数和普通函数(通常称为函数表达式或函数声明)在 JavaScript 中有一些重要的区别。以下是它们的主要差异:

1. 语法形式:

  • 箭头函数:
const add = (a, b) => a + b;
  • 普通函数:

    函数表达式:

const add = function(a, b{
  return a + b;
};

函数声明:

function add(a, b{
  return a + b;
}

2. this 的值:

  • 箭头函数:

    箭头函数没有自己的 this,它继承自外部作用域的 this 值。

const obj = {
  value42,
  getValuefunction({
    return () => this.value;
  }
};

const getter = obj.getValue();
console.log(getter()); // 输出 42
  • 普通函数:

    普通函数的 this 值由调用方式决定,通常是调用时的对象或者全局对象(在非严格模式下)。

const obj = {
  value42,
  getValuefunction({
    return function({
      return this.value;
    };
  }
};

const getter = obj.getValue();
console.log(getter()); // 输出 undefined 或者全局对象的值(在非严格模式下)

3. arguments 对象:

  • 箭头函数:

    箭头函数没有自己的 arguments 对象,它会继承外部作用域的 arguments

  • 普通函数:

    普通函数有自己的 arguments 对象,其中包含传递给函数的参数。

4. 构造函数:

  • 箭头函数:

    不能用作构造函数,不能使用 new 关键字调用。

  • 普通函数:

    可以作为构造函数,使用 new 关键字调用。

// 普通函数作为构造函数
function Person(name{
  this.name = name;
}

const person = new Person("John");

5. 返回值:

  • 箭头函数:

    如果箭头函数只有一条语句,且没有花括号,那么该语句的结果会被隐式地作为箭头函数的返回值。

  • 普通函数:

    需要使用 return 明确指定返回值。

// 箭头函数
const add = (a, b) => a + b;

// 普通函数
function add(a, b{
  return a + b;
}

选择使用箭头函数还是普通函数取决于具体的使用场景和需求。箭头函数通常更适合简短的函数,而普通函数则更灵活,可以用于更复杂的情况。

forEach 和 map 的区别

forEachmap 都是 JavaScript 数组提供的迭代方法,但它们在用法和返回值上有一些关键的区别。

1. 返回值:

  • forEach:

    forEach 没有返回值,它只是用来遍历数组,执行提供的回调函数。

const array = [123];

array.forEach(item => {
  console.log(item);
});
  • map:

    map 返回一个新数组,新数组的元素是对原数组中每个元素调用回调函数的结果。

const array = [123];

const newArray = array.map(item => {
  return item * 2;
});

console.log(newArray); // 输出 [2, 4, 6]

2. 修改原数组:

  • forEach:

    forEach 不会修改原数组,它只是用于遍历。

const array = [123];

array.forEach(item => {
console.log(item); // 输出 1, 2, 3
});

console.log(array); // 输出 [1, 2, 3]
  • map:

    map 创建一个新数组,不会修改原数组。

const array = [123];

const newArray = array.map(item => {
return item * 2;
});

console.log(newArray); // 输出 [2, 4, 6]
console.log(array); // 输出 [1, 2, 3]

3. 使用场景:

  • forEach:

    适用于需要遍历数组但不需要生成新数组的情况,例如在循环中执行一些操作。

  • map:

    适用于需要生成新数组,且新数组的元素是对原数组进行某种转换的情况。例如,对数组中的每个元素进行加倍并返回一个新数组。

promise 和 async/await 的区别

Promiseasync/await 都是用于处理 JavaScript 异步编程的机制,但它们在语法和使用上有一些重要的区别。

1. 语法:

  • Promise:
const myPromise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
  resolve(result);
else {
  reject(error);
}
});

myPromise
.then(result => {
  // 处理成功的结果
})
.catch(error => {
  // 处理错误
});
  • async/await:
async function myAsyncFunction({
try {
  // 异步操作
  const result = await myPromise;
  // 处理成功的结果
catch (error) {
  // 处理错误
}
}

myAsyncFunction();

2. 返回值:

  • Promise:

    Promisethencatch 方法返回新的 Promise 对象,使得可以链式调用。

myPromise
.then(result => {
  // 处理成功的结果
  return anotherPromise;
})
.then(anotherResult => {
  // 处理另一个成功的结果
})
.catch(error => {
  // 处理错误
});
  • async/await:

    async/await 使用 await 关键字等待一个 Promise 对象解决,并返回其解决值。

async function myAsyncFunction({
try {
  const result = await myPromise;
  const anotherResult = await anotherPromise;
  // 处理成功的结果
catch (error) {
  // 处理错误
}
}

3. 错误处理:

  • Promise:

    使用 catch 方法或在 then 方法链的末尾添加错误处理回调来处理错误。

  • async/await:

    使用 try/catch 块来捕获错误,更类似于同步代码的异常处理。

4. 适用性:

  • Promise:

    适用于处理多个并行异步操作,使用 Promise.allPromise.race

  • async/await:

    更适用于处理依赖关系的异步操作,可以使用同步的方式编写异步代码,更易读。

5. 主要区别:

  • async/await 是基于 Promise 的,是一种更高级、更清晰的语法糖,使异步代码看起来更像同步代码。

  • async/await 更易读,尤其是在处理多个异步操作的情况下。

  • async/await 代码通常比使用 Promise 更简洁,更容易理解。

new操作符的作用

new 操作符在 JavaScript 中用于创建一个新的对象实例。它执行以下步骤:

  1. 创建一个新对象: 一个空的对象被创建,并且该对象被赋值给 this 关键字。

  2. 链接到原型: 新对象被链接到构造函数的原型对象(prototype)。这意味着新对象可以访问构造函数原型对象上定义的属性和方法。

  3. 绑定 this: 构造函数内部的 this 关键字被设置为新创建的对象。这使得构造函数可以访问并操作新对象。

  4. 执行构造函数的代码: 构造函数内部的代码被执行,可以对新对象进行初始化操作。

  5. 返回新对象: 如果构造函数内部没有显式返回一个对象,则会默认返回新创建的对象。

下面是一个简单的示例:

function Person(name, age{
  this.name = name;
  this.age = age;
}

// 使用 new 操作符创建一个新的 Person 对象
const john = new Person("John"30);

console.log(john.name); // 输出 "John"
console.log(john.age);  // 输出 30

在这个例子中,new Person("John", 30) 创建了一个新的 Person 对象,并通过构造函数初始化了 nameage 属性。新对象 john 继承了 Person 构造函数的属性和方法。

需要注意的是,使用 new 操作符来调用函数时,如果函数内部没有显式返回一个对象,那么将返回新创建的对象;如果函数内部返回一个对象,那么返回的就是该对象,而不是新创建的对象。

事件捕获和事件冒泡

事件捕获和事件冒泡是指在 DOM 结构中处理事件的两个不同的阶段。当一个事件发生时,它会经历捕获阶段和冒泡阶段。

1. 事件捕获(Capture Phase):

在事件捕获阶段,事件从最外层的元素向目标元素传播。即从 window(或文档)到目标元素。

window -> document -> <html> -> <body> -> ... -> 目标元素

2. 事件冒泡(Bubbling Phase):

在事件冒泡阶段,事件从目标元素向最外层的元素传播。即从目标元素到 window(或文档)。

目标元素 -> ... -> <body> -> <html> -> document -> window

3. 阻止事件传播:

在事件处理函数中,可以使用 stopPropagation 方法来阻止事件的传播。如果在捕获阶段调用 stopPropagation,事件将停止在目标元素之前的传播;如果在冒泡阶段调用,事件将停止在目标元素之后的传播。

element.addEventListener('click'function(event{
  event.stopPropagation(); // 阻止事件传播
  // 具体处理事件的代码
});

4. 使用场景:

  • 事件捕获:

    • 通常用于在事件到达实际目标之前捕获它,可以在事件到达目标前执行一些处理。
    • 在一些特殊场景下使用,不常见。
  • 事件冒泡:

    • 是默认的事件处理阶段,大多数情况下都使用事件冒泡来处理事件。
    • 允许更容易地在 DOM 层次结构中找到目标元素的父级,并处理事件。

5. 示例:

<!DOCTYPE html>
<html>
<head>
  <title>Event Capture and Bubbling</title>
</head>
<body>

  <div id="outer" style="border: 2px solid red; padding: 10px;">
    <div id="inner" style="border: 2px solid blue; padding: 10px;">
      Click me
    </div>
  </div>

  <script>
    const outer = document.getElementById('outer');
    const inner = document.getElementById('inner');

    outer.addEventListener('click'function({
      console.log('Outer Div Capturing');
    }, true);

    inner.addEventListener('click'function({
      console.log('Inner Div Capturing');
    }, true);

    outer.addEventListener('click'function({
      console.log('Outer Div Bubbling');
    });

    inner.addEventListener('click'function({
      console.log('Inner Div Bubbling');
    });
  
</script>

</body>
</html>

在这个例子中,点击 “Click me” 文字时,首先会捕获阶段触发捕获事件,然后冒泡阶段触发冒泡事件。在控制台中会看到对应的输出。如果在捕获阶段或冒泡阶段的事件处理函数中使用 stopPropagation,可以看到不同的输出。

判断变量的类型几种方法

在 JavaScript 中,可以使用不同的方法来判断变量的类型。以下是几种常见的方法:

1. typeof 操作符:

typeof 操作符用于检测变量的数据类型。

let x = 42;
console.log(typeof x); // 输出 "number"

let y = "Hello";
console.log(typeof y); // 输出 "string"

let z = true;
console.log(typeof z); // 输出 "boolean"

typeof 返回的结果是一个字符串,表示变量的类型。

2. instanceof 操作符:

instanceof 操作符用于检测对象的类型,可以用于检测自定义对象的类型。

let arr = [123];
console.log(arr instanceof Array); // 输出 true

let obj = { key"value" };
console.log(obj instanceof Object); // 输出 true

3. Object.prototype.toString 方法:

通过调用 Object.prototype.toString 方法,可以获取更具体的对象类型信息。

let str = "Hello";
console.log(Object.prototype.toString.call(str)); // 输出 "[object String]"

let num = 42;
console.log(Object.prototype.toString.call(num)); // 输出 "[object Number]"

let bool = true;
console.log(Object.prototype.toString.call(bool)); // 输出 "[object Boolean]"

let arr = [123];
console.log(Object.prototype.toString.call(arr)); // 输出 "[object Array]"

4. Array.isArray 方法:

Array.isArray 方法用于检测一个对象是否是数组。

let arr = [123];
console.log(Array.isArray(arr)); // 输出 true

let notArr = "Not an array";
console.log(Array.isArray(notArr)); // 输出 false

5. 使用 typeof=== 结合:

有时候,typeof 的结果并不能很好地区分一些对象,例如 null 和数组。在这种情况下,可以结合 typeof=== 使用。

let value = null;

if (typeof value === 'object' && value !== null) {
  console.log('It is an object');
else if (Array.isArray(value)) {
  console.log('It is an array');
else {
  console.log('It is of another type');
}

如果判断一个对象是否存在

如果你想判断一个对象是否存在,你可以使用以下几种方式:

1. 使用 if 语句:

let myObject = { key"value" };

if (myObject) {
  // 对象存在
  console.log("Object exists");
else {
  // 对象不存在
  console.log("Object does not exist");
}

这种方式在大多数情况下是有效的,因为如果对象存在,条件为真;如果对象为 nullundefined,条件为假。

2. 使用 typeof

let myObject = { key"value" };

if (typeof myObject !== 'undefined' && myObject !== null) {
  // 对象存在
  console.log("Object exists");
else {
  // 对象不存在
  console.log("Object does not exist");
}

通过使用 typeof 进行明确的判断,可以防止在使用对象属性时遇到意外的错误,特别是当对象未定义或为 null 时。

3. 使用可选链操作符 ?.(ES2020):

let myObject = { key"value" };

if (myObject?.key) {
  // 对象存在且具有属性 "key"
  console.log("Object exists and has key property");
else {
  // 对象不存在或者没有 "key" 属性
  console.log("Object does not exist or does not have key property");
}

可选链操作符 ?. 可以在对象链中的任何点上停止并返回 undefined,而不会引发错误。


原文始发于微信公众号(前端大大大):JavaScript每日面试题

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/174083.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!