面试题JS篇

文章目录


参考资料

Js 基本数据类型有哪些

7:string,number,boolean,null,undefined,object,symbol、BigInt(ES6引入)

其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
Symbol代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
BigInt 是一种数字类型的数据,它可以表示 任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,能表示数字的大小 只收内存限制
这些数据可以分为原始数据类型和引用数据类型(复杂数据类型),他们在内存中的存储方式不同。
堆: 存放引用数据类型,引用数据类型占据空间大、大小不固定。如Object、Array、Function。
栈: 存放原始数据类型,栈内存是静态分配的,占据空间确定,如String、Number、Null、Boolean。栈的分配和释放是由系统自动管理的,通常遵循先进先出的原则。

Ajax 如何使用

ajax是什么?
实现在 不刷新整个页面的情况下更新部分内容 的技术

一个完整的 AJAX 请求包括以下五个步骤:

  1. 创建 XMLHttpRequest 对象,var xhr = new XMLHttpRequest();
  2. 设置请求参数 xhr.open(“GET”, “https://example.com/api/data”, true);
  3. 设置回调函数
xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    // 处理服务器响应的逻辑
    console.log(xhr.responseText);
  }
};
  1. 发送请求 xhr.send();
  2. 更新页面

ajax、axios、fetch的区别

ajax

  • 不是一个具体的 API,而是一种使用现有浏览器技术的异步通信技术。
  • 主要基于 XMLHttpRequest 对象,这是一个浏览器提供的原生 API
  • 比较老,一些库比如jquery基于这种技术进行封装$.ajax

fetch

  • Fetch 是浏览器提供的较新的原生 API
  • 它提供了更现代、更简洁的接口,基于 Promise,更容易使用和理解。

axios[👌]

  • axios是一个独立的第三方库
  • 基于 Promise,支持链式调用,使代码更清晰易读。
  • Axios 提供了拦截器(interceptors)的概念,可以在请求和响应阶段添加自定义逻辑,如在请求发送前或响应返回前进行处理。这使得全局配置和修改请求/响应更为方便。
  • Axios 在请求和响应时自动处理数据的转换,如 JSON 数据的解析。而在使用 Fetch 时,你需要手动处理这些事务。
  • 更丰富的配置选项

如何判断一个数据是 NaN?

  • isNaN()函数
  • 对字符串,这个函数会把字符串转为数字,产生误判
  • 解决:typeof value === ‘number’ && isNaN(value)

Js 中 null 与 undefined 区别

  • null:空值,主动赋值了一个空指针,可以用于清空对象引用;
  • und:声明了但没有赋值,表示缺少
  • Number(null) 的结果是 0。Number(undefined) 的结果是 NaN

闭包是什么?有什么特性?对页面会有什么影响

是什么
是一种语言特性:在一个函数作用于内部定义的函数,能够访问该作用域内的变量,当这个内部函数在其他地方被调用时,依然可以访问那个作用域的变量。
特点
1.函数嵌套函数。
2.函数内部可以引用外部的参数和变量。
3.内部函数所引用的参数和变量不会被垃圾回收机制回收。
优点
1.用于封装私有变量,隐藏细节,暴露接口
2.防止全局变量的滥用和污染
3.模块化,将一组相关的功能封装在一个单独的闭包中,使其具有独立的作用域,从而提高代码的可维护性和可重用性。
模块化示例
var module = (function() {
    var privateVariable = "I am private";

    return {
        publicMethod: function() {
            console.log(privateVariable);
        }
    };
})();

module.publicMethod(); // 输出:I am private

缺点:
内存泄露:程序中已经不再需要使用的内存没有被正确释放或回收,导致系统的可用内存减少,最终可能影响系统的性能

JS中模块化的方法

  1. COMMONJS(node.js上,是一种服务器端的模块化规范)使用的 requiremodule.exports
    // 在模块中导出
    var privateVariable = "I am private";
    module.exports = {
        publicMethod: function() {
            console.log(privateVariable);
        }
    };
    
    // 在另一个文件中导入
    var myModule = require('./myModule');
    myModule.publicMethod();
    
  2. ES6模块(浏览器上的模块化规范):importexport
    // 在模块中导出
    const privateVariable = "I am private";
    export const publicMethod = function() {
        console.log(privateVariable);
    };
    
    // 在另一个文件中导入
    import { publicMethod } from './myModule';
    publicMethod();
    

Js 中常见的内存泄漏

  1. 未及时清理定时器:定时器如果没有被清理,将一直保持对回调函数的引用
  2. 未解绑事件监听:如果在DOM元素被移除之前没有解绑事件监听,那么事件处理函数将一直存在于内存中,导致内存泄漏。使用 removeEventListener 解绑事件。
  3. 大对象未及时释放:在不再需要使用大型数据对象时,手动释放或将其设置为 null。
  4. 代码中无意间、不经意间创建的全局变量
  5. 闭包未释放
function createClosure() {
  var data = new Array(10000); // 大型数据结构

  return function() {
    // 使用 data
    console.log(data.length);
  };
}

var closure = createClosure(); // 创建闭包

// 在外部使用 closure,但未解除对 data 的引用
closure();

// 假设不再需要使用 closure,但没有解除对闭包的引用
// 这将导致 data 一直存在于内存中,无法被垃圾回收

什么是事件冒泡?

事件会从触发事件的元素一直冒泡到最外层的祖先元素。
例如父子都有点击事件,点儿子,先儿子输出,后父亲输出。

