前端js面试题 (一)

2023-09-20 17:55:21

1、请你阐述一下原型与原型链。

原型的定义:每一个JavaScript对象都有一个原型,它是一个指向另一个对象的引用,这个原型对象包含可以被继承的属性和方法。
每当你创建一个新的对象,如果这个对象的原型被关联到一个原型对象上,那么这个新对象上也可以使用原型对象的属性和方法。

原型链:如果对象A的原型是对象B,而对象B的原型又是对象C,那么对象A可以访问对象B和对象C上定义的属性和方法。对象ABC组成的这种多层对象的原型链条关系,称之为原型链。

// 创建一个对象 person
var person = {
  name: "John",
  age: 30,
};

// 创建一个对象 student,它的原型是 person
var student = Object.create(person);
student.grade = "A";

// 创建一个对象 teacher,它的原型是 person
var teacher = Object.create(person);
teacher.subject = "Math";

// 原型链的演示
console.log(student.name);     // 输出 "John",从原型链上继承了 name 属性
console.log(student.grade);    // 输出 "A",自身属性
console.log(teacher.name);     // 输出 "John",从原型链上继承了 name 属性
console.log(teacher.subject);  // 输出 "Math",自身属性

2、开发中的闭包问题。

闭包是指一个函数能够访问其外部函数作用域中的变量,即使外部函数已经执行完毕。
闭包问题:
1、闭包函数可以捕获并保持对其外部作用域中变量的引用,这意味着这些变量不会被垃圾回收机制回收,直到闭包不再被引用。

function outer() {
  var count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}

var closureFunc = outer();
closureFunc(); // 输出 1
closureFunc(); // 输出 2

inner函数形成了一个闭包,可以访问outer函数作用域中的count变量,即使outer函数已经执行完毕。

2、由于闭包可以保持对外部作用域中变量的引用,如果不注意,可能会导致内存泄漏问题。当不再需要闭包时,需要手动解除对其的引用,以便垃圾回收机制可以回收相应的内存。

function outer() {
  var count = 0;
  function inner() {
    count++;
    console.log(count);
  }
  return inner;
}

var closureFunc = outer();
closureFunc(); // 输出 1
closureFunc(); // 输出 2
// 当不再使用时,清除对其的引用
closureFunc = null;

3、在循环中创建闭包时需要格外小心,因为闭包会捕获循环变量的值,可能导致意外行为所以尽量避免在循环的函数里面使用循环变量本身。

function createCounter() {
 let count = 0;
 return function() {
   count += 1;
   return count;
 };
}

const counter = createCounter();
for (let i = 0; i < 10; i++) {
 console.log(counter());
}

3、call、apply、bind的用途与区别

手写 call apply bind及它们区别 : https://dengxi.blog.csdn.net/article/details/120334431

4、手写一个promise

es6 promise知识点 王者段位前来挑战 https://dengxi.blog.csdn.net/article/details/122497871

5、箭头函数与普通函数

JavaScript中,箭头函数和普通函数有一些重要的区别,包括语法、作用域、this的绑定以及适用场景等方面的差异。
1、语法

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

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

2、作用域
普通函数:拥有自己的this关键字,并且在函数内部可以通过this引用函数被调用的上下文。
箭头函数:没有自己的this关键字,它的this是在定义时继承自外层的作用域。

3、this的绑定
普通函数:this的值在运行时根据调用方式(方法、函数、构造函数等)动态确定。
箭头函数:this的值在定义时确定,不会被改变。

    const obj = {
        name: "John",
        regularFunction: function () {
            console.log(this.name, 'regularFunction'); // 正确,this指向obj
        },
        arrowFunction: () => {
            console.log(this.name, 'arrowFunction'); // this指向window,因为箭头函数继承自外部作用域的this
        }
    };

    const regularFunc = obj.regularFunction;
    const arrowFunc = obj.arrowFunction;

    // 谁调用this就指向谁,相当于window在调用,
    regularFunc() // regularFunction
    // 箭头函数定义时外层作用域是window,箭头函数this指向window是固定的,所以this仍然指向window
    arrowFunc() // arrowFunction

    // 谁调用this就指向谁,obj在调用 this指向obj
    obj.regularFunction(); // John regularFunction
    // 箭头函数定义时外层作用域是window,箭头函数this指向window是固定的,所以this仍然指向window
    obj.arrowFunction(); // arrowFunction

4、arguments 对象
普通函数:普通函数内部可以访问到arguments对象,它包含了所有传递给函数的参数。
箭头函数:箭头函数没有自己的arguments对象,它会继承外部函数的arguments对象(如果有的话),或者根本不能访问arguments。

5、构造函数:
普通函数:普通函数可以用作构造函数,通过new关键字创建对象实例。
箭头函数:箭头函数不能用作构造函数,不能通过new关键字创建对象实例。

