【面试题】JavaScript基础高频面试(上)

 1、简述JavaScript中map和foreach的区别?

`map`和`forEach`都是JavaScript数组的迭代方法,但它们之间存在一些关键区别。

1. 返回值:`map`方法会返回一个新的数组,这个新数组是由原数组通过某个函数处理后的结果组成的。而`forEach`方法则没有返回值,它仅仅是对数组中的每个元素执行一次给定的函数。

2. 修改原数组:`forEach`方法可以直接修改原数组,而`map`方法则不会改变原数组,而是创建一个全新的数组。

3. 性能:在大多数现代浏览器中,`map`的性能通常优于`forEach`。

让我们来看一个例子:

```javascript
let arr = [1, 2, 3, 4, 5];

// 使用map
let mapResult = arr.map(num => num * 2);
console.log(mapResult); // 输出:[2, 4, 6, 8, 10]
console.log(arr); // 输出:[1, 2, 3, 4, 5]

// 使用forEach
let forEachResult = [];
arr.forEach(num => forEachResult.push(num * 2));
console.log(forEachResult); // 输出:[2, 4, 6, 8, 10]
console.log(arr); // 输出:[1, 2, 3, 4, 5]
```

在这个例子中,我们可以看到,尽管`map`和`forEach`都可以对数组中的每个元素执行同样的操作(乘以2),但`map`返回了一个新的数组,原数组并没有改变;而`forEach`则没有返回值,我们需要创建一个新的数组来存储结果。同时,原数组在`forEach`操作后也没有发生改变。

2、解释下JavaScript中this是如何工作的?

在 JavaScript 中,`this`关键字是一个特殊的变量,它在每个函数作用域内都有定义。它的值取决于函数如何被调用。

1. **全局作用域或函数调用:** 在全局作用域或者普通函数调用中,`this`指向全局对象,也就是`window`(在浏览器中)或者`global`(在Node.js中)。

```javascript
console.log(this); // 输出:Window {...}

function test() {
  console.log(this);
}
test(); // 输出:Window {...}
```

2. **作为对象方法调用:** 当函数作为对象的一个方法被调用时,`this`指向这个对象。

```javascript
const obj = {
  name: 'Alice',
  sayHello: function() {
    console.log(this.name);
  }
};
obj.sayHello(); // 输出:Alice
```

3. **作为构造函数调用:** 当使用`new`关键字调用函数时,`this`指向新创建的对象。

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

let alice = new Person('Alice');
console.log(alice.name); // 输出:Alice
```

4. **在事件处理函数中:** 在 DOM 事件处理函数中,`this`通常指向触发事件的元素。

```javascript
button.addEventListener('click', function() {
  console.log(this); // 输出:触发点击事件的button元素
});
```

5. **箭头函数:** 箭头函数没有自己的`this`,它会捕获其所在(即定义的位置)上下文的`this`值。

```javascript
const obj = {
  name: 'Alice',
  sayHello: function() {
    setTimeout(() => {
      console.log(this.name); // 输出:Alice
    }, 1000);
  }
};
obj.sayHello();
```

6. **使用call,apply,bind调用:** 使用`call`,`apply`或`bind`方法,可以设置函数运行时的`this`值。

```javascript
function greet() {
  console.log(`Hello, ${this.name}`);
}

const alice = { name: 'Alice' };
const bob = { name: 'Bob' };

greet.call(alice); // 输出:Hello, Alice
greet.call(bob); // 输出:Hello, Bob
```

总的来说,`this`的值是在函数被调用时确定的,而不是在函数被定义时确定。这就是 JavaScript 中的动态作用域。

3、JavaScript阻止事件冒泡的方法?

在JavaScript中,阻止事件冒泡可以使用事件对象的`stopPropagation`方法。当事件发生时,浏览器会创建一个事件对象,这个对象包含了与事件相关的各种信息和方法,其中就包括`stopPropagation`方法。

这个方法可以阻止当前事件继续向上层元素传播,也就是停止事件冒泡。

我们来看一个例子:

```javascript
document.querySelector("#child").addEventListener('click', function(event) {
  event.stopPropagation();
  console.log("Child element clicked!");
});