如何阻止事件冒泡?

  1. stopPropagation
  2. preventDefault
e.addEventListener('click',function(event){
	event.stopPropagation()
	event.preventDefault()//或
})

事件委托是什么?如何确定事件源?

  • 利用冒泡
  • 将事件监听器添加到父元素上,减少了对每个子元素都添加监听器的工作量。
  • 使用 event.target 确定事件源

对比Cookie、localStorage、sessionStorage的异同

都是用于在客户端存储数据的机制

特性CookielocalStoragesessionStorage
存储容量小(通常4KB)通常 5MB通常 5MB
生命周期可设置过期时间永久存储,除非手动清除页面关闭后自动清除
发送到服务器每次请求都会将 Cookie 数据包含在请求头中,如果 Cookie 中包含大量数据,会导致更多的带宽消耗不会自动发送不会自动发送
作用域可设置作用域全域名共享单个页面共享
安全性非常低,可能被劫持低,但相对较安全低,但相对较安全
主要用途跨页面通信、认证信息本地存储,长期数据本地存储,会话级别数据

ES6 新特性

  1. let 和 const 声明:

    • 引入了 letconst 关键字,用于声明块级作用域的变量和常量。
  2. 箭头函数(Arrow Functions):

    • 提供了更简洁的函数声明语法,同时修复了传统函数中 this 关键字的一些问题。
  3. 模板字符串(Template Strings):

    • 使用反引号 `` 包裹字符串,支持多行字符串和变量插值。
  4. 解构赋值(Destructuring Assignment):

    • 允许通过模式匹配从数组或对象中提取值并赋给变量。
  5. 对象字面量的扩展:

    • 支持简写方法声明、计算属性名、以及 Object.assign 方法用于对象的浅复制。
  6. 类(Classes):

    • 引入了类的概念,提供了更简洁、面向对象的语法。
  7. Promise 对象:

    • 提供了更方便的异步编程模式,解决了回调地狱的问题。
  8. 模块化(Modules):

    • 引入了对模块的官方支持,允许将代码分割为多个文件,使项目更易维护和组织。
  9. 迭代器和生成器(Iterators and Generators):

    • 提供了迭代器协议和生成器函数,使遍历数据结构更加灵活和可控。
  10. Symbol 数据类型:

    • 引入了一种新的基本数据类型 Symbol,用于创建唯一的标识符,通常用于对象属性的键名。const mySymbol = Symbol(‘key’); myObject[mySymbol] = ‘value’;
  11. Set 和 Map 数据结构:

    • 引入了 SetMap,分别用于存储唯一值和键值对,提供了更高效的数据存储和查找。
  12. 默认参数值:

    • 允许在函数声明时为参数提供默认值。
  13. Rest 和 Spread 操作符:

    • ... 用于表示剩余参数(Rest),或将数组或对象展开为参数序列(Spread)。

for…of 和 for…in的区别

  • for…in 循环用于遍历对象的可枚举属性,迭代的是对象的键名(属性名)
  • for…of 循环是用于遍历可迭代对象(如数组、字符串、Set、Map 等),迭代的是对象的,但是for-of不会遍历普通对象!普通对象默认不可迭代。
  • for… in 会遍历对象的整个原型链,性能非常差不推荐使用

Let 与 var 与 const 的区别

特性varletconst
声明提升(声明提到作用域顶部)是(只提升声明 赋值undefined)是(但不可访问-死区)是(但不可访问-死区)
块级作用域不是
重复声明允许不允许不允许
初始值可选必须初始化
适用范围函数作用域块级作用域块级作用域
用途旧版 JavaScript 语法,全局作用域变量块级作用域变量,替代 var块级作用域常量
可变性可以重新赋值可以重新赋值不能重新赋值,常量
临时死区(声明之前不让用)

数组方法有哪些请简述

方法描述返回值
arr.push()从数组末尾添加元素,返回添加后数组的长度新数组的长度
arr.pop()从数组末尾删除元素,只能删除一个,返回被删除的元素被删除的元素
arr.shift()从数组头部删除元素,只能删除一个,返回被删除的元素被删除的元素
arr.unshift()从数组头部添加元素,返回添加后数组的长度新数组的长度
arr.splice(i, n)从索引 i 开始删除 n 个元素,返回被删除的元素数组被删除的元素数组
arr.concat()连接两个数组,返回连接后的新数组连接后的新数组
str.split()将字符串转化为数组转化后的数组
arr.sort()将数组进行排序,默认按照字符顺序排序,返回排序后的数组排序后的数组
arr.reverse()将数组反转,返回反转后的数组反转后的数组
arr.slice(start, end)切取索引值 start 到索引值 end 之间的数组,不包含 end切取的数组
arr.forEach(callback)遍历数组,无返回值,可以修改原数组
arr.map(callback)映射数组,返回一个新数组,根据回调函数的返回值生成新元素新数组
arr.filter(callback)过滤数组,返回满足条件的新数组满足条件的新数组
改变原数组的方法:
fill()、填充
pop()、push()、shift()、unshift()、
reverse()、sort()、splice() 反转、排序、切割
foreach 遍历
forEach和map方法有什么区别
map不修改元数组

Json 如何新增/删除键值对

直接修改 JSON 字符串是不可行的,首先需要将其解析成 JavaScript 对象,然后对对象进行修改,最后将修改后的对象转换回 JSON 字符串。

