JS前端面试题

前排提醒,本文所有引用标注在文末,感兴趣的朋友可以查看原帖学习!谢谢各位佬❤

1、解释一下虚拟DOM

2、介绍一下promise?常用的api都有哪些?

3、深浅拷贝

4、正则表达式

5、节流、防抖

6、什么是闭包?闭包有什么问题?什么是内存泄漏?哪些操作会造成内存泄漏?怎么解决内存泄漏?

7、说说你对作用域链的理解

8、ajax原理?有哪些优缺点?

9、javascript创建对象的几种方式?

10、数组去重方法总结

11、ES6新特性总结

12、type of 和instance of怎么区分数据类型?


1、解释一下虚拟DOM

虚拟 dom 本质上就是一个普通的 JS 对象,最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性。在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟 dom 树。相较于真实DOM的创建、更新、插入等操作会带来大量的性能损耗,虚拟DOM可以解决渲染效率的问题。

虚拟 dom 是如何转换为真实 dom 的?

虚拟 dom 是如何转换为真实 dom 的?
      在一个组件实例首次被渲染时,它先生成虚拟 dom 树,然后根据虚拟 dom 树创建真实 dom,并把真实 dom 挂载到页面中合适的位置,此时,每个虚拟 dom 便会对应一个真实的 dom。这时候虚拟 dom 多一个创建虚拟 dom 树的过程,所以效率比真实 dom 低。

      但是如果一个组件受响应式数据变化的影响,需要重新渲染时,它仍然会重新调用 render 函数,创建出一个新的虚拟 dom 树,用新树和旧树对比,通过对比,vue 会找到最小更新量,然后更新必要的虚拟 dom 节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实 dom,这样一来,就保证了对真实 dom 最小的改动。


2、介绍一下promise?常用的api都有哪些? 

Promise是异步编程的一种解决方法,promise对象有以下两个特点:

1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

3、promise内部是同步的,等到内部函数全部执行完毕,再去执行then/catch,then内部是异步的,可以不用等到then的结果就去执行下一步语句

new Promise(function (resolve, reject) {
  // resolve 表示成功的回调
  // reject 表示失败的回调
}).then(function (res) {
  // 成功的函数
}).catch(function (err) {
  // 失败的函数
})

常用的api:

Promise.all()

· promises: 包含 n 个 promise 的数组

· 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接失败

· 成功时返回所有的 promise 返回值组成一个数组,失败时返回第一个失败的 promise 的返回值

Promise.allSettled()

· promises: 包含 n 个 promise 的数组

· 返回一个新的 Promise 对象。只有等到参数数组的所有 Promise 对象都发生状态变更(不管是fulfilled还是 rejected),返回的 Promise 对象才会发生状态变更。

Promise.race()

· promises: 包含 n 个 promise 的数组

· 返回一个新的 promise,第一个完成的 promise 的结果状态就是最终的结果状态

Promise.resolve()

· 返回一个以给定值解析后的 Promise 对象。

new Promise((resolve, reject) => {})


3、深浅拷贝 

首先,拷贝的划分都是针对引用类型来讨论的。

浅拷贝:浅拷贝是对象的逐位复制。创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则只复制引用地址,即,复制内存地址。(默认情况下引用类型(object)都是浅拷贝)

         通俗易懂的说,浅拷贝复制的是对象的引用地址,没有开辟新的栈,复制的结果是两个对象指向同一个地址,所以修改其中一个对象的属性,另一个对象的属性也跟着改变了。

深拷贝:复制所有字段,并复制字段所指向的动态分配内存。深拷贝发生在对象及其引用的对象被复制时。(默认情况下基本数据类型(number,string,null,undefined,boolean)都是深拷贝。)

          通俗易懂的说,深拷贝会开辟新的栈,两个对象对应两个不同的地址,修改对象A的属性,并不会影响到对象B。

