前端面试题自检 JS CSS 部分

说在前面

限于这是个自查手册,回答不那么太详细,若是某个知识点下面有连接,本身又没有深刻了解过的,理应点击连接或自行搜索深刻了解。javascript

另外两个部分 :css

JS

类型

JavaScript的简单数据类型

Number , String , Boolean , Undefined , Null , Symbolhtml

typeof 操做符的返回值

  • number
  • string
  • boolean
  • undefined
  • object
  • function
  • symbol

typeof NaN 返回 number前端

typeof null 时也返回 object ,此为历史遗留问题vue

为何数字和字符串可使用方法?

由于数字和字符串在使用方法时会转换为包装对象java

包装对象就是其对应的构造函数创造出来的node

(1).toString() // (new Number(1)).toString()
复制代码

new Number(1) { __proto__: Number , [[PrimitiveValue]] : 1 }react

包装对象上会有一个内部值 [[PrimitiveValue]],值为被包装的原始值git

这个对象在表达式结束后就会被销毁,因此没法给字符串/数字上添加属性es6

0.1 + 0.2 === 0.3 // false ?

0.1 + 0.2 != 0.3背后的原理

  • JS 采用 IEEE 754双精度64位存储数据,因此并不是JS独有这个问题,采用了这个规范的语言都有

  • 64位 :1位符号位,11位指数位,52位尾数位

    JS能表示最大的整数是 2^53 - 1(52个二进制 1 ),而不是 2^52 -1

  • 0.1 和 0.2 在二进制中表现为无限循环,因此须要在尾数位末尾处进行舍入,被称为精度丢失

  • 两个数相加以后就获得了十进制小数位末位为4而不为0的结果