JSON ->OBJ
JSON.parse()
OBJ->JSON
JSON.stringify()
  • 增:直接赋值新增键值对,myObject.newKey = ‘newValue’;
  • 删:使用 delete 操作符删除键值对 delete myObject.key1;

改变函数内部 this 指针的指向函数(bind,apply,call 的区别)

  • 都是用于改变this指向
  • bind不会立即执行而是返回一个新的函数
  • apply和call立即执行,apply第二个参数传入参数数组array,call的第二个参数直接传入参数
var obj = { value: 42 };

function getValue() {
    console.log(this.value);
}

var boundFunction = getValue.bind(obj,1, 2, 3);
boundFunction(); // 输出:42

getValue.apply(obj, args); 
getValue.call(obj, 1, 2, 3);

手写bind

学习视频

  1. 手写apply
    其实本质上就是把函数a放到person里面,执行一下然后删除掉
    function a(gender){
      console.log(`this ${gender}'s name is ${this.name}`);
      return {
        name:this.name,
        age:this.age
      }
    }
    const person = {
      name:'xiaoming',
      age:'15'
    }
    // a.apply(person,['boy'])
    Function.prototype.newApply = function(obj){
      let obj=obj||window
      let newArguments = [];
      for(let i=1;i<arguments.length;i++){
        newArguments.push(arguments[i])
      }
      //最重要的逻辑
      obj.tempFunc = this;
      let res=obj.tempFunc(...newArguments);
      delete obj.tempFunc;
      return res;
    }
    let b=a.newApply(person,['boy'])
    console.log(b)
    
  2. 手写bind
	function a(gender){
	  console.log(`this ${gender}'s name is ${this.name}`);
	  return {
	    name:this.name,
	    age:this.age
	  }
	}
	const person = {
	  name:'xiaoming',
	  age:'15'
	}
//   b=a.bind(person,'boy')
// b()
	Function.prototype.newBind=function(obj){
    console.log(this)
    let that=this //保存this,也就是调用这个newbind的函数
		//因为下面在函数里面用,this指向可能会发生变化
		let newArgus = [...(Array.from(arguments).slice(1))] 
		//注意这里arguments是个对象,要先转化为数组
    return function(){
      that.apply(obj,newArgus)//核心代码
    }
	}
a.newBind(person,'boy')()

优化后的:
在这里插入图片描述

window和document的关系?

document 表示当前加载的 HTML 文档。
它是 window 对象的属性之一,可以通过 window.document 或简写为 document 来访问

什么是面向对象请简述

  • 是一种编程思想
  • 把程序看做是对象相互作用的过程
  • 每个对象都有自己的属性和方法,并能够与其他对象交互
  • 类:是对象的模板,描述了对象的共同特征和行为。对象是类的实例,具体的实体
  • 三个特点:
  • 1.封装:把属性和方法封装在一个单元内,隐藏了对象的具体实现细节,只暴露必要的接口。提高代码的可维护性和可重用性。
  • 2.继承:允许 子继承父类的属性和方法,便于共享和扩充已有的代码。
  • 3.多态:不同对象的行为可以用相同的方法名调用,但是具体操作不同(其实是废话)

普通函数和构造函数的区别

  1. 普通函数就是用于执行某个特定的任务或操作
  2. 构造函数: 用于创建和初始化对象,它在对象被实例化时被调用,常通过new关键字调用,在JavaScript中,所有的函数实际上都可以被用作构造函数,包括普通函数。当你使用 new 关键字调用一个函数时,该函数就被视为构造函数,用于创建一个新的对象实例
  • 构造函数内部的 this 指向实例,普通函数内部的 this 指向调用函数的
    对象
  • 构造函数习惯上首字母大写

补充-构造函数和类的区别:构造函数和类都用于创建对象和定义对象的属性和方法。都使用 new 关键字可以实例化构造函数或类,得到对象。现代开发中用“类”更多。

请简述原型/原型链/(原型)继承

  • 【先说明原型是什么】原型是对象的一个属性,指向一个父对象。每个对象都有这个属性,都继承父对象的属性和方法
  • 【再形象地解释一下】原型像一个说明书,假设你制造了很多相似的玩具,而它们都有一些共同的特征,比如都能发出声音。你可以把这个共同的特征写在一个“备用说明书”上,然后每个玩具都可以查阅这个说明书,知道怎么发声。原型是对象的属性和方法的集合
  • 【再关联到原型链】每个对象都有一个原型,而该原型又有自己的原型,依此类推,形成一条链。
  • 【补充说明一下作用】当你访问一个对象的属性或方法时,JavaScript 引擎会在原型链上查找,直到找到该属性或方法或者到达原型链的末尾
  • 【一个例外】在原型链的终点,Object.prototype 的原型是 null

操作方法

  • 实例对象本身是没有名为 prototype 的属性的,他们有的是__proto__
  • 只有函数有prototype属性,也可以通过Person.prototype=xxx来改变
  • 查看实例的原型:Object.getPrototypeOf(mydog)或者mydog.prototype
  • 设置实例的原型:Object.setPrototypeOf(myDog, newPrototype); 或者mydog.__proto__

请添加图片描述
构造函数的原型上的方法会被每一个实例方法继承!

function Person(){}
Person.prototype.name='gaga'
let p1=new Person()
let p2=new Person()
console.log(p1.name) //gaga
console.log(p2.name) //gaga

