当前位置: 首页 > news >正文

前端面试题整理-Javascript

JS组成:
JS是运行在浏览器的一门编程语言
在这里插入图片描述
函数类型:
在这里插入图片描述

1. 说说 js 都有哪些数据类型,他们在内存存储上有什么不同

基本数据类型:number、boolean、string、null(null就是特殊的object)、undefined、Symbol(ES6新增,表示唯一标识符)
引用数据类型:object、function、array
(内置引用类型:Math、Date、Number、String 等)
内存存储: 基本数据类型存储在栈内存,
引用数据类型存储在堆内存,栈内存中只存储引用地址

PS:判断数据类型
(1)typeof
优点:能够快速区分基本数据类型
缺点:不能将Object、Array和Null区分,都返回object

console.log(typeof 1);
// number
console.log(typeof null);
// object

(2) instanceof
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象
缺点:Number,Boolean,String基本数据类型不能判断

console.log(1 instanceof Number);
// false
console.log(true instanceof Boolean);
// false 
console.log('str' instanceof String);
// false 
console.log([] instanceof Array);
// true
console.log(function(){} instanceof Function); 
//true
console.log({} instanceof Object);
// true

(3)Object.prototype.toString.call()
优点:精准判断数据类型
缺点:写法繁琐不容易记,推荐进行封装后使用

var toString = Object.prototype.toString;
console.log(toString.call(1));
//[object Number]
console.log(toString.call(true));
//[object Boolean]
console.log(toString.call('mc'));
//[object String]
console.log(toString.call([]));
//[object Array]
console.log(toString.call({}));
//[object Object]
console.log(toString.call(function(){}));
//[object Function]
console.log(toString.call(undefined));
//[object Undefined]
console.log(toString.call(null));
//[object Null]

2. js 是如何进行垃圾回收的?

通过引用计数统计引用次数,若为0则自动回收

3. var let const 的区别

var 函数作用域,具有变量提升作用,可重复声明
let 块级作用域,不可重复声明,可变
const 块级作用域,不可重复声明,不可变

4. 说说 js 的作用域

全局作用域:在浏览器的控制台,script中,以及不在函数中的代码都是在全局作用域,如window变量
函数作用域:函数执行时的上下文,外部无法访问其中的变量,不会污染全局变量
块级作用域:for,while,{}中的代码
模块作用域:ES6或node中一个一个文件,相互隔离,不会污染全局变量

5. 什么是作用域链

首先在创建该变量的当前作用域中取值,当前作用域找不到,继续到上级作用域中查,直到查到全局作用域,这个查找过程形成的链条就做作用域链。

6. 什么是闭包,有什么用

JS闭包:内层函数+引用外层函数的变量(一个大函数里包含了一个变量+一个内部函数)
在这里插入图片描述
对闭包内的变量起到一个保护性的作用,外部不可直接使用此变量。
闭包不一定有return,不一定会有内存泄漏。
什么时候用到return?
当外部想用闭包变量时就用return,把局部变量返回到外面来,但是外面可以使用此变量但不能修改。
闭包的应用:实现数据的私有,对闭包内变量起到一个保护性作用和保存作用

7. 经典闭包 for 循环题目

    <script>for (let i = 0; i < 5; i++) {setTimeout(() => {console.log(i);}, i * 1000);}// 0,1,2,3,4for (var i = 0; i < 5; i++) {setTimeout(() => {console.log(i);}, i * 1000);}// 5,5,5,5,5</script>

