20230815
一、Vue是什么
https://dgrt.cn/a/2280970.html?action=onClick
1、概念
1、vue是一个用于构建用户界面的渐进式框架,其核心库只关注 视图层,是一个轻量级的JavaScript框架。支持引入不同的库或者插件以适应不同的需求,使用上非常灵活。
2、Vue采用MVVM模式(Model-View-ViewModel), 实现数据双向绑定,同步刷新
2、MVVM
即Model View ViewModel
View层和Model层是通过ViewModel层建立联系的。用户在View层的操作,通过数据绑定传达给View-Model层,进而更新Model层。Model层更新之后,ViewModel层也自动随之变化,对View层重新渲染更新
3、什么是轻量级
轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb;
4、什么是渐进式
渐进式是指 在不失去当前功能的前提下,逐步添加新的特性。vue主张较弱,支持引入不同的库或者插件适应不同的需求,使用成本低,更灵活
5、渐进增强和优雅降级
渐进增强 可以理解为 向上兼容,优先考虑低版本,针对较为低版本的浏览器来构建页面,保证页面的所有基本的功能点都能实现;然后根据更高版本的浏览器设计一些在用户体验上更为良好的交互界面、追加更多的功能。
优雅降级 可以理解为 向下兼容,优先对高版本的浏览器构建出功能、性能、体验都较为完美页面,然后针对低版本的浏览器进行兼容补充。
区别:渐进增强属于在基础的页面中增加更多的功能(加);优雅降级是属于从功能丰富的页面上慢慢地减少页面的功能、用户的体验等等。
https://blog.csdn.net/joyvonlee/article/details/87649932
6、声明式和命令式
命令式: 关注过程
声明式: 关注结果
二、组件化
将一个项目划分为不同的功能模块, 每个模块都对应一个组件,避免将所有代码都塞在一个文件中,便于后期管理和维护。组件化有利于团队的协作开发,组件的可复用性 也提高了开发效率
1、组件化的优势
- 组件化有利于团队的协作开发,
- 组件的可复用性也提高了开发效率。
- 组件化使得项目后期的管理、调试和维护都更加方便
1、**降低整个系统的耦合度**,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
2、**调试方便**,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
3、**提高可维护性**,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级
三、Vue和React对比
核心思想
- vue是渐进式框架,采用MVVM模式, 关注的核心是视图层,实现数据的双向绑定
- react 强调使用 单向数据流 来管理组件的状态和数据。React也是MVVM额鹅鹅鹅,晕了。React关注的也是视图层
语法
- Vue采用模板语法,通过在模板中编写HTML代码,然后通过 指令 实现 数据绑定和逻辑交互; React采用jsx语法,jsx允许直接在js中嵌入HTML结构,使用JavaScript表达式处理逻辑和数据
组件声明
- Vue 可以使用Vue.component注册全局组件, 也可以使用单文件组件进行局部注册。
- React 通常 以 函数 或 类 的形式声明组件。
响应式原理
- Vue使用 双向数据绑定的响应式原理,通过 Object.defineProperty() 或者 Proxy监听数据变化, 从而实现在数据变化时,自动更新相关的视图
- React使用单项数据流d 响应式原理,组件的状态(state)是可变的,状态更新时,React会重新渲染组件,(root的?)render方法被重新调用,返回新的React元素(virtualDOM)以实现视图的更新
相同点
都有组件化思想, 都有数据驱动视图的思想
都支持服务器端渲染
都使用Virtual DOM(虚拟dom)来进行组件的渲染和更新
数据驱动视图
都有支持native的方案:Vue的weex、React的React native
都有自己的构建工具:Vue的vue-cli、React的Create React App
区别
数据流向的不同。react从诞生开始就推崇单向数据流,而Vue是双向数据流
数据变化的实现原理不同。react使用的是不可变数据,而Vue使用的是可变的数据
组件化通信的不同。react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数
diff算法不同。react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM
四、三大框架的区别
https://baijiahao.baidu.com/s?id=1768462788996573882&wfr=spider&for=pc
https://www.jianshu.com/p/e3d7cb76844e
Angular
优势
- 模板功能强大丰富,自带了极其丰富的angular指令。
- 是一个比较完善的前端框架,包含服务,模板,数据双向绑定,模块化,路由,过滤器,依赖注入等所有功能;
- 自定义指令,自定义指令后可以在项目中多次使用。
- ng模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助。
- angularjs是互联网巨人谷歌开发,这也意味着他有一个坚实的基础和社区支持。
缺点
- angular 入门很容易 但深入后概念很多, 学习中较难理解.
- 文档例子非常少, 官方的文档基本只写了api, 一个例子都没有, 很多时候具体怎么用都是google来的, 或直接问misko,angular的作者.
- 对IE6/7 兼容不算特别好, 就是可以用jQuery自己手写代码解决一些.
- 指令的应用的最佳实践教程少, angular其实很灵活, 如果不看一些作者的使用原则,很容易写出 四不像的代码, 例如js中还是像jQuery的思想有很多dom操作.
- DI 依赖注入 如果代码压缩需要显示声明.
五、搭建Vue项目
命令行方式
- 脚手架安装:
- npm install vue-cli -g (vue-lcli2)
- npm install -g @vue/cli (vue-cli3)
- vue create project_name
- npm i 【vue-cli3不需要, vue-cli2不知道。。。。】
- npm run serve
图形化界面: 搭建项目/启动项目
- vue ui
1、Vue项目 目录结构
- 来源: vue项目结构目录介绍
六、生命周期
1、Vue2
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestory
- destroyed
- actived — 被 keep-alive 缓存的组件激活时调用。
- deactived — 被 keep-alive 缓存的组件失活时调用。
2、Vue3
- setup() – 开始创建组件之前,在Vue2中的 beforeCreate 和 created 之前执行,创建的是 data 和 method
- onBeforeMount() – 组件挂载到节点之前 的执行
- onMounted() – 组件挂载完成后执行
- onBeforeUpdate – 组件更新之前执行
- onUpdated – 组件更新完成后执行
- onBeforeUnmount() – 组件卸载之前执行的函数;
- onUnmounted() – 组件卸载完成后执行的函数;
- onActived – 被包含在 中的组件,会多出两个生命周期钩子函数,被激活时执行;
- onDeactived() – 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
- onErrorCaptured() – 当捕获一个来自后代组件传递的异常时调用
3、React 类组件生命周期
渲染阶段
constructor()
- It lets you declare state and bind your class methods to the class instance
- 由于现在 可以使用 the Public Class Field Syntax(>=chrome72) 直接在类中定义 实例属性, 所以constructor基本上不用了
- Do not run any side effects or subscriptions in the constructor. 不要再constructor中使用任何副作用函数或订阅,也不要使其他函数,例如 this.setState
- Typically, a constructor is only used for two purposes in React:It lets you declare state and bind your class methods to the class instance。
render()
ComponentDidMount
更新阶段
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate(prevProps, prevState)
- React will call it immediately before React updates the DOM.
- Any value returned by this lifecycle method will be passed as a parameter to componentDidUpdate.
- For example, you can use it in a UI like a chat thread that needs to preserve its scroll position during updates:
componentDidUpdate(prevP, prevS, snapshot)
- React will call it immediately after your component has been re-rendered with updated props or state.
销毁阶段
- componentWillUnmount()
- React will call it before your component is removed (unmounted) from the screen.
- This is a common place to cancel data fetching or remove subscriptions. 通常会在这个生命周期中 取消数据请求的操作,或者移除订阅(监听等
4、React函数组件
- useState()
- 创建 状态, 以及更新状态
- useEffect( Callback: () => { return Cleanup: () => {} }, Dependencies)
在组件 初始化完毕后 或是 更新完毕后 调用
Callback may also optionally return a Cleanup function. Callback可以返回一个清理函数!
(Dependencies)Reactive values include props, state, and all the variables and functions declared directly inside your component body
@1 When your component is added to the DOM, React will the Callback. 当组件初始化(挂载到节点)后, 会调用Callback
@2 After every re-render with changed Dependencies, React will first run the Cleanup function (if you provided it) with the old values, and then run the Callback with the new values. 当 依赖/状态 发生改变时[通过Object.is()比较],会先调用 上一次函数执行返回的清理函数, 清理函数内状态的值是更新前的值(上一个闭包的值)!!! 然后再调用 Callback(更新后的状态值)
- ① 没有设置第二个参数: 只要组件更新(重新渲染),就会调用
- ② 设置了第二个参数,但是是空数组(即没有指定任何依赖),则useEffect只会在初始化后调用
- ③ 设置了第二个参数,且指定了依赖, 则useEffect会在初始化后 以及 依赖值发生更改后调用!
@3 After your component is removed from the DOM, React will run your cleanup function.
- useLayoutEffect()
useLayoutEffect is a version of useEffect that fires before the browser repaints the screen.
- Call useLayoutEffect to perform the layout(position and size) measurements before the browser repaints the screen
类似 类组件中的 componentDidMount 和 componentDidUpdate
The code inside useLayoutEffect and all state updates scheduled from it block the browser from repainting the screen. When used excessively, this makes your app slow.
- Usage: Doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers闪烁),
- useInsertionEffect
- useInsertionEffect allows inserting elements into the DOM before any layout effects fire.
- When your component is added to the DOM, but before any layout effects fire, React will run your setup function. 当组件被添加到DOM中, 但是还未激发 任何布局效果 时, 调用
- Usage: Working on a CSS-in-JS library and need a place to inject the styles. (Injecting dynamic styles from CSS-in-JS libraries)
- React.memo()
- 类似 类组件中的shouldComponentUpdate!
- useMemo(callback, dependencies)
- useMemo is a React Hook that lets you cache the result of a calculation between re-renders.类似计算属性,默认使用 缓存的结果,只有当依赖改变时才会重新计算!
- 注意: useMemo是在 函数组件内部使用的hook!!!用于 缓存 某些依托于状态依赖 计算出的结果!
- 而 React.memo是用于 包裹整个组件!!!别搞混了!也好记呀是不是,useXxx是hook,在函数组件( require at the top level of the Component!)或其他hook内部使用。
- Function components also offer more granular optimization with useMemo.函数组件 通过使用useMemo 以实现 更细粒化的优化 [ granular 含颗粒的; 由颗粒构成的; 似颗粒状的 [ˈɡrænjələ®] ]
- 其他常用hook: useCallback、 useRef、 useImperativeHandle(与 forwardRef 结合使用)、useReducer …
七、Vue常用指令
1、v-text、v-html
v-text、v-html都可以用来渲染文本内容
v-text 只将数据直接以纯文本输出。不能输出真正的 html:即如果数据中存在html结构,则会将标签一并渲染到页面上。
v-text 与 {{}} 插值表达式的区别
语法
- v-text,v-html都是放在标签上
-{{}}是直接放在标签体内结果
- 插值表达式会有**插值闪烁**的问题,在网络延迟(或者表达式中数据量特别大) 的情况下,页面上会显示编译之前的文本。(解决v-cloak!)
- v-text 和 v-html不会有这种问题。
渲染方式
- v-text 和 v-html 会替换原有标签中的内容!
v-html 与 v-text 的区别
v-html 可以解析html结构。即如果数据中包含html片段,则会编译后再将结果渲染到页面上。
而v-text不可以
2、v-on
用于 事件绑定 v-on:xxx=“”
简写为 @xxx=“”
事件修饰符
参考:https://blog.csdn.net/qq_45902025/article/details/130812981
注意:使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。
- 例如:用v-on:click.prevent.self 会阻止所有的点击事件的默认行为,而 v-on:click.self.prevent 只会阻止对元素自身的点击
1、 .stop — 阻止事件冒泡
2、 .prevent — 阻止事件的默认行为 (例如:阻止a连接的跳转、阻止表单的提交等)
3、 .once — 只会触发一次回调
4、 .native —
5、 .capture — 以捕获模式触发当前的事件处理函数
6、 .self — 只有在event.target是当前元素自身时触发事件处理函数
事件对象 $event
绑定事件处理函数时
不传自定义参数, 则方法中默认第一个参数是 event事件对象
- @click=“fn”
- @click=“fn($event)”
传自定义参数,则将 $event 作为最后一个参数传入 ???
- @click=“fn(arg1, arg2, $event)”
- 好像 还是在第一个。。。。
- @change=“changePageTo($event, 1, pageInfo.pageNumber)”
vue键盘事件
原文:https://blog.csdn.net/m0_61307468/article/details/130900332
Vue中的 键盘修饰符
- 回车 => enter
- 例如: @keyup.enter=“”
- @keydown.enter=“”
- 删除 => delete (退格 、 删除 按键)
- 退出 => esc
- 空格 => space
- 换行 => tab (必须配合keydown去使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
- 也可以使用event.keyCode去指定具体的按键,例如:@keyup.13 绑定回车
- 特殊系统修饰键:ctrl、alt、shift、meta,规则如下:
- 若配合keydown使用:正常触发事件。
- 若配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
3、v-bind
- 动态的绑定一个或多个 attribute,也可以是组件的 prop。
使用: v-bind:参数 = “表达式”
同时传递多个属性时, 简写在一个对象内: v-bind = “obj”(省略参数名)
<template>
<Child v-bind="obj" />
<!-- 等价于 -->
<Child v-bind:id="obj.id" v-bind:name="obj.name" />
</template>
<script>
export default {
data(){
return {
obj: {
id: '1',
name: 'xxx'
}
}
}
}
</script>
属性绑定修饰符
参考: https://www.cnblogs.com/minozMin/p/8855208.html
1、 .sync
- 不推荐使用:用于组件props属性,进行双向绑定,即父组件绑定传递给子组件的值,无论在哪个组件中对其进行了修改,其他组件中的这个值也会随之更新。但一般不推荐子组件直接修改父组件数据,这样会导致耦合且组件内的数据不容易维护。
2、 .camel
- 将绑定的特性名字转回驼峰命名。只能用于普通HTML属性的绑定,通常会用于svg标签下的属性
- <svg width=‘400’ height=‘300’ :view-box.camel=‘viewBox’ …
- 转换后: <svg width=‘400’ height=‘300’ :viewBox=‘viewBox’…
3、 .once
- 用于组件props属性,但进行的是单次绑定。
- 将绑定数据传递给子组件后,子组件单独维护这份数据,和父组件的数据再无关系,父组件的数据发生变化不影响子组件中的数据。
类名 和 样式 的 动态绑定
1、 对象的方式 绑定 class 和 样式
class
:class=“{‘active’: isActive}”
- 当 isActive为true时, 就添加active样式类
style
:style=“{ fontSize: mySize + ‘px’ ‘’}”
- 需要 使用 驼峰命名法camelCase
1、 数组的方式 绑定 class 和 样式
4、v-model 双向数据绑定
只能用于表单元素:例如: input、select、textarea标签中
Vue3中的 v-model
- 在一个元素上 添加 v-model指令 意味着
- ① 动态绑定属性 :modelValue
- ② 绑定自定义事件 @update:modelValue=“change”
双向绑定的修饰符 / 表单修饰符
.lazy — 懒加载。 当表单元素失去光标或者按下回车后,。。。
.trim — 过滤掉输入内容的前后空格
.number — 会将输入的值转换为数值类型? 只能输入 数值类型?
5、v-for
key的作用
原文链接:https://blog.csdn.net/m0_66051368/article/details/123142867
在使用v-for进行列表渲染时,会给元素或者组件绑定一个key属性。key属性是DOM元素的唯一标识。
- 不加key, 默认情况下,当数组发生增删时,需要把发生改动的项目全部进行重绘 — 浪费资源;
- 当添加唯一标识之后, 一旦发生 增删操作之后,重绘之前会检测新绘制的元素 和 已有的元素 是否存在相同的 key , 相同则复用,减少了不必要的DOM操作 — 提高性能
- key的作用主要是为了高效的更新虛拟DOM,其原理是vue在patch(补丁)过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。
- 另外,若不设置key还可能在列表更新时引发一些隐蔽的bug。如某行数据不该更新的却更新了。
- vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。
为什么不建议使用 index作为 key的值
- 用 index 作为 key 时,在对数据进行,逆序添加,逆序删除等破坏顺序的操作时,会产生没必要的真实 DOM更新,从而导致效率低
- 用 index 作为 key 时,如果结构中包含输入类的 DOM,会产生错误的 DOM 更新
- 如果不存在对数据逆序添加,逆序删除等破坏顺序的操作时,仅用于渲染展示用时,使用 index 作为 key 也是可以的(但是还是不建议使用,养成良好开发习惯)。
- 在开发中最好每条数据使用唯一标识固定的数据作为 key
6、v-if, v-else, v-else-if
7、v-show
v-if 与 v-show 的异同
相同: v-if 和 v-show 都可以用来控制元素的显示与隐藏。
不同:
- v-if 是通过 是否渲染DOM元素 来控制元素的显隐的
- v-if为true, 则插入DOM节点, 为false 则移除DOM节点
- v-if是销毁或者创建元素之前切换,v-if在切换过程中,性能消耗较大
- v-if 适合条件不太可能改变的情况, 例如初始化时等待数据获取后再渲染
- 而v-show 是通过 当前元素的样式display是否为none 来控制显隐的
- v-show 为true, 则display的值为block, 为false 则display的值为none。
- v-show只在初始化时性能消耗较大
- v-show适用于 切换频繁的 情况
8、v-once
- v-once这个指令不需要任何表达式,它的作用就是定义它的元素或组件只会渲染一次,包括元素或者组件的所有字节点。首次渲染后,不再随着数据的改变而重新渲染。也就是说使用v-once,那么该块都将被视为静态内容。
9、v-cloak
- v-cloak指令的作用是vue实例渲染后关联结束
- 作用:使用css配合v-cloak可以解决网速慢时页面展示出没有被渲染的数据解决, 例如: {{}}插值表达式的 插值闪烁问题
- 给元素设置 v-cloak属性, 然后通过 属性选择器[v-cloak]的方式 给当前元素设置样式 display:none。
- 原理: 由于v-cloak指令会在vue实例渲染完成后结束关联,所以当页面渲染完成后,就删掉了v-cloak属性,同时也就不会有v-cloak对应的样式, 元素也就正常显示在页面上了
- 为了防止v-cloak对应的样式 被优先级/权重高的覆盖, 可以加 !important 以提高优先级
10、v-loading
八、Vue组件中的data为什么是函数
为了保证组件的独立性和可复用性。 (在创建多个组件实例的时候避免这些实例共享同一数据从而造成数据污染),如果data是个函数的话,每复用一次组件就会返回新的data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响
- 当data是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变
- 而如果data 如果单纯的写成对象形式,会使得所有组件实例共用了一份data。 修改其中一个组件实例,因为共有一份data,所以其他实例也会产生影响
九、计算属性 computed 的 getter 和 setter
- 计算属性包含 getter 和 setter 两个方法, 此时为 对象格式
- 简写: 计算属性可以只写 一个getter, 此时为 函数格式
computde: {
sum: {
get() {
// getter, 访问 属性的值,触发getter
return
}
set(newValue) {
// setter, 修改 属性的值,触发setter
}
},
// 简写
sum(){ // getter, 访问 属性的值,触发getter
return
}
}
十、methods 和 computed 的区别
computed 有 缓存, 只要依赖的数据值没有变化,就会直接使用之前的缓存结果。 只有依赖的数据值发生更改才会重新计算
methods没有缓存, 调用几次方法就会执行几次
十一、watch
watch 用于 监听data中某个属性的变化,从而根据变化进行一些交互逻辑
watch中的侦听 有两种写法
- 方法格式
- 缺点: 无法设置在初始化立即监听; 无法设置深度监听
- 对象格式
- 好处: 可以设置immediate和deep属性
watch中也可以直接监听对象的某一个属性
const vm = new Vue({
el: '#app',
data: function(){ // 这里因为不会被复用, 所以用 对象也可以
return {
username: '',
arr: [],
info: {
address: ''
}
}
},
watch: {
// 方法格式
username(newV, oldV) {
// ...
},
// 对象格式 进行 监听(推荐)
arr: {
handler(newV, oldV) {
// ...
},
deep: true, // 深度监听。 监听对象中的属性 或者 数组必须使用deep进行深度监听!
immediate: true // 初始化完毕后 watch侦听器立即被调用一次
},
// 监听对象的某一个属性
'info.address'(newV) {
console.log(newV)
}
}
})
十二、watch 和 computed 的区别
相同: 都 会在依赖项 发生变化时触发
不同:
computed
- 计算属性侧重 根据依赖 进行一系列运算后 得到一个 新值。这个新值根据依赖的变化而变化
- 计算属性 有缓存, 如果依赖项没有变,则默认使用之前的计算结果
- 所以计算属性 必须有返回值!
- computed内的函数不支持异步!
- 计算属性适合 一个属性(计算结果) 受到 已有的一个或者多个属性(依赖)影响的时候使用
watch
- watch侧重 通过监听某个属性或对象, 在属性或对象改变时进行一些业务逻辑处理
- watch只在 监听的属性变化时才会触发, 没有变化则不触发,没有缓存的概念
- watch一般没有返回值
- watch内的处理函数支持异步操作
- watch时候 一个数据 影响 多条数据的时候使用
十三、Vue实例中的this
this就是 当前Vue的实例化对象?
Vue构造函数内部解析时, 会把data、methods等属性 直接 设置给 实例对象
十四、Vue模板是怎么获取data中的数据的?
十五、Vue2响应式实现原理
- 参考原文: Vue2源码探析之数据响应式原理+面试过程中如何回答Vue2响应式原理
- 参考原文:VUE2数据响应式原理
- 参考原文: 对线面试官:Vue响应式原理
- 参考原文:重学前端之Vue.js响应式原理
Vue2中, 创建 Vue 实例时, vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
一句话总结: vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调
二次封装-defineReactive
- 由于set修改后的值无法及时被get捕捉到,我们就需要让get的return值返回一个变量,而且是一个需要保留的变量,下次访问还希望存在,但同时不希望声明成全局变量污染环境,于是用到了闭包,我们将Object.defineProperty()函数封装成defineReactive()函数方法,我们在函数内全局声明var tep 将他作为get的返回值,这样数据就实时更新了,到此我们就及时捕捉到数据的变化了,换而言之就是数据能够作为触发者了
var obj = {}
function defineReactive(data,key,val){
//如果传入的是两个参数值,值等于对象本身值
if (arguments.length == 2) {
val = data[key]
}
Object.defineProperty(data,key,{
//可枚举
enumerable:true,
//可以被配置,比如可以被delete
configurable:true,
//getter
get(){
console.log('访问obj的a属性')
return val;
}
set(newValue){
console.log('修改obj的a属性',newValue)
//当新值和旧值一样的时候直接return无需修改
if(val === newValue) {
return;
}
val = newValue
}
})
//原文链接:https://blog.csdn.net/weixin_54515240/article/details/129947441
依赖收集
收集依赖是为了知道哪些视图用到了data里的状态。
依赖收集的实现主要分两步,依靠Dep和Watch两个类来实现:
- 首先是当Vue实例化时,会遍历数据对象中的每个属性,并为每个属性创建一个依赖收集器(Dep)对象,用于存储依赖于该属性的Watcher对象。
- 然后当组件渲染时,会解析模板变量,触发属性的getter,同时会创建Watcher对象,用来将组件实例、属性和callback方法关联起来,并且存到依赖收集器对象里。
- 这样当状态变更时,Vue就知道是哪个组件需要更新了,只需调用与该组件对应的callback方法去完成更新即可。
Vue3原理 proxy与 Object.defineProperty()的区别
Object.defineProperty()
- 无法监听到对象新增属性或者删除属性的变更。
- 无法直接对数组进行监听(例如:对于直接更改数组长度、向指定位置添加元素以及修改指定位置元素,是无法被监听到的。)
- 解决:Vue2是通过重写了数组的push、pop、shift、unshift、sort、reverse、splice方法来实现对数组的响应式的,我们通过这些方法来改变数组是可以被Vue监听到的。
- $set API
Proxy 可以劫持整个对象,并返回一个新的对象。Proxy 不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。解决了Vue2中对象与数组响应式的问题
十六、Vue2如何监测数组和对象的变化?
※ Vue2中监听数组变化(新增/修改/删除)
- 重写了 数组部分方法, 包括: push、pop、shift、unshift、splice、sort、reverse
- 可以使用 Vue.set() / vm.$set() / this.$set() 添加/修改数组或对象
※ Vue2监听对象
- $set
- 注意Object.assign的使用, 需要同时传入 原对象 和 要合并的对象!!!
- 删除: $delete(obj, key)
十七、Vue2 如何进行依赖收集
- 依赖收集的实现主要分两步,依靠Dep和Watch两个类来实现:
- 首先是当Vue实例化时,会遍历数据对象中的每个属性,并为每个属性创建一个依赖收集器(Dep)对象,用于存储依赖于该属性的Watcher对象。
- 然后当组件渲染时,会解析模板变量,触发属性的getter,同时会创建Watcher对象,用来将组件实例、属性和callback方法关联起来,并且存到依赖收集器对象里。
- 这样当状态变更时,Vue就知道是哪个组件需要更新了,只需调用与该组件对应的callback方法去完成更新即可。
Vue中模板编译原理 (核心: ast- > 生成代码)
- 会将模板变成ast语法树
- 对ast语法树进行优化, 标记静态节点
- 代码生成, 拼接render函数字符串 + new Function + with
Vue生命周期钩子是如何实现的
- 声明周期钩子在内部会被vue维护成一个数组(vue内部有一个方法mergeOptions 和全局的生命周期合并最终转换成数组, 当执行到具体流程时会执行钩子(发布-订阅模式)
13. $refs
14. Vue生命周期
生命周期是一个组件从创建到销毁的过程
初始化阶段
beforeCreate
- 在实例初始化之后,数据观测 (dataobserver) 和 event/watcher 事件配置之前被调用。访问不到数据
created
- data和methods初始化完成, 但是还没有开始编译模板
- (常用作发送异步请求获取数据)
- 前提是,不涉及DOM操作!!!
beforeMount
- 在挂载开始之前被调用可以访问数据编译模板结束,虚拟dom已经存在
mounted
- DOM挂载完毕, 可以操作DOM, 发送请求等
更新阶段
- beforeUpdate
- 视图中的数据还是旧的, Vue实例中已经可以拿到新数据?
- updated
- 更新完毕
- watch是监控特定数据的变化,updated是监控组件里所有数据的变化
销毁阶段
beforeDetroy
- 实例销毁之前调用,在这一步,实例仍然完全可用,还能使用组件中的数据和方法,
- 可以进行一些清除工作。例如清除定时器…(避免造成内存泄漏
destroyed
- 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
15. 组件间的通信
父子传参
父传子
- 父组件通过 属性的方式, 向子组件传递数据
- 子组件通过 props 接收 [子组件不能直接更改父组件传递的数据!!!]
子传父- 子组件通过 this.$emit向父组件 发送事件以及传递参数
- this.$emit(‘事件名’, ‘参数’)
- 父组件通过 v-on接收事件名,并定义事件处理函数
- @[事件名]=“事件处理函数”
Vuex
$attr — 多层嵌套(父 - 子 - 孙组件) – 主要用于组件之间的隔代传值。
- 父组件传递的 属性, 如果没有被子组件的props接收,那么就会存入 this.$attr;
- 子组件 通过 v-bind=“$attr”, 可以将 $attr中的属性再传给 孙组件
- 孙组件通过 props就能接收 子组件传递的 $attr
// 父组件
<template>
<RootCom
:text="text"
:msg="msg"
:forGrandSon="grandson"
></RootCom>
</template>
// 子组件 Son
<template>
<Grandson
v-bind="$attrs"
></Grandson>
</template>
<script>
props: {
text: [String, Object],
msg: Object,
// 子组件没有接收 数据forGrandSon, 所以此时 $attr为 {forGrandSon: ...}
},
</script>
// 孙组件
<template>
//Grandson
</template>
<script>
props: {
// 父组件 将 $attr传入,所以这里使用 props接收 $attr中的数据
forGrandSon: Object
},
</script>
$props
Vue双向绑定的原理
- 参考: vue官网
Vue2 – Object.defineProperty()
利用 Object.defineProperty() 中的 getter和setter 对数据 进行 劫持
When you pass a plain JavaScript object to a Vue instance as its data option, Vue will walk through all of its properties and convert them to getter/setters using Object.defineProperty. This is an ES5-only and un-shimmable feature, which is why Vue doesn’t support IE8 and below.
- un-shimmable: (An un-shimmable feature is one that requires a core “engine” change due to some intrinsic property or operation that did not occur in a previous EMCAScript version. For example, ES6 generators are “un-shimmable”. (There are ES6 to ES5 transpilers; shims generally must work with ‘the source as it is’.))
- 个人理解是 不可向下兼容低版本
由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。已经创建的实例,Vue 不允许动态添加根级别的响应式 property
- Vue 不能检测数组和对象的变化
- 解决:
- 新增属性:
Vue.set(object, propertyName, value)
- 更改数组长度:
vm.items.splice(newLength)
- 通过设置vm.items.splice(newLength)修改数组长度,Vue 可以检测到数组的变动。
Vue3 – Proxy
- 在 Vue 3 中则使用了 Proxy 来创建响应式对象,仅将 getter / setter 用于 ref。
Vue2 — Object.defineProperty()
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
对象是由多个 键-值对 组成的 无序 的集合
定义对象的方式:
- 构造函数new Object
- 字面量
给对象 添加 成员 的方式
- obj.xxx / obj[‘xxx’] = …
- Object.defineProperty(obj, ‘xxx’, {})
Object.defineProperty(obj, prop, descriptor)
参数:
- obj ---- 表示目标对象, 即要给谁定义属性
- prop ---- 表示要 定义或修改 的属性的键名,接收String/Symbol类型
- descriptor ---- 被定义或修改的属性的属性描述符!可以控制/限制 当前属性的 读写行为
返回值:
- 返回 被传递给函数的对象obj
作用:
1、设置 成员的 规则(属性描述符)
- 如果 prop属性 不存在, 则为 对象 添加这个prop属性, 并设置规则(属性描述符),默认为 false/undefined
- 如果 prop属性 已存在,则修改 属性成员的规则
2、数据劫持(get/set)
属性描述符 分为: 数据描述符 + 存取/访问器描述符 【描述符只能是这两种类型之一,不能同时为两者】
configurable 数据描述符 、存取描述符
Boolean
值为 true时
- ① 才可以 将当前【属性】从对应的对象上【移除】
- ② 除writable特性外的其他特性才可以被修改!
- ③ 该属性的类型才能 在数据描述符和访问器描述符之间更改 !!!
- 即 在前面已经 定义了属性的 数据描述符(例如value/writable)之后, 在后面 可以再 定义 属性的 访问器描述符(例如get/set)默认为 false
- 该属性不可被删除,且
- 其描述符的其他属性也不能被更改(除了writable),且
- 该属性的类型不能在数据属性和访问器属性之间更改
- 即 在前面 已经 定义了属性的 数据描述符(例如value/writable)之后, 后面 【不可以】再 定义 属性的 访问器描述符(例如get/set)enumerable 数据描述符 、存取描述符
- Boolean
- 值为 true时, 当前【属性】 才可以 被【枚举】 (for-in、 Object.keys()列举出来的属性)
- 例如:循环一个对象的时候, 如果某个属性成员的 enumerable描述符为false,则该属性成员 不会 出现在这个循环中!
- 默认为 false
value 数据描述符
- 当前属性成员的 初始值!
- 默认为 undefined
writable 数据描述符
- Boolean
- 值为 true时, 当前【属性】 才可以 被【修改】
- 默认为 false
get(){} 存取描述符
- 给属性提供 getter 的方法, 【当 访问 对象中某个 属性成员 时, 首先会触发 getter!】
- 设置了getter,同时给 getter设置了返回值, 则最终 获取到的属性值 为 getter中设置的返回值
set(){} 存取描述符
- 给属性提供 setter的方法, 【当 修改 对象中某个 属性成员的值 时, 首先会触发 setter!】
注意!!!
以 Object.defineProperty(obj, ‘xxx’, {}) 为例
- 属性描述符的 默认值是 针对【添加属性成员】时的默认值。 即:当前对象obj 之前并没有 'xxx’这个属性, 而在 使用 defineProperty时 也没有 定义属性描述符 的 情况!
- 已存在的属性成员,在没有修改之前, 其 所有属性描述符都是 true/有值
例如:
const obj = { a: 100 }
// **obj中已存在a属性**, 所以 此时属性a中的属性描述符为:{ configurable: true, enumerable: true, writable: true, value: 100 }
-----------------------------------------------------------------------
Object.defineProperty(obj, 'b', {})
// 而通过 defineProperty 给 对象obj **添加一个 属性b, 同时并没有给 b属性 定义任何的属性描述符!**
所以此时 b的 属性描述符为{ configurable: false, enumerable: false, writable: false, value: undefined }
获取 属性描述符
- 获取 对象中 某个成员的 属性描述符
- Object.getOwnPropertyDescriptor(obj, ‘key’)
- 获取 对象 所有成员的 规则
- Object.getOwnPropertyDescriptors(obj)
const obj = {
a: 100
}
// 1、查看 某个 属性成员的 属性描述符规则
console.log(Object.getOwnPropertyDescriptor(obj, 'a'))
// {value: 100, writable: true, enumerable: true, configurable: true}
// 2、添加 属性成员, 并设置规则, 未设置则默认值为 false/undefined
Object.defineProperty(obj, 'b', {})
Object.defineProperty(obj, 'c', {
value: 2,
configurable: true,
enumerable: false,
writable: true
})
Object.defineProperty(obj, 'd', {
value: '啦啦啦啦啦不能被修改的啦',
configurable: true,
enumerable: true,
writable: false
})
Object.defineProperty(obj, 'e', {
value: '11111111111111',
configurable: false,
enumerable: true,
writable: true
})
Object.defineProperty(obj, 'f', {
value: 'ffffffffffff',
configurable: true,
enumerable: true,
writable: true
})
// 3、查看 对象 中 所有成员的 规则
console.log(Object.getOwnPropertyDescriptors(obj))
/*
{
a: {value: 100, writable: true, enumerable: true, configurable: true},
b: {value: undefined, writable: false, enumerable: false, configurable: false},
c: {value: 2, writable: true, enumerable: false, configurable: true},
d: {value: '啦啦啦啦啦不能被修改的啦', writable: false, enumerable: true, configurable: true}
...
}
*/
console.log('================================')
// 枚举属性
// 3、enumerable为 false的 成员 不会被 for-in 列举
for (const key in obj) {
console.log(key)
// a
// b、c 的enumerable都为 false!
}
console.log('================================')
// 修改属性值
// 4、writable为 false的属性 不能被修改
obj.d = '111'
console.log(obj.d)
console.log('================================')
// 数据劫持
// 5、get劫持: 访问 对象 某个属性时, 就会触发 getter!
Object.defineProperty(obj, 'a', {
get() {
return '获取的是getter中设置的返回值!'
}
})
console.log(obj.a) // 虽然 obj对象中属性a的值为 100
// 但是这里给a做了get劫持, 所以获取的是 get中设置的返回值
// 6、只要写了 get劫持函数, 默认情况下返回undefined!
Object.defineProperty(obj, 'c', {
get() {
return //写了 return,则函数返回值 默认为 undefined
}
})
console.log(obj.c) // undefined
Object.defineProperty(obj, 'd', {
get() {
// 虽然没有设置返回值, 但是get默认值也为 undefined
}
})
console.log(obj.d) // undefined
console.log('================================')
// 7、set劫持: 设置 对象某个属性的值 时,就会触发setter
// 8、configurable为 false
Object.defineProperty(obj, 'e', {
set(newV) {
console.log(newV)
}
})
obj.e = '23333333'
console.log(obj.e)
// 报错!!!
// Uncaught TypeError: Cannot redefine property: e at Function.defineProperty (<anonymous>)
// 原因: 添加属性e的时候,且定义了 value/writable, 所以描述符为数据描述符!
// 同时:设置了configurable为false
// 所以 e属性的描述符 不能再更改为 访问器描述符!!!!
// 描述符只能是这两种类型之一,不能同时为两者!!!
// 9、configurable为 true, 且 writable 也为true!!!
let newValue;
Object.defineProperty(obj, 'f', {
// get() {
// return newValue
// },
set(newV) {
// console.log(newV)
newValue = newV // 将获取的新值 赋值给 newValue,从而 通过get返回
}
})
obj.f = '23333333'
console.log(obj.f) // undefined!
// 设置了set, 即当前obj.f 为 访问器描述符!
// 访问器描述符中: obj.f 访问属性, 则触发get函数
// 没有设置 get,返回默认值 undefined!
Diff算法
Vue给对象新增属性 — vue2: Vue.$set()
nextTick()
- this. n e x t T i c k ( ) 解释为在下次 D O M 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 D O M . 也就是说,当 d o m 元素发生改变,重新渲染 d o m 树后,再执行 v u e . nextTick() 解释为在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM. 也就是说,当dom元素发生改变,重新渲染dom树后,再执行vue. nextTick()解释为在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM.也就是说,当dom元素发生改变,重新渲染dom树后,再执行vue.nextTick()里面的内容。
Vue3解决了什么 / Vue3新特性
1、Composition API
- 更好的逻辑复用 与 代码组织
- 便于维护
2、重新实现 响应式原理
- 使用ES6的 proxy 替代了 ES5的Object.defineProperty
- 解决了:
- 数组的更新检测
- 直接劫持整个对象,能够监听到对象属性的变化(新增等
相比Vue2, proxy的优势
3、重构虚拟DOM – 新算法
- 减少了创建 和 比较虚拟DOM的开销
4、源码用typescript重写, 有更好的类型推断(推导)
5、新的元素: Teleport
6、新的元素: Fragment – 模板支持多个根标签
7、<script setup>:用于在SFC中使用Composition API的语法糖
8、<style vars> :SFC中状态驱动的CSS变量
- RFC
- 征求意见稿 / 请求评论(Request For Comments,缩写为RFC),是由互联网工程任务组(IETF)发布的一系列备忘录。文件收集了有关互联网相关信息,以及UNIX和互联网社群的软件文件,以编号排定。RFC文件是由互联网协会(ISOC)赞助发行。
- SFC
- 单一文件组件(SFC,又名文件
Vue前端优化
1、Vue代码层面的优化
① v-if 和 v-show 区分使用场景
② computed 和 watch 区分使用场景
③ v-for进行列表遍历时, 为 item 添加 key
④ v-for列表循环,绑定事件时, 使用事件代理!
- 将事件处理程序代理到父节点,减少内存占用率
- 动态生成子节点时能自动绑定事件处理程序到父节点
<ul @click="meths">
<li v-for="(item,key) in 10" :key="key" :data-index="key">{{item}}</li>
</ul>
meths(e) {
if (e.target.nodeName.toLowerCase() === 'li') {
console.log(e.target.innerHTML)
console.log(e.target.dataset)
}
}
⑤ 避免同时使用 v-if 和 v-for
- 同时使用时的优先级:
- Vue2中: v-for 比 v-if 优先级高
- 编译过程中会把列表元素全部遍历生成虚拟 DOM,然后才会通过 v-if 判断符合条件的才渲染,就会造成性能的浪费,而我们希望不符合条件的虚拟 DOM都不要生成
- Vue3中: v-if 比 v-for 优先级高
- 解决:
- @ 对于 列表中不想渲染的部分,可以**使用计算属性进行过滤**
- @ 对于不显示整个列表,可以在 外部父容器(例如template) 加 v-if
- template 标签可以作为占位符使用。不会在 DOM 中渲染元素
- 前提: 只有 与 v-if、v-else-if 、 v-else、v-for、v-slot 任一指令 结合使用的时候, template才会是特殊处理
- 如果以上任一指令都不存在, 则template会被渲染为原生的template元素
⑥ 对于 仅仅用于数据展示的data, 不做成响应式:冻结Object.freeze() /
- Vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 Vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。
⑦ 事件的销毁
- 清除定时器
- 移动事件绑定(removeEventListener)
⑧ 图片的懒加载
⑨ 路由懒加载
- @ 组件引入使用 () => import(‘’) 的方式
- @ 在vue的路由配置文件时加上webpackChunkName,来命名打包js文件的输出的名称 。实现打包时的分包
const routes = [
{
path: '/login',
name: '登录',
component: () => import(/* webpackChunkName: "login" */ '../components/login'),
hidden: true,
meta: {
requireAuth: false,
keepAlive: false
}
},
]
⑩ 第三方插件的按需引入
⑪ 服务端渲染 SSR or 预渲染
- 服务端渲染是指 Vue 在客户端将标签渲染成的整个 html 片段的工作在服务端完成,服务端形成的 html 片段直接返回给客户端这个过程就叫做服务端渲染。【如果你的项目的 SEO 和 首屏渲染是评价项目的关键指标,那么你的项目就需要服务端渲染来帮助你实现最佳的初始加载性能和 SEO】
- 服务端渲染的优点:
- 更好的 SEO:因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA 中是抓取不到页面通过 Ajax 获取到的内容;而 SSR 是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
- 更快的内容到达时间(首屏加载更快):SPA 会等待所有 Vue 编译后的 js 文件都下载完成后,才开始进行页面的渲染,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
- 服务端渲染的缺点:
- 更多的开发条件限制:例如服务端渲染只支持 beforCreate 和 created 两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
- 更多的服务器负载:在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源,因此如果你预料在高流量环境下使用,请准备相应的服务器负载,并明智地采用缓存策略。
- 预渲染: 如果你的 Vue 项目只需改善少数营销页面(例如 /, /about, /contact 等)的 SEO,那么你可能需要预渲染,在构建时 (build time) 简单地生成针对特定路由的静态 HTML 文件。优点是设置预渲染更简单,并可以将你的前端作为一个完全静态的站点,具体你可以使用 prerender-spa-plugin 就可以轻松地添加预渲染 。
⑫ 使用组件缓存 keep-alive!
- 可以用 include/exclude属性 来 指定 缓存/不缓存组件
- 通过两个生命周期 activated/deactivated 来获取当前组件状态
<template>
<div id="app">
<keep-alive>
<router-view/>
</keep-alive>
</div>
</template>
- ⑬ 长列表滚动时, 仅展示一页数据!当滚动到底部时再次请求
- 使用 IntersectionObserver 判断元素(例如"加载更多"的字样) 是否进入视区
- 使用 IntersectionObserver 需要注意兼容性!!!!
- 判断浏览器支不支持 IntersectionObserver API,支持就用它实现懒加载,不支持就用监听 scroll 事件+节流的方式实现
/*作者:无梧桐
链接:https://www.zhihu.com/question/584127995/answer/2980419240
来源:知乎*/
const LazyLoad = {
// install方法
install(Vue, options) {
const defaultSrc = options.default
Vue.directive('lazy', {
bind(el, binding) {
LazyLoad.init(el, binding.value, defaultSrc)
},
inserted(el) {
if (IntersectionObserver) {
LazyLoad.observe(el)
} else {
LazyLoad.listenerScroll(el)
}
},
})
},
// 初始化
init(el, val, def) {
el.setAttribute('data-src', val)
el.setAttribute('src', def)
},
// 利用IntersectionObserver监听el
observe(el) {
var io = new IntersectionObserver((entries) => {
const realSrc = el.dataset.src
if (entries[0].isIntersecting) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
})
io.observe(el)
},
// 监听scroll事件
listenerScroll(el) {
const handler =
LazyLoad.throttle(LazyLoad.load, 300)
LazyLoad.load(el)
window.addEventListener('scroll', () => {
handler(el)
})
},
// 加载真实图片
load(el) {
const windowHeight =
document.documentElement.clientHeight
const elTop = el.getBoundingClientRect().top
const elBtm =
el.getBoundingClientRect().bottom
const realSrc = el.dataset.src
if (elTop - windowHeight < 0 && elBtm > 0) {
if (realSrc) {
el.src = realSrc
el.removeAttribute('data-src')
}
}
},
// 节流
throttle(fn, delay) {
let timer
let prevTime
return function (...args) {
const currTime = Date.now()
const context = this
if (!prevTime) prevTime = currTime
clearTimeout(timer)
if (currTime - prevTime > delay) {
prevTime = currTime
fn.apply(context, args)
clearTimeout(timer)
return
}
timer = setTimeout(function () {
prevTime = Date.now()
timer = null
fn.apply(context, args)
}, delay)
}
},
}
- ⑭ 移除项目中所有的console.log()控制台信息数据打印
- 过多了console.log也会影响性能
- 在babel.config.js中配置:如果是生产环境,则自动清理掉打印的日志,但保留error 与 warn
// npm install babel-plugin-transform-remove-console --save-dev
// 所有生产环境
const prodPlugin = []
if (process.env.NODE_ENV === 'production') {
// 如果是生产环境,则自动清理掉打印的日志,但保留error 与 warn
prodPlugin.push([
'transform-remove-console',
{
// 保留 console.error 与 console.warn
exclude: ['error', 'warn']
}
])
}
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [
...prodPlugin
]
};
插件babel-plugin-transform-remove-console
2、Webpack 层面的优化
- ① Webpack 对图片进行压缩
- 安装 image-webpack-loader :
npm install image-webpack-loader --save-dev
- 在 webpack.base.conf.js 中进行配置:
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use:[{
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
loader: 'image-webpack-loader',
options: {
bypassOnDebug: true,
}
}]
}
- 是否在构建生产包时生成sourcdeMap
3、基础的 Web 技术优化
① 开启 gzip 压缩(需要服务器端支持)
- gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE 等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右
② 浏览器(http)缓存
- 对静态资源进行缓存, 提高用户加载页面的速度。
- 根据是否需要重新向服务器发起请求来分类,将 HTTP 缓存规则分为两大类(强制缓存,对比缓存)
③ CDN 的使用
- 浏览器从服务器上下载 CSS、js 和图片等文件时都要和服务器连接,而大部分服务器的带宽有限,如果超过限制,网页就半天反应不过来。而 CDN 可以通过不同的域名来加载文件,从而使下载文件的并发连接数大大增加,且 CDN 具有更好的可用性,更低的网络延迟和丢包率 。
④ 使用 Chrome Performance 查找性能瓶颈
- Chrome 的 Performance 面板可以录制一段时间内的 js 执行细节及时间。
- 步骤:
- 打开 Chrome 开发者工具,切换到 Performance 面板
- 点击 Record 开始录制
- 刷新页面或展开某个节点
- 点击 Stop 停止录制
4、前端其他优化
① 优化css 、js 阻塞渲染
- 由于CSS和JS会影响DOM树和CSSOM的构建,所以浏览器在加载CSS和JS文件时会阻塞HTML的解析,为了避免阻塞,我们可以做以下优化:
css 放在head标签内,提前加载。
- 通常情况下 CSS 被认为是阻塞渲染的资源,在CSSOM 构建完成之前,页面不会被渲染(因为render tree没有生成),放在顶部让样式表能够尽早开始加载(引入和内联的css不会阻塞DOM tree的生成; 但是行内样式会!)。
- 但如果把引入样式表的 link 放在文档底部,页面虽然能立刻呈现出来,但是页面加载出来的时候会是没有样式的,是混乱的。当后来样式表加载进来后,页面会立即进行重绘,很可能会造成页面闪烁。
js文件放在body底部,防止阻塞解析
- GUI渲染线程 与 js引擎线程是互斥的。 GUI渲染线程 在遇到js的时候 会被挂起,等待js执行完毕再执行!
首页 不使用或者不改变dom和css的 js文件使用 defer 和 async 属性进行异步加载,不阻塞解析
② 减少重绘和回流
③ 减少DOM和CSSOM的构建时间
- DOM的层级尽量不要太深,否则会增加DOM树构建的时间,js访问深层的DOM也会造成更大的负担。
- 减少 CSS 嵌套层级和选择适当的选择器
④ CSS Sprites:图片采用精灵图、雪碧图,设置图片位置
5、页面初始化 白屏
- https://zhuanlan.zhihu.com/p/636688753?utm_id=0
- 骨架屏、预加载
移动端适配方案
- 媒体查询(meida queries)
- viewport + rem适配
- Flex布局
- vm/vh
1、媒体查询
- 早期采用的布局方式:通过查询设备的宽度来执行不同的 css 代码,最终达到界面的配置
- 语法:
- @media 逻辑符(一般忽略不写) 媒体类型(一般忽略不写) and(一般忽略不写) (媒体特性){}
逻辑符:
- and
- only
- not
媒体类型(如果写媒体类型后面必须加and):
- all 所有设备
- print 打印设备
- screen 带屏幕的设备
- speech 屏幕阅读器
- 可以使用,连接多个媒体类型,这样它们之间就是一个或的关系
- 可以在媒体类型前添加一个only,表示只有。(only的使用主要是为了兼容一些老版本浏览器)
媒体特性(必须用括号括起来写):
width 视口的宽度
height 视口的高度
min-width min-height 视口的最小宽高度(视口大于指定宽高度时生效)
max-width max-height 视口的最大宽度(视口小于指定宽高度时生效)
orientation 屏幕方向
- portrait竖屏
- landscape横屏
样式切换的分界点,我们称其为断点,也就是网页的样式会在这个点时发生变化。常用的有:
- 小于768 超小屏幕 max-width:768px
- 大于768 小屏幕 min-width:768px
- 大于992 中型屏幕 min-width:992px
- 大于1200 大屏幕 min-width:1200px
书写顺序:
- 同时可以写多个@media
- 如果用min-width就从小到大写
- 如果用max-width就从大到小写
- 一个@media中如果有多个媒体特性,中间用and连接
媒体查询外链式css引入:
<link rel="stylesheet" href="./xxx.css" media="逻辑符 媒体类型 and (媒体特性)">
- 缺点:
- 页面上所有的元素都得在不同的 @media 中定义一遍不同的尺寸,这个代价有点高
- 如果再多一种屏幕尺寸,就得多写一个 @media 查询块
@media screen add ( max-width:768px ){
/*当屏幕尺寸小于768px时,应用下面的css样式*/
}
@media only screen and (min-width: 375px) {
.logo {
width : 62.5px;
}
}
@media only screen and (min-width: 360px) {
.logo {
width : 60px;
}
}
@media only screen and (min-width: 320px) {
.logo {
width : 53.3333px;
}
}
2、 rem + @media
- em 和 rem 都是 CSS 中的长度单位。而且两个都是相对长度单位,不过两个有点区别:
- em 相对的是父级元素的字体大小,
- rem 相对的是根元素的字体大小 。
3、 flexible 适配方案: rem
- 在 rem 方案上进行改进,使用 js 动态来设置根元素fontSize
- 缺点: 字体不会响应式变化
window.onload = function() {
var documentWidth = document.documentElement.clientWidth;
if (documentWidth > 750) {
documentWidth = 750;
}
document.documentElement.style.fontSize = documentWidth / 7.5 + 'px';
}
window.onresize = function () {
var documentWidth = document.documentElement.clientWidth;
if (documentWidth > 750) {
documentWidth = 750;
}
document.documentElement.style.fontSize = documentWidth / 7.5 + 'px';
}
4、Viewport方案: vh/vm
- vw 是相对单位,1vw 表示屏幕宽度的 1%。
- 基于此,我们可以把所有需要适配屏幕大小等比缩放的元素都使用 vw 作为单位。不需要缩放的元素使用 px 做单位。
5、flex布局
6、百分比
7、 Bootstrap框架
- Bootstrap
- Bootstrap 提供了一套响应式、移动设备优先的流式栅格系统,随着屏幕或视口(viewport)尺寸的变化,系统会自动分为最多12列 。底层也使用了媒体查询。
16. VueRouter
router-link 标签元素
最终会被渲染为 a标签, 其属性to(指定跳转的链接) 会被解析为 href属性
router-view 标签元素
路由占位符(路由出口), 路由匹配到的组件 会被渲染到 router-view 标签所在位置
定义路由
定义 路由映射关系
路由重定向
动态路由匹配
路由传参的
keep-alive以及原理
路由的模式
hash
history
路由高亮 (exact-)active-class ???
声明式导航 与 编程式导航
声明式导航
- 即使用 router-link标签 进行跳转
编程式导航
- 在js中使用命令跳转, 更灵活,跳转的同时可以进行一个逻辑交互
前置导航守卫
路由改变后页面跳转前 触发, 可以进行一些交互逻辑
- 判断 是否登录(例如:token是否存在)
- 判断 将要跳转的页面 当前用户是否有权限访问
- 提示用户 将要跳转的页面等信息
router.beforeEach((to, from,next) => {
// ...
/*
to: 将要跳转的路由
from: 当前路由( 注意:实际此时还未跳转,这里只是做一个区分)
next: 执行next才能跳转页面!!!!next可以传参,指定跳转的页面!
*/
next()
})
组件内的路由守卫
beforeRouteLeave 离开当前路由时
路由元信息
嵌套路由
嵌套路由的组件 也要 放置一个 路由占位符!
通过 路由规则中的 children属性 添加嵌套路由
- 嵌套的路由 path不要加“/” ???
配置错误页面(404) 的路由规则
路由懒加载
配置路由时, 组件导入的方式
- const Com = () => import(‘…’)
vue-router 编程式导航 点击 已经点击过的路由时 报错:NavigationDuplicated: Avoided redundant navigation to current location: “/aiPpt”.
问题:this.$router.push({ path: router.path }) // 当A路由之前已经被点击过, (已经不在路由A页面),当再次点击A路由(参数未变)时, 会报错
原因:由于vue-router最新版本3.5.2,引入了promise,push、replace方法会返回一个Promise。当传递参数多次且重复,或是没有写成功或失败的回调。会抛出异常
- 解决方案一: 传入 相应的回调
this.$router.push(
{
path: router.path
},
() => {},
() => {}
)
- 解决方案二:重写方法
- 解决方案三:使用声明式导航
如何解决 VUE 首屏加载过慢/白屏 (原因)
17. Axios
对promise的理解 【备忘录】
async的作用
当函数被 async 修饰后, 这个函数的返回值一定是 promise!!!(无论函数本身是否有设置返回值)
// 延迟函数
const delay = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, interval)
})
}
// 定义一个 被 async修饰 的函数
const fn = async () => {
await delay()
return {x: 1}
} // 注意: 此时直接执行函数, 不会返回 函数本身的返回值({x:1})
// 因为这个fn函数被 async修饰, 只会返回一个promise实例!!!
console.log(fn()) // Promise {<pending>}
/*
函数执行, 遇到 被 await修饰的 delay()
所以 后面的代码 需要等待 delay执行完毕(即1s后 才能继续向下执行)
但 1s后, 由于函数已经执行了, 并返回了promise实例
所以不会再执行 return {x: 1}
除非 在 promise实例返回后, 再次调用
即调用 Promise实例内部的then方法
*/
// 要想 获取函数本身的返回值,必须使用 then!!!
fn().then(value => {
console.log(value) // {x: 1}
})
18. Vue动画
19. Vue过渡
20. Vuex
集中管理所有组件的 共有数据, 相当于一个仓库
流程:
- 下载: npm i vuex --save
- 创建Vuex仓库
- 在组件中获取Vuex的数据
- template模板 --> {{ $store.state.xxx }}
- JS --> this.$store.state.xxx
- 在组件中修改Vuex的数据
- this.$store.commit(‘mutations中的方法名’, ‘参数’)
Vuex – state
Vuex - mutations
Vuex中的state 必须通过 mutations 修改!!!
Vuex - actions
Vuex - getters
21. Vue 样式的 scoped的作用
给组件中的 style标签添加 属性scoped后:
- 编译后, DOM节点会自动被添加一个 唯一的随机属性 data-v-xxx
- 且编译style时, 样式中的 原选择器后面 也会加上 这个属性选择器 [data-v-xxx]
- 作用: 约束 Css的作用范围(为当前组件), 避免组件间样式冲突和互相影响
22. Link 与 @import 的区别
23. JavaScript中常用数组方法
实现队列使用什么方法? 实现栈使用什么方法
栈: Last In First Out 后进先出
24. JavaScript中常用字符串方法
substr 与 substring
JS精度丢失的原因? 如何解决?
25. 解决跨域的方案
26. Vue配置Proxy代理
27. Cookie / SessionStorage / LocalStorage
都可以被浏览器用来存储数据
不同:
- 存储时间
- sessionStorage仅在会话时存在,当会话窗口关闭时,数据也会被清空
- localStorage永久存储, 除非手动删除
- cookie可以设置存储的时间
- 存储大小
- cookie 存储大小为约4kb,而storage的存储大小约为5MB
- 创建与传递
- cookie 会携带在请求头中, 在浏览器和服务器之前来回传递
- webStorage仅在本地存储。(好处是。。。)
28. 同步 和异步 的区别
异步请求不会阻塞浏览器的进程, 遇到异步操作,则会加入任务队列,等到当前执行栈中的同步操作都结束之后,才会依次执行任务队列中的异步任务
同步请求需要等待上一个代码块结束才能执行下一段代码
29. HTML5是什么?
HTML的新标准, 减少了浏览器对插件的依赖, 提高了用户体验, 更有利于开发中开发
H5新特性
CSS3新特性
30. PX固定的大小?
em
元素的设置百分比
margin设置百分比 是基于 当前元素的父级元素的宽度!
31. margin负值
margin负值的 效果
块元素的 width有值:
margin-top/margin-left: Element moves in that direction
- margin-top为负值的时候 – 元素自身向上移动
- margin-left为负值的时候 – 元素自身向左移动
margin-bottom/margin-right: Succeeding(随后的, 成功) element moves in that direction (overlap交叠/重叠) – (即减少元素自身css读取的值)
margin-bottom为负值的时候 – 元素下方的其他元素向上移动!
margin-right为负值的时候 – 元素右侧的其他元素向上移动!
块元素的 width 为auto(没有设置值):
- margin-left负值 和margin-right 负值会增加元素的宽度
- 注意: 如果同时设置了浮动,则不会有这种效果!
总结: 除了块元素未设置宽度会增加宽度外,其他的几种情况都是反向移动或者减少css读取的值。
margin负值的应用场景
双飞翼布局(三列布局,两边定宽,中间不定)
- 原理: 用float在同一行, 中间元素给100%, 左侧元素 margin-left: -100%移动到最左边,右侧元素margin-left: -100px;
浮动元素 是 从左至右 排列的!
只是 因为 上一行位置被占满了, 所以元素才被挤到下一行!!!
固定宽度的 浮动元素,设置margin-left为负值,会使的元素向左移动,
- 如果左侧有元素, 则会交叠在元素上方
注意: html结构中, main元素必须在第一个!
- 这样是为了 使得后面的 left元素和right元素 在设置margin负值时,可以交叠在 main元素上方!从而实现三栏布局!
- 如果left元素在第一个, 而main的元素的宽度又是100%,
- 这样不仅 正常情况下,元素无法排列在一行(main需要100%的宽度,第一行有left所以不够,只能在第二行)
- 而且即使设置了 浮动和margin复制, 也是main交叠在 left元素上方!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
html,body{
height: 100%;
}
body{
margin: 0;
}
.main{
width: 100%;
height: 100%;
float: left;
}
.main .in{
margin: 0 110px;
background-color: pink;
height: 100%;
}
.left,.right{
height: 100%;
width: 100px;
float: left;
background-color: lightgreen;
}
.left{
margin-left: -100%;
}
.right{
margin-left: -100px;
}
</style>
<body>
<!--
@1 main 、 left 、right都设置了向左浮动
- 向左浮动元素会 脱离文档流,从前面的元素 依次向左移动
- 浮动元素不会重叠, 遇到包含块(如main元素) 或者 浮动块(如left、right元素) 就会停止移动
- 所以 浮动元素 会并排 排列
@2 但是由于 前面的元素main宽度为100%, 所以后面的元素只能排在下一行了
- 注意:本质上来说, 三个元素是并排排列的, 只是前面没有位置了,才会被挤到下一行!!!
- 也就是说,left元素的前面,即左侧是main元素!!!
@3 给left元素设置 margin-left负值,且元素left有固定的宽度,则元素left自身会向左移动!
- 所以 left设置了 margin-left: -100%;之后, left元素会向左移动100%
- 而左侧元素是 main!!!
- 所以left元素在向左移动100%后,会叠在main上面!!!(只是main本身没有设置背景色,所以看起来像是移动到main前面了一样!)
@4 right同理
-->
<div class="main">
<div class="in"></div>
</div>
<div class="left"></div>
<div class="right"></div>
</body>
</html>
圣杯布局 – 结合浮动+margin负值+相对定位
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.middle,
.left,
.right {
position: relative;
float: left;
min-height: 130px;
}
.container {
padding: 0 220px 0 200px;
overflow: hidden;
min-width: 300px;
}
.left {
/* 给一个浮动元素加上相反方向的负margin,则会使行间距为0且内容重叠!!! */
/* 给元素设置margin-left:-100%后,这里 100% 是基于【父元素的宽度】的设置的外边距 */
/*
原理:
1、元素设置了浮动会转换为块级元素,如果一行不能放下后面的浮动元素,则会在下一行显示
此时middle元素占据了父元素的所有空间,所有后面的元素都会在下一行显示
2、由于百分比,是基于父元素的宽度来设置的
此时, 元素的盒子模型 实际真正 占据的空间大小为:
自身宽度 + margin值(负数) => 自身宽度 - margin(正数)
==> 自身宽度 - (父元素宽度*100%) = 自身宽度 - 父元素宽度
==> 所以此时 元素的(左边框的)位置 正好贴在 父元素的左边框
*/
margin-left: -100%;
/* 相对定位: 元素 根据自身位置 向左再移动200px */
left: -200px;
width: 200px;
background: red;
}
.right {
/*
元素实际占据空间大小为
220px + (-220px) = 0
所以可以在一行显示,此时right元素的右边界紧贴父容器的右边界
*/
margin-left: -220px;
/* 相对定位: 元素 根据自身位置 向右再移动220px */
right: -220px;
width: 220px;
background: green;
}
.middle {
width: 100%;
background: blue;
word-break: break-all;
}
</style>
</head>
<body>
<div class="container">
<div class="middle"></div>
<div class="left"></div>
<div class="right"></div>
</div>
</body>
</html>
三栏等高布局 (文字不定,但是等高,且三栏的高度随最高的一栏展示:高度会根据文本内容最多(撑开最高)的一栏来定)
父级给overflow:hidden; 里面的元素用padding-bottom: -9999px; margin-bottom:-9999px;
原理: 使用足够高的padding-bottom 来显示高度的落差部分, 设置反向的margin-bottom 来抵消padding带来的影响,给父元素设置overflow:hidden隐藏溢出
- 设置 非常大的 paddingBottom值, 那么所有元素在**父容器内 占据的空间大小是一样的**!
- 再通过设置 overflow为hidden,则超出父容器部分就被裁切掉了!
- 设置 相同的margin负值是为了抵消 padding带来的影响, 相同marginBottom负值+ 相同paddingBottom正值 + height= height
- 即,最终 元素占据的空间大小 还是元素自身的高度!
总结: 给padding和overflow:hidden是为了让高度一致,margin-bottom:1000px;去消除padding带来的影响
- 使用 padding和margin的缺陷:
- 如果需要设置下边框,默认是看不到的!
- 解决: 在外部另外设置一个 div, 并设置边框样式, 然后通过定位的方式, 定位到元素下方
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
body{
margin: 0;
overflow: hidden;
}
ul{
margin: 0;
padding: 0;
list-style: none;
}
.list{
overflow: hidden; /* 重点: 父元素设置overflow隐藏子元素超出部分*/
width: 100%;
height: 100%;
}
.main{
margin: 0 110px;
background-color: lightgreen;
}
.left{
width: 100px;
float: left;
background-color: pink;
}
.right{
width: 100px;
float: right;
background-color: pink;
}
/* 重点*/
.main,.left,.right{
margin-bottom: -9999px;
padding-bottom: 9999px;
}
</style>
<body>
<ul class="list">
<li class="left">左侧文字比较少</li>
<li class="right">
右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多右侧文字比较多
</li>
<li class="main">中间文字比较少</li>
</ul>
</body>
</html>
去除列表右侧间隙 – 最后一个列表项贴(父元素的)边
原理:外层list元素未设置宽度(width:auto),margin-right:-10px;这会导致外层list元素右边增加宽度10px,那么每行的最后一个元素margin-right:10px就刚好list原来的边界,即新增的宽度与一行最后一个元素margin-right:10px相互抵消.
必要条件:
- 一行内所有列表项的占据空间 <= list的占据空间(width+magin)
- 如果是ul和li标签,还需要去除默认样式!
- 否则,会导致,最后一个列表项 被挤到下一行, 也就无法实现最后一个列表项贴(父元素的)边的效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
* {
/*必要: 重置默认样式!*/
margin: 0;
padding: 0;
}
.container{
margin:0 auto;
width: 500px;
border: 1px #ccc solid;
margin-bottom: 20px;
}
.list{
overflow: hidden;
margin-right: -10px; /*父元素实际占据横向空间+10*/
}
.list li{
width:92px;
height: 92px;
background-color: #ccc;
margin-bottom: 20px;
float: left;
margin-right: 10px;
}
</style>
<body>
<div class="container">
<ul class="list">
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
<li>我是一个列表</li>
</ul>
</div>
</body>
</html>
去除列表右侧间隙(二) – 最后一个列表项贴(父元素的)边 — flex布局 + margin(正值)
原理:
- @1 使用flex布局,通过设置 justify-content: space-between; 使得两边元素贴边
- @2 解决最后一行元素个数 < 能够容纳的元素个数: 获取一行内最多容纳元素的个数(假设一行能容纳的元素个数为 m个)
- ① 情形一: 最后一行只有 (m-1) 个元素: li:last-child:nth-child(m*n+(m-1)) { margin-right: calc((100% - LiWidth) / (m-1) * 1) }【选择器先选中最后一个元素, 同时这个元素要符合nth-child(4n+3),即位于第 (m-1) 列这个条件!】
- ② 情形二: 最后一行只有 (m-2) 个元素: li:last-child:nth-child(m*n+(m-2)) { margin-right: calc((100% - LiWidth) / (m-1) * 2 ) }【先选中最后一个元素, 同时这个元素要符合nth-child(4n+2),即位于第 (m-2) 列这个条件!】
- …
- 最后一行一个元素 和 四个元素都不影响排列 (因为space-between)
案例:一行最多能容纳 四个元素。 解决最后一行实际个数为2或3时 的flex布局问题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.box {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
/* align-content: flex-start; */
/*
CSS使用flex换行之后行距变大问题:
当元素不只一行意味着flex容器在交叉轴上有多行,
此时align-content属性生效,align-content默认属性为stretch
导致flex容器将交叉轴上的多余空间按行数平均分给每行。
处理此问题解决方法一:,直接给父元素设置align-content: flex-start即可。
http://www.taodudu.cc/news/show-3653543.html?action=onClick
解决方法二: 不设置父容器的高度!高度由内容自动撑开!
*/
width: 450px;
/* height: 500px; */
background-color: #ddd;
}
.child {
width: 100px;
height: 100px;
background-color: lawngreen;
margin-bottom: 20px;
}
.child:last-child:nth-child(4n+3) {
margin-right: calc((100% - 100px)/3); /* (100% - 100px)/3 即一个元素实际占据空间(横向)! */
/*
CSS中calc(100% - 100px) 减号两侧必须要有空格分隔符!!!
不加空格不生效
*/
}
.child:last-child:nth-child(4n+2) {
margin-right: calc((100% - 100px)/3 * 2);
/*
CSS中calc(100% - 100px) 减号两侧必须要有空格分隔符!!!
不加空格不生效
*/
}
</style>
</head>
<body>
<div class="box">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</body>
</html>
margin-top的负值 与 margin-bottom 有区别吗 – 有!
如果元素 下方没有其他元素, margin-bottom看起来就和没有移动一样
32. 实现CSS的水平居中
33. 实现CSS的垂直居中
34. 实现CSS的水平垂直居中
Flex
属性
flex: [ flex-grow, flex-shrink?, flex-basis? ]
flex属性是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
flex-glow
flex-shrink
flex-basic
- 定义了在分配多余空间之前,项目占据的主轴空间(main size)。它的默认值为auto,即项目的本来大小。
给 flex-basis 设置一个固定值后, 元素将会占据一个固定空间
flex-basis固定值 + flex-shrink:0 后, 元素原本的width值将会失效!
align-content
问题: 父元素设置了高度, 当设置flex布局,元素换行时,两行直接有间隙
- 原因: 当元素不只一行意味着 flex容器在交叉轴上有多行,
此时align-content属性生效,align-content默认属性为stretch
导致flex容器将交叉轴上的多余空间按行数平均分给每行。解决:
① 父元素不设置高度! 高度由内容撑开!
② 设置 align-content: flex-start;
Flex计算 – 同时设置 flex-grow 和 flex-basis
- 同时设置 flex-grow 和 flex-basis 之后, 浏览器首先会根据 flex-basic 分配一个固定空间, 然后再 根据 flex-gl占比 分配 剩余的空间!
- 即最终分配的空间是: flex-basic + 剩余空间 * flex-glow占比
- 例如: (D:\Web_project\Eleme\ 项目笔记.pptx)
CSS中的浮动
- float CSS 属性指定一个元素应沿其容器的左侧或右侧放置,允许文本和内联元素环绕它, 值有:none / left / right
- 设置了浮动的元素,
- @1 元素会从文档流脱离,
- @2 元素脱离文档流后 会按照设置的方向移动(left/right) ,直到它的外边缘碰到包含框或另一个浮动框的边框为止。
- @3 float 意味着使用块布局,设置浮动后, 元素会生成一个块级框
- @4 浮动以后块级元素在同一行显示,行内元素可以设置宽高
- @5 浮动不会重叠!(遇到浮动块或者包含框就会停止移动)
- @6 浮动元素没有设置宽度和高度时,宽度为内容撑开
清除浮动属性 clear
BFC和IFC
高度塌陷
IFrame
H5的hidden:hidden与css的display:none的区别
html是内容展示, 带有语义
- hidden属性表示与浏览者不相关,或者说不重要, 浏览器在解析的时候,就会将不相关的部分直接隐藏
- 例如: 提交信息的input,就可以用hidden
css是界面样式
- 设置displa并不代表不重要,不相关, 只是目前还不能展示
zoom:1
35. forIn 与 forOf forEach
36. JS数据类型
执行上下文与作用域
执行上下文 包括 全局上下文、局部(函数)上下文、eval()、块级上下文?
作用域:
收集并维护 由所有声明的标识符(即变量)组成 的一系列查询,并根据特定的规则, 确定当前执行的代码 对这些标识符 的 访问权限
JavaScript中查找变量的机制 – LHS+RHS
LHS – left-hand-side
- 赋值操作的目标 (即给谁赋值?)
RHS – right-hand-side
- 赋值操作的源头(不一定非要是=的右侧;只要没有被赋予任何值,都走RHS)
- RHS实际 应该表示 “非左侧”
- 易混淆的RHS: fn() —》 RHS
- fn函数的调用, 需要先对fn进行RHS,即找到这个变量对应的值
- 但这里并没有说要把 fn的值 赋值给谁的操作,所以是 RHS引用
块级作用域
let、const 的作用域是块级作用域
块级作用域 由最近的一对 包含花括号{}界定。
- 即 if块, while块, function块,{}包含块 都是let/const声明变量的作用域
变量提升
暂时性死区 temporal dead zone
37. 原型与原型链
38. 继承
39. this指向
40. 变量提升
41. 事件循环event loop
事件循环》:
?问: 微任务未执行完毕时又插入了一个微任务?会怎么样 -》 是排到最后一个位置等待执行吗
我好像知道场景了:
- new Promise().then(() => {
new Promise().then()
})
42. 闭包: 什么是闭包? 闭包的优缺点? 闭包的使用场景?
43. 什么是渐进式?
44. 浏览器的缓存机制
45. 网络状态码
- 200 OK:表示请求成功,并且服务器返回了请求的数据。
- 301 Moved Permanently:表示被请求的资源已经被永久移动到新的URL。
- 302 Found:表示被请求的资源暂时被移动到新的URL。
- 304 Not Modified:表示客户端发送的请求资源在服务器上没有变化,可以直接使用本地缓存的版本。
- 400 Bad Request:表示客户端发送的请求有错误,服务器无法理解。
- 401 Unauthorized:表示请求需要身份验证,客户端没有提供有效的身份凭证。
- 403 Forbidden:表示客户端没有权限访问请求的资源。
- 404 Not Found:表示请求的资源不存在。
- 500 Internal Server Error:表示服务器发生了未知的内部错误。
- 503 Service Unavailable:表示服务器暂时无法处理请求,通常是因为服务器过载或维护。
- 这些状态码能够提供给客户端和服务器之间进行有效的通信,帮助识别和处理不同类型的请求和错误情况。
46. 前端的职责是什么?
- 实现UI界面的展示
- 业务需求、
- 需要与后端、需求、UI。客户沟通
性能优化方案
- webpack层面
- HTTP层面
- 页面渲染层面 【包含代码渲染】
- 骨架屏
- 延迟/异步加载, 懒加载
- 大数据渲染优化
- 大文件传输处理
- …
强调结果: 例如:之前打包/加载时间是N秒,经过优化后是M秒
插件组件封装 【提高开发效率】
- 公共方法库
- 插件/组件封装: 二次封装 等
- Vue自定义指令
强调结果:开发周期缩短等等
新技术方向的攻坚
- Hybrid
- 可视化
- uni-app/flutter
- typescript
- node
✪ JS代码编写
手写call 和 apply
手写new
手写深拷贝
浅拷贝: 引用类型 的 浅拷贝是直接将原对象的引用地址 赋值给新对象 (指向同一个引用地址,即堆内存, 修改其中一个会影响另外一个!)
手写函数柯里化
什么是函数柯里化?
实现数组扁平化
数组去重的方法
冒泡排序、选择排序、插入排序、快速排序、归并排序
参考: https://www.pianshen.com/article/20752389254
参考: