[复习笔记-01] 01

475 阅读36分钟

(一) 前置知识

(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,导致除了第一个元素外,所有元素都会移动

(三) 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对象 )
  • 优化阶段2中的不足:
      1. 如果少调用一次没有参数的curryAdd:通过直接传入add函数(而不是在内部定义)的add.length获取形参的长度,当收集的参数长度大于等于add.length时就去调用
      1. 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 ) 的 ( 一部分参数或者全部参数 )
      • 返回一个 ( 新的函数 ),返回的新函数可以继续 ( 接受剩余参数 )
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运算符 ) 可以检查某个对象是否包含某个属性,包括 ( 自身属性 ) 和 ( 继承属性 )
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函数
      • 依次处理数组的每个成员,最终累积成一个值
  • 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(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执行实例
        • 但是传入的参数有效
  • 关键词
  • 我的掘金博客 - 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调用?
        1. 使用严格模式
        1. 使用 ( 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; // 避免contentfooter覆盖而看不到内容
      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.宽高都设置为0border的四边用不同颜色区分,将出现四个三角形
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
- √31.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包括滚动条的宽度,所以要减去滚动条的宽度,或者禁用掉滚动条 )
  • rem布局的步骤:
rem布局的步骤

设计稿:750px为基准的前提下

总体上三个步骤:
1. 动态设置htmlfont-size大小 ( 这里有两种方法来动态设置HTMLfont-size大小:js方式或css方式 )
2. 将px和rem做适当的比例换算(比如让1px=1rem3. font-size的大小可以继承,为了避免设置htmlfont-size影响后代的font-size,不要在body上重新设置font-size



12. 
动态设置htmlfont-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)
(1) 
// 定义 px 转化为 rem 的函数
@function px2rem ($px) {
    @return $px + rem; // ----------- 按照上面的设置,px和rem其实就是11的关系了
}
.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,还是要动态设置htmlfont-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)常见的内联块元素:inputtextarea,select,img
(block)常见的块级元素: div, form, table, p, h1~h6, ul, li, ol, dl, pre 等;


1. block
  - 块级元素,独占一行
  - block元素可以设置宽,高等属性
  - block可以设置marginpadding属性
  - 默认情况下,block元素的宽度自动填满其父元素的宽度

2. inline
  - 不独占一行,多个相邻行内元素排列在同一行,直到排不下才换行
  - inline元素设置widthheight无效
  - inline元素设置margin,在水平方向有效,在垂直方向无效
  - inline元素设置padding对自身有效,但是在垂直方向上不能撑开父元素

3. inline-block
  - 同时具有block的特性可以设置宽度和高度属性,又具有line元素的不占一行的属性

(16) 双栏布局 - 左侧固定,右侧自适应

  • float
    • left:width: 100px; float: left;
    • right: 如需做任何设置
  • 绝对定位
  • 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折叠
    • 独立容器
      • 容器里面的元素不会相应到元素外面的元素
  • 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属性

(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…