记录每日一题的学习整理,避免学了又忘记 = 白学
10.每日一题——事件循环
由于JavaScript是单线程的,在执行代码时,通过将不同函数的执行上下文压入栈中来保证代码的有序执行。在执行同步代码时,如若遇到异步的代码,js引擎不会一直等待其返回结果,还是将其挂起,继续执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行,任务队列分为宏任务(marco-task)和微任务(micro-task)。
JavaScript的运行环境主要有两个:浏览器,Node
浏览器的事件循环
- 宏任务(macro-task)包括:
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI render
- 微任务(micro-task)包括:
- Promise的回调
- async/await(实际上也是Promise)
- process.nextTick
- 对Dom变化监听的MutationObserver
总结:执行宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行这个新产生的微任务,执行完毕后再回到宏任务进行下一轮的循环,代码案例如下:
console.log('start')
async function async1() {
console.log('async1 start')
await async2();
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(() => {
console.log('setTimeout')
})
new Promise((resolve)=> {
console.log('Promise')
resolve()
}).then(() => {
console.log('promise1')
}).then(() => {
console.log('promise2')
})
console.log('end')
//执行结果顺序为:start -> async1 start -> async2 end -> Promise -> end -> promise1 -> promise2 -> async1 end -> setTimeout
分析代码:
- 首先我们执行console.log('start')
- 然后我们执行函数async1(),打印async1 start
- 遇到await,则执行async2这个异步操作,并等待结果返回,打印async2 end,此时保留async1的执行上下文,调出async1函数
- 遇到setTimeout,则产生一个宏任务
- 执行Promise,new Promise会直接执行(PS:这里的new操作并不会产生一个微任务),打印Promise
- Promise调用then(),产生第一个微任务
- 继续执行代码,打印end
- 代码逻辑执行完毕,开始执行宏任务产生的微任务队列,打印promise1,再次调用then(),产生一个新的微任务
- 执行产生的微任务,打印promise2,执行回到了async1的执行上下文中
- 由于await回执行返回一个Promise,执行完之后,打印async1 end
- 最后执行下一个宏任务,即执行setTimeout,打印setTimeout
注意:
新版的chrome浏览器不是这样打印的,因为chrome优化了await,让await变得更快了,输出为:
// start -> async1 start -> async2 end -> Promise -> end -> async1 end -> promise1 -> promise2 -> setTimeout
这种做法其实是违反了规范的,当然规范是可以更改的,这是V8团队的一个PR,目前新版本的打印已经修改
(1)如果await后面直接跟一个变量,相当于把await后面的代码变成一个微任务,可以简单理解为Promise.then(await下面的代码),然后跳出async1函数执行其他的代码,当遇到Promise函数的时候,会产生新的微任务,会将这个微任务压入到微任务队列中,这个时候await产生的微任务已经在里面了,这个时候会先执行await后面的代码(async1 end),再执行async1函数后面的微任务代码(promise1,promise2)
node的事件循环
事件循环是node处理非阻塞I/O操作的机制,node中事件循环的实现是依靠的libuv引擎。libuv使用异步,事件驱动的编程方式,核心是提供I/O的事件循环和异步回调
PS:(由于node11之后,事件循环的一些原理发生了变化,所以这里以新的标准来解释)
- 宏任务(macro-task)包括:
- setTimeout
- setInterval
- setImmediate
- script(整体代码)
- I/O操作
2.微任务(micro)包括:
- process.nextTick(与普通任务有区别,在微任务队列执行之前执行)
- new Promise().then()回调等
node事件循环整体理解
node的事件循环阶段顺序为:
输入数据阶段(incoming data) -> 轮询阶段(poll) -> 检查阶段(check) -> 关闭事件回调阶段(close callback) -> 定时器检测阶段(timers) -> I/O事件回调阶段(I/O callback) -> 闲置阶段(idle,prepare) -> 轮询阶段...
Node的event loop 一共分为6个阶段
- 定时器检测阶段(timers):执行setTimeout和setInterval中到期的callback
- I/O事件回调阶段(pending callback):上一轮循环中少数的callback会放在这一阶段执行
- 闲置阶段(idle,prepare):仅在内部使用
- 轮询阶段(poll):最重要的阶段,执行pending callback,在适当的情况下
- 检查阶段(check):执行setImmediate的callback
- 关闭事件回调阶段(close callback):一些关闭的回调函数
我们日常的绝大部分异步任务都是在poll,check,timers这三个阶段处理
- timers
timers阶段会执行setTimeout和setInterval回调,并且是有poll阶段控制的。ps(在node中的定时器也不是准确时间,只能是尽快执行)
- poll
如果当前有定时器,并且定时器到时间了,拿出来执行,eventloop将回到timers阶段。
如果没有定时器,会回到函数队列
-
如果poll不为空,则会遍历队列并同步执行,知道队列为空或达到限制
-
如果pull为空,则会发生两舰事件
(1)如果有setImmediate回调需要执行,poll阶段会停止进入到check阶段执行回调
(2)如果没有setImmediate回调需要执行,会等待回调被加入到队列并立即执行回调,这里会有一个超时时间设置,防止一直等待下去,一段时间后自动进入check阶段
- check
check阶段,用来执行setImmediate的回调
process.nextTick
这个函数其实是独立于event loop之外的,它有一个自己的队列,当每个eventloop阶段完成后就会去检查nextTick队列,如果存在nextTick,就会清空队列中的所有回调函数,并优先于其他的微任务执行
如果是node11版本一旦执行一个阶段里面的一个宏任务(setTimeout,setInterval和setImmediate)就会立刻执行对应的微任务队列
node和浏览器的eventLoop区别
两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。
9.每日一题——对async/await的理解
在异步编程的设计中,我们从回调函数引入了Promise来处理我们的网络操作请求,让代码变得井然有序,但是如果遇到嵌套回调,则会使代码变得臃肿,同时如果下一个Promise的请求参数需要上一个Promise的返回值,那么一层层嵌套下去也便于维护,这个时候我们就开始使用async/await的方法。
async是异步的简写,而await可以理解为 async wait的简写,所以可以很好的理解async用于声明一个function是异步的,而await则是等待这个异步方法执行完成。
async function test() {
return 'test'
}
let result = test()
console.log(result)
//Promise {<fulfilled>: 'test'}
从这段代码可以看出async将我们的return返回值变成了一个Promise,async会把这个返回值通过Promise.resolve()封装成Promise对象
result().then(resp => {
console.log(resp) //test
})
这样我们就可以正常的通过then()来获取function的return返回值
如果async函数没有返回值,那么相当于返回了Promise.resolve(undefined)
await等待的是一个表达式,这个表达式的计算结果是Promise对象或其他值(没有特殊限制),它等的是一个返回值,可以等任意表达式的结果
function getName() {
return 'chen'
}
async function getAsync() {
return 'hello async'
}
async function test() {
const p1 = await getName()
const p2 = await getAsync()
console.log(p1,p2)
}
test() //打印 'chen' 'hello async'
如果它等待的是一个Promise对象,await会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。等触发的异步操作完成后,再执行函数体内后面的语句。await只能在async函数内部使用
Tips:
- await后面的Promise运行结果可能为rejected,最后好await放入try{}catch{}中
- await后的异步操作,如果彼此没有依赖关系最好同时触发,这种场景建议直接用Promise.all
- await只能在async函数之中,如果在普通函数中会报错
8.每日一题——原型与原型链
在JavaScript中,万物皆对象。对象有两种,分为普通对象和函数对象,Object,Function等是JavaScript自带的函数对象
let a1 = {}
let a2 = new Object()
let a3 = new b1()
function b1() {}
var b2 = function(){}
var b3 = new Function()
console.log(typeof Object) //function
console.log(typeof Function) //function
console.log(typeof a1) //object
console.log(typeof a2) //object
console.log(typeof a3) //object
console.log(typeof b1) //function
console.log(typeof b2) //function
console.log()typeof b3 //function
通过new Function创建的都是函数对象
function Person(name) {
this.name = name;
}
var person1 = new Person()
person1是Person的实例,实例具有constructor(构造函数)属性,该属性指向Person
即:
person1.constructor === Person //true
实例的构造函数(constructor)指向构造函数
每个对象都有__proto__属性,但只有函数对象才有prototype属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法,当使用构造函数创建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的prototype属性对应的值
person1.__proto__ === Person.prototype //true
Person.prototype.constructor === Person //true
加入constructor属性,关系图如下:
原型对象是通过Object构造函数生成,实例的__proto__指向构造函数的prototype,所以可以得出如下图:
每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么假如我们让原型对象等于另一个类型的实例,结果会怎么样?显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含一个指向构造函数的指针,如此层层递进,就构成了实例与原型的链条,这就是所谓的原型链的基本概念。
Object.prototype.__proto__ === null //true
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
7.每日一题——Promise的理解
Promise是一种对函数异步处理的解决方法,从语法讲Promise是一个对象,从它可以获取异步操作的消息;从本意讲,它是承诺,承诺它过一段时间会返回一个结果。由于JavaScript是单线程执行的,为了避免阻塞,所有所有的网络请求都是异步的,在Promise没有出现的时候,都是通过回调函数的方法来完成异步操作,由于业务场景的不同,有可能会出现回调函数的嵌套,也就是等待一个异步请求执行完成之后继续执行异步请求,这样会导致层级太深,代码的可读性,可维护性,健壮性等多出现不太友好的情况,所有Promise的出现能够很好的解决这个问题,避免了回调地狱。
1.Promise实例有三个状态:
- Pending(进行中)
- Resolved(已成功)
- Rejected(已失败)
let promise = new Promise((resolve,reject) => {
if(/*异步操作成功*/) {
resolve(value)
}else{
reject(error)
}
})
resolve:在异步操作成功时调用,并将异步操作的结果作为参数传递进去;
reject:在异步操作失败时调用,并将异步操作报出的操作,作为参数传递进去
2.Promise的实例有两个过程:
- Pending -> Fulfilled:Resolved(已完成)
- Pending -> rejected:Rejected(已失败)
注意:一旦从进行状态变成为其他状态就永远不能更改状态了。
3.Promise的特点
-
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已完成)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他的操作都无法改变这个状态,这也是Promise这个名字的由来——“承诺”;
-
一旦状态改变就不会再变,任何时候都可以得到这个结果。如果你错过了它,再去监听是得不到结果的。
4.Promise的缺点
- 无法取消Promise,一旦建议它就会立刻执行,无法中途取消;
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部;
- 当处于pending状态时,无法得知目前进展到哪一个阶段。
一般情况用new Promise来创建Promise对象,但是也可以用Promise.resolve和Promise.reject这两个方法。
Promise.resolve
Promise.resolve(true).then(res=>{
console.log(res) //打印true
})
Promies.reject
Promise.reject(new Error('发生错误!'));
5.Promise的then、catch、finally方法
promise.then((res) => {
console.log(res) //打印的内容为resolve传递的参数
}).catch((err) => {
console.log(err) //打印的内容为reject传递的参数
}).finally(() => {
// 无论Promise的结果状态为fulfilled还是rejected,都会去执行,finally的本质是then的特里
console.log('...')
})
6.Promise的all和race的区别
Promise.all可以将多个Promise实例包装成一个新的Promise实例,成功的时候返回的是一个数组,而失败的时候则返回最先被reject失败状态的值
let p1 = new Promise((resolve,reject) => {
resolve(‘成功’)
})
let p2 = new Promise((resolve,reject) => {
resolve('success')
})
let p3 = Promise.reject('失败')
Promise.all([p1,p2]).then(res => {
console.log(res) // [ '成功', 'success' ]
})
Promise.all([p1,p2,p3]).then(res => {
console.log(res) // [ '成功', 'success' ]
}).catch(err => {
console.log(err) // 失败
})
Promise.race就是赛跑的意思,意思就是说,Promise.race([p1,p2,p3])里面哪个执行结果返回的最快,就返回那个结果,不管结果本身是成功状态还是失败状态
let p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('success')
},1000)
})
let p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('fail')
},500)
})
Promise.race([p1,p2]).catch(res => {
console.log(res) //打印出fail
})
6.每日一题——闭包的理解
闭包是指有权访问到另一个函数作用域内的变量的函数,创建闭包的方法就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
1.作用域
作用域是一套规则,用于确定在何处以及如何查找变量
(1)全局作用域
- 最外层函数和最外层函数外面定义的变量拥有全局作用域
- 所有未定义直接赋值的变量为全局作用域
- 所有window对象的属性拥有全局作用域
- 全局作用域有很多的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突
(2)函数作用域
- 函数作用域在函数内部的变量,一般只有固定的代码片段可以访问到
- 作用域是分层的,内层作用域可以访问到外层作用域,反过来则不行
(3)块级作用域
- 使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中创建
- let和const声明的变量不会有变量提升,也不可以重复声明
- 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部
2.闭包的原理
参考如下代码:
function add() {
var sum = 5;
var func = function() {
console.log(sum)
}
return func;
}
var addFunc = add();
addFunc(); //5
js执行流进入全局执行上下文环境时,全局执行上下文可表示为:
globalContext = {
VO:{
add:<reference to function>,
addFunc: undefined
},
this:window,
scope chain: window
}
当add函数被调用时,add函数执行上下文可表示为:
addContext = {
AO: {
sum:undefined //代码进入执行阶段时此处被赋值5
func:undefined //代码进入执行阶段时此处被赋值 function() { console.log(sum); }
},
this: window,
scope chain: addContext.AO + gloabalContext.VO
}
add函数执行完毕后,js执行流回到全局上下文环境中,将add的函数返回值赋值给addFunc。
由于addFunc仍保存着func函数的引用,所以add函数执行上下文从执行上下文栈顶端弹出后并未被销毁而是保存在内存中。
JavaScript允许使用内部函数,即函数定义和函数表达式位于另一个函数的函数体内,而且这些内部函数可以访问到它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数,当其中一个这样的函数在包含他们的外部函数之外被调用时,就会形成闭包。
闭包有两个常用的用途:
- 我们可以在函数外部访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
- 可以使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收
闭包的缺点:
- 闭包将函数的活动对象维持在内存中,过度使用闭包会导致内存占用过多,所以在使用完后需要将保存的内存中的活动对象接触引用;
- 闭包只能取得外部函数中任何变量的最后一个值,在使用循环且返回的函数中带有循环变量时会得到错误结果;
- 当返回函数为匿名函数时,注意匿名函数中的this值的是window对象。
5.每日一题——执行上下文
执行上下文,可以理解为当前代码执行的环境,包括当前执行环境中的变量,函数声明,参数(arguments),作用域链,this等信息。
1.执行上下文主要是三种类型:
-
1.全局执行上下文 所有不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this等于这个全局对象,一程序中只有一个全局执行上下文。
-
2.函数执行上下文
所有在函数内部的都是函数执行上下文,每次函数执行的时候,都会创建一个函数执行上下文
- 3.eval执行上下文 执行在eval函数中的代码又属于自己的执行上下文,因为不常用,忽略(主要还是我不会,有时间查查资料再整理一下)
2.执行上下文的三个属性
-
1.变量对象(variable object, VO),每个执行环境都有与之关联的变量对象,环境中所有定义的变量和函数都存放在这个对象中。
-
作用域链(scope chain),当代码在执行中,会创建一个变量对象的一个作用域链,用途是保证对执行环境中所有变量和函数有序的访问权限
-
this,谁调用这个函数,this就指向谁,指向上下文的指向
3.执行上下文的生命周期
- 1.创建阶段
(1)创建变量对象:
1.初始化函数的arguments
2.函数声明
3.变量声明
(2)创建作用域链
函数的作用域链在函数定义的时候就创建好了,作用域链包括变量对象,在查找变量对象时,会从当前的上下文中的变量对象开始找,如果没有找到则从父级的执行上下文开始找,一直找到全局执行上下文中的变量对象
(3)确定this的指向
在全局执行上下文中,this指向window 在函数执行上下文中,this指向取决于函数如何调用,如果被一个引用对象调用,那么this指向这个对象,否则this则指向全局对象或undefined,通俗讲就是谁调用这个函数this就指向谁
- 2.执行阶段
包括变量赋值,函数的引用,以及执行其他代码
- 3.回收阶段
执行上下文出栈被垃圾回收机制进行回收
4.执行上下文栈
JavaScript使用执行上下文栈来管理执行上下文,当JavaScript执行代码时,会先创建全局执行上下文并且压入执行栈中,每当遇到一个函数调用时,会创建一个函数执行上下文压入栈顶,引擎会执行执行上下文栈顶的函数,当函数完成后,则从执行上下文栈中弹出,被回收
function foo() {
console.log('foo');
bar();
}
function bar() {
console.log('bar');
}
foo();
4.每日一题——var,let,const的理解
var,let,const我们知道是用来声明变量的关键字,let和const是ES6新增的。
- var具有变量提升的机制
a = 10;
var a;
console.log(a); // 10
以上代码输出结果为10,并非undefined,这里我们了解到引擎在解释JavaScript的时候会对其进行编译,找到所有的声明,并用合适的作用域将其关联起来,所以包含变量和函数在内的所有声明被执行前先被处理,var声明的变量得到了提升,只有声明本身会被提升,而赋值以及其他运行逻辑会留在原地。
PS:函数声明和变量声明都会被提升,但是函数会优先
var重复的声明,后面的会覆盖前面的
foo();
var foo;
function foo() {
console.log(1)
}
foo = function() {
console.log(2)
}
//执行结果为 1
在这段代码里,我们根据var的特性,可以根据执行顺序重新整理一下
function foo() {
console.log(1)
}
var foo
foo()
foo = function() {
console.log(2)
}
//函数声明会优先于变量声明,所以会执行foo(),输入1
let和const声明的变量在块级作用域内
console.log(a);
let a = 10;
// 运行结果:ReferenceError: a is not defined
console.log(b);
let b = 10;
// 运行结果:ReferenceError: b is not defined
由此可见,let和const都不会出现变量提升
- let声明的变量可以修改
- const声明的变量为常量,const实际保证的,并不是变量不可修改,而是变量指向的那个内存地址不可修改,例如const声明的是object,实际上object上的属性是可以修改的。
3.每日一题——this以及call,apply,bind的实现区别
this是执行上下文的一个属性,它指向最后一次调用这个方法的对象,在实际开发中,this的指向可以通过四种调用模式来判断
- 1.第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数调用时,this指向全局对象(window)
- 第二种是方法调用模式,如果一个函数是作为对象的一个方法调用时,this指向这个对象
- 第三种是构造器调用模式,如果一个函数用new调用时,函数执行前会创建一个对象,this指向这个新创建的实例
- 第四种是call,apply,bind调用的模式,通过call,apply,bind方法可以改变this的指向,其中call方法接收的参数第一个是this绑定的对象,其余是传入函数执行的参数,apply方法和call方法的区别在于,接收的第二个参数是数组,所有函数执行的参数存放在数组中,call和apply在改变this指向的同时,还会将函数调用一次,而bind不会调用,bind方法通过传入一个对象,返回一个this绑定了传入对象的函数,这个函数的this指向除了new时会被改变,其余情况下都不会改变。
这四种方法,使用构造器调用模式的优先级最高,其次是apply,call,bind调用模式,然后是方法调用模式,最后是函数调用模式
手写call,apply,bind代码如下:
Function.protptype.myCall = function(context) {
//判断调用的对象是否为function
if(typeof this !== 'function') {
throw error('type error');
}
//判断是否传入context,否则指定为window
if(!context || context === null) {
context = window;
}
let args = [...arguments].slice(1);
let result = null;
//将调用函数设置为对象的方法
context.fn = this;
//调用函数
result = context.fn(...args);
//将属性删除
delete context.fn;
return result;
}
//apply和call的区别在于调用方法传入的第二个参数为数组,执行函数传入的参数存放在数组中
Function.prototype.myApply = function(context) {
if(typeof this !== 'function') {
throw error('type error');
}
if(!context || context === null) {
context = window;
}
let result = null;
context.fn = this;
if(arguments[1]) {
result = context.fn(...arguments[1]);
}else {
result = context.fn()
}
delete context.fn;
return result;
}
Function.prototyoe.myBind = function(context) {
if(typeof this !== 'function') {
throw error('type error');
}
if(!context || context === null) {
context = window;
}
let args = [...arguments].slice(1)
fn = this;
return function Fn() {
//根据调用方式,传入不同绑定值
return fn.apply(
this instanceOf fn ? this: context,
args.concat(...arguments)
)
}
}
2.每日一题——实现垂直居中的方法
<html>
<head>
<style>
.box{
/* ... */
}
.children-box{
/* ... */
}
</style>
</head>
<body>
<div class="box">
<div class="children-box"></div>
</div>
</body>
定宽高
- 1.绝对定位+负margin值
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
}
.children-box{
width:200px;
height:200px;
background:green;
position:absolute;
left:50%;
top:50%;
margin-left:-100px;
margin-top:-100px;
}
</style>
</html>
- 2.绝对定位+transform
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
}
.children-box{
width:200px;
height:200px;
background:green;
position:absolute;
left:50%;
top:50%;
transform:translate(-50%, -50%);
}
</style>
</html>
- 3.绝对定位+ left/top/right/bottom + margin
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
}
.children-box{
width:200px;
height:200px;
background:green;
position:absolute;
left:0;
top:0;
right:0;
bottom:0;
margin:auto;
}
</style>
</html>
- 4.flex布局
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
display:flex;
justify-content: center;
align-items: center;
}
.children-box{
width:200px;
height:200px;
background:green;
}
</style>
</html>
-
- grid + margin布局
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
display:grid;
}
.children-box{
width:200px;
height:200px;
background:green;
margin:auto;
}
</style>
</html>
-
- table-cell + vertical-align + inline-block/margin: auto
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
display:table-cell;
vertical-align:middle;
text-align:center;/* 如果children-box设置inline-block,则需要设置text-align:center */
}
.children-box{
width:200px;
height:200px;
background:green;
margin:auto;
display:inline-block; /* 如果children-box设置inline-block,则可以不设置margin:auto */
}
</style>
</html>
不定宽高
- 1.绝对定位 + transform
代码同上
-
- table-cell布局
代码同上
- 3.flex布局
代码同上
- 4.flex变异布局
代码同上有差异:box设置display为flex,不设置justify-content和align-items值,children-box设置margin:auto
- 5.grid+flex布局
<html>
<style>
.box{
width:500px;
height:500px;
border:1px solid red;
position:relative;
display:grid;
}
.children-box{
width:50%;
height:50%;
background:green;
justify-self:center;
align-self:center;
}
</style>
</html>
- 6.grid + margin布局
代码同上
- writing-mode属性布局
<html>
<style>
.box{
width: 200px;
height: 200px;
border: 1px solid red;
writing-mode: vertical-lr;
text-align: center;
}
.children-box{
width:50%;
height:50%;
background:green;
writing-mode: horizontal-tb;
display: inline-block;
text-align: center;
}
</style>
</html>
代码居然没效果,暂未找到原因,先记录一下该方法
writing-mode 属性定义了文本在水平或垂直方向上如何排布。 兼容性上还有些小瑕疵,但大部分浏览器已经支持。
特例:伪类 after和before也能实现
1.每日一题——BFC
今日整理BFC(Block Formatting Context)相关的知识。
1.什么是BFC
BFC,俗称块级格式化上下文,是独立的渲染区域,内部不受其他外界的干扰,并且不会干扰外界
2.BFC的布局规则
- 内部的Box会在垂直方向,一个接一个的放置
- Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠
- 每个盒子的margin box的左边,与包含border box的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此
- BFC的区域不会与float box重叠
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响外面的元素,反之也如此
- 计算BFC的高度时,浮动元素也参与计算
3.如何触发BFC
- float不为none
- position为absolute或fixed
- overflow不为visible
- display为 inline-block或table-cell,flex,inline-flex,table-caption,grid
4.BFC的应用场景
- 清除浮动:BFC内部的浮动会参与高度计算,因此可用于清除浮动
- 避免某元素被浮动元素覆盖:BFC的区域不会与浮动元素的区域重叠
- 阻止外边距重叠:属于同一个BFC的两个相邻Box的margin会重叠,不同BFC不会发生折叠