document.querySelector("#parent").addEventListener('click', function() {
  console.log("Parent element clicked!");
});
```

在这个例子中,当你点击ID为`child`的元素时,浏览器会首先执行该元素的点击事件处理函数,然后因为我们调用了`event.stopPropagation()`,事件就不会继续向上冒泡到父元素,也就是ID为`parent`的元素。所以,你只会看到控制台打印出"Child element clicked!",而不会看到"Parent element clicked!"。

需要注意的是,`stopPropagation`只能阻止事件向上冒泡,但不能阻止其他同级事件监听器的执行。如果你希望完全阻止事件的进一步传播,包括阻止其他同级事件监听器的执行,你可以使用`event.stopImmediatePropagation()`方法。

4、JavaScript阻止默认事件?

在JavaScript中,阻止默认事件可以使用事件对象的`preventDefault`方法。很多浏览器的事件都有默认的行为,例如点击链接会跳转到新的页面,提交表单会刷新页面等。如果我们不希望触发这些默认行为,就可以使用`preventDefault`方法。

下面是一个例子,展示了如何阻止链接的默认跳转行为:

```javascript
document.querySelector("a").addEventListener('click', function(event) {
  event.preventDefault();
  console.log("Link clicked, but default action is prevented.");
});
```

在这个例子中,当你点击链接时,浏览器会首先执行链接的点击事件处理函数。然后因为我们调用了`event.preventDefault()`,链接的默认跳转行为就被阻止了。所以,你会看到控制台打印出"Link clicked, but default action is prevented.",但页面并不会跳转到链接的目标地址。

需要注意的是,不是所有的事件都有默认行为,只有部分事件才有。对于没有默认行为的事件,调用`preventDefault`方法没有任何效果。另外,一些事件的默认行为无法被取消,例如页面的unload事件。对于这些事件,调用`preventDefault`方法也没有任何效果。

5、简述 Javascript 盒子模型?

在 Web 开发中,CSS 盒模型是用来布局和设计的基本概念。在 CSS 盒模型中,每个元素都被视为一个矩形的盒子,这个盒子具有宽度、高度、边距、填充和边框。

盒模型主要包含四个部分:

1. **内容(Content):** 这是盒子里面的实际内容,如文本、图片等。其尺寸可以通过 `width` 和 `height` 属性来设置。

2. **内边距(Padding):** 内边距是内容周围的空白区域,它清晰地隔离了内容和边框。内边距的大小可以通过 `padding` 属性来设置。

3. **边框(Border):** 边框就像是盒子的外壳,它包围了内容和内边距。边框的大小和样式可以通过 `border` 属性来设置。

4. **外边距(Margin):** 外边距是盒子和其他元素之间的空白区域。它在边框的外面,用来隔离盒子和其他元素。外边距的大小可以通过 `margin` 属性来设置。

在 CSS 中,盒模型有两种:标准盒模型和IE盒模型。

- **标准盒模型:** 在这个模型中,`width` 和 `height` 指的是内容区的宽度和高度,而不包括内边距、边框和外边距。总的盒子大小计算公式为:`总宽度 = width + padding-left + padding-right + border-left + border-right + margin-left + margin-right`,高度同理。

- **IE盒模型:** 在这个模型中,`width` 和 `height` 指的是内容区、内边距和边框的总宽度和高度。外边距不包括在内。总的盒子大小计算公式为:`总宽度 = width + margin-left + margin-right`,高度同理。

可以使用 CSS 的 `box-sizing` 属性来选择使用哪种盒模型,`content-box` 为标准盒模型,`border-box` 为IE盒模型。

6、Javascipt中async await 和promise和generator有什么区别?

`async/await`、`Promise` 和 `generator` 都是 JavaScript 中用于处理异步操作的工具,但它们的使用方式和机制各有不同。

**Promise:** Promise 是 JavaScript 中处理异步操作的一个对象。它有三种状态:pending(待定)、fulfilled(已成功)和 rejected(已失败)。Promise 在创建时处于 pending 状态,然后可能转换为 fulfilled 或 rejected 状态。一旦状态改变,就不能再次改变。Promise 可以使用 `then` 和 `catch` 方法来处理成功或失败的结果。

```javascript
let promise = new Promise((resolve, reject) => {
  // 异步操作
  if (/* 成功 */) {
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(value => {
  // 处理成功的结果
}).catch(error => {
  // 处理错误
});
```

**Generator:** Generator 是 ES6 引入的一种新的函数类型,它可以在执行过程中被暂停和恢复。Generator 使用 `yield` 关键字来暂停函数的执行,使用 `next` 方法来恢复执行。Generator 可以用于处理异步操作,但需要配合其他工具(如 Promise)使用。

```javascript
function* myGenerator() {
  const data = yield doAsyncOperation();
  console.log(data);
}

const generator = myGenerator();
generator.next().value.then(data => {
  generator.next(data);
});
```

**Async/Await:** Async/Await 是 ES7 引入的一种新的处理异步操作的方式,它是基于 Promise 的。Async/Await 使用 `async` 和 `await` 关键字,使得异步代码看起来像同步代码。这大大提高了代码的可读性和可维护性。

```javascript
async function myAsyncFunction() {
  try {
    const data = await doAsyncOperation();
    console.log(data);
  } catch (error) {
    // 处理错误
  }
}

myAsyncFunction();
```

总的来说,Promise 提供了一种在异步操作完成后处理结果的方式,Generator 提供了一种可以暂停和恢复的函数,而 Async/Await 则提供了一种更简洁、更直观的处理异步操作的方式。

 7、JavaScript中手写promise ?

在 JavaScript 中,Promise 是一个代表了异步操作最终完成或者失败的对象。我们可以手动创建一个 Promise,如下所示:

```javascript
function MyPromise(executor) {
  let self = this;
  self.status = 'pending'; // Promise 当前的状态
  self.value = undefined; // Promise 的值
  self.reason = undefined; // Promise 失败的原因

  // 成功
  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'fulfilled';
      self.value = value;
    }
  }

  // 失败
  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected';
      self.reason = reason;
    }
  }

  try {
    executor(resolve, reject);
  } catch (error) {
    reject(error);
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let self = this;
  if (self.status === 'fulfilled') {
    onFulfilled(self.value);
  }
  if (self.status === 'rejected') {
    onRejected(self.reason);
  }
}
```

这是一个最基础的 Promise 实现,它包含了 Promise 的基本逻辑。但是,这个实现还缺少很多真正的 Promise 具备的功能,比如链式调用、异步支持、错误处理、`catch` 方法、`Promise.all` 方法、`Promise.race` 方法等等。

在实际开发中,我们通常使用内置的 Promise 对象,因为它已经实现了完整的功能,并且经过了大量的测试和优化。

8、JavaScript中promise.all作用?

`Promise.all` 是 JavaScript 中的一个 Promise 方法,它接收一个 Promise 对象的数组作为参数,返回一个新的 Promise 对象。这个新的 Promise 对象只有在所有的 Promise 对象都成功地完成(fulfilled)时才会完成,如果有任何一个 Promise 对象失败(rejected)了,那么新的 Promise 对象会立即失败。

在所有的 Promise 对象都成功完成时,新的 Promise 对象的结果会是一个数组,这个数组包含了每个 Promise 对象的结果。这些结果的顺序和原来的 Promise 对象的顺序一致。

如果有任何一个 Promise 对象失败了,新的 Promise 对象的结果会是那个失败的 Promise 对象的结果。

下面是一个例子:

```javascript
let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // 输出:[3, 42, "foo"]
});
```

在这个例子中,`Promise.all` 接收了三个 Promise 对象,当这三个 Promise 对象都成功完成时,它返回的 Promise 对象也成功完成,结果是一个包含了每个 Promise 对象结果的数组。

`Promise.all` 在处理多个相互独立的异步操作,并且需要等待所有异步操作都完成时非常有用。

9、Javascript 浅拷贝/深度拷贝的区别?

在 JavaScript 中,浅拷贝和深拷贝都是用来复制对象的,但它们复制的深度不同。

**浅拷贝(Shallow Copy):** 浅拷贝只复制对象的顶层属性。如果对象的属性值是基本类型(如数字、字符串、布尔值),那么就直接复制这个值;如果属性值是引用类型(如对象、数组),那么复制的是这个值的引用,而不是实际的对象或数组。这就意味着,如果你修改了新对象的一个引用类型的属性,那么原对象的对应属性也会被修改。

```javascript
let obj1 = { a: 1, b: [1, 2, 3] };
let obj2 = {...obj1};
obj2.b.push(4);
console.log(obj1.b); // 输出:[1, 2, 3, 4]
```

在这个例子中,我们使用了对象扩展运算符(...)来创建一个新的对象,这是一种浅拷贝的方式。当我们修改了新对象的 b 属性时,原对象的 b 属性也被修改了。

**深拷贝(Deep Copy):** 深拷贝不仅复制对象的顶层属性,还会递归地复制所有的子属性。无论属性值是基本类型还是引用类型,都会创建一个新的副本。这就意味着,新对象和原对象完全独立,修改其中一个不会影响另一个。

```javascript
let obj1 = { a: 1, b: [1, 2, 3] };
let obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.push(4);
console.log(obj1.b); // 输出:[1, 2, 3]
```

在这个例子中,我们使用了 `JSON.stringify` 和 `JSON.parse` 方法来创建一个新的对象,这是一种深拷贝的方式。当我们修改了新对象的 b 属性时,原对象的 b 属性没有被修改。

需要注意的是,使用 `JSON.stringify` 和 `JSON.parse` 方法进行深拷贝有一些限制,比如无法复制函数和循环引用的对象等。在实际开发中,我们通常会使用一些库(如 lodash)的深拷贝函数,因为这些函数已经处理了各种边缘情况。


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值