解决方法

  • toFixed 能够精确到某一位,舍弃小数位

  • Number.EPSILON ['epsɪlɒn] 是 JS 能表示最小精度 2^(-52)

    const isEquel = (a, b) => Math.abs(a - b) < Number.EPSILON // 相等
    复制代码
  • 转换成整数运算

    /** * 精确加法 */
    function add(num1, num2) {
      const num1Digits = (num1.toString().split('.')[1] || '').length;
      const num2Digits = (num2.toString().split('.')[1] || '').length;
      const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
      return (num1 * baseNum + num2 * baseNum) / baseNum;
    }
    add(0.1,0.2); // 0.3
    复制代码

类型转化与规则

转到boolean

除了如下 5 种,其余都被转为 true

  • undefined
  • null
  • 0(包括+0和-0)
  • NaN
  • "" 空字符串

toString

toString(10,2) // "1010" 输入数字时,第二个参数可选为进制

toString(new Date())  // Wed Jan 20 2021 20:06:24 GMT+0800 (中国标准时间)

toString([1,2]) // "1,2"
复制代码

不少内置类型的原型上都被重写了toString方法

判断类型时,能够调用Object上的toString方法如数组 ({}).toString.call(x) === '[object Array]'

valueOf

  • null 和 undefined 没有包装对象 不能调用valueOf
  • Number Boolean String 的 prototype 上各自实现了一个 valueOf,不过功能同样,即返回包装对象内部的[[primitiveValue]]值
  • 各类内置对象类型,除了 Date 原型上实现了 valueOf,返回的是形如:1536416960724 的从 1970 年 1 月 1 日午夜开始计的毫秒数 ;其余都未自定义即 Object.prototype.valueOf,返回调用者自身。

toPrimitive

用于转换对象到原始值的内置函数,经过Symbol.toPrimitive能够覆写一个对象的转换到原始值的行为

它有个参数 hint,通常有两个可传入值 "number" "string",内部根据上下文选择传入或不传入

  • "number":先调用 valueOf 再 toString
  • "string":先调用 toString 再 valueOf
  • 非Date对象默认传入"number",Date对象默认传入"string";不过实际上都是调用了 toString

关于对象更细节的转化,请了解 toPrimitive ECMAScript7规范中的ToPrimitive抽象操做

转到number

  • boolean,true 转 1 false 转 0

  • null 转 0,undefined 转 NaN

  • string

    • 若是字符串只包含数字(包括十六进制格式“0x”),则将其转换成对应的十进制。
    • 若是字符串是空,"""\n",返回0。
    • 其余状况转为 NAN
  • 对象,调用 ToPrimitive 方法,PreferredType 参数为 "number",即

    ​ 1. 调用 valueOf 方法

    ​ 2.调用 toString 方法

    ​ 3. 转到 string 的状况

对象转换为数字实例

Number([1]) // 1

​ 1. [1].valueOf() 返回 [1]

​ 2. [1].toSting() 返回 "1"

​ 3. "1" 转为 1

Number([1,1]) // NaN

​ 1. [1,1].valueOf() 返回 [1,1]

​ 2. [1,,1].toSting() 返回 "1,1"

​ 3. "1,1" 转为 NaN

隐式转换和valueOf、toString

js将对象转换为基本类型时,会调用内部函数 ToPrimitive 进行转换,

分为如下两点

  • 非 Date 类型先 valueOftoString
  • Date 类型先 toStringvalueOf

考虑到这两种类型实际都是调用了 toStringvalueOf 并未改变输出 (除包装类型外),

因此默认对象的隐式转换都是调用了 toString

[] == false // true []先调用 toString转化为"",""取布尔是false
![] // false
{} + 1 // [object Object]1 
复制代码

符号中的强制类型隐式转换

a + b 的转换

表中 object 是非包装对象(下表能够不看,看总结便可)

左右值组合\符号 +
string number number -> string
string boolean boolean -> string
string object object -> object.toString()
number boolean boolean -> number
number object number -> string,object -> object.toString()
boolean boolean boolean -> number
boolean object boolean -> string,object -> object.toString()
object object object -> object.toString()
null number null -> 0
null object null -> "null",object -> object.toString()
null boolean null -> 0,boolean -> number
null string null -> "null"
null null 0
undefined number undefined -> NaN
undefined object undefined -> "undefined",object -> object.toString()
undefined boolean undefined -> NaN,boolean -> number
undefined string undefined -> "undefined"
undefined undefined NaN
undefined null NaN

注意 undefined 到 number 转换为 NaN,而 null 转换为 0

经过上表的排列组合咱们看出:

1.一边有对象(非包装)先转为字符串(其实是调用 toPrimitive)

特别注意 Date 对象,它也是转为字符串

new Date() + 1 // "Fri Mar 19 2021 10:59:08 GMT+0800 (中国标准时间)1"
复制代码

2.一边是字符串,另外一边也转为字符串

1 + "1" = "11"

3.一边是布尔值,另外一边是数字或布尔值,布尔值转为数字

4.一边是 null 或 undefined,另外一边是 字符串 或者 数字,跟着另外一边转;若都不是,null 或 undefined 先转为数字

undefined + true // NaN

null + true // 1
复制代码

5.不断从上往下检索规则,直到两边都是字符串或者数字。

注意 +ab + a,对 a的转换是不同的:+a 是转换到 number,而 b + a 须要按照上述规则进行转换后相加

a == b 的转换

对象(非包装)比较或者不一样类型比较时:

​ 1.两边是对象对比地址
​ 2.一边是对象先调用 toString(实际是 toPrimitive)
​ 3.一边是布尔值转换为数字
​ 4.两边分别是数字和字符串时,字符串转换为数字
​ 5.不断从上往下检索规则,直到两边类型相同。

其余状况

null == undefined // true undefined 和 null 不与其余假值 `==`

NaN == NaN // false 须要判断 NaN,应该用 Number.isNaN
复制代码

实例

  • 'true' == true // false

    1. 符合 3,true 转为 1
    2. 符合 4,"true" 转为 NaN
    3. NaN == 1 返回false
  • [] == ![] // true

    1. ![] 转化为 false:除了 0,"",NaN,undefined,null,其余转布尔时都转为true,再取反为false

    2. 符合 2 和 3,[] 转为 "",false 转为 0

    3. 符合 4 ,"" 转为 0

    4. 0 == 0 返回 true

语言内置

关于Symbol

ES6入门教程#Symbol

关于Symbol,使用得很少,它的做用是做为一个惟一值,做对象的键,防止属性被覆盖或覆盖已存在属性

如手写 apply,为了防止覆盖传入的函数上的属性,咱们能够用Symbol做为键

function myApply(ctx,args = []){
	if(typeof this !== 'function') {
        throw new TypeError('not a function!')
    }
    const symbol = Symbol(0)
	ctx[symbol] = this
    const result = ctx[symbol](...args)
    delete ctx[symbol]
    return result
}
Fuction.prototype.apply = myApply
复制代码

它第二个做用,它提供咱们访问内置方法和覆写内置行为的可能。

Symbol上储存着各类内置方法的键,

经过重写类上的迭代器,能够改变实例使用迭代器的行为如

class Collection {
  *[Symbol.iterator]() {
    let i = 0;
    while(this[i] !== undefined) {
      yield this[i];
      ++i;
    }
  }
}

let myCollection = new Collection();
myCollection[0] = 1;
myCollection[1] = 2;

for(let value of myCollection) {
  console.log(value);
}
复制代码

迭代器(iterator)

迭代器是一个对象,它符合如下规范

  • 对象上可访问 next 函数

  • next 函数 返回 {value,done},value为本轮迭代的值,done为布尔值,表示迭代是否结束

  • 迭代器对象能够经过重复调用next()显式地迭代。 迭代一个迭代器被称为消耗了这个迭代器,由于它一般只能执行一次。 在产生终止值以后,对next()的额外调用应该继续返回{done:true}。

    var it = makeIterator(['a', 'b']);
    
    it.next() // { value: "a", done: false }
    it.next() // { value: "b", done: false } 最后一个值done为false,下一轮再next done为true
    it.next() // { value: undefined, done: true }
    
    function makeIterator(array) {
      var nextIndex = 0;
      return {
        next: function() {
          return nextIndex < array.length ?
            {value: array[nextIndex++], done: false} :
            {value: undefined, done: true};
        }
      };
    }
    复制代码

迭代器接口(iterator)

ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具备Symbol.iterator属性,就能够认为是“可遍历的”。

它是一个返回迭代器的函数。

咱们能够经过 Symbol.interator 访问

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();

iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
复制代码

原生具有 Iterator 接口的数据结构以下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

生成器函数(generator)

生成器由于出现不久后就被 async 函数取代了,我学习时已经遍及 async 语法了。

不过咱们仍是颇有必要了解它的基础语法,由于它与迭代器有关,进而与 for of,解构语法等有关;

同时 async 是 generator 函数的语法糖,了解了 gennerator 的原理后 async 的原理也就很好理解了。

Generator 函数的语法

生成器和迭代器[MDN]

生成器生成什么?生成迭代器

生成器函数(generator)function * name{} 是一个返回迭代器的函数,这个迭代器能够自动维护本身的状态。

基本用法

  • function 关键词后添加 * ,声明生成器函数,调用生成器函数后,返回一个迭代器;

  • yield是迭代器调用next 后的执行暂停处,继续调用next执行到下一个next

  • yield后表达式的结果做为 next的返回值;

  • next传入的参数做为上一个 next暂停处整个yield 表达式的结果

  • 生成器函数最后 return 没有 yield 的效果,可是它会被保留在后续第一次调用next返回对象的 value

function* f() {
 for (let i=0; i<3; i++){
   if(yield i) yield 10 // 返回并记录函数状态
 }
 return 20
}
const iter = f()
iter.next() //第三条 {value:0,done:false}
iter.next() // {value:1,done:false}
iter.next(true) //第四条 {value:10,done:false} 上一个 yield 是 if(yield i) 传入true,if成功,执行到 yield 10
iter.next(true) // {value:2,done:false} 上一个 yield 是 yield 10 传入true 无影响
iter.next() //第五条 {value:20,done:true} 返回值 20 被保存了
iter.next() //{value:undefined,done:true}
复制代码

咱们能够经过生成器很简便地写 iterator 接口

class O {
  constructor(p = []) {
    p.forEach(([key, value]) => (this[key] = value))
  }
  *[Symbol.iterator]() {
    const keys = Object.keys(this)
    for (let i = 0; i < keys.length; i++) {
      yield this[keys[i]]
    }
  }
}
const c = new O([
  ['a', 1],
  ['b', 2],
  ['c', 3]
])
for (let value of c) {
  console.log(value)
}
// 1 2 3
复制代码

yield * [Iterator] 迭代器委托

将迭代委托给另外一个迭代器

let delegatedIterator = (function* () {
  yield 'Hello!';
  yield 'Bye!';
}());

let delegatingIterator = (function* () {
  yield 'Greetings!';
  yield* delegatedIterator;
  yield 'Ok, bye.';
}());

for(let value of delegatingIterator) {
  console.log(value);
}
// "Greetings!
// "Hello!"
// "Bye!"
// "Ok, bye."
复制代码

关于抛出错误,以及原型上的方法请看 Generator 函数的语法

generator函数的原理

由 switch case 组成的状态机模型中, 除此以外,利用闭包技巧,保存生成器函数上下文信息。

Regenerator 经过工具函数将生成器函数包装,为其添加如 next/return 等方法。同时也对返回的生成器对象进行包装,使得对 next 等方法的调用,最终进入由 switch case 组成的状态机模型中。除此以外,利用闭包技巧,保存生成器函数上下文信息。

【转向 Javascript 系列】深刻理解 Generators

简单实现

Async / Await / Generator 实现原理

// 生成器函数根据yield语句将代码分割为switch-case块,后续经过切换_context.prev和_context.next来分别执行各个case
function gen$(_context) {
  while (1) {
    switch (_context.prev = _context.next) {
      case 0:
        _context.next = 2;
        return 'result1';

      case 2:
        _context.next = 4;
        return 'result2';

      case 4:
        _context.next = 6;
        return 'result3';

      case 6:
      case "end":
        return _context.stop();
    }
  }
}

// 低配版context 
var context = {
  next:0,
  prev: 0,
  done: false,
  stop: function stop () {
    this.done = true
  }
}

// 低配版invoke
let gen = function() {
  return {
    next: function() {
      value = context.done ? undefined: gen$(context)
      done = context.done
      return {
        value,
        done
      }
    }
  }
} 

// 测试使用
var g = gen() 
g.next()  // {value: "result1", done: false}
g.next()  // {value: "result2", done: false}
g.next()  // {value: "result3", done: false}
g.next()  // {value: undefined, done: true}
复制代码

async函数 与 generator 函数

Async / Await / Generator 实现原理

咱们知道,async 函数是 generator 函数的语法糖,它们有三点不一样

  • async/await自带执行器,不须要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await可以返回Promise的resolve/reject的值
function run(gen) {
  //把返回值包装成promise
  return new Promise((resolve, reject) => {
    var g = gen()

    function _next(val) {
      //错误处理
      try {
        var res = g.next(val) 
      } catch(err) {
        return reject(err); 
      }
      if(res.done) {
        // 将最后 return 值返回
        return resolve(res.value);
      }
      //res.value包装为promise,以兼容yield后面跟基本类型的状况
      Promise.resolve(res.value).then(
        val => {
          // 递归执行_next 达到了自动执行next功能
          _next(val);
        }, 
        err => {
          //抛出错误
          g.throw(err)
        });
    }
    _next();
  });
}

function* myGenerator() {
  try {
    console.log(yield Promise.resolve(1)) 
    console.log(yield 2)   //2
    console.log(yield Promise.reject('error'))
  } catch (error) {
    console.log(error)
  }
}

const result = run(myGenerator)     //result是一个Promise
//输出 1 2 error
复制代码

原型链

如何判断数组类型?

  • xxx instanceof Array

  • xxx.construtor === Array

  • Array.isArray(xxx) === true

  • Object.prototype.toString.call(xxx) === 'object Array'

描述new的过程

​ 1. 建立一个新对象

​ 2. this 指向这个新对象

​ 3. 执行代码,即对 this 赋值

​ 4. 返回 this

instanceof的原理

instance instanceof constructor

  • 判断实例对象 instance__proto__ 与构造函数 constuctorprototype 是否是引用的同一个原型对象

  • 若不是,沿instance的原型链继续向上找

    function myInstanceof(l, r) {
            while (l) {
                if (l.__proto__ == r.prototype) {
                    return true
                }
                l = l.__proto__
            }
            return false
    }
    复制代码

对象的遍历方法

  • Object.prototype.entries:返回对象自身自身的全部可枚举的属性名和值对的数组。

  • Object.keys():返回对象自身的全部可枚举的属性的键名。

  • JSON.stringify():只串行化对象自身的可枚举的属性。

  • Object.assign(): 忽略enumerablefalse的属性,只拷贝对象自身的可枚举的属性。

特别注意

for in 会遍历到原型上的属性,须要配合 hasOwnProperty

for (let key in obj) {        
	if (obj.hasOwnProperty(key)){
		// dosomething
	}
}
复制代码

继承

ES5 中几种继承方法

JS原型链与继承别再被问倒了

  • 原型链继承

    将子类的原型赋值为父类实例,缺点是子类不能改变传入父类构造函数的参数,且当原型链中包含引用类型值的原型时,该引用类型值会被全部实例共享;

    Child.prototype = new Parent('parent');
    复制代码
  • 构造函数内部继承

    在子类构造函数内call父类函数并传入this和参数,缺点是只能继承父类构造函数内赋予的属性

    function Child(){
    	Parent.call(this,'yellow'); // 这句代码就是借助构造函数实现部分继承,绑定this并执行父构造函数
    	this.type = 'child';
    }
    复制代码
  • 组合继承(结合原型链继承和构造函数继承)

  • 原型式继承

    在object()函数内部, 先建立一个临时性的构造函数, 而后将传入的对象做为这个构造函数的原型,最后返回了这个临时类型的一个新实例.

    function object(o){
    	function F(){}
    	F.prototype = o;
    	return new F();
    }
    var person = {
    	friends : ["Van","Louis","Nick"]
    };
    var anotherPerson = object(person);
    复制代码
  • 寄生式继承

    function createAnother(original){
    	var clone = object(original);//经过调用object函数建立一个新对象
    	clone.sayHi = function(){//以某种方式来加强这个对象(加强:为其添加属性或方法)
    		alert("hi");
    	};
    	return clone;//返回这个对象
    }
    复制代码
  • 组合寄生式

    • 第一步,将子类的原型赋值为一个空对象,这个对象的原型是父类,同时修改 constructor 属性,这一步的做用是将子类拽到父类的原型链上
    • 第二步,同理构造函数内部继承:在子类构造函数内call父类函数并传入this和参数
    function extend(subClass,superClass){
    	var prototype = object(superClass.prototype);//建立对象,这个对象的原型是父类的原型
    	prototype.constructor = subClass;//加强对象
    	subClass.prototype = prototype;//指定对象
    }
    function Father(name){
    	this.name = name;
    }
    Father.prototype.sayName = function(){
    	alert(this.name);
    };
    function Son(name,age){
    	Father.call(this,name);//继承实例属性,第一次调用Father()
    	this.age = age;
    }
    //Son.prototype = new Father();
    //不建立新的父类实例而是用extend完成
    extend(Son,Father);
    复制代码

ES6 class

继承

子类实例继承父类实例的属性和方法

  • 子类继承时,在构造函数内必须调用super方法,执行了父类的构造函数(与 ES5 中构造函数继承同理)

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        super(x, y); // 不调用就报错
        this.color = color;
      }
    }
    复制代码
  • 原型链继承

  • 构造函数做为对象, 构造函数的属性, 即静态方法继承

    // 上面两种都是 extends 后 js 自动完成
    class A {}
    class B extends {}
    
    A.prototype // {} 无属性
    A.prototype.__proto__ === B.prototype // true 至关于完成了组合寄生式的extends方法
    
    B.__proto__ === A // true 这样B能够访问到A上静态方法
    复制代码

做用域

JavaScript是如何支持块级做用域的

变量提高

  • 变量声明: 把声明和赋值拆解 , 声明提高到做用域最前面 , 赋值保留在原位
  • 函数声明: 把函数声明 如同剪切通常, 整个提高到做用域前面(在变量声明后面).
  • if中变量声明,声明会提高;函数声明转换为变量声明,声明会提高。

let 和 var 的区别?

  • var 是函数做用域 ,let 是块级做用域

  • var有变量提高,let无变量提高

    准确得说,let和var的建立是都会提高,可是let在原声明前是禁止访问的,这也是形成暂时性死区的缘由

  • let 不能在同一个做用域声明相同的变量 而 var没有此限制

  • let 在全局环境声明不会挂载到window上 而 var会

let的暂时性死区

暂时性死区:指块级做用域内,某个let声明的变量,在声明前的区域,没法访问做用域链上同名的变量。

JS中一个声明且赋值语句,它有三种状态:

​ 1.建立

建立在函数开始执行前就会完成,此时它还没法被访问,可是它会拦截函数上下文的访问并报错

let 在函数执行前只完成了建立

​ 2. 初始化

初始化后,变量能够被访问,此时它的值为undefined;var 变量在函数执行前就完成建立和初始化了,

let 的初始化在原句处

​ 3. 赋值

varlet 的赋值都在原句处

闭包是什么?

简单来讲,全部引用了自由变量且不被销毁的函数就是闭包

自由变量就是既不是函数内参数又不是已声明的变量

你在实践中怎么运用闭包?

  • 解决for循环中setTimeout打印index最终数值一致的问题
  • 回调函数使用闭包改变外部的变量
  • 储存私有变量
    • 节流、防抖函数
    • React的高阶组件

深刻闭包

做用域链和闭包:代码中出现相同的变量,JavaScript引擎如何选择

自由变量本来归属调用栈中 本层或本层如下 的调用上下文,但它不会随调用上下文而销毁

编译器建立一个闭包的步骤以下:

​ 1. 在每一个函数执行前,它会编译并建立一个执行上下文

​ 2. 若是发现内部有函数定义,会快速扫描这个函数,若是函数使用了自由变量,则断定这个函数是一个闭包,并建立自由变量所属的执行上下文的闭包对象,这是个内部对象,存储在堆空间。

​ 3. 将自由变量挂载到闭包对象,若是后面有使用这个执行上下文的其余自由变量,也一样被挂载到这个执行上下文的闭包对象上;一个执行上下文对应一个闭包对象

​ 4. 没有被内部的函数使用的变量依旧在栈上

为何闭包会致使内存泄漏?

不被销毁的闭包,与它相关的闭包对象都不会被销毁。

若是闭包不被执行,那么这个对象会一直占用内存。

同是使用变量,为什么闭包就是内存泄漏?

函数中建立变量也是使用变量,闭包使用闭包变量上的变量也是使用变量,二者有着一样用途且都占用内存,为什么说后者是内存泄漏?

首先,函数执行时建立的变量是执行时才建立,随调用上下文销毁而销毁;而与闭包相关的闭包对象与闭包共生,不管闭包是否执行都占用着一块内存。

第二,闭包在执行时,闭包变量会被使用,此时不是内存泄漏;闭包不被执行时,闭包变量没法被外界访问且一直占用内存,那么就是内存泄漏。

this

this的指向?

  • 全局环境指向window
  • 全局调用函数指向window
  • 对象调用函数指向对象
  • 箭头函数指向外部的this

以一个变量形式调用时,this 指向window,而不是调用这个函数的上下文的 this

关于 reference 如何影响 this,请看 JavaScript深刻之从ECMAScript规范解读this

apply、call和bind

这三者的做用?

  • apply和call用于执行一个函数并强制改变其this的指向,差异在于参数的写法
  • bind基于传入的参数生成强制绑定this指向的函数

事件循环

为何要区分宏任务、微任务?

若是不将任务进行划分,按照队列方式执行,当大量任务执行时,某些任务的回调迟迟得不到执行(都在队尾),就会形成应用效果上的卡顿。因此设计者将任务分为宏任务和微任务,微任务能够穿插在宏任务中执行。

哪些属于宏任务、微任务

宏任务:

  • script执行

  • 事件回调

  • setTimeout/setInterval

  • requestAnimationFrame

微任务:

  • promise.then
  • MutationObserver (用于监视dom的改变)

描述一下事件循环过程

执行一个宏任务,而后执行该宏任务中产生的微任务,若是微任务中产生了微任务,那么这个微任务也会被执行,直到微任务队列被清空,以后开启下一轮循环

关于事件循环,更详细请看

关于Promise和async的考题

async function foo() {
    console.log('foo')
}
async function bar() {
    console.log('bar start')
    await foo()
    console.log('bar end')
}
console.log('script start')
setTimeout(function () {
    console.log('setTimeout')
}, 0)
bar();
new Promise(function (resolve) {
    console.log('promise executor')
    resolve();
}).then(function () {
    console.log('promise then')
})
console.log('script end')
复制代码

​ 1. 首先在主协程中初始化异步函数foo和bar,碰到console.log打印script start;

​ 2. 解析到setTimeout,初始化一个Timer,建立一个新的task

​ 3. 执行bar函数,将控制权交给协程,输出bar start,碰到await,执行foo,输出foo,建立一个 Promise返回给主协程

​ 4. 将返回的promise添加到微任务队列,向下执行 new Promise,输出 promise executor,返回resolve 添加到微任务队列

​ 5. 输出script end

​ 6. 当前task结束以前检查微任务队列,执行第一个微任务,将控制器交给协程输出bar end

​ 7. 执行第二个微任务 输出 promise then

​ 8. 当前任务执行完毕进入下一个任务,输出setTimeout

特别注意 await 非 Promise 的值时,它会隐式建立 Promise 实例并 resolve 这个值

Promise的api

Promise[MDN]

Promise.all(iterable)

这个方法返回一个新的promise对象,该promise对象在iterable参数对象里全部的promise对象都成功的时候才会触发成功,一旦有任何一个iterable里面的promise对象失败则当即触发该promise对象的失败。这个新的promise对象在触发成功状态之后,会把一个包含iterable里全部promise返回值的数组做为成功回调的返回值,顺序跟iterable的顺序保持一致;若是这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息做为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。

Promise.race(iterable)

首先 race 的返回值是一个 promise;当 iterable 参数里的任意一个 promise 成功或失败后,race 将这个 promise 的成功返回值或失败详情做为参数传入 race 返回的 promise 的 resolve 或 reject 中。

因此数组内的Promise实例,谁执行的快,就继承谁的执行结果和执行状态,无论是成功仍是失败

//race方法 
Promise.race = function(promises){
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(resolve,reject)
    };
  })
}
//all方法(获取全部的promise,都执行then,把结果放到数组,一块儿返回)
Promise.all = function(promises){
  let arr = [];
  let i = 0;
  function processData(index,data){
    arr[index] = data;
    i++;
    if(i == promises.length){
      resolve(arr);
    };
  };
  return new Promise((resolve,reject)=>{
    for(let i=0;i<promises.length;i++){
      promises[i].then(data=>{
        processData(i,data);
      },reject);
    };
  });
}

复制代码

Promise 符合规范的实现

史上最最最详细的手写Promise教程

// 来源 
class Promise{
  constructor(executor){
    this.state = 'pending';
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled';
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    };
    let reject = reason => {
      if (this.state === 'pending') {
        this.state = 'rejected';
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    };
    try{
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }
  then(onFulfilled,onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === 'fulfilled') {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.state === 'pending') {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0)
        });
      };
    });
    return promise2;
  }
  catch(fn){
    return this.then(null,fn);
  }
}
function resolvePromise(promise2, x, resolve, reject){
  if(x === promise2){
    return reject(new TypeError('Chaining cycle detected for promise'));
  }
  let called;
  if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    try {
      let then = x.then;
      if (typeof then === 'function') { 
        then.call(x, y => {
          if(called)return;
          called = true;
          resolvePromise(promise2, y, resolve, reject);
        }, err => {
          if(called)return;
          called = true;
          reject(err);
        })
      } else {
        resolve(x);
      }
    } catch (e) {
      if(called)return;
      called = true;
      reject(e); 
    }
  } else {
    resolve(x);
  }
}
复制代码

拓展:vue异步批量更新

Vue源码详解之nextTick:MutationObserver只是浮云,microtask才是核心!

数据被修改触发setter函数,修改被watcher收集了,watcher将本身放入待更新的数组,在这次宏任务中的调用了this.$nextTick中的回调函数也被收集入回调的数组中;宏任务结束后,nextTick回调数组在微任务执行,nextTick回调数组中的第一个执行的函数就是Watcher数组先去通知更新vm实例更新,以后就按顺序执行被收集的nextTick回调。

调用微任务形式是:Promise.resolve 或 MutationObersever

手写代码

手写EmitEvent

class EventEmitter {
  constructor() {
    this.events = {}
  }
  on(eventName, callback = () => {}, once = false) {
    // const name = !!once ? 'onceEvents' : 'events'
    if (typeof callback !== 'function')
      throw new Error('callback must be a function')
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    if (this.events[eventName] === undefined) this.events[eventName] = []
    this.events[eventName].push({
      callback,
      once
    })
  }
  once(eventName, callback) {
    this.on(eventName, callback, true)
  }
  off(eventName, callback) {
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    const nameOfevent = this.events[eventName]
    if (Array.isArray(nameOfevent)) {
      this.events[eventName] =
        typeof callback !== 'function'
          ? []
          : nameOfevent.filter(e => e.callback !== callback)
    }
  }
  emit(eventName, ...args) {
    if (typeof eventName !== 'string')
      throw new Error('eventName must be a string')
    const nameOfevent = this.events[eventName]
    if (Array.isArray(nameOfevent)) {
      for (const e of nameOfevent) {
        e.callback.apply(this, args)
      }
      this.events[eventName] = nameOfevent.filter(e => !e.once)
    }
  }
}
复制代码

手写节流 throttle

节流函数的原理:

  • 闭包存储私有变量lock
  • 函数运行后上锁,并设置定时器解锁

复杂版, JavaScript专题之跟着 underscore 学节流

//节流函数


// 简洁版
function throttle(fn, { interval = 500 } = {}) {
  let lock = false
  return function (...args) {
    if (lock) return false
    lock = true
    setTimeout(() => {
      lock = false
    }, interval)
    return fn.apply(this, args)
  }
}


export function throttle(fn, { interval = 500 } = {}) {
	if (typeof fn != "function") return new Error("类型错误");
	const _self = fn;
	let timer,
		firstTime = true; // 是否第一次调用
	return function(...args) {
		const _me = this;
		if (firstTime) {
			fn.apply(_me, args);
			return (firstTime = false);
		}
		if (timer) {
			return false;
		}
		timer = setTimeout(() => {
			clearTimeout(timer);
			timer = null;
			_self.apply(_me, args);
		}, interval);
	};
}
复制代码

手写防抖 debounce

防抖函数计时器版原理:

  • 用闭包保存计时器引用 timer
  • 调用时清除计时器
  • 生成新的计时器

记录前一次运行的时间

复杂版,JavaScript专题之跟着underscore学防抖

// 开始执行方案 只执行第一次
function debounce(fn, { immediate = 500 } = {}) {
  let timestamp = 0
  return function (...args) {
    const pre = timestamp
    timestamp = Date.now()
    // if (!pre) return
    if (timestamp - pre >= immediate) return fn.apply(this, args)
  }
}

// 延迟执行方案 只执行最后一次 且最后一次也延迟
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// [JavaScript专题之跟着underscore学防抖] https://github.com/mqyqingfeng/Blog/issues/22
function debounce(func, wait, immediate) {

    var timeout;

    return function () {
        var context = this;
        var args = arguments;

        if (timeout) clearTimeout(timeout);
        if (immediate) {
            // 若是已经执行过,再也不执行
            var callNow = !timeout;
            timeout = setTimeout(function(){
                timeout = null;
            }, wait)
            if (callNow) func.apply(context, args)
        }
        else {
            timeout = setTimeout(function(){
                func.apply(context, args)
            }, wait);
        }
    }
}
复制代码

手写apply和bind

先实现apply再实现bind

apply

function myApply(ctx,args = []){
	if(typeof this !== 'function') {
        throw new TypeError('not a function!')
    }
    const symbol = Symbol(0)
	ctx[symbol] = this
    const result = ctx[symbol](...args)
    delete ctx[symbol]
    return result
}
Fuction.prototype.apply = myApply
复制代码

bind

function myBind(ctx,...preArgs){
    const fn = this
    return (...args)=> fn.apply(ctx,[...preArgs,...args])
}
Function.prototype.bind = myBind
复制代码

手写curry化

curry化的做用:固定函数参数,减小参数的输入,参数的私有化;提升函数参数适用性,减小通用性;

  • fn.length能够获得原函数的参数个数
  • 经过已接收的参数个数判断继续curry仍是执行
  • 注意参数的链接
function curry(fn, ...args) {
  // 继续接受参数而后柯里化
  return args.length < fn.length ? (...params) => {
    return curry(fn, ...args, ...params)
  } : fn(...args)
}
复制代码

手写深拷贝

  • 数组和对象类型区分创造而后递归下去
  • 原始类型直接返回
// 简单版
function deepCopy(obj) {
	if (typeof obj == "object") {
		const result = obj.constructor === Array ? [] : {}
		for (let key in obj) {
			if (obj.hasOwnProperty(key)) result[key] = deepCopy(obj[key])
		}
		return result
	} else return obj;
}

// 循环引用如何解决 ?
function deepCopy(obj) {
  // 使用 map 标记对象避免无限循环
  const map = new Map()

  function traverse(obj) {
    if (typeof obj == 'object' && !map.get(obj)) {
      map.set(obj, true)
      const result = obj.constructor === Array ? [] : {}
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) result[key] = traverse(obj[key])
      }
      return result
    } else return obj
  }
  return traverse(obj)
}
复制代码

手写virtual Dom 生成真实 Dom节点

思路与深拷贝如出一辙,都是递归遍历

result[key] = deepCopy(obj[key]) 替换成了 el.appendChild(createElement(child))

// 假设虚拟dom的结构
// {
// tag:'div', // 元素标签
// attrs:{ // 属性
// class:'a',
// id:'b'
// },
// text:'我是内容', // 文本内容
// children:[] // 子元素
// }

function createElement(virtualDom) {
  const { tag, attrs, text, children } = virtualDom
  const el = document.createElement(tag)
  Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key]))
  if (text !== null || text !== undefined) el.innerText = text
  for (let child of children) {
    el.appendChild(createElement(child))
  }
  return el
}
复制代码

手写new

  • 内置this
  • 设置this原型
  • 执行函数
  • 返回值判断
// 用于触发微任务的触发器类 正常使用setTimeout便可
class MicTaskTrigger {
  constructor(callback = () => {}) {
    this.counter = 1
    this.node = document.createTextNode(String(this.counter))
    this.callback = callback
    this.observer = new MutationObserver(() => {
      this.callback()
    })
    this.observer.observe(this.node, {
      characterData: true
    })
  }
  changeCallback(callback) {
    this.callback = callback
  }
  trigger(callback = () => {}) {
    this.callback = callback
    this.counter = (this.counter + 1) % 2
    this.node.data = String(this.counter)
  }
}

const mic = new MicTaskTrigger() // mic 是用于触发微任务的触发器 正常使用setTimeout便可
class MyPromise {
  constructor(fn) {
    // 三个状态
    this.state = 'pending' // fulfilled rejected
    this.value = undefined
    this.reason = undefined
    this.ResolvedCallbacks = []
    this.RejectedCallbacks = []
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        mic.trigger(() =>
          this.ResolvedCallbacks.forEach(callback =>
            callback.call(this, this.value)
          )
        )
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
        if (this.RejectedCallbacks.length)
          mic.trigger(() => {
            this.RejectedCallbacks.forEach(callback =>
              callback.call(this, this.reason)
            )
          })
        else throw this.reason
      }
    }
    // 自动执行函数
    try {
      fn.call(this, resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function') {
      // 若是状态已经肯定了,调用then时直接执行回调
      if (this.state === 'fulfilled')
        return mic.trigger(() => onFulfilled.call(this))
      this.ResolvedCallbacks.push(onFulfilled)
    }
    if (typeof onRejected === 'function') {
      if (this.state === 'rejected')
        return mic.trigger(() => onRejected.call(this))
      this.RejectedCallbacks.push(onRejected)
    }
  }
  catch(onRejected) {
    this.then(null, onRejected)
  }
}

const promise = new MyPromise((resolve, reject) => {
  console.log(111)
  resolve({ data: 100 })
})
promise.then(res => console.log(++res.data))
promise.then(res => console.log(++res.data))
复制代码

手写Promise

  • 完成了 异步执行回调,then 和 catch 单次调用传入回调,状态固定后调用 then 直接回调;

    没有链式调用,没有处理返回值是 Promise 的状况,没有错误冒泡 和 then 内的错误处理

  • 状态

    • state 当前状态
    • value 调用 resolve 传入的值,即成功的结果
    • reason 调用 reject 传入的值,即失败的结果
    • ResolvedCallbacks 收集成功的回调
    • RejectedCallbacks 收集失败的回调
  • 构造函数内

    • 定义 resolved 和 rejected,做用是在 pending 状态下,改变状态并将回调函数放入事件队列中

    • 在 try-catch 中执行用户传入的函数,并传入以上两个函数,交予用户改变状态权力

    • catch 中调用 reject

  • then 函数中收集传入的成功和失败回调;若是是状态已定,直接将传入的回调函数放到队列中,无需收集

  • catch 执行 this.then(null, onRejected)

// 用于触发微任务的触发器类 正常使用setTimeout便可
class MicTaskTrigger {
  constructor(callback = () => {}) {
    this.counter = 1
    this.node = document.createTextNode(String(this.counter))
    this.callback = callback
    this.observer = new MutationObserver(() => {
      this.callback()
    })
    this.observer.observe(this.node, {
      characterData: true
    })
  }
  changeCallback(callback) {
    this.callback = callback
  }
  trigger(callback = () => {}) {
    this.callback = callback
    this.counter = (this.counter + 1) % 2
    this.node.data = String(this.counter)
  }
}

const mic = new MicTaskTrigger() // mic 是用于触发微任务的触发器 正常使用setTimeout便可
class MyPromise {
  constructor(fn) {
    // 三个状态
    this.state = 'pending' // fulfilled rejected
    this.value = undefined
    this.reason = undefined
    this.ResolvedCallbacks = []
    this.RejectedCallbacks = []
    let resolve = value => {
      if (this.state === 'pending') {
        this.state = 'fulfilled'
        this.value = value
        mic.trigger(() =>
          this.ResolvedCallbacks.forEach(callback =>
            callback.call(this, this.value)
          )
        )
      }
    }
    let reject = value => {
      if (this.state === 'pending') {
        this.state = 'rejected'
        this.reason = value
        if (this.RejectedCallbacks.length)
          mic.trigger(() => {
            this.RejectedCallbacks.forEach(callback =>
              callback.call(this, this.reason)
            )
          })
        else throw this.reason
      }
    }
    // 自动执行函数
    try {
      fn.call(this, resolve, reject)
    } catch (e) {
      reject(e)
    }
  }
  // then
  then(onFulfilled, onRejected) {
    if (typeof onFulfilled === 'function') {
      // 若是状态已经肯定了,调用then时直接执行回调
      if (this.state === 'fulfilled')
        return mic.trigger(() => onFulfilled.call(this, this.value))
      this.ResolvedCallbacks.push(onFulfilled)
    }
    if (typeof onRejected === 'function') {
      if (this.state === 'rejected')
        return mic.trigger(() => onRejected.call(this, this.reason))
      this.RejectedCallbacks.push(onRejected)
    }
  }
  catch(onRejected) {
    this.then(null, onRejected)
  }
}

const promise1 = new MyPromise((resolve, reject) => {
  console.log(111)
  setTimeout(() => resolve({ data: 100 }), 5000)
})
promise1.then(res => console.log(++res.data))
promise1.then(res => console.log(++res.data))
复制代码

数组扁平化

let arr = [[1,2,2], [6,7,8, [11,12, [12,13,[14]]], 10]]
// 原生
arr = arr.flat(infinity)

// 递归法
function flatten(arr){
    let res = []
    arr.forEach(item => {
		// 判断item是否为数组
        if(Array.isArray(item)) res = res.concat(flatten(item))
        else res.push(item)
    })
    return res
}
复制代码

setTimeout 实现 setInterval

由于事件循环的机制,setInterval 可能会出现两次或屡次任务执行间隔远小于设置的间隔时间的状况

好比,在设置 setInterval 执行后,执行一个密集计算的任务;第一个时间点,setInterval的一个回调推入宏任务队列,此时密集计算任务仍未完成;到第二个时间点, setInterval的第二个回调推入宏任务队列,此时宏任务队列中,两个任务是连着的,最终致使两个任务连续执行而远小于设置间隔的状况。

setTimeout实现setInterval原理是setTimeout的回调内递归调用,能够保证两个任务的执行间隔至少大于设置的间隔。

详细能够看 《JavaScript高级程序设计》22.3 高级定时器

// 简单实现
function mySetInterval(fn, millisec){
  function interval(){
    setTimeout(interval, millisec);
    fn();
  }
  setTimeout(interval, millisec)
}

// 加上执行次数和取消定时器,类写法
class MySetInterval {
  constructor(fn, { interval, count = Infinity } = {}) {
    this.fn = fn
    this.interval = interval
    this.count = count
    this._count = 0 // 使用计数
    this._isOn = false
    this._timer = null
  }
  _interval() {
    this._timer = setTimeout(() => this._interval(), this.interval)
    this.fn()
    if (++this._count === this.count) this.off()
  }
  on() {
    if (this._isOn) return
    this._isOn = true
    this._timer = setTimeout(() => this._interval(), this.interval)
  }
  off() {
    if (!this._isOn) return
    this._isOn = false
    this._count = 0
    clearTimeout(this._timer)
    this._timer = null
  }
}

const itt = new MySetInterval(()=>console.log(111),{interval:1000,count:5 })
itt.on() // 111 * 5
itt.on() // 111 * 3
itt.off() // 中止后续
复制代码

手写响应式

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {
    console.log('获取数据了')
  },
  set(newVal) {
    console.log('数据更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 输入监听
input.addEventListener('keyup', function(e) {
  obj.text = e.target.value
})
复制代码

实现 v-show

如何用原生 JS 实现一个最简单的 v-show 指令?

看到题目不要慌了,考察的仍是上面的响应式

<button onClick="model.isShow = true">显示</button>
<button onClick="model.isShow = false">隐藏</button>
 
<div v-show="isShow">Hello World!</div>
 
<script> // 第 1 步: 定义数据和视图 var model = { isShow: false } var view = document.querySelector('div') // 第 2 步: 定义视图刷新方法 var updateView = function(value) { view.style.display = value ? '' : 'none' } // 第 3 步: 设置初始视图表现 var directiveKey = view.getAttribute('v-show') updateView(model[directiveKey]) // 第 4 步: 监听数据变化,而后刷新视图,达到数据驱动的目的 Object.defineProperty(model, 'isShow', { set: function(val) { updateView(val) } }) </script>
复制代码

CSS与HTML

如何理解html语义化?

  • 增长代码可读性
  • 有利于搜索引擎爬虫分析
  • 在css加载失败的状况下也能呈现完整的页面结构

CSS选择器与权重

CSS选择器的权重详解

选择器 表达式或示例 说明 权重
ID选择器 #aaa 100
类选择器 .aaa 10
标签选择器 h1 元素的tagName 1
属性选择器 [title] 详见这里 10
相邻选择器 selecter + selecter 拆分为两个选择器再计算
兄长选择器 selecter ~ selecter 拆分为两个选择器再计算
亲子选择器 selecter > selecter 拆分为两个选择器再计算
后代选择器 selecter selecter 拆分为两个选择器再计算
通配符选择器 * 0
各类伪类选择器 如:link, :visited, :hover, :active, :target, :root, :not等 10
各类伪元素 如::first-letter,::first-line,::after,::before,::selection 1
  • 1,0,0,0 > 0,99,99,99。也就是说从左往右逐个等级比较,前一等级相等才日后比。
  • 不管是行间、内部和外部样式,都是按照这个规则来进行比较。而不是直观的行间>内部>外部样式;ID>class>元素。之因此有这样的错觉,是由于确实行间为第一等的权重,因此它的权重是最高的。而内部样式可能通常写在了外部样式引用了以后,因此覆盖掉了以前的。
  • 在权重相同的状况下,后面的样式会覆盖掉前面的样式。
  • 通配符、子选择器、相邻选择器等的。虽然权值为0000,可是也比继承的样式优先,0 权值比无权值优先。

盒子模型 border-box 和 content-box的区别?

  • offsetWidth = border + padding + width

  • 当设置 box-sizing:border-box 时,offserWidth = width = border + padding + content,content是剩余下来的空间

    IE盒子模型默认 border-box

magin叠加

两个垂直外边距相遇时,他们将合为一个外边距

  • 兄弟节点 margin-top 和 margin-bottom 会叠加

  • 父子节点 margin-top 叠加 或者 margin-bottom 叠加

  • 一个元素没有内容,内边距和边框,它的margin-top 和 margin-bottom 会叠加

    margin-top:20px
    margin-bottom:20px
    叠加后 20px
    复制代码
  • 上一种状况下,叠加后的垂直边距与其余元素的边距相遇后也一样会发生叠加

    如:多个空内容的p标签发生叠加的状况

    <p><p>
    <p>1<p>
    <p><p>
    
    这几个段落最终效果是只显示 <p>1<p> 
    由于其余p标签无内容,叠加后就消失了
    复制代码
  • 接第二种状况,父子节点垂直边距叠加完后,仍会与父节点的兄弟节点叠加

解决:使用 BFC 包裹兄弟节点中的一个能够消除叠加的状况

margin负值问题

  • margin-left、margin-top 为负,影响自身,自身陷入前面的元素
  • margin-right、margin-bottom 为负,影响后面的元素,后面的元素陷入自身

BFC

格式化上下文[MDN]

BFC是什么?它的特色是什么?

BFC(block format content)块级格式化上下文,盒模型布局的CSS渲染模式,指一个独立的渲染区域或者说是一个隔离的独立容器。

BFC生成了新的渲染层,它能够解决同级边距折叠的问题,由于他们根本再也不一个层面上。

特色:

  • 属于同一个BFC的两个相邻容器的上下margin会重叠

  • 元素的margin-left与其包含块的border-left相接触

  • bfc区域不会与float元素重叠

  • 计算bfc高度时,float元素也会被计入其中

  • bfc区域内的子元素不会影响外部元素

BFC的产生条件?

  • float不为none
  • position是absolute或者fixed
  • overflow不为visible
  • display为flex,inline-block等

absolute和relative定位的依据

  • relative依据自身

  • absolute依据最近的已定位(postion:relative,absolute,fixed)的祖先元素

Flex

Flex 布局教程:语法篇

Flex 布局教程:实例篇

flex属性

  • flex-direction 主轴方向 row column row-reverse column-reverse
  • flex-wrap 换行
  • justify-content 主轴内容如何排布
    • flex-start 开始端对齐
    • flex-end 结束端对齐
    • center 中心端对齐
    • space-between 两端对齐,项目之间的间隔都相等
    • space-around 每一个项目两侧的间隔相等。因此,项目之间的间隔比项目与边框的间隔大一倍
  • align-items 交叉轴如何对齐
    • flex-start:交叉轴的起点对齐。
    • flex-end:交叉轴的终点对齐。
    • center:交叉轴的中点对齐。
    • baseline: 项目的第一行文字的基线对齐。
    • stretch(默认值):若是项目未设置高度或设为auto,将占满整个容器的高度。
  • align-content 多行的元素如何对齐
  • self-align 单个item的交叉轴如何对齐 属性与 align-items同样

flex:1?

css弹性盒子-------桃园三兄弟之:flex-grow、flex-shrink、flex-basis详解

flex: 1 分配父盒子的主轴大小,它实际上是三种属性的简写

  • flex-grow: 1;
  • flex-shrink: 1;
  • flex-basis: auto;

如下默认 flex-direaction : row,主轴上的大小为 宽度

flex-basis

语义是盒子基础的宽度,

肯定一个子盒子的宽度,优先级级比 width 高,好比 flex-basis:200px;width:100px,优先 flex-basis 的 200px 生效

flex-grow

语义是盒子如何 增大

当父元素的宽度大于子元素宽度之和时,子元素如何分配父元素的剩余宽度,也就是 会比 basis(基础) 的大小grow up(增大)

flex:1 能均分父盒子就是 flex-grow 在起做用

公式:

剩余宽度 = 父级的宽度 - 各个子元素的 flex—basis之和

自身在flex - grow 的占比 = 自身的 flex-grow /各个子元素 flex-grow 之和

宽度 = flex-basis + 剩余宽度 * flex-grow占比

flex-shrink

语义是盒子如何 收缩

当父元素的宽度小于于子元素宽度之和时,子元素如何缩小超出父元素的多余宽度,也就是 会比 basis(基础) 的大小shrink(缩小)

多余宽度 = 各个子元素的 flex—basis之和 - 父级的宽度

flex-shrink 加权 占比 = 自身的 flex-shrink 的加权 / 各个子元素的 flex-shrink 的加权之和

权重就是 flex-basis,flex-shrink加权 = flex-shrink * flex-basis

公式:宽度 = flex-basis - 多余宽度 * flex-shrink加权占比

居中

(水平、垂直、水平垂直) 居中

  • position + margin(适用于有对应的宽高)

    .h{
        position:absolute;
        left:50%;
        margin-left:-25px; /* 盒子宽度的一半 */    
    }
    
    .v{
        position:absolute;
        top:50%;
        margin-top:-25px; /* 盒子高度宽度的一半 */    
    }
    .vh{
        position:absolute;
        top:50%;
        left:50%
        margin-top:-25px;
        margin-left:-25px;
    }
    复制代码
  • position + tansform

    .h{
        position:absolute;
        left:50%;
        transform:translate(-50%,0);    
    }
    
    .v{
        position:absolute;
        top:50%;
        transform:translate(0,-50%);    
    }
    .vh{
        position:absolute;
        top:50%;
        left:50%
        transform:translate(-50%,-50%);
    }
    复制代码
  • flex

    .f{
        /* 父盒子 */
        display:flex
    }
    
    .h{
        justify-content:center    
    }
    
    .v{
        align-items:center    
    }
    .vh{
        justify-content:center;
        align-items:center;
    }
    复制代码

水平居中独有的两种

  • margin: 0 auto; 适用于宽度肯定的子盒子

  • 转换为行内块元素

    .h{
      display:inline-block;
      text-align:center;
    }
    复制代码

垂直居中独有的

  • line-height 设置为 height 大小

水平垂直居中独有的

.vh{
    /* 能够保证浏览器兼容性 */
    position:absolute;
    left:0;
    right:0;
    top:0;
    bottom:0;
    margin:auto;
}
复制代码

浮动

清除浮动

给后面元素加上

.clear{
	clear:all
}
复制代码

给父盒子加上

.clear:after{
	clear:all
}
复制代码

给父盒子加上overflow 触发bfc(计算bfc高度时,float元素也会被计入其中)

.box{
	overflow:hidden
}
复制代码

line-height如何继承?

  • 直接写大小如:18px或20px,直接继承

  • 直接写比例如:1或者1.5,直接继承

  • 写百分比时如200%,先换算成父元素line-height大小再继承此大小而不是继承百分比

    .f{
    	font-size:20px;
    	line-height:200%;
    }
    .son{
    	font-size:16px;
    }
    /* 子元素的line-height是40px */
    复制代码

移动端

rem是什么?

px:绝对像素

em:根据父元素的font-size肯定

rem:根据根元素html的font-size肯定

如何实现响应式?

css响应式

/* 根据屏幕宽度在media query 中设置 html的font-size */
@media only screen and (max-width: 320px){
    html {
        font-size: 5px !important;
    }
}
@media only screen and (min-width: 320px){
    html {
        font-size: 5px !important;
    }
}
@media only screen and (min-width: 384px){
    html {
        font-size: 6px !important;
    }
}
@media only screen and (min-width: 480px){
    html {
        font-size: 7.5px !important;
    }
}
/* 后续代码rem为单位时,1rem = 5px */
复制代码

js动态设置

// 提早执行,初始化 resize 事件不会执行
setRem()
// 原始配置
function setRem () {
  let doc = document.documentElement
  let width = doc.getBoundingClientRect().width
  let rem = width / 75
  doc.style.fontSize = rem + 'px'
}
// 监听窗口变化
addEventListener("resize", setRem)
复制代码

vh 和 vw理解

首先理解 屏幕视口高度 [ window.screen.height ] 和 网页视口高度 [ window.innerHeight ]

前者是整个手机屏幕的高度,后者是去除导航栏等高度以后用于显示网页内容的高度;

window.innerHeight = 100vh

xcss

rpx是如何计算的?

小程序编译后,rpx会作一次px换算。换算是以375个物理像素为基准,也就是在一个宽度为375物理像素的屏幕下,1rpx = 1px。

举个例子:iPhone6屏幕宽度为375px,共750个物理像素,那么1rpx = 375 / 750 px = 0.5px。

相关文章
相关标签/搜索