对下面的函数进行修改使其输出0,1,2,3,4

      for (var i = 0; i < 5; i++) {// 增加外部函数function outer() {// 通过闭包保存ivar index = i;function inner() {console.log(index);}return inner;}var func = outer();setTimeout(func, i * 1000);}

简化:

      for (var i = 0; i < 5; i++) {setTimeout(((i) => () =>console.log(i))(i),i * 1000);}

简化过程:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8. js 中的原型了解吗?说说原型和原型链。为什么要用到原型,直接用点方法不好吗?

(1)原型:每个函数都有prototype属性,称之为原型,这个属性是一个对象,也称为原型对象。
起作用是存放一些属性、方法;在JS中实现继承

__proto__:每个对象都有该属性,指向原型

原型链:对象都有__proto__属性,此属性指向它的原型对象。原型对象也是对象,也有__proto__属性,指向原型对象的原型对象。这样一层一层形成的链式结构叫原型链,最顶层找不到则返回null

(2)JS中的原型通过面向对象来实现,prototype上的属性是所有实例共享的,可以节省内存。如果定义一个对象,比如一个数组arr=[1,2,3],调用arr.push(4)方法不是在原型上,而是在每一个对象上,那么每调用一次就要占用一次内存。但是定义到原型上,所有实例的方法只占据一片空间。

prototype 上的属性,各个实例都是共享的,this 中的属性,都是各自占据一片空间

9. call,apply,bind 有什么区别?(能自己实现一个吗?)

它们都定义在Function.prototype上,任何一个函数都可以访问到call,apply,bind方法,其作用是修改函数 this 的指向,第一个参数都是想要指定的 this 的值。
call()将实参在对象后依次传递 a,b,c,d
apply()需将实参封装到一个数组中统一传递 [a,b,c,d]
bind() 也是依次传递参数,返回改过this指向的新函数,调用才会执行,即bind(a,b,c,d)() a,b,c,d
在这里插入图片描述
在这里插入图片描述
自己实现call:

      // 手写实现callFunction.prototype.myCall = function (context, ...args) {// 先判断调用对象是不是函数if (typeof this !== "function") {console.log("type error");}// 判断context是否传入,null则设为windowcontext = context || window;// 增加context的临时属性fn,用来存储原来的this指向,也就是函数自己,方便后面调用(为了避免fn与context本身属性重复,使用symbol)const fn = Symbol();context[fn] = this;// 调用方法,此时使用的是context的方法,那么fn 属性所引用的函数(即原始函数)在执行时,this 将指向 context 对象const result = context[fn](...args);// 删除临时属性,避免 context 对象被 fn 属性污染delete context[fn];// 返回函数本身调用结果return result;};

自己实现apply:
在这里插入图片描述
自己实现bind:

      // 手写实现bindFunction.prototype.myBind = function (context, ...args) {// 先判断调用对象是不是函数if (typeof this !== "function") {console.log("type error");}// 判断context是否传入,null则设为windowcontext = context || window;// 闭包存一下当前函数,此时this指向要调用的函数const fn = this;return function (...innerArgs) {// 由于bind语法与call语法类似,此处利用call帮助实现return fn.call(context, ...args, ...innerArgs);// 也可以使用apply实现return fn.apply(context, args.concat(innerArgs));};};

10. new 字段发生了什么,可以自己实现吗?

创建了一个新的空对象,构造函数this指向新对象,执行构造函数,给新对象添加属性值,最后返回新对象。
手写new:

      // 改进版:如果构造函数有返回值且返回值是个对象,那么实力直接返回该返回值function myNew2(constructor, ...args) {// 创建新的空对象const obj = {};// 设置改空对象的__proto__指向构造函数的prototype(为新对象增加属性)// obj.__proto__ = constructor.prototype;Object.setPrototypeOf(obj, constructor.prototype);// apply调用constructor,修改this指向objconst res = constructor.apply(obj, args);// 如果构造函数返回对象而且不是null,那么myNew2就返回该对象,否则返回objreturn typeof res === "object" && res !== null ? res : obj;}

在这里插入图片描述
在这里插入图片描述

11. for in 和 for of 有什么区别?

for...infor...of 语句都用于迭代某个内容,它们之间的主要区别在于迭代的对象。
for...in 语句用于迭代对象的可枚举字符串属性,而 for...of 语句用于迭代可迭代对象定义的要进行迭代的值。
前者迭代属性名称,后者迭代属性值。
for in 通常不推荐用于迭代数组,因为它不保证迭代顺序,并且可能会遍历到数组的原型链上的属性。
在这里插入图片描述

12. 数组都有哪些迭代方法,他们会在原数组上改变还是返回新数组?

sort、splice、forEach、reverse、push/pop、unshift/shift会在原数组上进行改变,map、filter、concat等会返回新数组

13. 手写深拷贝和浅拷贝

直接复制对象是复制地址,会影响原对象。
浅拷贝只适合单层数据,单层不影响,多层有影响
深拷贝不影响原数据
在这里插入图片描述
手写浅拷贝:

      const obj = {uname: "pink",age: 18,family: {baby: "小pink",},// 1. 浅拷贝,拷贝的是地址(适合单层,单层不影响,多层会影响)// (1)扩展运算符const shadowCopy = { ...obj };console.log(shadowCopy);shadowCopy.age = 20;console.log(shadowCopy);console.log(obj); //不变// (2)Object.assign()const shadowCopy1 = {};Object.assign(shadowCopy1, obj);shadowCopy1.age = 22;// shadowCopy1.family.baby = "小red";console.log(shadowCopy1);console.log(obj); //外面18不变,里面那层变了

手写深拷贝:

      // 2. 深拷贝,拷贝的是对象不是地址// 新对象不会影响旧对象// 需要用到递归,遇到普通数值对象直接赋值,遇到数组再次调用递归函数,遇到对象也再次利用递归调用函数。先数组后对象// (1) 递归实现:函数内部再去调自己// (1) 递归实现:函数内部再去调自己const obj = {uname: "pink",age: 18,hobby: ["music", "study"],family: {baby: "小pink",},};const o = {};// 拷贝函数// Array和Object判断不能更换顺序,因为数组也属于对象function deepCopy(newObj, oldObj) {for (let k in oldObj) {// 处理数组的问题if (oldObj[k] instanceof Array) {newObj[k] = [];// newObj[k] 接收方 []deepCopy(newObj[k], oldObj[k]);} else if (oldObj[k] instanceof Object) {newObj[k] = {};// newObj[k] 接收方 {}deepCopy(newObj[k], oldObj[k]);} else {// k 属性名  oldObj[k]属性值newObj[k] = oldObj[k];}}}// 函数调用 两个参数:新对象 旧对象deepCopy(o, obj);console.log(o);o.age = 20;o.hobby[0] = "basketball";o.family.baby = "老pink";console.log(obj);// 数组也属于对象console.log([1, 2, 3] instanceof Object);  //true

14. 了解函数防抖和节流,尝试手写

防抖: 连续触发事件但是在设定的一段时间内只执行最后一次(强调只要打断就重新开始)
例如:设定1000毫秒执行,当你触发事件了。他会1000毫秒后执行,但是在还剩500毫秒的时候你又触发了事件,那就会重新开始1000毫秒之后再执行
应用场景:搜索框搜索输入、文本编辑器实时保存、手机号邮箱输入检测

节流: 连续触发事件但是在设定的一段时间内只执行一次函数(强调不要打断我)、
例如:设定1000毫秒执行,那你在1000毫秒触发在多次,也只在1000毫秒后执行一次
应用场景:高频事件(快速点击、鼠标移动mousemove、下拉加载、scroll事件(页面滚动触发)、resize事件(页面尺寸改变触发))、视频播放记录时间等

手写防抖:
核心思路:利用定时器(setTimeout)来实现
setTimeout只能执行一次,而setInterval会反复执行
(1) 先声明一个定时器变量
(2) 当鼠标每次滑动先判断是否有定时器了,若有先清除之前的定时器
(3) 若没有定时器则开启定时器,记得存到变量里面
(4) 在定时器里调用要执行的函数

    <div class="box"></div><script src="./lodash.min.js"></script><script>// 利用防抖实现性能优化// 需求: 鼠标在盒子上移动,里面的数字就会变化+1// 浪费性能,优化:鼠标停止500ms以后,里面的数字才会变化+1const box = document.querySelector(".box");let i = 1;function mouserMove() {box.innerHTML = i++;// 如果里面存在大量小号性能的代码,比如dom操作、数据处理,可能造成卡顿}// 添加事件:鼠标一移动就会触发mouseMove事件// box.addEventListener("mousemove", mouserMove);// 1. lodash提供的防抖处理 _.debounce(func,waitTime)// 500ms之后采取+1box.addEventListener("mousemove", _.debounce(mouserMove, 500));// 2. 手写防抖函数来处理// 核心思路:利用定时器(setTimeout)来实现// setTimeout只能执行一次,而setInterval会反复执行// (1)先声明一个定时器变量// (2)当鼠标每次滑动先判断是否有定时器了,若有先清除之前的定时器// (3)若没有定时器则开启定时器,记得存到变量里面// (4)在定时器里调用要执行的函数function debounce(fn, t) {let timer;// return返回一个匿名函数return function () {if (timer) clearTimeout(timer);// function () {} 匿名函数timer = setTimeout(function () {fn(); //加小括号调用fn函数}, t);};}// 我们想每次鼠标移动都要执行一下匿名函数里的所有代码box.addEventListener("mousemove", debounce(mouserMove, 500));</script>

手写节流:
核心思路:利用定时器(setTimeout)来实现
setTimeout只能执行一次,而setInterval会反复执行
(1) 声明一个定时器变量
(2) 当鼠标每次滑动都先判断是否有定时器了,如果有定时器咋不开启新定时器
(3) 如果没有定时器则开启定时器,记得存到变量里面
定时器里面的操作:定时器里面调用执行的函数;定时器里面要把定时器清空

    <div class="box"></div><script src="./lodash.min.js"></script><script>// 节流:单位时间内频繁触发事件只执行一次// 要求:鼠标在盒子上移动,不管移动多少次,每隔500ms才+1const box = document.querySelector(".box");let i = 1;function mouserMove() {box.innerHTML = i++;// 如果里面存在大量消耗性能的代码,比如dom操作、数据处理,可能造成卡顿}// box.addEventListener("mousemove", mouserMove);// 1. 利用lodash库实现节流//  _.throttle(func,waitTime) 在waitTime最多执行func一次的函数// box.addEventListener("mousemove", _.throttle(mouserMove, 3000));//  2. 手写节流函数来处理// 核心思路:利用定时器(setTimeout)来实现// setTimeout只能执行一次,而setInterval会反复执行// (1) 声明一个定时器变量// (2) 当鼠标每次滑动都先判断是否有定时器了,如果有定时器咋不开启新定时器// (3) 如果没有定时器则开启定时器,记得存到变量里面// 定时器里面的操作:定时器里面调用执行的函数;定时器里面要把定时器清空function throttle(fn, t) {let timer = null;return function () {if (!timer) {timer = setTimeout(function () {fn();// 时间到了清空定时器// 在setTimeout中是无法删除定时器的,因为定时器在运作,所以使用timer = null 而不是 clearTimeout(timer)timer = null;}, t);}};}box.addEventListener("mousemove", throttle(mouserMove, 500));</script>

15. 了解下函数柯里化

柯里化是编程语言中的一个通用的概念(不只是Js,其他很多语言也有柯里化),是指把接收多个参数的函数变换成接收单一参数的函数,嵌套返回直到所有参数都被使用并返回最终结果。
更简单地说,柯里化是一个函数变换的过程,是将函数从调用方式:f(a,b,c)变换成调用方式:f(a)(b)©的过程。柯里化不会调用函数,它只是对函数进行转换。
以下来自文章 一文搞懂Javascript中的函数柯里化(currying)
在这里插入图片描述在这里插入图片描述
用处:延迟计算、参数复用、动态生成函数

16. this指向情况有哪些?

(1) 普通函数,取决于它的调用方
(2) 箭头函数,取决于它定义时绑定的作用域中 this 的指向,具体一点,如果定义时,处于全局作用域,那么就指向 window,定义时,处于某一函数作用域,那么就指向该函数被执行时的 this,也就是指向该函数的调用方.
(3)call, apply, bind 调用时,可以通过第一个参数指定普通函数 this 的指向。但是如果原函数是箭头函数,那么修改将不会生效

17. 字符串常见方法

在这里插入图片描述

☆☆☆ 18. JS常见数组题:

(1)map和forEach区别

两者都可遍历数组,但是map可以返回一个数组,forEach不返回值。

	  // map  ele不可省略,index可省略const newArr = arr.map(function (ele, index) {// console.log(ele); //数组元素// console.log(index);  //索引号return ele + "颜色";});console.log(newArr);//(4) ['red颜色', 'green颜色', 'pink颜色', 'blue颜色']
	  // forEach  item不可省略,index可省略const arr = ["red", "green", "blue"];const res = arr.forEach(function (item, index) {console.log(item); //数组元素console.log(index); //索引号});console.log(res); //undefined

(2)创建数组有哪几种方式

    <script>// 创建数组// 1. 字面量创建let arr1 = [1, 2, 3];console.log(arr1);// 2. 使用Array构造函数let arr2 = new Array(1, 2, 3);console.log(arr2);// 3.Array.of方法let arr3 = Array.of(1, 2, 3);console.log(arr3);// 4. Array.from方法let arr4 = Array.from([1, 2, 3]);console.log(arr4);let strArr = Array.from("string");console.log(strArr);//  ['s', 't', 'r', 'i', 'n', 'g']</script>

(3)遍历数组有哪几种方式

    <script>// 数组遍历let arr = [1, 2, 3, 4];// 1. for循环for (let i = 0; i < arr.length; i++) {console.log(arr[i]);}// 2. forEach方法arr.forEach((item) => console.log(item));// 3. map方法  新数组arr.map((item) => console.log(item));// 4. reduce方法arr.reduce((prev, current) => console.log(current), []);// 5. for...in循环(不推荐用于数组,因为他会遍历数组的所有可枚举属性)for (let index in arr) {console.log(arr[index]);}// 6. for...of循环for (let item of arr) {console.log(item);}// 7. while循环let i = 0;while (i < arr.length) {console.log(arr[i]);i++;}</script>

(4)数组常见方法

arr.map :处理(返回新数组)
arr.filter: 筛选(返回新数组)
arr.every: 每一项都要符合条件才行true/false
arr.some: 有一项符合即可true/false
arr.fill: 从某个位置开始替换(返回新数组)
arr.findIndex: 返回第一个符合条件的索引值
arr.find: 返回第一个符合条件的值,没有返回undefined
arr.indexOf() : 返回数组中第一次出现给定元素的下标,如果不存在则返回 -1。indexOf(查找元素, 起始索引)
arr.includes(): 用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
arr.reduce():返回累计器结果

在这里插入图片描述

(5)数组元素加前缀/后缀

    <script>// 数组元素加统一前缀/后缀let arr = [1, 2, 3, 4];// 1. forEach方法实现let arr1 = [];arr.forEach((item) => arr1.push(`排名${item}`));console.log(arr1); //['排名1', '排名2', '排名3', '排名4']// 2. map实现let arr2 = arr.map((item) => `排名${item}`);console.log(arr2);// 3. for循环let arr3 = [];for (let i = 0; i < arr.length; i++) {arr3.push(`排名${arr[i]}`);}console.log(arr3);// 4. reduce方法,初始值是[]let arr4 = arr.reduce((prev, current) => {if (!prev.includes(current)) {prev.push(`排名${current}`);return prev;}}, []);console.log(arr4);// 5. Array.from()方法let arr5 = Array.from(arr, (item) => `排名${item}`);console.log(arr5);</script>

(6)数组去重

    <script>// 数组去重let arr = [1, 2, 3, 4, 3, 2];// 1. 使用set方法,因为set元素不可重复let arr1 = Array.from(new Set(arr));console.log(arr1);// 2. 使用filter和indexoflet arr2 = arr.filter(function (ele, index) {if (arr.indexOf(ele) === index) {console.log(`ele:${arr.indexOf(ele)}  index:${index}`);return ele;}});console.log(arr2);// 3. 使用reduce和includes,初始值是[]let arr3 = arr.reduce(function (prev, current) {if (!prev.includes(current)) {prev.push(current);}return prev;}, []);console.log(arr3);// 4. 使用forEach和includeslet arr4 = [];arr.forEach(function (item, index) {if (!arr4.includes(item)) {arr4.push(item);}});console.log(arr4);// 5. 利用obj键唯一实现let arr5 = [];let obj = {};arr.forEach((item) => {if (!obj[item]) {obj[item] = true;arr5.push(item);}});console.log(arr5);</script>

(7)数组类型判断方法

    <script>// 判断是不是数组let arr = [1, 2, 3];// 1. Array.isArray()console.log(Array.isArray(arr)); //true// 2. instanceof方法console.log(arr instanceof Array); //true// 3. 使用Object.prototype.toString.call()let judge = Object.prototype.toString;console.log(judge.call(arr)); //[object Array]</script>

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 凤凰端子音频矩阵应用领域
  • 【问题解决】git status中文文件名乱码
  • #laravel部署安装报错loadFactoriesFrom是undefined method #
  • WordPress原创插件:Download-block-plugin下载按钮图标美化
  • 力扣面试经典算法150题:罗马数字转整数
  • 【JavaEE初阶】线程池
  • [LitCTF 2024]exx
  • 「MyBatis」实现留言板
  • LVS服务的搭建之NAT模式、DR模式的搭建实战
  • 情绪电商的数字化营销方法适用行业大揭秘
  • Linux的安装和使用
  • 使用RestHighLevelClient进行Elasticsearch Function Score查询
  • 【喜报】祝贺青创智通签约广州进德生物科技SunFMEA项目
  • JavaSE 网络编程
  • 【学习笔记】Day 12
  • [译]CSS 居中(Center)方法大合集
  • 2017-09-12 前端日报
  • Android单元测试 - 几个重要问题
  • express如何解决request entity too large问题
  • iOS小技巧之UIImagePickerController实现头像选择
  • Javascript Math对象和Date对象常用方法详解
  • Less 日常用法
  • python学习笔记 - ThreadLocal
  • tab.js分享及浏览器兼容性问题汇总
  • ubuntu 下nginx安装 并支持https协议
  • Yeoman_Bower_Grunt
  • 安装python包到指定虚拟环境
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 关于字符编码你应该知道的事情
  • 计算机在识别图像时“看到”了什么?
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 昨天1024程序员节,我故意写了个死循环~
  • # 飞书APP集成平台-数字化落地
  • ### RabbitMQ五种工作模式:
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (vue)el-cascader级联选择器按勾选的顺序传值,摆脱层级约束
  • (二十六)Java 数据结构
  • (附源码)springboot社区居家养老互助服务管理平台 毕业设计 062027
  • (附源码)ssm基于jsp的在线点餐系统 毕业设计 111016
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (十)c52学习之旅-定时器实验
  • (十六)一篇文章学会Java的常用API
  • (转)chrome浏览器收藏夹(书签)的导出与导入
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • .axf 转化 .bin文件 的方法
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .net6Api后台+uniapp导出Excel
  • .NET程序员迈向卓越的必由之路
  • .NET框架
  • .Net中wcf服务生成及调用
  • [ Linux ] git工具的基本使用(仓库的构建,提交)
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [Android]竖直滑动选择器WheelView的实现
  • [Assignment] C++1