原理、定义
ref的作用
- 获取dom元素
this.$refs.box - 获取子组件中的data
this.$refs.box.msg - 调用子组件中的方法
this.$refs.box.open()
object.assign和扩展运算符是深拷贝还是浅拷贝,两者区别?
扩展运算符:
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
Object.assign():
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
可以看到,两者都是浅拷贝。
- Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
- 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。
js中用到了oop编程范式,oop是什么,包含了哪些?
oop(Object-Oriented Programming,OOP)是一种面向对象的编程范式,旨在通过使用对象来组织和管理代码。OOP 的核心思想是将数据(属性)和操作数据的行为(方法)封装在一个单一的单位——对象中。这样可以提高代码的重用性、可维护性和可扩展性。
OOP 的核心概念
- 类(Class)
- 类似对象的蓝图或模板。它定义了对象的属性和方法,但不实际创建对象。
- 类定义了对象的结构(数据属性)和行为(方法)。
- 对象(Object)
- 对象是类的实例,是实际存在的实体。每个对象都有独立的状态(属性值)和行为(方法执行)。
- 对象通过类的定义来创建,使用
new关键字或类似的语法在编程语言中实例化。
- 封装(Encapsulation)
- 封装指的是将数据(属性)和对数据的操作(方法)结合在一起,并隐藏内部实现细节。
- 封装使得对象的内部状态对外界隐藏,只能通过公开的方法进行访问和修改。
- 继承(Inheritance)
- 继承允许创建一个新的类(子类),它基于已有的类(父类)进行扩展和修改。
- 子类继承了父类的属性和方法,并可以添加新的属性和方法或重写父类的方法。
- 多态(Polymorphism)
- 多态允许不同的对象以相同的方式响应相同的方法调用。即使对象的类型不同,它们可以通过相同的接口来调用不同的行为。
- 多态可以通过方法重载(在同一类中)或方法重写(在继承的子类中)来实现。
- 抽象(Abstraction)
- 抽象指的是隐藏对象的复杂性,只暴露必要的部分。抽象通过创建抽象类或接口来实现,这些类或接口定义了必须由具体类实现的方法。
$nextTick的作用
$nextTick 是 Vue 实例的方法,用于在 DOM 更新循环结束后执行回调函数。它确保在下次 DOM 更新周期完成后再进行操作,这对于确保在操作 DOM 或更新数据后,所有变化都已经应用到视图中很有用。
例如,修改完数据并希望在 DOM 更新后执行某些操作(如获取更新后的元素尺寸),以确保在执行操作时 DOM 已经是最新的
this.$nextTick(() => {
console.log('DOM updated'); // 在 DOM 更新后执行的代码
});
父子组件的通信原理
- 父组件通过
v-bind向子组件传数据,子组件用props属性接收数据 - 子组件通过
$emit向父组件发送数据,父组件用点击事件绑定和接收数据 - 使用全局事件总线
Event Bus - 父组件用
provide提供数据,子组件使用inject接收这些数据
双向数据绑定原理
vue的双向数据绑定原理是什么?里面的关键点在哪?
Vue 双向绑定就是:数据变化更新视图,视图变化更新数据
Vue的双向数据绑定原理主要基于:
- 数据劫持和观察者(发布-订阅)模式实现的,vue2采用Object.defineProperty实现,vue3采用Proxy实现,
- 其关键点在于数据劫持,就是说对数据的读取和修改进行拦截。
- 而观察者模式,是当属性发生改变的时候,使用该数据的地方也发生改变。
Vue2 跟 vue3 响应式的区别
Vue2.0和Vue3.0的区别在于性能优化、组合式API、响应式系统和虚拟DOM等方面
- Vue 2 使用 Object.defineProperty 实现响应式,而 Vue 3 使用 ES6 的 Proxy 对象实现响应式。
- Vue 2 只能监听存在的属性,无法监听数组的索引变化或动态添加对象属性,而Vue3可以监听数组的索引变化和动态添加对象属性,进行响应式处理。
- 总的来说,Vue 3 的响应式系统更灵活,能够更好地处理复杂的数据结构,并且在性能上有所提升
Foreach、map、for的区别
- forEach在return时返回undefined;map返回新数组
- 数组类型为基本数据类型时(arr=[1,2,3]),forEach()和map()都不会改变原数组;
- 数组类型为引用数据类型时(arr=[{name: 2, age: 18},{},{}]),forEach()和map()都会改变原数组;
- forEach()不支持链式操作;map()支持链式操作;
for
for循环是 JavaScript 的基本语法之一,也可以用于遍历数组。for循环需要手动控制循环的索引值和循环的边界条件,因此需要编写更多的代码。for循环可以使用break和continue控制循环的流程。for循环通常用于对数组进行复杂的遍历,或者需要根据数组元素生成一个新的数组时使用。
let arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
Ajax、Axios、Fetch 的区别
定义:
- AJAX 是一种技术,用于在后台与服务器交换数据并更新网页部分内容,而无需重新加载整个页面。它本身不是一个库,而是一种技术的实现方式。
- Axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。它简化了 HTTP 请求,并提供了易于使用的 API 和请求拦截器。
- Fetch 是一个现代的浏览器内置 API,用于处理 HTTP 请求和响应。它基于 Promise,比旧的
XMLHttpRequest更强大和灵活。
使用上的区别:
- AJAX 通常指使用
XMLHttpRequest来发起请求,配置较为复杂,不支持 Promise,错误处理不如现代方法直观。 - Axios 提供了更简洁的 API,支持 Promise 和
async/await,可以更轻松地处理请求和响应,支持拦截器、取消请求等高级功能。 - Fetch 使用
Promise,具有更简洁的语法,但需要手动处理响应的 JSON 解析和网络错误处理,默认不支持请求和响应拦截器。
性能上的区别:
- AJAX 的性能取决于
XMLHttpRequest的实现,通常较为基础。 - Axios 的性能与
Fetch相近,但由于其内建的功能(如请求和响应拦截),在某些场景下可能会略慢。 - Fetch 在现代浏览器中性能优秀,且因为其较为简洁的设计,通常能实现更高的效率,但在旧浏览器中的支持需考虑 polyfills。
哪些遍历方式会修改原数组?
unshift、shift、push、pop、splice会改变原数组
forEach、map、filter、reduce等遍历方式不会改变原数组
本地存储有哪些?有什么区别?
- Cookie:
- 存储方式:以键值对的形式存储在客户端,并随每次 HTTP 请求发送到服务器。
- 容量:每个 cookie 的存储容量通常不超过 4KB。
- 生命周期:可以设置过期时间,可以长期保存在客户端,即使浏览器关闭后仍然存在。
- 安全性:由于会随每个 HTTP 请求发送到服务器,因此可能会带来安全风险,且可以被用户和服务端修改。
- 主要用途:用于会话管理、跟踪用户信息等。
- localStorage:
- 存储方式:同样以键值对的形式存储在客户端,但数据保存在浏览器中。
- 容量:每个域名下一般可以存储 5MB 左右的数据。
- 生命周期:除非被清除,否则会一直保存在客户端,没有过期时间。
- 安全性:数据仅存储在客户端,不会随每个 HTTP 请求发送给服务器。
- 主要用途:用于本地持久化存储,适合存储较大量的数据。
- sessionStorage:
- 存储方式:同样以键值对的形式存储在客户端,数据仅在当前会话期间有效。
- 容量:与localStorage相似,一般也是 5MB 左右。
- 生命周期:数据仅在当前会话期间有效,关闭标签页或浏览器后数据被清除。
- 安全性:数据仅存储在客户端,不会随每个 HTTP 请求发送给服务器。
- 主要用途:适合在会话期间临时存储数据,数据不需要长期保留。
总体来说
- Cookie 是最古老的一种存储方式,主要用于会话管理和用户信息跟踪;
- localStorage 适合用于本地持久化存储大量数据;
- sessionStorage 则适合在会话期间存储临时数据
JS数据类型,如何判断?
常见数据类型有8种,分别为:
数字number、布尔值boolean、字符串string、对象object
数组array、函数function、空值null、未定义undefined
可通过typeof、instanceof、isArray等来判断数据类型
ES6新特性
let、const关键字、箭头函数、扩展运算符、解构赋值、类和继承、模块化等
Vue 组件 data 为什么必须是函数 ?
data 是一个函数,是为了确保每个组件实例有自己的数据副本。当创建多个组件实例时,每个实例都需要独立的数据状态。如果 data 是一个对象,那所有组件实例会共享同一个对象,导致数据冲突。使用函数返回一个对象,可以确保每个组件实例有自己的数据对象,从而避免这种问题。
var、let、const的区别
- 作用域:
var:var声明的变量具有函数作用域或全局作用域。这意味着变量在声明它的函数内可见,而且在函数外也可以访问。let:let声明的变量具有块级作用域。这意味着变量仅在声明它的块(例如,if语句或循环)内可见。const:const声明的变量也具有块级作用域,类似于let,但其值不能被重新赋值。请注意,这不意味着该值是不可变的,而只是指变量名不能再指向其他值。
- 变量提升:
var:var可以进行变量提升。这意味着你可以在变量声明之前使用变量,但其值将为undefined。let和const:let和const声明的变量不会被提升,这意味着在变量声明之前使用变量会抛出错误。
- 重复声明:
var:可以多次声明同一个变量,并且不会抛出错误。let和const:不允许在同一作用域内重复声明同一个变量,否则会抛出错误。
- 重新赋值:
var和let:声明的变量可以被重新赋值,并且其值可以更改。const:声明的变量是常量,一旦赋值后就不能再更改。但对于对象和数组等引用类型,其属性或元素可以修改。
综上所述,推荐使用let和const来声明变量,因为它们具有更严格的作用域规则,并能避免一些常见的问题,如变量提升和重复声明。而const则适用于那些不需要被重新赋值的常量。只有在特殊情况下才使用var
let、const解决了什么问题?
JavaScript底层原理包括变量提升、作用域链等,ES6引入了let和const来解决var存在的全局作用域、变量污染等问题,同时通过块级作用域和暂时性死区提高了代码可读性和安全性,满足了JavaScript成为企业级语言的需求,const还解决了var缺乏常量功能的问题。
Vue生命周期、组件通讯、Vuex、登录拦截、导航守卫、常用指令
vue2 生命周期:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed
vue3 生命周期:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、 beforeUnmount、unmounted
mixins生命周期跟上面一致,DOM渲染在mounted周期中就已经完成
第一次加载页面会触发 beforeCreate,created,beforeMount,mounted这四个钩子
组件通讯:props、$emit(父子)、$parent、$children、$refs(父子)、EventBus、vuex(父子、隔代、兄弟)
Vuex属性:state、getters、mutations、actions、modules
登录拦截:可通过路由守卫、请求拦截、响应拦截等方式实现
导航守卫:全局前置守卫、全局后置钩子、路由独享守卫、组件内守卫
常用指令:v-model、v-for、v-show、v-if、v-bind、v-on等
Vuex执行流程
在 vue 组件里面,通过 dispatch 来触发 actions 提交修改数据的操作,然后通过 actions 的 commit 触发 mutations 来修改数据,mutations 接收到 commit 的请求,就会自动通过 mutate 来修改 state,最后由 store 触发每一个调用它的组件的更新。
Vuex怎么请求异步数据
首先在 state 中创建变量,然后在 action 中调用封装好的 axios 请求,异步接收数据,commit 提交给 mutations,
Mutations 中改变 state 中的状态,将从 action 中获取到的值赋值给 state
父子组件之间的声明周期执行顺序
父beforeCreate、父created、父beforeMount、子beforeCreate、子created、子beforeMount、子mounted、父mounted
v-if跟v-show的区别
v-if会销毁DOM元素,而v-show不会销毁,只会在样式上隐藏元素
如果频繁切换时可用v-show,运行时较少改变则用v-if
监听属性和计算属性的区别
Vue的监听属性和计算属性都用于监测数据变化,
但监听属性是一个需要手动调用的函数,而计算属性是一个会自动计算并缓存结果的属性
闭包
闭包是指函数内部可以访问外部函数的变量,可用于封装私有变量和实现柯里化等操作。
@@@
Url到浏览器的过程有哪些步骤?(*)
将URL渲染到浏览器的过程可以分为以下几个步骤和流程:
- URL解析:浏览器接收到URL后,会进行URL解析,提取出协议、主机名、端口号、路径等信息。
- DNS解析:浏览器将主机名转换为对应的IP地址,这个过程称为DNS解析。浏览器首先会检查本地缓存中是否存在对应的IP地址,如果没有,则会向DNS服务器发送请求,获取IP地址。
- 建立网络连接:浏览器使用提取到的IP地址和端口号,与Web服务器建立TCP连接。这个过程包括三次握手,确保双方能够正常通信。
- 发送HTTP请求:浏览器通过TCP连接向Web服务器发送HTTP请求。请求包括请求方法(GET、POST等)、请求头部(包含用户代理、Cookie等信息)、请求体(POST请求的数据)等。
- 服务器处理请求:Web服务器接收到浏览器发送的HTTP请求后,根据请求的路径和参数,处理请求并生成相应的HTTP响应。
- 接收响应:浏览器接收到来自服务器的HTTP响应,包括响应状态码、响应头部和响应体等。
- 渲染页面:浏览器根据接收到的响应数据,开始解析HTML文档,并构建DOM树。同时,解析CSS样式表,构建CSSOM树。然后将DOM树和CSSOM树合并,生成渲染树。最后,根据渲染树进行页面布局和绘制,得到最终的呈现结果。
- 显示页面:浏览器将渲染好的页面内容显示在用户界面上。
- 执行JavaScript:如果HTML文档中包含JavaScript代码,浏览器会执行这些代码,以实现交互和动态效果。执行过程中可能会修改DOM树和CSS样式,触发页面重新渲染。
vue项目中,不使用框架怎么封装?
可以使用原生的html、css、JavaScrip来实现,需要手动处理数据和事件等方面
什么是JS原型,原型链?
JavaScript的原型是指:每个对象都有一个原型对象,原型对象又有自己的原型,形成了一条原型链。
原型链详细解释:
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的 prototype,如果还没有找到就会再在构造函数的 prototype 的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
作用域是什么?
变量的可访问范围,具体分为全局作用域和函数作用域等
0.1+0.2 == 0.3吗?为什么?
0.1+0.2不等于0.3,这是因为JavaScript采用的是IEEE 754浮点数标准,存在精度问题。可以使用toFixed方法处理。
keep-alive是什么?有哪几个生命周期阶段?
keep-alive是Vue中的一个组件,用于缓存组件实例、提高组件的渲染效率,其生命周期包括activated和deactivated等阶段
set和map是什么
Set是一种数据结构,用于存储唯一值,Map是一种键值对数据结构,用于存储任意类型的键和值
介绍下promise
Promise是一种异步操作管理解决方案,用于解决回调地狱问题,其特点是链式调用和状态机制,通常会解决以下三种问题:
- 通过then方法实现链式回调
- 通过all方法同时发起多个异步请求,谁先有结果就拿谁的
- 发起多个请求,通过race方法实现等待多个异步请求的结果
JS 异步解决方案的发展历程以及优缺点
-
回调函数(callback)
优点:解决了同步的问题(只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。)
缺点:回调地狱,不能用 try catch 捕获错误,不能 return
-
Promise
优点:解决了回调地狱的问题
缺点:无法取消 Promise ,错误需要通过回调函数来捕获
-
Generator
特点:可以控制函数的执行,可以配合 co 函数库使用
-
Async/await
优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
Evenbus是什么东西?
EventBus是一种事件发布/订阅机制,可以实现组件之间的通讯
http与https的区别
| 定义 | 端口号 | 连接方式 | |
|---|---|---|---|
| http | 超文本明文传输协议 | 80 | 基于请求-响应模式,无连接、无状态保存 |
| https | ssl加密传输协议 | 443 | ssl+http模式,加密传输、身份认证 |
懒加载与预加载的区别
懒加载(Lazy Loading)和预加载(Preloading)是两种优化页面加载性能的技术,它们有不同的作用和效果:
- 懒加载(Lazy Loading):懒加载是指延迟加载页面中的某些资源,直到这些资源真正需要被加载时才进行加载。通常用于图片、视频等内容,在用户滚动页面或执行特定操作时才加载这些资源,以减少初始页面加载时的资源请求量,提高页面加载速度。懒加载能够减少初次加载时需要下载的内容量,从而减少页面加载时间。
- 预加载(Preloading):预加载是在页面加载过程中提前加载可能需要使用到的资源,以减少后续请求时的等待时间。通过在页面中添加
<link rel="preload">标签或 JavaScript 动态加载资源,可以在浏览器空闲时提前加载资源。预加载可以加速后续资源加载的过程,提高用户体验,但同时也会增加初始页面加载的时间,因为需要额外加载资源。
总的来说,懒加载主要减少了页面初始加载时的资源请求量,提高了首屏加载速度;而预加载则提前加载可能需要使用到的资源,提高了后续请求时的加载速度。具体使用哪种技术取决于页面的具体需求和资源加载情况。在实际开发中,可以结合使用懒加载和预加载来更好地优化页面加载性能。
v-for循环中key的作用
Key 值的存在保证了唯一性,Vue 在检查dom节点时,如果没有 key 值,就会对内容清空并赋新值,如果有 key 值,则会对新老节点进行对比,比较两者 key 是否相同,来进行调换位置或删除的操作。
什么是计算属性
计算属性是用来声明式的描述一个值依赖了其他的值,当它依赖的这个值发生改变时,就更新 DOM;
当在模板中把数据绑定到一个计算属性上时,vue 会在它依赖的任何值导致该计算属性改变时更新 DOM;
每个计算属性都包括一个 getter 和 setter,读取时触发 getter,修改时触发 setter;
总之,计算属性是一种通过对其他状态进行动态计算得到的属性,它在简化数据处理、提高代码可读性和性能优化方面具有重要的作用。
计算属性与监听的区别
区别就是 computed 有缓存功能,当无关数据数据改变时,不会重新计算,而是直接使用缓存中的值,而watch则没有缓存功能
Route与Router的区别
route 是一个跳转的路由对象,每一个路由都会有一个 route 局部对象,可以获取对应的 name,path,params,query 等;
router 是 VueRouter 的一个对象,通过 Vue.use(VueRouter)和 VueRouter 构造函数得到一个 router 的全局实例对象,其中包含了所有路由许多关键的对象和属性。
Hash与History的区别
- Hash 模式:
- 在 hash 模式下,URL 中的 hash(#)部分被用来管理路由,例如:
http://www.example.com/#/about。 - 在不同的路由之间切换时,实际上是改变了 URL 中的 hash 部分,而不会向服务器发送请求。
- 因为 hash 不会被包括在 HTTP 请求中,所以不会触发页面的完全刷新,这样可以有效地避免页面的重新加载,同时也能够支持老版本浏览器。
- 在 hash 模式下,URL 中的 hash(#)部分被用来管理路由,例如:
- History 模式:
- 在 history 模式下,URL 中不再需要包含 # 符号,例如:
http://www.example.com/about。 - 使用 HTML5 History API 来实现路由切换,因此需要服务器端的支持。当用户访问路由时,会发送真正的 HTTP 请求到服务器,服务器需要配置来正确处理这些 URL。
- 由于使用了真实的 URL 路径,因此可以更加友好地展示在地址栏中,并且不会带有 # 符号。
- 在 history 模式下,URL 中不再需要包含 # 符号,例如:
总的来说,hash 模式在兼容性和部署简单性方面具有优势,因为不需要特殊的服务器配置;而 history 模式则使得 URL 更加美观,并且可以利用 HTML5 History API 的额外功能。在选择路由模式时,需要根据项目需求、服务器配置以及兼容性等因素进行综合考虑。
Promise 构造函数是同步执行还是异步执行,那么then 方法呢?
new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
}).then(() => {
console.log(3)
})
console.log(4)
执行结果是:1243,promise 构造函数是同步执行的,then 方法是异步执行的
setTimeout(()=>{
console.log(1)
},0)
new Promise((resolve, reject) => {
console.log(2)
for(let i=0; i<100; i++) {
i==99 && relove();
}
console.log(3)
}).then(()=>{
console.log(4)
})
console.log(5)
执行结果是:23541
vue2相比vue3的响应式原理有什么缺陷?
为什么在 Vue3.0 采用了 Proxy,抛弃了 Object.defineProperty?
- Object.defineProperty 无法监控数组下标的变化,导致通过数组下标添加元素,不能实时响应;
- Object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果属性值是对象,还需要深度遍历;而Proxy 可以劫持整个对象,并返回一个新的对象。
- Proxy 不仅可以代理对象,还可以代理数组、代理动态增加的属性。
CSS选择器的优先级
!Important>行内样式>ID 选择器>类选择器>标签>通配符>继承>浏览器默认属性
CSS 单位中 px、em 和 rem 的区别?
- px 像素:绝对单位。像素 px 是相对于显示器屏幕分辨率而言的,是一个虚拟长度单位,是计算机系统的数字化图像长度单位
- em 是相对长度单位,相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。它会继承父级元素的字体大小,因此并不是一个固定的值
- rem 是 CSS3 新增的一个相对单位(root em,根 em),使用 rem 为元素设定字体大小时,仍然是相对大小,但相对的只是 HTML 根元素
区别:
IE 无法调整那些使用 px 作为单位的字体大小,而 em 和 rem 可以缩放,rem相对的只是 HTML 根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了 IE8 及更早版本外,所有浏览器均已支持 rem
Display:none 与 visibility:hidden 的区别?
dispaly:none 设置该属性后,该属性下的元素都会隐藏,占据的空间消失
visibility:hidden 设置该元素后,该元素虽然也会隐藏,但任然占据空间的位置
::before 和::after 中双冒号和单冒号有什么区别、作用?
区别
在 CSS 中伪类一直用 : 表示,如 :hover, :active 等
伪元素在 CSS1 中已存在,当时语法是用 : 表示,如 :before 和 :after
后来在 CSS3 中修订,伪元素用 :: 表示,如 ::before 和 ::after,以此区分伪元素和伪类
由于低版本 IE 对双冒号不兼容,开发者为了兼容性各浏览器,继续使使用 :after 这种老语
法表示伪元素
单冒号(:)用于 CSS3 的伪类
双冒号(::)用于 CSS3 的伪元素
想让插入的内容出现在其它内容前,使用::before,否者,使用::after;
在代码顺序上,::after 生成的内容也比::before 生成的内容靠后
如果按堆栈视角,::after 生成的内容会在::before 生成的内容之上
作用
::before 和::after 的主要作用是在元素内容前后加上指定内容
伪类与伪元素都是用于向选择器加特殊效果
伪类与伪元素的本质区别就是是否抽象创造了新元素
伪类只要不是互斥可以叠加使用
伪元素在一个选择器中只能出现一次,并且只能出现在末尾
伪类与伪元素优先级分别与类、标签优先级相同
for in 和 for of 的区别
- for...in适合用于循环对象,for...of 适合用于循环数组
- for...in 循环出的是 key,for...of 循环出的是 value
- for...of 是 ES6 新引入的特性。修复了 ES5 引入的 for...in 的不足
- for...of 不能循环普通的对象,需要通过和 Object.keys()搭配使用
Ajax 的实现流程
1、创建 XMLHTTPRequest 对象,也就是创建一个异步调用对象.
var xhr = new XMLHTTPRequest();
2、创建新的 HTTP 请求,并指定其请求方法、URL 及验证信息.
// 这里的第一个参数是请求的类型,第二个参数是要请求的 URL,第三个参数表示是否使用异步方式发送请求
xhr.open('POST', 'https://example.com/data', true);
3、设置响应 HTTP 请求状态变化的函数.
xhr.onreadystatechange=function(){
if(xhr.readyState===4&&/^2\d{2}/.test(xhr.status)){
// 说明数据已经传输到了客户端
}
}
4、发送 HTTP 请求.
xhr.send();
5、获取异步调用返回的数据,然后使用 JavaScript 和 DOM 实现局部刷新
//接收服务器响应数据
function response22() {
//判断请求状态码是否是 4【数据接收完成】
if(HTTPRequest.readyState==4) {
//再判断状态码是否为 200【200 是成功的】
if(HTTPRequest.status==200) {
//得到服务端返回的文本数据
var text = HTTPRequest.responseText;
//把服务端返回的数据写在 div 上
var div = document.getElementById("result");
div.innerText = text;
}
}
}
Get 和 Post 的区别以及使用场景
区别
1、Get 使用 URL 或 Cookie 传参。而 Post 则将数据放在 body 中
2、Get 的 URL 会有长度限制,则 Post 的数据则可以非常大
3、Post 比 Get 安全,因为数据在地址栏上不可见
最本质的区别
Get 是用来从服务器上获得数据,而 post 是用来向服务器传递数据
使用场景
若符合下列任一情况,则用 Post 方法:
- 请求的结果有持续性的作用,例如:数据库内添加新的数据行
- 若使用 Get 方法,则表单上收集的数据可能让 URL 过长
- 要传送的数据不是采用 ASCII 编码
若符合下列任一情况,则用 Get 方法:
- 请求是为了查找资源,html 表单数据仅用来搜索
- 请求结果无持续性的副作用
- 收集的数据及 html 表单内的输入字段名称的总长不超过 1024 个字符
使用箭头函数应注意什么
- 箭头函数没有this,this 不指向 window,而是父级(指向是可变的)
- 不能使用 arguments 对象
- 不能用作构造函数
- 不能用作 Generator 函数
SPA单页面的理解
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
设计模式和准则
设计模式:
● 工厂模式(Factory Pattern):用于创建对象的模式,通过工厂函数或类来创建对象实例。
● 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
● 观察者模式(Observer Pattern):定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知。
● 发布-订阅模式(Publish-Subscribe Pattern):类似观察者模式,但使用一个调度中心来管理订阅和发布事件。
● 策略模式(Strategy Pattern):定义一系列算法,将每个算法封装起来,并使它们可以互相替换。
● 装饰者模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,就像给一个人穿不同的外套一样。
准则:
● DRY原则(Don’t Repeat Yourself):避免重复代码,尽量将重复的逻辑抽象成函数或模块。
● 单一职责原则(Single Responsibility Principle):一个类应该只有一个引起变化的原因。
● 开放-封闭原则(Open-Closed Principle):软件实体应该对扩展开放,对修改封闭。
● Liskov替换原则(Liskov Substitution Principle):子类应该能够替换其父类而不影响程序的正确性。
● 接口隔离原则(Interface Segregation Principle):多个特定接口要好于一个通用接口。
● 依赖倒置原则(Dependency Inversion Principle):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
操作
合并数组、对象
合并数组:
- 用
concat() - 使用ES6语法并去重:
[...new Set([...a,...b])]
合并对象:
- Object.assign({目标对象}, 源对象),有去重效果
- const obj = {...obj1,...obj2}
引用类型
let obj1={val: 1,name: "名称1"};
let obj2=obj1;
obj1.val=2;
console.log(obj1.val+obj2.val) // 4
初始时,obj1.val 和 obj2.val 都是 1。
当你将 obj1.val 改为 2 时,obj2.val 也变为 2。
因此,obj1.val + obj2.val 的结果是 2 + 2,即 4。
obj2 是对 obj1 的引用。因此,当你修改 obj1.val 时,obj2.val 也会发生变化,因为两个变量指向同一个对象。
操作数组方式
遍历、添加、删除、去重、排序等
怎么区分数组和对象?
方法一:通过 ES6 中的 Array.isArray 来识别
Array.isArray([]) //true
Array.isArray({}) //false
方法二:通过 instanceof 来识别
[] instanceof Array //true
{} instanceof Array //false
方法三:通过调用 constructor 来识别
{}.constructor //返回 object
[].constructor //返回 Array
方法四:通过 Object.prototype.toString.call 方法来识别
Object.prototype.toString.call([]) //["object Array"]
Object.prototype.toString.call({}) //["object Object"]
typeof 跟 instanceof 的区别
typeof 用于检查变量的数据类型,它能返回一个表示变量类型的字符串,例如 'number', 'string', 'object', 'function' 等,typeof 对原始类型和函数特别有效,但对于对象的细节不够具体。
typeof 42; // 'number'
typeof 'hello'; // 'string'
typeof [] // 'object'
typeof function() {}; // 'function'
instanceof 用于检查对象是否是某个构造函数的实例。它检查对象的原型链上是否包含构造函数的 prototype 属性,适用于检测对象类型和继承关系。
[] instanceof Array; // true
{} instanceof Object; // true
new Date() instanceof Date; // true
简单来说,typeof 用于检测基本数据类型,而 instanceof 用于检测对象的构造函数和原型链。
对象/数组常用方法
push、pop、shift、unshift、slice、splice、concat、map、filter、reduce等
创建空对象/空数组有哪些方法
创建空数组:[] 或 new Array(),创建空对象:{} 或 new Object()
JS创建对象的几种方式
- 使用对象字面量{}创建
var Cat = {};
Cat.name = "kity";
Cat.age = 18;
Cat.sayHi=function(){
alert("hello"+Cat.name+",今年"+Cat["age"]+"岁了") // .或HashMap方式访问都可
};
Cat.sayHi(); // 调用方法
- 使用无参构造函数 / 有参构造函数来创建
/* 无参构造函数 */
function Person() {}; // 定义一个function,如果有new关键字去实例化,那么function可看作一个类
var one = new Person();
one.name="dady";
one.work=function(){
alert(one.name+"在工作")
}
one.work(); // 调用方法
/* 有参构造函数 */
function Person(name,age,work) {
this.name=name;
this.age=age;
this.work=function(){
alert("我叫"+this.name+",我"+this.age+"岁了")
}
};
let maidou = new Person("麦兜", 18)
maidou.work(); // 调用方法,我叫麦兜,我18岁了
- 使用工厂方法(Object关键字)
let dog = new Object();
dog.name="旺财";
dog.age=3;
dog.work=function(){
alert("我是"+dog.name)
}
dog.work(); // 我是旺财
- 使用原型对象(prototype关键字)
function Dog(){}
Dog.prototype.name="旺财";
Dog.prototype.eat=function(){
alert(this.name+"是个吃货")
}
let wangcai = new Dog();
wangcai.eat(); // 旺财是个吃货
- 混合模式(原型+有参构造函数)
function Car(name,price){
this.name=name;
this.price=price;
}
Car.prototype.sell=function(){
alert("车名叫"+this.name+",卖"+this.price+"万")
}
let baoma = new Car("宝马",40);
baoma.sell(); // 车名叫宝马,卖40万
如何改变一个函数的上下文?
通过使用 call、apply、bind等方法
数组去重
- 使用ES6的扩展运算符 "..." 和 set数据结构:const newArray = [...new Set(array)]
const array = [1, 2, 3, 3, 4, 5, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 4, 5]
- 使用 filter 过滤方法
const array = [1, 2, 3, 3, 4, 5, 5];
/**
* value: 数值
* index: 索引
* self: 数组本身
* indexOf(value):返回指定元素在数组中第一次出现的位置,找到了返回true,如果没有则返回 -1
*/
const uniqueArray = array.filter((value, index, self) => {
return self.indexOf(value) === index;
});
console.log(uniqueArray); // [1, 2, 3, 4, 5]
- 使用 reduce 方法
const array = [1, 2, 3, 3, 4, 5, 5];
/**
* acc(必选): 累加器,表示初始值或者是上次调用函数累加的返回值
* value(必选): 数值
* index(可选): 索引
* array(可选): 数组本身
*/
const uniqueArray = array.reduce((acc, value) => {
if (!acc.includes(value)) {
acc.push(value); // 检查数组acc中是否包含特定的值value,如果不包含,则将该值添加到数组中
}
return acc;
}, []);
console.log(uniqueArray); // [1, 2, 3, 4, 5]
- 双重for循环
function unique(arr) {
for(let i=0; i<arr.length; i++) {
for(let j=1; j<arr.length; j++) {
if(arr[i] == arr[j]) {
// 如果第一个等于第二个,则删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
对象结构转树结构
const arr = [
{ id: 12, parentId: 1, name: "朝阳区" },
{ id: 241, parentId: 24, name: "田林街道" },
{ id: 31, parentId: 3, name: "广州市" },
{ id: 13, parentId: 1, name: "昌平区" },
{ id: 2421, parentId: 242, name: "上海科技绿洲" },
{ id: 21, parentId: 2, name: "静安区" },
{ id: 242, parentId: 24, name: "漕河泾街道" },
{ id: 22, parentId: 2, name: "黄浦区" },
{ id: 11, parentId: 1, name: "顺义区" },
{ id: 2, parentId: 0, name: "上海市" },
{ id: 24, parentId: 2, name: "徐汇区" },
{ id: 1, parentId: 0, name: "北京市" },
{ id: 2422, parentId: 242, name: "漕河泾开发区" },
{ id: 32, parentId: 3, name: "深圳市" },
{ id: 33, parentId: 3, name: "东莞市" },
{ id: 3, parentId: 0, name: "广东省" },
];
/**
* 首先对传入的数组按照 parentId 降序排列,这样可以确保后面构建树形结构时父节点已经在子节点之前处理过。
* 然后使用 forEach 循环遍历排好序的数组,针对每个元素进行处理。
* 在循环内部,判断当前元素是否有parentId,如果有,则在整个数组中查找与当前元素的 parentId 相匹配的元素。
* 如果找到匹配的父级元素,就将当前元素添加到父级元素的 children 属性中
* 如果父级元素原本不存在 children 属性,则创建一个新的数组保存当前元素。
* 最后,函数返回经过处理的数组,筛选出顶级父节点(parentId 为 0 或者不存在)并按照 id 升序排列。
* 即可将扁平的数组转换为树形结构
*/
function getTree(arr) {
const newArr = arr.sort((a, b) => b.parentId - a.parentId);
for (let i = 0; i < newArr.length; i++) {
let item = newArr[i];
if (item.parentId) {
newArr.forEach((arrItem) => {
if (arrItem.id === item.parentId) {
if (arrItem.children) {
arrItem.children.push(item);
} else {
arrItem.children = [item];
}
}
});
}
}
return newArr.filter((item) => !item.parentId).sort((a, b) => a.id - b.id);
}
const tree = getTree(arr);
console.log("tree: ", JSON.stringify(tree, null, 2));
树结构转对象结构
const obj = {
id: 0,
value: "test_0",
children: [
{
id: 1,
value: "test_1",
},
{
id: 2,
value: "test_2",
},
{
id: 3,
value: "test_3",
children: [
{
id: 31,
value: "test_31",
},
{
id: 32,
value: "test_32",
},
{
id: 33,
value: "test_33",
children: [
{
id: 331,
value: "test_331",
},
{
id: 332,
value: "test_332",
},
],
},
],
},
],
};
const arr = [];
function changeObj(obj) {
arr.push({ id: obj.id, value: obj.value }); // 将当前节点的 id 和 value 添加到数组 arr 中
if (obj.children) {
for (let i = 0; i < obj.children.length; i++) {
changeObj(obj.children[i]); // 调用递归处理当前节点,在处理子节点
}
}
}
changeObj(obj); // 将嵌套的对象结构转换为扁平的数组结构
console.log(arr);
// [
// { id: 0, value: 'test_0' },
// { id: 1, value: 'test_1' },
// { id: 2, value: 'test_2' },
// { id: 3, value: 'test_3' },
// { id: 31, value: 'test_31' },
// { id: 32, value: 'test_32' },
// { id: 33, value: 'test_33' },
// { id: 331, value: 'test_331' },
// { id: 332, value: 'test_332' }
// ]
深拷贝和浅拷贝,如何实现深拷贝
深拷贝和浅拷贝是指在复制对象时,是否对其内部的子对象进行递归复制。浅拷贝只复制对象或数组的第一层,而深拷贝则复制整个嵌套的对象或数组。
实现深拷贝
- 使用JSON序列化和反序列化:JSON.parse(JSON.stringify(obj))
- 递归复制:遍历对象或数组的每个属性或元素,对于每个属性或元素,如果是对象或数组,则递归调用深拷贝函数,直到复制完成。
- 使用第三方库:lodash、underscore等提供了深拷贝函数的
// 手写深拷贝(数组或对象)
function deepClone(source) {
if (source instanceof Object === false) return source;
let target = Array.isArray(source) ? [] : {};
for (let i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = deepClone(source[i]);
} else {
target[i] = source[i];
}
}
}
return target;
}
手写延时器
function delay(time) {
return new Promise((res) => {
setTimeout(() => {
res()
}, time)
})
}
CSS实现水平垂直居中的方式
Flex布局、绝对定位、表格布局、Grid布局
/* flex布局 */
.parent {
display: flex;
justify-content: center;
align-items: center;
}
/* 绝对定位,需要考虑浏览器兼容问题 */
.parent { position: relative; }
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
/* 表格布局 */
.container {
display: table-cell;
text-align: center; /* 水平居中 */
vertical-align: middle; /* 垂直居中 */
}
/* grid布局 */
.container {
display: grid;
place-items: center; /* 水平垂直居中 */
}
Vue组件怎么传值?
-
正向:父传子,父组件把要传递的数据绑定在属性上发送,子组件通过 props 接收
-
逆向:子传父,子组件通过 this.$emit(自定义事件名,要发送的数据),父组件设置一个监听事件来接收,然后拿到数据
-
兄弟:eventbus 中央事件总线
-
通过 Vuex
Vue路由的实现
前端路由就是更新视图但不请求页面,利用锚点完成切换,页面不会刷新
官网推荐用 vue-router.js 来引入路由模块,先定义路由组件,使用 component 进行路由映射组件,用 name 导航到对应路由;然后创建 router 实例,传入 routes 配置,创建和挂载根实例;最后用 router-link 设置路由跳转即可实现路由跳转
Vue路由跳转方式
编程式和声明式
-
用js跳转的方式叫编程式跳转,比如this.$router.push()
-
用router-link跳转的方式叫声明式,router-view是路由出口、路由模板显示的位置
npm安装机制
在输入 npm install 后,首先查询 node_modules 目录之中是否存在指定模块,若存在,不再重新安装;若不存在,则npm 向 registry 查询模块压缩包的网址里下载压缩包,然后存放在根目录下的.npm 中,最后解压压缩包到当前项目的 node_modules 目录即可。
token加密过程
首先需要一个 secret(随机数),后端利用 secret 和加密算法生成一个字符串(token),返回给前端,前端每次 request 在 header 中带上 token,后端用同样的算法解密即可。
实现防抖和节流
防抖和节流是用于控制函数执行频率的方法,
防抖是在一定时间内只执行最后一次操作,节流是在一定时间内只执行一次操作。可以通过setTimeout和时间戳等方式实现。
防抖——触发高频事件后 n 秒后函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间;
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function() {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
// 然后又创建一个新的定时器, 这样就能保证输入字符后的interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
timeout = setTimeout(()=>{
fn.apply(this,arguments);
},500);
}
}
function sayHi() {
document.getElementById('inp').addEventListener('input', debounce(sayHi));
}
节流——高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率。
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function() {
if(!canRun) return // 判断是否为true,不为true则返回
canRun = false // 立即设置为false
// 将外部传入的函数的执行放在 setTimeout 中
setTimeout(()=>{
fn.apply(this,arguments);
// 最后在 setTimeout 执行完毕后再把标记设置为 true(关键) 表示可以执行下一次循环了。当定时器没有执行的时候标记永远是 false,在开头被 return 掉
canRun = true
},500);
}
}
window.addEventListener('resize',throttle(sayHi));
JavaScript 中什么情况下会返回 undefined 值?
1、访问声明了但没有初始化的变量
var aaa;
console.log(aaa); // undefined
2、访问不存在的属性
var aaa = {};
console.log(aaa.c); // undefined
3、访问函数的参数没有被显式传递值
(function (b){
console.log(b); // undefined
})();
4、访问被设置为 undefined 值的变量
var aaa = undefined; console.log(aaa); // undefined
5、没有定义 return 的函数隐式返回
function aaa(){} console.log(aaa()); // undefined
6、函数 return 没有显式返回任何内容
function aaa(){
return;
}
console.log(aaa()); // undefined
多维数组降维/数组扁平化
1、数组字符串化
let arr = [[222, 333, 444], [55, 66, 77] ]
arr += '';
arr = arr.split(',');
console.log(arr); // ["222", "333", "444", "55", "66", "77"]
2、递归
function reduceDimension(arr){
let ret = [];
let toArr = function(arr){
arr.forEach(function(item){
item instanceof Array ? toArr(item) : ret.push(item);
});
}
toArr(arr);
return ret;
}
3、flat(infinity)
var arr1 = [1, 2, [3, 4]];
arr1.flat(); // [1, 2, 3, 4]
var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat(); // [1, 2, 3, 4, [5, 6]]
var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2); // [1, 2, 3, 4, 5, 6]
//使用 Infinity 作为深度,展开任意深度的嵌套数组
arr3.flat(Infinity); // [1, 2, 3, 4, 5, 6]
列举三种强制类型转换和两种隐式类型转换?
强制
转化成字符串 toString() String()
转换成数字 Number()、 parseInt()、 parseFloat()
转换成布尔类型 Boolean()
隐式
拼接字符串,eg:var str = "" + 18
- * / % ==
将数组转为字符串
已知数组 var stringArray = [“This”,“is”, “Baidu”,** **“Campus”],Alert 出”This is Baidu Campus”
var stringArray = ["This", "is", "Baidu", "Campus"]
alert(stringArray.join(""))
将字符串转换成驼峰表示
已知有字符串 foo=”get-element-by-id”,写一个 function,将其转化成驼峰表示法”getElementById”
/** arr[i]:获取数组 arr 中索引为 i 的元素(字符串)。
* .charAt(0):获取该字符串的第一个字符(索引为 0 的字符)并转换为大写。
* .toUpperCase():将第一个字符转换为大写。
* +:连接符号,用于将大写的首字母与剩余部分进行连接。
* arr[i].substr(...):从字符串中第二个字符开始(索引为 1),截取剩余部分(长度为 arr[i].length-1)。
* 最后,将大写的首字母与剩余部分拼接起来,完成首字母大写的操作。
*/
function combo(msg) {
let arr = msg.split("-");
for(let i=1; i<arr.length; i++) {
arr[i] = arr[i].charAt(0).toUpperCase() + arr[i].substr(1,arr[i].length-1)
};
msg = arr.join("");
return msg;
}
提取URL中的参数并放到json中
有这样一个 URL:item.taobao.com/item.htm?a=… JS 程序提取 URL 中的各个 GET 参数(参数名和参数个数不确定),将其按 key-value 形式返回到一个 json 结构中,如{a: "1", b: "2", c: "", d: "xxx", e: undefined}
function serilizeUrl(url) {
var obj = {};
if (/\?/.test(url)) { // 是否包含?,表示是否有查询参数
var ary = url.substring(url.indexOf("?") + 1).split("&"); // 找到?后面的字符串并按&分割成数组
ary.forEach(i=>{
var item = i.split("=");
obj[item[0]] = item[1];
});
return obj;
}
return null;
}
清除字符串前后空格
if (!String.prototype.trim) { // 考虑兼容性
String.prototype.trim = function() {
// 匹配字符串起始位置(^)连续的空格字符(\s+), 然后用空字符串来替换
// 匹配字符串结尾位置($)连续的空格字符(\s+), 然后用空字符串来替换
return this.replace(/^\s+/, "").replace(/$\s+/, "");
}
}
截取字符串 abcdefg 的 efg
// substring(startIndex,endIndex),第一个参数必传,第二个可选
'abcdefg'.substring(4)
手写一个Promise
var Promise = new Promise((resolve, reject) => {
if (操作成功) {
resolve(value)
} else {
reject(error)
}
})
Promise.then(function (value) {
// success
}, function (value) {
// failure
})
代码执行顺序
执行顺序:1 2 4 3,因为:Promise 构造函数是同步执行的,Promise.then 中的函数是异步执行的
const Promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
Promise.then(() => {
console.log(3)
})
console.log(4)
使用结构赋值,实现两个变量的值的交换
let a = 1;
let b = 2;
[a,b] = [b,a];
流程
前端拉包流程
- 先用
git clone <仓库地址>从git上克隆仓库,然后git pull origin <仓库名>拉取项目代码 - 然后安装
node和 跟npm(或yarn)来管理依赖 - 再
npm install安装node_modules来存放包管理工具 - 最后运行package.json文件里的
npm run dev跑起项目即可
前端工程化流程
前端工程化流程包括项目初始化、开发、测试、构建和部署。
- 首先,通过Vue CLI等脚手架工具快速创建项目,使用 idea 工具进行开发,用 ESLint 和 Prettier 保持代码整洁;
- 然后,测试阶段使用 Jest 或 Cypress 进行验证,构建和优化使用 Webpack 或 Vite 来打包代码,提升性能;
- 最后,在阿里云服务器上购买并备案好域名,并通过宝塔进行自动化部署,将后端通过maven 工具打成jar包,前端通过npm run build 打包成dist,再将对应文件丢到宝塔上运行即可
这一系列步骤和工具帮助开发者高效地创建、测试和上线前端应用。
小程序分包流程
小程序的分包流程是为了优化小程序的启动速度和用户体验,将小程序的代码和资源分成多个包,从而在用户需要时按需加载。下面是一般的小程序分包流程:
- 确定分包策略:
- 主包:包含小程序的核心功能和启动时必需的资源。主包会在小程序启动时加载。
- 子包:包含额外的功能模块或资源,可以在用户需要时加载。
- 配置分包:
-
在小程序的
app.json配置文件中,定义分包配置。你需要设置主包和子包的路径。``` jsonCopy Code { "pages": [ "pages/index/index", "pages/logs/logs" ], "subpackages": [ { "root": "subpackageA", "pages": [ "pages/page1", "pages/page2" ] }, { "root": "subpackageB", "pages": [ "pages/page1" ] } ] } ``` -
root是子包的根目录,pages是子包中的页面列表。
- 组织代码和资源:
- 根据配置,将主包和子包的代码、资源文件(如图片、样式表等)组织到对应的文件夹中。每个子包的文件夹结构应符合小程序的目录结构要求。
- 测试和优化:
- 在开发过程中,通过微信开发者工具进行测试,确保子包的加载和功能正常。
- 优化子包的资源加载,减少子包之间的依赖,确保用户体验流畅。
- 发布和维护:
- 发布小程序时,确保分包配置符合小程序的发布要求。
- 在小程序上线后,定期维护和更新分包配置,以适应业务需求的变化。
通过合理配置和使用分包功能,可以有效地提升小程序的启动速度和用户体验。
优化
js内存泄漏
内存泄漏是指一块被分配的内存既不能使用又不能回收,直到浏览器进程结束;而释放内存的方法:赋值为 null
跨域问题,如何解决?
出现跨域问题的原因:
在前后端分离的模式下,前后端域名不一致的情况下就会发生跨域问题。 在请求的过程中我们要想回去数据一般都是 post/get 请求,所以会出现跨域问题。
跨域问题来源于 JavaScript 的同源策略,即只有 协议+主机名+端口号(如存在)相同,则允许相互访问。也就是说 JavaScript 只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。
同源策略 是由 NetScape 提出的一个著名的安全策略。所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源
解决跨域的办法:
前端:
- JSONP(JSON with Padding):利用
<script>标签的跨域特性,在客户端动态创建<script>标签来请求数据,服务器返回的数据被包裹在一个函数调用中,从而实现跨域请求。 - WebSocket:使用 WebSocket,它不受同源策略的限制,能在浏览器和服务器之间建立持久连接,允许双向通信,可用于解决跨域问题。
后端:
- CORS(跨源资源共享):在后台服务器端设置响应头,允许特定源(Origin)的请求访问资源。通过设置
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers等响应头来实现。 - 中间代理服务器:在同源服务器上设置一个中间代理服务器,将跨域请求转发到目标服务器,浏览器只与代理服务器通信,从而避免跨域问题。
- nginx反向代理:在服务器端配置反向代理服务器,将跨域请求转发到目标服务器,再将结果返回给客户端。常见的 Nginx、Apache 等服务器都支持反向代理配置。
这些方法可以根据具体情况选择适合的方式来解决跨域问题
移动端如何适配不同尺寸的屏幕
- 通过meta标签中的viewport属性来适配不同尺寸的屏幕
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- 视口单位(基于视口的宽高来设置尺寸)
.container {
width: 100vw; /* 视口宽度 */
height: 100vh; /* 视口高度 */
}
- 流式布局(用百分比单位来设置宽高)
.container {
width: 100%;
}
.item {
width: 50%;
float: left;
}
- 弹性布局
.container {
display: flex;
flex-wrap: wrap;
}
.item {
flex: 1;
margin: 10px;
}
- 灵活图片(使用 CSS 的
max-width: 100%确保图片大小自适应)
img {
max-width: 100%;
height: auto;
}
-
媒体查询(@media)
首先为小屏设备设计样式,然后使用媒体查询逐步添加更复杂的样式
/* 移动小屏设备优先样式 */
body {
font-size: 14px;
}
/* 大屏设备样式 */
@media (min-width: 768px) {
body { font-size: 16px; }
}
减少页面加载时间方式
-
优化图片,使用懒加载或预加载等方式缩短加载加载时间
-
图像格式的选择(GIF:提供的颜色较少,可用在一些对颜色要求不高的地方)
-
优化 CSS(压缩合并 css,如 margin-top, margin-left...)
-
网址后加斜杠(如 www.campr.com/目录,会判断这个目录是… cdn 托管
-
标明高度和宽度(如果浏览器没有找到这两个参数,它需要一边下载图片一边计算大小,如果图片很多,浏览器需要不断地调整页面。这不但影响速度,也影响浏览体验。当浏览器知道了高度和宽度参数后,即使图片暂时无法显示,页面上也会腾出图片的空位,然后继续加载后面的内容。从而加载时间快了,浏览体验也更好了)
-
减少 http 请求(合并文件,合并图片)
如何对网站的文件和资源进行优化?
- 文件合并(目的是减少 http 请求)
- 文件压缩(目的是直接减少文件下载的体积)
- 使用 cdn 托管资源
- 使用缓存
- gizp 压缩你的 js 和 css 文件
- meta 标签优化(title,description,keywords)、heading 标签的优化、alt 优化
- 反向链接,网站外链接优化
如何优化小程序首页白屏卡顿,提高加载时间的速度?
- 采用骨架屏、loading动画效果:在数据还没请求回来之前,先展示骨架屏页面或者loading动画效果,等请求到数据再渲染并显示页面
- 小程序分包:上传的小程序压缩包内容过大,一次性加载完会卡顿,可采用分包写法来优化小程序的启动速度,将小程序的代码和资源分成多个包,在用户需要时按需加载
- 图片懒加载:对首屏图片可采用懒加载的形式先显示占位图,只发送用户视口可见的数据请求,等数据请求到之后再把占位图切换成请求到的图片,图片内存过大的话也可进行切片处理来上传使用
- 数据加载:一些不需要等待页面渲染完成的数据,可通过异步加载或预加载来提前请求数据,减少等待时间
- 优化json:优化后端回传的json数据结构,不要一次性地将所有数据通过一个接口返回,而是分多个接口批量返回,能加快页面显示速度
- storage缓存:利用 storage API 对异步请求数据进行缓存,二次启动时先利用缓存数据渲染页面,再进行后台更新
- CDN托管:使用CDN对资源进行托管,保证服务器的响应速度
- 代码分割:利用webpack将js进行代码分割、分片处理,以减少文件加载体积时间
为什么会出现浮动?浮动元素会引起什么问题?如何清除浮动?
浮动定位将元素排除在普通流之外,即元素讲脱离文档流,不占据空间。浮动元素碰到包
含它的边框或者浮动元素的边框停留
为什么需要清除浮动
- 父元素的高度无法被撑开,影响与父元素同级的元素;
- 与浮动元素同级的非浮动元素(内联元素)会跟随其后;
- 若非第一个元素浮动,则该元素之前的元素也需要浮动,否则会影响页面显示的结构解决方法
清除浮动的方式
- 使用 CSS 中的 clear:both;(放一个空的 div,并设置上述 css),属性来清除元素的浮动,可解决 2、3 问题
- 对于问题 1,添加如下样式,给父元素添加 clearfix 样
.clearfix {
display: inline-block;
}
.clearfix:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
- 给父级元素设置双伪元素
.clearfix {
zoom: 1 /* 为了兼容IE*/
}
.clearfix:after {
content: "";
display: block;
height: 0;
clear: both;
visibility: hidden;
line-height: 0;
}
- 给父级元素设置 overflow:hidden;或 overflow:auto
如何解决 margin "塌陷"?
外边距塌陷共有两种情况:
第一种情况:两个同级元素,垂直排列,上面的盒子给 margin-bottom 下面的盒子给 margin-top,那么他们两个的间距会重叠,以大的那个计算。解决这种情况的方法为:两个外边距不同时出现
比如上盒子的下外边距为100,下盒子的上外边距为20,求两个盒子之间的距离;答案为:100,因为上面的外边距高度塌陷
第二种情况:两个父子元素,内部的盒子给 margin-top,其父级也会受到影响,同时产生上边距,父子元素会进行粘连,决绝这种情况的方法为:父级添加一个 css 属性,overflow:hidden,禁止超出外边距重叠就是 margin-collapse
解决方案:
- 为父盒子设置 border,为外层添加 border 后父子盒子就不是真正意义上的贴合(可以设置成透明:border:1px solid transparent);
- 为父盒子添加 overflow:hidden;
- 为父盒子设定 padding 值;
- 为父盒子添加 position:fixed;
- 为父盒子添加 display:table;
- 利用伪元素给父元素的前面添加一个空元素
.father::after {
content: '';
display: table;
}
常见bug
github拉取项目缓慢怎么办?
- 改成gitee,不用上梯子访问还快
- 使用淘宝镜像:先下载镜像(config set registry registry.npm.taobao.org) ,然后npm切换成淘宝镜像(npm config get registry)
post为什么会发送两次请求?
POST 请求发送两次的原因通常是由于浏览器的预请求(Preflight)机制导致的。预请求机制通常在以下情况下会被触发:
-
跨域请求:在发送跨域请求时,浏览器会首先发送一个预请求(OPTIONS 请求)进行验证,确定服务器是否允许该请求的跨域访问。如果预请求得到响应,并且访问被允许,浏览器会再次发送 POST 请求。
-
发送自定义头部信息:如果 POST 请求中包含了自定义的头部信息(例如
Authorization),同样需要进行预请求验证。
在这两种情况下,浏览器会先发送 OPTIONS 请求进行验证,如果服务器允许跨域或者请求的头部信息得到验证,才会再发送一次 POST 请求
解决这个问题的方法,可以在服务端将 OPTIONS 请求过滤掉,或者在客户端判断是否是预请求,避免出现额外的请求。
在服务端,处理跨域请求可以使用 CORS(跨域资源共享)或者在服务器中配置反向代理等方式,来解决预请求问题。在客户端,可以通过设置合适的请求头和请求方式来避免预请求,例如在发送 POST 请求时,可以将请求方式设置为application/x-www-form-urlencoded或者multipart/form-data,而不是application/json等格式。如果必须使用自定义的头部信息,可以在服务器端配置响应头`Access-Control-Allow-Headers允许这些头信息的跨域请求。
未完待续...