js的基本数据类型:

  • js的六大数据类型:Number, String, Boolean, Undefined , Null , Object;
  • 基本数据类型:Number,String,Boolean,Undefined, Null;
  • 引用数据类型:Object , Array, Function

浅拷贝方法:

Object.assign({}, arr);

如果对象的属性值为简单类型(如Number,String),通过Object.assign({},Obj)得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。

slice();

concat()

深拷贝方法: 

思路:递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝

//定义检测数据类型的功能函数
    function checkedType(target) {
      return Object.prototype.toString.call(target).slice(8, -1)
    }
    //实现深度克隆---对象/数组
    function clone(target) {
      //判断拷贝的数据类型
      //初始化变量result 成为最终克隆的数据
      let result, targetType = checkedType(target)
      if (targetType === 'Object') {
        result = {}
      } else if (targetType === 'Array') {
        result = []
      } else {
        return target
      }
      //遍历目标数据
      for (let i in target) {
        //获取遍历数据结构的每一项值。
        let value = target[i]
        //判断目标结构里的每一值是否存在对象/数组
        if (checkedType(value) === 'Object' ||
          checkedType(value) === 'Array') { //对象/数组里嵌套了对象/数组
          //继续遍历获取到value值
          result[i] = clone(value)
        } else { //获取到value值是基本的数据类型或者是函数。
          result[i] = value;
        }
      }
      return result
    }

JSON.stringify() 和JSON.parse() 

前提条件:对象是安全的,比如属性值不能是 undefined、symbol、函数、日期和正则。

let a = {
	    name:"datiechui",
	    like:{
	       food:"chicken",
	       color:"blue"
	    }
	}
let b = JSON.parse(JSON.stringify(a));
a.name = "xiaobuding";
a.like.color = "green";
console.log(a,b);

4、正则表达式

正则表达式通俗的讲就是按照某种规则去匹配符合条件的字符串。


 5、节流、防抖

节流(throttling)和防抖(debouncing)是在处理用户输入和其他事件时常用的两种性能优化技巧。它们的主要目的是控制事件触发的频率,以避免不必要的重复操作,减轻系统负担,提高用户体验。

1.节流(Throttling):

  • 节流是一种限制事件处理函数执行频率的技术,确保事件在一定时间间隔内最多执行一次。
  • 当一个事件被触发,节流会立即执行事件处理函数,并在指定的时间间隔内忽略任何后续触发的相同事件。
  • 适用于需要定期更新的操作,如滚动事件、搜索框输入,确保它们不会太频繁地触发,减轻服务器和客户端的负担。

2. 防抖(Debouncing):

  • 防抖是一种延迟事件处理函数执行的技术,确保事件触发后,在指定的时间内没有更多事件触发时才执行一次事件处理函数。
  • 当一个事件被触发,防抖会等待一段时间,如果在这段时间内没有更多事件触发,那么事件处理函数将被执行。
  • 适用于防止重复提交表单、搜索建议等需要等待用户停止输入的场景。

3.区别

1.触发方式:节流是在一定时间间隔内最多执行一次事件处理函数,而防抖是等待一段时间后执行事件处理函数,如果在等待期间有新的触发事件,等待时间会被重新计时。

2.应用场景:节流适用于需要限制事件频率的场景,确保事件在一定间隔内触发;防抖适用于需要等待用户停止操作后才执行的场景,例如搜索建议、输入框验证。

简而言之:在初始化时,节流会立即执行一次事件处理函数,而防抖不会在初始化时执行。防抖只有在触发事件后,并且在指定的等待时间内没有再次触发事件时才会执行事件处理函数。这是两者之间的关键区别。

4.代码实现

function throttle(fn, delay) {
  let lastExecutionTime = 0;
  return function(...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecutionTime >= delay) {
      fn(...args);
      lastExecutionTime = currentTime;
    }
  };
}
function debounce(fn, delay) {
    let timer;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(() => {
        fn(...args);
        }, delay);
    };
}

