2023.06.08 - 2023.06.14 更新前端面试问题总结(16道题)
获取更多面试相关问题可以访问
github 地址: github.com/pro-collect…
gitee 地址: gitee.com/yanleweb/in…
目录:
-
初级开发者相关问题【共计 6 道题】
- 452.JS 创建对象的方式有哪些?【热度: 894】【JavaScript】
- 453.hasOwnProperty 与 instanceof 区别【热度: 490】【JavaScript】
- 454.原型链的终点是什么?【热度: 1,325】【JavaScript】
- 455.异步编程的实现方式?【热度: 809】【JavaScript】
- 459.手写实现 Object.create【热度: 179】【JavaScript】【出题公司: 小米】
- 460.手写实现 instanceof【热度: 535】【JavaScript】【出题公司: PDD】
-
中级开发者相关问题【共计 10 道题】
- 445.执行上下文栈是什么【热度: 632】【JavaScript】【出题公司: 网易】
- 446.移动端如何实现上拉加载,下拉刷新?【热度: 718】【web应用场景】【出题公司: 京东】
- 447.JS 中的数组和函数在内存中是如何存储的?【热度: 815】【JavaScript】【出题公司: PDD】
- 448.手写实现一个缓存函数 memoize【热度: 787】【JavaScript】【出题公司: 小米】
- 449.JS 执行上下文的生命周期阶段有哪些【热度: 713】【JavaScript】【出题公司: 百度】
- 450.普通函数动态参数 和 箭头函数的动态参数有什么区别?【热度: 927】【JavaScript】
- 451.函数声明与函数表达式的区别【热度: 551】【JavaScript】
- 456.requestAnimationFrame 了解多少【JavaScript】
- 457.JS里的类就是构造函数的语法糖,这个说法是否正确【热度: 541】【JavaScript】【出题公司: 腾讯】
- 458.如何判断dom元素是否在可视区域【热度: 846】【web应用场景】【出题公司: 百度】
初级开发者相关问题【共计 6 道题】
452.JS 创建对象的方式有哪些?【热度: 894】【JavaScript】
关键词:JS 创建对象
- 使用对象字面量创建对象。
var obj = {
name: "John",
age: 30
};
- 使用 Object 构造函数创建对象。
var obj = new Object();
obj.name = "John";
obj.age = 30;
- 使用构造函数创建对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
var john = new Person("John", 30);
- 使用 Object.create() 方法创建对象。
var obj = Object.create(null);
obj.name = "John";
obj.age = 30;
- 使用类和继承创建对象。
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
var john = new Person("John", 30);
453.hasOwnProperty 与 instanceof 区别【热度: 490】【JavaScript】
关键词:hasOwnProperty、instanceof、hasOwnProperty作用、instanceof作用
hasOwnProperty 和 instanceof 是两个不同的操作符,用于不同的目的。
- hasOwnProperty
hasOwnProperty 是一个对象的原型方法,用来检测一个对象自身是否具有指定名称的属性(不会检查原型链上的属性)。其语法如下:
object.hasOwnProperty(property)
其中,object 是要检测的对象,property 是要检测的属性名。如果对象自身具有指定名称的属性,则返回 true,否则返回 false。
- instanceof
instanceof 是一个运算符,用来检测一个对象是否是某个类的实例。其语法如下:
object instanceof constructor
其中,object 是要检测的对象,constructor 是要检测的类(构造函数)。如果对象是指定类的实例,则返回 true,否则返回 false。
举个例子来说,假设有以下代码:
function Person(name) {
this.name = name;
}
var john = new Person("John");
console.log(john.hasOwnProperty("name")); // true
console.log(john instanceof Person); // true
上述代码中,我们创建了一个 Person 类,并使用构造函数创建了一个实例 john。然后我们分别使用 hasOwnProperty 和 instanceof 操作符检测 john 对象是否具有 name 属性和是否是 Person 类的实例,得到的结果分别为 true 和 true。
454.原型链的终点是什么?【热度: 1,325】【JavaScript】
关键词:原型链的终点
在JavaScript中,原型链的终点是 null。当访问一个对象的属性或方法时,如果当前对象没有该属性或方法,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的终点 null。
每个对象都有一个原型(prototype)属性,指向它的原型对象。原型对象也是一个对象,也有自己的原型,形成了原型链。原型链是由一系列对象的连接构成的,每个对象都有一个指向其原型的引用,通过这个引用可以沿着原型链向上查找属性和方法。
原型链的终点是 null,即最顶层的原型对象没有原型,它的 [[Prototype]] 指向 null。当查找属性或方法时,如果一直沿着原型链找到最顶层的原型对象仍然没有找到,则返回 undefined。
示例:
const obj = {};
console.log(obj.toString()); // obj 没有定义 toString 方法,通过原型链找到 Object.prototype 上的 toString 方法
const arr = [];
console.log(arr.join()); // arr 没有定义 join 方法,通过原型链找到 Array.prototype 上的 join 方法
const str = 'Hello';
console.log(str.toUpperCase()); // str 没有定义 toUpperCase 方法,通过原型链找到 String.prototype 上的 toUpperCase 方法
const num = 42;
console.log(num.toFixed(2)); // num 没有定义 toFixed 方法,通过原型链找到 Number.prototype 上的 toFixed 方法
console.log(Object.prototype.__proto__); // 最顶层的原型对象 Object.prototype 的原型是 null
因此,原型链的终点是 null,表示在原型链的最顶层无法再继续向上查找。
455.异步编程的实现方式?【热度: 809】【JavaScript】
关键词:JS异步编程、JS异步编程实现方式
异步编程的实现方式有以下几种:
- 回调函数
回调函数是最基本的异步编程方式。在执行异步操作时,将回调函数作为参数传递给异步函数,异步函数在操作完成后将结果传递给回调函数,回调函数再进行下一步操作。例如:
function getData(callback) {
setTimeout(function () {
callback('Data received');
}, 1000);
}
getData(function(data) {
console.log(data); // 'Data received'
});
- Promise
Promise 是一种更高级的异步编程方式。通过 Promise 对象可以管理异步操作的状态、结果与错误。Promise 支持链式调用,使得异步操作的多个步骤可以更加清晰地表达。例如:
function getData() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Data received');
}, 1000);
});
}
getData().then(function(data) {
console.log(data); // 'Data received'
});
- Async/await
Async/await 是基于 Promise 的一种语法糖,使异步操作的代码更加简单、易读。通过在函数前面加上 async 关键字,可以将函数变成 async 函数,使用 await 关键字可以等待 Promise 对象的结果。例如:
function getData() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Data received');
}, 1000);
});
}
async function outputData() {
const data = await getData();
console.log(data); // 'Data received'
}
outputData();
- Generator
Generator 是一种能够暂停和恢复执行的函数,可以用来实现异步编程。通过在函数中使用 yield 关键字可以暂停函数的执行,并在需要时恢复执行。例如:
function* getData() {
yield new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('Data received');
}, 1000);
});
}
const gen = getData();
gen.next().value.then(function(data) {
console.log(data); // 'Data received'
});
总的来说,异步编程的实现方式有很多,不同的方式适用于不同的情况。在实际编码中,需要根据具体情况选择合适的方式来实现异步操作。
459.手写实现 Object.create【热度: 179】【JavaScript】【出题公司: 小米】
关键词:Object.create实现、Object.create手写
Object.create() 方法可以用于创建一个新对象,使其原型与指定的对象完全相同。可以通过以下方式手写实现 Object.create() 方法。
function createObject(proto) {
function F() {
}
F.prototype = proto;
return new F();
}
// Example usage
const person = {
firstName: 'John',
lastName: 'Doe',
fullName: function() {
return this.firstName + ' ' + this.lastName;
}
};
const anotherPerson = createObject(person);
anotherPerson.firstName = 'Jane';
console.log(anotherPerson.fullName()); // Output: "Jane Doe"
该实现方式创建了一个名为 F 的空函数,将其原型设置为传入的 proto 对象,然后返回一个新创建的 F 函数对象。这个新对象的原型与传入的 proto 对象相同,从而实现了 Object.create() 的功能。
460.手写实现 instanceof【热度: 535】【JavaScript】【出题公司: PDD】
关键词:instanceof原理、instanceof实现、instanceof手写
instanceof 运算符用于检测一个对象是否是某个构造函数的实例。其作用是判断一个对象是否属于某个类(或其父类)的实例,类似于类的继承关系,如果是则返回 true,否则返回 false。通常情况下,用于判断一个对象的类型或类别。可以结合构造函数和原型链来理解。
示例代码:
function Person(name) {
this.name = name;
}
const person = new Person("张三");
console.log(person instanceof Person); // Output: true
console.log(person instanceof Object); // Output: true
console.log(person instanceof Array); // Output: false
在上面的示例中,我们通过 new 关键字创建了一个 Person 类的实例 person。然后我们使用 instanceof 运算符检测 person 对象是否是 Person 类的实例,结果为 true。同样地,我们也可以检测 person 对象是否是 Object 类的实例,结果也为 true,因为 Person 类是 Object 类的子类。而 Array 类则是 Object 类的子类,但不是 Person 类的子类,因此检测 person 对象是否是 Array 类的实例,结果为 false。
手写实现
instanceof 运算符用于检测一个对象是否是某个构造函数的实例。可以通过以下方式手写实现 instanceof 运算符。
function myInstanceof(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// Example usage
const arr = [1, 2, 3];
console.log(myInstanceof(arr, Array)); // Output: true
console.log(myInstanceof(arr, Object)); // Output: true
console.log(myInstanceof(arr, RegExp)); // Output: false
该实现方式获取传入对象的原型对象,并逐层向上搜索其原型链,直到找到目标构造函数的原型对象或者原型链到达最顶层 Object.prototype。如果找到目标构造函数的原型对象,则返回 true,否则返回 false。
中级开发者相关问题【共计 10 道题】
445.执行上下文栈是什么【热度: 632】【JavaScript】【出题公司: 网易】
关键词:执行上下文栈
在JavaScript中,执行上下文栈(Execution Context Stack)是用于跟踪和管理函数执行的机制。每当JavaScript代码执行到一个函数时,就会创建一个执行上下文(Execution Context)并被推入执行上下文栈的顶部。当函数执行完毕后,执行上下文将从栈中弹出,控制权将返回给调用该函数的上下文。
执行上下文栈遵循"先进后出"(Last-In-First-Out)的原则。也就是说,最后一个推入栈的执行上下文会被最先弹出。
每个执行上下文都包含了以下三个重要的组成部分:
- 变量对象(Variable Object):变量对象存储了函数的形参、函数声明、变量声明和作用域链等信息。
- 作用域链(Scope Chain):作用域链是一个由当前执行上下文的变量对象和所有父级执行上下文的变量对象组成的链表结构。它用于变量查找的过程。
- this 值:this 值指定了当前执行上下文中的 this 关键字的指向。
通过执行上下文栈,JavaScript引擎能够追踪到代码的执行位置,并根据当前执行上下文的环境来解析变量和执行函数。这种栈结构的管理方式使得JavaScript能够实现函数的嵌套调用和正确的变量作用域处理。
446.移动端如何实现上拉加载,下拉刷新?【热度: 718】【web应用场景】【出题公司: 京东】
关键词:上拉加载、下拉刷新
移动端实现上拉加载和下拉刷新通常使用一些特定的库或框架来简化开发。以下是两种常见的实现方式:
- 使用第三方库:一些流行的移动端UI库(如iScroll、BetterScroll、Ant Design Mobile等)提供了上拉加载和下拉刷新的功能,你可以使用它们来实现。这些库通常提供了易于使用的API和配置选项,可以在你的应用中轻松地集成上拉加载和下拉刷新功能。
自定义实现:如果你想更自定义地实现上拉加载和下拉刷新,可以使用原生的触摸事件(如touchstart、touchmove、touchend等)和滚动事件(如scroll)来监测用户的手势操作和滚动行为,并根据这些事件来触发相应的加载或刷新逻辑。你可以监听触摸事件来检测用户的下拉或上拉手势,当达到一定的阈值时,触发刷新或加载的操作。同时,你还需要监听滚动事件来判断当前滚动位置是否已经到达页面底部,从而触发上拉加载的操作。
当自定义实现上拉加载和下拉刷新时,你可以使用JavaScript和HTML/CSS来编写代码。下面是一个简单的示例,演示了如何通过原生事件来实现上拉加载和下拉刷新的功能:
HTML 结构:
<!DOCTYPE html>
<html>
<head>
<title>上拉加载和下拉刷新示例</title>
<style>
/* 用于展示加载和刷新状态的样式 */
.loading {
text-align: center;
padding: 10px;
background-color: #f1f1f1;
}
.refresh {
text-align: center;
padding: 10px;
background-color: #f1f1f1;
}
</style>
</head>
<body>
<div id="content">
<!-- 内容区域 -->
</div>
<div id="loading" class="loading">
加载中...
</div>
<div id="refresh" class="refresh">
下拉刷新
</div>
<script src="your_script.js"></script>
</body>
</html>
JavaScript 代码(your_script.js):
// 获取相关元素
var content = document.getElementById('content');
var loading = document.getElementById('loading');
var refresh = document.getElementById('refresh');
var isRefreshing = false;
var isLoading = false;
// 监听触摸事件
var startY = 0;
var moveY = 0;
content.addEventListener('touchstart', function(event) {
startY = event.touches[0].pageY;
});
content.addEventListener('touchmove', function(event) {
moveY = event.touches[0].pageY;
// 下拉刷新
if (moveY - startY > 100 && !isRefreshing) {
refresh.innerHTML = '释放刷新';
}
// 上拉加载
var scrollTop = content.scrollTop;
var scrollHeight = content.scrollHeight;
var offsetHeight = content.offsetHeight;
if (scrollTop + offsetHeight >= scrollHeight && !isLoading) {
loading.style.display = 'block';
}
});
content.addEventListener('touchend', function(event) {
// 下拉刷新
if (moveY - startY > 100 && !isRefreshing) {
refresh.innerHTML = '刷新中...';
simulateRefresh();
}
// 上拉加载
var scrollTop = content.scrollTop;
var scrollHeight = content.scrollHeight;
var offsetHeight = content.offsetHeight;
if (scrollTop + offsetHeight >= scrollHeight && !isLoading) {
loading.style.display = 'block';
simulateLoad();
}
// 重置状态
startY = 0;
moveY = 0;
});
// 模拟刷新
function simulateRefresh() {
isRefreshing = true;
setTimeout(function() {
// 刷新完成后的操作
refresh.innerHTML = '刷新成功';
isRefreshing = false;
}, 2000);
}
// 模拟加载
function simulateLoad() {
isLoading = true;
setTimeout(function() {
// 加载完成后的操作
loading.style.display = 'none';
isLoading = false;
}, 2000);
}
上面的代码使用了touchstart、touchmove和touchend事件来监测用户的手势操作,实现了下拉刷新和上拉加载的功能。通过修改refresh和loading元
素的内容和样式,可以实现相应的状态展示效果。
447.JS 中的数组和函数在内存中是如何存储的?【热度: 815】【JavaScript】【出题公司: PDD】
关键词:数组和函数在内存中存储方式
在JavaScript中,数组和函数在内存中的存储方式有一些不同。
-
数组(Array)的存储:
数组是一种线性数据结构,它可以存储多个值,并且这些值可以是不同类型的。在内存中,数组的存储通常是连续的。当创建一个数组时,JavaScript引擎会为其分配一段连续的内存空间来存储数组的元素。数组的每个元素都会被存储在这段内存空间中的相应位置。数组的长度可以动态改变,当向数组添加或删除元素时,JavaScript引擎会重新分配内存空间并移动元素的位置。
-
函数(Function)的存储:
函数在JavaScript中被视为一种特殊的对象。函数的定义实际上是创建一个函数对象,并将其存储在内存中。函数对象本身包含了函数的代码以及其他相关信息,例如函数的名称、参数和闭包等。函数对象的代码部分通常是一段可执行的JavaScript代码,它被存储在内存中的某个位置。当调用函数时,JavaScript引擎会查找该函数对象的存储位置,并执行其中的代码。
需要注意的是,数组和函数的存储方式是由JavaScript引擎决定的,不同的引擎可能会有一些微小的差异。此外,JavaScript引擎还会使用一些优化技术,如垃圾回收和内存管理,来优化内存的使用和回收。
448.手写实现一个缓存函数 memoize【热度: 787】【JavaScript】【出题公司: 小米】
关键词:缓存函数实现、memoize函数
用于创建一个带有缓存功能的函数。下面是一个简化版本的手写实现,展示了如何自己实现 memoize 函数:
function memoize(func) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = func.apply(this, args);
cache[key] = result;
return result;
};
}
// 示例用法
const expensiveFunction = memoize(function(n) {
console.log('Computing...');
return n * 2;
});
console.log(expensiveFunction(5)); // 第一次调用,输出:Computing... 10
console.log(expensiveFunction(5)); // 第二次调用,直接从缓存中获取结果,输出:10
console.log(expensiveFunction(10)); // 新的参数,再次计算并缓存结果,输出:Computing... 20
console.log(expensiveFunction(10)); // 再次调用,直接从缓存中获取结果,输出:20
上述代码中的 memoize 函数接受一个函数 func 作为参数,并返回一个新的函数。返回的函数具有缓存的能力,即根据参数的不同缓存计算结果。
在返回的函数内部,首先将传入的参数 args 转换成一个唯一的字符串 key,以便作为缓存对象 cache 然后检查 cache 对象中是否存在对应的缓存结果,如果存在直接返回缓存结果,否则执行原始函数 func 并将结果缓存起来。
通过这种方式,对于相同的参数,后续的调用将直接从缓存中获取结果,而不会再次执行函数。这样可以避免重复计算,提高函数的性能。
在示例中,我们创建了一个名为 expensiveFunction 的函数,并使用 memoize 进行包装。第一次调用时,函数会执行计算,并输出 "Computing...",结果为 10。第二次调用时,函数直接从缓存中获取结果,无需再次计算。最后两次调用分别使用了不同的参数,会触发新的计算并缓存结果。
需要注意的是,这个手写的 memoize 函数是一个简化版本,仅适用于参数为基本类型的情况。对于参数为复杂类型(如对象、数组等)的情况,需要使用更复杂的缓存键值生成方法,以确保正确的缓存行为。此外,实际的 Lodash 库中的 memoize 函数还提供了其他选项和功能,例如自定义缓存键生成函数、缓存过期时间等。
449.JS 执行上下文的生命周期阶段有哪些【热度: 713】【JavaScript】【出题公司: 百度】
关键词:JS 执行阶段、JS执行上下文生命周期
在JavaScript中,执行上下文的生命周期可以分为三个阶段:创建阶段(Creation phase)、执行阶段(Execution phase)和回收阶段(Cleanup phase)。
-
创建阶段(Creation phase):
-
在创建阶段,JavaScript引擎会做以下工作:
- 创建变量对象(Variable object):变量对象是执行上下文中的一个重要部分,用于存储变量和函数声明。在该阶段,JavaScript引擎会扫描当前上下文中的代码,并创建变量对象。变量对象包括函数的参数、函数声明和变量声明。对于全局上下文,变量对象是全局对象(如
window对象)。 - 建立作用域链(Scope chain):作用域链用于解析变量的访问权限。JavaScript引擎会根据当前执行上下文的词法环境和作用域嵌套关系来建立作用域链。
- 确定this值:在创建阶段,JavaScript引擎会确定
this关键字的值,这取决于函数的调用方式(如函数调用、方法调用、构造函数调用等)。
- 创建变量对象(Variable object):变量对象是执行上下文中的一个重要部分,用于存储变量和函数声明。在该阶段,JavaScript引擎会扫描当前上下文中的代码,并创建变量对象。变量对象包括函数的参数、函数声明和变量声明。对于全局上下文,变量对象是全局对象(如
-
-
执行阶段(Execution phase):
-
在执行阶段,JavaScript引擎会按照代码的顺序执行语句,执行以下操作:
- 变量赋值:根据代码中的赋值操作,给变量分配内存并赋予相应的值。
- 函数引用:根据代码中的函数调用,将函数的引用添加到变量对象中。
- 代码执行:按照代码的顺序执行语句,包括表达式计算、条件判断、循环等操作。
- 创建局部变量:当函数内部存在局部变量时,在执行到相应代码行时,会为局部变量分配内存空间。
-
-
回收阶段(Cleanup phase):
-
在回收阶段,JavaScript引擎会进行垃圾回收和释放内存等清理工作。当执行上下文不再被引用或执行完毕后,会触发回收阶段,进行以下操作:
- 解除引用:将执行上下文中的变量和函数从变量对象中移除,解除对它们的引用。
- 内存回收:对不再被引用的变量和对象进行垃圾回收,释放占用的内存空间。
-
这三个阶段共同构成了执行上下文的生命周期。创建阶段主要用于初始化执行上下文的变量和函数,建立作用域链和确定this值。执行阶段是实际执行代码的阶段,按照代码顺序执行语句。回收阶段主要用于清理执行上下文,释放内存空间。这个生
命周期的循环会在代码的执行过程中反复进行,直到所有的代码都执行完毕并且没有引用指向该执行上下文时,执行上下文将被彻底回收。
450.普通函数动态参数 和 箭头函数的动态参数有什么区别?【热度: 927】【JavaScript】
关键词:JS函数动态参数
普通函数和箭头函数在处理动态参数方面有以下区别:
-
普通函数的动态参数:
- 在普通函数中,可以使用
arguments对象来访问传递给函数的所有参数,无论是否定义了具名参数。arguments是一个类数组对象,可以通过索引访问每个参数的值。 - 普通函数可以使用剩余参数语法(Rest parameters)来声明动态参数,通过三个点(
...)和一个参数名表示。剩余参数会被收集成一个真正的数组,可以直接使用数组的方法和属性对参数进行操作。
- 在普通函数中,可以使用
示例:
function sum(a, b, ...rest) {
console.log(a, b); // 输出前两个参数
console.log(rest); // 输出剩余的动态参数,作为数组
}
sum(1, 2, 3, 4, 5); // 输出: 1 2, [3, 4, 5]
-
箭头函数的动态参数:
- 箭头函数不具有自己的
arguments对象。在箭头函数中,无法直接访问传递给函数的所有参数的类数组对象。 - 箭头函数可以使用剩余参数语法来声明动态参数,与普通函数相同。剩余参数会被收集成一个真正的数组,可以直接使用数组的方法和属性对参数进行操作。
- 箭头函数不具有自己的
示例:
const sum = (a, b, ...rest) => {
console.log(a, b); // 输出前两个参数
console.log(rest); // 输出剩余的动态参数,作为数组
}
sum(1, 2, 3, 4, 5); // 输出: 1 2, [3, 4, 5]
总结:
- 普通函数和箭头函数都可以接受动态参数。
- 普通函数可以使用
arguments对象访问所有参数,也可以使用剩余参数语法将参数收集成数组。 - 箭头函数没有自己的
arguments对象,无法直接访问所有参数,但可以使用剩余参数语法将参数收集成数组。
451.函数声明与函数表达式的区别【热度: 551】【JavaScript】
关键词:函数声明、函数表达式
JavaScript中有两种主要的方式来定义函数:函数声明(Function Declaration)和函数表达式(Function Expression)。
-
函数声明(Function Declaration):
- 函数声明是通过使用
function关键字后面跟着函数名称来创建的,通常位于作用域的顶部。 - 函数声明会被提升(Hoisting),即在执行代码之前就可以使用。这意味着可以在函数声明之前调用该函数。
- 函数声明创建的函数可以在整个作用域内部访问。
- 函数声明是通过使用
示例:
function sayHello() {
console.log("Hello!");
}
sayHello(); // 可以在函数声明之后调用
-
函数表达式(Function Expression):
- 函数表达式是将函数赋值给变量或作为其他表达式的一部分创建的。
- 函数表达式通常是匿名函数,即没有指定函数名称。但也可以使用具名函数表达式,为函数表达式指定一个名称。
- 函数表达式不会被提升,必须在定义之后才能使用。
- 函数表达式创建的函数只能在其所在的变量或表达式作用域内访问。
示例:
// 匿名函数表达式
const sayHello = function() {
console.log("Hello!");
};
sayHello(); // 必须在函数表达式之后调用
// 具名函数表达式
const add = function sum(a, b) {
return a + b;
};
console.log(add(2, 3)); // 输出: 5
// console.log(sum(2, 3)); // 错误,无法在外部访问具名函数表达式的名称
总结:
- 函数声明是使用
function关键字创建的函数,会被提升,可以在声明之前调用,而且在整个作用域内都可访问。 - 函数表达式是将函数赋值给变量或作为其他表达式的一部分创建的,不会被提升,必须在定义之后才能使用,且只能在其所在的变量或表达式作用域内访问。
456.requestAnimationFrame 了解多少【JavaScript】
requestAnimationFrame 是一种优化动画性能的方法,它会在浏览器重绘之前执行指定的回调函数。相比于传统的 setInterval 或 setTimeout 方法,requestAnimationFrame 会在浏览器的下一次重绘之前执行回调函数,能够更好地与浏览器的渲染机制结合,减少页面的卡顿和闪烁。
requestAnimationFrame 的使用方法如下:
let animationId;
function animate() {
animationId = requestAnimationFrame(animate);
// 在这里执行动画代码
}
animate(); // 启动动画
在上面的代码中,requestAnimationFrame 方法返回一个唯一的标识符,可以用来取消动画,如下所示:
cancelAnimationFrame(animationId); // 取消动画
需要注意的是,requestAnimationFrame 并不一定每秒都会执行 60 次,它会根据浏览器的刷新频率来自动调整执行次数,保证动画的流畅性。同时,由于 requestAnimationFrame 是在浏览器的主线程中执行,如果动画计算量过大,会占用过多的 CPU 资源,导致页面的卡顿和性能问题。因此,需要合理使用 requestAnimationFrame,避免在单个动画中进行复杂的计算。
457.JS里的类就是构造函数的语法糖,这个说法是否正确【热度: 541】【JavaScript】【出题公司: 腾讯】
关键词:JS构造函数、JS类的语法糖
这个说法是正确的。
在 JavaScript 中,类实际上是构造函数的语法糖,也就是说,通过类的语法创建的对象和通过构造函数创建的对象是一样的。
例如,下面是一个通过构造函数创建对象的示例:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person1 = new Person("Alice", 30);
person1.sayHello(); // 输出:Hello, my name is Alice and I am 30 years old.
而使用类的语法创建对象的示例代码如下:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person2 = new Person("Bob", 25);
person2.sayHello(); // 输出:Hello, my name is Bob and I am 25 years old.
可以看到,使用类的语法创建对象时,实际上是在创建一个与构造函数相同的对象。在类中,类名即为构造函数的名称,类中的构造函数即为类的构造函数,类中的方法即为构造函数的原型方法。
在 JavaScript 中,类实际上是构造函数的语法糖,可以通过以下几个方面来体现:
- 类的名称即为构造函数的名称,在类中通过
constructor方法来初始化对象:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("Tom", 20);
console.log(person instanceof Person); // true
这里定义的 Person 类实际上就是一个构造函数,通过 new 关键字创建的对象也是一个 Person 类型的对象。
- 在类中定义的方法即为构造函数的原型方法:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person("Tom", 20);
person.sayHello(); // 输出:Hello, my name is Tom and I am 20 years old.
可以看到,在类中定义的 sayHello 方法实际上就是 Person 构造函数的原型方法。
- 子类继承父类时,使用
super()方法调用父类的构造函数:
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(this.name + ' barks.');
}
}
const dog = new Dog('Max', 'Labrador');
dog.speak(); // 输出:Max barks.
这里定义了一个 Animal 类和一个继承自 Animal 类的 Dog 类,Dog 类在构造函数中通过 super() 方法调用了父类 Animal 的构造函数,实现了继承功能。可以看到,这里使用的 super() 方法也体现了类是构造函数的语法糖。
综上所述,JavaScript 中的类实际上是构造函数的语法糖,通过类的语法创建的对象和通过构造函数创建的对象是一样的。
458.如何判断dom元素是否在可视区域【热度: 846】【web应用场景】【出题公司: 百度】
关键词:元素是否在可视区域
判断 DOM 元素是否在可视区域可以使用以下方法:
- getBoundingClientRect() 方法
该方法返回元素的大小及其相对于视口的位置,包括 top、right、bottom、left 四个属性。我们可以根据这四个属性来判断元素是否在可视区域内。
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// Example usage
const element = document.getElementById('my-element');
if (isInViewport(element)) {
console.log('Element is in viewport');
} else {
console.log('Element is not in viewport');
}
- IntersectionObserver API
该 API 可以观察元素与其祖先元素或视口交叉的情况,并且可以设置回调函数,当元素的可见性发生变化时会调用该回调函数。
function callback(entries, observer) {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log('Element is in viewport');
} else {
console.log('Element is not in viewport');
}
});
}
const observer = new IntersectionObserver(callback);
const element = document.getElementById('my-element');
observer.observe(element);
使用 IntersectionObserver API 的优点是可以减少不必要的计算和事件监听,提高了性能。