Skip to content

速记

ES6 新增哪些内容
  1. 变量声明方式新增了let、count,解决了var的各种问题,例如变量提升、没有块级作用域等
  2. 箭头函数,箭头函数this指向要注意,它是从上下文寻找的,没有自身的this
  3. 函数入参默认值,有默认值的只能放在最后,否则会报错
  4. 解构赋值
  5. 拓展运算符
  6. 模板字符串
  7. promise对象,解决了callback回调地狱了,但是一样会有then地狱,所以后续新增await和async,可以用同步的写法去编写异步函数。
  8. 新增了两个数据结构,分别是Set跟Map,Set是类似数组的数据结构,它里面的数据具有唯一性,可以快速给数组去重了;然后Map就是跟对象差不多的,是一种键值对数据结构,在其他语言里面是叫做字典,它的key可以是任何东西,包括方法等,相对于对象,它还自带一些像size的方法,查询多少个属性等便捷方法。
  9. 模块化,export和import,为前端开发提供了模块化开发的可能性。
  10. 新增了一个新的基础数据类型Symbol,它是代表独一无二的值;后面的版本还加了个bigint,突破了number类型的最大最小值。
  11. Class,类跟继承,后面版本陆续扩展了静态、私有等功能。顺带提一嘴,里面的方法最好不要用箭头函数,this的指向会指向外面,而不是指向class实例。
  12. 给各类型数据,例如字符串、数组、对象新增了很多方法,就不一一细举了
mvc 与 mvvm

是两种开发模式,mvc是比较传统的开发模式,mvvm是前端的新开发模式,两种模式的前两个字母都是代表同一样东西,m是model,也就是数据模型,v是view,也就是视图,c是Ctrl,控制器,vm是view-model。 mvc的三层是互相影响的,而且三者都需要开发去开发维护,简单来说就是用户在view层输入指令,通过ctrl层处理逻辑,再发送到model层,最后更新view层。这样会导致view层会依赖model层,从而导致一些业务逻辑不好复用 mvvm的话是将“数据模型数据双向绑定”的思想作为核心,view层跟model层不再粘合在一起,没有直接关系,是通过view-model层实现双向绑定,一般来说,我们前端可以使用vue或者react等框架提供view-model层,业务开发只需要专注于view层跟model层开发就好了。

浏览器打开一个网页的全流程
  1. 用户输入url地址,通过dns解析ip地址,找到服务器, 顺带提一句一个域名可以对应多个IP, 用户在访问时可以被分配到最近的服务器,这样就可以做负载均衡和容灾(虽然这不是前端开发的工作了)。
  2. 找到对应服务器后就开始三次握手tcp连接了。
  3. tcp成功后,客户端开始接收数据了,开始解析html构建dom树,解析css构建Rule树,然后合在一起构建成渲染树,然后计算布局,计算样式,如果后面有位置样式变化就会重复计算布局,计算样式操作,这个就是回流跟重绘,这部分是会相对消耗性能的。vue等框架会构建一个虚拟dom,通过diff算法去尽量减少回流跟重绘。直到没有回流跟重绘就渲染页面完成了。
  4. 到最后就是关闭页面了,关闭页面会跟服务器来个4次挥手的通知服务器关闭了,不用再发消息过来。
JS 中常见的内存泄漏
  1. 意外的全局变量
  2. 被遗忘的计时器或回调函数
  3. 脱离 DOM 的引用
  4. 闭包
proto 和函数的 prototype 属性之间的区别
  • prototype

    • 谁拥有: 函数 (特指可以作为构造函数使用的函数)。普通对象默认没有 prototype 属性。
    • 作用: 它是一个对象,用作通过该构造函数创建出来的新对象的原型 (即新对象的 proto 将指向它)。它定义了所有实例共享的属性和方法 (蓝图/模板)。
    • 何时使用: 主要在定义构造函数时,往其 prototype 对象上添加共享方法和属性。
  • proto

    • 谁拥有: 所有对象 (包括函数,因为函数也是对象)。
    • 作用: 它指向该对象的原型 (parent object)。当试图访问一个对象的属性或方法时,如果在对象自身上找不到,JavaScript 引擎就会沿着 proto 链向上查找,直到找到该属性/方法或到达链的末端 (null)。
    • 何时使用: 主要在运行时,用于实现原型链查找(继承)。