6、什么是闭包?闭包有什么问题?什么是内存泄漏?哪些操作会造成内存泄漏?怎么解决内存泄漏?

闭包就是能够读取其他函数内部变量的函数,利用闭包可以突破作用域链。使用闭包主要是为了设计私有的方法和变量,闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄漏。

内存泄漏:指对象在不需要使用它时仍然存在,导致占用的内存不能使用或回收

  • 未使用 var 声明的全局变量
  • 闭包函数(Closures)
  • 循环引用(两个对象相互引用)
  • 控制台日志(console.log)
  • 移除存在绑定事件的DOM元素(IE)
  • setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏
  • 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环的,那么该对象的内存即可回收

解决内存泄漏:在退出函数之前,将不适用的局部变量全部删除


7、说说你对作用域链的理解

作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止。作用域链向下访问变量是不被允许的。


8、ajax原理?有哪些优缺点?

原理:

        简单来说是在用户和服务器之间加了一个中间层(AJAX引擎),通过XmlHttpRequset对象来向服务器发异步请求,从服务器获得数据,然后用js来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据。

Ajax的过程只涉及js,XMLHttpRequest和DOM。XMLHttpRequest和ajax的核心机制。

/** 1. 创建连接 **/
var xhr = null;
xhr = new XMLHttpRequest()
/** 2. 连接服务器 **/
xhr.open('get', url, true)
/** 3. 发送请求 **/
xhr.send(null);
/** 4. 接受请求 **/
xhr.onreadystatechange = function(){
	if(xhr.readyState == 4){
		if(xhr.status == 200){
			success(xhr.responseText);
		} else { 
			/** false **/
			fail && fail(xhr.status);
		}
	}
} 

 优点:

· 通过异步模式,提升了用户体验

· 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用

· Ajax在客户端运行,承担了一部分本来由服务器承担的工作,减少了大用户量下的服务器负载。

· Ajax可以实现动态不刷新(局部不刷新)

缺点:

· 安全问题ajax暴露了与服务器交互的细节 

· 对搜索引擎的支持比较弱

· 不容易调试


9、javascript创建对象的几种方式?

· 对象字面量的方式

var person={
        firstname:"Mark",
        lastname:"Yun",
        age:25,
        eyecolor:"black"
}; 

·  用function模拟构造函数来实现

function Pet(name,age,hobby){
       this.name=name;//this作用域:当前对象
       this.age=age;
       this.hobby=hobby;
       this.eat=function(){
           alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
       }
}
var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象
maidou.eat();//调用eat方法 

· 用工厂方式来创建 

var wcDog =new Object();
     wcDog.;
     wcDog.age=3;
     wcDog.work=function(){
       alert("我是"+wcDog.name+",汪汪汪......");
     }
     wcDog.work(); 

· 用原型方式来创建

function Dog(){}
Dog.prototype.;
Dog.prototype.eat=function(){
	alert(this.name+"是个吃货");
}
var wangcai =new Dog();
wangcai.eat(); 

10、数组去重方法总结

· Set

const fn = arr => {
    console.log(Array.from(new Set(arr)))
}

· 双层for循环,+splice()

const fn = arr => {
    for(var i = 0; i<arr.length; i++) {
        for(var j = i + 1; j< arr.length; j++){
            if(arr[i] === arr[j]) {
                arr.splice(j, 1);
                j--;
            }
        }
    }
    console.log(arr)
}

· indexOf()

const fn = arr => {
    var newArr = [];
    for (var i = 0; i < arr.length; i++) {
        if (newArr.indexOf(arr[i]) === -1) {
            newArr.push(arr[i])
        }
    }
    console.log(newArr)
}

· sort()

· 利用对象的属性不能相同的特点进行去重

· includes

· hasOwnProperty

· filter

const fn = arr => {
    const newArr = arr.filter((item, index, arr) => {
        return arr.indexOf(item, 0) ===index;
    })
    console.log(newArr)
}

· 递归

