前端面试题自检 JS CSS 部分

劳伦斯 等级 701 0 0

JS

类型

JavaScript的简单数据类型

Number , String , Boolean , Undefined , Null , Symbol

typeof 操作符的返回值

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

typeof NaN 返回 number

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

为什么数字和字符串可以使用方法?

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

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

(1).toString() // (new Number(1)).toString() 

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

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

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

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。

本文转自 https://juejin.cn/post/6942796562464505863,如有侵权,请联系删除。

收藏
评论区

相关推荐

2. web前端开发分享-css,js进阶篇
2. web前端开发分享css,js进阶篇 一,css进阶篇:   等css哪些事儿看了两三遍之后,需要对看过的知识综合应用,这时候需要大量的实践经验, 简单的想法:把qq首页全屏另存为jpg然后
30个前端开发人员必备的顶级工具
在本文中,我为前端Web开发人员汇总了30种顶级工具,从代码编辑器和代码游乐场到CSS生成器,JS库等等。 (https://imghelloworld.osscnbeijing.aliyuncs.com/2e7966318084a45d05a0926cbd749a02.png) 目录 CSS代码生成器 CSS3 Generator
VUE3(七)vue项目抽离.vue文件中的js、css代码
平常再做开发的时候,一般情况下不会将html,js,css代码写到一个文件中。基本上都会写在各自对应的文件中,然后再引入即可。那么在VUE中我们如何抽离vue文件中的js,与css代码呢? 1:抽离javascriptHome.vue<template <div <div :style"{ padding: '24px', back
只听说过CSS in JS,怎么还有JS in CSS?
CSS in JS是一种解决css问题想法的集合,而不是一个指定的库。从CSS in JS的字面意思可以看出,它是将css样式写在JavaScript文件中,而不需要独立出.css、.less之类的文件。将css放在js中使我们更方便的使用js的变量、模块化、treeshaking。还解决了css中的一些问题,譬如:更方便解决基于状态的样式,更容易追溯依赖关
JS动画与CSS动画
**一、JS动画**(setInterval setTimeOut requestAnimationFrame) **优点**:   1)过程控制能力强。可以对动画工程进行精准的控制,暂停、取消、开始、终止都可以。   2)动画效果多、炫酷。有一些效果是CSS动画所不能实现的   3)兼容性比较高 **缺点**:    1)由于JS是通过不断的操
JS和CSS加载(渲染)机制不同
一、结论 ==== CSS可以在页面加载完成后随时渲染。举个例子:通过js给某个元素加一个id或者css,只要这个id或者css有对应的样式,此元素的样式就会自动生效。 JS不可以在页面加载完成后生效。最明显的例子就是使用EasyUI的时候,iframe中哪些样式无效(EasyUi是依靠JS进行样式处理的,所以无法运行JS,那么样式也就无法设置。简单点说
23、Django实战第23天:视频播放页面
打开素材course-play.html,会发现播放页面处了包含播放器,其他和“章节”页面一样。 1、把course-play.html复制到template目录下 2、把下面两段代码拷贝出来 <link rel="stylesheet" type="text/css" href="../css/video-js.min.css">
Bootstrap引入在线路径
  如果不想麻烦地安装Bootstrap,可以通过引入在线路径的方式使用bootstrap 样式表文件 <meta rel="stylesheet" src="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css"> 核心JS文件
Go 1.16 embed特性的简单应用
项目结构如下: └─ui └─embed_ui.go └─dist └─index.html └─static ├─css └─ ... ├─fonts
JS MarcoTasks MicroTasks
##JS MarcoTasks MicroTasks 在JS的`event loop`中,有两种任务队列`microtasks`和`macrotasks` **microtasks** * process.nextTick * Promise * Object.observe * MutationObserver **macrotas
JavaScript 从select表中获取数据在表格中添加行
<!DOCTYPE html> <html> <head lang="en">     <meta charset="UTF-8">     <title></title>     <link rel="stylesheet" href="css/mian.css"/>     <script src=
JavaScript学习笔记
JavaScript学习笔记 ============== 和HTML和CSS不一样,它是一门编程语言。 JS简介 ---- JS是一个客户端脚本语言,不需要编译,每一个浏览器都有JS的解析引擎。可以增强用户和HTML页面的交互,使网页产生动态。 JS的生成是在当时网速所限,必须在客户端就完成一些表单的校验等工作以减少客户端和服务器端的通信次数的实际
Javascript基础知识学习(三)
**前言:** javascript是一种轻量的、动态的脚本语言,我们为什么要使用javascript ?对于一个网页的设计,.html用来放置网页的内容,.css则用来设计网页的样式和布局,那么.js它主要是使网页能够产生交互,意思就是能够通过代码动态的修改HTML、操作CSS、响应事件、获取用户计算机的相关信息等。javascript不是所有的浏览器
Subime使用笔记(持续跟进)
Subime使用笔记 ---------- ### 常用插件 * Package Control * Emmet * CSS Format * 格式化CSS代码插件 * DocBlockr * 快速添加代码注释插件 * Side Bar * 增强侧边
Vue 中 使用MUI Picker时间器等其他组件报错了,来来正确姿势!(~﹃~)~zZ
步骤1:     下载好Mui 时间器picker 依赖的js文件,文件???骚年别急下面看代码 步骤2:     下面是我再mian.js里面引入mui相关的依赖js 和css      ![](https://oscimg.oschina.net/oscnet/1709410844d5888c0fcdbc5340c9db9a322.png)