详细解释与示例:

  1. 函数的 prototype 属性
  • 当你定义一个函数时,JavaScript 会自动为这个函数创建一个 prototype 属性,这个属性的值是一个对象。
  • 这个 prototype 对象默认有一个 constructor 属性,指向函数本身。
  • 这个 prototype 对象的主要目的是:当这个函数被用作构造函数 (通过 new 关键字调用) 时,所有由它创建的实例的 proto 属性都会指向这个 prototype 对象。
  • 你可以向函数的 prototype 对象添加属性和方法,这些属性和方法可以被所有实例共享,从而节省内存。
javascript
// 1. 定义构造函数 Person
function Person(name) {
    this.name = name // 实例自身的属性
}

// 2. 在 Person 函数的 prototype 对象上添加共享方法
Person.prototype.sayHello = function () {
    console.log(`Hello, my name is ${this.name}`)
}

// Person 函数有一个 prototype 属性,它是一个对象
console.log(typeof Person.prototype) // "object"
console.log(Person.prototype.constructor === Person) // true

// 3. 使用 new 创建实例
const person1 = new Person('Alice')
const person2 = new Person('Bob')

// 实例自身有 name 属性
console.log(person1.hasOwnProperty('name')) // true
console.log(person1.hasOwnProperty('sayHello')) // false (sayHello 不在实例自身上)

// 实例可以访问 prototype 上的方法
person1.sayHello() // "Hello, my name is Alice"
person2.sayHello() // "Hello, my name is Bob"

// 实例共享同一个 sayHello 方法
console.log(person1.sayHello === person2.sayHello) // true
  1. 对象的 proto 属性
  • proto 是每个 JavaScript 对象 (除了 Object.create(null) 创建的对象) 都具有的一个内部链接,它指向该对象的原型。
  • 当你通过 new Constructor() 创建一个对象 instance 时,instance 的 proto 会被自动设置为 Constructor.prototype。
  • 当你创建一个对象字面量 let obj = {} 时,obj 的 proto 会被自动设置为 Object.prototype。
  • 当你创建一个数组字面量 let arr = [] 时,arr 的 proto 会被自动设置为 Array.prototype (而 Array.prototype 的 proto 又指向 Object.prototype)。
  • proto 形成了原型链。当你访问一个对象的属性或方法时,如果对象本身没有,就会沿着 proto 链向上查找。
javascript
// 续上例

// person1 的 __proto__ 指向 Person.prototype
console.log(person1.__proto__ === Person.prototype) // true

// person2 的 __proto__ 也指向 Person.prototype
console.log(person2.__proto__ === Person.prototype) // true

// Person.prototype 自身也是一个对象,它的 __proto__ 指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype) // true

// person1 可以访问 Object.prototype 上的方法,如 toString
console.log(person1.toString()) // "[object Object]" (默认的 toString)

// Object.prototype 是原型链的顶端之一,它的 __proto__ 是 null
console.log(Object.prototype.__proto__) // null

// 查找过程示例:person1.sayHello()
// 1. 在 person1 自身查找 sayHello -> 没找到
// 2. 查找 person1.__proto__ (即 Person.prototype) -> 找到了 sayHello,执行它

// 查找过程示例:person1.toString()
// 1. 在 person1 自身查找 toString -> 没找到
// 2. 查找 person1.__proto__ (即 Person.prototype) -> 没找到 (假设 Person.prototype 没有重写 toString)
// 3. 查找 Person.prototype.__proto__ (即 Object.prototype) -> 找到了 toString,执行它

关键联系:

new 操作符在创建实例时,将构造函数的 prototype 对象 赋值给了 新实例的 proto 属性。 这是连接 prototype 和 proto 的关键桥梁。

javascript
instance = new Constructor()
// 这步操作隐含了:
instance.__proto__ = Constructor.prototype

注意事项:

  • proto 曾经是非标准属性,但现在已被 ES6 标准化,可以在所有现代浏览器和 Node.js 环境中使用。
  • 推荐使用标准的方法来获取和设置对象的原型:
    • Object.getPrototypeOf(obj): 获取 obj 的原型 (等价于读取 obj.proto)。
    • Object.setPrototypeOf(obj, prototype): 设置 obj 的原型 (等价于设置 obj.proto)。
    • Object.create(prototype): 创建一个新对象,并将新对象的 proto 设置为指定的 prototype 对象。
  • 直接修改一个对象的 proto (或使用 Object.setPrototypeOf) 是一个性能敏感的操作,应尽量避免在性能要求高的代码中频繁使用。最好在对象创建时就确定其原型(通过 new 或 Object.create)。
