(一) 前置知识
(1) 一些单词
actual:真实的
markup:标记,加价
up to date:最新的
ceiling:天花板
streak:连胜
destructure:解构
// dob dar imd
pixel:像素
ratio:比率
// window.devicePixelRatio 设备像素比
ellipsis:省略号
collapse:折叠 // margin-collapse外边距折叠
brave:勇敢的
anonymous:匿名的,不知名的 // anonymous
composition:组合
protocol:协议
permanent:永久的
temporary:临时的
grammar:语法
(二) react部分
(1) diff算法
- 传统算法的复杂度
O(n^3)
,其中 n 是节点数 diff
算法可以把复杂度降低到O(n)
- 类型:
tree diff
component diff
element-diff
1. tree diff
- 跨层级的移动操作非常少,可以忽略不计
- 关键词:
- 逐层比较,不存在将会删除整个不一样的树,并不再向下比较,只需遍历一次即可完成对整棵树的比较
- diff过程:
- 逐层对同一层级的节点进行比较
- React通过updateDepth更新深度对Virtual DOM 树进行层级控制
- 对
树
进行分层比较
,两棵树只对同一层次的节点
进行比较,如果该节点不存在
,则该节点及其子节点会被完全删除
,不会在进一步比较
- 只需要遍历
一次
,就能完成整棵树的比较 - 先创建,后删除
- 跨层级移动了怎么办?
- diff只会考虑 ( 同层级的节点位置变换 ),如果跨层级的化,只有 ( 创建 ) 和 ( 删除 ) 节点的操作
- 优化:
- 所以在开发中,要尽量避免跨层级的组件的移动的情况,直接创建和删除消耗性能,
- 可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点 ( 在频繁切换时 )
2. component diff
- diff过程:
- 同一类型的组件,按原策略,逐层进行比较
- 不同类型的组件,会被判定为脏组件,则会替换掉整个组件的所有节点
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化(props,state未变化时),是没有必要再进行逐层比较的,此时,可以通过 shouldComponentUpdate() 返回false,来进行进行性能优化,不再进行逐层的比较
- 如果判断类型:即class名(组件名)一样就是同一类型
3. element diff
- 当节点处于同一层级时,diff将会有三种操作:
插入INSERT_MARKUP
,删除REMOVE_NODE
,移动MOVE_EXISTING
- 移动操作的一些概念
oldIndex
:元素在旧集合中的位置lastIndex
:取oldIndex和上一次的lastIndex的较大值,lastIndex的初始值是0,类似游标的概念移动:oldIndex < lastIndex 移动,移动的位置是下表lastIndex对应的位置
不移动:oldIndex > lastIndex 不移动
- 情况一
- --------- 新旧集合中存在相同节点但位置不同时 ---------
- diff过程
- 1. 先通过 ( 唯一的key ) 逐个判断 ( 新集合中的元素 ) 在 ( 旧集合 ) 中是否存在
- 2. 如果存在,就会进行移动操作,具体的 ( 移动规则 ) 如下
第一步:
- 元素:B
- B在旧集合中的位置:
oldIndex = 1
- B比较时的游标,刚开始游标默认值是0:
lastIndex = 0
- 比较:
oldIndex > lastIndex 不移动
- 下一轮比较前的游标更新:取oldIndex=1和lastIndex=0的最大值
lastIndex = 1
第二步: - 元素:A
- A在旧集合中的位置:
oldIndex = 0
- A比较时的游标:
lastIndex = 1
- 比较:
oldIndex < lastIndex 移动
- A移动到的位置:
移动到A元素在新集合中的位置对应的位置
- 下一轮比较前的游标更新:取oldIndex=0和lastIndex=1的最大值
lastIndex = 1
第三步: - 元素:D
- D在旧集合中的位置:
oldIndex = 3
- D比较时的游标:
lastIndex = 1
- 比较:
oldIndex > maxIndex 不移动
- 下一轮比较的游标更新:取oldIndex=3和lastIndex=1的最大值
lastIndex = 3
第四步: - 元素:C
- C在旧集合中的位置:
oldIndex = 2
- C比较时的游标:
lastIndex = 3
- 比较:
oldIndex < lastIndex 移动
- C移动到的位置:
移动到元素C在新集合中的位置对应的位置
- 下一轮比较前的游标更新:取oldIndex=2和lastIndex=3的最大值
lastIndex = 3
情况二
- --------- 新集合中有新加入的节点,旧集合中有删除的节点 ---------
- diff过程
第一步: - 元素B,oldIndex=1, lastIndex=0, oldInex>lastIndex不移动,更新后的lastIndex=1 第二步:
- 元素E
- 先判断是新建,删除,或者移动操作的哪一种
- 新建:
E在新集合中存在,在就集合中不存在,新建E
- 新建的位置:在E元素在新集合中的位置对应的位置,
新建E
- 下一轮比较前的游标更新:
lastIndex = 1
第三步: - 元素C,oldIndex=2, lastIndex=1, oldIndex>lastIndex不移动,更新后的lastIndex=2 第四步:
- 元素A
- A在就集合中的位置:
oldIndex=0
- A比较时的游标:
lastIndex=2
- 比较:
oldIndex < lastIndex移动
- 移动到的位置是:
A元素在新集合中的位置对应的位置
- 下一轮比较前的游标更新:
lasIndex = 2
第五步: - 元素D
- 在新集合中不存在,在旧集合中存在,删除操作,直接删除D
diff算法总结
- 把传统的 O(n^3) 的复杂度降低到了 O(n),n表示节点数
- 存在
treeDiff
componentDiff
elementDiff
treeDiff
跨层级移动非常少,逐层比较componentDiff
判断是不是同类型的组件,区分脏组件和逐层比较,以及VitureDOM没变通过shouldComponentUpdate来做优化elementDiff
重点理解移动的算法规则- 特殊情况:
- 在第一个元素和最后一个元素交换时,oldIndex是集合中的最大值,这会导致除了第一个元素不移动,后面的oldIndex<最大值lastIndex,
导致除了第一个元素外,所有元素都会移动
- 在第一个元素和最后一个元素交换时,oldIndex是集合中的最大值,这会导致除了第一个元素不移动,后面的oldIndex<最大值lastIndex,
(三) JS部分
(1) 变量提升
- 优先级:
函数形参 > 函数声明 > 变量声明
- 函数名已经存在,则新的覆盖旧的
- 变量名已经存在,直接跳过变量的声明
形参和变量名的提升比较
------
function a(name, age) {
console.log(name)
console.log(age)
var name = 'zhang'
console.log(name)
}
a('wang')
实际执行的代码如下:
function a(name, age) {
var name = 'wang'; // 形参声明并赋值
var age = unfined; // 形参声明并fuzhi
// var name = undefined;
// 1. 变量提升,但是先后顺序是:函数形参 > 函数声明 > 变量声明
// 2. 变量如果存在,直接跳过,所以 var name = undefined 不会执行
console.log(name) // 'wang'
console.log(age) // undefined,因为并没有传入实参
name = 'zhang'
console.log(name) // 'zhang'
}
a('wang')
函数形参,函数名,变量名的提升比较
------
function b(name) {
console.log(name)
var name = 'zhang'
function name() {console.log('function')}
console.log(name)
}
b('wang')
实际执行的代码:
function b(name) { // 变量提升优先级:函数形参 > 函数声明 > 变量声明
// var name = 'wang'; // 函数形参,优先提升
// var name = undefined; // 变量声明提升,变量名已经存在,变量的声明直接跳过,不会执行
function name() { // 函数声明提升,函数名已经存在,新的覆盖旧的
console.log('function')
}
console.log(name) // function name() {...}
name = 'zhang'
console.log(name) // 'zhang'
}
b('wang')
(2) Reflect
- Reflect对象设计的目的
- 将Object对象上的一些明显属于语言内部的方法,放到Reflect对象上
- 修改某些Object对象上方法的返回结果,让其更合理
- Reflect 一共有 ( 13 ) 个静态方法
- Reflect.has(obj, name)
Reflect.has
对应in
运算符
Reflect.has(obj, name)
---------------------
const obj = {
name: 'woow_wu7'
}
const has = Reflect.has(obj, 'name')
const notHas = Reflect.has(obj, 'age')
console.log(has, notHas) // true false
- Reflect.deleteProperty(obj, name)
Reflect.deleteProperty()
对应delete obj.name
- Reflect.construct(target, args)
Reflect.construct(target, args) 对应 new target(...args)
- 注意:
- Reflect.construct(target, args) 的第二个参数 args 是一个数组
- 提供了一个不使用 new 命令来调用构造函数的一种方法
- Reflect.getPrototypeOf(obj)
- Reflect.getPrototypeOf(obj) 对应 Object.getPrototypeOf(obj)
- 区别:
- 如果参数不是对象,Reflect.getPrototypeOf()会报错
- 如果参数不是对象,Object.getPrototypeOf()会将参数转换为对象
- Reflect.setPrototypeOf(obj, newProto)
- Reflect.setPrototypeOf(obj, newProto) 对应 Object.setPrototypeOf(obj, newProto)
- 返回值:
- 一个boolean值,表示是否设置成功
- 如果无法设置其原型对象,将返回一个false
- Reflect.apply(func, thisArg, args)
- Reflect.apply(func, thisArg, args) 等同于 Function.prototype.apply.call(func, thisArg, args)
- 注意:
- 一般来说,如果要绑定一个函数的this对象,可以这样写fn.apply(obj, args),但是如果函数定义了自己的apply方法,就只能写成Function.prototype.apply.call(fn, obj, args),采用Reflect对象可以简化这种操作。
(3) 数组乱序
- sort方法
- 数组的sort方法,默认按照字典顺序就行排序,会先被转成字符串,在按照字典进行排序
- 该变原数组,没有返回值
- 参数:
- 可以没有参数
- 也可以是一个函数(自定义方式排序就接受函数为参数),参数函数又有两个参数
- 返回值大于0,表示比较的第一个成员排在第二个成员的后面 (a-b>0表示升序)
- 返回值小于0,表示比较的第一个成员排在第二个成员的前面(a-b<0表示降序)
- 返回值等于0,位置不变
- 乱序的应用
- 换一批
- 猜你喜欢
- 中奖方案
数组乱序 - 方法1
arr.sort(() => Math.random() - .5)
解析:
// 因为:Math.random() 的值得范围区间是 [0, 1)
// 所以:Math.random() - 0.5 ===> 大于0或小于0的概率都是50%
// Math.random()参数参数的返回值:
// 函数返回值大于0,表示两个比较的成员,第一个排在第二个的后面
// 函数返回值小于0,表示两个比较的成员,第一个排在第二个的前面
// 函数返回值等于0,表示两个比较的成员,位置不变
// 当小数是0点几时,可以省略前面的0
(四) 函数式编程部分
(1) curry 函数柯里化
- 定义1:(
curry函数柯里化
) 是将 (接受多个参数的函数
),转变成接受一个 (单一参数
) 的函数,并 (返回该函数
) 的技术 - 定义2:( curry柯里化函数 ) 接受 ( 函数A ) 作为参数,运行后返回一个 ( 新函数 ),并且这个新函数能够处理 ( 函数A的剩余参数 )
1. 柯里化阶段一
- 需求:将
add(1,2,3,4)
转化成curryAdd(1)(2)(3)(4)
- 缺点:
- 只能处理固定的4个参数,不能处理任意多个参数,毫无扩展性
- 并且一次只能传入一个参数,不能传入多个参数
需求: 将add(1,2,3,4) 转化成 curryAdd(1)(2)(3)(4)
缺点:只能处理4个参数的情况,不能处理任意多个参数的情况,毫无扩展性
type TAdd<T> = (a: T, b: T, c: T, d: T) => T
const add: TAdd<number> = (a, b, c, d) => {
return a + b + c + d
}
// curry柯里化阶段1
const curryAdd = (a: number) => {
return function (b: number) {
return function (c: number) {
return function (d: number) {
return a + b + c + d
}
}
}
}
console.log('curryAdd(1)(2)(3)(4)', curryAdd(1)(2)(3)(4))
2. 柯里化阶段二
- 增加需求:
- 处理 (
任意多个参数
) 相加 - 一次可以传入 (
任意多个参数
)
- 处理 (
- 原理:
- 通过闭包来保存变量,使之可以记住上一次的值
- 当curryAdd函数的参数存在时,收集参数
- 当curryAdd的参数不存在时,则证明参数收集完毕,执行相加操作
- 缺点:
- 当参数收集完毕后,还需调用一次没有参数的调用,即多一次调用来执行相加操作
- 待优化点:
- 相加的逻辑单独抽离,通过参数传入
// 柯里化阶段二
function curry() {
let totalParams: number[] = []
function closure(...rest: number[]): any {
if (rest.length) {
totalParams = [...totalParams, ...rest]
return closure
}
return totalParams.reduce((total: number, current: number) => total + current)
}
return closure
}
const curryAdd = curry()
const res = curryAdd(1)(2)(3, 4)()
console.log(res) // 10
2022/03/24发现可以优化的点:生成curry的函数名可以叫 curryCreator
3. 柯里化阶段三
- 前置知识:
- function.length 和 arguments对象
length
:函数的length属性:返回函数预期的参数个数 ( 形参 )arguments
:包含了函数运行时的所有参数 ( 实参 )记忆方法
:arguments 是在函数内获取的,只有执行时才有效- 注意点:在ES6的 箭头函数中使用 ( rest参数 ) 代替 ( arguments对象 )
- function.length 和 arguments对象
- 优化阶段2中的不足:
-
- 如果少调用一次没有参数的curryAdd:通过直接传入add函数(而不是在内部定义)的add.length获取形参的长度,当收集的参数长度大于等于add.length时就去调用
-
- add函数从外部传入
-
function add(a: number, b: number, c: number, d: number, e: number) {
return a + b + c + d + e
}
function curry(fn: (...args: number[]) => number): any {
let totalParams: number[] = []
function closure(...rest: number[]): any {
totalParams = [...totalParams, ...rest] // 先收集参数,收集完,在拿最新的进行判断
if (totalParams.length >= fn.length) {
return fn(...totalParams)
} else {
return closure
}
}
return closure
}
const curryAdd = curry(add)
const res = curryAdd(1, 2)(3)(4, 5, 6) // 实参个数多于形参个数时,会自动过滤多余的实参
console.log('res11111:>> ', res);
4. 柯里化变通版
- curry柯里化阶段三中,需要知道函数的length属性,如果add中没有声明形参,而是通过arguments获取参数则会报错
function add(a: number, b: number, c: number, d: number, e: number) {
return a + b + c + d + e
}
function curry(fn: (...args: number[]) => number): any { // 闭包,只负责收集参数
let totalParams: number[] = []
function closure(...rest: number[]): any {
totalParams = [...totalParams, ...rest]
return closure
}
closure.getSum = () => { // 在闭包上单独绑定add函数,调用该函数实现相加并返回
return fn(...totalParams)
}
return closure
}
const curryAdd = curry(add)
const closure = curryAdd(1, 2)(3)(4, 5, 6)
const res = closure.getSum()
console.log('res2222:>> ', res);
(2) partial 偏函数
- 定义:partial偏函数,将 ( 一个或者多个参数 ) 固定到一个函数上,并产生返回一个更小元的函数
function add (a, b) {
return a + b
}
function partial (fn) {...}
const addPartial = partial(add, 1) // ------------------ 实现固定一部分参数1
const res = addPartial(2) // 3 -------------------------- 只传一部分参数 2
1. 偏函数的实现方式一
- 使用
bind
方法来实现- bind方法
- 可以接受被绑定函数 ( fn ) 的 ( 一部分参数或者全部参数 )
- 返回一个 ( 新的函数 ),返回的新函数可以继续 ( 接受剩余参数 )
- bind方法
function add(a: number, b: number, c: number, d: number): number {
return a + b + c + d
}
function partial(...rest: any[]): any {
const add = rest.shift() // shift,删除数组第一个成员,返回该成员,改变原数组
return add.bind(null, ...rest) // bind函数,绑定上下文,返回一个新函数,接受add的部分或者全部参数
}
const bindResFn = partial(add, 1,2)
const res = bindResFn(3,4) // bind返回的新函数,继续接受剩余参数
console.log('resPartial :>> ', res);
2. 偏函数的实现方式二
function add(a: number, b: number, c: number, d: number): number {
return a + b + c + d
}
function partial(...rest: any[]) {
const add = rest.shift()
let paramsFixed: number[] = [...rest] // 收集总参数,初始化收集的是固定部分的参数
function closure(...rest: number[]) {
paramsFixed = [...paramsFixed, ...rest]
if (paramsFixed.length < add.length) { // 这里返不返回closure可以根据实际情况,参数少于形参时,实际上也可以进行计算
return closure
}
return add(...paramsFixed)
}
return closure
}
const closure = partial(add, 1, 2)
const res = closure(3, 4)
console.log('resPratial5 :>> ', res);
(3) memorize 函数记忆
- 定义:函数记忆是指将上次的(计算结果)缓存起来,当下次调用时,如果遇到相同的(参数),就直接返回(缓存中的数据)
- 实现原理:
- 将 (
参数
) 和 (对应的结果
) 保存在 (对象
) 中,再次调用时,判断对象key是否存在,存在返回缓存的值 - 注意:
当函数有返回值时,做函数记忆化才有意义,因为如果参数一样,最终的结果直接复用,结果得是一个返回值
一次的参数可能有多个,所以以参数作为key时,需要将参数转成字符串
- 可以利用闭包来保存每次变更的key和value到对象中
- 将 (
- 前置知识:
- in运算符:(
in运算符
) 可以检查某个对象是否包含某个属性,包括 (自身属性
) 和 (继承属性
)
- in运算符:(
function add(a: number, b: number, c: number, d: number): number {
return a + b + c + d
}
function memorizeCreator(fn: (...params: any[]) => any) {
const cacheMap = {}
function closure(...rest: any[]) {
const key = JSON.stringify(rest) // 一次参数可能有多个,转成字符串作为key
if (!cacheMap.hasOwnProperty(key)) {
cacheMap[key] = fn(...rest) // key不存在,添加映射
console.log('执行了');
}
return cacheMap[key] // 返回结果
}
return closure
}
const memorize = memorizeCreator(add)
const res1 = memorize(1, 2, 3, 4)
const res2 = memorize(1, 2, 3, 4) // 调用两次,'执行了字符串只打印了一次'
console.log(res1, res2);
(4) recursive 递归函数
1. 尾调用
- 定义:当一个函数执行时的最后一步是 ( 返回另一个函数的调用 ),就叫做尾调用
- 优点:
当里层函数执行时,外层函数已经执行完,出了调用栈,不会造成内存泄漏
在递归中,尾调用使得栈中只有一个函数在运行,不会造成性能问题
- 注意点:
尾调用优化只在严格模式下有效
尾调用: 函数执行的最后一个步骤,是返回另一个函数的调用,叫尾调用
优点:
1. 尾调用,当里层函数被调用时,外层函数已经执行完,出栈了,不会造成内存泄漏
2. 在递归中,尾调用使得栈中只有一个函数在运行,不会造成性能问题
f(x) {
return g(x)
}
// 尾调用,因为返回g(x)调用的时候,f(x)已经执行完
f(x) {
return g(x) + 1
}
// 非尾调用,因为返回 g(x) 调用时,f(x)并未执行完,当f(x)执行完后,还有执行 g(x)+1,f(x)才执行完
// 函数只有执行完后才会出栈(执行上下文调用栈)
const a = x => x ? f() : g();
// f()和g()都是尾调用
const a = () => f() || g()
// f()非尾调用,还要接着判断
const a = () => f() && g();
// f()非尾调用
2. recursive 递归函数 ( 尾递归优化 )
- 构成递归的条件
- 边界条件
- 递归前进段
- 递归返回段
- 当边界不满足时,递归前进
- 当边界条件满足时,递归返回
- 尾递归
recursive:递归
factorial:阶乘
(例1) 实现一个阶乘函数
function factorial(num) {
if (num === 1) {
return 1
}
return num * factorial(num - 1) // 这不是尾递归(或者说尾调用),因为执行完内层函数,还需要计算相乘的结果
}
const res = factorial(3)
console.log(res, 'res') // 6
过程:
第一次:3 * factorial(2)
第二次:3 * 2 * 1
分析:
1. 每次返回一个递归的函数,都会创建一个闭包
2. 所以维护这么多层执行上下文栈,开销大,用以造成内存泄漏
3. 优化方法:尾调用
( ---------------------------- 优化 ---------------------------- )
(例1优化,利用尾递归优化)
function factorial(num, result) {
if (num === 1) {
return result
}
return factorial(num-1, result * num)
}
优化:利用尾递归做优化,这样调用栈中始终就只有一个函数执行,不会内存溢出,提升性能
原理:在函数的结尾返回 自身的调用
思路:
- 1. factorial(number, result)的第一个参数时每次就算的number,第二个参数是每次计算的结果
- 2. result第一次的默认值是1,因为是乘法运算,所以1不会影响结果值
流程:
- 1. factorial(3, 1) => factorial(2, 1 * 3)
- 2. factorial(2, 3) => factorial(1, 1 * 3 * 2)
- 3. 6
( ---------------------------- 再优化 ---------------------------- )
( 再优化:阶乘优化,多传了一个参数,可以用函数柯里化或者偏函数来实现)
function factorial(res, n) {
if (n === 1) return res;
return factorial(n * res, n - 1)
}
function curring(fn) {
let par_arr = Array.prototype.slice.call(arguments, 1)
const closure = function () {
par_arr = par_arr.concat(Array.prototype.slice.call(arguments))
console.log(par_arr, 'par_arr')
if (par_arr.length < fn.length) {
return closure
}
return fn.apply(null, par_arr)
}
return closure
}
const curringFactorial = curring(factorial, 1)
const res = curringFactorial(4)
console.log(res)
(5) composition 函数组合 - compose函数
- 比如在redux源码中就有 compose 函数
- redux源码中的compose函数 - 我的掘金博客
- 前置知识:
- reduce函数
- 依次处理数组的每个成员,最终累积成一个值
- reduce函数
- redux中的compose已经很完善了
- --------------- redux中的compose函数 ---------------
- 函数签名:
(...funcs) => funcs.reduce((a, b) => (...args) => a(b(...args)))
- 返回值:
- compose函数的最终 (
返回值
) 是一个 (函数
),这样的一个函数 ((...args) => a(b(...args))
) - 因为
compose的返回值是:funcs.reduce()
, 而 reduce函数的返回值是(...args) => a(b(...args))
- compose函数的最终 (
- 函数签名:
- 如何调用
compose(a, b, c)(1)
- 说明compose是一个高阶函数,返回值是一个函数,返回的函数的也接受参数
redux中的compose函数源码
function compose(...funcs) {
if (funcs.length === 0) { // ------------- (1) 如果compose函数没有传入参数,就返回一个函数,该函数直接将参数值返回
return arg => arg
}
if (funcs.length === 1) { // ------------- (2) 如果compose函数传入了一个参数,那么直接返回该函数
return funcs[0]
}
// ---------------------------------------- (3) 当compose的参数大于1个参数时,返回迭代器reduce,而reduce返回的是一个函数
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
const a = (num) => num + 1;
const b = (num) => num + 2;
const c = (num) => num + 3;
const resFn = compose(a, b, c)(1)
console.log(resFn, 'resFn')
compose分析:
(1) 如果compose函数没有传入参数,就返回一个函数,该函数直接将参数值返回
(2) 如果compose函数传入了一个参数,那么直接返回该函数
(3) 当compose的参数大于1个参数时,返回迭代器reduce,而reduce返回的是一个函数
- compose(a, c, c)
- 本例中compose函数一共传入了两层参数
- 第一层:一共传入了三个函数作为参数,分别是 a b c
- 第二层:传入了参数 1
第一步:第一层函数被调用 compose(a, b, c)
[a, b, c].reduce((a, b) => (...args) => a(b(...args)))
第二步:
- reduce第一次迭代
- (...args) => a(b(...args))
- b(...args)执行的结果作为a的参数 => a((...args) + 2)
- a((...args) + 2)执行的结果 => (...args) + 2 + 1
- reduce的总的返回值 => (...args) => (...args) + 2 + 1
第三步:
- reduce的第二次迭代
- (...args) => ab迭代结果(c(...args))
- c(...args)执行的结果作为 ab迭代结果的参数 => ((...args) + 3) => (...args) + 2 + 1
- ab((...args) + 3)执行的结果 => (...args) + 3 + 2 + 1
- reduce的总的返回值 => (...args) => (...args) + 3 + 2 + 1
第四步:
- compose的最终形态
- const composex = (...args) => (...args) + 3 + 2 + 1
第五步:第二层函数被调用compose(a,b,c)返回的函数在调用(1)
- composex(1)
- 返回值 1+3+2+1
- 7
函数组合案例1
function composeGenerator(a, b) { // compose函数生成器,将右边参数函数执行的结果,作为左边参数函数的参数传入
return function(params) { // compose函数
return a(b(params))
}
}
function reverse(str) { // 反转数组
return str.reverse()
}
function transformToString(arr) { // 将数组转成字符串
return arr.join('')
}
const arr = [1,2,3,4]
const compose = composeGenerator(transformToString, reverse)
const res = compose(arr)
console.log(res, typeof res, '函数组合例子:先反转数组,再转成字符串')
(五) 手写函数
(1) call函数模拟实现
- call函数的作用
- 1.绑定函数内部的this指向
- 2.在所指定的作用域中调用该函数
- call函数的参数
- 理论上应该是一个:对象
- 如果传入的参数是 (
null undefined 空
),则相当于传入了全局对象 (window/global
) - 如果传入的参数是 (
原始值
) 则会转为对应的 (包装对象
) - 可以接收多个参数,第一个参数是this需要绑定的对象,后面的参数是被绑定的函数调用用需要的参数
- call方法的应用
- 当一个对象调用 ( 继承的属性 ) 时,如果该对象 ( 自身 ) 也有一个 ( 重名的属性 ) 时,将发生 ( 覆盖 )
- call方法可以把 ( 继承的方法的原始定义放在该对象上执行 ) ,这样无论对象是否有重名的属性,都不会受影响
call方法的应用
- call方法解决自身属性覆盖原型链上的属性调用
------------------------
var obj = {};
obj.hasOwnProperty('toString') // false,注意toString是继承的方法,空对象自身并没有该方法
// 覆盖掉继承的 hasOwnProperty 方法
// 通过在obj自己定义一个方法,这样就会覆盖原型链上的同名方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
// true,调用时先找自身是否有该方法,而本意是调用原型链上的hasOwnProperty方法
// 这样就造成了结果偏差,可以用call方法解决
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
上面代码中,hasOwnProperty是obj对象继承的方法,如果这个方法一旦被覆盖,就不会得到正确结果。
- call方法可以解决这个问题
- 它将hasOwnProperty方法的原始定义放到obj对象上执行,这样无论obj上有没有同名方法,都不会影响结果。
1. call方法的模拟实现 - es5
const obj = {
name: 'woow_wu7',
age: 20,
}
function add(address, sex) {
return this.name + this.age + address + sex
}
Function.prototype._call = function (obj) {
obj = obj || window
// 相当于 obj = obj ? obj : window
// 当 fn.call() 的参数是 ( null undefined 空 ) 时,call函数相当于传入了全局对象 ( window/global )
// ( null undefined 空 ) 三者的布尔值都是 false
const params = []
// params用于收集bind被绑定函数的参数,需要除去第一个表示被绑定对象的参数的 剩余参数
for (let i = 1; i < arguments.length; i++) {
params.push('arguments[' + i + ']')
}
// 注意:循环是从1开始的,因为arguments[0] 是fn中this绑定的对象,这里是收集传入fn的参数
// arguments是类似数组的对象,具有length属性
// params相当于 ['arguments[1]', 'argument[2]']
// + 这里重载为 字符串的拼接
obj.fn = this
// this调用时确定指向,_call方法是函数调用的,指向调用_call方法的函数
// fn可以随便取名,表示一个方法,该方法就是调用 _call 的函数
const res = eval('obj.fn(' + params + ')')
// + 号存在 ( 重载 ),存在 ( 相加 ) 和 ( 相连 )
// 因为:当 + 运算子存在对象时,会先将 ( 对象 ) 转成 ( 原始类型的值 ),即先执行 valueOf -> 对象本身 -> toStirng -> 出object外,都是返回该类型对象的字符串形式
// 所以: params 数组和字符串相加会被先转化为原始值: ['arguments[1], arguments[2]'] => 'arguments[1], arguments[2]'
// 例如:[1,'2',3,'4'].toString() -> "1,2,3,4"-> 数组返回数组的字符串形式
// 最终: eval('obj.fn(argument[1], arguments[2]'))
// 返回值:fn可能有返回值,所以用res接收
Reflect.deleteProperty(obj, 'fn')
// 相当于:delete context.fn
// 删掉对象上的函数
// 注意:delete context.fn 这样的旧的写法会逐渐被抛弃
return res
// fn可能有返回值,需要返回最终的结果
}
const res = add._call(obj, 'chongqing', 'man')
console.log(res)
2. call方法的模拟实现 - es6
### call方法的模拟实现 - es6
const obj = {
name: 'woow_wu7',
age: 18
}
function fn(address, sex) {
return this.name + this.age + sex + address
}
Function.prototype._call = function(context) {
context = context ? context : window;
context.fn = this;
const res = context.fn(...[...arguments].slice(1));
Reflect.deleteProperty(context, 'fn')
//delete context.fn;
return res;
}
const res = fn._call(obj, 'chongqing', 'man')
console.log(res, 'res')
(2) apply函数模拟实现
const obj = {
name: 'wang',
};
function fn(name) {
return {
name: name || this.name
}
}
Function.prototype._apply = function (context, arr) {
context = context ? context : window;
context.fn = this;
let res = null;
if (!arr) { //_apply第二个参数不存在,就不给fn传参,直接执行fn
res = context.fn()
} else {
arr instanceof Array
?
res = context.fn(...arr)
:
console.log('第二个参数只能是数组')
}
delete context.fn;
return res;
}
const result = fn._apply(obj, ['woow_wu7']);
console.log(result)
(3) bind模拟实现
- bind方法的作用:
- 绑定被绑定函数的 this 指向
- 返回一个新的函数
- bind函数的参数
- 第一个参数:函数中this需要绑定的对象
- 第二个参数:传入被绑定函数的参数
- bind时,可以传入部分参数
- 剩余参数,在bind调用后返回的新函数被调用时传入
- 注意点:
- 当bind除了第一个参数,第二个参数传入:( null,undefined,空 ) 时,相当于传入全局对象 ( widnow/global )
- bind函数调用时,可以传入 (部分,或者全部参数),传入部分参数时,剩余参数可以在bind返回函数被调用时传入
- 返回的新函数
- 可是通过 new 命令调用,即返回的函数可以作为构造函数,这种情况下
- bind绑定的this失效,因为在构造函数中,this执行实例
- 但是传入的参数有效
- 可是通过 new 命令调用,即返回的函数可以作为构造函数,这种情况下
- 关键词
- 寄生继承 - 我的掘金博客
- instanceof 的原理
- apply方法
- 闭包
- 我的掘金博客 - bind模拟实现
bind方法的模拟实现
---------------------
const obj = {
name: 'woow_wu7',
age: 20
}
function fn(name, age, address, sex) {
this.name = name
this.age = age
return this.name + this.age + address + sex
}
fn.prototype.score = 100
// 如果 ( _bind ) 返回的新函数,通过 ( new ) 命令调用,那么生成的实例可以继承 fn.prototype 上的属性和方法
Function.prototype._bind = function (context) {
context = context || window;
// 当 _bind 传入的参数是:( null undefined 空 ) 时,相当于传入全局对象 window/global
const self = this
// 绑定外层函数的this,this在调用时才能确定指向,本例中_bind中的this指向 fn
const bindParams = [...arguments].slice(1)
// 获取 _bind 除了绑定的对象以外的剩余参数,剩余的参数将会作为_bind返回函数的参数
function TempFn() { }
// 寄生继承
function fbind() {
const fbindParams = [...arguments] // _bind返回函数中的参数
const totalParams = [...bindParams, ...fbindParams] // 总的传入fn的参数
return self.apply(this instanceof self ? this : context, totalParams)
// (1) 如果 _bind 返回的函数fbind通过 new 命令调用
// - 那么 this 指向的就是_bind返回函数new后生成的实例,所以 this instanceof self 是 ture
// - 为什么 this instanceof self 是 ture
// - 因为:fbind.prototype => new TempFn() => TempFn.prototype => self.prototype,则 fbind的实例可以继承 new TempFn() 实例上的属性和方法,和 self.prototype 上的属性和方法
// (2) 如果 _bind 返回的函数fbind不是通过 new 命令调用,那么fn中的this就需要绑定到传入的对象context上
// (3) 函数可能有返回值:所以要返回最终的结果
// (4) instanceof 判断的是 ( 右边构造函数的prototype属性 ) 是否在 ( 左边实例对象的 ) 原型链上
}
TempFn.prototype = self.prototype
fbind.prototype = new TempFn()
// 寄生式继承
// 将fbind.prototype绑定到TempFn的实例上
// 这样fbind作为构造函数时,fbind的实例能继承TempFn实例上的属性和方法,和 TempFn.prototype上的属性和方法,即原型链上的所有属性和方法
return fbind
}
const fBind = fn._bind(obj, 'wang', 20, 'chongqing')
const res = new fBind('man')
console.log(res, 'res')
(4) 手写new
- new函数的作用:
- 执行构造函数
- 返回实例对象
- new 命令本身可以执行构造函数,所以
new Constructor()
和new Constructor
都可以 - 如果构造函数 ( 忘记使用new ) 来调用,则 ( 构造函数 ) 相当于 ( 普通函数 ),( this ) 则指向全局对象 ( window/global )
- 如果解决忘记new调用?
-
- 使用严格模式
-
- 使用 (
this instanceof 该构造函数
),如果返回false,则没有通过new调用;模拟bind方法时候用了同样的方法判断
- 使用 (
-
- ES6中的 Reflect.construct(target, args)
- args是一个数组
Reflect.construct(target, args) === new target(...args)
- 构造函数的返回值:
- return后面跟的是一个对象,new命令会返回 ( 这个对象 )
- return后面跟的不是一个对象,而是基本类型的值,new命令会返回 ( this对象 )
- 如果普通函数使用 new 去调用,返回一个 ( 空对象 )
- 即:
new命令总会返回一个对象,无论是在构造函数还是普通函数中
- 继承相关
- 构造函数中的属性和方法,都是直接生成在实例上的,实例之间不共享,造成资源浪费
- 实例能继constructor.prototype上的属性和方法,多个实例可以共享,修改则相互影响
- arguments
- 使用argumetns的好处是在参数个数不确定的情况下,获取任意多的参数
- es6中则用 rest参数获取剩余参数,可以代替arguments
- new 命令的原理
- 新建一个空对象
- 将空对象的隐式原型指向构造函数的显示原型,则空对象可以继承构造函数prototype上的属性和方法
- 注意:
这里最好不要用 {}.__proto__ = Constructor.prototype
- 因为:
__proto__只有浏览器才有
- 代替:
const obj = Object.create(Constructor.prototype)
- 注意:
- 将构造函数中的this指向空对象
- 执行构造函数
function _new(Constructor, ...rest) {
// (1) const obj = {} 创建一个空对象
// (2) obj.__proto__ = Constructor.prototype 将空对象的隐式原型指向构造函数的显示原型,这样空对象就能继承Constructor.prototype上的属性和方法
const obj = Object.create(Constructor.prototype)
// 优化:使用 const obj = Object.create(Constructor.prototype)代替上面的12两步
// 因为:__proto__只在浏览器中存在,兼容性不好,比如在node环境中就不存在
// 解读:Object.create(prototypeObj) 作用是以 ( 参数对象 ) 为 ( 原型 ) 生成 ( 实例对象 ),实例对象就继承原型对象的属性和方法
const res = Constructor.apply(obj, rest)
// (3) 将 Constructor 构造函数中的 this 指向这个 空对象
// (4) 执行 Constructor 构造函数
// 用 apply方法实现了 (3)(4)
// 用 es6中的 rest 参数变相代替了arguments对象
return (typeof res === 'object' && res !== null)
? res
: obj
// 构造函数可能有
// (5) 返回值:如果return后面跟一个对象,就返回这个对象,否则返回this对象即这个空对象obj
// typeof 值得注意的是:object,array,null 都会返回 'object' 字符串
}
function Constructor(name, age) {
this.name = name
this.age = age
// 没有返回值时,相当于返回undefined
// 而在构造函数中,只要不是return的对象,就都会返回this对象
}
const res = _new(Constructor, 'woow_wu7', 20)
console.log(res, 'res')
const res2 = new Constructor('woow_wu8', 30)
console.log('res2', res2)
对比我以前笔记中的总结写法,以前还是有不完善的地方啊
以前的总结如下:
<!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>
<body>
<script>
function Constructor(name, age) {
this.name = name
this.age = age
return this
}
function _new() {
const obj = {}
// 第一步
// 新建一个空对象
// 相当于 const obj = new Object()
const paramsConstructor = Array.prototype.shift.call(arguments)
// 将arguments转化成数组,并且取除数组中的第一个元素,即传入的构造函数
// 相当于 ([]).prototype.shift.call(arguments)
// 相当于 ([]).shift.call(arguments) => 因为数组实例是继承了Array.prototype上的属性和方法
// 相当于 Array.prototype.shift.apply(arguments) => call 和 apply 都可以
// 注意:
// push unshift pop shift都会改变原数组
// push unshift 返回值是操作后,数组的长度
// pop shift 返回值是添加或者删除的元素
obj.__proto__ = paramsConstructor.prototype
// 第二步
// 将 ( 空对象的隐式原型 ) 指向 ( 构造函数的显示原型 )
// 这样空对象就可以继承构造函数prototype上的属性和方法
// 注意:
// const obj = {} 和 obj.__proto__ = paramsConstructor.prototype
// 可以简写为:const obj = Object.create(paramsConstructor.prototype)
// b = Object.create(a)作用是以参数对象a为原型,生成实例对象b - 即可以用一个对象创建实例对象
const res = paramsConstructor.apply(obj, arguments)
// 第三步
// 将构造函数中的this绑定到空对象上,并执行构造函数
// 注意:
// 这里是argumets是去除了构造函数参数后的,剩余参数的集合
// _new(constructor, p1, p2, ...)
return /Object|Function/.test(Object.prototype.toString.call(res)) ? res : obj
// 如果构造函数的返回值
// 是对象,就返回这个对象
// 是原始类型的值,就返回this对象,即空对象
}
const instance = _new(Constructor, 'woow_wu7', 20)
console.log(instance, 'instance')
</script>
</body>
</html>
(六) 正则表达式
- regular expression
- regular:规则
- expression:表达式
(1) 新建正则表达式的两种方法
- 字面量
var regex = /xyz/;
( 在编译时新建 )
- RegExp构造函数:
var regex = new RegExp('xyz');
( 在运行时新建 )
- 字面量方式新建正则的优势:直观,效率高,因为在编译时就构建了,不用等到运行时才去构建
(2) new RegExp('abc', 'i')
- ( RegExp ) 构造函数的第二个参数:表示 ( 修饰符 )
new RegExp('abc', 'i') === /abc/i
- 修饰符
- g:表示全局匹配 ( global ),匹配全部符合条件的结果,主要用于 ( 搜索 ) 和 ( 替换 )
- i:表示忽略大小写 ( ignorecase )
- m:表示多行匹配 ( multiply )
(3) 匹配规则
(1) 字面量字符 和 元字符
- 字面量字符:在正则表达式中的字符表示它字面的含义
- 元字符:并不表示其字面的意思
(2) 元字符
- 点字符(
.
):匹配除回车符\r
换行符\n
行分隔符\u2028
段分隔符\u2029
以外的所有字符 - 位置字符(
^
$
):表示字符串的开始位置 和 结束位置 - 选择符(
|
):表示 ( 或or ) 关系
(3) 转义符
- 用于 ( 具有特殊含义的元字符要匹配自身时 ),需要在前面加上 ( 反斜杠\ )
- 需要用反斜杠转义的元字符,一共有 (
12
) 个元字符. ^ $ | ? * + () [ { \\
(4) 特殊字符
回车符 \r ---------------------------------- enter
换行符 \n ---------------------------------- return
制表符 \t ---------------------------------- tab
垂直制表符 \v
换页符 \f
\0 匹配 null 字符
(5) 字符类 []
- 脱字符 (
[^]
):表示匹配除了字符类中的字符以外的其他字符- [^] :表示匹配一切字符
- . :表示匹配除了 \n \r \u2028 \u2029 以外的所有字符
- 连字符 (
[-]
):连续序列字符的简写形式- [1-31]:表示的是 1-3,而不是表示 1-31
- 不要过分使用连字符,设定一个很大的范围,因为可能选中意想不到的字符
(6) 重复类 ( {}
)
- 重复类表示:模式的精确匹配次数
{n} ------------------- 表示重复 n 次
{n, } ----------------- 表示至少重复 n 次
{n, m} ---------------- 表示重复不少于n次,不多于m次
(7) 量词符 ( ? * +
)
- ? :表示出现0次或者1次,相当于{0,1}
-
- :表示出现0次或者多次,相当于{0,}
-
- : 表示出现1次或者多次,相当于{1,}
- 注意:默认情况下都是最大可能匹配,直到下一个字符不满足匹配规则为止,被成为贪婪模式
(8) 预定义模式
\d 匹配0-9之间的任一数字,相当于[0-9]。 digit数字
\D 匹配所有0-9以外的字符,相当于[^0-9]。
\w 匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]。word单词
\W 除所有字母、数字和下划线以外的字符,相当于[^A-Za-z0-9_]。
\s 匹配空格(包括换行符、制表符、空格符等),相等于[ \t\r\n\v\f]。
\S 匹配非空格的字符,相当于[^ \t\r\n\v\f]。
\b 匹配词的边界。
\B 匹配非词边界,即在词的内部。boundary边界
(9) 贪婪模式
- 定义:三个量词符,都是最大可能匹配,直到下一个字符不满足匹配规则为止,这叫做 ( 贪婪模式 )
贪婪模式如何改为非贪婪模式????
- 在量词符后面加一个问号
非贪婪模式
+?
*?
注意:没有??,因为?本身就是有边界的,表示0次或者1次
(10) 组匹配
括号
:( 正则表达式 ) 中的 ( 括号 ),表示 ( 分组匹配 )括号中的模式
:( 括号中的模式 ) 可以用来匹配 ( 分组的内容 )引用括号匹配的内容
:通过 \n 引用括号匹配的内容非捕获组
:(?:x)先行断言
:x(?=y) => x只有在y前面才匹配,y不会被计入返回结果先行否定断言
:x(?!y) => x只有不在y前面才匹配,y不会被计入返回结果- 注意:组匹配时,不宜使用 /g 全局匹配修饰符,否则match函数不会匹配分组内容
引用括号匹配的内容
/(.)b(.)\1b\2/.test("abcabc")
// true
\1 表示匹配到的第一个括号的内容 a
\2 表示匹配到的第二个括号的内容 b
非捕获组
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"]
第一个括号好是非捕获组,在结果中不会有第一个组的内容输出
(11) 可以使用正则表达式的字符串方法
- match
- search
- replace
- split
(1) match
- 1. 匹配 ( 成功 ) 返回一个 ( 数组 )
- 2. 匹配 ( 失败 ) 返回 ( null )
- 2. 如果在match的参数正则中,带有 ( g修饰符 ),会一次性返回 ( 所有匹配成功的结果 )
('abcabc').match(/a/g) // ['a', 'a']
----
(2) replace
str.replace(search, replacement)
- 参数
- search 正则表达式,表示搜索模式
- replace 要替换的内容,注意可以使用 $ 美元符号,来指代所替换的内容
'aaa'.replace('a', 'b') // "baa"
'aaa'.replace(/a/, 'b') // "baa"
'aaa'.replace(/a/g, 'b') // "bbb"
$&:匹配的子字符串。
$`:匹配结果前面的文本。
$’:匹配结果后面的文本。
$n:匹配成功的第n组内容,n是从1开始的自然数。
$$:指代美元符号$。
'hello world'.replace(/(\w+)\s(\w+)/, '$2 $1')
// "world hello"
// 注意区别:/(a)b(c)\1b\2/.test('abcabc')
// true
'abc'.replace('b', '[$`-$&-$\']')
// "a[a-b-c]c"
(七) HTTP 和 HTTPS
(1) TCP/IP协议 - 应用层,传输层,网络层,数据链路层
- 应用层:HTTP FTP TELNET SMTP DNS等协议
- 传输层:TCP协议 UDP协议
- 网络层:IP协议 ICMP协议等
- 数据链路层
- 字数达到限制了,后面的文章再总结
(八) css部分
(1) scrollTop 和 offsetTop 和 clientHeight 和 offsetHeight 和 scrollHeight
- scrollTop
- scrollTop表示有滚动条时,滚动条向下垂直滚动的距离
- offsetTop
- offsetTop表示当前元素顶部距离最近父元素顶部的距离
- offsetTop和有没有滚动条无关,是一个固定值,滚动时值不会改变
- 当元素 display:none 时,offsetTop = 0
- clientHeight
- 包括:( padding )
- 不包括:( border ),( margin ),( 水平滚动条 )
- 对于 ( display:inline ) 时,( clientHeight = 0 ),说明clientHeight对行内元素无效,单位px,只读
- offsetHeight
- 包括:( padding ),( border ),( 水平滚动条 )
- 不包括:( margin )
- 对于 ( display:inline ) 时,( clientHeight = 0 ),说明clientHeight对行内元素无效,单位px,只读
- 所以:
offsetHeight = clientHeight + border
2021/10/17更新
- scrollHeight
- scrollHeight = scrollTop + clientHeight
- 在有滚动条时讨论scrollHeight才有意义,因为没有滚动条时scrollHeight===clientHeight
(2) ceiling 吸顶效果
1. css方案 - position: sticky
- 前置知识:
- position: fixed absolute relative static inherit sticky
- static:默认值,没有定位
- inherit:继承,从父元素继承position属性
sticky:粘性定位,该定位基于用户滚动的位置
,相当于position:relative + position:fixed
sticky:正常情况下相当于position:relative,当页面滚动超出目标区域时,就相当于position:fixed会固定在目标位置
- position: sticky
- position: sticky; top:0px;
- 定位:
设置 ( position: sticky ) 的元素,定位是基于 ( 具有滚动条的距离最近的祖先元素 ),不存在这样的祖先元素,则基于 ( viewport ) 元素定位
- 注意:当同一层级的多个元素都设置了position:sticky属性时,top相同的将会被替换,不同top值的不会
2. IntersectionObserver方案
3. js方案 - scrollTop 和 offsetTop
-
利用 scrollTop 和 offsetTop 来判断 ( 目标元素 ) 是否滚动到了 ( 父元素 ) 的最顶部
- 当scrollTop >= offsetTop时,已经到了顶部,到顶部后,显示和需要吸顶元素一样的元素
- 当scrollTop <= offsetTop时,隐藏
- 上面那样用一个完全一样的div去做,不会抖动,当然也可以动态计算吸顶元素的高度,脱离文档流后,去用空白的div去补充
-
注意:
- offsetTop是固定不变的值,值是距离最近的具有位置属性的祖先元素之间的距离
- 所以一般都要把父元素设置position:relative
-
关于 position: fixed;
- 一般情况是基于 viewport 元素进行定位
- 当祖先元素 transform 属性的值是非none时,定位则会基于该祖先元素,而不是viewport
-
具体三种实现过程:请看我的github仓库源码 [7-react-admin-ts]
(3) StickyFooter 粘性布局
- StickyFooter效果定义:当内容不足一屏时,footer固定在最底部,内容超出一屏时,footer在内容最底部
1. margin和padding组合
- 适用性:适合footer高度已知的情况,兼容性好
- wrap:{content,footer}
- content:
- min-height: 100%;保证内容不足一屏时,充满屏幕
- padding-bottom: footer的高度;保证footer不会遮挡content的内容
- box-sizing: border-box;保证height包括content,padding,border
- footer:
- margin-top: -fooer高度;保证footer向上移动footer高度,从而在最底部,因为content充满屏幕了,需要向上移
2. flex布局
- 适用性:对比其他方式,优点是 在footer高度不知道的情况下,也能实现sticky-footer
- container:{content,footer}
- container容器
- display: flex;
- flex-direction: column;
- min-height: 100%;
- content项目
- flex: 1; // 表示放大比例,在存在剩余空间时,放大占满剩余空间
3. calc动态计算
- 适用性:也只能适用于footer高度已知的情况
- wrap:{content, footer}
- content
- calc(wrap高度 - footer高度)
方法1
- margin pading组合
- 适用于 footer固定的情况,兼容性较好
- footer
- margin-top: -50px;
- 因为内容区设置了
- min-height: 100%;在内容不足时候,沾满整个网页
- padding-bottom: 50px; 主要是为了防止footer往上移动到可视区域底部后,内容区的内容被footer遮挡
- footer的margin-top的负值则是往上移动,正好和padding-bottom区域重合
- content
- min-height:100%,保证内容不全时,沾满整个网页
- 注意:min-height: 100%是根据父元素计算的,和父元素高度一样,所以content-wrap的所有外层元素都要设置height: 100%
- 注意清掉所有默认样式的padding和margin
- 代码
<!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>
* {
padding: 0;
margin: 0;
}
html, body {
height: 100%;
}
.wrap {
height: 100%;
}
.content {
min-height: 100%; // 内容不足占满整个内容空间
padding-bottom: 60px; // 避免content被footer覆盖而看不到内容
box-sizing: border-box; // width,height包括content,padding,border
background: blueviolet;
}
.footer {
margin-top: -60px; // 当content内容不足,占满屏幕后,margin-top则让footer显示在底部
height: 60px;
background: red;
}
</style>
</head>
<body>
<div class="wrap">
<div class="content">
<div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div>
<!-- <div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div>
<div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div> -->
</div>
<div class="footer">footer</div>
</div>
</body>
</html>
2. 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>
* {
padding: 0;
margin: 0;
}
html, body {
height: 100%;
}
.wrap {
/* height: 100%; */
min-height: 100%;
display: flex;
flex-direction: column;
}
.content {
/* min-height: 100%; */
flex: 1;
/*
padding-bottom: 60px;
box-sizing: border-box;
*/
background: yellow;
}
.footer {
/* margin-top: -60px; */
height: 60px;
background: blue;
}
</style>
</head>
<body>
// 在最层元素上设置 min-height: 100% 内容不住沾满空间
// 在最层元素上设置flex作为容器,注意flex-direction: column竖直方向为主轴
// 让content项目的flex: 1; 这样就能让cotent占满主轴的剩余空间,即除了footer以外的所有高度,主轴方向min-height: 100%
<div class="wrap">
<div class="content">
<div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div>
<div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div>
<div>content <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br></div>
</div>
<div class="footer">footer</div>
</div>
</body>
</html>
方法三
- calc属性
- calculate: 计算
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.content {
min-height: calc(100vh - 30px) // 参数是表达式,可以使用 + - * /
}
.footer {
height: 30px;
background: black;
color: white;
}
</style>
</head>
<body>
<div class="content">
content <br><br><br><br>
content <br><br><br><br>
content <br><br><br><br>
</div>
<div class="footer">
footer
</div>
</body>
</html>
(4) visibility:hidden; display:none 两者的区别
- visibility: hidden;隐藏后,会占据原来的位置
- display: none;隐藏后不会占据原来的位置
- 注意:他们都会在DOM节点中,只是通过css方式隐藏,任然在DOM树中
(5) display: inline-block存在间隙的原因和解决办法
- 原因:标签之间存在空白字符
- 解决办法一:将父元素的font-size设置为0,然后在子元素中设置自己需要的font-size大小,因为是空白字符,所以设置font-size有效
- 解决办法二:各个标签不要换行,而是仅仅贴合在一起
(6) css 画三角形
- triangle:是三角形的意思
- 等边三角形,直角三角形,带边框的三角形
css画三角形
1.宽高都设置为0,border的四边用不同颜色区分,将出现四个三角形
2.为什么会出现4个三角形,因为四个border相互遮挡了
3.#b是向上的三角形
- 三角形底边长度是border的两倍 ( border-left+border-right的长度 )
- 三角形高都就是border-bottom的值,所以改变border-bottom就能得到高度不同的三角形
4.其他方向的三角形同理
5. 实例
<!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>
.triangle { // --------------------------------- 三角形,把div宽高都设置为0;四个border就是四个三角形
width: 0;
height: 0;
border: 100px solid transparent; // ----------- 四个边设置为透明,用底边覆盖后,就显示向上的三角形,其他方向同理
border-bottom: 100px solid red; //------------- 向上三角形的底边=left+right的长度,高就是bottom的长度
}
.rectangle {
width: 200px;
height: 200px;
background: blue;
}
</style>
</head>
<body>
<div class="triangle"></div>
<div class="rectangle"></div>
</body>
</html>
6. 直角三角形 right triangle
.triangle-one-right {
display: inline-block;
width: 0;
height: 0;
border: 60px solid transparent;
border-left: 60px solid rgb(100, 185, 255); // 这样能画出直角三角形,直角在左下角
border-bottom: 60px solid rgb(100, 185, 255); // 直角边是border的两倍,即120px
}
7. 等边三角形
- 原理:显示部分的宽度 = transparent部分的宽度 * √3
- √3 ≈ 1.7321
.triangle-two-equal {
display: inline-block;
width: 0;
height: 0;
border: 34.5px solid transparent; // 等边三角形 ( 显示部分跨度 = transparent部分宽度 * √3 )
border-bottom: 60px solid rgb(100, 185, 255);
}
8. 带边框的三角形
- 原理:利用伪类三角形来做边框,原理是使两个三角形重合,然后通过不同大小颜色来覆盖区分
.triangle-two-equal {
display: inline-block;
width: 0;
height: 0;
border: 34.5px solid transparent; // 等边三角形 ( 显示部分跨度 = transparent部分宽度 * √3 )
border-bottom: 60px solid rgb(253, 0, 0);
position: relative;
// 利用伪类来做边框,原理是使两个三角形重合,然后通过不同大小颜色来覆盖区分
&::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-51%, -29%);
width: 0;
height: 0;
border: 29px solid transparent; // 等边三角形 ( 显示部分跨度 = transparent部分宽度 * √3 )
border-bottom: 50px solid rgb(100, 185, 255);
}
}
(7) 进度条
- gradient:渐变
- 实时进度条宽度( 百分比 ):(target.scrollTop/(target.scrollHeight - target.clientHeight))px;
- 滚动了的宽度:target.scrollTop
- 需要滚动的宽度:target.scrollTop
- github源码链接
(8) position: fixed 和 transform 的关系
- position:fixed
- 在一般情况下是基于 viewport 元素进行定位的
- 当祖先元素设置了 transform 属性为非none属性时,fixed定位不再基于viewport,而是居于具有非none的设置了transform属性的祖先元素
(9) 盒模型
- ( 标准盒模型 ) 和 ( ie盒模型 )
- 标准盒模型:box-sizing: content-box; -------- 宽高包括:content
- ie盒子模型:box-sizing: border-box; ---------- 宽高包括:content,padding,border
(10) 移动端一像素1px物理边框
- 利用 ( @media ),( 伪元素 ),( -webkit-min-device-pixel-ratio ),( transform: scaleY() ) 实现一像素物理边框
- 如何获取屏幕的像素比:window.devicePixelRatio
- 在web端,window.devicePixelRatio === 1
- 在移动端,window.devicePixelRatio 为 2 3 等
<!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;
}
.one-px-border {
height: 200px;
width: 200px;
position: relative;
}
.one-px-border::before { // ------------------------- 伪元素,当前元素的子元素,不在DOM中,提升效率
content: ''; // ----------------------------------- 必须设置content
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 1px; // 高度为1px
background: red;
}
@media screen and (-webkit-min-device-pixel-ratio: 2) { // --------------- 像素比是2,则缩放y方向的尺寸为0.5
.one-px-border::before {
transform: scaleY(.5); // -------------------------------------------- 2倍屏,将伪类y方向缩小0.5倍
}
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {// ---------------- 像素比是3,则缩放y方向的尺寸为0.333
.one-px-border::before {
transform: scaleY(.33333);// ----------------------------------------- 3倍屏,将伪类y方向缩小0.3333倍
}
}
</style>
</head>
<body>
<div class="one-px-border"></div>
</body>
</html>
(11) @media媒体查询
- @media媒体查询:主要运用于 ( 响应式布局 ),针对不同 ( 屏幕尺寸 ) 定义不同的 ( 样式 )
- 使用方式:可以在 ( link标签 ) 和 ( css ) 中使用
(1) @media语法
@media mediaType and|not|only (media feature) {
css code
}
1. mediaType 媒体类型
- screen:电脑,平板,手机
- print:打印机
- all:所有设备
2. mediaFeature 媒体特征
- 每条媒体特性表达式都必须用括号括起来
3. 逻辑操作符
- and
- and运算符用于将多个媒体功能组合到一个媒体查询中
- 要求每个链接功能都返回true才能使查询为true // --------------------------- 要都为tru才会为true
- 它还用于将媒体功能与媒体类型结合在一起。
- not
- only
4. ------------------------ link标签中使用 @media ------------------------
<link rel="stylesheet" media="screen and (max-width: 600px)" href="small.css" />
<link
rel="stylesheet"
media="screen and (min-width:600px) and (max-width:900px)"
href="style.css"
type="text/css"
/>
表示:屏幕宽度大于600px并且小于900px时,使用style.css文件
5. ------------------------ css中使用 @media ------------------------
@mixin bgColor($bgColor) {
.interview-media {
.media-main {
.media-main__title {
background: $bgColor;
padding: 10px 0;
}
}
}
}
@include bgColor($bgColor:#c6fd8f);
@media screen and (min-width: 1200px) { // 大于1200px的样式
@include bgColor($bgColor:#a8c9ff);
}
@media screen and (min-width: 1000px) and (max-width: 1100px) { // 1000px-1100px的样式
@include bgColor($bgColor:#ffa9ff);
}
@media screen and (max-width: 900px) { // 小于900px的样式
@include bgColor($bgColor:#ffc7c7);
}
(12) em
- em是相对单位
- em作为font-size的单位时,1em表示父元素的font-size的大小
- em作为其他属性单位时,1em表示自身font-size的大小
- em布局的缺点:
em做弹性布局的缺点在于牵一发而动全身,一旦某个节点的字体大小发生变化,那么其后代元素都得重新计算
.em {
.max-border {
font-size: 20px;
padding: 10px;
background: #fdf2ff;
.em__div1 {
background: #bbdffd;
font-size: 1em; // em作为font-size的单位时,1em表示父元素font-size的大小
}
.em__div2 {
background: #bbfdc9;
font-size: 20px;
height: 10em; // em作为其他属性的单位时,1em表示自身font-size的大小
}
}
}
(13) rem 布局
- 原理:rem是根据HTML的font-size来做基准的,1rem=HTML的font-size
物理像素 = css像素(设备独立像素) * 像素比(几倍屏)
- 单位换算:
deviceWidth/ui设计稿的总宽度 = 某元素的实际宽度/该元素ui宽度
- 最终推出:某元素的实际宽度 = deviceWidth/ui设计稿的总宽度 * 该元素ui宽度
- deviceWidth:可以通过js方式和css方式获取
- js方式:( document.documentElement.clientWidth ) 即HTML的宽度
- css方式:vw ( 注意:vw包括滚动条的宽度,所以要减去滚动条的宽度,或者禁用掉滚动条 )
- deviceWidth:可以通过js方式和css方式获取
- rem布局的步骤:
rem布局的步骤
设计稿:750px为基准的前提下
总体上三个步骤:
1. 动态设置html的font-size大小 ( 这里有两种方法来动态设置HTML的font-size大小:js方式或css方式 )
2. 将px和rem做适当的比例换算(比如让1px=1rem)
3. font-size的大小可以继承,为了避免设置html的font-size影响后代的font-size,不要在body上重新设置font-size
1和2.
动态设置html的font-size大小 ----------- (可以用js实现,也可以用css的calc方法实现)
(1) js
document.documetElement.style.fontSize = document.documetElement.clientWidth / 750 + 'px'
- 即一个等式:设备实际宽度deviceWidth / 设计稿宽度750 = 某个元素的实际宽度 / 某个元素的设计稿宽度designWidth
- 得出结果:x = deviceWidth/750 * designWidth
- 最终结果:x = 1rem * designWidth = designWidth rem
(2) css-calc()
.html {
font-size: calc(100vw / 750);
}
3. body
body {
font-size: 16px; // 浏览器默认字体大小是 16px
}
- 实例1
import React, { useState } from 'react'
import './rem.scss'
const Rem = () => {
const [links] = useState([
{
name: 'Rem布局 - 我的掘金博客',
url: 'https://juejin.cn/post/6844904090644774926#heading-34'
},
])
const handleClick = (e: React.MouseEvent) => {
document.documentElement.style.setProperty(
'font-size',
`${document.documentElement.clientWidth / 750}px` // HTML的总宽度分成750份,则1rem=设计稿上的1px
)
}
const renderLinks = () => links.map(({ name, url }) => <div key={name}><a href={url} target="blank">{name}</a></div>)
return (
<div className="rem">
<p>Rem布局</p><br />
<h1>!!!!请用两倍屏手机模式打开</h1>
<div className="max-border">
<div className="rem__test">
<div>测试rem布局</div>
<div>width: 200rem</div>
</div>
<div className="rem__test2">
<div>对比:width=100px</div>
<div>两倍屏,width=100px相当于设计稿的200px</div>
</div>
</div><br/><br/>
<button onClick={handleClick}>点击,设置HTML的font-size为:HTML.clientWidth / 750px设计稿宽度,这样1rem = 1/750px = 设计稿每个元素的宽度,1:1关系</button>
<br/>
<div>
{renderLinks()}
</div>
</div>
)
}
export default Rem
.rem {
.max-border {
.rem__test {
width: 200rem;
height: 200rem;
background: rgb(156, 255, 161);
}
.rem__test2 {
width: 100px;
height: 100px;
background: rgb(192, 188, 255);
}
}
}
- 实例2
- 如何直接写px达到实际是rem单位的效果,即在开发时还是使用px为单位
- 自己写一个pxtorem的函数,转换
- 利用第三方依赖 (postcss-pxtorem)
- 如何直接写px达到实际是rem单位的效果,即在开发时还是使用px为单位
(1)
// 定义 px 转化为 rem 的函数
@function px2rem ($px) {
@return $px + rem; // ----------- 按照上面的设置,px和rem其实就是1:1的关系了
}
.demo {
width: px2rem(100);
height: px2rem(100);
}
(2) 插件
postcss-pxtorem
地址:https://github.com/cuth/postcss-pxtorem
如果是基于webpack的项目,则可以新建postcss.config.js文件
-------------- app.vue --------------
* {
margin: 0;
padding: 0;
}
html {
font-size: calc(100vw / 750);
}
--------------- postcss.config.js ---------------
--------------- 也可以在vue.config.js中配置 ---------------
module.exports = {
plugins: {
'autoprefixer': {------------------ 解决浏览器前缀的兼容性
browsers: ['Android >= 4.0', 'iOS >= 7']
},
'postcss-pxtorem': { // ------------ 自动转换,注意该插件只是将px转成rem,还是要动态设置html的font-size大小
rootValue: 1, // 基数,即1px = 1rem
unitPrecision: 5,
propList: ['*'],
selectorBlackList: [],
replace: true,
mediaQuery: false,
minPixelValue: 0,
exclude: /node_modules/i
}
}
}
--------------- ue.config.js ---------------
module.exports = {
lintOnSave: true,
css: {
loaderOptions: {
postcss: {
plugins: [
require('postcss-pxtorem')({
rootValue : 1, // 换算的基数
selectorBlackList : ['weui','mu'], // 忽略转换正则匹配项
propList : ['*'],
}),
]
}
}
},
}
(14) 单行省略号 和 多行省略号
- ellipsis:省略号
- orient:方向
单行省略号 和 多行省略号
<!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>
.content1 { // ------------------------------------------ 单行省略号
background: yellow;
width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.content2 { // ------------------------------------------ 多行省略号
background: red;
width: 100px;
overflow: hidden;
display: -webkit-box; // ------------------------------ display: -webkit-box
-webkit-box-orient: vertical; // ---------------------- 方向
-webkit-line-clamp: 2; //------------------------------ 行数
}
</style>
</head>
<body>
<div class="content1">
单行省略号
单行省略号
单行省略号
单行省略号
单行省略号
</div>
<div class="content2">多行省略号,多行省略号,多行省略号,多行省略号</div>
</body>
</html>
(15) block inline inline-block 的区别
(line)常见的内联元素:span a
(inline-block)常见的内联块元素:input,textarea,select,img
(block)常见的块级元素: div, form, table, p, h1~h6, ul, li, ol, dl, pre 等;
1. block
- 块级元素,独占一行
- block元素可以设置宽,高等属性
- block可以设置margin,padding属性
- 默认情况下,block元素的宽度自动填满其父元素的宽度
2. inline
- 不独占一行,多个相邻行内元素排列在同一行,直到排不下才换行
- inline元素设置width和height无效
- inline元素设置margin,在水平方向有效,在垂直方向无效
- inline元素设置padding对自身有效,但是在垂直方向上不能撑开父元素
3. inline-block
- 同时具有block的特性可以设置宽度和高度属性,又具有line元素的不占一行的属性
(16) 双栏布局 - 左侧固定,右侧自适应
- float
- left:
width: 100px; float: left;
- right:
如需做任何设置
- left:
- 绝对定位
- flex布局
import React, { useState } from 'react'
import './layouts.scss'
const Layouts = () => {
const [links] = useState([ { name: '双栏布局 - 我的掘金博客', url: 'https://juejin.cn/post/6844904090644774926#heading-27' }, ])
const renderLinks = () => links.map(({ name, url }) => <div key={name}><a href={url} target="blank">{name}</a></div>)
return (
<div className="layouts">
<p>双栏布局</p><br />
{/* float */}
<div className="max-border">
<div className="float-left"></div>
<div className="float-right">
<h1>float</h1>
<h1>left: width: 100px;</h1>
<h1>left: float: left</h1>
<h1>right: 无需做任何设置</h1>
</div>
</div>
{/* absolute */}
<div className="max-border">
<div className="absolute-left"></div>
<div className="absolute-right">
<h1>绝对定位</h1>
<h1>left: width: 100px;</h1>
<h1>left: height: 100%;</h1>
<h1>right: position: absolute;</h1>
<h1>right: left: 100px;</h1>
<h1>right: top: 0; right: 0;bottom: 0;</h1>
</div>
</div>
{/* flex */}
<div className="max-borderx">
<div className="max-border-inner">
<div className="flex__left"></div>
<div className="flex__right">
<h1>flex</h1>
<h1>container: display: flex;</h1>
<h1>left: flex: 0 0 100px; width: 100px;</h1>
<h1>right: flex: 1</h1>
</div>
</div>
</div>
<div>
{renderLinks()}
</div>
</div>
)
}
export default Layouts
.layouts {
// float方式
.max-border {
.float-left {
background: #ff8800;
height: 100%;
width: 100px;
float: left;
}
.float-right {
// 右边无需做任何设置
background: #ffefb8;
height: 100%;
}
}
// 绝对定位方式
.max-border {
position: relative;
.absolute-left {
background: #ff8800;
width: 100px;
height: 100%;
}
.absolute-right {
background: #ffefb8;
position: absolute;
left: 100px;
top: 0;
right: 0;
bottom: 0;
}
}
// flex
.max-borderx {
width: 360px;
height: 360px;
display: inline-block;
border: 1px solid rgb(201, 201, 201);
.max-border-inner {
width: 100%;
height: 100%;
display: flex;
.flex__left {
background: #ff8800;
flex: 0 0 100px;
}
.flex__right {
background: #ffefb8;
flex: 1;
}
}
}
}
(17) 三栏布局 - 圣杯布局
- flex
- 绝对定位
- 浮动
圣杯布局 - 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;
}
html, body {
width: 100%;
height: 100%;
}
.container {
width: 100%;
height: 100%;
display: flex;
justify-content: flex-start;
align-items: flex-start;
}
.left {
height: 100%;
background: red;
flex: 0 0 200px; // flex: 0 0 200px;
}
.center {
height: 100%;
background: blue;
flex: 1; // flex: 1;
}
.right {
height: 100%;
background: yellow;
flex: 0 0 200px; // flex: 0 0 200px;
}
</style>
</head>
<body>
<div class="container">
<div class="left">left</div>
<div class="center">center</div>
<div class="right">right</div>
</div>
</body>
</html>
圣杯布局 - 浮动
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="main"></div> ## main放最后
</div>
.left {
float: left;
width: 100px;
}
.right {
width: 200px;
float: right;
}
.main {
margin-left: 120px;
margin-right: 220px;
}
## 或者 触发bfc,BFC区域,不会与浮动元素重叠
.main {
overflow: hidden;
}
(18) BFC
- BFC ( Block Formatting Context )
块级格式化上下文
,是一种特性- BFC指页面的一块渲染区域,并有一套渲染规则,它决定了其子元素如何定位,以及和其他元素的关系和相互作用
- 具有BFC元素特性的元素,可以看作是隔离了的独立元素,容器里的元素不会在布局上影响其他元素
- 如何触发BFC特性
- 根元素
- 浮动 flot
- 绝对定位 position: absolute 和 fixed
- display 为 inline-block、table-cells、flex
- overflow 除了 visible 以外的值( hidden, auto, scroll )
- BFC布局规则
- margin折叠
- 属于同一个BFC的两个相邻块级元素会发生margin折叠
- 独立容器
- 容器里面的元素不会相应到元素外面的元素
- margin折叠
- BFC的运用
去除margin重叠
清除浮动,解决浮动引起父元素高度塌陷
- 利用BFC清除浮动的原理
- 具有BFC特性的元素在计算高度时,包含所有子元素 ( 包括浮动元素 )
- 所以能解决 ( 浮动 ) 造成的 ( 父元素高度塌陷 ) 的问题
- 为什么要清楚浮动
- 原因:浮动元素脱离文档流后,造成父元素高度塌陷 ( 在父元素没有设置高度时 )
- 解决办法:触发BFC特性,或者在浮动元素的兄弟元素使用 clear 属性
(19) 清除浮动
- 清除浮动的方法 - 解决父元素高度塌陷
(1) overflow: hidden
- 方法:在浮动元素的父元素上设置overflow: hidden
- 原理:触发BFC,具有BFC特性的元素在计算高度时,会把所有子元素计算在内,包括浮动的元素,则不会高度塌陷
所以:overflow: hidden scroll auto都可以,只要不是 ( overflow: visible ) 都可以
- 哪些属性可以触发BFC,即都可以用来清除浮动
- 根元素
- 浮动
- 绝对定位:absolute fixed
- overflow: hidden/auto/scroll, 不是visible就行
- display: inline-block/flex/table-cell
(2) 伪元素 ( ::before ) ( ::after )
- 方法:给浮动元素的父元素,添加伪元素
- 原理:
- 1.伪元素是当前元素的子元素,再给伪元素设置 clear 属性,注意 clear 属性只适用于块级元素
- 注意区分伪元素和伪类
- 伪元素:是一个元素 ::after ::before
- 伪类:是一个class类 :hover :link ...
- 注意区分伪元素和伪类
- 2.给浮动元素的父元素添加伪元素,然后设置clear:both,相当于浮动元素的兄弟元素设置clear属性
- 1.伪元素是当前元素的子元素,再给伪元素设置 clear 属性,注意 clear 属性只适用于块级元素
(3) 给浮动元素的父元素插入一个子元素,比如span,在设置clear属性,display要为block
- 和添加伪元素是一个道理,用子元素去撑开父元素的高度
(4) 把浮动元素的父元素也设置浮动,从而触发BFC (重复了,但是容易忘记写上把)
<!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>
.father {
background: yellow;
padding: 30px;
position: relative;
/* float: left; */
/* position: absolute; */
/* overflow: hidden; */
/* display: inline-block; */
}
.father::before {
content: '.';
height: 100PX;
width: 100PX;
position: absolute;
top: 0;
left: 0;
display: block;
background: greenyellow;
clear: both;
}
.brother {
height: 200px;
width: 200px;
}
.child {
height: 200px;
width: 200px;
background: red;
float: left;
}
</style>
</head>
<body>
<div class="father">
<div class="child">child</div>
<div class="brother"></div>
</div>
</body>
</html>
(20) margin重叠 ( margin-collapse )
- 当相邻两个盒子( 父子或者兄弟 )的外边距结合成一个单独的外边距,就是margin重叠
- 水平方向上的margin不会重叠
1. 重叠的规则 ( 同一个BFC下 )
- 相邻的两个元素的 ( 垂直方向 ) margin是 ( 正数 ) 时,重叠值是两个中的 ( 较大值 )
- 相邻的两个元素的 ( 垂直方向 ) margin是 ( 负数 ) 时,重叠值是 ( 绝对值较大 ) 的值
- 相邻的两个元素的 ( 垂直方向 ) margin ( 一正一负时 ),重叠值是两者 ( 相加 ) 值
2. 如何解决同一个BFC下,上下margin重叠
- 1两个上下相邻的元素使用同一个方向的margin
- 2使用padding代替margin
- 3使两个元素在不同的BFC下
- 根元素
- 浮动:float
- 绝对定位:position: absolute fixed
- display: inline-block, flex, table-cell
- overflow不是visiable ( overflow: hidden, scroll, auto )
3. 利用上下两个元素在不同的BFC下-解决margin-overlap
2022/03/26
1. 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>
.up {
margin-bottom: 50px;
background: red;
}
.down {
margin-top: 50px;
background: yellow;
overflow: hidden;
}
</style>
</head>
<body>
<div class="up">上</div>
<div class="down">下</div>
</body>
</html>
2. 解决margin重叠
- 利用BFC解决margin重叠 - 是上下两个元素在不同的BFC下
- 流程
- 给down添加一个父元素
- 给down的父元素触发BFC (根元素,浮动,绝对定位,overflow,display)
---
<!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>
.up {
margin-bottom: 50px;
background: red;
}
.down-wrap {
overflow: hidden;
}
.down {
margin-top: 50px;
background: yellow;
overflow: hidden;
}
</style>
</head>
<body>
<!-- 解决margin上下重叠 -->
<div class="up">上</div>
<div class="down-wrap">
<div class="down">下</div>
</div>
</body>
</html>
(21) 多列等高布局
- float + pading,margin抵消
- table-ceil布局
- flex布局
.column-equal {
// flex方式多列等高
.max-border-one {
.max-border-flex {
display: flex;
flex-flow: row nowrap;
}
& > div {
width: 25%;
background: #f6ffed;
}
}
// float方式,padding+margin多列等高
.max-border-two {
.max-border-float {
overflow: hidden;
& > div {
float: left;
width: 25%;
background: #f6ffed;
padding-bottom: 999px;
margin-bottom: -999px;
}
}
}
// table-ceil布局
.max-border-three {
.max-border-table {
display: table;
& > div {
display: table-cell;
width: 25%;
background: #f6ffed;
}
}
}
}
(22) 水平垂直居中
- table-ceil布局
- 父元素:display: table-cell;
- 父元素:text-align: center;
- 父元素:vertical-align: middle;
- 子元素:display: inline-block;
- 绝对定位
- 父元素:position: relative;
- 子元素:position: absolute;
- 子元素(不知道宽高):top: 50%; left: 50%; transform: translate(-50%, -50%);
- 子元素(知道宽高):top: 50%; left: 50%; margin: -halfHeight -halfWidth;
- grid布局
- father: display: grid;
- child: justify-self: center;
- child: align-self: center
- flex布局
项目源码
资料
React-diff算法1-知乎 ( 概念部分讲得好 ) zhuanlan.zhihu.com/p/103187276
React-diff算法2-简书 ( elementDiff移动部分讲得好 ) www.jianshu.com/p/3ba082201…
css-interview我的掘金文章 juejin.cn/post/684490…
margin重叠 juejin.cn/post/684490…
三角形的各种画法 juejin.cn/post/684490…
函数式编程我的掘金文章 juejin.cn/post/684490…