目录
- Js 基本数据类型有哪些
- Ajax 如何使用
- 如何判断一个数据是 NaN?
- Js 中 null 与 undefined 区别
- 闭包是什么?有什么特性?对页面会有什么影响
- JS中模块化的方法
- Js 中常见的内存泄漏
- 什么是事件冒泡?
- 如何阻止事件冒泡?
- 事件委托是什么?如何确定事件源?
- 对比Cookie、localStorage、sessionStorage的异同
- ES6 新特性
- for...of 和 for...in的区别
- Let 与 var 与 const 的区别
- 数组方法有哪些请简述
- Json 如何新增/删除键值对
- 改变函数内部 this 指针的指向函数(bind,apply,call 的区别)
- 手写bind
- window和document的关系?
- 什么是面向对象请简述
- 普通函数和构造函数的区别
- 请简述原型/原型链/(原型)继承
- JS 的 new 操作符做了哪些事情?
- 如何实现继承?/ 请写出一个简单的类与继承
- Promise 的理解
- 介绍一下async/await
- 我们用 Promise 来解决什么问题?
- 一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
- get 请求传参长度的误区
- 说说前端中的事件流
- 解释一下什么是 Event Loop
- clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop 的区别?
- 鼠标的位置
- JS 拖拽功能的实现
- JS的垃圾回收机制
- 有哪些垃圾回收策略?
- DOM VS BOM
- JS 监听对象属性的改变
- JS 怎么控制一次加载一张图片,加载完后再加载下一张
- 深拷贝VS浅拷贝?
- 实现 JS 中所有对象的深度克隆(包装对象,Date 对象,正则对象)
- 能来讲讲 JS 的语言特性
- 为什么会造成跨域/请简述同源策略?
- 请输出三种减少页面加载时间的方式
- 什么是 jsonp 工作原理是什么?他为什么不是真正的 ajax
- 请掌握 2 种以上数组去重的方式
- 箭头函数与普通函数的区别
- 常见的 HTTP状态码
- 预加载和懒加载的使用场景
- Js 的函数节流和函数防抖的区别
- 介绍一下js中的计时器
参考资料
Js 基本数据类型有哪些
7:string,number,boolean,null,undefined,object,symbol(ES6引入)
Ajax 如何使用
-
ajax是什么?
- 实现在 不刷新整个页面的情况下更新部分内容 的技术 与axios的区别?
- ajax是原生的(浏览器提供的 XMLHttpRequest 对象来实现),axios是对原生的一个包装
一个完整的 AJAX 请求包括以下五个步骤:
- 创建 XMLHttpRequest 对象,var xhr = new XMLHttpRequest();
- 设置请求参数 xhr.open(“GET”, “https://example.com/api/data”, true);
- 设置回调函数
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
// 处理服务器响应的逻辑
console.log(xhr.responseText);
}
};
- 发送请求 xhr.send();
- 更新页面
如何判断一个数据是 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中模块化的方法
- COMMONJS(node.js上,是一种服务器端的模块化规范)使用的
require
、module.exports
// 在模块中导出 var privateVariable = "I am private"; module.exports = { publicMethod: function() { console.log(privateVariable); } }; // 在另一个文件中导入 var myModule = require('./myModule'); myModule.publicMethod();
- ES6模块(浏览器上的模块化规范):
import
、export
// 在模块中导出 const privateVariable = "I am private"; export const publicMethod = function() { console.log(privateVariable); }; // 在另一个文件中导入 import { publicMethod } from './myModule'; publicMethod();
Js 中常见的内存泄漏
- 未及时清理定时器:定时器如果没有被清理,将一直保持对回调函数的引用
- 未解绑事件监听:如果在DOM元素被移除之前没有解绑事件监听,那么事件处理函数将一直存在于内存中,导致内存泄漏。使用 removeEventListener 解绑事件。
- 大对象未及时释放:在不再需要使用大型数据对象时,手动释放或将其设置为 null。
- 代码中无意间、不经意间创建的全局变量
- 闭包未释放
function createClosure() {
var data = new Array(10000); // 大型数据结构
return function() {
// 使用 data
console.log(data.length);
};
}
var closure = createClosure(); // 创建闭包
// 在外部使用 closure,但未解除对 data 的引用
closure();
// 假设不再需要使用 closure,但没有解除对闭包的引用
// 这将导致 data 一直存在于内存中,无法被垃圾回收
什么是事件冒泡?
事件会从触发事件的元素一直冒泡到最外层的祖先元素。
例如父子都有点击事件,点儿子,先儿子输出,后父亲输出。
如何阻止事件冒泡?
- stopPropagation
- preventDefault
e.addEventListener('click',function(event){
event.stopPropagation()
event.preventDefault()//或
})
事件委托是什么?如何确定事件源?
- 利用冒泡
- 将事件监听器添加到父元素上,减少了对每个子元素都添加监听器的工作量。
- 使用 event.target 确定事件源
对比Cookie、localStorage、sessionStorage的异同
都是用于在客户端存储数据的机制
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
存储容量 | 小(通常4KB) | 通常 5MB | 通常 5MB |
生命周期 | 可设置过期时间 | 永久存储,除非手动清除 | 页面关闭后自动清除 |
发送到服务器 | 每次请求都会将 Cookie 数据包含在请求头中,如果 Cookie 中包含大量数据,会导致更多的带宽消耗 | 不会自动发送 | 不会自动发送 |
作用域 | 可设置作用域 | 全域名共享 | 单个页面共享 |
安全性 | 非常低,可能被劫持 | 低,但相对较安全 | 低,但相对较安全 |
主要用途 | 跨页面通信、认证信息 | 本地存储,长期数据 | 本地存储,会话级别数据 |
ES6 新特性
-
let 和 const 声明:
- 引入了
let
和const
关键字,用于声明块级作用域的变量和常量。
- 引入了
-
箭头函数(Arrow Functions):
- 提供了更简洁的函数声明语法,同时修复了传统函数中
this
关键字的一些问题。
- 提供了更简洁的函数声明语法,同时修复了传统函数中
-
模板字符串(Template Strings):
- 使用反引号 `` 包裹字符串,支持多行字符串和变量插值。
-
解构赋值(Destructuring Assignment):
- 允许通过模式匹配从数组或对象中提取值并赋给变量。
-
对象字面量的扩展:
- 支持简写方法声明、计算属性名、以及
Object.assign
方法用于对象的浅复制。
- 支持简写方法声明、计算属性名、以及
-
类(Classes):
- 引入了类的概念,提供了更简洁、面向对象的语法。
-
Promise 对象:
- 提供了更方便的异步编程模式,解决了回调地狱的问题。
-
模块化(Modules):
- 引入了对模块的官方支持,允许将代码分割为多个文件,使项目更易维护和组织。
-
迭代器和生成器(Iterators and Generators):
- 提供了迭代器协议和生成器函数,使遍历数据结构更加灵活和可控。
-
Symbol 数据类型:
- 引入了一种新的基本数据类型
Symbol
,用于创建唯一的标识符,通常用于对象属性的键名。const mySymbol = Symbol(‘key’); myObject[mySymbol] = ‘value’;
- 引入了一种新的基本数据类型
-
Set 和 Map 数据结构:
- 引入了
Set
和Map
,分别用于存储唯一值和键值对,提供了更高效的数据存储和查找。
- 引入了
-
默认参数值:
- 允许在函数声明时为参数提供默认值。
-
Rest 和 Spread 操作符:
...
用于表示剩余参数(Rest),或将数组或对象展开为参数序列(Spread)。
for…of 和 for…in的区别
- for…in 循环用于遍历对象的可枚举属性,迭代的是对象的键名(属性名)
- for…of 循环是用于遍历可迭代对象(如数组、字符串、Set、Map 等),迭代的是对象的值,但是for-of不会遍历普通对象!普通对象默认不可迭代。
Let 与 var 与 const 的区别
特性 | var | let | const |
---|---|---|---|
声明提升(声明提到作用域顶部) | 是(只提升声明 赋值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) | 过滤数组,返回满足条件的新数组 | 满足条件的新数组 |
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
学习视频
- 手写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)
- 手写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.多态:不同对象的行为可以用相同的方法名调用,但是具体操作不同(其实是废话)
普通函数和构造函数的区别
- 普通函数就是用于执行某个特定的任务或操作
- 构造函数: 用于创建和初始化对象,它在对象被实例化时被调用,常通过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 操作符做了哪些事情?
- new 操作符新建了一个空对象,
- 这个对象原型(
person.__proto__
)指向构造函数的prototype(Person.prototype
) - 执行构造函数
- 返回这个对象
如何实现继承?/ 请写出一个简单的类与继承
暂时只记一种方法: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 是一个对象,从它可以获取异步操作的消息
三种状态
- pending等待
- fulfilled完成,调用Promise的then方法指定的回调函数,传递操作结果。
- rejected失败,调用Promise的catch方法指定的回调函数,传递失败原因。
状态一旦改变,就不会再变
特点
- 外界无法直接影响Promise的状态,内部异步操作完成后通过resolve或reject改变状态
- 一旦Promise的状态从Pending转变为Fulfilled或Rejected,就不会再发生变化
缺点
无法取消: Promise一旦创建,就无法被取消。
用户输入关键词 “apple”。
开始搜索操作,但用户很快意识到输入错误,想要更改关键词为 “banana”。
由于无法取消前一个搜索操作,系统仍在尝试获取 “apple” 的搜索结果,而用户已经不再关心这个结果。
基本语法
const myPromise = new Promise((resolve,reject)=>{
//一些操作
if(成功){
resolve(成功结果)
}else{
reject("失败原因")
}
)
介绍一下async/await
- async/await是JavaScript中用于处理异步代码的一种语法糖
- 一个使用async关键字定义的函数会返回一个Promise对象。在函数体内,你可以使用await关键字等待一个Promise解决,并且在等待期间代码会暂停执行,直到Promise被解决(Fulfilled)或拒绝(Rejected)。
- 使用try-catch块可以直接捕获异步操作中的错误,而不需要通过回调函数或Promise的catch方法。
我们用 Promise 来解决什么问题?
- 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
- 多并发的异步操作: 使用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);
});
一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
- dns请求。UDP,dns服务器从域名服务开始进行递归搜索,找到ip地址
- TCP三次握手
- 发送一个http请求,基于tcp
- 服务器监听80端口获取请求,发送响应数据。
- 浏览器解析
深度遍历把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
- 【是什么】Event Loop,简单翻译就是 事件循环,是 JS 语言下实现运行时的一个机制。
- 【解决什么问题】JS采用这种机制,来解决单线程运行带来的一些问题。
- JS是一种单线程语言,所有任务都在一个线程上完成
- 遇到耗时的任务,就会有很多闲置资源因为等待而浪费
- 【怎么解决的】浏览器环境中JS引擎运行在主线程上,同时,浏览器还有其他线程,比如说用户IO线程、HTTP请求线程、定时器线程等等,主线程执行同步任务,当主线程遇到IO操作等异步任务时,就会将异步任务交给其他线程处理,当异步任务完成时,会将结果的回调函数返回给消息队列中,等待主线程执行。这些线程的协作是通过事件循环机制来完成的。
- 【具体实现细节】异步任务又分为宏任务和微任务,宏任务的执行时机是在主线程空闲时,事件循环从宏任务队列中取出一个宏任务执行。微任务会在当前任务执行结束后、下一个任务开始前执行,并且优先级比宏任务高。
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;
}
代码解释:
-
基本数据类型: 基本数据类型(如字符串、数字、布尔值等)在 JavaScript 中是按值传递的,而不是按引用传递的。因此,对基本数据类型进行赋值或传递时,会直接复制其值而不涉及引用。所以,不需要对基本数据类型进行深拷贝。上述实现中的条件语句
if (obj === null || typeof obj !== 'object' || obj instanceof Date || obj instanceof RegExp)
就是用来检查是否为基本数据类型,如果是,则直接返回原始值。 -
循环引用: 循环引用指的是对象包含对自身的引用,形成一个循环链。在深度克隆时,如果不处理循环引用,可能导致无限递归,最终导致堆栈溢出或其他性能问题。使用
WeakMap
来记录已经访问过的对象,可以在发现循环引用时停止递归,避免陷入无限循环。 -
obj.hasOwnProperty(key)
: 这是为了确保只复制对象自身的属性,而不包括从原型链继承的属性。在 JavaScript 中,对象的属性可以分为自身属性和继承属性。使用hasOwnProperty
方法可以判断一个属性是否为对象自身的属性,而不是从原型链上继承的属性。这是为了避免复制不必要的属性,只复制对象自身的属性。 -
WeakMap
是 JavaScript 中的一种集合类型,它允许将对象作为键存储在映射中。与Map
不同,WeakMap
中的键只能是对象,而值可以是任意类型。一个WeakMap
中的键是弱引用的,这意味着如果对象作为键不再被引用,它可以被垃圾回收器回收,从而释放相应的键值对。
能来讲讲 JS 的语言特性
JavaScript 是一门多范式的编程语言,主要用于在网页上实现交互性。它具有一些独特的语言特性,以下是其中一些主要的特点:
- 动态类型: JavaScript 是一门动态类型的语言,变量的数据类型可以在运行时动态改变。这意味着你可以在不事先声明变量类型的情况下直接赋值。
- 弱类型: JavaScript 是一门弱类型语言,也被称为动态弱类型语言。这意味着在进行一些操作时,JavaScript 会进行隐式类型转换,而不会引发错误。
- 原型继承: JavaScript 使用原型继承作为对象之间共享和复用代码的机制。每个对象都有一个原型对象,而对象可以继承原型对象的属性和方法。
- 跨平台性: JavaScript 不仅用于浏览器端,还可以通过 Node.js 在服务器端运行。这使得 JavaScript 成为一种具有广泛用途的通用编程语言。
- 脚本语言、解释性语言: 脚本语言的一个特点是可以逐行地执行,而不需要事先将整个程序编译成机器码。解释性语言,是因为它的代码在运行时由解释器逐行解释执行。
为什么会造成跨域/请简述同源策略?
同源策略(Same-Origin Policy)是浏览器的一种安全机制,用于防止恶意网站通过脚本等方式进行恶意请求。同源策略规定,两个页面只有在协议、主机和端口均相同的情况下,才属于同一个源(origin)。
举例一种如果没有同源策略,可能会发生的情况: 恶意网站读取了cookie后,可以使用用户的身份信息发起对银行网站的恶意请求
请输出三种减少页面加载时间的方式
- 压缩图片/视频音频质量
- 懒加载
- 异步加载,不阻塞:使用async或defer属性加载JavaScript文件,以确保它们不会阻止页面的渲染。
<script defer src="example.js"></script>
- 使用 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 种以上数组去重的方式
- set
const uniqueArray = [...new Set(originalArray)];
- 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状态码
-
信息性状态码(Informational Codes):
- 100 Continue: 表示服务器已经接收到请求的部分,但仍然等待客户端发送剩余的请求。
-
成功状态码(Successful Codes):
- 200 OK: 表示请求已成功。
- 201 Created: 表示请求已经被成功处理,并且服务器创建了新的资源。
- 204 No Content: 表示服务器成功处理了请求,但没有返回任何内容。
-
重定向状态码(Redirection Codes):
- 301 Moved Permanently: 表示被请求的资源已经永久移动到新位置。
- 302 Found (Moved Temporarily): 表示被请求的资源已经临时移动到新位置。
- 304 Not Modified: 表示资源未被修改,可以使用缓存的版本。
-
客户端错误状态码(Client Error Codes):
- 400 Bad Request: 表示客户端发送了一个服务器无法理解的请求。
- 401 Unauthorized: 表示请求需要用户身份验证。
- 403 Forbidden: 表示服务器理解请求,但拒绝执行。
- 404 Not Found: 表示服务器无法找到请求的资源。
-
服务器错误状态码(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);