Csrf(跨站点请求伪造)

攻击者在用户已经登录目标网站之后,诱使用户访问一个伪造的操作页面,通过引导用户的操作,从而对目标网站发起伪造请求,达到攻击目的 后台应该检验 http 请求的来源(reference),禁止其它域名的访问

提高首屏加载时间
  • 提前请求:异步数据数据请求不需要等待页面渲染完成
  • 利用缓存:利用 storage API 对异步请求数据进行缓存,二次启动时先利用缓存数据渲染页面,再进行后台更新
  • 避免白屏:先展示页面骨架和基础内容
  • 及时反馈:及时地对需要用户等待的交互操作给出反馈,避免用户以为小程序没有响应
  • 性能优化:减少页面回流、接口请求、组件拆分
javascript
// v-for 渲染的时候,根据时机进行逐步渲染,减少 CPU 密集
function useDefer(maxCount = 100) {
    const _current = ref(0)
    let _rafId
    function updateCurrent() {
        _rafId = requestAnimationFrame(() => {
            if (_current.value >= maxCount) return
            _current.value += 1
            updateCurrent()
        })
    }

    updateCurrent()

    tryOnScopedDisposed(() => {
        cancelAnimationFrame(_rafId)
    })

    return (index) => _current.value >= n
}
/*
* <ul v-for="(item, index) in items">
		<li v-if="defer(index)"> {{ item }} </li>
  </ul>
* */
讲下esm、cmd、umd区别
  1. ESM ES6 引入的模块化方案,现代浏览器和 Node.js 原生支持,依赖关系在编译时确定 运行环境:

浏览器: script type="module" Node.js: .mjs 文件或 package.json 设置 "type": "module"

✅优点:静态分析支持 Tree Shaking、浏览器原生支持,无需打包工具(但生产环境仍需打包优化)、支持循环引用(模块只被加载一次,存在缓存中)

❌缺点:旧浏览器不支持(需通过 Webpack/Babel 转译)、Node.js 中与 CommonJS 混用需注意兼容性

2.CMD NodeJS默认规范,主要用于服务端,依赖关系在运行时确定,模块同步加载会阻塞执行 运行环境:

Node.js 原生支持 浏览器需通过 Webpack/Browserify 打包

✅优点:Node.js 生态默认支持、简单易用

❌缺点:同步加载不适合浏览器环境(会导致性能问题)、不支持 Tree Shaking

3.UMD

同时兼容 ESM、CMD 和浏览器全局变量,根据环境自动选择导出方式

运行环境

浏览器:直接引入 script 或通过 AMD 加载器 Node.js:直接 require

✅优点:跨环境兼容(浏览器/Node.js/AMD)、适合库开发(如 jQuery、Lodash) ❌缺点:代码冗余(包含多种兼容逻辑)、无法利用 ESM 的静态优化

esm 符号绑定

在 esm 如果导出 let count = 1, 直接修改 count 会报错,但是如果使用函数(相同模块下定义一个操作函数)操作,那么 count 的值会改变,按常理说,导入是值传递,内存空间是两处,不会被影响,此处不用。

栈内存跟堆内存区别
  1. 存储类型

栈: 基本数据类型 堆: 引用类型

  1. 内存分配方式​

栈:

  • ​​自动分配固定大小​​的内存空间(编译时确定);
  • 按值访问,直接操作实际数据。
  • 内存由系统自动管理(压栈/出栈)。

堆:

  • 动态分配​​内存空间(运行时确定)
  • 按引用访问(变量存储的是堆内存地址)。
  • 内存通过垃圾回收机制(GC)管理。
  1. 访问速度​

​​栈: 快 堆: 慢

  1. 生命周期管理​

​​栈: 函数执行完毕后,局部变量立即出栈销毁。 堆: 由垃圾回收器(GC)自动回收不再使用的内存。

defer 跟 async 的区别

两者都不会阻塞 dom 的渲染

  • defer: 延迟加载,当脚本在文档解析完成后执行
  • async:异步加载,下载完毕后立即解释执行代码(执行的时候暂停 dom 的解析,执行完成再继续解析 dom)
页面白屏问题分析与解决

页面白屏是前端开发中常见的问题,通常由JavaScript错误(语法错误,浏览不支持)、资源加载失败或渲染阻塞引起。