6、递归与尾递归。

递归(Recursion)
递归是一种函数调用自身的技术。在递归函数中,函数会不断地调用自身,直到满足某个终止条件,然后递归过程将逐层返回,将结果组合起来。
递归函数的调用栈会保存每次函数调用的上下文,直到达到终止条件,然后逐层返回。
递归函数的典型示例包括计算斐波那契数列、树的遍历等问题

function factorial(n) {
  if (n === 0) {
    return 1;
  } else {
    return n * factorial(n - 1);
  }
}

尾递归(Tail Recursion)
尾递归是一种特殊类型的递归,在尾递归函数中,递归调用是函数的最后一条语句。也就是说,在尾递归中,递归调用的返回值直接被返回给了当前函数的调用者,没有额外的计算操作。

尾递归的优势在于一些编程语言的优化器可以对其进行优化,将其转化为迭代循环,从而减少了函数调用栈的深度,提高了性能。

尾递归的经典示例是阶乘函数的尾递归实现。

function factorialTail(n, accumulator = 1) {
  if (n === 0) {
    return accumulator;
  } else {
    return factorialTail(n - 1, n * accumulator);
  }
}

7、await 返回值是什么。

await 关键字用于等待一个 Promise 解决(或拒绝),并暂停当前函数的执行,直到 Promise 的状态发生变化。

当 await 表达式成功完成时,它返回 Promise 解决时的值。如果 Promise 被拒绝(rejected),await 表达式将抛出一个错误。

只有在 async 函数中才能使用 await 关键字。如果你尝试在非异步函数中使用 await,会导致语法错误。

总之,await 返回 Promise 解决时的值,使得异步代码更容易理解和编写,因为它使异步操作看起来更像同步代码。

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/user');
    const data = await response.json();
    return data; // 返回成功时的数据
  } catch (error) {
    console.error('请求出错:', error);
    throw error; // 抛出错误
  }
}

