js基础
1.js中的数据类型
两大类:基本类型和引用类型
基本类型存储数据本身、引用类型仅仅存储地址
基本类型使用栈来存储,引用类型中的地址存在栈中,实际数据放在堆中。因为引用类型比较复杂、为了解决反复的创建和回收数据所带来的损耗, 就用堆内存为其开辟另外一部分空间,以便于数据重复利用和垃圾回收。
具体细分:
1.symbol:是一种唯一标识符,可用作对象的唯一属性名,这样其他人就不会改写或覆盖你设置的属性值,具有隐藏性,不能使用 for in和object.keys访问。Object.getOwnPropertySymbols获取值
2.BigInt:提供了一种方法来表示大于2^53-1或者小于-2^53-1的整数,BigInt可以表示任意大的整数
3.Boolean
4.Null
5.Undefined
6.Number
7.String
8.Object
null和undefined的区别
用一句话总结两者的区别就是:undefined 表示一个变量自然的、最原始的状态值,而 null 表示主动释放一个变量引用的对象,表示一个变量不再指向任何对象地址.所以,在实际使用过程中,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。
2.JS判断类型
1.typeof
可以判断所有基本数据类型:比如number string boolean undefined symbol
常见的引用类型判断不了,比如object,array,null都会返回object,document.all 返回 undefined, 但可以判断函数 typeof function fn(){} ===> 'function'
2.constructor
constructor 指向创建该实例对象的构造函数
不能判断 null 和 undefined,并且当改变了原型后,会判断出错
3.instanceof
语法:obj instanceof Type 🌰:
var a = {};
alert(a instanceof Object); //true
var b = [];
alert(b instanceof Array); //true
instanceof只能用来判断对象和函数,不能用来判断字符串和数字null 和 undefined
4.isPrototypeOf
用于判断当前对象是否为另外一个对象的原型,功能基本等同于 instanceof
5.Object.prototype.toString.call
6.等比较:与某个固定值进行比较,underscore里会这么用
7.判断数组:Array.isArray()
4.原型链,类class和构造函数的区别
原型:Object.prototype就是原型,它同样也是个对象,也被称为原型对象,prototype是显式原型,proto是隐式原型,
作用:可以共享方法,实例可以使用原型上的方法,不会新开辟空间存储方法
原型链:原型与原型层层相链接的过程即为原型链。更外一层的对象的隐式原型会指向内层构造函数的显示原型。在调用某个方法时会看本上是否有该方法,如果有,则执行,没有就沿着原型链往上找,没有则报错。原型的构造器指向构造函数
区别:
(1) 类必须使用new调用,否则会报错。构造函数不用new也可以执行。
(2) 类的所有实例共享一个原型对象。
(3) 类的内部,默认就是严格模式,所以不需要使用use strict指定运行模式
5.new操作符生成一个新对象的过程
- 新建一个对象obj
- 把obj的和构造函数通过原型链连接起来,将obj的隐式原型指向构造函数中的显式原型中
- 将构造函数的this指向obj
- 如果该函数没有返回对象,则返回this
6.new 操作符能跟什么
new 只能用于创建对象实例,因此后面跟的是构造函数;函数式编程中跟的是函数名,面向对象编程中通常跟的是类名;还可以跟一个内置对象的构造函数名,例如 Array Date RegExp 等
手写 new 见手写题
7.闭包,闭包会导致内存泄露吗
概念:闭包就是能够读取其他函数内部变量的函数
有两种使用场景:函数作为参数被传递、函数作为返回值被返回
在读取闭包中的变量时,决定值的地方是在函数定义的地方,然后向上级作用域进行查找,而不是在执行的地方。
作用:
- 访问其他函数内部变量
- 保护变量不被内存回收机制回收
- 避免全局变量被污染 方便调用上下文的局部变量 加强封装性
比如vue2中defineProperty对dep实例的引用
会导致内存泄露吗?不会,因为内存泄露是指用不到或者访问不到的变量,依然占居着内存空间,不能被再次利用起来。而闭包里面的变量就是我们需要的变量,不能说是内存泄露。
8.this
在哪些情况下被使用:
-
当作普通函数被调用时,this指向它的调用者,默认情况下指向 window
-
使用call apply bind时,传入什么就绑定什么,因为这三个方法强行改变了this的指向
-
在对象方法中调用时,this 指向该对象
-
在class的方法中调用,指向new出来的实例
-
箭头函数中指向window,因为没有自己的this
11.js同步和异步的区别&单线程的原因
同步和异步基于JS的执行,因为JS是单线程语言;异步不会阻塞代码的执行;同步会阻塞代码的执行
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
12.event loop
event loop 就是异步回调的实现原理
执行同步代码,直到遇到第一个异步代码,将异步代码添加到任务队列中,执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查异步任务队列,即微任务和宏任务队列,首先检查微任务(microTask)队列是否为空,若有则全部执行,若无,则执行宏任务。在每次执行完一个宏任务后,会立即执行所有的微任务,然后再执行下一个宏任务。因此微任务优先级大于宏任务
每次单个宏任务执行完毕后,检查微任务(microTask)队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务(microTask),然后再执行宏任务, 并设置微任务(microTask)队列为null,如此循环。
3/10
13.事件机制
是浏览器在某种操作(如用户的点击、鼠标移动、键盘按键等)发生时,会产生相应的事件,并通过执行事件处理函数来响应这些事件。事件机制包括事件的触发、事件的传播和事件的处理。
事件的触发: 当事件发生时,浏览器会创建相应的事件对象,并将其传递给事件目标,包含了关于事件的详细信息,例如事件的类型、事件发生的位置
事件的传播:是指事件从事件目标向其他元素传递的过程,分为三个阶段:捕获阶段、目标阶段和冒泡阶段。
在捕获阶段中,事件从文档根节点开始向下传递,直到达到事件目标;
在目标阶段中,事件在事件目标上触发;
在冒泡阶段中,事件从事件目标开始向上冒泡,直到达到文档根节点。
事件的处理:事件的处理是指执行事件处理函数来响应事件。
事件处理函数可以通过多种方式来注册,例如使用 HTML 属性、DOM API 和 JavaScript 代码等。事件处理函数会在事件目标上执行,并可以访问事件对象中的信息
事件绑定的方式:
1.html内联属性 2.DOM属性绑定 document.getElementById 3.事件监听函数 element.addeventListener
其他:
target是触发事件的某个具体对象,currentTarget 是绑定事件的对象
可以通过 e.stopPropagation 中断事件的向下或向上传递。
可以使用 e.preventDefault 取消默认行为。
13.1 事件委托
事件委托是基于 JavaScript 事件冒泡机制实现的一种事件处理技术。在事件冒泡过程中,当一个子元素触发了一个事件时,该事件会一直冒泡到祖先元素,直到被处理或者冒泡到 window 对象。事件委托利用了事件冒泡机制,将事件处理程序绑定到祖先元素上,从而减少事件处理程序的数量,提高页面性能。
14.跨域,同源策略
同源策略:
当发送请求时,为了保证安全浏览器是有同源策略的,即当前网页和服务器必须同源。即协议、域名、端口三者一致。
加载图片 css js 时可无视同源策略
跨域:
1.JSONP:利用加载 script 标签时无视同源策略的能力,绕过跨域限制
- 浏览器存在同源安全机制,需要和后端同学进行约束,约束script标签中的callback,只能使用get请求
2.CORS(跨域资源共享):浏览器可以利用 CORS 机制,放行符合规范的跨域访问,阻止不合规范的跨域访问,
分为简单请求和复杂请求,
简单请求:满足以下条件的就是简单请求
- 请求方法:只能是 GET、HEAD 或 POST
- HTTP 头只能是 Accept/Accept-Language/Conent-Language/Content-Type 等
- Content-Type 头只能是 text/plain、multipart/form-data 或 application/x-www-form-urlencoded
跨域请求流程如下:
- 浏览器向服务器发起跨域请求,请求头部信息中包含 Origin 字段,表示请求的源地址;
- 服务器接收到请求后,在响应头部信息中添加 Access-Control-Allow-Origin 字段,表示允许该源地址访问该资源;
- 如果请求中包含其他头部信息,服务器需要在响应头部信息中添加 Access-Control-Allow-Headers 字段,表示允许携带的头部信息;
- 如果请求中包含认证信息(例如 cookie),服务器需要在响应头部信息中添加 Access-Control-Allow-Credentials 字段,表示允许发送认证信息。
复杂请求
当不满足简单请求条件时,浏览器发出的跨域请求被认为是复杂请求。复杂请求需要先发起一个预检请求(OPTIONS 请求),用于确认服务器是否允许该请求。预检请求的响应中需要包含 Access-Control-Allow-Methods、Access-Control-Allow-Headers 和 Access-Control-Max-Age 字段。
- 浏览器向服务器发起预检请求,请求头部信息中包含 Origin、Access-Control-Request-Method 和 Access-Control-Request-Headers 字段;
- 服务器接收到预检请求后,在响应头部信息中添加 Access-Control-Allow-Origin、Access-Control-Allow-Methods 和 Access-Control-Allow-Headers 字段;
- 如果服务器允许该请求,浏览器会再次发起实际请求,实际请求的请求头部信息中包含 Origin 字段和实际的请求头部信息;
- 服务器接收到实际请求后,在响应头部信息中添加 Access-Control-Allow-Origin 字段,表示允许该源地址访问该资源;
- 如果请求中包含认证信息(例如 cookie),服务器需要在响应头部信息中添加 Access-Control-Allow-Credentials 字段,表示允许发送认证信息。
3.NGINX 反向代理,后端发送请求没有跨域限制,设置 ACAO 和 ACAC
4.iframe:可以使用 window.postMessage
5.img 标签也可以跨域
15.cookie localStorage sessionStorage 的区别
cookie
最开始不是用来存储数据,而是用于浏览器和服务端通讯,后来被借来做本地存储,可以使用 document.cookie 来修改,使用 key value 的形式来修改
缺点:
- 存储量只有4KB左右
2.http 请求需要发送到服务端,增加请求的数据量
3.只能用document.cookie = '...' 来修改,太过简陋
cookie 字段
1. Name:Cookie的名称,用于标识Cookie。
2. Value:Cookie的值,可以是任意字符串。
3. Domain:Cookie的作用域,表示Cookie可以被哪些域名访问。如果不设置该字段,则默认为当前网站的域名。
4. Path:Cookie的路径,表示Cookie可以被哪些路径访问。如果不设置该字段,则默认为当前页面的路径。
5. Expires/Max-Age:Cookie的过期时间,表示Cookie何时失效。Expires字段是一个日期时间字符串,表示Cookie在该日期时间之后失效;而Max-Age字段是一个整数,表示Cookie在该秒数之后失效。如果不设置该字段,则默认为会话Cookie,即关闭浏览器后失效。
6. Secure:表示Cookie只能在HTTPS协议下传输,用于增强Cookie的安全性。
7. HttpOnly:表示Cookie只能通过HTTP协议访问,不能通过JavaScript等客户端脚本访问,用于增强Cookie的安全性。
localStorage 和 sessionStorage
- 最大可存5M
- API简洁易用,比如setItem getItem
- 不会随着 http 请求被发出去
- localStorage和sessionStorage存储的数据都存储在浏览器本地的一个类似于数据库的存储空间中。这个存储空间的大小限制取决于浏览器的实现和配置,通常为5MB到10MB左右。在Chrome浏览器中,这个存储空间的位置在用户目录下的Local Storage或Session Storage文件夹中。在其他浏览器中,也有类似的存储位置。
两者区别:
-
localStorage 数据会永久存储在客户端本地,除非代码删除或者手动删除
-
sessionStorage 数据只存在于当前会话,浏览器关闭则清空
-
一般用 localStorage 会更多一些
-
要在同一个域名下得使用 iframe 嵌套来解除限制
15.1实现一个localstorage
- 创建一个名为 LocalStorage 的构造函数,用于创建一个本地存储对象。
- 在 LocalStorage 的原型对象上添加 getItem 和 setItem 方法,用于存储和获取数据。
- 可以在 LocalStorage 的原型对象上添加其他的方法,如 removeItem、clear 等方法,用于删除数据和清空存储对象。
- 最后,可以创建一个 localStorage 对象,通过该对象调用 setItem、getItem 等方法进行数据的存储和获取。
22.js继承
六种方式:
1.原型链继承
2.借用构造函数继承
3.组合继承
4.原型式继承
5.寄生式继承
6.寄生组合式继承 - class 继承的逻辑
ES5继承和ES6继承的区别:
- class 申明会提升,但不会初始化赋值
const bar = new Bar(); // it's ok
function Bar() {
this.bar = 42;
}
const foo = new Foo(); // ReferenceError: Foo is not defined
class Foo {
constructor() {
this.foo = 42;
}
}
- class 声明内部会启用严格模式
- class 的所有方法(包括静态方法和实例方法)都是不可枚举的。
// 引用一个未声明的变量
function Bar() {
this.bar = 42;
}
Bar.answer = function() {
return 42;
};
Bar.prototype.print = function() {
console.log(this.bar);
};
const barKeys = Object.keys(Bar); // ['answer']
const barProtoKeys = Object.keys(Bar.prototype); // ['print']
class Foo {
constructor() {
this.foo = 42;
}
static answer() {
return 42;
}
print() {
console.log(this.foo);
}
}
const fooKeys = Object.keys(Foo); // []
const fooProtoKeys = Object.keys(Foo.prototype); // []
- class 的所有方法(包括静态方法和实例方法)都没有原型对象 prototype
- 必须使用 new 来调用 Class
- class 内部无法重写类名。
- ES5使用原型链和构造函数的组合来实现继承。通过prototype属性和new关键字创建对象实例并继承原型链上的属性和方法。ES6引入了class关键字和extends关键字,提供了更简洁和清晰的语法来定义类和继承关系。
- super关键字:在ES5中,如果子类需要调用父类的构造函数或方法,需要使用call或apply方法显式地指定父类对象,并传递相应的参数。在ES6中,使用super关键字可以直接调用父类的构造函数和方法,无需显式指定父类对象。可以通过super()调用父类的构造函数,在子类构造函数中必须首先调用super(),然后才能访问this关键字
- 静态类方法:ES5中没有直接支持类的静态方法的语法。通常使用构造函数本身来定义静态方法。ES6中,可以使用static关键字在类中定义静态方法,这些方法属于类本身而不是实例,并且不能被实例继承。
3/23
23.class 继承
在 class 继承中主要依靠两个东西: extends 和 super
- 在子类中通过 extends 关键字,可以继承 父类的所有属性和方法。
- extends实现继承不一定要写constructor和super,会默认产生并调用
- extends后面接着的目标不一定是class,只要是个有prototype属性的函数就可以了
- 子类必须在 constructor 方法中 调用 super 方法,否则新建实例时会报错。因为子类有自己的 this 对象,需要先拿到父类的然后再加上自己的实例属性和方法
24.重绘和回流
两者区别:取决于 DOM 变与不变
回流一定会触发重绘,而重绘不一定会回流
回流:比如我们增删DOM节点,或者修改一个元素的宽高,使得页面布局发生变化,修改dom后会对页面进行再次的渲染。
重汇: 当你给一个元素更换颜色,这样的行为是不会影响页面布局的,DOM树不会变化,但颜色变了,渲染树得重新渲染页面,但效率相对较高
防止回流的方法:
1.减少 DOM 的操作: 对于 DOM 的添加和删除,尽量综合在一起操作,使用文档片段documentFragment
2.页面加载时将 css 的 link 放在 body 外
3.减少样式的操作: 减少设定样式,class 名从而改变样式
4.避免使用 table 布局:可能很小的一个小改动会造成整个 table 的重新布局。
5.使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流
25.call/bind/apply 的区别
-
参数传递方式不同:call bind方法的参数是逐个传递的,而 apply 方法的参数是以数组的形式传递的。
-
执行方式不同:call 和 apply 方法会立即执行函数,而 bind 方法返回一个新的函数,并不会立即执行。
-
返回值不同:call 和 apply 方法返回函数的返回值,而 bind 方法返回一个新的函数。
26.Object.defineProperty 可配置的属性
除了get和set之外,Object.defineProperty还可以配置以下属性:
- configurable:表示该属性是否可以通过delete操作符删除,以及是否可以再次修改属性描述符。默认值为false。
- enumerable:表示该属性是否可以被for...in循环遍历到,以及是否可以通过Object.keys()方法获取到。默认值为false。
- value:表示该属性的值。可以是任何类型的值,包括基本类型和对象类型。如果同时设置了value和get/set,会抛出一个TypeError异常。
- writable:表示该属性的值是否可以被修改。如果该属性为false,那么对该属性赋值会被忽略。默认值为false。
ES6
9.let\const\var的区别
定义时:
var 定义变量时若定义全局变量即定义在最外层,会成为顶层对象属性,会改变 this 的内容。
let,const 无论定义在哪都不会定义为顶层对象的属性
变量提升:
var存在变量提升,若在赋值之前使用,会获得 undefined 值,而 let 在赋值之前使用,会报错,因为此时定义的变量仍然处于未初始化状态,存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
作用域:
let和const存在块级作用域,防止外层变量覆盖内层变量,防止用来计数的循环变量泄露为全局变量。var的作用域是函数作用域或者是全局作用域
let和const的区别:let 定义时可以不立即初始化,但是 const 定义时必须开始初始化
const定义的常量不可改变,但是定义的属性值是可以改变的,因为const仅保证指针不可改变就可以了
使用var实现const
用于声明变量和常量。var声明的变量是可变的,而const声明的常量是不可变的
// 使用闭包实现只读变量
(function () {
var myVar = 'Hello';
Object.defineProperty(window, 'MY_CONST', {
get: function() { return myVar; }
});
})();
console.log(MY_CONST); // 输出 'Hello'
MY_CONST = 'World'; // 抛出错误:Cannot assign to read only property 'MY_CONST'
在上面的代码中,我们使用立即执行函数创建了一个闭包,将变量myVar封装起来。然后,通过Object.defineProperty方法将一个只读属性MY_CONST添加到window对象中,该属性的值是myVar的值。由于MY_CONST是只读属性,所以无法通过赋值操作来修改它的值,从而实现了类似于const的效果。
10.解构赋值&箭头函数
数组解构赋值,分为完全解构和非完全解构,完全解构是定义的变量数组和数据值数组个数相同,非完全则是变量个数和数据值不同。若数据值为非数组则会报错,在解构时可以接受默认值.
对象解构赋值,对象解构赋值时,对象的属性没有次序限制,变量必须与属性同名
函数对象赋值,在函数式组件中常用
用途:可以交换变量的值,从函数返回多个值
箭头函数:
1.书写方面,如果只有一个参数,可以省去参数的括号,函数体的返回值只有一句,可以省略大括号
2.没有:没有自己的this,默认指向window;不能使用构造函数,没有argument(一个对应于传递给函数的参数的类数组对象),没有prototype,不能用 generator函数和yield关键字
3.call apply bind方法中不能改变this的指向
11.箭头函数和普通函数的区别
- 语法简洁:箭头函数使用箭头符号(=>)来定义函数,语法较为简洁。而普通函数需要使用 function 关键字来定义,并且有较多的语法结构,例如函数名、参数列表、花括号等。
- this 指向:箭头函数的 this 指向是固定的,指向函数定义时所在的作用域对象,而不是在调用时所在的作用域对象。普通函数的 this 指向是动态的,它的值取决于函数的调用方式。
- arguments 对象:箭头函数没有自己的 arguments 对象,而是引用了外层作用域的 arguments 对象。普通函数有自己的 arguments 对象,可以用来获取函数的参数列表。
- 箭头函数不能作为构造函数使用:由于箭头函数没有自己的 this 指向,所以不能用来创建对象实例。
- 箭头函数需要先定义再使用而普通函数可以在任意位置定义(变量提升是 JavaScript 中的一个特性,它允许在变量或函数定义之前就可以引用它们。在普通函数中,函数定义会被提升到作用域的顶部,因此可以在函数定义之前调用该函数,而箭头函数使用const 或者 let 定义的,所以不能变量提升)
12.arguments
- 获取函数的所有参数:可以使用 arguments.length 属性获取传递给函数的参数数量,使用 arguments[index] 来获取指定索引位置的参数值,其中 index 从 0 开始。
- 实现函数重载:由于 JavaScript 不支持函数重载,因此可以使用 arguments 对象根据传递的参数个数和类型的不同来实现函数重载。
- 实现可变参数函数:可以使用 arguments 对象来实现可变参数函数,即可以接受任意数量的参数。
16.ES6 模块化规范
发展历程:
CommonJS->AMD->CMD->ESM(ESModule)
模块化的作用:
- 避免命名空间的冲突(减少命名空间的污染)
- 更好的分离,实现按需加载
- 提高可代码的复用性、维护性
几种规范总结
CommonJS规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境,因为同步意味着阻塞加载,浏览器资源是异步加载的
AMD支持异步加载,允许指定回调函数,适用于浏览器
CMD:结合 CommonJS 和 AMD,异步加载,用于浏览器
目前主流是ESMoudle:即使用 export / export default 将一个功能模块的代码导出,使用 import 进行导入,编译时确定依赖关系
区别:
- ES Module是ECMAScript标准中定义的官方模块化规范,而AMD和CMD则是非官方的模块化规范。
- ES Module是JavaScript原生支持的,通过import和export关键字来实现模块化,而AMD和CMD需要引入对应的库(如RequireJS)来实现模块化。
- ES Module是静态导入,即在编译时就确定了模块的依赖关系,而AMD和CMD则是动态导入,即在运行时才能确定模块的依赖关系。
- ES Module默认使用严格模式,不允许在模块作用域中使用this关键字,而AMD和CMD则没有这个限制。
- ES Module支持在浏览器和Node.js中使用,而AMD和CMD则主要用于浏览器端。
31.iterator 迭代器
是什么:
可以理解成是一种特殊的对象 - 迭代器对象,返回此对象的方法叫做迭代器方法,可以为不同的数据结构提供统一的数据访问机制
问题: for in 循环、forEach、map 有各自的限制和缺点
比如 for in 通常不用来获取数据,而是遍历对象上所有的可枚举属性。forEach、map 遍历对象时还需要做转换
需要一个通用方法,比如 for of
原理:
当 for of 执行时,会调用这个对象上的迭代器方法, 依次执行迭代器对象的 next 方法,将 next 返回值赋值给 for of 内的变量,从而得到具体的值。数组、字符串、arguments 类数组、扩展运算符都有 Symbol.iterator属性。
迭代器作为一个对象,上面有一个 next 方法。内部会维护一个指针,用来指向当前集合的位置,每调用一次 next 方法,指针都会向后移动一个位置,直到没有数据为止
40.ES新出特性
#private
在 class 中,通过 # 开头将类的属性和方法设置为私有,这种只能从类本身访问,不能被删除或者动态分配
静态类成员
分为静态方法和静态属性,静态方法定义在类的内部,不定义在实例对象的this上,可被继承;静态属性通过对象的属性访问器定义
负数索引
at(-1)
hasOwn
出现了 hasOwn 替代了 hasOwnProperty,旨在解决Object.Create(null)后再访问 prototype 失败的问题,以及手动修改 prototype的问题
数字分隔符
允许在数字字面量中使用下划线作为分隔符,以提高数字的可读性。在数字字面量中,下划线必须在数字之间,或者在数字的开头或结尾
41.Object.defineProperty和Proxy的区别
Object.defineProperty和Proxy都是JavaScript中用于对象属性管理的工具,但它们的用途和实现方式有所不同。
Object.defineProperty用于定义一个对象的属性。它可以定义对象的新属性,或修改对象的现有属性。通过Object.defineProperty可以设置属性的特性,如值、可枚举性、可写性等等。Object.defineProperty只能对单个属性进行设置,不能对整个对象进行拦截。
Proxy是ES6中新增的一个特性,它可以拦截对象的底层操作,如读取、赋值、函数调用等等。通过Proxy可以对整个对象进行拦截和修改。Proxy提供了get、set、apply等方法,可以使用这些方法来实现对对象的拦截和修改。与Object.defineProperty不同,Proxy可以对整个对象进行拦截,而不仅仅是单个属性。
因此,Object.defineProperty和Proxy的主要区别在于:
- Object.defineProperty只能对单个属性进行设置,而Proxy可以对整个对象进行拦截和修改。
- Object.defineProperty只能设置属性的特性,而Proxy可以拦截对象的底层操作。
- Object.defineProperty是ES5中的特性,而Proxy是ES6中的特性。
需要注意的是,由于Proxy是ES6中的新特性,在一些老旧的环境中可能无法使用,因此在使用Proxy时需要进行兼容性处理。
Promise
25.promise
1.promise解决的问题
Promise是一种用于处理异步操作的JavaScript对象,它的设计思路主要是为了解决回调地狱(Callback Hell)问题(回调地狱是指在处理多个异步操作时,由于回调函数嵌套过多,导致代码结构复杂、难以维护的情况)和提高代码的可读性和可维护性。
2.promise 的三个状态
pending:进行中,既不是开始又不是结束
Resolved:完成,又称 fullfilled
Rejected:失败
变化路径:pending->resolved,pending->rejected,异步操作成功或者失败。状态一旦改变,就无法再次改变状态,
3.promise的静态方法
all(): 返回一个直接包裹 resolved 内容的数组,如果有一个Promise对象报错了,则all()无法执行,会报错你的错误,无法获得其他成功的数据。
allSettled(): 返回一个包裹着对象的数组,不管有没有报错,把所有的Promise实例的数据都返回回来,放入到一个对象中。
race():返回最快完成那一个 Promise 实例。只要参数数组中有一个 Promise 实例执行 resolve 回调或 reject 回调后,新实例就直接返回结果。
finally(): 无论Promise成功还是失败,都会执行此方法,他不会接受上一层 .then 方法传递的参数
any(): 有多个Promise实例,如果其中有一个成功了,就会走其成功结果(取出第一个成功的值),只有全部失败了,才会走失败结果。
26 promise实现原理
Promise 是 JavaScript 中一种异步编程的解决方案,它可以让异步操作更加直观和易于处理。Promise 的实现原理是基于回调函数和状态机的概念,它包括三种状态:pending、fulfilled 和 rejected。
当一个 Promise 对象被创建时,它处于 pending 状态。当异步操作完成时,Promise 对象会从 pending 状态转换为 fulfilled 或 rejected 状态。如果异步操作成功完成,则 Promise 对象进入 fulfilled 状态并返回结果;如果异步操作失败,则 Promise 对象进入 rejected 状态并返回错误信息。
27.有多个请求同时发出,要在所有请求结束时一起处理的解决方法
- 使用Promise.all
- 定义一个 通用的 promise 请求,然后定义一个数组 state 存储请求的结果,然后再依次调用每个请求,将结果存储在 state 数组中去
- 通过 generator 异步函数,调用遍历器对象的next方法,使得指针移向下一个状态,定义一个 通用的 promise 请求,将获取结果 通过 yield表达式承接,最后使用 next 方法调用
- async await,将多个请求包裹在一个立即执行函数中
- generator 使用 co 函数,递归执行next,直到done为true
28 ajax 底层原理
Ajax 的底层原理是通过 XMLHttpRequest 对象向服务器发送 HTTP 请求,并在请求完成后通过回调函数处理服务器返回的数据。这种异步数据交互的方式可以提高 Web 应用的性能和用户体验,使页面内容可以动态更新而无需重新加载整个页面。
详细:
-
创建 XMLHttpRequest 对象:使用 JavaScript 代码创建 XMLHttpRequest 对象,该对象用于向服务器发送 HTTP 请求和处理服务器返回的数据。
-
发送 HTTP 请求:使用 XMLHttpRequest 对象的 open() 和 send() 方法发送 HTTP 请求。open() 方法用于指定请求的类型、URL 和是否使用异步模式;send() 方法用于发送请求并传递请求参数。
-
接收服务器响应:当服务器响应 HTTP 请求时,XMLHttpRequest 对象会触发 readyStateChange 事件,并调用相应的回调函数来处理服务器返回的数据。可以使用 onreadystatechange 属性设置 readyStateChange 事件的回调函数,并使用 readyState 和 status 属性获取服务器响应的状态和数据。
-
处理服务器返回的数据:根据服务器返回的数据类型,可以使用 XMLHttpRequest 对象的 responseText 或 responseXML 属性获取服务器返回的数据,并在回调函数中使用该数据来动态更新页面内容。
32.async await
async函数是以更简洁的方式,即以同步代码的形式写出Promise异步行为,而不是以Promise的链式调用方式实现。
它是Generator函数的语法糖,实现原理是将Generator函数和自动执行器包装在一个函数里。其中自动执行器的作用是监听Promise状态改变后,触发Generator继续执行。
-
执行 async 函数, 返回的是 Promise 对象
-
await 相当于 Promise 的 then
-
try...catch 可捕获异常,代替了 Promise 的 catch
基于 es5 实现
- 根据yield语句将代码分割并生成switch语句,switch语句通过判断状态执行相应的动作,并且会修改状态。
- 采用context存储状态机状态,每次执行迭代器的next方法的时候执行状态机函数。
- 采用闭包方式保存执行上环境下文。
45.axios
axios 的请求流程
- 创建Axios实例:在使用Axios发送请求之前,需要先创建一个Axios实例,可以通过axios.create()方法来创建一个Axios实例,也可以直接使用全局的Axios实例。
- 设置请求配置:通过配置Axios实例的defaults属性,可以设置请求的默认配置,例如请求的URL、请求方法、请求头、超时时间等。
- 发送请求:通过调用Axios实例的request()方法来发送请求。request()方法接受一个配置对象作为参数,该配置对象会覆盖默认的配置,并返回一个Promise对象。或者.get .post等
- 处理响应:当服务器返回响应时,Axios会根据服务器返回的状态码、响应头和响应体来解析响应,并将解析后的结果作为Promise的resolve值返回。
axios 的底层实现
在浏览器中,Axios使用XMLHttpRequest对象来发送HTTP请求,并通过Promise来实现异步处理。在Node.js环境中,Axios使用http模块来发送HTTP请求,并通过Promise来实现异步处理。
axios 请求拦截和响应拦截
可以通过Axios实例的interceptors属性来实现。interceptors属性包含了一个请求拦截器列表和一个响应拦截器列表,可以通过use()方法向列表中添加拦截器,或者通过eject()方法从列表中删除拦截器。请求拦截器会在发送请求之前被调用,可以用于添加请求头、修改请求参数等操作;响应拦截器会在接收到响应之后被调用,可以用于处理响应数据、错误处理等操作。拦截器可以通过Promise的resolve和reject方法来控制请求和响应的流程
46.axios取消请求的方式
使用 cancelToken 来取消请求(现已废弃),在发送 axios 请求的时候对可能需要取消的请求传入 cancelToken,取消时调用 cancelToken 中的 cancel 方法,使用 isCancel 来检查错误
在0.21.0版本之后,取而代之的是使用标准的 AbortController API 来取消请求,对可能需要取消的请求传递 signal 属性。需要取消时调用 controller 的 abort 方法,如果请求被取消,axios 将抛出一个带有 message 为 AbortError 的错误,使用 isCancel 来检查错误
47.宏任务、微任务分类
宏任务:
script(整体代码):加载时如果遇到 script 标签,它就会请求该标签的 src 属性指定的 JavaScript 文件,下载和执行 JavaScript 代码都会被视为一个宏任务
setTimeout、setInterval:定时器
I/O:包括文件读写、网络请求等异步 I/O 操作
UI交互事件:当页面需要重新渲染时,会产生一个宏任务,用于更新页面上的内容
postMessage:不同的窗口或者iframe之间传递消息的一种机制。用于同源、跨域通信,比如iframe、webworker
MessageChannel:创建异步消息通道的一种机制,与postMessage不同的是,MessageChannel 可以实现有序的消息传递,即可以保证消息的发送和接收顺序
setImmediate(Node.js 环境):Node.js 中用于创建异步任务的一种 API,它可以让回调函数在下一次事件循环时执行,类似于 setTimeout(fn, 0),性能更好
requestAnimationFrame:执行机制不同于定时器,会在下一次浏览器重新渲染前执行,可以执行一些动画、更新 UI 等操作
微任务:
Promise.then
MutationObserver:用于监听 DOM 树的变化并触发回调函数
process.nextTick(Node.js 环境):Node.js 中用于创建异步任务的一种 API,它可以让回调函数在当前事件循环结束后、下一次事件循环开始前执行,优先级高于setImmediate 和 setTimeout
async/await 中的 await 是微任务,而 async 函数本身返回的 Promise 对象是宏任务。
JS高级
17.防抖和节流
1.防抖:触发高频事件后的 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
如百度搜索时,每次输入时的联想词弹出。也就是说:每次有新内容先清旧的定时器,保证是当前规定时间内的最后一次操作
2.节流:触发高频事件后的 n 秒内函数只会执行一次,再次触发时无效。简单记忆:每次有新内容,不执行,等执行完再建立新的
如 drag 事件、滚动 scroll 时触发某一个回调
区别:防抖是将多次执行变为最后一次执行,节流是多次执行变成每隔一段时间执行一次
3/17
18.安全
1.XSS 攻击(跨站脚本攻击)
当提交数据时,攻击者可以嵌入
预防:
1.替换特殊字符,比如将<替换为< >替换为>。script就会直接显示,不会作为脚本执行
2.对于链接跳转redirect_to,如 <a href="xxx" 或 location.href="xxx",要检验其内容,禁止以 javascript: 开头的链接,和其他非法的 scheme。
3.使用CSP,1.通过 content-security-policy 响应头来指定规则,2.在content 字段设置白名单内容(meta 字段中添加 http-equiv="Content-Security-Policy")。以白名单的形式配置可信任的内容来源
4.使用第三方库 js-xss
19.安全(2)
2.CSRF攻击(跨站请求伪造)
攻击者通过已有的 cookie 进行了合法验证,比如通过图片链接 image 标签等方式伪造跨站请求,然后利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
预防手段:
1.同源检测
- 通过获取 HTTP request header 中字段,也就是该请求的来源地址,获取这个地址的 Origin 部分,从而判断
- 在 CSP 中设置,在页面头部中加入 meta 标签,a 标签中加入 referrerpolicy 标签
- 同源检测是一个相对简单的防范方法,但不能保证万无一失,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施
2.sameSite Cookie 属性
- 为了从源头解决这个问题,在 response header 中加入 sameSite 属性,用来表示是同站 cookie,sameSite 有两个属性值,分别是 strict 和 lax
- lax:可以使用跳转 cookie,安全性低,strict则不行
- 兼容性差,仅chrome、Firefox支持
- 不支持子域
3.提交时要求附加本域才能获取的信息
- 第一种:增加 CSRF Token,为加密信息,post请求时需要加入该token
- 第二种:双重 cookie 认证,用户访问时向域名中注入一个cookie。发请求时,取出这个 cookie 添加到 URL 参数中,后端验证这两个从cookie是否相同
20 安全-前端对参数进行转义
JavaScript提供了一些内置的方法可以对字符串进行转义,例如encodeURIComponent()方法和encodeURI()方法等。这些方法可以将字符串中的特殊字符进行编码,以避免出现安全漏洞和数据错误。
38.浏览器垃圾回收
1.什么是垃圾
在数据不被需要的时候,它就是垃圾数据,其内存应该被回收。全局变量随时可能用到,所以一定不是垃圾
2.如何标记垃圾
从根节点(Root)出发,遍历所有的对象。可以遍历到的对象,是可达的(reachable);没有被遍历到的对象,不可达的(unreachable)。回收不可达的数据所占据的内存
3.什么时候捡垃圾
衍生出三种垃圾回收算法
-
3.1.分代收集:根据存活时间来分代;分别为新生代,老生代,永久代,
- 新生代:新创建的对象,采用清扫工算法来回收
- 老生代:存活时间比新生代长,多次垃圾回收后依旧存活,采用标记清除算法来清除
- 永久代:存放元数据,一般不清除
-
3.2.增量收集:引擎将垃圾收集工作分成更小的块,每次处理一部分,多次处理
-
3.3.闲时收集:垃圾收集工作只会在 CPU 空闲时尝试运行,以减少可能对代码执行的影响。
4.不同类型变量的内存都何时释放
- 引用类型:在没有引用之后,引擎自动回收。
- 值类型
- 如果处于闭包的情况下,要等闭包没有引用才会被 V8 回收。
- 非闭包的情况下,等待 V8 的新生代切换的时候回收。
39.SPA
什么是SPA:
SPA将所有的活动局限于一个Web页面中,该页面初始化时加载相应的HTML、JavaScript 和 CSS。一旦页面加载完成,不会因为用户的操作而进行页面的重新加载或跳转。
取而代之的是利用 JavaScript 动态的变换HTML的内容,从而实现UI与用户的交互。由于避免了页面的重新加载提供较为流畅的用户体验。
优点:
- 友好的交互体验:SPA的内容改变不需要重新加载整个页面,没有页面之间切换时的白屏现象,假死甚至闪烁
- 良好的前后端工作分离模式:API通用化,后端不负责模板渲染等工作,减轻服务器压力
缺点:
- 首屏加载慢
- 不利于SEO
解决:
- 减少入口文件体积,通过使用路由懒加载,单独打包路由,使入口文件变小
- 使用SSR(React:Next.js,Vue:Nuxt.js),组件和页面通过服务器生成html字符串,再发送到浏览器
- 使用服务端动态渲染,服务端对请求的 user-agent 进行判断,浏览器端直接给 SPA 页面,如果是爬虫,给经过动态渲染的 html 页面
43.常见登录方式
- cookie+session
简要流程:登录成功后生成session对象,存在服务端中,并将其id存在cookie中,每次请求时都携带cookie发给服务器做校验。未登录或者过期后会要求重新登录。
缺点:1.安全性问题:会话劫持,XSS、CSRF攻击 2.扩展性问题:session对象要维护,如服务器负载过高、session 状态共享等问题,采用JWT来解决 3.兼容性问题,可能会受浏览器限制大小,字段等,采用OAuth 2.0 或 OpenID Connect
- JWT(JSON Web Token)
简要流程:登录成功后在服务器端生成一个 JWT 令牌,并将用户的身份信息加密到 JWT 令牌中。然后将令牌返回给客户端,在发送请求的时候,将头部信息发送给服务器,最后由服务端解密校验
优点:1.扩展性强,无状态,不需要维护session对象 2.安全性强,可以防范cookie+session中产生的安全性问题3.跨平台性,支持多语言,多平台,操作系统
缺点:1.令牌的大小会影响传输时间2.令牌过期时间需要合理3.密钥的安全性4.无法注销
- SSO(single sign-on)
简要流程:第一次访问某个系统时登录,成功后将信息发送给SSO服务器,并返回一个令牌并由客户端存起来,每次登录其他系统时,会检查本地存储的令牌信息
优点:用户体验好,无需重复登录;管理成本低
缺点:存在一定的安全隐患,SSO服务存在被攻击的可能性;依赖性强,强依赖于SSO服务
- OAuth 、OAuth2第三方登录,简单易用,对用户和开发者都友好,但第三方平台很多,需要选择合适自己的第三方登录平台。
-
-
不同的是,会生成访问令牌和刷新令牌,每次请求时会使用访问令牌,当过期或者注销时会使用刷新令牌
-
常用方式:授权码模式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌
-
4/20
44.设计模式
- 观察者模式(行为型模式):观察者模式也称为发布-订阅模式,用于在对象之间建立一对多的依赖关系。在前端中,使用的场景有:
1.组件的状态管理,比如用户在表单中输入数据时,验证用户输入的合法性并给出提示。表单组件作为被观察者,验证组件作为观察者
2.消息通知,用户收到一个新消息时,需要在界面给出提示。将消息组件作为被观察者,组件界面作为观察者(使用 订阅/发布模式)
- 单例模式(创建型模式):用于确保一个类只有一个实例,并提供全局访问点。使用场景有:
1.全局状态管理,这些状态需要在多个组件共享,比如登录状态、主题样式等,使用单例模式,多个组件可访问单例对象来共享这些状态
2.资源池管理,如HTTP请求,ws,webworker等,将资源池作为单例对象的属性
- 工厂模式:用于创建对象。在前端中,使用场景有:
1.UI组件库,UI组件库中的组件,比如按钮,下拉框,借助工厂模式配置参数组建不同的实例
优点:当需要创建大量相似对象时、需要隐藏创建细节时、需要动态创建对象时比较方便
- 装饰器模式:用于在不改变对象原有功能的情况下,为对象添加新的行为。通常使用装饰器模式来实现代码复用和增强功能。例子:mobx注入store,antd中的form使用Form.create()
45.微前端
微前端定义;
是一种将前端应用程序划分为多个小型应用程序的架构模式,小型应用程序可以有自己的代码库,团队,独立开发测试部署
微前端的原则和实践;
模块化:将应用程序划分为多个小型模块,每个模块都有自己的职责和功能;解耦:将模块之间的关联度降到最低;自治:每个模块由团队单独负责;容错机制:通过备份、降级、重试等机制保证可用性
优缺点:
优点:提高开发效率和可扩展性,缺点:增加额外开销,复杂性,增加安全隐患,增加团队协作成本。
使用的场景:大型电商平台,金融服务平台,多平台应用
49.错误捕获
- document.addEventListener("unhandledrejection"),用于捕获未处理的 Promise rejected 情况,即没有写 .catch 就会触发这个事件
- document.addEventListener("error"),当 JavaScript 代码执行出错时,被触发,可以接收到出错的位置等信息
- try catch捕获,finally放入必须执行的代码
- throw new Error() 手动抛出错误
- Promise.catch 处理 promise 中的错误
67.websocket
1.是什么,与传统http的区别
WebSocket 是一种双向通信协议,是一种基于TCP的协议,与传统的 HTTP 请求不同之处在于,它允许客户端和服务器之间建立持久连接,可以实时地进行双向通信。
2.优势:
双向通信:WebSocket 允许客户端和服务器之间实时进行双向通信,可以更快地进行数据交互。
低延迟:WebSocket 的持久连接可以减少每次通信的开销,降低延迟。
更少的网络流量:由于 WebSocket 的持久连接,相对于传统的 HTTP 请求,可以减少头部信息的传输,节省网络流量。
3.缺点:
兼容性问题:WebSocket 不是所有浏览器都支持,需要进行兼容性处理。
安全性问题:WebSocket 连接容易受到跨站点 WebSocket 攻击等安全问题的影响。
4.在 HTTP 协议切换到 WebSocket 协议,需要进行一次握手过程,这个过程称为 WebSocket 握手(WebSocket handshake),握手过程:
WebSocket 的握手过程包括客户端发送 HTTP 请求、服务器返回 HTTP 响应、客户端和服务器之间进行协议升级等步骤
5.创建方法,结束方法:
new Websocket 对象,然后监听其中的open,message,error方法,close来结束
6.心跳机制:
指客户端和服务器之间通过定时发送数据包来保持连接。客户端和服务器可以约定一个心跳间隔时间,在这个时间内发送一次数据包,如果在规定时间内没有收到数据包,则认为连接已断开
7.重连机制:
断开连接后可以在客户端进行重连,具体实现方式可以在 close 事件的回调函数中进行判断
8.在后端可以使用 ws、socket.io 等库创建 WebSocket 服务器
5/12
68.服务端推送
SSE (Server-sent Events)服务端派发事件
1.原理和应用场景:SSE 技术的原理是通过 HTTP 的长连接机制,在客户端和服务器之间建立一次持久连接,使服务器可以实时向客户端发送数据。
SSE 技术适用于需要实时获取数据的场景,如实时通知、在线聊天、股票行情等
2.实现:使用 JavaScript 中的 EventSource 对象来接收服务器发送的数据。new 一个 EventSource 对象,通过onmessage方法获取数据
3.网络中断,服务器宕机问题:检测连接状态和重新连接等方式来解决;重连或者设置备用服务器解决
4.和websocket,长轮询的对比:
- 优点:简单易用,只需要使用 EventSource 对象来接收服务器发送的事件即可
- 缺点:只能从服务器向客户端推送数据,不能进行双向通信
websocket:优点:可以双向通信,缺点:需要额外协议,代码量高
长轮询:缺点:需要不断发送请求,对服务器负载较大
长轮询
1.原理:客户端向服务器发送请求,服务器在请求超时之前一直保持连接打开状态,直到有数据可供传输时再返回数据给客户端。前端使用定时器来定时发送请求
69.web worker
- 什么是Web Worker?它有什么作用?
Web Worker是一种浏览器提供的多线程解决方案,它可以在后台线程中执行JavaScript代码,从而避免阻塞主线程,提高页面的响应速度和性能。
- Web Worker有哪些类型?它们之间有什么区别?
Web Worker有两种类型:DedicatedWorker和SharedWorker。DedicatedWorker只能被一个页面使用,而SharedWorker可以被多个页面共享。
- Web Worker如何与主线程进行通信?如何在Worker线程中发送和接收消息?
Web Worker通过postMessage方法在主线程和Worker线程之间进行通信。主线程可以通过Worker的onmessage事件监听Worker线程发送的消息,Worker线程可以通过self.postMessage方法向主线程发送消息。
- Web Worker是否可以直接访问DOM?如果不能,有什么替代方案?
Web Worker不能直接访问DOM,因为DOM只能在主线程中操作。如果需要在Worker线程中操作DOM,可以通过postMessage方法将DOM操作请求发送给主线程,然后由主线程来完成操作。
- Web Worker的生命周期是怎样的?如何创建和终止一个Worker线程?
Web Worker的生命周期包括创建、运行和终止三个阶段。可以通过new Worker()方法创建Worker线程,通过terminate()方法终止Worker线程。
- 如何在Webpack或者Rollup等构建工具中使用Web Worker?
worker-loader
- Web Worker是否支持多线程?如果支持,如何创建多个Worker线程并且共享数据?
new 多个 worker
- 使用场景:大量计算和数据处理、多媒体处理、数据传输和通信
大量计算和数据处理:Web Worker可以在后台线程中执行复杂的计算和数据处理操作,从而避免阻塞主线程,提高页面的响应速度和性能。例如,可以使用Web Worker来处理大型数据集的排序、过滤、映射等操作,或者进行图像处理、加密算法等计算密集型任务。
Web Worker可以在后台线程中执行音视频的编解码、解析等操作,从而避免阻塞主线程,提高页面的响应速度和性能。例如,可以使用Web Worker来对音频数据进行混音、降噪、变声等处理,或者对视频数据进行解码、转码、剪辑等操作
Web Worker可以在不同的浏览器窗口或标签页之间共享数据和通信,从而实现实时协作和数据同步。例如,可以使用SharedWorker来共享数据和通信,或者使用Worker来处理WebSocket的数据传输和通信。
70.链式调用
1.Promises链式调用:Promises是一种用于异步编程的技术,它也支持链式调用模式。在Promises中,每个函数返回的都是一个Promise对象,可以在其后面继续调用其他Promise函数。
2.Fluent API链式调用:Fluent API是一种面向对象的编程风格,它的链式调用模式非常灵活。在Fluent API中,每个函数返回的都是当前对象本身,可以在其后面继续调用其他函数
class Person {
constructor(name) {
this.name = name
this.age = 0
}
setName(name) {
this.name = name
return this
}
setAge(age) {
this.age = age
return this
}
introduce() {
console.log(`Hi, my name is ${this.name}, and I'm ${this.age} years old.`)
return this
}
}
const person = new Person('John').setAge(30).introduce()
71.CSR vs SSR
CSR(Client-Side Rendering)和SSR(Server-Side Rendering)是两种不同的前端渲染技术,用于构建 Web 应用程序。下面是它们之间的比较:
- 渲染过程:
-
CSR:在 CSR 中,整个页面的渲染过程发生在客户端(浏览器)中。初始请求返回一个简单的 HTML 文件,然后浏览器下载 JavaScript 文件,并在客户端执行 JavaScript 代码,以动态加载和渲染页面内容。
-
SSR:在 SSR 中,服务器在接收到请求时负责渲染完整的 HTML 页面。服务器执行应用程序的逻辑,并将最终渲染的 HTML 页面发送给客户端。
- 页面加载速度:
-
CSR:CSR 首次加载时需要下载初始的 HTML 文件和 JavaScript 文件。然后,JavaScript 文件需要下载并执行,才能动态生成和渲染页面内容。这可能会导致较长的首次加载时间。
-
SSR:SSR 在服务器端已经渲染好了完整的 HTML 页面,因此首次加载时会迅速呈现页面内容。这可以提供更快的首次加载速度和更好的用户体验。
- SEO(搜索引擎优化):
-
CSR:由于 CSR 中初始 HTML 页面通常是一个简单的骨架,而大部分内容是通过 JavaScript 动态生成的,搜索引擎爬虫可能无法正确抓取和索引页面内容。这可能对 SEO 产生一定的负面影响。
-
SSR:SSR 在服务器端已经渲染好了完整的 HTML 页面,并将其发送给客户端,因此搜索引擎爬虫能够直接抓取和索引完整的页面内容,对 SEO 更加友好。
- 页面交互和复杂性:
-
CSR:CSR 基于 JavaScript 在客户端进行页面渲染和交互,适用于构建复杂的交互式应用程序。一旦初始页面加载完成,后续的页面切换和交互可以更加快速流畅,因为只需要更新和操作 DOM 节点。
-
SSR:SSR 在服务器端渲染页面,因此对于一些简单的页面交互,需要进行额外的客户端 JavaScript 代码编写,以便处理页面的动态更新。相对于 CSR,SSR 的页面交互体验可能稍微受限。
综上所述,CSR 和 SSR 是两种不同的前端渲染技术,它们在渲染过程、页面加载速度、SEO 和页面交互等方面有所差异。选择适合的技术取决于具体的应用需求,包括对首次加载速度、SEO 和复杂页面交互的重视程度。一些应用可能会结合两者的优点,采用混合的渲染方式(如 CSR + SSR 或 SSR + CSR),以在不同场景下获得最佳的性能和用户体验。
优化
1.优化 DOM 操作的性能
对 DOM 查询做缓存(查出来的存下来避免重复查)
将频繁操作改为一次性操作(打个包一次操作)
2.长列表性能优化
- 虚拟滚动:虚拟滚动是一种优化技术,它可以在列表滚动时动态加载和卸载列表项,以减少 DOM 元素的数量。这样可以提高列表的渲染性能和滚动流畅度。虚拟滚动可以通过使用第三方库,如 react-virtualized、vue-virtual-scroller 等,来实现。
- 分页加载:如果列表项数量非常大,虚拟滚动可能无法解决性能问题。这时可以考虑将列表分成多个页面,每次加载一部分数据,以避免一次性渲染大量的 DOM 元素。分页加载可以通过后端 API 和前端的分页组件来实现。
- 优化渲染性能:如果列表项的内容比较复杂,渲染性能可能会受到影响。可以尝试优化渲染性能,例如使用 CSS 动画代替 JavaScript 动画、避免使用复杂的 CSS 样式、使用图片懒加载等。
- 使用 Web Worker:如果列表项的渲染涉及到比较复杂的计算或处理,可以考虑使用 Web Worker 来将这些计算放到后台线程中,以避免阻塞主线程。
- 优化数据流:如果列表的数据来源比较复杂,可能会导致性能问题。可以尝试优化数据流,例如使用缓存、对数据进行分组、避免重复计算等。
让加载更快
-
减少代码资源体积,压缩代码(gzip压缩——>1/3压缩)
-
减少访问次数:合并代码(减少访问次数),SSR服务端渲染,缓存
-
- webpack 打包时对静态资源加 hash 后缀,根据文件内容计算 hash
- 使用更快的网络:CDN(根据区域来决定运营商的IP)
代码层面:
1.减少页面重绘和回流
- 修改 className 批量修改元素样式,减少使用层级较深的选择器
- 复杂的动画元素定位设置为 fixed 或者absolute ,避免回流
- 不使用 table 布局
2.图片压缩、分割
- 将图片压缩(tinyPng),大图片渲染时使用图片分割,使用精灵图
3.字体包压缩
- 使用 font-spider 字蛛提取文字
4.资源预加载:基于 link 标签加入 rel关键字
webpack 优化:
1.优化 resolve.alias 配置,配置别名将原导入路径映射成一个新的,可以减少查找过程
2.优化 resolve.extensions 配置,后缀尝试列表要足够少,不存在的不要放后面,按频次来排列
3.优化 加载 loader,配置loader时,使用include和except来缩小loader执行范围,从而优化性能
让渲染更快
-
CSS放在head,JS放在body的最下面
-
尽早开始执行JS,用 DOMContentLoaded 触发
-
懒加载,(图片懒加载,上滑才加载更多)
-
对DOM查询进行缓存
-
对于频繁的DOM操作,合并到一起 插入到DOM结构
-
节流 throttle 防抖 debounce(体验更好)
API
21.slice 和 splice 的区别 & 数组去重
1.slice 是纯函数,splice是非纯函数。slice传入两个值,开始下标和结束下标,返回中间的值,前闭后开(前取后不取)。splice传参为2个必传和一个选传,必传为开始下标和个数,选传为添加的内容。
2.数组去重
- 使用set数据结构
- indexOf 去重(indexOf 可返回某个指定的元素在数组首次出现的位置,遍历结果,不存在则放入新数组)
- 相邻元素去重,首先调用排序方法sort,然后相邻元素比对,如果不相等,则放入新数组
- 双重 for 循环去重
33.set,map,object汇总
- set 和 map 用于数组去重和数据存储。增:add/set,删:delete,clear,查:has,get,遍历:keys返回所有的key,values返回所有values,entries返回所有键值对
- set 是种集合不能重复;多数情况下用于去重;初始化时需要一个一维数组,key即value。背后手段是哈希表,以空间换时间的数据结构,
- map 是字典,key可以是字符串也可以是对象,不能重复;初始化时需要一个二维数组;key不能修改,可以修改 value。key/value形式存在
- object的key只能是基础类型(string、symbol),无序的
- WeakSet是一种特殊的Set,它只能包含对象,并且对这些对象的引用是弱引用。WeakSet中的对象是无序的,不会重复。WeakSet的主要特点是,它的成员对象没有被其他引用所持有时,会被垃圾回收机制自动回收。WeakSet没有提供像Set那样的迭代方法,也无法获取其大小或检查成员的存在性。
- WeakMap是一种特殊的Map,它的键必须是对象,并且对这些键的引用是弱引用。WeakMap中的键值对是无序的。WeakMap的主要特点是,当键不再被其他引用所持有时,会被垃圾回收机制自动回收。WeakMap没有提供像Map那样的迭代方法,也无法获取其大小或直接遍历键或值。
35.Object.keys() 和 for in 的区别
for in: 主要用于遍历对象的可枚举属性,包括自有属性、继承自原型的属性,不可枚举属性不可输出({value:"18", enumerable:false}),设置 enumerable 为 false 不可输出
object.keys,此方法返回一个数组,元素均为对象自有可枚举的属性,不输出继承自原型的属性和不可枚举的属性。
Object.getOwnProperty,不包括继承自原型的属性
41.判断对象属性是否存在
in,in运算符检查对象是否具有给定名称的属性,in会判断继承过来的属性
const obj = { a: 1, b: 2 };
console.log('a' in obj); // true
console.log('c' in obj); // false
hasOwnProperty: 接收一个参数,用来检测一个对象是否含有特定的自身属性,判断一个属性是否是定义在对象本身或者继承原型链的,
const obj = { a: 1, b: 2 };
console.log(obj.hasOwnProperty('a')); // true
console.log(obj.hasOwnProperty('c')); // false
缺点:不支持 Object.create,会被覆盖,使用 hasOwn
Reflect.has() :两个参数,第一个是检查的对象,第二个参数是检查的属性名称。具有统一的 API 设计,可以用于所有对象,包括普通对象、数组、函数、代理对象等。
42.数组和字符串相互转换的方法
字符串转数组:
1.split方法2.扩展运算符 3.Array.from 4.遍历赋值(遍历使用charAt后push)
"abc".split('') // ['a', 'b', 'c']
[...'abc'] // ['a', 'b', 'c']
Array.from("abc") // ['a', 'b', 'c']
var a = 'abcdefgh';
var arr = [];
for(var i = 0;i<a.length;i++){
arr.push(a.charAt(i));
}
console.log(arr);//['a', 'b', 'c','d', 'e', 'f','g', 'h']
数组转字符串:
1.join方法 2.toString 方法 3.toLocaleString 方法 4.直接拼接空字符串
['a', 'b', 'c'].join('') // 'abc'
['a', 'b', 'c'].toString().replace(/,/g, '')
['a', 'b', 'c'].toLocaleString().replace(/,/g, '')
(['a', 'b', 'c']+ '').replace(/,/g, "")
43.map和foreach的区别
通过 throw new Error 来中断遍历,try catch 来实现
- 返回值不同:map 方法返回一个新数组,而 forEach 方法没有返回值。
- 使用方式不同:map 方法通常用于对原数组进行转换,生成一个新的数组,而 forEach 方法通常用于对原数组进行遍历,并在回调函数中对每个元素执行一些操作。
44.类数组对象转换为标准数组对象的方法
1.Array.from 2.扩展运算符 3.Array.prototype.slice()
其他
1.SVG 和 Canvas
定义:
1.svg: 一种使用 XML 描述的 2D 图形的语言,这样就意味着SVG DOM 中的每个元素附加 Javascript 事件处理器。每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形
2.Canvas:是一块画布,可以在网页中绘制图像
比较
Canvas
- 位图,依赖分辨率,放大可能会造成失真
- 不支持事件处理器
- 弱的文本渲染能力
- 能够以 .png 或 .jpg 格式保存结果图像
- 最适合图像密集型的游戏,其中的许多对象会被频繁重绘
SVG
- 矢量图,不依赖分辨率
- 支持事件处理器
- 最适合带有大型渲染区域的应用程序(比如谷歌地图)
- 不适合游戏应用
3/30
2.HTML5新标签
1.drag and drop API 可拖拽标签元素
2.语义化更好的内容标签,header,nav,footer,aside,article,section
3.音视频的API audio
4.canvas,使用JavaScript操作图像、图形和动画
5.navigator.geolocation 获取设备地理信息
6.localStorage sessionStorage 本地存储数据
7.新的技术 webworker多线程操作web应用程序
8.html5 新增文档声明:
9.新的表单特性:form 中的 autocomplete 属性,input 中的 autofocus
10.webRTC:提供了实时通信功能,允许浏览器之间进行视频和音频通话以及共享数据。
11.Notifications API:允许开发人员向用户发送桌面通知,以便在应用程序不活动时通知用户
12.自定义属性:自定义属性的命名应该以 data- 开头,这可以避免与 HTML5 中已有的属性名冲突,并且符合 HTML5 规范。自定义属性的命名应该使用小写字母、数字和破折号(-),不能包含大写字母和空格。通过 getAttribute 和 setAttribute 方法来访问和修改
3.0.1+0.2 !== 0.3
原因:
JavaScript 中使用基于 IEEE 754 标准的浮点数运算,所以会产生舍入误差。
解决方法:
- 使用 JavaScript 提供的最小精度值判断误差是否在该值范围内
Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON - 转为整数计算,计算后再转回小数
- 设定保留小数的位数
- 使用第三方插件,例如:math.js
- 转成字符串相加(效率较低)
具体过程:
1.整数部分采取 除以2的取余法
2.小数部分采取 乘以2的取整法
因此既有整数又有小数的数值进行二进制转换,就是分别对整数和小数部分进行二进制转换,再相加即可。
0.1 转二进制会发生无限循环, IEEE 754 标准中的尾数位只能保存 52 位有效数字,0.1 转二进制就会发生舍入(0舍1入),就产生了误差。
4.如何绘制 0.5px 的线
1.采用meta viewport的方式
这样子就能缩放到原来的0.5倍,如果是1px那么就会变成0.5px
要记得viewport只针对于移动端,只在移动端上才能看到效果
2.采用transform: scale()的方式
transform: scale(0.5,0.5);
3.使用 box-shadow 设置 box-shadow为 0 0.5px 0 #000
5.图片加载不出来的解决方法
1.显示默认图
1.利用img标签中的 onerror 方法更改 img 的 src 为默认图片路径
优:无须新标签,缺点:加载失败时会显示破裂图,需要多尺寸的破裂图
- 利用img标签中的 onerror 方法更改 img 的 display 为 none,外套一个div,让img标签失败后不可见
优:不会显示破裂图,通过css方式调整尺寸 缺:多一个标签
3.利用 onload 方法更改img的样式display:inline-block,加载过程中再显示,也需要额外标签
6.响应式设计(Responsive Design)的理解以及如何在项目中实现响应式设计
响应式设计就是指让网站在不同设备下实现自适应展示,实现响应式设计有以下几种方法
1.使用流式布局,不使用固定单位使用相对单位,如百分比,rem,vw/vh等,使用flex布局或者grid布局
2.媒体查询:媒体查询是指在CSS中使用@media规则来根据设备屏幕大小、分辨率等特征来设置不同的样式。媒体查询可以很方便地实现响应式设计,但是需要编写大量的CSS代码,并且在不同设备上的样式可能存在兼容性问题。
3.Flexbox布局:Flexbox布局是一种新的布局方式,可以用于实现响应式设计。Flexbox可以根据容器的大小和方向来自动调整子元素的大小和位置,可以实现灵活的响应式布局。
4.Grid布局:Grid布局是另一种新的布局方式,也可以用于实现响应式设计。Grid布局可以根据容器的大小和子元素的位置和间距来自动调整子元素的大小和位置,适用于复杂的响应式布局。
5.JavaScript框架:JavaScript框架如React、Vue等可以通过组件化的方式来实现响应式设计。通过动态生成组件和组件之间的交互,可以实现灵活的响应式布局。
响应式设计的优点是可以适应不同设备和屏幕大小,提供更好的用户体验。然而,不同的响应式方式也存在一些缺点。媒体查询需要编写大量的CSS代码,不够灵活;Flexbox和Grid布局需要浏览器支持,可能存在兼容性问题
6.1 flexable.js 设计思路
flexible.js是一种用于移动端适配的JavaScript库,它的设计思路主要是通过动态设置根元素的字体大小,来实现页面的自适应缩放。
flexible.js的设计思路可以分为以下几个步骤:
- 获取设备像素比:通过window.devicePixelRatio属性获取设备的像素比。
-
- 设备的像素比(Device Pixel Ratio,DPR)是指设备物理像素和设备独立像素之间的比例关系。设备的物理像素是指设备屏幕的实际像素点数,而设备独立像素是指设备屏幕的逻辑像素点数,通常称为CSS像素
- 计算基准字体大小:根据设计稿的宽度和像素比,计算出页面的基准字体大小。通常情况下,我们会将设计稿的宽度设置为750像素,像素比设置为2,这样计算出的基准字体大小为75像素。即1rem = 75/16 = 4.6875px。
- 设置根元素的字体大小:通过设置根元素(即html元素)的字体大小为计算出的基准字体大小,来实现页面的自适应缩放。
- 监听窗口大小变化事件:由于窗口大小可能会发生变化,因此需要监听窗口大小变化事件,重新计算并设置根元素的字体大小。
- 适配特定设备:为了适应特定的设备和浏览器,可能需要进行一些特殊处理。例如,对于iOS设备上的Safari浏览器,需要将viewport的宽度设置为设备的宽度加上20像素,以避免出现横向滚动条。此外,对于一些特殊的设备和浏览器,可能需要单独进行适配。
综上所述,flexible.js的设计思路主要是通过动态设置根元素的字体大小,来实现页面的自适应缩放。该库的实现比较简单,但是可以有效地解决移动端适配的问题。
7.window.onload和DOMContentLoaded的区别
页面上所有的 DOM,CSS,脚本,图片等资源已经加载完毕, onload 事件才会触发时
仅当 DOM 解析完成后, DOMContentLoaded 中的事件就会触发,DOMContentLoaded 加载更快,不用等待 CSS 和 图片的加载完成
8.script 标签上有哪些属性
- src:指定外部脚本文件的 URL。如果设置了该属性,则
- type:指定脚本代码的 MIME 类型。通常情况下,这个属性的值为 "text/javascript" 或 "module"。如果没有指定该属性,则默认为 "text/javascript"。例如:
- async:指定脚本的异步加载方式。如果设置了该属性,则浏览器将会异步加载脚本文件,不会阻塞页面的加载。例如:
- defer:指定脚本的延迟加载方式。如果设置了该属性,则浏览器将会延迟加载脚本文件,直到页面完成解析。例如:
- charset:指定脚本文件的字符编码。通常情况下,这个属性的值为 "UTF-8",可以设置为其他的字符编码。例如:
- crossorigin:指定脚本文件的 CORS 设置。如果需要从其他域名加载脚本文件,则需要设置该属性。例如:
- integrity:指定脚本文件的完整性校验。可以使用该属性来确保脚本文件没有被篡改或者被不信任的第三方修改。例如:
9.
async、defer共同点:
两者都不会阻止页面内容(document)的解析和渲染
async、defer区别:
defer 会在 DOMContentLoaded 前按照页面内出现顺序依次执行;可以确保顺序,但是可能会影响页面加载速度
async 则是下载完立即执行,不一定是在 DOMContentLoaded 前;由于执行时机不确定,多个async属性的脚本之间的执行顺序也不确定,可能会导致脚本之间的依赖关系出现问题
preload、prefetch共同点:
都提前下载
preload、prefetch的区别:
preload适用于加载当前页面所需的关键资源,可以确保资源在页面渲染前被加载完成,以提高页面的渲染速度;而prefetch适用于预请求下一个页面可能需要的资源,可以在用户打开下一个页面时提高页面的加载速度。
10.后端一次性返回十万条数据怎么处理
1.找后端同学沟通,让他把接口改成分页查询的形式。
2.使用定时器分批分组分堆依次渲染
前端拿到10万条数据后先不着急渲染,先将10万条数据分成几堆,比如一堆分10条数据的话,10万条就是1万堆我们只需要使用定时器,每次渲染一堆,渲染一万次就行了,这样页面就不会卡死。
3.分页查询(前端实现)
如果后端不做分页的话,前端接收到所有数据后,点击页码,前端从数据数组中依次截取。比如点页码1就截取1-10条数据,点2就截取11-20条数据,以此类推
4.表格滚动触底加载(滚动到底,再加载一堆)
利用鼠标滚轮事件,判断当滚动条触底的时候就去加载数据。当然触底加载也是需要分堆的,将数据分好堆,然后每次触底就加载一堆即可。
5.使用虚拟列表/无限加载
写一个代表可视区域的div固定高度。通过overflow使其允许纵向y轴滚动。计算可视区域中可以显示的数据条数。用可视区域高度除以单条数据高度就可以得到。监听滚动,当滚动条滚动的时候,计算出被卷起的数据的高度计算可视区域内数据的起始索引,也就是区域内的第一条数据,用卷起的高度除以单条数据的高度。计算可视区域内数据的结束索引。通过起始索引加上刚刚计算出来的可以显示的数据的条数。取起始索引和结束索引中间的数据渲染到可视化区域。计算起始索引对应的数据在列表中的偏移位置,并设置到列表上。
6.以第二种为基准