vue spa 构建资源位置问题(history 模式​​需要在服务器上配置将所有路由重定向到 index.html);SSR 水合失败

css display、opacity

js 延迟加载方式
  • defer属性
  • async属性
  • 动态创建script标签
  • ES6模块
  • 按需延迟加载
大文件上传

S:

1.​​网络不稳定​​: 上传时间长,网络波动容易导致整个上传失败,需要重头再来。

2.​​服务器压力​​: 服务器需要为每个上传请求保留大量内存和临时空间来处理整个文件。

3.​​用户体验差​​: 用户无法知道上传进度,一旦失败前功尽弃。

A:

1.​​前端分片​​: 使用 JavaScript 的 Blob.prototype.slice方法将文件切成多个小块。(spark-md5 进行文件 hash 判断,是否重复,服务器是否已经存在对应文件)

2.​​并发上传​​: 使用多个 HTTP 请求(如 Promise.all)同时上传这些分片,提高速度。

3.​​断点续传​​: 记录每个分片的上传状态(成功/失败)。即使网络中断,再次上传时也只需传失败的分片,而不是整个文件。

4.​​服务端合并​​: 所有分片上传完毕后,通知服务器将所有分片按顺序合并成完整文件。

JavaScript Bridge

一种通信机制,它建立了 ​​Web 前端(运行在浏览器中的 JavaScript)​​ 和 ​​原生应用程序(Native App,如 Android/iOS)​​ 之间的双向通信通道。