JS 的 new 操作符做了哪些事情?

  1. new 操作符新建了一个空对象,
  2. 这个对象原型(person.__proto__)指向构造函数的prototype(Person.prototype)
  3. 执行构造函数
  4. 返回这个对象

如何实现继承?/ 请写出一个简单的类与继承

暂时只记一种方法:ES6的class继承

class Animal{
	constructor(name){
		this.name=name;
	}
	sayName(){
		console.log('name is'+this.name)
	}
}
class Dog extends Animal {
	constructor(name.color){
		super(name);//在子类的构造函数中调用父类的构造函数,并传递 name 参数给父类的构造函数
		this.breed = breed;
	}
}

let mydog=new Dog('andy','black');
mydog.sayName();

Promise 的理解

  • Promise 是承诺的意思,承诺它过一段时间会给你一个结果
  • Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更强大
  • 从语法上讲,promise 是一个对象,从它可以获取异步操作的消息

三种状态

  1. pending等待
  2. fulfilled完成,调用Promise的then方法指定的回调函数,传递操作结果。
  3. rejected失败,调用Promise的catch方法指定的回调函数,传递失败原因。
    状态一旦改变,就不会再变

特点

  1. 外界无法直接影响Promise的状态,内部异步操作完成后通过resolve或reject改变状态
  2. 一旦Promise的状态从Pending转变为Fulfilled或Rejected,就不会再发生变化

缺点

无法取消: Promise一旦创建,就无法被取消。

用户输入关键词 “apple”。
开始搜索操作,但用户很快意识到输入错误,想要更改关键词为 “banana”。
由于无法取消前一个搜索操作,系统仍在尝试获取 “apple” 的搜索结果,而用户已经不再关心这个结果。

基本语法

const myPromise = new Promise((resolve,reject)=>{
	//一些操作
	if(成功){
		resolve(成功结果)
	}else{
		reject("失败原因")
	}
)

Promise方法有哪些?

promise.then()
promise.catch()
promise.all()
promise.race
promise.allSettled() :返回一个promise, 解析为一个包含所有 Promise 状态的数组。

const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);

allSettledPromise.then((results) => {
  console.log(results);
  /*
    [
      { status: 'fulfilled', value: 42 },
      { status: 'rejected', reason: 'Oops!' },
      { status: 'fulfilled', value: 'Delayed' }
    ]
  */
});

我们用 Promise 来解决什么问题?

  1. 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
  2. 多并发的异步操作: 使用Promise.all()可以并行执行多个异步操作,当所有操作完成时,Promise.all()返回一个包含所有结果的Promise。这种并发执行的方式可以提高异步操作的效率。
  • Promise.all() 接受一个包含多个Promise对象的数组,并返回一个新的Promise。这个新的Promise在传入的所有Promise对象都成功解决时才会成功解决
  • Promise.race() 同样接受一个包含多个Promise对象的可迭代对象,但与Promise.all()不同的是,它在传入的Promise对象中的任何一个解决或拒绝时就会立即解决或拒绝。
Promise.all([promise1, promise2, promise3])
  .then(values => {
    console.log(values); // [1, 2, 3]
  })
  .catch(error => {
    console.error(error);
  });

介绍一下async/await

  • async/await是JavaScript中用于处理异步代码的一种语法糖
  • 一个使用async关键字定义的函数会返回一个Promise对象。在函数体内,你可以使用await关键字等待一个Promise解决,并且在等待期间代码会暂停执行,直到Promise被解决(Fulfilled)或拒绝(Rejected)。
  • 使用try-catch块可以直接捕获异步操作中的错误,而不需要通过回调函数或Promise的catch方法。

async/await对比Promise的优势

  • async/await 建立在 Promise 的基础上,使得异步代码更易读、更易写
  • async/await 语法使异步代码看起来更像同步代码,减少了回调嵌套
// 使用 Promise
function example() {
  return asyncOperation()
    .then(result => {
      return anotherOperation(result);
    })
    .then(finalResult => {
      console.log(finalResult);
    })
    .catch(error => {
      console.error(error);
    });
}

// 使用 async/await
async function example() {
  try {
    const result = await asyncOperation();
    const finalResult = await anotherOperation(result);
    console.log(finalResult);
  } catch (error) {
    console.error(error);
  }
}

一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

  1. dns请求。UDP,dns服务器从域名服务开始进行递归搜索,找到ip地址
  2. TCP三次握手
  3. 发送一个http请求,基于tcp
  4. 服务器监听80端口获取请求,发送响应数据。
  5. 浏览器解析
    深度遍历把HTML解析成DOM tree
    遇到script、link、style等会产生阻塞
    css解析渲染dom tree
    js解析

get 请求传参长度的误区

误区:我们经常说 get 请求参数的大小存在限制,而 post 请求的参数大小是无限制的。实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对 get 请求参数的限制是来源与浏览器或 web服务器,浏览器或 web 服务器限制了 url 的长度

说说前端中的事件流

  • 事件流表示 事件在页面中传播的顺序
  • DOM事件流有两个阶段:捕获阶段和冒泡阶段
捕获阶段
从根节点,沿着DOM树向目标元素传播
应用:在事件到达目标元素之前拦截事件,在用户点击某个区域之前执行一些验证操作
冒泡阶段
从目标元素开始,向根节点传播
应用:事件委托(又叫事件代理)

addEventListener() 方法的第三个参数来控制事件处理程序是在捕获阶段执行还是冒泡阶段执行。如果该参数为true,则事件在捕获阶段执行;如果为false或省略,则事件在冒泡阶段执行

