vue2和vue3区别
vue3有更好的ts支持,打包大小比vue2减少41%,初次渲染和整体渲染快
双向绑定原理不同(响应式原理)
-
Vue2.0实现响应式(双向数据绑定)的原理是通过 Object.defineProperty 来劫持各个属性的getter、setter,在数据变动时发布消息给订阅者,触发相应的监听回调
- 基于Object.defineProperty,不具备监听数组的能力,需要重新定义数组的原型来达到响应式('push','pop','shift'等方法)。
- Object.defineProperty 无法检测到对象属性的添加和删除 。
- 由于Vue会在初始化实例时对属性执行getter/setter转化,所有属性必须在data对象上存在才能让Vue将它转换为响应式(核心API Object.defineProperty)。
- 深度监听需要一次性递归,对性能影响比较大
function observer(target){ if(typeof target !== 'object' || target === null)return target // 如果是数组类型,重写数组原型的方法("push","pop","shift","unshift","splice") if(Array.isArray(target)) target.__proto__ == arrProto; // 如果是对象,遍历对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter for(let key in target){ defineReactive(target,key,target[key]) } }
-
vue3用proxy+Reflect对数据进行处理监听整个对象的变化
- 解决了数组无法通过下标修改,无法监听到对象属性的新增和删除的问题。也提升了响应式的效率
- 基于Proxy和Reflect,可以原生监听数组,可以监听对象属性的添加和删除。
- 不需要一次性遍历data的属性,可以显著提高性能
深入回答:vue3并不是完全抛弃了defineProperty,通过reactive定义的响应式数据使用proxy包装出来,而ref还是用的defineProperty去给一个空对象,定义了一个value属性来做的响应式
数据响应式实现方式不同
- vue2数据写在data中就可以实现响应式
- vue3用ref,reactive实现
ref,reactive
-
ref通过Object.defineProperty()的get和set方法实现数据代理
-
reactive是使用ES6的Proxy对象来实现数据代理
-
ref定义的数据可以是基本数据类型也可以是引用数据类型,、返回一个包含value属性的对象,通过.value去访问具体的值
-
reactive一定义的数据只能是对象或数组或者像map,set这样的集合类型,、返回一个响应式的Proxy对象、可以直接去访问对象的一些属性
一般定义对象和数组用ref还是reactive是根据赋值方式决定的:
-
直接赋值用ref(赋值用reactive的话,会让数据失去响应式proxy{})
-
修改数据用reactive,和ref没区别,ref的话会多个.value
- ref得到的变量必须.value赋值,不然等于把ref变成了普通的数据,失去响应式
- ref的值如果是对象,里面的对象是响应式的,因为引用类型会先包装成proxy再赋值,所以ref的值如果是对象,可以修改其中的属性而引发响应式
-
- 使用
ref()函数可以替换整个对象实例,但是在使用reactive()函数时就不行:
- 使用
// 无效 - x 的更改不会被 Vue 记录
let x = reactive({name: 'John'})
x = reactive({todo: true})
// 有效
const x = ref({name: 'John'})
x.value = {todo: true}
reactive会先判断数据是不是引用类型,是的话就new Proxy,在设置Proxy的get和set,get进行依赖收集,set修改值,触发依赖更新
根结点个数不同
- vue2不支持碎仅div
- vue3引入了碎片Fragments,允许有多个根结点,减少不必要的嵌套
api类型不同
vue2用选项类型api(optionsApi),在代码中分割了不同的属性:dat,methods,computed等,使用生命周期和方法可直接引用
tree-shaking按需引入
-
vue3用组合式api(composition Api)更简洁,方便逻辑更加的聚合使用、使用生命周期和方法需要先引入、可以更好tree-shaking- 组合式api的写法下源码改成了函数式编程、方便按需引入、因为tree-shaking功能必须配合按需引入,所以vue3更好的配合tree-shaking
能打包体积更小 - 因为改成组合式api,所有没有this
- 组合式api的写法下源码改成了函数式编程、方便按需引入、因为tree-shaking功能必须配合按需引入,所以vue3更好的配合tree-shaking
生命周期不同
vue2中:
- beforeCreate: 实例初始化
- created: 实例的数据观测、事件等已经初始化完成
- beforeMount: 相关的render函数首次被调用
- mounted: 挂载后调用,
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之
注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick
5、beforeUpdate: 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
6、updated:组件更新完成后调用,此时虚拟 DOM 已重新渲染并应用补丁
7、activated: 被 keep-alive 缓存的组件激活时调用。
8、deactivated:被 keep-alive 缓存的组件失活时调用。
9、beforeDestroy: 实例销毁之前调用。
10、destroyed: 实例销毁后调用
11、errorCaptured 在捕获一个来自后代组件的错误时被调用
- vue3中:
Vue 3在Composition API中引入了新的生命周期钩子,它们以on开头,并且是在setup()函数中使用的:
setup(): 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 methodonBeforeMount(): 组件挂载到DOM之前调用;onMounted():组件挂载完成后调用,此时可以访问到DOM元素;onBeforeUpdate(): 组件更新之前执行的函数;onUpdated(): 组件更新完成之后执行的函数,虚拟DOM重新渲染和打补丁后调用;onBeforeUnmount(): 组件卸载之前执行的函数;onUnmounted(): 组件卸载完成后执行的函数;onActivated(): 被包含在<keep-alive>中的组件,会多出两个生命周期钩子函数,被激活时执行;onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数
- beforeCreate --> setup(()=>{})
- created --> setup(()=>{})
- beforeMount --> onBeforeMount(()=>{})
- mounted --> onMounted(()=>{})
- beforeUpdate --> onBeforeUpdate(()=>{})
- updated --> onUpdated(()=>{})
- beforeDestroy --> onBeforeUnmount(()=>{})
- destroyed --> onUnmounted(()=>{})
- activated --> onActivated(()=>{})
- deactivated --> onDeactivated(()=>{})
- errorCaptured --> onErrorCaptured(()=>{})
总结: Vue2和Vue3钩子变化不大,beforeCreate 、created 两个钩子被setup()钩子来替代、卸载改成unmount
setUp
- 是一个语法糖,,组件中用到的数据,方法,生命周期都要写在setup中,
- 返回值:如果返回的是对象,对象中的属性和方法可以直接在template中使用、若返回的是一个函数,就可以自定义函数的内容
- 在beforeCreate之前执行一次,是领先所有钩子执行的
- setup的参数,props值为对象,组件外部传递过来,组件内部声明并接受
diff算法不同
v2使用的是双端算法,v3使用的是快速diff算法
静态节点标记PatchFlag
增加了静态节点标记。会标记静态节点,不对静态节点进行比对。从而增加效率
不同动态节点的值时不一样的,这样的目的就是为了区分静态节点以及不同类型的动态节点
深入回答标记策略:在文本节点中文本内容为变量会标记为1,属性为动态会标记为2,如果静态则不标记跳过比对
在vue2中,diff算法执行时,不管你是静态节点还是动态节点,都会进行比较。但是vue3的话就只会比较patchflag标记的节点,跳过了静态节点的比较,大大节省了时间
存放公共数据和生态系统不同
- vue2用vuex和webpack
- vue3用pina和vite
v-if和v-for优先级不同
- vue2中v-if低于v-for的优先级
- vue3中v-if高于v-for的优先级
watch,computed的不同
复用逻辑提取方式不同
- vue3不推荐使用mixin进行复用逻辑提取,而是推荐写成hook、因为mixin写法更贴合选项时api、和组合式api就没办法配合
- mixin 重复出现的数据和操作逻辑会提取出来到一个文件里.mixin
- hook使用方式,创建一个文件,哪里需要使用时引入该文件即可
v-model
-
v-model应用于组件时,监听的事件和传递的值改变
-
v-model主要是用在表单
<input>、<textarea>、<select>元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素 -
v-model 会忽略所有表单元素的 value、checked、selected attribute 的初始值而总是将 Vue 实例的数据作为数据来源
-
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用 value property 和 input 事件
- checkbox 和 radio 元素使用 checked property 和 change 事件
- select 元素使用 value property 和 change 事件
- 注意:v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件
-
相同点
用在表单上都是给input绑定一个 value 值和 input 事件(默认情况)
- 不同点(主要用在自定义组件上)
vue2
- v-model 只能使用一次
- 默认传递 vulue属性,接受 input 事件
- 默认传递的属性和事件可通过 model 选项进行修改
vue3
- v-model 只能使用一次,但可以在v-model后添加参数可使用多次(因为去除了.sync修饰符,但把相应功能合并到了 v-model 上,所以可以使用多次)
- 默认传递 modelValue 属性,接受 update:modelValue 事件,当在v-model后添加参数时如v-model:myPropName,则传递为 myPropName 属性,接受 update:myPropName事件
- 默认传递的属性和事件无法修改,必须是modelValue 和 监听update:modelValue事件
<input type="text" v-model="searchText">
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
<input type="radio" id="man" value="Man" v-model="sex" />
等同于
<input type="radio" id="man" value="Man" :checked="sex === 'Man'" @change="handleSexChange" />
vue3中父组件通过v-model:属性名=‘属性值’的形式给子组件传递数据
<Son v-model:num = 'num'></Son>
子组件通过defineProps定义接受的数据
const props = defineProps({
num:{
type:Number,
default:0
}
})
子组件要修改父组件传来的数据:通过$emit事件去出发父组件修改 修改时定义的一个事件名是写死的,必须是update:属性名
- 用在vue2上时
父组件:
<!-- Test.vue -->
<CustomInput v-model="searchText" />
等同于
<CustomInput
:value="searchText"
@input="newValue => searchText = newValue"
/>
子组件:
<!-- CustomInput.vue -->
<template>
<input type="text" :value="value" @input="handleInput($event)" />
</template>
<script>
export default {
props: {
value: String//必须定义value属性,否则input元素无法使用
},
methods: {
handleInput (event) {
this.$emit('input', event.target.value)//需要向外派发 input 事件去更新 value 值
}
}
}
</script>
如果想在 CustomInput.vue 中使用v-model,则需要定义同时具有 getter 和 setter 的计算属性
get 方法需返回 value prop,而 set 方法需触发相应的事件
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<input type="text" v-model="customValue" />
</template>
<script>
export default {
props: {
value: String
},
computed: {
customValue: {
get () {
return this.value
},
set (value) {
this.$emit('input', value)
}
}
}
}
</script>
如果在 CustomInput.vue 中我们不想使用默认 props 里的 value 名和 触发 input事件,那我们可以使用 model 选项,它可以通过 prop 自定义 value 字段名并通过 event 自定义触发的 事件名
<!-- Test.vue -->
<CustomInput v-model="searchText"></CustomInput>
<!-- CustomInput.vue -->
<template>
<div>
<input type="text" :value="customValue" @input="handleInput($event)" />
</div>
</template>
<script>
export default {
model: {
prop: 'customValue',
event: 'customInput'
},
props: {
customValue: String
},
methods: {
handleInput (event) {
this.$emit('customInput', event.target.value)
}
}
}
</script>
- v-model用在vue3组件 默认情况下 vue2 的 v-model 用在表单上 和 用在组件上 表现形式是一样的。都是 :value 和 @input,但在vue3上 v-model的表现就不一样了 用在表单上是:
<input v-model="searchText" />
等同于
<input
:value="searchText"
@input="searchText = $event.target.value"
/>
**而当使用在一个组件上时,v-model 会被展开为如下的形式:**
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
ts更好地配合
toRef 和 toRefs
toRef 和 toRefs 是 Vue3 中的响应式转换工具函数,它们的存在主要有以下两点原因:
- 在 Vue 中,直接使用响应式对象的属性可以实现属性的双向绑定和响应式更新。但是有时候需要将某个属性提取出来作为独立的 ref 对象,这样可以在不影响源对象的情况下,对属性进行单独的访问和修改。toRef 函数正是为了解耦属性的关联,将属性转换为一个独立的 ref 对象。
- Vue 的组件系统中,父组件向子组件传递属性时,需要将这些属性声明为响应式对象。但是,如果直接将整个响应式对象传递给子组件,子组件无法通过解构或者直接访问每个属性。这时,toRefs 函数就可以将整个响应式对象转换为一个普通对象,每个属性都是独立的ref对象,子组件可以轻松解构和访问这些属性
toRef 和 toRefs 就是用来创建响应式的引用的,主要用来取出响应式对象里的属性,或者解构响应式对象,解构出来的属性值依然是响应式属性,如果不用 toRef 或者 toRefs,直接解构会丢失响应式效果
<script setup>
import { reactive } from 'vue';
let info = reactive({
name: 'Echo',
age: 26,
gender: 'Male',
})
// 这样解构会丢失响应式效果
let { name, age, gender } = info;
let age = toRef(info, 'age');//单个属性
let { name, age, gender } = toRefs(info);//多个属性
</script>
-
toRef
- toRef 函数可以将一个响应式对象的属性转换为一个独立的 ref 对象。
- 返回的是一个指向源对象属性的 ref 引用,任何对该引用的修改都会同步到源对象属性上。
- 使用 toRef 时需要传入源对象和属性名作为参数
-
toRefs
- toRefs 函数可以将一个响应式对象转换为一个普通的对象,该对象的每个属性都是独立的 ref 对象。
- 返回的对象可以进行解构,每个属性都可以像普通的 ref 对象一样访问和修改,而且会保持响应式的关联。
- toRefs 的使用场景主要是在将响应式对象作为属性传递给子组件时,确保子组件可以正确地访问和更新这些属性。
-
相同点
- toRef 和 toRefs 都用于将响应式对象的属性转换为 ref 对象。
- 转换后的属性仍然保持响应式,对属性的修改会反映到源对象上。
- 不管是使用 toRef 还是 toRefs 将响应式对象转成普通对象,在 script 中修改和访问其值都需要通过 .value 进行。
-
不同点
- toRef 修改的是对象的某个属性,生成一个单独的 ref 对象。
- toRefs 修改的是整个对象,生成多个独立的 ref 对象集合。
- toRefs 适用于在组件传递属性或解构时使用,更加方便灵活,而 toRef 更适合提取单个属性进行操作
v-router
模式
- hash:使用window.location.hash属性以及onhashchange事件监听浏览器地址hash值的变化,仅hash符号前的内容被包含在请求中,改变hash不会重新加载页面,对后端来说不会有404,hash变化会增加一个访问历史记录,push(),replace()
- history:发生改变时只改变路径不刷新页面
- history对象保存所有访问的页面,history.length,pushstate新增,replacestate替换,history.go(-2/2)后退/前进、back后退,forWord前进
- 该模式下刷新页面无法从服务端匹配到静态资源,因路由分两种,前端路由和后端路由,若是该history路由只是改变了地址的显示,并未发请求,但刷新就会发送get请求,由于后端未配置相应路由就返回404
路由守卫
- 全局前置守卫:beforEach(to,form,next)=>{next()}跳转前操作、next是正常跳转,不写就不会跳转,可用在验证用户访问权限
- 全局后置守卫:afterEach()跳转后操作,可用在路由切换将页面滚动位置,返回到顶部window.scrollTo(0,0),页面跳转后判断当前页面宽度大小后是否隐藏侧边栏
- 路由独享守卫:beforeEnter(),在路由配置中定义,只有访问到此路由时才触发
- 组件内守卫
- beforRouterEnter(){}在渲染该组件对应路由被调用,不能获取组件实例this,因执行前组件实例还没被创建
- beforRoutundate(){}当前路由改变,但该组件被复用时可调用this,动态参数的路径/foo/:id
- beforRouteLeave(){}离开该组件时可调用可访问this,可处理不未保存时或正在会议中离开的情况
路由传参
push传参的区别
性能优化
cloud.tencent.com/developer/a…
数据类型和检测数据类型的方法
数据类型
| 比较 | 基本类型 | 引用类型 |
|---|---|---|
| 类型 | string、number、boolean、undefinedd、null | Object、array、function |
| 数据存放位置 | 栈中,数据大小确定,内存空间大小可分配 | 堆中,每个空间大小不同(可动态调整(添加/删除属性/方法),动态分配在内存中 |
| 变量存储内容 | 值本身,值不可变(比较时是值比较) | 地址保存在栈中,值保存在堆中 |
| 变量用来赋值时 | 把变量的值复制一份去赋值,互不影响、var a = 1;b=a;a=2; console.log(a,b)//2 console.log(a===b)//true | 把变量的内存地址复制一份去赋值,相互影响,因将引用地址赋给新变量了 |
检测数据类型方式
typeof
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息
- 000:对象
- 010:浮点数
- 100:字符串
- 110:布尔
- 1:整数
- undefined:用 −2^30 整数来表示
- null:所有机器码均为0、由于
null的所有机器码均为0,因此直接被当做了对象来看待 typeof null === 'object'为true、这是js初版遗留的bug
判断函数类型:typeof function(){} // function
因此在用typeof来判断变量类型时,最好检测基本类型:boolean、undefined、string、number、symbol,避免对null来判断 typeof检测null ,Array、Object时返回是Object ,返回字符串
要想判断数据具体是哪一种object时,可以利用instanceof,
instanceof
原理:用来判断两个对象是否属于实例关系,通过这种关系来判断对象是否属于某一类型
[] instanceof Array实际上是判断Array.prototype是否在[]的原型链上
a instanceof B 即a是B的实例,a的原型连是否在B的构造函数
实现原理: instanceof 主要的实现原理就是只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例

返回布尔值、判断引用类型,不能判断基本类型 、左边对象的隐式原型等于右边构造函数的显示原型
instanceof检测是不准确的、且所有对象类型 instanceof Object 都是 true:[] instanceof Array; // true、[] instanceof Object; // true
所以要想比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call 方法
Object.prototype.toString.call()
每一个引用类型都有
toString方法,默认情况下,toString()方法被每个Object对象继承。 但许多引用类型都重写了Object继承的的toStrong方法,需用call或apply来改变this指向、这种方法对所有基本类型都能进行判断
- Object.prototype.toString.call(true) [object Boolean]
- Object.prototype.toString.call(123) [object Number]
- Object.prototype.toString.call(Function(){}) [object Function]
对Object.prototype.toString() 进行解释的:
-
如果你传进来的值为
undefined的话,直接返回一个[object Undefined]。 -
如果你传进来的值为
null的话,直接返回一个[object Null]。 -
如果你既不是
undefined又不是null的话,JS将调用ToObject方法,将O作为ToObject(this)的执行结果 -
ToObject的执行机理简单来说就是,传进来一个boolean类型会创建一个boolean包装类对象,传进来Number会创建一个Number字面量,传进来一个String会创建一个字符串字面量,传进来一个对象就会创建这个对象。总而言之,任何传进去的任何值都会转换为对象- 定义一个
class作为内部属性[[class]]的值,用于承接传进来的值。 - 返回由
"["object和class和"]"组成的字符串
.call(obj)的作用是将.call之前的函数中的this指向 obj,但也可以说是把.call之前的函数方法借给 obj 去使用,call确保了类型判断是被原型在调用,从而能输出正确的值。 - 定义一个
construcor
construtor 其实也是用了原型链的知识、、constructor 属性返回对创建此对象的数组函数的引用。
var a = 123;
console.log( a.constructor == Number); // true
var c = [];
console.log( c.constructor == Array); // true
var f = null;
console.log( f.constructor == Null); // TypeError: Cannot read property 'constructor' of null
var g;
console.log( g.constructor == Undefined); // Uncaught TypeError: Cannot read property 'constructor' of undefined
- 无论是通过字面量或者构造函数创建的基本类型,都可以检测出。并且也可以检测出
Array、Object、Function引用类型,但是不能检测出Null和Undefined
Null 和 Undefined
- null:Object类型,代表空值,一个空对象指针,没有对象,此处不应该有值
- Undefined:表示缺少值,此处应该有值,但还未被定义,
- 两者相等但不全等,在if中都会被转为false
原型和原型链
原型链的几条规则:
- 所有引用类型都具有对象特性,即可以自由扩展属性
- 所有引用类型都具有一个
__proto__(隐式原型)属性,是一个普通对象 - 所有的函数都具有
prototype(显式原型)属性,也是一个普通对象 - 所有引用类型
__proto__值指向它构造函数的prototype - 当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的
__proto__中去找
prototype
- 为什么只有函数有prototype属性
JS通过new来生成对象,但是仅靠构造函数,每次生成的对象都不一样。
有时候需要在两个对象之间共享属性,由于JS在设计之初没有类的概念,所以JS使用函数的prototype来处理这部分需要被共享的属性,通过函数的prototype来模拟类:
当创建一个函数时,JS会自动为函数添加prototype属性,值是一个有constructor的对象。
let a = {}
let b = function () { }
console.log(a.prototype) // undefined
console.log(b.prototype) // { constructor: function(){...} }
function Person() {}
Person.prototype.name = 'Kevin';
var person1 = new Person();//只要是通过 `new` 关键字调用的函数都是构造函数
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
构造函数和实例原型之间的关系:

proto
JS中任意对象(除了 null )都有一个内置属性 __proto__,隐式原型指向创建这个对象的函数(constructor)的 prototype。
Object.prototype 这个对象是个例外,它的 __proto__ 值为 null:Object.prototype.proto===null
function Person() {}
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true

既然实例对象和构造函数都可以指向原型,那么原型是否有属性指向构造函数或者实例呢?
var fn = function(){}
fn.__proto__ ===Function.prototype // 为 true
fn.__proto__.__proto__ === Object.prototype // 为 true
var array = []
array.__proto__ === Array.prototype // 为 true
array.__proto__.__proto__ === Object.prototype // 为 true
Array.__proto__ === Function.prototype // 为 true
- 每一个函数,都有一个 prototype 属性。
- 所有通过函数 new 出来的对象,这个对象都有一个
__proto__指向这个函数的 prototype。 - 当你想要使用一个对象(或者一个数组)的某个功能时:如果该对象本身具有这个功能,则直接使用;如果该对象本身没有这个功能,则去
__proto__中找
//首先Object是一个构造函数,即一个函数
Object.__proto__ === Function.prototype // 为 true
//Function同样是一个构造函数,是来用来创建(构造)一个函数的构造函数
Function.__proto__ === Function.prototype // 为 true
//`true`是什么数据类型,`Boolean`嘛,因此它是由构造函数`Boolean`构造出来的
true.__proto__ === Boolean.prototype // 为 true
//需要第一句话和第二句话一起用了,首先根据第一句话,`prototype`是一个对象,然后根据第二句话,那么既然它是一个对象,他的构造函数很明显就是`Object`
Function.prototype.__proto__ ===Object.prototype // 为 true
在了解了什么是显示原型 prototype 和隐式原型 __proto__ 之后,我们也知道了怎么去找隐式原型,那么它们有什么作用呢?
- 显式原型的作用:用来实现基于原型的继承与属性的共享。
- 隐式原型的作用:构成原型链,同样用于实现基于原型的继承。举个例子,当访问 obj 这个对象中的 x 属性时,如果在 obj 中找不到,那就会沿着
__proto__依次查找
constructor
指向实例倒是没有,因为一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
function Person() {}
console.log(Person === Person.prototype.constructor); // true

//综上我们已经得出:
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
console.log(person.constructor === Person); // true
//当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
//所以person.constructor === Person.prototype.constructor
实例与原型
//当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
function Person() {}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
//在这个例子中,给实例对象 person 添加了 name 属性,打印 person.name时为 Daisy
但删除了 person 的 name 属性时,读取 person.name,
从 person 对象中找不到 name 属性就会从 person 的原型即 person.__proto__ ,
即Person.prototype中查找,结果为 Kevin、
但万一还没有找到呢?原型的原型又是什么呢?
原型的原型
其实原型对象就是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图:

原型链
可以用以下三句话来理解原型链:
- 每个对象都拥有一个原型对象
- 对象的原型可能也是继承其他原型对象的:
foo.prototype也有它的原型Object.prototype。 - 一层一层的,以此类推,这种关系就是原型链
原型链的终点是Object.prototype、所有对象都是从它继承了方法和属性、而Object.prototype 没有原型对象
console.log(Object.prototype.__proto__ === null) // true
null 表示“没有对象”,即该处不应该有值。
所以 Object.prototype.proto 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。所以查找属性的时候查到 Object.prototype 就可以停止查找了。

class A {}
class B extends A {
constructor() {
super(); // ES6 要求,子类的构造函数必须执行一次 super 函数,否则会报错。
}
}
super:注:在 constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于
A.prototype.constructor.call(this, props)。
原型链
- 每个函数都有prorotype属性,除了Function.prototype.bind(),该属性指向原型链
- 每个对象都有__proto__属性,指向了创建,该对象的构造函数的原型,其实此属性指向了[prototype],但[prototype]是内部属性,并不能访问到,所以用__proto__来访问
- 对象可通过__proto__来寻找不属于该对象的属性,proto 将对象连接起来组成了原型链
原型对象
每个函数都有prototype,这就是原型对象 通过函数实例化处理的对象,有__proto__属性指向原型对象 let a = new A() a.proto = A.prototype
作用:为每个实例对象存共享的方法和属性,所有实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份,而实例有很多,且实例属性和方法是独立的,在构造函数中为了属性的私有性及方法的复用,提倡:
- 将属性封装在构造函数中
- 将方法放在原型对象上
this指向
this 永远指向最后调用它的那个对象
函数调用的方法一共有 4 种:
- 普通函数调用-->window:fn()
var name = "windowsName";
function fn() {
var name = 'Cherry';
innerFunction();//作为一个函数调用(它就是作为一个函数调用的,没有挂载在任何对象上
function innerFunction() {
console.log(this.name); // windowsName
}}
fn()
2. 作为对象方法调用-->实例对象(该对象):a.fn(); 3. 作为定时器调用-->window
- 使用构造函数调用函数(new)-->实例对象
- 作为函数方法调用函数(call、apply、bind)-->
window.a.fn();/最后调用的也是a函数调用fn()
var name = "windowsName";
var a = {
name : null,
// name: "Cherry",
fn : function () {
console.log(this.name); // windowsName
}
}
var f = a.fn;//将 a 对象的 fn 方法赋值给变量 f 了,但是没有调用
f();//this 永远指向最后调用它的那个对象、所以输出windowsName
this的由来和备份的作用:www.ruanyifeng.com/blog/2018/0…
this表示当前对象,var that=this是将当前this对象复制一份到that变量中意义是:this对象在程序中随时会变,备份后that没变之前仍是指向当时的this,这样就不会出现找不到原来的对象
绑定优先级:new绑定 > 显示绑定 > 隐式绑定> 默认绑定>
改变this指向
this指向指定的上下文
- 箭头函数
箭头函数的 this 始终指向函数定义时的 this,而非执行时
箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,若箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”
- 备份this:在函数内部使用 _this = this
先将调用这个函数的对象保存在变量 _this 中,然后在函数中都使用这个 _this,这样 _this 就不会改变了
var name = "windowsName";
var a = {
name : "Cherry",
func1: function () {
console.log(this.name)
},
func2: function () {
var _this = this;
setTimeout( function() {
_this.func1()
},100);
}
};
a.func2() // Cherry
tips:在 func2 中,首先设置 var _this = this;,这里的 this 是调用 func2 的对象 a,为防止在func2中的setTimeout被 window调用而导致的在setTimeout中的 this 为 window。将 this(指向变量 a) 赋值给一个变量 _this,这样,在 func2 中使用 _this 就是指向对象 a 了
3. 使用 apply、call、bind 4. new 实例化一个对象 this 根据优先级:new 绑定 > 显示绑定 > 隐式绑定 > 默认绑定
数组有哪些方法
- 原数组改变的方法:pop、push、shift、unshift、reverse、sort、splice、、、、
- 原数组不变的方法:includes、find、flat、lastIndexOf、const、slice、join、toString、indexOf、forEach、map、filter、some、every、reduce、、、
sort
根据unicode编码排序不稳定:arr.sort((a,b)=>a-b)//a-b升序,b-a降序
splice
对数组进行增减:arr.splice(index,howmany,item1,item2...)//下标从0开始,howmany数字是几就是删除几个
['a','b',2,3].splice(2,1,'add1','add2')//['a','b','add1','add2',3]
循环遍历
foEach和map区别
- 相同点
- 都循环数组中的每一项
- 每次循环的匿名函数都有三个参数:当前项item,当前索引index,原始数组arr
- 匿名函数的this都指向window
- 只能循环数组
- 不同点
- forEach没有返回值,会改变原数组
- map有返回值,返回新的数组,原数组不变,可以链式别的方法
- 用途上:forEach不改变原数组,只是用做数据做一些事情
every/some
- every:
- 判断数组中每一项是否都满足条件,只有所有项都满足条件,才会返回true
- 当每个回调函数的返回值都为true时,every的返回值为true,只要有一个回调函数的返回值为false,every的返回值都为false
- 当回调函数的返回值为true时,类似于forEach的功能,遍历所有;如果为false,那么停止执行,后面的数据不再遍历,停在第一个返回false的位置
var arr = ["Tom","abc","Jack","Lucy"];
var a = arr.every(function(value,index,self){
console.log(value + "--" + index + "--" + (arr == self))
return true;//因为回调函数中没有return true,默认返回undefined,等同于返回false
})
// Tom--0--true
// abc--1--true
// Jack--2--true
// Lucy--3--true
//因为每个回调函数的返回值都是true,那么会遍历数组所有数据,等同于forEach功能
- some
- 判断数组中是否存在满足条件的项,只要有一项满足条件,就会返回true
- 因为要判断数组中的每一项,只要有一个回调函数返回true,some都会返回true、当遇到一个回调函数的返回值为true时,可以确定结果,那么停止执行,后面都数据不再遍历,停在第一个返回true的位置;当回调函数的返回值为false时,需要继续向后执行,到最后才能确定结果,所以会遍历所有数据,实现类似于forEach的功能,遍历所有。
- 只要有一个回调函数的返回值都为true,some的返回值为true,所有回调函数的返回值为false,some的返回值才为false
var arr = ["Tom","abc","Jack","Lucy","Lily","May"];
var a = arr.some(function(value,index,self){
return value.length > 3;
})
console.log(a); //true
var a = arr.some(function(value,index,self){
return value.length > 4;
})
console.log(a);//false
for
var arr = [1,2,3,4,5]
for(var i = 0;i<arr.length;i++){
console.log(arr[i])
}
find
该方法对数组所有成员依次执行 callback 函数,直到找出第一个返回值为 true 的成员,然后返回该成员。如果没有符合条件的成员,则返回 undefined。
[1, 4, -5, 10].find((v,i,arr)=>v<0) //-5
[1,2,3,4,5].find(item=> item > 3) // 4
- map和forEach: forEach:用来遍历数组,没有返回值 map:1.同forEach功能; 2.map的回调函数会将执行结果返回,最后map将所有回调函数的返回值组成新数组返回。
//功能2:每次回调函数的返回值被map组成新数组返回
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.map(function(value,index,self){
return "hi:"+value;
})
console.log(a); //["hi:Tom", "hi:Jack", "hi:Lucy", "hi:Lily", "hi:May"]
console.log(arr); //["Tom", "Jack", "Lucy", "Lily", "May"]---原数组未改变
filter::1.同forEach功能;2.filter的回调函数需要返回布尔值,当为true时,将本次数组的数据返回给filter,最后filter将所有回调函数的返回值组成新数组返回
//功能2:当回调函数的返回值为true时,本次的数组值返回给filter,被filter组成新数组返回
var arr = ["Tom","Jack","Lucy","Lily","May"];
var a = arr.filter(function(value,index,self){
return value.length > 3;
})
console.log(a); //["Jack", "Lucy", "Lily"]
console.log(arr); //["Tom", "Jack", "Lucy", "Lily", "May"]---原数组未改变
一:map(),foreach,filter循环的共同之处:
1.foreach,map,filter循环中途是无法停止的,总是会将所有成员遍历完。
2.他们都可以接受第二个参数,用来绑定回调函数内部的this变量,将回调函数内部的this对象,指向第二个参数,间接操作这个参数(一般是数组)。
二:map()循环和forEach循环的不同:
forEach循环没有返回值;map,filter循环有返回值。
三:map(环和filter()循环都会跳过空位,for和while不会
四:some()和every():
some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.
五:reduce(),reduceRight():
reduce是从左到右处理(从第一个成员到最后一个成员),reduceRight则是从右到左(从最后一个成员到第一个成员)。
六:Object对象的两个遍历Object.keys与Object.getOwnPropertyNames:
他们都是遍历对象的属性,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。但Object.keys不能返回不可枚举的属性;Object.getOwnPropertyNames能返回不可枚举的属性。

var、let、const
- var
- 是es5提出的,只有函数和全局作用域
- 可重复声明同一个值,后面的会覆盖前面的
var a = 1; //全局作用域
console.log(a) //1
{
var a = 2; // 代码块中声明,毫无影响
console.log(a) //2
}
console.log(a) //2
-
let、const
-
是es6提出的块级作用域
-
不存在变量声明提升,在声明前不可获取,称为暂时性死区
-
不能重复声明同一个值
-
const声明的常量,不能被修改且声明时必须赋值
-
let,var声明的是变量,可以被修改
var a = 1; let a = 1; //不能重复声明 报错 Identifier 'a' has already been declaredvar a = 1; //全局作用域 { let a = 2; // 代码块中声明,毫无影响 console.log(a)//2 } console.log(a)//1
-
-
变量声明提升
- 函数声明提升:即将函数声明提升到作用域顶部
-
函数声明式:声明和赋值都提升:
fn() function fn(){console.log(1)} //1 -
变量形式:只提升变量不提升值 fn() var fn = function(){console.log(1)} //报错
-
- 函数声明提升:即将函数声明提升到作用域顶部
-
变量声明提示:将变量声明提升到作用域顶部,但只提升声明不提升值、只有var才可以,let,const没有
console.log(num)//报错 var num = 10 console.log(fun)//报错 var fun = function(){}
const声明的是常量,常量不可以修改。常量定义必须初始化值,如果不初始化值就会报错。
tips:const变量不能修改指针,但是可以修改值
const b = 1
b = 2
console.log(b)//报错
const car = { type:'fait',mode:'500',color:'red' };//创建常量对象
car.type = 'hhh'//修改属性
car.owner = 'wsn'//添加属性
console.lo/(car)//最新/属性{type: 'hhh', mode: '500', color: 'red', owner: 'wsn'}
// 但是不能对常量对象重新赋值
car = {type: 'test', mode: '500', color: 'red'}//报错
输入url到展示的过程
- 浏览器先检查本地的缓存中是否有对应的域名,有就根据ip与服务器建立链接,若无则进入下一步的网络请求流程
- DNS域名解析:获取待请求域名的ip地址(解析http协议,端口号,资源地址),若请求协议是https那还需要建立tls链接、、dns解析时,会按本地浏览器缓存->本地host文件->路由器缓存->dns服务器->根、dns服务器的顺序查询域名对应的ip地址,知道找到为止
- 浏览器与服务器ip地址建立tcp链接,建立链接后浏览器端会构建请求行,请求头信息,并把和该域名相应的cookie等数据附加到请求头中,向服务器发送构建的请求信息
- 发起http请求,3次握手
- 服务器响应请求并返回结果:服务器对浏览器请求作出响应,并把对应的html文件发给浏览器
- 关闭tcp链接,4次挥手
- 浏览器渲染:浏览器解析html内容并渲染出来
渲染过程:构建dom树,词法分析然后解析dom tree由dom元素及属性节点,组成树的根是document对象
构建cs规则树,构建render树:dom和css合并构建render tree
布局layout:浏览器计算网页中有哪些节点,各节点的css及从属关系->回流
绘制painting:根据render tree回流得到节点信息,计算出每个节点在屏幕的位置->重绘,最后把信息交给图形处理程序显示页面
- 回流(Reflow):回流比重绘的代价要更高、回流必将引起重绘,重绘不一定会引起回流
当 Render Tree 中部分或全部元素的尺寸、结构、属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流 页面首次渲染
流程是:修改CSSOM树-->更新渲染树-->重布局-->重绘制
操作:浏览器窗口大小发生改变、、元素尺寸或位置发生改变元素内容变化、元素字体大小变化、增删可见的DOM元素、激活 CSS 伪类(:hover)、查询某些属性或调用某些方法(offsetTop, clientTop,,,,)
- 重绘(Repaint)
页面中元素样式的改变并不影响它在文档流中的位置时(如:color、background-color、visibility 等),浏览器不需重 新计算元素的几何属性、直接为该元素绘制新的样式,流程是:修改CSSOM树-->更新渲染树-->重绘制
cookie、sessionStorage、localStorage
都不能跨域都在本地浏览器中缓存
- 存放数据大小不同:cookie是4k左右,另外2个一般是5M
- 与服务器端通信:sessionStorage、localStorage仅在客户端中保存,不和serve通信、cookie每次都会携带在http头中发给server,但使用cookie保存过多数据会带来性能问题
- 数据的生命周期不同:
- cookie一般由serve生成,可设置失效时间,若在浏览器端生成cookie默认是关闭浏览器后失效,过期时间前一直有效
- sessionStorage仅在当前会话下有效,关闭页面或浏览器被清除后,页面刷新不会消除数据,页面打开的链接才可访问sessionStorage的数据
- localStorage 永久保存即使是关闭浏览器也不会让数据消失,除非主动删除
GET和POST的区别
- GET - 从指定的资源请求数据。
- POST - 向指定的资源提交要被处理的数据
- 缓存:GET会被浏览器主动缓存下来,留下历史记录,但POST不会,刷新后退数据会被重新提交
- 数据类型:GET只能进行URL编码,它只能接收ASCII字符,但POST没有限制支持多种编码方式
- 安全性:
GET把参数包含在URL中,通过历史记录,缓存很容易查到数据信息,POST通过request body传递参数,更适合传递敏感信息 - 数据长度发送数据时,GET方法向url添加数据,URL的长度是受限制的(URL的最大长度是2048个字符),POST无限制
- 链接:两者本质都是tcp链接,并无差别。但由于HTTP的规定和浏览器/服务器的限制,导致它们在应用过程中体现出一些不同
- tcp:GET产生一个TCP数据包;POST产生两个TCP数据包
- get请求比post快主要是因为:get请求比post请求少一条和get请求可以缓存
http各版本区别:
各版本区别
HTTP/0.9:
功能简陋,只支持GET方法且不支持请求头,只支持纯文本一种内容,服务器只能回应HTML格式的字符串,里边不能插入图片
HTTP/1.0:
根据Content-Type可支持多种数据格式(text/html、application/javascript、image/jpeg,传输文字,图像、音频、视频等二进制文件)
增加POST、HEAD等方法,
增加头信息
每次只能发送一个请求(无持久连接即不支持keepalive)、
支持cache,就是当客户端在规定时间内访问统一网站,直接访问cache即可
HTTP/1.1:
默认持久连接:即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive
请求管道化:(在同一个TCP连接里,允许多个请求同时发送,增加了并发性,进一步改善了HTTP协议的效率)、
新增了请求方式PUT、PATCH、OPTIONS、DELETE、
增加Host字段:用来指定服务器的域名、
支持断点传输分块传输:RANGE:bytes,HTTP/1.0每次传送文件都是从文件头开始,即0字节处开始。RANGE:bytes=XXXX表示要求服务器从文件XXXX字节处开始传送,断点续传。即返回码是206(Partial Content)
HTTP/2.0:二进制分帧、多路复用、头部压缩、
增加双工模式:不仅客户端能够同时发送多个请求,服务端也能同时处理多个请求
多路复用:同一个连接并发处理多个请求,解决了队头堵塞的问题
服务器推送:允许服务器未经请求,主动向客户端发送资源
二进制分帧:头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧,好处是,可以定义额外的帧,
头部压缩:HTTP 协议不带有状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的(比如Cookie和User Agent),一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度,引入了头信息压缩机制(header compression)。一方面,头信息使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了
生命周期的变化
Vue 3引入了两个新的调试钩子onRenderTracked和onRenderTriggered。
在Vue 3中,销毁相关的生命周期钩子从beforeDestroy和destroyed变为onBeforeUnmount和onUnmounted,以保持与挂载钩子的一致性。
什么是虚拟Dom
真实dom:操作真实dom以及后续更新,如果没有虚拟dom的话就会全量更新整个dom树,但是真实dom操作是很消耗资源的,虚拟dom就是为了提高性能的
虚拟dom是相对于游览器所渲染出来的真实dom的、用js按照dom结构来实现的一个树形结构,这个树形结构可以完全表现真实dom的每个dom节点以及dom节点的属性真实dom 有了虚拟dom后,每次dom的更改就变为了js对象的属性的更改,这样一来就能查询js对象的属性变化要比查询dom树的性能开销小
那虚拟dom是怎么解决这个问题的?diff算法 通过依赖更新触发,然后生成新的虚拟dom,将新的虚拟dom传入path中,通过diff算法对比新旧两个虚拟dom,如果虚拟dom没有改变则不操作,改变了就操作真实dom,,减少了dom操作,只会操作以及改变的dom
- 为什么说虚拟dom可提高性能 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没必要的dom操作来提高性能
当数据发生变化时,vue是怎么更新节点的? 先根据真实dom生成一个虚拟dom,当虚拟dom某个节点的数据改变后会生成一个新的节点,然后新旧节点做对比,发现有不一样的地方就直接修改在真实的dom上,然后使旧节点的值为节点
diff的过程就是就是调用名为patch函数,比较新旧节点,一边比较一边给真实的dom打补丁
1、vue项目的路由权限是怎么开发的?
2、如何做大文件上传的功能?
3、单点登录是怎么做的?
4、refresh_token的逻辑是怎么写的?
5、如何使用自定义指令控制按钮级别的权限?
6、websocket有没有接触过?
7、说一下websocket的心跳检测和断线重连。
8、有没有封装过npm组件库?
9、有没有做过项目代码优化和打包优化?
10、大屏可视化如何实现屏幕自适应?
强缓存和协商缓存分别什么时候用
BFC简单介绍一下
promise和async await的关系
具备什么优势: 技能方面 经验方面 兴趣 性格 能力 工作风格:比如做事认真负责
小程序为什么会有两个线程?
单点登录 如何一次性渲染十万条数据页面又不卡顿?
箭头函数和普通函数的区别
| 不同 | 普通函数 | 剪头函数 |
|---|---|---|
| this指向不同 | 有原型prototype,this指向不确定 | 本身没有this,因为没有 prototype (原型),this指向确定,指向他父级作用域,call,apply,bind无法改变剪头函数this指向 |
| new不同 | 可new | 不能new,因为prototype,不可被当构造函数 |
| 传参方式不同 | 可获取argument对象 | 不绑定arguments可以通过 ...rest ,用 rest 对象代替 |
| 声明方式不同 | 可声明也可赋值 | 只能赋值 |
响应式布局:屏幕尺寸兼容
响应式布局viewport
利用meta标签控制viewport
<meta charset="utf-8" name='viewport' content="width=device-width" initial-scale=1.0 user-scalable = 0>
该meta作用是让当前viewport的宽等于设备宽,同时不允许用户手动缩放,initial-scale:设置页面的初始缩放值
user-scale:是否允许用户进行缩放
minimum-scale:允许用户的最小缩放值
maxmun-scale:...最大
媒体查询@media
可根据设备的特定条件(如窗口或视口的宽高,设备的方向,分辨率等来应用不同的样式)) @media media-type and (media-feature){匹配的css样式}
@media (min-width:600px)and (max-width:700px){}
@media (not min-width:600px)and (not max-width:700px){}
@media (min-width:600px ,max-width:700px){}
//,or的意思、、、、not是对查询结果取反
自适应单位:rem,vw,%
px自动转换为vw:postcss-px-to-viewport
响应式布局flex
Flexbox 布局更易于学习,也更适用于轻量级的布局。当布局中主要是行或者主要是列时,Flexbox 布局的表现更出色
flex布局
原理:通过给父盒子添加flex属性来控制盒子的位置和排列方式
特点:Flex布局可以实现空间自动分配、自动对齐(弹性、灵活) flex 的核心的概念就是 容器 和 轴。容器包括外层的 父容器 和内层的 子容器,轴包括 主轴 和 交叉轴
父容器可以统一设置子容器的排列方式,子容器也可以单独设置自身的排列方式
tips:如果两者同时设置,以子容器的设置为准
轴 包括 主轴 和 交叉轴、flex-direction 属性决定主轴的方向,交叉轴的方向由主轴确定
有两种方式可以设置flex布局:
- display: flex;会生成一个块状的flex容器盒子
- display: inline-flex;会生成一个行内的flex容器盒子
父元素
父元素的属性:flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content
flex-direction
- 主轴方向,它决定了容器内元素排列方向
row: 默认值,主轴为水平方向从左到右;
row-reverse: 主轴为水平方向从右到左;
column: 主轴为垂直方向从上到下;
column-reverse: 主轴为垂直方向从下到上
justify-content:
主轴上子元素在主轴上的对齐方式
flex-start: ➡左到右
flex-end:➡️右到左
cneter: 居中对齐
space-between: 两端对齐,平均分配剩余空间 space-around:先两侧贴边,再平分剩余空间
align-items
定义子元素在交叉轴上的对齐方式(垂直方向)(单行)
strech: 默认值,子元素的高度铺满父元素
flex-start: 子元素⬇️从上到下排列、、让子项沿着容器顶部对齐,并且让子项保持各自的高度
flex-end: 子元素从⬆️ 从下到上
center: 子元素垂直居中
baseline: 根据子元素第一行文字的基线对齐(当字体大小不一致时,突出效果)
align-content
定义子元素多根轴线在侧轴上的对齐方式,只在多行显示下有效
stretch: 默认值,轴线铺满交叉轴
flex-start: 与交叉轴起点对齐
flex-end: 与交叉轴终点对齐
center: 与交叉轴中点对齐
space-between: 子元素先分配在上下,再平均非配剩余空间
space-around: 每根轴线两侧的间隔相等
flex-wrap
决定子容器是否换行排列,不但可以顺序换行而且支持逆序换行
nowrap: 默认值,不换行
wrap: 换行,第一行在上方
wrap-reserve: 换行,第一行在下方
flex-flow
是flex-direction和flex-wrap的组合、默认值为row nowrap
子元素
子元素属性:flex-grow、flex-shrink、flex-basis 、flex 、order、align-self
flex
- 是
flex-grow、flex-shrink和flex-basis的合集,默认值为0 1 auto,后两个属性可不写 - flex:1(默认:1 1 0%) *
flex-grow
父元素空间有剩余时,将剩余空间分配给各子元素的比例,默认为0,表示不分配;当为数值时,表示父元素剩余空间分配给各子元素的比例
flex-shrink
表示当子元素宽度总和大于父元素宽度,且未换行显示时,各子元素压缩大小,默认为1,表示各子元素等比压缩;当数值不一时,表示各子元素因为压缩空间而减小的尺寸的比例
flex-basis
可以用来设置子元素的空间,默认值为auto,表示为原本大小。当父元素有剩余空间时,可通过此属性扩充子元素的空间;各子元素通过扩孔之后的空间总和超过了父元素的空间大小时,按flex-basis值比例来设置子元素的大小,没有flex-basis属性时,默认flex-basis值为子元素原本大小,使子元素大小总和不得超过父元素空间大小
order
定义子元素在排列顺序,默认值为0,值越小越靠前,可以为负值
align-self
允许子元素单独设置对齐方式,优先级比父元素的align-items高。默认值为auto,表示继承父元素的align-items,如果没有父元素,则等同于stretch
auto: 继承父元素的align-items
stretch \
flex-start
flex-end
center
baseline
盒模型
- 基本概念:包括margin,border,padding,content 包含了元素内容(content)、内边距(padding)、边框(border)、外边距(margin)几个要素
box-sizing:- content-box :标准模型(外扩模式)
- border-box:IE模型(内减模式)

- BFC(边距重叠解决方案)解决边距重叠
box-sizing:指定盒子模型种类
body {
width: 100px;
height: 1000px;
margin: 25px;
padding: 20px;
border: 15px;
}
- content-box:默认值:标准/外加模式、设置一个元素的宽为100px,那么这个元素的内容区会有100px宽,并且任何边框和内边距的宽度都会被增加到最后绘制出来的元素宽度中
注意:border,padding,margin都在这个盒子的外部。比如说,
.box {width: 350px; border: 10px solid black;}在浏览器中的渲染的实际宽度将是 370px
盒子宽:width+padding+border=100+202+152=170px
- border-box:IE盒模型/内减模式、将一个元素的 width 设为100px,那么这100px会包含它的 border 和 padding,内容区的实际宽度会是 width 减去 border + padding 的计算值
width = 内容的宽度 + padding + border
盒子宽 = width=100px
内容宽 = width - padding - border = 100-202 - 152 = 30px
为什么用 margin 而不用 padding?
- 一条约定俗成的规则:在元素右侧和下方设置 margin,不去碰左侧和上方的 margin
- 至少是在英文界面的布局中,文档流的方向是从左到右、从上到下的,因此,每个元素都 “依赖” 其左侧和上方的元素
- 在 CSS 中,每个元素的定位都受到其左侧和上方的元素的影响
BFC:块级格式上下文
BFC是块级格式化上下文,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用
BFC的原理
- BFC元素垂直方向的边距会发生重叠。属于不同BFC外边距不会发生重叠
- BFC的区域不会与浮动元素的布局重叠。
- BFC元素是一个独立的容器,这个环境中的元素不会影响到其他环境中的布局,所以BFC内的外边距不与外部的外边距发生重叠
- 计算BFC高度的时候,浮动元素也会参与计算(清除浮动)
BFC应用
- 防止margin重叠
- 当两个相邻块级子元素分属于不同的BFC时可以阻止margin重叠、给其中一个div外面包一个div,然后通过触发外面这个div的BFC,就可以阻止这两个div的margin重叠
- 可以包含浮动元素--清除内部浮动
- 自适应两栏布局
- 可以阻止元素被浮动元素覆盖、防止字体环绕
触发BFC条件
- 根元素,即HTML元素
- 浮动元素:float 除 none 以外的值
- float为left/right是子元素本身触发了BFC,使普通布局流变成了浮动流布局
- overflow 除了 visible 以外的值 (hidden、auto、scroll)
- 父元素因浮动从而高度塌陷,所以需要overflow来触发父元素的BFC来重新布局回到普通布局
- display的值为inline-block、tabel、table-cell、table-caption
- 绝对定位元素:position (absolute、fixed)、值不为static、relative
BFC的特性
- 内部的Box会在垂直方向上一个接一个的放置。
- 垂直方向上的距离由margin决定
- bfc的区域不会与float的元素区域重叠。
- 计算bfc的高度时,浮动元素也参与计算
- bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素
定位position
- static 静态定位:默认值,表示没有定位,使用静态定位的元素会按照元素正常的位置显示,并且不会受到 top、bottom、left、right 和 z-index 属性的影响
- relative相对定位:元素相对于自己默认的位置(起始位置)来进行位置上的调整,通过top、bottom、left、 right来设置元素相对于默认位置在不同方向上的偏移量、定位的盒子仍在标准流中、元素的初始位置占据的空间会被保留
tips:相对定位的元素可以移动并与其他元素重叠,但会保留元素默认位置处的空间。
- absolute绝对定位:元素相对于第一个非静态定位(static)的父级元素进行定位,若找不到符合条件的父级元素则会相对与浏览器窗口(html)来进行定位。可使用top、bottom、left 和 right 四个属性来设置元素相对于父元素或浏览器窗口不同方向上的偏移、
元素会脱离文档流- 相对于body定位时,若right/bottom:0;则屏幕往上滚动时,绝对定位的元素也随之滚动
- fixed固定定位:相对于浏览器窗口进行定位、使用固定定位的元素不会因为浏览器窗口的滚动而移动、
元素会脱离文档流 - sticky:粘性定位:
- relative和fixed的结合;滑动过程中元素距离顶部距离大于设定的阀值top:0时,元素以relative 表现,小于 0px 时,相当于 fixed就会固定在顶部
- 生效是有一定的限制如下:
须指定 top, right, bottom 或 left 四个阈值其中之一
父元素不能overflow:hidden或者overflow:auto属性
父元素不能设置定位
隐藏元素
- display:none:元素彻底消失,不在文档流中占用位置了、不可点击、具有继承性即给父元素设置后,子元素也隐藏、所以会有回流(重排和重绘)操作,性能开销比较大
- visibility:hidden:元素消失但会占据空间所以页面结构未发生变动,不可点击,不具有继承性,子元素visibility: visible
- opacity:0:(占据空间,可点击会影响交互)、会被子元素继承且不能取消藏
怎么让元素平铺整个页面
- content{width:100%;height:100%;position:absolute/fixed}//元素不在文档流中了,高为100%才会生效
- 给div设width:100vw;height:100vh;(viewport可视窗口)
浮动
浮动产生的根本原因在于元素脱离了文档流,由于设置了float为none以外的属性(元素img和p形成了BFC(格式化上下文),说白了就是它自己变成了一个完整的容器,不再受外部父元素的控制了)
浮动会造成以下两种情况:
- 脱标(脱离标准文档流)还有position:absolute/fixed
- 父元素高度塌陷:子元素设浮动后就会脱离标准文档流导致子元素无法撑起父元素的高度
清除浮动的两类方法:
- 触发父元素的BFC(格式化上下文)
- 使用
clear: both(在浮动元素后使用空元素)
文本省略
//单行
div {
width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
//多行
div {
width: 200px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
动画
animation动画、transition过渡、transform变形:旋转、缩放、移动或倾斜
- transition:颜色值,位置,大小的过渡,: transition: transform 1s ease-in 1s;
- 需要触发,没法自动发生
- 只有起始两个状态,是一次性的,不能重复发生,除非一再触发
- 只能定义一个属性的变化
- animation
- 自主触发,可重复发生
- 有多个状态:keyframes、尤其是时间轴的控制
- 可定义多个属性的变化:比如颜色,大小,位置
- transform
- 移动(translate)
- 缩放(scale)
- 旋转(rotate)
- 倾斜(skew)
rem,em,px,%,vw和vh,vmin和vmax
juejin.cn/post/734569… em:定义字体大小时以父级的字体大小为基准;定义长度单位时以当前字体大小为基准。例父级font-size: 14px,则子级font-size: 1em;为font-size: 14px;;若定义长度时,子级的字体大小如果为14px,则子级width: 2em;为width: 24px
- 优点:页面更灵活、改动父元素字体大小,子元素会等比例变化
- 缺点:某节点的字体大小变时,子元素都得重新计算
rem:以根元素的字体大小为基准。例如html的font-size: 14px,则子级1rem = 14px
百分比%:以父级的宽度为基准。例父级width: 200px,则子级width: 50%;height:50%;为width: 100px;height: 100px;
vw和vh:基于视口的宽度和高度(视口不包括浏览器的地址栏工具栏和状态栏)。例如视口宽度为1000px,则60vw = 600px;
vmin和vmax:vmin为当前vw 和vh中较小的一个值;vmax为较大的一个值。例如视口宽度375px,视口高度812px,则100vmin = 375px;,100vmax = 812px;
预处理器(less/scss)
嵌套
.a {
&:hover{}
}
//嵌套属性
.demo {
// 命令空间后带有冒号:
font: {
family: fantasy;
size: 30em;
weight: bold;
}
}
变量
//less
@red: #f00;
strong {
color: @red;
}
//scss
$red: #f00;
strong {
color: $red;
}
混入
- 不带任何值的参数
@mixin border-radius($radius){
border-radius: $radius;
}
.box {
@include border-radius(3px);
}
- 传一个带值参数(传入一个默认值)
@mixin border-radius($radius:3px){
border-radius: $radius;
}
.btn {
@include border-radius;//使用默认参数值的混合宏
}
.box {
@include border-radius(50%);//可以自己传入参数值
}
- 传多个参数值
@mixin size($width,$height){
width: $width;
height: $height;
}
.box-center {
@include size(500px,300px);
}
函数
引用父级选择器
.side{
&:hover{}
}
.main-side{
&-side{}
}
生命周期
-
onLoad: 监听页面加载 页面加载时触发,可在onLoad的参数中获取打开当前页面路径中的参数
-
onReady: 监听页面初次渲染完成,代表页面已经准备妥当,可以和视图层进行交互
-
onShow:监听页面显示,页面显示/切入前台时触发
-
onHide:监听页面隐藏如navigateTo或底部tab切换到其他页面,小程序切入后台等
-
onUnload:监听页面卸载
-
onPullDownRefresh: 页面相关事件处理函数--监听用户下拉动作
- 需在app.json的window选项中或页面配置中开启enablePullDownRefresh。
- 通过wx.startPullDownRefresh触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致。
- wx.stopPullDownRefresh停止当前页面的下拉刷新
-
onReachBottom: 页面上拉触底事件的处理函数
-
onShareAppMessage:用户点击右上角分享