· Map

const fn = arr => {
    const map = new Map();
    let newArr = [];
    for(i = 0; i < arr.length; i++) {
        if(map.has(arr[i])) {
            map.set(arr[i],true);
        } else {
            map.set(arr[i], false);
            newArr.push(arr[i]);
        }
    }
    console.log(newArr)
}

· reduce+includes

· [...new Set(arr)]


11、ES6新特性总结 

1、let和const

避免用var声明变量引发变量提升的问题。

var定义一个变量的时候,它会成为包含它的函数的局部变量。去掉var关键字,会成为全局变量,不易维护。var声明的变量会变量提升。

let声明的变量的作用域为块级作用域。例如if的花括号。而var声明的变量的作用域为函数作用域。且在同一块内不允许多次声明。let没有变量提升 

var会成为window的对象,而let不会。如果用for循环,var声明的变量会渗透到循环体外,而let不会有这个问题。 

const 与let基本相同,也是块级作用域,但是const声明常量,尝试修改const声明的变量会出错。const如果声明的是一个对象,则修改这个对象内部的属性值不会报错 

2、模块化

用export 导出,import {} from导入

3、解构

数组解构:

const arr = [1,2,3,4]

let [a,b] = arr;  //相当于把1赋值给a,把2赋值给b

let [a, , , b] = arr; //相当于把1赋值给a,把4赋值给b

let [a,...b] = arr; // a =1 ; b = [2,3,4] 扩展运算符只能出现在数组的最后

对象解构:

let obj = {a:1,b:2};

let {x,y} = obj; //undefined;undefined  *因为对象解构必须和key值相等

let {a,b} = obj; // 1 ,2

let {b,a} = obj; //2,1 *顺序没有关系

let {a:x,b:y} = obj; // ab均报错,x =1;y=2 *使用冒号给原对象修改key值


let obj = {a: 1, b: 2, c: 3};
let {a, ...b} = obj;  //a 1  b {b: 2, c: 3}

 4、扩展运算符

5、支持对象属性的简写

const userInfo = {
   name:name,
   age:age,
   city:city
}

可以简写成

const userInfo = {
   name,
   age,
   city
}

6、async、await

7、includes方法

let arr = ['si','sixuetang','mlh'];

if(arr.includes('sixuetang')) {
    //...
}

8、指数操作符

2**10 => 就是2的10次方

9、Object.keys  Object.values Object.entri

const obj = {a:1,b:2,c:3}

const keys = Object.keys(obj);  //[a,b,c] 返回对象的key值集合

const values = Object.values(obj);  //[1,2,3] 返回对象的value值集合

const entries = Object.entries(obj); //[[a,1], [b,2], [c,3]] 返回对象的键值对集合

10、模板字符串

const name = 'sixuetang';

const age = 18;

const myInfo = `my name is ${name}, my age is ${age}`

12、type of 和instance of怎么区分数据类型?

type of 对于原始类型来说,除了bull都可以显示正确的类型。但是它对于对象来说,除了函数都会显示Object,所以说typeof并不能准确判断变量到底是什么类型。如果像判断一个对象的正确类型,这时候可以考虑使用instanceof。因为内部机制是通过原型链来判断的。

typeof 判断不出来是对象还是数组

instanceof内部机制是通过判断对象的原型链中是不是能找到类型的prototype


13、原型链 


14、token

前端登录的时候,向服务端发起请求。

服务器验证成功会生成一个token,前端就会存储这个token => Local storge。之后服务器收到用户的请求后,验证这个token成功以后,就允许获取请求数据。*服务器应支持跨域。

引用

虚拟 DOM 详解_虚拟dom-CSDN博客

Promise难懂?一篇文章让你轻松驾驭_promise也不太懂-CSDN博客

深拷贝与浅拷贝_深拷贝和浅拷贝的区别-CSDN博客

节流(throttling)和防抖(debouncing)_防抖和节流-CSDN博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值