解释一下什么是 Event Loop

  1. 【是什么】Event Loop,简单翻译就是 事件循环,是 JS 语言下实现运行时的一个机制
  2. 【解决什么问题】JS采用这种机制,来解决单线程运行带来的一些问题
  3. JS是一种单线程语言,所有任务都在一个线程上完成
  4. 遇到耗时的任务,就会有很多闲置资源因为等待而浪费
  5. 【怎么解决的】浏览器环境中JS引擎运行在主线程上,同时,浏览器还有其他线程,比如说用户IO线程、HTTP请求线程、定时器线程等等,主线程执行同步任务,当主线程遇到IO操作等异步任务时,就会将异步任务交给其他线程处理,当异步任务完成时,会将结果的回调函数返回给消息队列中,等待主线程执行。这些线程的协作是通过事件循环机制来完成的。
  6. 【具体实现细节】异步任务又分为宏任务和微任务,宏任务的执行时机是在主线程空闲时,事件循环从宏任务队列中取出一个宏任务执行。微任务会在当前任务执行结束后、下一个任务开始前执行,并且优先级比宏任务高

clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop 的区别?

offsetHeight=内容高度+padding+border
偏移高度,即这个元素总共占了多少空间,但注意不包括margin,maigin不算是这个元素里面的,而是元素周围的空白
offset属性是相对于offsetParent元素偏移的数值,offsetParent是距离最近的一个被定位的父元素
在这里插入图片描述
在这里插入图片描述

clientHeight=内容高度+padding
请添加图片描述
scrollHeight=实际内容高度+padding


offsetTop=margin-top
clientTop=border-top
scrollTop提供内容已滚动的距离

鼠标的位置

  • offsetX,offsetY:相对于监听鼠标事件的元素而言的左/上距离
  • clientX/Y:相对于浏览器窗口
  • 原点都在左上角

JS 拖拽功能的实现

使用h5的drop API

<body>

<div class="draggable" draggable="true" id="draggableElement">拖我</div>
<div class="droppable" id="droppableElement">放在这里</div>

<script>
  var draggableElement = document.getElementById('draggableElement');
  var droppableElement = document.getElementById('droppableElement');

  // 开始拖动时触发
  draggableElement.addEventListener('dragstart', function(e) {
    e.dataTransfer.setData('text/plain', 'Hello, Drag!'); // 设置拖动数据
  });

  // 在拖动目标上移动时触发
  droppableElement.addEventListener('dragover', function(e) {
    e.preventDefault(); // 阻止默认行为,允许放置
  });

  // 放置目标上释放时触发
  droppableElement.addEventListener('drop', function(e) {
    e.preventDefault(); // 阻止默认行为,允许放置
    var data = e.dataTransfer.getData('text/plain'); // 获取拖动数据
    console.log('拖放的数据:', data);
  });
</script>

JS的垃圾回收机制

JavaScript 使用自动垃圾回收(Garbage Collection)机制来管理内存。这意味着开发者不需要手动释放不再使用的内存,而是由 JavaScript 引擎自动完成。这有助于避免内存泄漏和提高开发效率

垃圾回收的基本原理是找到不再被引用的对象,然后释放它们占用的内存。下面是 JavaScript 中垃圾回收的一些关键概念:

有哪些垃圾回收策略?

  • 引用计数
    该策略通过在对象上维护一个引用计数器来跟踪对象的引用次数。当引用计数变为零时,对象被认为是不再被引用的,即可被垃圾回收。
    缺点是无法解决循环引用的问题,因为循环引用的对象的引用计数永远不会变为零。
  • 标记清除(Mark and Sweep)
    从根对象(一般是全局对象)开始,标记所有可以从根对象访问到的对象。这通常涉及到遍历变量、函数作用域、闭包、以及全局作用域中的所有引用。
    在标记阶段完成后,所有未被标记的对象被认为是不再被引用的,即不可达的对象。这些对象将被清除(回收),释放其占用的内存。

DOM VS BOM

  • DOM 树是文档对象模型的表示,它反映了 HTML 或 XML 文档的层次结构,将文档的内容表示为一个树形结构。在 DOM
    树中,每个HTML 或 XML元素、属性、文本均被表示为一个节点(Node)。DOM树的根节点是文档节点(Document),它包含整个文档的内容。
  • BOM树表示浏览器窗口和浏览器提供的一些对象(非文档对象)。在 BOM 树中,常见的对象包括window、screen、location、history 等。window 对象是 BOM 树的根节点,它代表了整个浏览器窗口。

JS 监听对象属性的改变

方法1:使用Object.defineProperty修改对象属性的内部特性

// 创建一个对象
let myObj = {};

// 在对象上定义属性 'myProperty'
Object.defineProperty(myObj, 'myProperty', {
  get: function() {
    return this._myProperty;
  },
  set: function(value) {
    this._myProperty = value;
    console.log('myProperty 被修改了:', value);
  },
  enumerable: true,
  configurable: true
});

// 使用该属性
myObj.myProperty = 42; // 触发 set 方法,并输出 'myProperty 被修改了: 42'
console.log(myObj.myProperty); // 触发 get 方法,并输出 42

方法2:Proxy
//以后再学

JS 怎么控制一次加载一张图片,加载完后再加载下一张

使用promise