在上面的代码中,await fetch(‘https://api.example.com/data’) 等待请求完成,并返回响应对象。然后,await response.json() 等待响应数据解析为 JSON,并返回解析后的数据。

8、promise.then,setInterval,Promise.resolve,执行顺序

正常情况下,Promise.resolve 是属于同步代码最先执行,promise.then 属于异步微任务,其次执行,setInterval才是真正异步方法,最后执行。

首先,promise.then中的回调函数被添加到Promise的回调队列中。
接下来,setInterval开始定期触发。
如果你在Promise上调用Promise.resolve(),并且Promise已经解决(resolved),则与该Promise相关的微任务(如.then回调)将在事件循环中的微任务阶段执行。

9、let const var 的区别。

1、作用域:
var:使用 var 声明的变量具有函数作用域(function scope)。这意味着它们只在包含它们的函数内部可见,而不在块级作用域内可见。
let 和 const:使用 let 和 const 声明的变量具有块级作用域(block scope)。这意味着它们在包含它们的代码块(例如,{})内可见,包括 if 语句、for 循环、while 循环等。

2、变量提升(Hoisting):
var 声明的变量会被提升到其作用域的顶部,即使在声明之前引用它们也不会报错。这意味着变量在声明之前会被初始化为 undefined。
let 和 const 也会被提升,但它们不会被初始化。如果在声明之前引用 let 或 const 变量,会导致暂时性死区(Temporal Dead Zone,TDZ)错误。

3、重复声明:
使用 var 可以多次声明同一个变量名,不会引发错误。
使用 let 或 const 在同一作用域内重复声明同一个变量名会引发错误。

4、可变性:
var 声明的变量可以随时重新赋值。
let 声明的变量也可以重新赋值,但它们可以在同一作用域内多次声明。
const 声明的变量是常量,一旦赋值后就不能再次赋值。但对于复杂数据类型(例如对象或数组),可以修改其内部内容,只要不改变变量本身的引用。

5、全局对象属性:
使用 var 声明的全局变量会成为全局对象(在浏览器中为 window)的属性。
使用 let 和 const 声明的全局变量不会成为全局对象的属性。

10、关于声明赋值的优先级。

1、变量声明提升(Hoisting):
在 JavaScript 中,var变量声明 会被提升到其所在作用域的顶部。这意味着变量的声明会在代码执行之前被解析,但赋值操作不会提升。

console.log(x); // 输出 undefined,不会报错
var x = 5;

2、赋值操作:
赋值操作按照代码的执行顺序进行。当代码执行到赋值语句时,右侧的表达式会被计算,然后结果将赋给左侧的变量。

a = 30;
var a;
var b = a + 5;
a = 10

在上述示例中,首先 a 被赋值为 30,然后 b 被赋值为 a + 5,因此 b 的值为 35,最后a重新被赋值10。

3、let 和 const 的暂时性死区(Temporal Dead Zone,TDZ):
使用 let 或 const 声明的变量存在暂时性死区,即在声明之前引用它们会引发错误

console.log(x); // 报错:x is not defined
let x = 5;

更多推荐

Linux: Cache 简介

文章目录1.前言2.背景3.Cache硬件基础3.1什么是Cache?3.2Cache工作原理3.3Cache层级架构3.4内存架构中各级访问速度概览3.5Cache分类3.6Cache的查找和组织方式3.6.1Cache组织相关术语3.6.2Cache查找3.6.2.1Cache查找过程概述3.6.2.2Cache查

cgroup限制内存

首先简单介绍下cgroup限制cpu的使用率,写一段代码如下:#include<stdio.h>#include<pthread.h>intmain(){inti=0;for(;;)i++;return0;}很明显,这里面是单核拉满,然后top看下进程的cpu使用率,如下所示:很明显,截图中的cpu使用率是正常的,现在

一,安卓aosp源码编译环境搭建

系列文章目录第一章安卓aosp源码编译环境搭建第二章手机硬件参数介绍和校验算法第三章修改安卓aosp代码更改硬件参数第四章编译定制rom并刷机实现硬改(一)第五章编译定制rom并刷机实现硬改(二)第六章不root不magisk不xposedlsposedfrida原生修改定位第七章安卓手机环境检测软件分享第八章硬改之设

mybatis学习记录(二)-----CRUD--增删改查

目录使用MyBatis完成CRUDz--增删改查3.1insert(Create)3.2delete(Delete)3.3update(Update)3.4select(Retrieve)查询一条数据查询多条数据使用MyBatis完成CRUDz--增删改查准备工作创建module(Maven的普通Java模块):myb

算法通关村-----动态规划高频问题

最少硬币数问题问题描述给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。你可以认为每种硬币的数量是无限的。详见leetcode322问题分析设f(n)为amount=n时使用的最少金币数。遍

【JavaSE笔记】数组

一、前言数组被广泛应用于各种应用场景中。在这篇文章中,我们将深入探讨Java数组的概念、定义、基本操作、多维数组以及常见的应用场景。二、数组的基本概念1、什么是数组数组:可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。比如现实中的车库:在java中,包含6个整形类型元素的数组,就相当于上图中连在一起的6个车

【腾讯云 Cloud Studio 实战训练营】基于Python实现的快速抽奖系统

文章目录⭐️CloudStudio-简介🌟操作步骤🌟注册CloudStudio🌟创建工作空间🌟启动对应的开发环境⭐️抽奖系统项目介绍⭐️抽奖系统代码结构图⭐️项目基础类-文件检查🌟base.py基础类文件检查示例如下:🌟common模块的error.py脚本的代码如下:🌟utils.py模块check_f

CRM软件系统趣味性——游戏化销售管理

对于企业销售来说,高薪酬也伴随着更高的压力与挑战。高强度的单一工作会让销售人员逐渐失去对工作的兴趣,导致售状态缺少动力和激情,工作开展愈加困难。您可以通过CRM系统进行游戏化销售管理,让销售人员重新干劲满满。游戏并不是纯粹的娱乐,它其实还是提升个人竞争意识、团队协作的一种方式,因此,将它一味的妖魔化是不可取的。也正因如

Linux 中nc指令的使用总结

nc指令概述用法一:端口扫描用法二:命令行中发送和接收数据用法三:建立双方通信nc指令概述nc是Linux系统中的netcat命令之简称,它是一个强大的网络工具,可以用于创建TCP/UDP套接字连接。常见的其用法模板可定位:nc[选项][地址][端口],它的用法如下图所示:知道你英语差了一丢丢,小鸽鸽给你准备了中文版:

【C++】string类模拟实现下篇(附完整源码)

目录1.resize2.流插入<<和流提取>>重载2.1流插入<<重载2.2流提取<<3.常见关系运算符重载4.赋值重载4.1浅拷贝的默认赋值重载4.2深拷贝赋值重载实现4.3赋值重载现代写法5.写时拷贝(了解)6.源码6.1string.h6.2test.cpp1.resize下面我们来实现一下resize():re

【C++ 程序设计】实战:C++ 实践练习题(21~30)

目录21.计算并输出1到9之间奇数之和22.多层嵌套计算23.循环结构:打印变量a、b、c24.函数调用:全局变量、局部变量25.找到数组中不等于最大值和最小值的元素26.计算:平方根、平方、立方根、立方27.找出三个整型数中的最大值28.初始化一个5x5的二维数组a,根据表达式(i+1)*(j+1)将数组元素设置为i

热文推荐