方式

  1. ​​URL Scheme(拦截请求),前端通过创建一个特殊的 ​​伪 URL​​(如 jsbridge://methodName?param1=value1&param2=value2)来发起调用(例如修改 iframe.src或发起 location.href跳转)。原生代码会监听 WebView 的所有请求,当发现约定好的 Scheme(如 jsbridge://)时,​​不进行网络请求​​,而是拦截下这个 URL。解析 URL 中的路径(methodName)和参数(param1=value1),然后执行对应的原生方法。

  2. ​​注入 API(方法注入);原生应用在启动 WebView 时,会向 Window 对象中​​注入一个全局的 JavaScript 对象​​(如 window.NativeBridge),前端代码可以直接调用这个对象上的方法。

sessionStorage 数据共享

新 tab 打开的时候(非窗口级别)会复制上下文的数据,并非共享,之后也拿不到之前 tab 更新后的数据

前端性能优化

:::danger 优化通常指速度加载快,页面流畅

从体积考虑,可以减少构建大小,也就是 tree shaking、懒加载、原子化 css 等机制

从加载速度可以套上 cdn(内容分发网络) 加速

首屏考虑 SSR 预渲染,减少白屏时间,配合 ISR 流式渲染动态内容

从框架上考虑,像是 vue 就可以减少非必须响应式数据,如使用 const 、let 定义或者 shallReactive,减少内存的占用和浏览器重绘

优化动画、函数使用,如防抖,优先使用 css 动画,例如 transformwill-change、3d 加速、减少重排(width、margin、padding 等改变,添加或删除 dom 元素) :::

  • JS
    • 按需引入、懒加载、副作用清除、requestAnimationFrame、useIdleCallback、防抖、节流、遍历(lodash.forEach)
    • 减少同类型的事件绑定,可以使用事件委托而代替。
  • CSS
    • 关注继承属性:能继承的就不用重复写
    • 避免过深的 css 嵌套
    • 尽量使用CSS3动画代替一些JS动画或者git图。
  • 其它
    • 体积优化:压缩、tree shaking、图片资源压缩、cdn 加载、分包
    • http 优化:协商缓存(先请求服务器,根据服务器响应决定是使用新的内容还是缓存)、强缓存(expires 字段,该字段过期前,浏览器只会从缓存中获取数据)

前端性能优化到底该怎么做(下)— 直捣黄龙

Commonjs 和 ES6 模块区别

common 模块是拷贝,可以修改值,es6 模块是引用,只读状态,不能修改值

commonjs 模块是运行时加载,es6 模块是编译时输出接口

事件循环

首先,JavaScript是一门单线程的语言,意味着同一时间内只能做一件事,但是这并不意味着单线程就是阻塞,而实现单线程非阻塞的方法就是事件循环

在JavaScript中,所有的任务都可以分为

同步任务:立即执行的任务,同步任务一般会直接进入到主线程中执行

异步任务:异步执行的任务,比如ajax网络请求,setTimeout定时函数等

从上面我们可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就事件循环

宏任务和微任务(异步任务)

常见的微任务有:

  • Promise.then
  • MutaionObserver
  • Object.observe(已废弃;Proxy 对象替代)
  • process.nextTick(Node.js)

宏任务:

  • script (可以理解为外层同步代码)
  • setTimeout/setInterval
  • UI rendering/UI事件
  • postMessage、MessageChannel
  • setImmediate、I/O(Node.js)

纯函数跟偏函数

纯函数指在相同地输入下始终产生相同地输出并且没有副作用的函数(求和、过滤数组中的奇数)

偏函数指一种创建新函数的方法,通过预设一个或多个参数从而实现对原始函数的封装和定制

javascript
function partial(fn, ...presetArgs) {
    return function (...laterArgs) {
        return fn.apply(this, presetArgs.concat(laterArgs))
    }
}

上述 partial 函数接收一个函数 fn 作为参数,以及一系列预设参数(...presetArgs)。partial 函数返回一个新的函数,当新函数被调用时,它将预设参数和新传入的参数(...laterArgs)合并,并以此调用原始函数 fn。

举个例子,假设我们有一个简单的加法函数 add:

javascript
function add(x, y) {
    return x + y
}

我们可以使用偏函数 partial 来创建一个新的函数 addFive,该函数会将第一个参数预设为 6:

javascript
const addFive = partial(add, 6)
console.log(addFive(3)) // 9

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

  • this: 箭头函数的 this 取决于上下文;普通函数取决于调用方式,也可通过 call、bind、apply 进行 this 指向修改
  • new: 箭头函数不能被 new
  • arguments: 箭头函数没有 arguments 参数,但可以通过扩展运算符的方式进行获取
  • prototype: 箭头函数没有

new 操作符做了什么

在 JavaScript 中,new 操作符用于创建一个给定构造函数的实例对象

javascript
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.sayName = function () {
    console.log(this.name)
}
const person1 = new Person('Tom', 20)
console.log(person1) // Person {name: "Tom", age: 20}
t.sayName() // 'Tom'

从上面可以看到:

  • new 通过构造函数 Person 创建出来的实例可以访问到构造函数中的属性
  • new 通过构造函数 Person 创建出来的实例可以访问到构造函数原型链中的属性(即实例与构造函数通过原型链连接了起来)
如果构造函数返回的是对象,那么该对象会被使用,但如果返回的是原始值,则不受影响(相当于没写 return)

new 流程

  • 创建一个新的对象obj
  • 将对象与构建函数通过原型链连接起来
  • 将构建函数中的 this 绑定到新建的对象 obj 上
  • 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
javascript
// 实现
function myNew(constructFn, ...args) {
    const obj = Object.create({})
    obj.__proto__ = constructFn.prototype
    const result = constructFn.apply(obj, args)
    return result instanceof Object ? result : obj
}
const person = myNew(Person, 'name', 18)

ajax 原理

一种创建交互式网页应用的网页开发技术,可以在不重新加载整个网页的情况下,与服务器交换数据,并且更新部分网页

Ajax 的原理简单来说通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面

实现

  • 创建 Ajax的核心对象 XMLHttpRequest对象
  • 通过 XMLHttpRequest 对象的 open() 方法与服务端建立连接
  • 构建请求所需的数据内容,并通过XMLHttpRequest 对象的 send() 方法发送给服务器端
  • 通过 XMLHttpRequest 对象提供的 onreadystatechange 事件监听服务器端你的通信状态
  • 接受并处理服务端向客户端响应的数据结果
  • 将处理结果更新到 HTML页面中

web 常见攻击

XSS (Cross Site Scripting) 跨站脚本攻击

攻击者将恶意代码植入到提供给其它用户使用的页面中
储存型
  1. 攻击者将恶意代码提交到目标网站的数据库中
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
  5. 这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等
反射型
  1. 攻击者构造出特殊的 URL,其中包含恶意代码
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作
DOM 型
  1. 攻击者构造出特殊的 URL,其中包含恶意代码
  2. 用户打开带有恶意代码的 URL
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作

DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞

预防: 检验用户输入的合法性;对内容进行转义;在使用 .innerHTML、.outerHTML、document.write()、v-html/dangerouslySetInnerHTML 应特别注意

CSRF(Cross-site request forgery)跨站请求伪造

在假冒网站上引导用户操作,从而对已经登录的官方进行接口请求,窃取对应的信息

预防:

  • 阻止不明外域的访问:同源检测、Samesite Cookie
  • 提交时要求附加本域才能获取的信息:Token、cookie

SQL注入攻击

是通过将恶意的 Sql查询或添加语句插入到应用的输入参数中,再在后台 Sql服务器上解析执行进行的攻击

APP 与 H5 通信

简单来说,H5 可以通过创建 iframe 的方式,在这个定义好 JavaScript Bridge 协议和要携带的参数以及 callback 回调,向原生发送消息 IOS、Android 都可以往 windows 上注入相关的函数,方便 H5 直接调用

PostMessage API:HTML 页面和原生应用可以使用 postMessage API 进行跨文档通信。HTML 页面中的 JavaScript 代码可以使用 window.postMessage 方法发送消息给原生应用,原生应用通过监听 window 对象的 message 事件来接收并处理这些消息。

类型比较(==)

原始值比较会先转成数字,比如 true == 1,如果是对象进行比较,会优先调用 valueOf 方法,如果此时拿到的还不是原始值,则使用 toString

Object.is() 跟 === 的区别

  • NaN 在 Object.is 为 true
  • Object.is 对 -0 ,+0 符号会进行区分

+ 法转换

  • 当一侧为String类型,被识别为字符串拼接,并会优先将另一侧转换为字符串类型。
  • 当一侧为Number类型,另一侧为原始类型,则将原始类型转换为Number类型。
  • 当一侧为Number类型,另一侧为引用类型,将引用类型和Number类型转换成字符串后拼接。

有字符串就拼接,有对象就拼接 [object Object] ,其他转数字相加

javascript
;`${123}123` // 123123   (规则1)
123 + null // 123    (规则2)
123 + true // 124    (规则2)
123 + {} // 123[object Object]    (规则3)

window.onload 、 DOMContentLoaded

window.onloadDOMContentLoaded
加载时机在所有 DOM 加载完成后执行DOM 树构建完成
能否多次触发✔️
兼容性IE8 以下不支持IE9以上才支持

资源提示符

  • preload 优先加载本页资源,css、img、script
  • prefetch 其它页面的资源,通知浏览器空闲的时候再进行下载
  • async 不阻塞浏览器的解析,当下载完成的时候直接解析
  • defer 不阻塞浏览器的解析,当全部的资源下载完成的时候再执行

electron 通信方式

event.reply()
javascript
// 渲染进程
ipcRenderer.send('自定义事件', 'params')

// 主进程
const { ipcMain } = require('electron')

ipcMain.on('自定义事件', (event, params) => {
    event.reply('reply', params)
})
异步通信
javascript
// 渲染进程
ipcRenderer.invoke('自定义事件', 'params').then((r) => {
    console.log(r)
})

// 主进程
const { ipcMain } = require('electron')

ipcMain.handle('自定义事件', async (event, params) => {
    return await 'result'
})
同步通信
javascript
// 渲染进程
const result = ipcRenderer.sendSync('自定义事件', 'params') // 会阻塞掉,直到获取 result

// 主进程
const { ipcMain } = require('electron')

ipcMain.on('自定义事件', (event, params) => {
    setTimeout(() => {
        event.returnValue = 'Sync data'
    }, 1e3)
})
主进程主动向窗口发送信息
javascript
// 主进程
mainWin.webContents.send('自定义事件', 'params')

// 渲染进程
ipcRenderer.on('自定义事件', (event, args) => {
    console.log(event, args)
})

流式数据获取

javascript
const resp = await fetch('/a')
const reader = resp.body.getReader()
const decoder = new TextDecoder()

while (true) {
    const { done, value } = await reader.read()
    if (done) break
    const txt = decoder.decode(value)
    console.log(txt)
}

手写 bind

javascript
/**
 * !实现 binBind() 方法
 * @param {*} context 绑定的对象
 * @param  {...any} args 剩余参数
 * @returns
 */
Function.prototype.binBind = function (context, ...args) {
    if (typeof this !== 'function') return console.error('Error')
    context = context !== null && context !== undefined ? new Object(context) : window

    context.fn = this // 这一步不能放在返回的函数里面

    // 返回一个函数
    return function Fn(...args2) {
        // 处理参数,调用函数,返回结果
        const newArr = [...args, ...args2]
        const result = context.fn(...newArr)
        delete context.fn
        return result
    }
}

Promise 解决了什么问题

统一了异步方案,之前偏向于使用回调(但每个库的封装使用方式很乱,不统一)

meta 标签

html
<!--默认视图宽度为设备宽度...避免 IOS 软键盘弹起导致页面布局错乱-->
<meta
    name="viewport"
    content="width=device-width,initial-scale=1.0,minimum-scale=1,maximum-scale=1,user-scalable=no"
/>
<!--HTML 页面中的 <a> 标签 会 自动启用 DNS 提前解析 来提升网站性能,但是在使用 https 协议的网站中失效,可通过设置以下方式进行打开-->
<meta
    http-equiv="x-dns-prefetch-control"
    content="on"
/>
<!--用于声明 MIME 类型 和 文档的 字符编码-->
<meta
    http-equiv="Content-Type"
    content="text/html;charset=utf-8"
/>
<!--在 单页面应用 (Single Page Application, SPA) 中,对应的 html 文件 是不需要被缓存的,只需要对 html 文件中的 CSS、JavaScript 等资源进行缓存(得益于现代构建工具的特性,可以直接使用 强缓存),那么此时我们就可以通过设置 <meta http-equiv> 来实现 html 文件清除缓存功能,如下:-->
<meta
    http-equiv="Expires"
    content="0"
/>
<meta
    http-equiv="Pragma"
    content="no-cache"
/>
<meta
    http-equiv="Cache-control"
    content="no-cache"
/>
<meta
    http-equiv="Cache"
    content="no-cache"
/>
<!--针对 Chrome 浏览器 在访问我们自己的项目时可以让其不进行 自动翻译(避免网站自带的翻译功能跟浏览器的冲突)-->
<meta
    name="google"
    content="notranslate"
/>

暂时性死区

指的是 let、const 声明的变量,在声明之前不能使用,否则会报错。

PerformanceObserver

监听性能指标,比如 DOM 渲染时间、资源加载时间等,可以实时获取到性能指标,从而实现对性能优化的监控。

javascript
// 用法:
const observer = new PerformanceObserver((list) => {
    const entries = list.getEntries()
    for (const entry of entries) {
        if (entry.duration > 1000) {
            // 上报性能指标
        }
    }
})
observer.observe({ entryTypes: ['longtask'] })

网页白屏

  • js 错误、代码错误、加载失败、长时间阻塞
  • 资源加载失败,网络不佳
  • spa + history,绝对路径问题,需要正确的网络配置,返回 index.html 文件
  • 浏览器兼容性

浏览器性能指标

  • FCP

    首次内容绘制:浏览器首次渲染出任何文本、图片(包括背景图)、非白色 canvas 或 SVG 的时间。衡量页面开始加载的反馈。

  • LCP

    最大内容绘制:视口内最大的图片或文本块完成渲染的时间。衡量主要内容加载完成的时间,用户体验核心指标。

  • INP

    下一次绘制的交互:页面所有交互(点击、键入、拖拽)的延迟时间,取一个阈值(通常是第98百分位数)。FID 的替代者,更全面衡量交互响应。

  • CLS

    累计布局偏移:页面整个生命周期中,所有意外布局偏移分数的总和。衡量视觉稳定性。

  • TTFB

    首字节时间:浏览器从服务器接收到第一个字节数据的时间。衡量网络连接和服务器响应速度。

cdn 内容分发网络

一种分布式服务器系统,它通过将网站内容缓存到全球多个地理位置的边缘服务器上,使用户可以从距离最近的服务器获取内容,从而显著提高访问速度。

原理

  1. 用户请求网站内容时,CDN会智能地将请求路由到最近的边缘服务器

  2. 如果边缘服务器有缓存所需内容,直接返回给用户

  3. 如果没有缓存,边缘服务器会从源服务器获取内容,缓存后返回给用户

  4. 后续请求相同内容的用户可以直接从边缘服务器获取,减少延迟

优势

  • 加速网站访问:通过将内容缓存到离用户更近的边缘服务器,大幅减少延迟,提高加载速度。
  • 增强安全性:提供DDoS防护、SSL加密等安全功能,保护源服务器和用户数据。
  • 提高可用性:通过分布式架构和负载均衡,即使某个服务器故障也不会影响整体服务。
  • 减少带宽成本:通过缓存静态内容,减少对源服务器的直接请求,降低带宽消耗和成本。