//图片的地址数组
const imgsArr = ['img1',....]
//下载图片的函数
function loadImg(src){
	return new Promise((resolve, reject)=>{
		const img = new Image();
		img.src = src;
		img.onload = ()=>resolve(img)//加载成功的回调函数
		img.onerror = reject; //加载失败的回调
	})
}

//按顺序下载图片
async function loadImgSeq(){
	for(const src of imgArr){
		const img = await loadImg(src);
		imageDIV.appendChild(img);
	}
}

深拷贝VS浅拷贝?

  • 浅拷贝只复制对象本身以及对象内的基本数据类型,而对于引用类型的元素仅复制引用而不复制实际数据 (直接newobj=oldObj即可)
  • 深拷贝会复制所有嵌套的引用类型比如对象和数组,新复制的对象上的任何更改都不会影响旧对象,两者完全独立。(需要通过一些库来实现)

实现 JS 中所有对象的深度克隆(包装对象,Date 对象,正则对象)

function deepClone(obj, visited = new WeakMap()) {
  // 检查是否为基本数据类型,如果是,则直接返回
  if (obj === null || typeof obj !== 'object' || obj instanceof Date || obj instanceof RegExp) {
    return obj;
  }

  // 检查是否已经访问过该对象,防止循环引用
  if (visited.has(obj)) {
    return visited.get(obj);
  }

  // 创建一个新的对象或数组
  let clone;
  if (obj instanceof Date) {
    clone = new Date(obj.getTime());
  } else if (obj instanceof RegExp) {
    clone = new RegExp(obj);
  } else if (Array.isArray(obj)) {
    clone = [];
  } else {
    clone = Object.create(Object.getPrototypeOf(obj));
  }

  // 记录已经访问过的对象,以便处理循环引用
  visited.set(obj, clone);

  // 递归地深度克隆对象的属性
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key], visited);
    }
  }

  return clone;
}

代码解释:

  1. 基本数据类型: 基本数据类型(如字符串、数字、布尔值等)在 JavaScript 中是按值传递的,而不是按引用传递的。因此,对基本数据类型进行赋值或传递时,会直接复制其值而不涉及引用。所以,不需要对基本数据类型进行深拷贝。上述实现中的条件语句 if (obj === null || typeof obj !== 'object' || obj instanceof Date || obj instanceof RegExp) 就是用来检查是否为基本数据类型,如果是,则直接返回原始值。

  2. 循环引用: 循环引用指的是对象包含对自身的引用,形成一个循环链。在深度克隆时,如果不处理循环引用,可能导致无限递归,最终导致堆栈溢出或其他性能问题。使用 WeakMap 来记录已经访问过的对象,可以在发现循环引用时停止递归,避免陷入无限循环。

  3. obj.hasOwnProperty(key) 这是为了确保只复制对象自身的属性,而不包括从原型链继承的属性。在 JavaScript 中,对象的属性可以分为自身属性和继承属性。使用 hasOwnProperty 方法可以判断一个属性是否为对象自身的属性,而不是从原型链上继承的属性。这是为了避免复制不必要的属性,只复制对象自身的属性。

  4. WeakMap 是 JavaScript 中的一种集合类型,它允许将对象作为键存储在映射中。与 Map 不同,WeakMap 中的键只能是对象,而值可以是任意类型。一个 WeakMap 中的键是弱引用的,这意味着如果对象作为键不再被引用,它可以被垃圾回收器回收,从而释放相应的键值对。

能来讲讲 JS 的语言特性

JavaScript 是一门多范式的编程语言,主要用于在网页上实现交互性。它具有一些独特的语言特性,以下是其中一些主要的特点:

  1. 动态类型: JavaScript 是一门动态类型的语言,变量的数据类型可以在运行时动态改变。这意味着你可以在不事先声明变量类型的情况下直接赋值。
  2. 弱类型: JavaScript 是一门弱类型语言,也被称为动态弱类型语言。这意味着在进行一些操作时,JavaScript 会进行隐式类型转换,而不会引发错误。
  3. 原型继承: JavaScript 使用原型继承作为对象之间共享和复用代码的机制。每个对象都有一个原型对象,而对象可以继承原型对象的属性和方法。
  4. 跨平台性: JavaScript 不仅用于浏览器端,还可以通过 Node.js 在服务器端运行。这使得 JavaScript 成为一种具有广泛用途的通用编程语言。
  5. 脚本语言、解释性语言: 脚本语言的一个特点是可以逐行地执行,而不需要事先将整个程序编译成机器码。解释性语言,是因为它的代码在运行时由解释器逐行解释执行。

为什么会造成跨域/请简述同源策略?

同源策略(Same-Origin Policy)是浏览器的一种安全机制,用于防止恶意网站通过脚本等方式进行恶意请求。同源策略规定,两个页面只有在协议、主机和端口均相同的情况下,才属于同一个源(origin)。
举例一种如果没有同源策略,可能会发生的情况: 恶意网站读取了cookie后,可以使用用户的身份信息发起对银行网站的恶意请求

请输出三种减少页面加载时间的方式

  1. 压缩图片/视频音频质量
  2. 懒加载
  3. 异步加载,不阻塞:使用async或defer属性加载JavaScript文件,以确保它们不会阻止页面的渲染。
    <script defer src="example.js"></script>
  4. 使用 CDN 托管资源(使用 CDN Content Delivery Network,内容分发网络 将静态资源放在全球多个服务器上,CDN 会根据用户的地理位置自动选择距离用户最近的服务器提供资源)

