闭包的理解
通过一个例子来理解闭包:
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
已经执行完毕。
闭包的主要特点有:
-
访问外部变量: 闭包可以访问其外部函数的变量,即使外部函数已经执行完毕。
-
保持词法作用域: 闭包会保持它被创建时的词法作用域,这意味着它可以访问其定义时所在的作用域中的变量。
-
存储状态: 闭包可以用于创建私有变量,因为它们可以在函数执行后继续存储状态。
闭包在实际开发中有许多用途,例如:
-
封装私有变量: 使用闭包可以创建具有私有状态的对象。
-
实现模块化: 通过闭包可以模拟私有和公共成员,实现一定程度的封装和模块化。
-
处理异步操作: 在异步操作中,闭包可以用于捕获异步回调中的状态。
需要注意的是,过度使用闭包可能导致内存泄漏,因为闭包会保持对其包含作用域的引用。确保及时释放不再需要的闭包是很重要的。
原型和原型链
JavaScript 中的原型(prototype)和原型链(prototype chain)是理解对象和继承的关键概念。让我们逐步解释这两个概念:
1. 原型(Prototype):
每个 JavaScript 对象都有一个原型,它是一个指向另一个对象的引用。对象可以通过原型继承属性和方法。当你尝试访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会在对象的原型上查找。
示例:
// 创建一个对象
let myObject = {
name: "John",
sayHello: function() {
console.log("Hello, " + this.name + "!");
}
};
// 使用原型创建另一个对象
let anotherObject = Object.create(myObject);
anotherObject.age = 25;
// 原型链:anotherObject -> myObject
anotherObject.sayHello(); // 输出 "Hello, John!"
在这个例子中,anotherObject
继承了 myObject
的属性和方法。原型链的形成使得 anotherObject
可以访问 myObject
的 sayHello
方法。
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 模块中,export
和 export 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(5, 7)); // 输出 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(5, 7)); // 输出 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 = {
value: 42,
getValue: function() {
return () => this.value;
}
};
const getter = obj.getValue();
console.log(getter()); // 输出 42
-
普通函数:
普通函数的
this
值由调用方式决定,通常是调用时的对象或者全局对象(在非严格模式下)。
const obj = {
value: 42,
getValue: function() {
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 的区别
forEach
和 map
都是 JavaScript 数组提供的迭代方法,但它们在用法和返回值上有一些关键的区别。
1. 返回值:
-
forEach
:forEach
没有返回值,它只是用来遍历数组,执行提供的回调函数。
const array = [1, 2, 3];
array.forEach(item => {
console.log(item);
});
-
map
:map
返回一个新数组,新数组的元素是对原数组中每个元素调用回调函数的结果。
const array = [1, 2, 3];
const newArray = array.map(item => {
return item * 2;
});
console.log(newArray); // 输出 [2, 4, 6]
2. 修改原数组:
-
forEach
:forEach
不会修改原数组,它只是用于遍历。
const array = [1, 2, 3];
array.forEach(item => {
console.log(item); // 输出 1, 2, 3
});
console.log(array); // 输出 [1, 2, 3]
-
map
:map
创建一个新数组,不会修改原数组。
const array = [1, 2, 3];
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 的区别
Promise
和 async/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:
Promise
的then
和catch
方法返回新的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.all
或Promise.race
。 -
async/await:
更适用于处理依赖关系的异步操作,可以使用同步的方式编写异步代码,更易读。
5. 主要区别:
-
async/await
是基于Promise
的,是一种更高级、更清晰的语法糖,使异步代码看起来更像同步代码。 -
async/await
更易读,尤其是在处理多个异步操作的情况下。 -
async/await
代码通常比使用Promise
更简洁,更容易理解。
new操作符的作用
new
操作符在 JavaScript 中用于创建一个新的对象实例。它执行以下步骤:
-
创建一个新对象: 一个空的对象被创建,并且该对象被赋值给
this
关键字。 -
链接到原型: 新对象被链接到构造函数的原型对象(prototype)。这意味着新对象可以访问构造函数原型对象上定义的属性和方法。
-
绑定 this: 构造函数内部的
this
关键字被设置为新创建的对象。这使得构造函数可以访问并操作新对象。 -
执行构造函数的代码: 构造函数内部的代码被执行,可以对新对象进行初始化操作。
-
返回新对象: 如果构造函数内部没有显式返回一个对象,则会默认返回新创建的对象。
下面是一个简单的示例:
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
对象,并通过构造函数初始化了 name
和 age
属性。新对象 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 = [1, 2, 3];
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 = [1, 2, 3];
console.log(Object.prototype.toString.call(arr)); // 输出 "[object Array]"
4. Array.isArray
方法:
Array.isArray
方法用于检测一个对象是否是数组。
let arr = [1, 2, 3];
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");
}
这种方式在大多数情况下是有效的,因为如果对象存在,条件为真;如果对象为 null
或 undefined
,条件为假。
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