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 ?
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 类型先
valueOf
后toString
, - Date 类型先
toString
后valueOf
考虑到这两种类型实际都是调用了 toString
而 valueOf
并未改变输出 (除包装类型外),
所以默认对象的隐式转换都是调用了 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.不断从上往下检索规则,直到两边都是字符串或者数字。
注意 +a
和 b + 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
- 符合 3,true 转为 1
- 符合 4,"true" 转为 NaN
- NaN == 1 返回false
[] == ![] // true
![] 转化为 false:除了 0,"",NaN,undefined,null,其他转布尔时都转为true,再取反为false
符合 2 和 3,[] 转为 "",false 转为 0
符合 4 ,"" 转为 0
0 == 0 返回 true
语言内置
关于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)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__
与构造函数constuctor
的prototype
是不是引用的同一个原型对象若不是,沿
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()
: 忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
特别注意
for in 会遍历到原型上的属性,需要配合 hasOwnProperty
for (let key in obj) {
if (obj.hasOwnProperty(key)){
// dosomething
}
}
继承
ES5 中几种继承方法
原型链继承
将子类的原型赋值为父类实例,缺点是子类不能改变传入父类构造函数的参数,且当原型链中包含引用类型值的原型时,该引用类型值会被所有实例共享;
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上静态方法
作用域
变量提升
- 变量声明: 把声明和赋值拆解 , 声明提升到作用域最前面 , 赋值保留在原位
- 函数声明: 把函数声明 如同剪切一般, 整个提升到作用域前面(在变量声明后面).
- 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. 赋值
var
和 let
的赋值都在原句处
闭包是什么?
简单来说,所有引用了自由变量且不被销毁的函数就是闭包
自由变量就是既不是函数内参数又不是已声明的变量
你在实践中怎么运用闭包?
- 解决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.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 符合规范的实现
// 来源
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
看到题目不要慌了,考察的还是上面的响应式
<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选择器与权重
选择器 | 表达式或示例 | 说明 | 权重 |
---|---|---|---|
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
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-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,如有侵权,请联系删除。