什么是 jsonp 工作原理是什么?他为什么不是真正的 ajax

Jsonp 其实就是一个跨域解决方案。
Js 跨域请求数据是不可以的,但是 js 跨域请求 js 脚本是可以的。
jsonp 原理:(动态创建 script 标签,回调函数)
浏览器在 js 请求中,是允许通过 script 标签的 src 跨域请求,可以在请求的结果中添加回调方法名,在请求页面中定义方法,就可获取到跨域请求的数据。

// 客户端定义的回调函数
function handleResponse(data) {
  // 处理从服务器返回的数据
  console.log(data);
}

// 创建一个 <script> 元素
const script = document.createElement('script');

// 设置 <script> 元素的 src 属性,包含 JSONP 请求的 URL,并指定回调函数名
script.src = 'https://example.com/data?callback=handleResponse';

// 将 <script> 元素添加到页面中,触发跨域请求
document.body.appendChild(script);

他为什么不是真正的 ajax
ajax 的核心是通过XmlHttpRequest获取本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的 js 脚本。

请掌握 2 种以上数组去重的方式

  1. set
const uniqueArray = [...new Set(originalArray)];
  1. filter
const uniqueArray = originalArray.filter((value, index, self) => {
  return self.indexOf(value) === index;
});

箭头函数与普通函数的区别

箭头:没有原型属性,不能作为构造函数,不存在arguments,没有自己的this,this指向这个函数的上下文作用域;
普通函数:有arguments对象,可以作为构造函数,this指向调用这个函数的对象,没有被调用时,指向全局对象;

function example() {
  console.log(arguments.length); // 参数个数=0
}

常见的 HTTP状态码

  1. 信息性状态码(Informational Codes):

    • 100 Continue: 表示服务器已经接收到请求的部分,但仍然等待客户端发送剩余的请求。
  2. 成功状态码(Successful Codes):

    • 200 OK: 表示请求已成功。
    • 201 Created: 表示请求已经被成功处理,并且服务器创建了新的资源。
    • 204 No Content: 表示服务器成功处理了请求,但没有返回任何内容。
  3. 重定向状态码(Redirection Codes):

    • 301 Moved Permanently: 表示被请求的资源已经永久移动到新位置。
    • 302 Found (Moved Temporarily): 表示被请求的资源已经临时移动到新位置。
    • 303 See Other:用于POST请求后的重定向,告诉客户端应该使用GET方法获取资源。
    • 304 Not Modified: 表示资源未被修改,可以使用缓存的版本。
  4. 客户端错误状态码(Client Error Codes):

    • 400 Bad Request: 表示客户端发送了一个服务器无法理解的请求。
    • 401 Unauthorized: 表示请求需要用户身份验证。
    • 403 Forbidden: 表示服务器理解请求,但拒绝执行。
    • 404 Not Found: 表示服务器无法找到请求的资源。
  5. 服务器错误状态码(Server Error Codes):

    • 500 Internal Server Error: 表示服务器遇到了一个未知的错误。
    • 501 Not Implemented: 表示服务器不支持当前请求所需要的某个功能。
    • 503 Service Unavailable: 表示服务器当前无法处理请求,通常是因为服务器过载或维护。

预加载和懒加载的使用场景

  • 预: 在用户打开商品详情页面前,提前加载商品的相关信息和评论,减少用户等待时间,提高用户体验。
  • 懒:在游戏开始时,只加载当前场景所需的资源,例如地图、角色和音效,以避免一开始就加载整个游戏的资源,提高游戏启动速度。

Js 的函数节流和函数防抖的区别

  • 用于控制函数调用频率的技术,用在一些高频触发的场景中
函数节流
函数节流是指在一定时间内,无论触发事件多少次,只执行一次函数。
场景:微博热搜,热搜词条可能以非常高的频率进行更新,可以使用防抖设置一分钟更新一次,而不是每次有新词条都要触发重新计算。
实现原理
function throttle(func, delay) {
    let lastTime = 0;
    return function() {
        const now = Date.now();
        if (now - lastTime >= delay) {
            func.apply(this, arguments);
            lastTime = now;
        }
    };
}
实际开发中使用的是lodash库中的throttle函数
import _ from 'lodash';
const newfunc = _.throttle(func, 500);
函数防抖
等待一定时间,如果在这个时间内没有新的触发事件,则执行函数;如果有新的触发事件,则重新计时。
场景:搜索,每次输入新的文字都会进行搜索内容,但是如果打字太快就会导致过多的搜索请求,就可以使用节流。
实现原理
function debounce(func, delay) {
    let timer;
    return function() {
        clearTimeout(timer);
        timer = setTimeout(() => {
            func.apply(this, arguments);
        }, delay);
    };
}
实际使用Lodash库中的debounce函数来实现函数防抖
const newfunc = _.debounce(func, 500);

介绍一下js中的计时器

// 在2秒后执行函数
const timeoutId = setTimeout(() => {
    console.log('Timeout executed!');
}, 2000);

// 取消定时执行
clearTimeout(timeoutId);
---------------------------------------------------------
// 每隔1秒执行一次函数
const intervalId = setInterval(() => {
    console.log('Interval executed!');
}, 1000);

// 取消重复执行
clearInterval(intervalId);

typeof instanceof区别

  • 都可以用于类型判断
  • typeof用于基本数据类型
  • 对于object没法精确分类,只能区别出function
  • typeof null 返回 “object”,这是一个历史遗留问题,不代表null实际上是对象
  • instanceof 返回一个布尔值,会顺着原型链检查这个对象是否是某个特性对象的实例
  • 一般用于判断数据arr instanceof Array;

正则表达式

1. 基本语法:

  • 字符匹配: 正则表达式中的普通字符表示自身,例如,abc 表示匹配 “abc”。

  • 元字符: 具有特殊含义的字符,例如 . 表示匹配任意字符,^ 表示匹配字符串的开始。

  • 字符类: 使用 [] 表示字符类,例如 [aeiou] 表示匹配任意一个元音字母。

2. 量词:

  • * 匹配前一个元素0次或多次。
  • + 匹配前一个元素1次或多次。
  • ? 匹配前一个元素0次或1次。
  • {n} 匹配前一个元素恰好n次。
  • {n,} 匹配前一个元素至少n次。
  • {n,m} 匹配前一个元素至少n次,最多m次。

3. 特殊字符:

  • \ 转义字符,用于匹配元字符本身,例如 \\ 表示匹配 \

  • | 或运算符,用于匹配多个选择中的一个,例如 a|b 表示匹配 “a” 或 “b”。

4. 边界:

  • ^ 匹配字符串的开始。
  • $ 匹配字符串的结束。

5. 预定义字符类:

  • \d 匹配任意数字(相当于 [0-9])。
  • \w 匹配任意字母、数字或下划线(相当于 [a-zA-Z0-9_])。
  • \s 匹配任意空白字符(包括空格、制表符等)。

6. 分组和捕获:

  • **():** 用于捕获匹配的内容,例如 (\d{3})` 可以匹配三个数字并捕获为一个分组。

示例:

// 匹配邮箱地址
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;

const email = "user@example.com";
if (emailRegex.test(email)) {
    console.log("邮箱地址有效");
} else {
    console.log("邮箱地址无效");
}

arguments、对类数组对象的理解,如何转化为数组

  • arguments是个类数组,本质上是个对象。
  • 箭头函数没有arguments,箭头函数中的arguments是父函数的
  • 如何转化为数组:Array.from(arguments)

为什么0.1+0.2 ! == 0.3,如何让其相等

  • 因为浮点数运算的精度问题。在计算机运行过程中,需要将数据转化成二进制,然后再进行计算。
  • 0.1和0.2已经超出了IEEE745浮点数能表达的最小值范围。

如何相等?

  • 将其先转换成整数,再相加之后转回小数。具体做法为先乘10相加后除以10
  • 使用number对象的toFixed方法,只保留一位小数点(n1 + n2).toFixed(2)

判断数组的方式有哪些

  • Array.isArrray(obj);
  • obj instanceof Array
  • obj.__proto__ === Array.prototype;

substring和substr的区别

  • 参数不同
  • substring(startIndex, endIndex)
  • substr(startIndex, length)

严格模式和正常模式

  • 严格模式通过在脚本或函数的开头添加 “use strict”; 来启用
  • 在严格模式下,一些在正常模式下潜在的错误会抛出异常,使得代码更加健壮。
  • 比如说,在正常模式下,如果没有使用 var、let 或 const 关键字声明变量,会自动创建一个全局变量。在严格模式下,这样的操作会引发错误。
  • 在严格模式下,delete 操作符用于删除对象的属性,但是删除变量或函数会引发错误。可以通过让变量引用 null 来释放变量。

Set、Map的区别

  • Set 和 Map 都是 ECMAScript 6 (ES6) 中引入的新的数据结构
  • Map是一种键值对的集合,和对象不同的是,键可以是任意值,键名唯一
  • Set是类似数组的一种的数据结构,类似数组的一种集合,值唯一
  • 添加数据操作,map.set(key,value),set.add(value),其他操作都一样

Map vs weakmap

  1. 键的类型:

    • WeakMap 的键只能是对象引用,而不能是基本数据类型(如字符串、数字)。
  2. 内存管理:

    • WeakMap 对象对键的引用是弱引用,这意味着如果对象在其他地方没有被引用,它在 WeakMap 中的引用可能被垃圾回收。
    • 当键对象被垃圾回收时,与之关联的键值对也会从 WeakMap 中自动删除。
  3. 迭代:

    • WeakMap 不提供直接的迭代方法,因为由于弱引用的特性,迭代所有键或值的操作是不可行的。
  • WeakMap 的适用场景:
    • 当需要在键是对象时,能够在对象被垃圾回收时自动清理关联的键值对时,使用 WeakMap
    • 当不需要迭代键值对,而只需根据键获取对应值时,可以考虑使用 WeakMap

对作用域、作用域链的理解

  • 作用域是一个变量或函数的可访问范围
  • 块级作用域是指在一对花括号 {} 内声明的变量或函数在这个块级范围内有效,超出这个范围就无法访问
  • 作用域链: 变量在指定的作用域中没有找到,会依次向一层作用域进行查找,直到全局作用域。这个查找的过程被称为作用域链。
  • var 关键字声明的变量具有函数作用域,而使用 let 和 const 关键字声明的变量则具有块级作用域。不声明的话则是全局(隐式全局变量

页面跳转的方式

js

window.location.href = "https://example.com/page";

小程序

wx.navigateTo({
  url: '/pages/otherPage/otherPage'
})
wx.redirectTo({
  url: '/pages/otherPage/otherPage'
})
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值