GithubHelp home page GithubHelp logo

diary's People

Contributors

caoxinhui avatar

Stargazers

 avatar

Watchers

 avatar

diary's Issues

外边距

  • 边距重叠
  • 负边距
  • 伪类实现垂直居中

React事件代理

参考文献

stopPropagation 与 stopImmediatePropagation

  • stopPropagation 能够阻止事件的进一步捕获或者冒泡
  • stopImmediatePropagation 假设事件流已经被某个元素捕获,那么便会触发此元素上绑定的事件。如果绑定的事件不止一个,便会依次触发。如果想中断此次触发,使用 stopImmediatePropagation

redux实现

redux做的性能优化

react 数据流和渲染机制

Context api 导致的 re-render

All consumers that are descendants of a Provider will re-render whenever the Provider's value prop changes.

children 相关的 re-render 机制

组件内渲染 this.props.children 不 re-render

性能优化实现

  1. 每次 store 变动,都会触发根组件 setState 从而导致 re-render。我们知道当父组件 re-render 后一定会导致子组件 re-render 。然而,引入 react-redux 并没有这个副作用,这是如何处理的?
    其实在 react-redux v6 中,需要关心这个问题,在 v6 中,每次 store 的变化会触发根组件的 re-render。但是根组件的子组件不应该 re-render。其实是用到了我们上文中提到的 this.props.children。避免了子组件 re-render。在 v7 中,其实不存在该问题,store 中值的变化不会触发根组件 Provider 的 re-render。

  2. 不同的子组件,需要的只是 store 上的一部分数据,如何在 store 发生变化后,仅仅影响那些用到 store 变化部分 state 的组件?

redux 状态管理

redux原理原文链接

状态值 只有 count 值

// 修改count值后,使用count的地方都能收到通知。使用发布-订阅模式
let state = {
    count: 1
}

let listeners = []

function subscribe(listener){
    listeners.push(listener)
}

function changeCount(count) {
    state.count = count
    for (let i = 0; i < listeners.length; i++) {
        const listener = listeners[i]
        listener()
    }
}

subscribe(() => {
    console.log(state.count)
})

changeCount(2)
changeCount(3)
changeCount(4)

将 count 值调整为 initState

const createStore = function(initState) {
    let state = initState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function changeState(newState) {
        state = newState
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i]
            listener()
        }
    }

    function getState() {
        return state
    }
    return {
        getState,
        changeState,
        subscribe
    }
}

对状态约束,只允许通过 action 操作。(明确改动范围)

function plan(state, action) {
    switch (action.type) {
        case 'INCREMENT':
            return {
                ...state,
                count: state.count + 1
            }
            case 'DECREMENT':
                return {
                    ...state,
                    count: state.count - 1
                }
                default:
                    return state
    }
}

const createStore = function(plan, initState) {
    let state = initState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function changeState(action) {
        state = plan(state, action)
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i]
            listener()
        }
    }

    function getState() {
        return state
    }
    return {
        subscribe,
        getState,
        changeState
    }
}

使用

let initState = {
    count: 0
}
let store = createStore(plan, initState)
store.subscribe(() => {
    let state = store.getState()
    console.log(state.count)
})
store.changeState({
    type: 'INCREMENT'
})
store.changeState({
    type: 'DECREMENT'
})

多文件协作

let state = {
    counter: {
        count: 0
    },
    info: {
        name: '',
        description: ''
    }
}

function counterReducer(state, action) {
    switch (action.type) {
        case 'INCRE':
            return {
                count: state.count + 1
            }
        case 'DECRE':
            return {
                count: state.count - 1
            }
        default:
            return count
    }
}

function InfoReducer(state, action) {
    switch (action.type) {
        case 'SET_NAME':
            return {
                ...state,
                name: action.name
            }
        case 'SET_DESCRIPTION':
            return {
                ...state,
                description: action.description
            }
        default:
            return state
    }
}

const reducers = combineReducers({
    counter: counterReducer,
    info: InfoReducer
})

function combineReducers(reducers) {
    const reducerKeys = Object.keys(reducers)
    return function combination(state, actions) {
        const nextState = {}
        for (let i = 0; i < reducerKeys.length; i++) {
            const key = reducerKeys[i]
            const reducer = reducers[key]
            const previousState = state[key]
            const nextStateForKey = reducer(previousState, actions)
            nextState[key] = nextStateForKey
            state[key] = nextStateForKey
        }
        return nextState
    }
}

let listeners = []

function subscribe(listener) {
    listeners.push(listener)
}
subscribe(() => {
    console.log(state)
})


function changeCount() {
    const newState = reducers(state, {type: 'INCRE'})
    for (let i = 0; i < listeners.length; i++) {
        listeners[i]()
    }
}

changeCount()

使用

const reducer = combineReducers({
    counter: counterReducer,
    info: InfoReducer
})
let initState = {
    counter: {
        count: 0
    },
    info: {
        name: '',
        description: ""
    }
}
let store = createStore(reducer, initState)
store.subscribe(() => {
    let state = store.getState()
    console.log(state.counter.count, state.info.name, state.info.description)
})
// 这里 dispatch 同前面 changeState
store.dispatch({
    type: 'INCREMENT'
})
store.dispatch({
    type: 'SET_NAME',
    name: ''
})

state 拆分与合并

let initState = {
    count: 0
}

function countReducer(state, action) {
    if (!state) {
        state = initState
    }
    switch (action.type) {
        case 'INCREMENT':
            return {
                count: state.count + 1
            }
            default:
                return state
    }
}

const createStore = function(reducer, initState) {
    let state = initState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function dispatch(action) {
        state = reducer(state, action)
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i]
            listener()
        }
    }

    function getState() {
        return state
    }
    dispatch({
        type: Symbol()
    })
    return {
        subscribe,
        dispatch,
        getState
    }
}

中间件 middleware

中间件增强 dispatch 的功能

createStore(reducers[,initialState])
reducer(previousState,action)=>newState

const createStore = function (reducer, initState) {
    let state = initState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function dispatch(action) {
        state = reducer(state, action)
        for (let i = 0; i < listeners.length; i++) {
            listeners[i]()
        }
    }

    function getState() {
        return state
    }

    dispatch({type: Symbol()})

    return {
        subscribe,
        dispatch,
        getState
    }
}
let state = {

    count: 0

}


function counterReducer(state, action) {
    switch (action.type) {
        case 'INCRE':
            return {
                count: state.count + 1
            }
        case 'DECRE':
            return {
                count: state.count - 1
            }
        default:
            return state
    }
}

const loggerMiddleware = (action) => {
    console.log('this state', store.getState());
    console.log('action', action);
    next(action);
    console.log('next state', store.getState());
}

const exceptionMiddleware = (next) => (action) => {
    try {
        next(action)
    } catch (e) {
        console.error(e)
    }
}
const store = createStore(counterReducer, state)
const next = store.dispatch

store.dispatch = exceptionMiddleware(loggerMiddleware(next))

store.dispatch({
    type: 'INCRE'
})

实现 applyMiddleware

const applyMiddleware = function (...middlewares) {
    return function rewriteCreateStoreFunc(oldCreateStore) {
        return function newCreateStore(reducer, initState) {
            const store = oldCreateStore(reducer, initState)
            const chain = middlewares.map(middleware => middleware(store))
            let dispatch = store.dispatch
            chain.reverse().map(middleware => dispatch = middleware(dispatch))
            store.dispatch = dispatch
            return store
        }
    }
}

react-imvc 状态管理

const createStore = function (reducer, initState) {
    let state = initState
    let listeners = []

    function subscribe(listener) {
        listeners.push(listener)
    }

    function dispatch(action) {
        state = reducer(state, action)
        for (let i = 0; i < listeners.length; i++) {
            listeners[i]()
        }
    }

    function getState() {
        return state
    }

    dispatch({type: Symbol()})
    subscribe((data) => {
        dispatch({
            type: data.actionType,
            payload: data.actionPayload
        })
    })
    return {
        subscribe,
        dispatch,
        getState
    }
}
let state = {
    count: 0
}

算法

function *Fibonacci() {
    let [prev,curr] = [0,1]
    for(;;){
        [prev,curr]=[curr,prev+curr]
        yield curr
    }
}
for(let i of Fibonacci(100)){
    console.log(i)
}

setState

以下将一直渲染为5,即使他的父组件重新渲染

import React from "react";
import "./styles.css";

export default function App() {
  let variable = 5;
  setTimeout(() => {
    variable = variable + 5;
  }, 100);
  return <div className="App">{variable}</div>;
}

一秒+3

export default function App() {
    const [variable, setVariable] = useState(5)
    setTimeout(() => {
        setVariable(variable + 3)
    }, 1000)
    return (
        <div className="App">
            {variable}
        </div>
    );
}

re-render会加3,否则不变

export default function App() {
    const variable = useRef(5)
    setTimeout(() => {
        variable.current = variable.current + 3
    }, 1000)
    return (
        <div className="App">
            {variable}
        </div>
    );
}

父级

export default function App() {
  const [variable, setVariable] = useState(5);
  setTimeout(() => {
    setVariable(variable + 3);
  }, 1000);
  return (
    <div className="App">
      <h1>Hello {variable}</h1>
      <h2>Start editing to see some magic happen!</h2>
      <Child />
    </div>
  );
}

function Child() {
  let variable = 5;

  setTimeout(() => {
    variable = variable + 3;
  }, 100);

  return <div>{variable}</div>;
}

参考文献

知识点汇总

tip
源码实现
实现promise.retry

Promise.retry = function (fn, num) {
    function executeFn() {
        return new Promise((resolve, reject) => {
            resolve(fn())
        }).then(res => {
            return Promise.resolve(res)
        }).catch(e => {
            num--;
            if (num < 0) {
                return Promise.reject(e)
            } else {
                return executeFn()
            }
        })
    }
    return executeFn()
};

function getData() {
    let num = Math.floor((Math.random() * 10));
    return new Promise((resolve, reject) => {
        if (num === 1) {
            resolve(num)
        } else {
            reject(num)
        }
    })
}


Promise.retry(getData, 3).then(res => console.log(res)).catch(err => console.log('失败'));

浏览器缓存

index 作为 key

react协程

react 协程

diff 算法

  • 不同类型的 Element

组件会重建。当组件将要被销毁的时候,组件实例触发 componentWillUnmount,当建立新树,组件实例接收到 componentWillMount,接着 componentDidMount。旧树的所有状态都会被销毁

  • 同类型的 DOM Element

react 会查看 Element 的属性,只会更新改变的属性。当更新 style,也只会更新改变的属性

  • 同类型的 Component Element
    当组件更新,组件实例还是一样的,所以在render过程中,状态会维持不变。react 更新组件的 props,并且调用 componentWillReceiveProps 和 componentWillUpdate,然后调用 render

子元素上的递归

递归一个list,如果是在最后添加一个元素,前面的DOM是维持不变的。如果是在第一个添加,性能很差,react会修改所有子节点。

keys

react通过keys去匹配子节点。
可以使用index 作为key,如果 这对子元素没有被重新排列的情况很适用。但是 重排列会变得很慢
组件实例会依据他们的key更新和重用。如果移除一个item,组件状态(类似于非受控input)会引起混乱。

key更新原理

我们知道在react中渲染一个列表的时候,会要求传入key属性,其实这主要是出于react对性能的考虑。当组件被传入的属性或状态发生改变时,render会重新触发,所以,在一个不确定长度的数组中,react需要基于key去判断整个列表结构,从而操作列表中的dom,大体上有三种情况。

  1. 当有新key时,则创建新节点
  2. 当有key消失时,则销毁key所对应的节点
  3. 当key相同,则更新对应节点

使用 index 作为 key 可能会出现问题

因为key是用来唯一标识一个元素的。当你向列表中添加一条数据或者移除某条数据时。如果该键与之前的相同,React会认为DOM元素表示的组件和以前是一样的。然而我们想要的并非如此。

class Home extends React.Component {
    constructor() {
        super();
        this.state = {
            list: [
                {name: 'zs', id: 1},
                {name: 'ls', id: 2},
                {name: 'ww', id: 3}
            ]
        }
    }
    addAHeadItem() {
        this.setState({
            list:[{name:'zq',id:4},...this.state.list]
        })
    }
    addBehindItem(){
        this.state({
            list:[...this.state.list,{name:'zq',id:4}]
        })
    }
    render(){
        return (
            <div>
                <button onClick={()=>{this.addAHeadItem()}}>
                    添加到头部
                </button>
                <button onClick={()=>{this.addBehindItem()}}>
                    添加到尾部
                </button>
                <div>
                    {this.state.list.map((item,index)=>{
                        return (
                            <li key={index}>
                                {item.name}
                                <input type='text'/>
                            </li>
                        )
                    })}
                </div>
                <div>
                    {this.state.list.map((item,index)=>{
                        return (
                            <li key={item.id}>
                                {item.name}
                                <input type={'text'}/>
                            </li>
                        )
                    })}
                </div>
            </div>
        )
    }
}

BFC

参考文献

什么是BFC

W3C对BFC的定义如下: 浮动元素和绝对定位元素,非块级盒子的块级容器(例如 inline-blocks, table-cells, 和 table-captions),以及overflow值不为"visiable"的块级盒子,都会为他们的内容创建新的BFC(Block Fromatting Context, 即块级格式上下文)

触发条件

  1. 根元素()
  2. 浮动元素(元素的 float 不是 none)
  3. 绝对定位元素(元素的 position 为 absolute 或 fixed)
  4. 行内块元素(元素的 display 为 inline-block)
  5. 表格单元格(元素的 display为 table-cell,HTML表格单元格默认为该值)
  6. 表格标题(元素的 display 为 table-caption,HTML表格标题默认为该值)
  7. 匿名表格单元格元素(元素的 display为 table、table-row、 table-row-group、table-header-group、table-footer-group(分别是HTML table、row、tbody、thead、tfoot的默认属性)或 inline-table)
  8. overflow 值不为 visible 的块元素 -弹性元素(display为 flex 或 inline-flex元素的直接子元素)
  9. 网格元素(display为 grid 或 inline-grid 元素的直接子元素) 等等

BFC渲染规则

  1. BFC垂直方向边距重叠

  2. BFC的区域不会与浮动元素的box重叠

  3. BFC是一个独立的容器,外面的元素不会影响里面的元素

  4. 计算BFC高度的时候浮动元素也会参与计

数组扁平化

  • ES6方法
ary.flat([Infinity])
  • 正则替换
ary.replace(/(\[|\])/g,'').split(',')
  • 普通递归
function flatArray(array) {
    let result = []
    for(let i = 0;i<array.length;i++){
        if(Array.isArray(array[i])){
            result = result.concat(flatArray(array[i]))
        } else {
            result = result.concat(array[i])
        }
    }
    return result
}
  • reduce函数迭代
function flatArray(array) {
    const arr  = array.reduce((prev,cur)=>{
        if(Array.isArray(cur)){
            return prev.concat(flatArray(cur))
        } else {
            return prev.concat(cur)
        }
    },[])
    return arr
}
  • 扩展运算符
while (ary.some(Array.isArray)) {
    ary = [].concat(...ary);
}
  • generator函数
var flat = function *(a) {
    const length = a.length
    for(let i = 0;i<length;i++){
        const item = a[i]
        if(typeof item!=='number'){
            yield *flat(item)
        } else {
            yield item
        }
    }
}

for(let i of flat(arr)){
    console.log(i)
}

页面渲染过程

从输入url到页面打开过程

  1. 用户输入
    用户输入并回车,当前页面即将被替换成新的页面。在这之前,浏览器给当前页面一次执行 beforeunload 事件的机会,beforeunload 事件允许页面在退出之前执行一些数据清理操作,还可以询问用户是否要离开当前页
window.addEventListener('beforeunload',function () {

})
// addEventListener 第三个参数
/**
可选。布尔值,指定事件是否在捕获或冒泡阶段执行。

可能值:
true - 事件句柄在捕获阶段执行
false- false- 默认。事件句柄在冒泡阶段执行
*/
  1. 用户输入url,浏览器会根据用户输入的信息判断是搜索还是网址;如果是搜索内容,就将搜索内容+默认搜索引擎合成新的url;
    如果用户输入的内容符合url规则,浏览器会根据url协议,在这段内容上内容上加上协议合成合法的url。

DNS域名解析

DNS 服务器是高可用、高并发和分布式的,它是树状结构

- 根 DNS 服务器 :返回顶级域 DNS 服务器的 IP 地址
- 顶级域 DNS 服务器:返回权威 DNS 服务器的 IP 地址
- 权威 DNS 服务器 :返回相应主机的 IP 地址
DNS的域名查找,在客户端和浏览器,本地DNS之间的查询方式是递归查询;在本地DNS服务器与根域及其子域之间的查询方式是迭代查询;


- DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
- 在域名和 IP 的映射过程中,给了应用基于域名做负载均衡的机会,可以是简单的负载均衡,也可以根据地址和运营商做全局的负载均衡。

三次握手

  • 进行三次握手,建立TCP连接。

  • 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;

  • 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;

  • 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

SSL握手过程

  • 第一阶段 建立安全能力 包括协议版本 会话Id 密码构件 压缩方法和初始随机数
  • 第二阶段 服务器发送证书 密钥交换数据和证书请求,最后发送请求-相应阶段的结束信号
  • 第三阶段 如果有证书请求客户端发送此证书 之后客户端发送密钥交换数据 也可以发送证书验证消息
  • 第四阶段 变更密码构件和结束握手协议
    完成了之后,客户端和服务器端就可以开始传送数据。更多 HTTPS 的资料可以看这里:

关闭TCP连接

  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;

  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我"同意"你的关闭请求;

  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;

  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

  1. url 请求过程
  • 网络进程会查找本地缓存是否缓存了该资源。

    1. 浏览器缓存
    2. 直接查找
  • 未缓存资源,进入网络请求流程。

    1. DNS解析,以获取请求域名的服务器IP地址。如果请求的是HTTPS协议,还需要建立TLS连接
      • DNS缓存:浏览器缓存、hosts 文件
      • 如果本地域名解析服务器也没有该域名的记录,开始递归+迭代解析
      • TCP三次握手,HTTP。TLS握手,HTTPS。
  • IP地址和服务器建立TCP连接后,浏览器端会构建请求行、请求头等信息,并把该域名相关的cookie等数据附近到请求头中,然后向服务器发送构建的请求信息。

  • 数据在进入服务端之前,可能还会先经过负责负载均衡的服务器,它的作用是将请求合理分发到多台服务器上。服务器会响应一个HTML文件

  • 浏览器判断状态码。200 -> 继续解析。400 或者 500 -> 报错。 300 -> 重定向,这里有重定向计数器,避免多次重定向,重定向多次会报错

  • 浏览器开始解析文件,如果是gzip文件会先解压,然后通过文件的编码格式知道该如何去解码文件。

  1. 准备渲染进程
    chrome会为每个页面分配一个渲染进程

  2. 渲染阶段
    先根据HTML构建DOM树,有CSS的话会构建CSSOM树。如果有script标签,会判断是否存在 async 或者 defer

  • async 并行下载并执行JS
  • defer 先下载文件,等待HTML解析完成后顺序执行
  • 都没有,阻塞渲染流程直到 JS 执行完毕

CSSOM树 和 DOM树构建完成后会开始生成render树,确定页面元素的布局,样式等

在生成render树的过程中,浏览器开始调用GPU绘制,合成图层。

页面打开过程

DOMContentLoaded event 首屏渲染
window:load event 页面加载完成
页面打开过程中依次触发了 DOMContentLoaded -> First Paint -> First Contentful Paint -> Onload Event -> First Meaningful Paint

  • DOMContentLoaded
    HTML加载解析完成后触发。浏览器尚未开始渲染,其他静态资源,图片等还未加载
  • First Paint, First Contentful Paint。
    用户开始看到文字。此时图片尚未加载回来,所以还不能看到图片
  • onLoad Event
    浏览器把图片加载回来了,所有资源都加载完成
  • First Meaningful Paint
    异步请求回来,页面DOM更新,触发First Meaningful Paint

浏览器基础

  • 在接收到用户输入的网址后,浏览器会开启一个线程来处理这个请求,对用户输入的url地址进行分析判断,如果是HTTP协议就按照HTTP方式来处理。
  • 调用浏览器引擎中的对应方法,比如webview中的loadUrl方法,分析并加载这个url地址。
  • 通过DNS解析获取该网站地址对应的IP,查完后连同浏览器的cookie、userAgent等信息向网站目的IP发出GET请求。
  • 进行HTTP协议会话,浏览器客户端向Web服务器发送报文。
  • 进入网站后台上的web服务器处理请求,如Apache/Tomcat/Nodejs等服务器。
  • 进入部署好的后端应用,如PHP、等后端程序,找到对应的请求处理逻辑,这期间可能会读取服务器缓存或查询数据库等。
  • 服务器处理请求并返回响应报文,此时如果浏览器访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,一致返回304
  • 浏览器开始下载HTML文档,或者从本地缓存读取文件内容。
  • 浏览器根据下载接收到的HTML文件解析解构建立DOM文档树,并根据HTML中的标记请求下载指定的MIME类型文件。同时设置缓存等内容。
  • 页面开始解析渲染DOM,CSS根据规则解析并结合DOM文档树进行网页内容布局和绘制渲染,JavaScript根据DOM API操作DOM,并读取浏览器缓存、执行事件绑定等,页面整个展示过程完成。

浏览器组成

  • 用户界面
  • 网络
  • JavaScript引擎
  • 渲染引擎
  • UI后端
  • JavaScript解释器
  • 持久化数据存储

浏览器引擎可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据。是浏览器中各个部分之间相互通信的核心。
浏览器渲染引擎的功能是解析DOM文档和CSS规则并将内容排版到浏览器中显示有样式的界面,我们常说的浏览器内核主要指的是渲染引擎。
JavaScript解释器是浏览器解析和执行JavaScript脚本的部分,例如V8引擎。

单页路由实现原理

单页面应用路由

hash

  • 改变url,不刷新页面
  • 浏览器提供 onhashchange 事件监听

模拟react-router 路由

<ul>
    <li><a href="#/">turn white</a></li>
    <li><a href="#/blue">turn blue</a></li>
    <li><a href="#/green">turn green</a></li>
</ul>
<script>
    function Router() {
        this.routes = {};
        this.currentUrl = '';
    }

    Router.prototype.route = function (path, callback) {
        this.routes[path] = callback || function () {

        }
    };
    Router.prototype.refresh = function () {
        console.log('触发一次 hashchange,hash值为', location.hash)
        this.currentUrl = location.hash.slice(1) || '/';
        this.routes[this.currentUrl]()
    };
    Router.prototype.init = function () {
        window.addEventListener('load', this.refresh.bind(this), false);
        window.addEventListener('hashchange', this.refresh.bind(this), false)
    };
    window.Router = new Router();
    window.Router.init();
    const content = document.querySelector('body');

    function changeBgColor(color) {
        content.style.backgroundColor = color
    }
    Router.route('/', function () {
        changeBgColor('white')
    });
    Router.route('/blue', function () {
        changeBgColor('blue')
    });
    Router.route('/green', function () {
        changeBgColor('green')
    })
</script>

history

通过 history.pushState 或者 history.replaceState。能做到改变url的同时,不刷新页面。

url 的改变可能有3中途径

  • 点击浏览器前进后退
    H5新增的 onpopstate 事件,通过他可以监听前进或者后退按钮的点击。但是history.pushState 和 history.replaceState 不会触发 onpopstate 事件
  • 点击a标签
  • JS代码直接修改路由

监听路由变化

window.addEventListener('replaceState',function(e) {
  
})
window.addEventListener('pushState',function(e) {
   
})
let instances = []
const register = (comp) => instances.push(comp)
const unRegister = (comp) => instances.splice(instances.indexOf(comp), 1)

const historyPush = (path) => {
    window.history.pushState({}, null, path)
    instances.forEach(instance => instance.forceUpdate())
}

window.addEventListener('popstate', () => {
    instances.forEach(instance => instance.forceUpdate())
})

const matchPath = (pathname, options) => {
    const {path, exact = false} = options
    const match = new RegExp(`^${path}`).exec(pathname)
    if (!match) return null
    const url = match[0]
    const isExact = pathname === url
    if (exact && !isExact) return null
    return {
        path,
        url
    }
}
export class Link extends Component {
    static propTypes = {
        to: PropTypes.string
    }
    handleClick = (event) => {
        event.preventDefault()
        const {to} = this.props
        historyPush(to)
    }

    render() {
        const {to, children} = this.props
        return (
            <a href={to} onClick={this.handleClick}>
                {children}
            </a>
        )
    }
}

export class Route extends Component {
    static propTypes = {
        path: PropTypes.string,
        component: PropTypes.func,
        exact: PropTypes.bool
    }

    componentWillMount() {
        register(this)
    }

    componentWillUnMount() {
        unRegister(this)
    }

    render() {
        const {path, component, exact} = this.props
        const match = matchPath(window.location.pathname, {path, exact})
        if (!match) return null
        if (component) {
            return React.createElement(component)
        }
    }

}

export const jsHistory = {
    pushState: historyPush
}

CSS 水平垂直居中

水平居中

原文链接

行内元素

text-align 写在div中,div中的行内元素水平居中

<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        text-align: center;
    }

    .child {
        border: 1px solid red;
        height: 100px;
    }
</style>
<div class="parent">
    <span class="child">块级框</span>
</div>

确定宽度的块元素

<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
    }
    .text{
        border: 1px solid red;
        width: 100px;
        margin: 0 auto;
        height: 100px;
    }
</style>


<div class="parent">
    <div class="text"></div>
</div>

宽度未知的块级元素

  • 方法1
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
    }
    .child{
        border: 1px solid red;
        display: table;
        margin: 0 auto;
    }
</style>


<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法2
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        text-align: center;
    }
    .child{
        border: 1px solid red;
        display: inline-block;
        width: 100px;
    }
</style>


<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法3
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: flex;
        justify-content: center;
    }
    .child{
        border: 1px solid red;
    }
</style>


<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法4
.parent{
    position: relative;
    height:1.5em;
}
.child{
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
}
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        position: relative;
    }
    .child{
        border: 1px solid red;
        position: absolute;
        left: 50%;
        transform: translateX(-50%);
    }
</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法5

适用于 child 定宽

.parent{
    position: relative;
    height:1.5em;
}
.child{
    position: absolute;
    width:100px;
    left: 50%;
    margin-left:-50px;
}
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        position: relative;
    }
    .child{
        border: 1px solid red;
        position: absolute;
        left: 50%;
        width:100px;
        left: 50%;
        margin-left:-50px;
    }
</style>
<div class="parent">
    <div class="child">块级框</div>
</div>

垂直居中

  • 方法1
.parent{
	display: table-cell;
	vertical-align: middle;
}
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: table-cell;
        vertical-align: middle;
    }
    .child{
        border: 1px solid red;
    }

</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法2
.parent {
        display: flex;
    }

    .child {
        margin: auto;
    }
<style>
    .parent{
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: flex;
    }
    .child{
        border: 1px solid red;
        margin: auto;
    }

</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法3
<style>
    .parent {
        height:500px;
        text-align: center;
        border: 1px solid black;
    }
    .parent:before {
        content: '';
        display: inline-block;
        height: 100%;
        vertical-align: middle;
        border: 1px solid yellow;
    }
    .child {
        border: 1px solid red;
        display: inline-block;
        vertical-align: middle;
    }
</style>

<div class="parent">
    <div class="child">
        Centered Content
    </div>
</div>
  • 方法4
.parent{
	position: relative;
}
.child{
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
}
<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: flex;
        position: relative;
    }
    .child {
        border: 1px solid red;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
    }

</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法5
.parent{
	display: flex;
	align-items: center;
}
<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: flex;
        align-items: center;

    }

    .child {
        border: 1px solid red;
    }

</style>
<div class="parent">
    <div class="child">块级框</div>
</div>

水平垂直居中

定宽高

<style>
    .container {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        position: relative;
    }
    .child{
        border: 1px solid red;
        position: absolute;
        left: 0;
        right: 0;
        top: 0;
        bottom: 0;
        margin: auto;
        width: 100px;
        height: 50px;
    }
</style>

<div class="container">
    <div class="child">内容区域</div>
</div>
  • 方法1
.parent{
	text-align: center;
	display: table-cell;
	vertical-align: middle;
}
.child{
	display: inline-block;
}
<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        text-align: center;
        display: table-cell;
        vertical-align: middle;
    }
    .child {
        border: 1px solid red;
        display: inline-block;
    }
</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法2
.parent{
	position: relative;
}
.child{
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%,-50%);
}
<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        position: relative;
    }
    .child {
        border: 1px solid red;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%);
    }
</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • 方法3
.parent{
	display: flex;
	justify-content: center;
	align-items: center;
}
<style>
    .parent {
        width: 500px;
        height: 500px;
        border: 1px solid black;
        display: flex;
        justify-content: center;
        align-items: center;
    }
    .child {
        border: 1px solid red;

    }
</style>
<div class="parent">
    <div class="child">块级框</div>
</div>
  • grid布局
<body>
<style>
    .outer {
        display: grid;
        width: 300px;
        height: 300px;
        background: red;
    }

    .inner {
        width: 200px;
        height: 200px;
        align-self: center;
        justify-self: center;
        background: yellow;
    }
</style>
<div class="outer">
    <div class="inner"></div>
</div>
</body>
<body>
<style>
    .outer {
        display: grid;
        align-content: center;
        justify-content: center;
        width: 300px;
        height: 300px;
        background: red;
    }

    .inner {
        width: 200px;
        height: 200px;
        background: yellow;
    }
</style>
<div class="outer">
    <div class="inner"></div>
</div>
</body>

多列布局

左边定宽,右边自适应

  • float+margin
.left{
	float: left;
	width: 100px;
}
.right{
	margin-left: 120px;
}
<style>
    .container{
        border: 2px dashed blue;
    }
    .left {
        width: 500px;
        border: 1px solid black;
        float: left;
    }
    .right {
        border: 1px solid red;
        margin-left: 500px;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="right">右边区域</div>
</div>
  • float+overflow
.left{
	float: left;
	width: 100px;
}
.right{
	overflow: hidden;
}
<style>
    .container{
        border: 2px dashed blue;
    }
    .left {
        width: 500px;
        border: 1px solid black;
        float: left;
    }
    .right {
        border: 1px solid red;
        overflow: hidden;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="right">右边区域</div>
</div>
  • table
.container{
	display: table; 
    width: 100%;
	table-layout: fixed;
}
.left,.right{
	display: table-cell;
}
.left{
	width: 100px;
}
<style>
    .container{
        border: 2px dashed blue;
        display: table;
        width: 100%;
        table-layout: fixed;
    }
    .left {
        width: 100px;
        border: 1px solid black;
        display: table-cell;
        padding-right: 20px;
    }
    .right {
        border: 1px solid red;
        display: table-cell;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="right">右边区域</div>
</div>
  • flex
.container{
        display: flex;
    }
    .left {
        width: 100px;

    }
    .right {
        flex: 1;
    }
<style>
    .container{
        border: 2px dashed blue;
        display: flex;
    }
    .left {
        width: 100px;
        border: 1px solid black;

    }
    .right {
        border: 1px solid red;
        flex: 1;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="right">右边区域</div>
</div>

多列定宽,一列自适应

  • flex
.container{
	display: flex;
}
.left,.center{
	width: 100px;
}
.right{
	flex: 1;
}
<style>
    .container{
        border: 2px dashed blue;
        display: flex;
    }
    .left,.center {
        width: 100px;
        border: 1px solid black;

    }
    .right {
        border: 1px solid red;
        flex: 1;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="center">中间区域</div>
    <div class="right">右边区域</div>
</div>
  • float+overflow
.left,.center{
	float: left;
	width: 100px;
}
.right{
	overflow: hidden;
}
<style>
    .container{
        border: 2px dashed blue;
    }
    .left,.center {
        float: left;
        width: 100px;
        border: 1px solid red;
    }
    .right {
        overflow: hidden;
        border: 1px solid red;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="center">中间区域</div>
    <div class="right">右边区域</div>
</div>
  • table
<style>
    .container {
        border: 2px dashed blue;
        display: table;
        width: 100%;
        table-layout: fixed;
    }

    .left, .center, .right {
        display: table-cell;
        border: 1px solid red;
    }

    .left,.center {
        width: 100px;
        border: 1px solid red;
    }
</style>
<div class="container">
    <div class="left">左边区域</div>
    <div class="center">中间区域</div>
    <div class="right">右边区域</div>
</div>

一列不定宽,一列自适应

.right {
    overflow: hidden;
}
.left {
    float: left;
}
<style>
    .container {
        border: 2px dashed blue;
    }

    .right {
        overflow: hidden;
        border: 1px solid red;
    }

    .left {
        float: left;
        border: 1px solid red;
    }
</style>
<div class="container">
    <div class="left">左边区域对的</div>
    <div class="right">右边区域</div>
</div>
  • table
.container {
    display: table;
    width: 100%;
}
.left {
    width: 10%;
}
.left,.right {
    display: table-cell;
}
<style>
    .container {
        border: 2px dashed blue;
        display: table;
        width: 100%;
    }
    .left {
        width: 10%;
        border: 1px solid red;
    }
    .left,.right {
        display: table-cell;
        border: 1px solid red;
    }
</style>
<div class="container">
    <div class="left">左边区域对的</div>
    <div class="right">右边区域</div>
</div>
  • flex
.container {
    display: flex;
}
.left {
    width: 200px;
}
.right{
    flex: 1;
}
<style>
    .container {
        border: 2px dashed blue;
        display: flex;
    }
    .left {
        width: 200px;
        border: 1px solid red;
    }

    .right{
        flex: 1;
        border: 1px solid black;
    }
</style>
<div class="container">
    <div class="left">左边区域对的</div>
    <div class="right">右边区域</div>
</div>

多列不定宽,一列自适应

.left,.center {
    float: left;
}
.right{
    overflow: hidden;
}
<style>
    .container {
        border: 2px dashed blue;
    }
    .left,.center {
        float: left;
        border: 1px solid red;
    }

    .right{
        overflow: hidden;
        border: 1px solid black;
    }
</style>
<div class="container">
    <div class="left">左边区域对的</div>
    <div class="center">中间区域</div>
    <div class="right">右边区域</div>
</div>

等分

  • float+margin
.parent {
    margin-left: -20px;
}
.column{
    float: left;
    width: 25%;
    padding-left: 20px;
    box-sizing: border-box;
}
<style>
    .parent {
        margin-left: -20px;
        border: 2px dashed blue;
        overflow: hidden;
    }
    .column{
        float: left;
        width: 25%;
        padding-left: 20px;
        box-sizing: border-box;
        height: 100px;
        border: 1px solid yellow;
    }
</style>
<div class="parent">
    <div class="column">1</div>
    <div class="column">2</div>
    <div class="column">3</div>
    <div class="column">4</div>
</div>
  • table+margin
.parent {
    display: table;
    table-layout: fixed;
    width: 100%;
}
.column{
    display: table-cell;
    padding-left: 20px;
}
<style>
    .parent {
        border: 2px dashed blue;
        display: table;
        table-layout: fixed;
        width: 100%;
    }
    .column{
        display: table-cell;
        padding-left: 20px;
        border: 1px solid black;
    }
</style>
<div class="parent">
    <div class="column">1</div>
    <div class="column">2</div>
    <div class="column">3</div>
    <div class="column">4</div>
</div>
  • flex
.parent {
    border: 2px dashed blue;
    display: flex;
}

.column {
    flex: 1;
    border: 1px solid black;
}
<style>
    .parent {
        border: 2px dashed blue;
        display: flex;
    }

    .column {
        flex: 1;
        border: 1px solid black;
    }
</style>
<div class="parent">
    <div class="column">1</div>
    <div class="column">2</div>
    <div class="column">3</div>
    <div class="column">4</div>
</div>

等高

  • float+overflow
    🤔不对啊
<style>
    .parent {
        border: 2px dashed blue;
        overflow: hidden;
    }

    .left, .right {
        padding-bottom: 9999px;
        margin-bottom: -9999px;
    }
    .left{
        float: left;
        width: 100px;
        border: 1px solid red;
    }
    .right{
        overflow: hidden;
        border: 1px solid black;
    }
</style>
<div class="parent">
    <div class="left">1</div>
    <div class="right">2</div>
    <div class="right">3</div>
</div>
  • table
<style>
    .parent {
        border: 2px dashed blue;
        display: table;
        width: 100%;
    }


    .left{
        display: table-cell;
        width: 100px;
        border: 1px solid red;
    }
    .right{
        border: 1px solid black;
        display: table-cell;
    }
</style>
<div class="parent">
    <div class="left">1</div>
    <div class="right">2</div>
    <div class="right">3</div>
</div>
  • flex
<style>
    .parent {
        border: 2px dashed blue;
        display: flex;
        width: 100%;
    }


    .left{
        width: 100px;
        border: 1px solid red;
    }
    .right{
        border: 1px solid black;
        flex: 1;
    }
</style>
<div class="parent">
    <div class="left">1</div>
    <div class="right">2</div>
    <div class="right">3</div>
</div>

并排等分,单排对齐靠左布局

<style>
    .parent {
        border: 2px dashed blue;
        display: flex;
        flex-flow: row wrap;
        justify-content: space-between;
    }


    .item{
        display: inline-block;
        border: 1px solid red;
        width: 100px;
    }
</style>
<div class="parent">
    <div class="item">1</div>
    <div class="item">2</div>
    <div class="item">3</div>
</div>

圣杯布局

<style>
    .container {margin: 50px auto;}
    .wrapper {padding: 0 100px 0 100px;}
    .col {position: relative; float: left;}
    .header,.footer {height: 50px;background-color: red}
    .main {width: 100%;height: 200px;}
    .left {width: 100px; height: 200px; margin-left: -100%;left: -100px;background-color: yellow}
    .right {width: 100px; height: 200px; margin-left: -100px; right: -100px;background-color: black}
    .clearfix::after {content: ""; display: block; clear: both; visibility: hidden; height: 0; overflow: hidden;}</style>
<div class="container">
    <div class="header">header</div>
    <div class="wrapper clearfix">
        <div class="main col">main</div>
        <div class="left col">left</div>
        <div class="right col">right</div>
    </div>
    <div class="footer">footer</div>
</div>

双飞翼布局

<style>
    .col {float: left;}
    .header {height: 50px;border: 1px solid red}
    .main {width: 100%;}
    .main-wrap {margin: 0 100px 0 100px;height: 200px;}
    .left {width: 100px; height: 200px; margin-left: -100%;background-color: silver}
    .right {width: 100px; height: 200px; margin-left: -100px;background-color: red}
    .footer {height: 50px;border: 1px solid red}
    .clearfix::after {content: ""; display: block; clear: both; visibility: hidden; height: 0; overflow: hidden;}
</style>
<div class="container">
    <div class="header">header</div>
    <div class="wrapper clearfix">
        <div class="main col">
            <div class="main-wrap">main</div>
        </div>
        <div class="left col">left</div>
        <div class="right col">right</div>
    </div>
    <div class="footer">footer</div>
</div>

定位布局

<style>
    .wrapper { position: relative;background-color: black }
    .main { margin:0 100px;background-color: #00acec}
    .left { position: absolute; left: 0; top: 0;background-color: yellow}
    .right { position: absolute; right: 0; top: 0;background-color: silver}
</style>
<div class="header">header</div>
<div class="wrapper">
    <div class="main col">
        main
    </div>
    <div class="left col">
        left
    </div>
    <div class="right col">
        right
    </div>
</div>
<div class="footer">footer</div>

伪类实现垂直居中原理

参考文献

  • :after伪类+content清除浮动的影响
<style>
    .box {
        padding: 10px;
        background: gray;
    }

    .l {
        float: left;
    }
    .box:after{display:block; content:"clear"; height:0; clear:both; overflow:hidden; visibility:hidden;}
</style>


<div class="box">
    <img class="l" src="https://youimg1.c-ctrip.com/target/100h180000013qm0m8FCA_C_400_400_Q90.jpg"/>
</div>
  • :after伪类+content实现垂直居中
<style>
    .pic_box {
        width: 800px;
        height: 800px;
        background-color: #beceeb;
        font-size: 0;
        *font-size: 200px;
        text-align: center;
    }

    .pic_box img {
        vertical-align: middle;
    }

    .pic_box:after {
        display: inline-block;
        width: 0;
        height: 100%;
        content: "center";
        vertical-align: middle;
        overflow: hidden;
    }
</style>


<div class="pic_box">
    <img class="l" src="https://youimg1.c-ctrip.com/target/100h180000013qm0m8FCA_C_400_400_Q90.jpg"/>
</div>

布局

2:1 布局

伪元素

<style>
    .container {
        background-color: #00acec;
    }
    .container::before{
        content: '';
        float: left;
        padding-bottom: 50%;
    }
    .container::after{
        clear: both;
        content: '';
        display: table;
    }
</style>

<div class="container">

内容绝对定位

<style>
    .container {
        background-color: #00acec;
        overflow: hidden;
        padding-top: 50%;
        position: relative;
        height: 0;
    }
    .content{
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
    }

</style>

<div class="container">
    <div class="content"></div>
</div>

视窗单位vw/vh

<style>
    .container {
        background-color: #00acec;
        width: 100vw;
        height: 50vw;
    }
</style>

<div class="container">
</div>
<style>
    .container {
        background-color: #00acec;
        width: 100vh;
        height: 50vh;
    }
</style>

<div class="container">
</div>

圣杯布局

方法1

DOM 结构

<div class="wrapper">
    <div class="header"></div>
    <div class="content">
        <div class="left">左边</div>
        <div class="middle">中间</div>
        <div class="right">右边</div>
    </div>
    <div class="footer"></div>
</div>
.header, .footer {
    background: green;
    width: 100%;
    height: 40px;
}
.content{
    display: flex;
}
.left{
    width: 200px;
    background-color: yellow;
}
.middle{
    flex: 1;
    background-color: pink;
}
.right{
    width: 300px;
    background-color: aqua;
}

方法2

DOM结构

<div class="wrapper">
    <div class="header"></div>
    <div class="container">
        <div class="middle">中间</div>
        <div class="left">左边</div>
        <div class="right">右边</div>
    </div>
    <div class="footer"></div>
</div>
.header, .footer {
    background: green;
    width: 100%;
    height: 40px;
}
.footer{
    clear: both;
}
.container {
    padding-left: 200px;
    padding-right: 250px;
}
.container div {
    position: relative;
    float: left;
}
.left {
    width: 200px;
    background: yellow;
    margin-left: -100%;
    left: -200px;
}
.middle{
    width: 100%;
    background-color: pink;
}
.right {
    width: 250px;
    background: aqua;
    margin-left: -250px;
    left: 250px;
}

方法3

DOM结构

<div class="wrapper">
    <div class="header"></div>
    <div class="container">
        <div class="left">左边</div>
        <div class="right">右边</div>
        <div class="middle">中间</div>
    </div>
    <div class="footer"></div>
</div>
*{
    margin: 0;
    padding: 0;
}
.header, .footer {
    background: green;
    width: 100%;
    height: 40px;
}


.container{
    overflow: hidden;
}
.left {
    width: 200px;
    background: yellow;
    float: left;
}
.middle{
    width: 100%;
    background-color: pink;
}
.right {
    width: 250px;
    background: aqua;
    float: right;
}

方法4

绝对定位

DOM结构同上

*{
    margin: 0;
    padding: 0;
}
.header, .footer {
    background: green;
    width: 100%;
    height: 40px;
}


.container{
    position: relative;
}
.container div{
    position: absolute;
}
.left {
    width: 200px;
    background: yellow;
    left: 0;
}
.middle{
    left: 200px;
    right: 250px;
    background-color: pink;
}
.right {
    width: 250px;
    background: aqua;
    right: 0;
}

forEach中使用await问题

async function test() {
    let arr = [4, 2, 1]
    arr.forEach(async item => {
        const res = await handle(item)
        console.log(res)
    })
    console.log('结束')
}

function handle(x) {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
            resolve(x)
        }, 1000 * x)
    }))
}

test()

forEach底层实现

for (let i = 0; i < length; i++) {
    if (i in array) {
        const element = array[i]
        callback(element, i, array)
    }
}

解决办法

async function test() {
    const arr = [4, 2, 1]
    for (let item of arr) {
        const res = await handle(item)
        console.log(res)
    }
    console.log('结束')
}

解决原理--Iterator

前端缓存

强缓存

Expires(HTTP/1.0)

存在于服务端返回的响应头中,返回一个过期时间
问题:服务器的时间和浏览器的时间可能并不一致

Cache-Control(HTTP/1.1)

  • public: 客户端和代理服务器都可以缓存
  • private: 只有浏览器能缓存
  • no-cache: 跳过当前强缓存,发送HTTP请求,进入协商缓存阶段
  • no-store: 不进行任何形式的缓存
  • s-maxage: 针对代理服务器的缓存时间
  • max-age: 缓存时间
  • must-revalidate:一旦缓存过期,就必须回到源服务器验证

协商缓存

强缓存失效之后,浏览器在请求头中携带相应的缓存tag来向服务器发请求,由服务器根据这个tag,来决定是否使用缓存,这就是协商缓存。

Last-Modified

即最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。

浏览器接收到后,如果再次请求,会在请求头中携带If-Modified-Since字段,这个字段的值也就是服务器传来的最后修改时间。

服务器拿到请求头中的If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:

  • 如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
  • 否则返回304,告诉浏览器直接用缓存。

ETag

ETag 是服务器根据当前文件的内容,给文件生成的唯一标识,只要里面的内容有改动,这个值就会变。服务器通过响应头把这个值给浏览器。

浏览器接收到ETag的值,会在下次请求时,将这个值作为If-None-Match这个字段的内容,并放到请求头中,然后发给服务器。

服务器接收到If-None-Match后,会跟服务器上该资源的ETag进行比对:

  • 如果两者不一样,说明要更新了。返回新的资源,跟常规的HTTP请求响应的流程一样。
  • 否则返回304,告诉浏览器直接用缓存。

两者对比

  1. 在精准度上,ETag优于Last-Modified。优于 ETag 是按照内容给资源上标识,因此能准确感知资源的变化。而 Last-Modified 就不一样了,它在一些特殊的情况并不能准确感知资源变化,主要有两种情况:
  • 编辑了资源文件,但是文件内容并没有更改,这样也会造成缓存失效。
  • Last-Modified 能够感知的单位时间是秒,如果文件在 1 秒内改变了多次,那么这时候的 Last-Modified 并没有体现出修改了。
  1. 在性能上,Last-Modified优于ETag,也很简单理解,Last-Modified仅仅只是记录一个时间点,而 Etag需要根据文件的具体内容生成哈希值。
  2. 另外,如果两种方式都支持的话,服务器会优先考虑ETag

Git

git rebase 和 merge 区别

主要的区别在于是否保留分支的 commit 提交节点,rebase 会给你一个简洁的线性历史树

git cherry-pick

git cherry-pick命令的参数,不一定是提交的哈希值,分支名也是可以的,表示转移该分支的最新提交。

转移多个提交

git cherry-pick <HashA> <HashB>

如果想要转移一系列的连续提交

git cherry-pick A..B (不包含A)

包含A

git cherry-pick A^..B

git clone

该命令会在本地主机生成一个目录,与远程主机的版本库同名。若要指定不同的目录名,将目录名作为git clone 命令的第二个参数
git clone <版本库的网址> <本地目录名>

git 批量删除本地分支(删除分支名包含指定字符的分支)

git branch |grep 'xxx' |xargs git branch -D

删除当前分支以外的分支

git branch | xargs git branch -d

git remote

每个远程主机都有一个主机名,git remote 用于管理主机名

git remote 列出所有远程主机
git remote -v参看远程主机网址
git remote show <主机名>查看主机详细信息
git remote add <主机名> <网址>添加远程主机
git remote rm <主机名>删除远程主机
git remote rename <原主机名> <新主机名>修改远程主机

克隆版本库的时候,所使用的远程主机,自动被git命名为origin,若想使用其他主机名,使用 -o
git clone -o jQuery https://github.com/jquery/jquery.git

HEAD^ 与 HEAD~ 区别

A = A^0
A^ = A^1= A~1
^^= A^1^1= A~2

reset, hard, soft 区别

  • HEAD:当前分支当前版本的游标
  • index:暂存区

git reset --soft HEAD~ 本地的内容没有发生变化,index中有最近一次修改的内容,提交变成staged状态。

--mixed 修改了index,使得提交变成了unstaged状态

--hard 彻底回到上一次提交的状态,无法找回

git revert commitid ,git revert HEAD~3会回到最近的第四个提交状态,并且生成一个新的commitid

代码自动格式化

// 安装husky,prettier,lint-staged
"husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,jsx}": [
      "prettier --write \"src/**/*.{js,jsx}\"",
      "git add"
    ]
  }

本机权限问题

git pull 的时候 error: cannot open .git/FETCH_HEAD: Permission denied , 因为没有当前目录的修改权限
sudo chmod -R g+w .git 修改目录权限。即可正常 git pull

git pull

git pull <远程主机名> <远程分支名>:<本地分支名>

git表情提示符

全局安装 git-cz 或者 gitmoji
npm install -g git-cz
npm i -g gitmoji-cli

撤销错误的提交

git reset --soft HEAD^ 撤销commit,并保留更改
git push origin <分支名> --force

force

如果远程主机的版本比本地版本更新,推送时 git 会报错,要求先在本地做 git pull 合并差异,再推送到远程主机。这时,如果一定要推送,可以使用 force
git push --force origin

清理分支

git remote prune origin 清理远程分支,把本地不存在的远程分支删除,同时 git branch -a 拉到的也是远程最新的分支,不会保留已删除的远程的分支
git remote show origin ,可以查看 remote 地址,远程分支,还有本地分支与之相对应关系等信息。

修改git的用户名和密码

Git config –global user.name 用户名
Git config –global user.email 邮箱名

回退到之前的版本号

Git reset –hard 提交版本号

删除远程分支(不需要先切换到其他分支)

  1. git push origin --delete 要删除的分支名
  2. git push origin -d 分支名
  3. git push <远程分支名> -d 分支名

删除本地分支

git branch -d 本地分支名
git branch -D 本地分支名(分支没有完全merge会报错提示,改为强制删除即可)

暂存修改

  1. 暂存 git stash save "message"
  2. 恢复 git stash apply
  3. 恢复之前一个 git stash apply stash@{2}
  4. 删除 git stash drop
  5. 恢复 + 删除 git stash pop
  6. 列表 git stash list
  7. 从储藏创建一个文件 git stash branch branchname

查看远程仓库信息

  1. git remote -v

git撤销操作

<!-- 只会产生一次提交,第二次提交修正了第一次的提交内容 -->
`git commit -m 'initial commit'` 
`git add forgotten_file` 
`git commit --amend` 

新建分支

git branch testing 新建testing分支 在当前commit对象上新建一个分支指针,不会自动切换到该分支中去
git checkout -b iss53 新建分支并切换到该分支
git push -u origin 分支名 提交新建的分支到远程
git push origin 分支名:分支名 推送新分支到远程
git fetch <远程主机名> 同步远程服务器上的数据到本地。
git fetch <远程主机名> <分支名>取回特定分支的更新
git checkout -b newBranch origin/master 在origin/master分支的基础上,新建一个分支

如果没有推送的远程的话,commit之后只会显示 working tree clean

删除远程分支

git push origin :[分支名] 删除远程分支

ssh key

cd ~/.ssh
查找有没有 somethingsomething.pub 来命名的一对文件,这个 something 通常是 id_dsaid_rsa
.pub 后缀的文件就是公钥,另一个文件是私钥。若 .ssh 目录没有,可以用 ssh-keygen 来创建。

rebase

  • 合并多个commit为1个
  • 合并分支 git rebase master 在master分支上的最先提交上补上开发分支的代码

合并commit

git 修改多个 commit 为一个 commit

  1. 从HEAD版本开始往过去数3个版本 git rebase -i HEAD~3
    将后面的2个commit之前的pick改成s,表示,要把后面的两个commit合并到前一个commit

或者,指明要合并的版本之前的版本号 git rebase -i commitId(不参与合并)

  1. 除了第一个以外,后面的多个 commit pick 改为 s
  2. esc 键退出编辑
  3. :wq 退出
  4. git add .
  5. git rebase --continue
  6. git rebase --abort 放弃压缩

撤销改动

git clean -f -d 移除工作目录中所有未追踪的文件以及空的子目录
如果只是想要看看它会做什么,可以使用 -n 选项来运行命令,这意味着 “做一次演习然后告诉你 将要 移除什么”。
git clean -d -n

git checkout. / publish
git clean - f. / publish

merge 分支

// merge 代码用
git merge 分支名--squash
git merge abort 取消合并

比较两次 commit 的差异

  1. 比较两次commit提交之后的差异:
    git diff hash1 hash2 --stat能够查看出两次提交之后,文件发生的变化。

  2. 具体查看两次commit提交之后某文件的差异:
    git diff hash1 hash2 --文件名

  3. 比较两个分支的所有有差异的文件的详细差异:
    git diff branch1 branch2

  4. 比较两个分支的指定文件的详细差异
    git diff branch1 branch2 文件名(带路径)

  5. 比较两个分支的所有有差异的文件列表
    git diff branch1 branch2 --stat

撤销已经提交的commit

git reset --hard HEAD~1 撤销上次的commit,保留之前的更改
git reset --hard <需要回退到的版本号(只需输入前几位)>
git push origin <分支名> --force 或者 git push --force 强制提交

删除中间某次提交(待补充)

  1. 首先 git log 查看提交记录,找到出错的前一笔提交的 commit_id
  2. 用命令 git rebase -i commit_id , 查找提交记录
  3. 将出错那次提交的 pick 改为 drop
  4. Esc,:wq
  5. 完成!

git reset分为三种模式

  • soft
  • mixed
  • hard

git reset --hard commitId
重置暂存区和工作区,完全重置为指定的commitId,当前分支没有commit的代码会被清除
git reset --soft commitId
保留工作目录,把指定的commit节点与当前分支的差异都存入暂存区。没有被commit的代码也能够保留下来
git reset commitId
不带参数,就是mixed模式。将会保留工作目录,并把工作区、暂存区、以及与reset的差异都放到工作区,然后清空暂存区。

rn 项目操作命令

xcrun simctl list devices 获取所有设备名称
crn-cli run-ios --port=5390 --simulator="iPad Air (3rd generation)" --reset 指定打开设备

关联本地项目与GitHub

  1. 在本地项目 git init
  2. git add .
  3. git commit -m " message"
  4. GitHub 建立一个项目
  5. 复制项目 HTTPS 地址
  6. git remote add origin https 地址
  7. git push -u origin master

代码版本

  1. 查看过去版本 git log --pretty=oneline

暂存区文件撤销

  1. git reset HEAD 文件

删除文件

  1. 从版本库中删除文件 git rm 文件名
  2. 从版本库中删除文件,但是本地不删除该文件 git rm --cached 文件名

创建分支

  1. 仅创建 git branch 分支名
  2. 创建并切换 git checkout -b 分支名

git branch -r查看远程分支

npm

npm rebuild 重建软件包
npm build

^ 与 ~ 的区别

  • ~ 会匹配最近的小版本依赖包,~1.2.3 会匹配 1.2.x
  • ^ 会匹配最新的大版本依赖包,^1.2.3 会匹配 1.x.x

Unexpected end of JSON input while parsing near '... "http://registry.npm.'

第一步
npm cache clean --force
第二步
npm install --registry=https://registry.npm(镜像)

原因:可以先看下npm install的执行过程:

  • 发出npm install命令
  • npm 向 registry 查询模块压缩包的网址
  • 下载压缩包,存放在~/.npm(本地NPM缓存路径)目录
  • 解压压缩包到当前项目的node_modules目录

    实际上说一个模块安装以后,本地其实保存了两份。一份是 ~/.npm 目录下的压缩包,另一份是 node_modules 目录下解压后的代码。但是,运行 npm install 的时候,只会检查 node_modules 目录,而不会检查 ~/.npm 目录。如果一个模块在 ~./npm 下有压缩包,但是没有安装在 node_modules 目录中,npm 依然会从远程仓库下载一次新的压缩包。

    我们想利用已经在缓存中之前已经备份的模块实现离线模块安装的的 cache 机制已经在V5的时候重写了,缓存将由 npm 来全局维护不再需要开发人员操心,离线安装时将不再尝试连接网络,而是降级尝试从缓存中读取,或直接失败。就是如果你 offline ,npm将无缝地使用您的缓存。

    这是一个与npm缓存腐败的问题。尽管在较新版本的npm中他们实现了自我修复,这通常可以保证没有腐败,但似乎并不那么有效。

钩子

npm 脚本有pre和post两个钩子。举例来说,build脚本命令的钩子就是prebuild和postbuild
用户执行npm run build的时候,会自动按照 npm run prebuild && npm run build && npm run postbuild 的顺序执行

更新 npm 插件

npm update <name> [-g] [--save-dev]

vscode 自带终端,每次打开都报错误

nvm is not compatible with the npm config "prefix" option: currently set to "/usr/local" Run "npm config delete prefix" or "nvm use --delete-prefix v10.15.1 --silent" to unset it.

👇
npm config delete prefix
npm config set prefix $NVM_DIR/versions/node/v10.15.1

git.jpg

Linux命令

cat file.js 输入 file.js 文件内容

ArrayList 和 LinkList优缺点

Array List Link List
arraylist 在内部使用动态数组存储数据 linklist在内部使用双向链表存储数据
用arraylist操作是缓慢的,移除任何一个元素,则所有位都将在内存中移位 使用LinkedList进行操作要比ArrayList快,因为它使用了双向链接列表,因此内存中不需要移位
ArrayList类只能用作列表,因为它仅实现List。 LinkedList类可以实现列表和队列,因为它实现了List和Deque接口
ArrayList更适合存储和访问数据 LinkedList更适合处理数据。

Ref & forwardRef

forwardRef 在高级组件中的应用

const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref} className="FancyButton">
    {props.children}
  </button>
));

function logProps(WrappedComponent) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log("old props:", prevProps);
      console.log("new props:", this.props);
    }
    render() {
      const { forwardedRef, ...rest } = this.props;
      return <WrappedComponent {...rest} ref={forwardedRef} />;
    }
  }
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}
export default logProps(FancyButton);

class component 和 functional component的区别

参考文献
React 中 props 是不可变的,但是,this 是可变的
React本身会随着时间的推移而改变,以便你可以在渲染方法以及生命周期方法中得到最新的实例
不同于this,props对象本身永远不会被React改变。

  1. 输入框中随意输入内容
  2. 点击send
  3. 继续输入某些内容
  4. 查看控制台打印的内容

区别示例

  • class Component

控制台打出的是变化后的值

class App extends React.Component {
  constructor() {
    super();
    this.state = { message: "" };
  }
  handleMessageChange = e => {
    this.setState({
      message: e.target.value
    });
  };
  handleSendClick = () => {
    setTimeout(() => {
      console.log(this.state.message);
    }, 3000);
  };
  render() {
    return (
      <>
        <input value={this.state.message} onChange={this.handleMessageChange} />
        <button onClick={this.handleSendClick}>Send</button>
      </>
    );
  }
}
  • functional component

控制台打出的是点击send时input输入框的值

export default function MessageThread() {
  const [message, setMessage] = useState("");
  const showMessage = () => {
    console.log("you said: " + message);
  };
  const handleSendClick = () => {
    setTimeout(showMessage, 3000);
  };
  const handleMessageChange = e => {
    setMessage(e.target.value);
  };
  return (
    <>
      <input value={message} onChange={handleMessageChange} />
      <button onClick={handleSendClick}>Send</button>
    </>
  );
}

解决class component问题

  • 一种方法是在调用事件之前读取this.props,然后将他们显式地传递到timeout回调函数中去
class App extends React.Component {
    constructor() {
        super();
        this.state = { message: "" };
    }
    handleMessageChange = e => {
        this.setState({
            message: e.target.value
        });
    };
    handleSendClick = () => {
        //将当前信息暂存,显示传递到timeout中去
        const message = this.state.message
        setTimeout(() => {
            console.log(message);
        }, 3000);
    };
    render() {
        return (
            <>
                <input value={this.state.message} onChange={this.handleMessageChange} />
                <button onClick={this.handleSendClick}>Send</button>
            </>
        );
    }
}
  • 在特定渲染中捕获所用的props或者state
    代码示例
class ProfilePage extends React.Component {
  render() {
    const props = this.props;
    const showMessage = () => {
      alert("Followed " + props.user);
    };

    const handleClick = () => {
      setTimeout(showMessage, 3000);
    };

    return <button onClick={handleClick}>Follow</button>;
  }
}

如果想要读取未来的值,functional component可以使用ref

export default function MessageThread() {
    const [message, setMessage] = useState("");
    const ref = useRef()
    const showMessage = () => {
        console.log("you said: " + ref.current);
    };
    const handleSendClick = () => {
        setTimeout(showMessage, 3000);
    };
    const handleMessageChange = e => {
        setMessage(e.target.value);
    };
    return (
        <>
            <input ref={ref} value={message} onChange={handleMessageChange}/>
            <button onClick={handleSendClick}>Send</button>
        </>
    );
}

原文链接
class 组件中,我们从 this 获取到 props.count。this固定指向同一个组件实例
函数组件中,每次执行 render,props 作为该函数的参数传入,它是函数作用域下的变量。

由于 props 是 Example 函数作用域下的变量,可以说对于这个函数的每一次调用中,都产生了新的 props 变量,它在声明时被赋予了当前的属性,他们相互间互不影响。

深浅拷贝

浅拷贝

手工实现

function shallowCopy(obj) {
    let copy = {}
    for (let i in obj) {
        copy[i] = obj[i]
    }
    return copy
}
  • for...in

    • Enumerable
    • 遍历对象属性
    • key
  • for...of

    • [Symbol.iterator]
    • 遍历数组
    • value

Object.assign

Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象

concat/slice

var array = [1, [1,2,3], {name:"array"}];
const arraySlice= array.slice(0)
const arrayConcat = array.concat()
arraySlice[1][0] = 5
console.log({array},{arraySlice})
arrayConcat[1][1]=3
console.log({array},{arrayConcat})

原数组都发生了改变

深拷贝

JSON.parse(JSON.stringify())

function cloneDeep(source) {
    const target = {}
    for (let key in source) {
        if(typeof source[key]==='object'){
            target[key]=cloneDeep(source[key])
        } else {
            target[key] = source[key]
        }
    }
    return target
}

const b = cloneDeep(a)

JS源码实现

参考文献

实现双向绑定

JSON.parse

function jsonParse(opt) {
  return eval(`(${opt})`)
}

function jsonParse(opt) {
  return new Function(`return ${opt}`)()
}

defineProperty 实现

<input class="input" type="text">
<span class="inputText"></span>
<script>
    const data = {
        text:'default'
    }
   const input = document.getElementsByClassName('input')[0];
    const span = document.getElementsByClassName('inputText')[0];
    Object.defineProperty(data,'text',{
        set(v) {
            input.value = v;
            span.innerHTML = v
        }
    });
    input.addEventListener('keyup',function (e) {
        data.text = e.target.value
    })
</script>

proxy 实现

<input class="input" type="text">
<span class="inputText"></span>
<script>
    const data = {
        text: 'defalt'
    };
    const input = document.getElementsByClassName('input')[0];
    const span = document.getElementsByClassName('inputText')[0];
    const handler = {
        set(target, key, value) {
            target[key] = value;
            input.value = value;
            span.innerHTML = value;
            return value
        }
    };
    const proxy = new Proxy(data, handler);
    input.addEventListener('keyup', function (e) {
        proxy.text = e.target.value
    })
</script>

防抖

function debounce(fn,delay) {
    let timer ;
    return function () {
        const context = this;
        clearTimeout(timer);
        setTimeout(()=>{
            fn.apply(context,arguments)
        },delay)
    };
}
container.addEventListener('scroll',debounce(action,1000))

节流

function throttle(fn, delay) {
  var prevTime = Date.now();
  return function() {
    var curTime = Date.now();
    if (curTime - prevTime > delay) {
      fn.apply(this, arguments);
      prevTime = curTime;
    }
  };
}
// 使用
var throtteScroll = throttle(function() {
  console.log('throtte');
}, 1000);
container.onscroll = throtteScroll;

async/await 实现

使用 generator 函数实现

const getData = () => new Promise(resolve => setTimeout(() => resolve('data'), 1000))
// async/await 功能
async function test() {
    const data = await getData();
    console.log(data);
    const data2 = await getData();
    console.log(data2);
    return 'success'
}

test().then(res => console.log(res));

/********** 使用 generator 函数实现 async/await ************/
function * testG() {
    const data = yield getData();
    console.log(data);
    const data2 = yield getData();
    console.log(data2);
    return 'success'
}


function asyncToGenerator(generatorFunc) {
    return function () {
        const gen = generatorFunc.apply(this, arguments);
        return new Promise(((resolve, reject) => {
            function step(key, arg) {
                let generatorResult;
                try {
                    generatorResult = gen[key](arg)
                } catch (e) {
                    return reject(e)
                }
                const {value, done} = generatorResult;
                if (done) {
                    return resolve(value)
                } else {
                    return Promise.resolve(value).then(val => step('next', val), err => step('throw', err))
                }
            }

            step('next')
        }))
    }
}


const hello =asyncToGenerator(testG)();
hello.then(value=>console.log(value));

实现promise.race

let promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 200, '200ms')
})
let promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 300, '300ms')
})
let promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 400, '400ms')
})


Promise.race1 = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(data => {
                resolve(data)
                return
            }, error => reject(error))
        }
    })
}

Promise.race1([promise1, promise2, promise3]).then(data => console.log(data))

promise.race 实现超时函数

function TimeoutPromise(promise, ms) {
    const delayPromise = ms => {
        return new Promise(function (resolve) {
            setTimeout(resolve, ms)
        })
    }

    const timeout = delayPromise(ms).then(function () {
        throw new Error('operation time out after ' + ms + ' ms')
    })
    return Promise.race([promise, timeout])
}


function testPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve()
            console.log('resolve emit')
        }, 3000)
    })
}

TimeoutPromise(testPromise(),5000)

使用 promise 函数实现

function asyncFunc(value) {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
            resolve(value * 2)
        }, 3000)
    }))
}

async function main1(value) {
    let d = new Date().getTime();
    console.log('starting');
    let result1 = await asyncFunc(value);
    let result2 = await asyncFunc(result1);
    console.log('ending, ' + result2);
    console.log(new Date().getTime() - d)
}


async function main2(value) {
    let d = new Date().getTime();
    console.log('starting...');
    asyncFunc(value).then(res=>{
        let result1= res;
        return asyncFunc(result1)
    }).then(res=>{
        let result2 = res;
        console.log('ending... ' + result2);
        console.log(new Date().getTime() - d)
    })
}

实现 promise.all

function promiseAll(promises) {
    return new Promise(function (resolve, reject) {
        if (!Array.isArray(promises)) {
            return reject(new TypeError('arguments must be an array'))
        }
        let resolvedCounter = 0
        let promiseNum = promises.length
        let resolvedValues = new Array(promiseNum)
        for (let i = 0; i < promiseNum; i++) {
            Promise.resolve(promises[i]).then(function (value) {
                resolvedCounter++
                resolvedValues[i] = value
                if (resolvedCounter === promiseNum) {
                    return resolve(resolvedValues)
                }
            }, function (reason) {
                return reject(reason)
            })
        }
    })

}

实现promise.race

Promise.race1 = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            Promise.resolve(promises[i]).then(data => {
                resolve(data)
            }, error => {
                reject(error)
            })
        }
    })
}

let promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 200, '100')
})

let promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, '200')
})
let promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 200, '300')
})


let promises = [promise1, promise2, promise3]
Promise.race1(promises).then(data => console.log(data))

实现promise.retry

Promise.retry = function (fn, num) {
    function executeFn() {
        return new Promise((resolve, reject) => {
            resolve(fn())
        }).then(res => {
            return Promise.resolve(res)
        }).catch(e => {
            num--;
            if (num < 0) {
                return Promise.reject(e)
            } else {
                return executeFn()
            }
        })
    }
    return executeFn()
};

function getData() {
    let num = Math.floor((Math.random() * 10));
    return new Promise((resolve, reject) => {
        if (num === 1) {
            resolve(num)
        } else {
            reject(num)
        }
    })
}


Promise.retry(getData, 3).then(res => console.log(res)).catch(err => console.log('失败'));

实现 promise

参考地址

function Promise(executor) {
    var self = this;
    self.status = 'pending';
    self.data = undefined;
    self.onResolvedCallback = [];
    self.onRejectedCallback = [];
    executor(resolve, reject);

    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved';
            self.data = value;
            for (let i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](value)
            }
        }
    }

    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.data = reason;
            for (let i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason)
            }
        }
    }

    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }

}


Promise.prototype.then = function (onResolved, onRejected) {
    var self = this;
    var promise2;
    onResolved = typeof onResolved === 'function' ? onResolved : function (v) {
        return v
    };
    onRejected = typeof onRejected === 'function' ? onRejected : function (r) {
        return r
    };
    if (self.status === 'resolved') {
        return promise2 = new Promise(function (resolve, reject) {
            try {
                const x = onResolved(self.data);
                if (x instanceof Promise) {
                    x.then(resolve, reject)
                }
                resolve(x)
            } catch (error) {
                reject(error)
            }

        })
    }
    if (self.status === 'rejected') {
        return promise2 = new Promise(function (resolve, reject) {
            try {
                var x = onRejected(self.data)
                if (x instanceof Promise) {
                    x.then(resolve, reject)
                }
            } catch (error) {
                reject(error)
            }
        })
    }
    if (self.status === 'pending') {
        return promise2 = new Promise(function (resolve, reject) {
            self.onResolvedCallback.push(function (value) {
                try {
                    const x = onResolved(self.data);
                    if (x instanceof Promise) {
                        x.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }
            });
            self.onRejectedCallback.push(function (reason) {
                try {
                    const x = onRejected(self.data);
                    if (x instanceof Promise) {
                        x.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }

            })
        })
    }
};


Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected)
};

柯里化

const curry = fn =>
    (judge = (...args) =>
        args.length >= fn.length
            ? fn(...args)
            : (...arg) => judge(...args, ...arg));

const sum = (a, b, c, d) => a + b + c + d;
const currySum = curry(sum);

currySum(1)(2)(3)(4); // 10
currySum(1, 2)(3)(4); // 10
currySum(1)(2, 3)(4); // 10

Object.create 实现原理

Object.create = function (prototype, properties) {
    if (typeof prototype !== "object") {
        throw TypeError();
    }

    function Ctor() {
    }

    Ctor.prototype = prototype;
    var o = new Ctor();
    if (prototype) {
        o.constructor = Ctor;
    }
    if (properties !== undefined) {
        if (properties !== Object(properties)) {
            throw TypeError();
        }
        Object.defineProperties(o, properties);
    }
    return o;
};

instanceof 实现

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

原理: L 的 proto 是不是等于 R.prototype,不等于再找 L.proto.proto 直到 proto 为 null

function instance_of(L, R) {
    var O = R.prototype;
    L = L.__proto__;
    while (true) {
        if (L === null) return false;
        // 这里重点:当 O 严格等于 L 时,返回 true
        if (O === L) return true;
        L = L.__proto__;
    }
}

splice 实现

new 操作的实现

  • 创建一个空对象,并把 this 指向空对象
  • 继承了函数的原型
  • 属性和方法被加入到 this 引用的对象中
  • 新创建的对象由 this 引用,最后隐式返回 this
function new(Function) {
    let obj = {
    }
    obj.__proto__ = Function.prototype
    let res = Function.call(obj)
    if(typeof(res) ==="function" || typeof(res) ==="object"){
        return res
    }
    return obj
}

单例实现

class Storage {
    constructor(){
        if(!Storage.instance){
            Storage.instance = this
        }
        return Storage.instance
    }
}

call

Function.prototype.call2 = function (context) {
    var context = context || window
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}


// 测试一下
var value = 2;

var obj = {
    value: 1
}

function bar(name, age) {
    console.log(this.value);
    return {
        value: this.value,
        name: name,
        age: age
    }
}

bar.call2(null); // 2

console.log(bar.call2(obj, 'kevin', 18));

apply

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    } else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

bind

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

防抖和节流

防抖

你尽管触发事件,但是我一定在事件触发 n 秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,那我就以新的事件的时间为准,n 秒后才执行,总之,就是要等你触发完事件 n 秒内不再触发事件

<!DOCTYPE html>
<html>
<head>

</head>
<body>
<div id="container" style="width: 100px;height: 100px;border: 1px solid red"></div>
<script>
    let count = 0
    const container = document.querySelector('#container')
    function getUserAction(){
        this.innerHTML = count++
    }
    function debounce(fn){
        let timer
        return function () {
            const context = this
            clearTimeout(timer)
            timer = setTimeout(()=>{
                fn.apply(context,arguments)
            },1000)
        }
    }
    container.onmousemove = debounce(getUserAction)
</script>
</body>
</html>

节流

如果你持续触发事件,每隔一段时间,只执行一次事件

使用时间戳

<!DOCTYPE html>
<html>
<head>

</head>
<body>
<div id="container" style="width: 100px;height: 100px;border: 1px solid red"></div>
<script>
    let count = 0
    const container = document.querySelector('#container')
    function getUserAction(){
        this.innerHTML = count++
    }
    function throttle(fn){
        let previous = 0
        let context,args
        return function () {
            let now = new Date()
            context = this
            args = arguments
            if(now-previous>1000){
                fn.apply(context,args)
                previous = now
            }
        }
    }
    container.onmousemove = debounce(getUserAction)
</script>
</body>
</html>

使用定时器

<!DOCTYPE html>
<html>
<head>

</head>
<body>
<div id="container" style="width: 100px;height: 100px;border: 1px solid red"></div>
<script>
    let count = 0
    const container = document.querySelector('#container')
    function getUserAction(){
        this.innerHTML = count++
    }
    function throttle(fn){
        let previous = 0
        let timeout
        return function () {
            let now = new Date()
            context = this
            args = arguments
            if(!timeout){
                timeout = setTimeout(()=>{
                    timeout = null
                    fn.apply(context,args)
                },1000)
            }
        }
    }
    container.onmousemove = throttle(getUserAction)
</script>
</body>
</html>
const throttle = (fn, wait) => {
    let previous = 0;
    return function(...args) {
        let now = +new Date();
        if (now - previous > wait) {
            previous = now;
            fn.apply(this, args);
        }
    };
};
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log("fn 函数执行了"), 1000);
// 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn
setInterval(betterFn, 10);

function debounce(fn, wait = 50) {
    let timer = null;
    return function(...args) {
        if (timer) clearTimeout(timer);
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, wait);
    };
}

const betterFn = debounce(() => console.log("fn 防抖执行了"), 1000);
document.addEventListener("scroll", betterFn);

// immediate表示第一次是否立即执行
function debounce(fn, wait = 50, immediate) {
    let timer = null;
    return function(...args) {
        if (timer) clearTimeout(timer);
        if (immediate && !timer) {
            fn.apply(this, args);
        }
        timer = setTimeout(() => {
            fn.apply(this, args);
        }, wait);
    };
}

const betterFn = debounce(() => console.log("fn 防抖执行了"), 1000, true);
document.addEventListener("scroll", betterFn);

// 加强版throttle
// wait 时间内,可以重新生成定时器,但只要 wait 的时间到了,必须给用户一个响应

function throttle(fn, wait) {
    let previous = 0,
        timer = null;
    return function(...args) {
        let now = +new Date();
        if (now - previous < wait) {
            if (timer) clearTimeout(timer);
            timer = setTimeout(() => {
                previous = now;
                fn.apply(this, args);
            }, wait);
        } else {
            previous = now;
            fn.apply(this, args);
        }
    };
}
// 执行 throttle 函数返回新函数
const betterFn = throttle(() => console.log("fn 节流执行了"), 1000);
// 第一次触发 scroll 执行一次 fn,每隔 1 秒后执行一次函数 fn,停止滑动 1 秒后再执行函数 fn
document.addEventListener("scroll", betterFn);

EventLoop

浏览器内核(渲染进程).png

进程线程

进程:资源分配的最小单位
线程:程序执行的最小单位

任务队列

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous),
同步任务
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
异步任务
不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

定时器

setTimeout(fn,0)指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在"任务队列"的尾部添加一个事件,因此要等到同步任务和"任务队列"现有的事件都处理完,才会得到执行。HTML5标准规定了setTimeout()的第二个参数的最小值(最短间隔),不得低于4毫秒,如果低于这个值,就会自动增加。

Node.js 的 EventLoop

Nodejs运行机制

(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),以异步的方式将任务的执行结果返回给V8引擎。
(4)V8引擎再将结果返回给用户。

process.nextTick 方法可以在当前"执行栈"的尾部----下一次Event Loop(主线程读取"任务队列")之前----触发回调函数。
setImmediate 方法则是在当前"任务队列"的尾部添加事件。
process.nextTick和setImmediate的一个重要区别:多个process.nextTick语句总是在当前"执行栈"一次执行完,多个setImmediate可能则需要多次loop才能执行完。

宏任务

JS 中,大部分任务都是在主线程上执行的,常见任务

  • setTimeout
  • setInterval
  • requestAnimationFrame
  • I/O
  • setImmediate
  • 渲染事件
  • 用户交互事件
  • js脚本执行
  • 网络请求、文件读写完成事件等等。
    其中 setImmediate 只存在于node中,requestAnimationFrame 只存在于 浏览器中

JS引擎需要对之执行的顺序做一定的安排,V8 其实采用的是一种队列的方式来存储这些任务, 即先进来的先执行。
将队列中的任务一一取出,然后执行。但是其中包含了两种任务队列,除了上述提到的任务队列, 还有一个延迟队列,它专门处理诸如setTimeout/setInterval这样的定时器回调任务。

普通任务队列和延迟队列中的任务,都属于宏任务

微任务

微任务包括

  • Promise
  • MutationObserver
  • Process.nextTick

其中process.nextTick只存在于Node中,MutationObserver只存在于浏览器中。

引入微任务的初衷是为了解决异步回调的问题
在每一个宏任务中定义一个微任务队列,当该宏任务执行完成,会检查其中的微任务队列,如果为空则直接执行下一个宏任务,如果不为空,则依次执行微任务,执行完成才去执行下一个宏任务。
常见的微任务有 MutationObserver、Promise.then(或.reject) 以及以 Promise 为基础开发的其他技术(比如fetch API), 还包括 V8 的垃圾回收过程

事件的执行顺序,是先执行宏任务,然后执行微任务。任务可以有同步任务和异步任务,同步的进入主线程,异步的进入 Event Table 并注册函数,异步事件执行完后,会将回调函数放入 Event Queue 中,同步任务执行完成后,会从 Event Queue 中读取事件放入主线程执行,回调函数还可能包含不同的任务,因此会循环执行上述操作。

setTimeout(function() {
    console.log('a')
});

new Promise(function(resolve) {
    console.log('b');
    for(var i =0; i <10000; i++) {
        i ==99 && resolve();
    }
}).then(function() {
    console.log('c')
});

console.log('d');
//b
//d
//c
//a

1.首先执行script下的宏任务,遇到setTimeout,将其放入宏任务的队列里。
2.遇到Promise,new Promise直接执行,打印b。
3.遇到then方法,是微任务,将其放到微任务的队列里。
4.遇到console.log('d'),直接打印。
5.本轮宏任务执行完毕,查看微任务,发现then方法里的函数,打印c。
6.本轮event loop全部完成。
7.下一轮循环,先执行宏任务,发现宏任务队列中有一个setTimeout,打印a。

console.log('a');

setTimeout(function () {
    console.log('b');
    process.nextTick(function () {
        console.log('c');
    })
    new Promise(function (resolve) {
        console.log('d');
        resolve();
    }).then(function () {
        console.log('e')
    })
})
process.nextTick(function () {
    console.log('f');
})
new Promise(function (resolve) {
    console.log('g');
    resolve();
}).then(function () {
    console.log('h')
})

setTimeout(function () {
    console.log('i');
    process.nextTick(function () {
        console.log('j');
    })
    new Promise(function (resolve) {
        console.log('k');
        resolve();
    }).then(function () {
        console.log('l')
    })
})

第一轮事件循环:
1.第一个宏任务(整体script)进入主线程,console.log('a'),打印a。
2.遇到setTimeout,其回调函数进入宏任务队列,暂定义为setTimeout1。
3.遇到process.nextTick(),其回调函数被分发到微任务队列,暂定义为process1。
4.遇到Promise,new Promise直接执行,打印g。then进入微任务队列,暂定义为then1。
5.遇到setTimeout,其回调函数进入宏任务队列,暂定义为setTimeout2。
此时我们看一下两个任务队列中的情况

宏任务队列 微任务队列
setTimeout1/setTimeout2 process1/then1

第一轮宏任务执行完毕,打印出a和g。
查找微任务队列中有process1和then1。全部执行,打印f和h。
第一轮事件循环完毕,打印出a、g、f和h。

第二轮事件循环:

  1. 从setTimeout1宏任务开始,首先是console.log('b'),打印b。
  2. 遇到process.nextTick(),进入微任务队列,暂定义为process2。
  3. new Promise直接执行,输出d,then进入微任务队列,暂定义为then2。
    此时两个任务队列中
宏任务队列 微任务队列
setTimeout2 process2/then2

第二轮宏任务执行完毕,打印出b和d。
查找微任务队列中有process2和then2。全部执行,打印c和e。
第二轮事件循环完毕,打印出b、d、c和e。

第三轮事件循环
1.执行setTimeout2,遇到console.log('i'),打印i。
2.遇到process.nextTick(),进入微任务队列,暂定义为process3。
3.new Promise直接执行,打印k。
4.then进入微任务队列,暂定义为then3。
此时两个任务队列中
宏任务队列:空
微任务队列:process3、then3
第三轮宏任务执行完毕,打印出i和k。
查找微任务队列中有process3和then3。全部执行,打印j和l。
第三轮事件循环完毕,打印出i、k、j和l。

Tips
以上是在浏览器中的运行结果,在node中的运行结果不一样

浏览器环境下EventLoop执行流程

  1. 一开始整段脚本作为第一个宏任务执行
  2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  3. 当前宏任务执行完出队,检查微任务队列,如果有则依次执行,直到微任务队列为空
  4. 执行浏览器 UI 线程的渲染工作
  5. 检查是否有Web worker任务,有则执行
  6. 执行队首新的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

nodejs环境下EventLoop

  1. 执行 定时器回调 的阶段。检查定时器,如果到了时间,就执行回调。这些定时器就是setTimeout、setInterval。这个阶段暂且叫它timer。

  2. 轮询(英文叫poll)阶段。因为在node代码中难免会有异步操作,比如文件I/O,网络I/O等等,那么当这些异步操作做完了,就会来通知JS主线程,怎么通知呢?就是通过'data'、 'connect'等事件使得事件循环到达 poll 阶段

  3. timer 阶段

  4. I/O 异常回调阶段

  5. 空闲、预备状态(第2阶段结束,poll 未触发之前)

  6. poll 阶段

  7. check 阶段

  8. 关闭事件的回调阶段

nodejs 和 浏览器关于eventLoop的主要区别

两者最主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而nodejs中的微任务是在不同阶段之间执行的。

process.nextTick

process.nextTick 是一个独立于 eventLoop 的任务队列。
在每一个 eventLoop 阶段完成后会去检查这个队列,如果里面有任务,会让这部分任务优先于微任务执行。

HTTP

HTTP面试题
参考文献
大白话三次握手
Http

网络协议分层

七层:应用层/表示层/会话层/传输层/网络层/数据链路层/物理层
五层:应用层/传输层/网络层/网络接口层

应用层包括

1、超文本传输协议(HTTP):万维网的基本协议;
2、文件传输(TFTP简单文件传输协议);
3、远程登录(Telnet),提供远程访问其它主机功能, 它允许用户登录internet主机,并在这台主机上执行命令;
4、网络管理(SNMP简单网络管理协议),该协议提供了监控网络设备的方法, 以及配置管理,统计信息收集,性能管理及安全管理等;
5、域名系统(DNS),该系统用于在internet中将域名及其公共广播的网络节点转换成IP地址。

网络层包括

1、Internet协议(IP);
2、Internet控制信息协议(ICMP);
3、地址解析协议(ARP);
4、反向地址解析协议(RARP)。

TCP 和 UDP 区别

  1. 基于连接与无连接;
  2. 对系统资源的要求(TCP较多,UDP少);
  3. UDP程序结构较简单;
  4. 流模式与数据报模式 ;
  5. TCP保证数据正确性,UDP可能丢包;
  6. TCP保证数据顺序,UDP不保证。

DNS 解析

DNS (Domain Name System)是互联网中的重要基础设施,负责对域名的解析工作,为了保证高可用、高并发和分布式,它设计成了树状的层次结构。
查找哪台机器有你需要的资源,互联网上每一台计算机的唯一标识是它的IP地址。网址到IP地址的转换,这个过程就是DNS解析

DNS 解析过程

  1. 首先在本地域名服务器中查询 IP 地址
  2. 本地域名服务器未找到,向根域名服务器发送请求
  3. 根域名服务器也不存在该域名,本地域名会向com顶级域名服务器发送一个请求
  4. 直到最后本地域名服务器得到IP地址并缓存在本地,供下次查询使用。

DNS 缓存

  • 浏览器缓存
  • 系统缓存
  • 路由器缓存
  • IPS服务器缓存
  • 根域名服务器缓存
  • 顶级域名服务器缓存
  • 主域名服务器缓存

DNS 负载均衡

CDN利用DNS重定向技术。DNS服务器会返回一个跟用户最接近的点的IP地址给用户,CDN节点的服务器负责响应用户的请求,提供所需的内容。

CDN 原理

CDN(Content Delivery Network)就是内容分发网络。

CDN 网络则是在用户和服务器之间增加 Cache 层,将用户的访问请求引导到Cache 节点而不是服务器源站点,要实现这一目的,主要是通过接管DNS 实现

  • 全局负载均衡DNS 解析服务器会根据用户端的源IP 地址,如地理位置(北京还是上海)、接入网类型(电信还是网通)将用户的访问请求定位到离用户路由最短、位置最近、负载最轻的Cache 节点(缓存服务器)上,实现就近定位。定位优先原则可按位置、可按路由、也可按负载等。
  • 浏览器得到该域名CDN 缓存服务器的实际IP 地址,向缓存服务器发出访问请求

-缓存服务器根据浏览器提供的域名,通过Cache 内部专用DNS 解析得到此域名源服务器的真实IP 地址,再由缓存服务器向此真实IP 地址提交访问请求

输入url后发生了什么

状态码整理

HTTP 请求方法

  • GET 获取资源 (幂等)
  • POST 新增资源
  • HEAD 获取HEAD元数据 (幂等)
  • PUT 更新资源 (带条件时幂等)
  • DELETE 删除资源 (幂等)
  • CONNECT 建立 Tunnel 隧道
  • OPTIONS 获取服务器支持访问资源的方法 (幂等)
  • TRACE 回显服务器收到的请求,可以定位问题。(有安全风险)

HTTPS的理解

HTTPS 就是在 HTTP 和 TCP 协议中间加入了 SSL/TLS 安全套接层。
结合非对称加密和对称加密的各自优点,配合证书。既保证了安全性,也保证了传输效率。
目前应用最广泛的是TLS1.2,实现原理如下:

  1. Client 发送 random1+对称加密套件列表+非对称加密套件列表
  2. Server 收到信息, 选择 对称加密套件+非对称加密套件 并和 random2+证书(公钥在证书中) 一起返回
  3. Client 验证证书有效性,并用 random1+random2 生成 pre-master 通过服务器公钥加密+浏览器确认 发送给 Server
  4. Server 收到 pre-master,根据约定的加密算法对 random1+random2+pre-master(解密)生成 master-secret,然后发送服务器确认
  5. Client 收到生成同样的 master-secert,对称加密秘钥传输完毕
    TLS1.3 则简化了握手过程,完全握手只需要一个消息往返,提升了性能。不仅如此,还对部分不安全的加密算法进行了删减。

HTTP缓存策略

强缓存

服务器使用 Cache-Control 来设置缓存策略,常用 max-age 来表示资源的有效期。

  • no-store:不允许缓存 (用于秒杀页面等变化频率非常高的场景)
  • no-cache:可以缓存,使用前必须要去服务端验证是否过期,是否是最新版本
  • must-revalidate:如果缓存不过期就可以继续使用,过期了就必须去服务端验证

协商缓存

验证资源是否失效就需要使用条件请求。常用的是 If-Modified-Since 和 If-None-Match,收到 304 状态码就可以复用缓存里的资源。
(If-None-Match 比 If-Modified-Since 优先级更高)
验证资源是否被修改的条件有两个 Last-modified 和 ETag (ETag 比 Last-modified 的精确度更高),需要预先在服务端的响应报文里设置,配合条件请求使用。

HTTP重定向

重定向是服务器发起的跳转,要求客户端使用新的 URI 重新发送请求。在响应头字段 Location 中指示了要跳转的 URI。使用 Refresh 字段,还可以实现延时重定向。
301 / 302 是常用的重定向状态码。分别代表永久性重定向和临时性重定向。

除此之外还有:

  • 303:类似于 302,重定向后的请求方法改为 GET 方法
  • 307:类似于 302,含义比 302 更明确,重定向后请求的方法和实体不允许变动
  • 308:类似于 301,代表永久重定向,重定向后请求的方法和实体不允许变动
  • 300:是一个特殊的重定向状态码,会返回一个有多个链接选项的页面,由用户自行选择
  • 304:是一个特殊的重定向状态码,服务端验证过期缓存有效后,要求客户端使用该缓存

HTTP 的常用的首部字段

通用首部字段

  • Cache-Control 控制缓存
  • Connection 连接管理
  • Transfor-Encoding 报文主体的传输编码格式
  • Date 创建报文的时间
  • Upgrade 升级为其他协议

请求首部字段

  • Host 请求资源所在的服务器 (唯一一个HTTP/1.1规范里要求必须出现的字段)
  • Accept 客户端或者代理能够处理的媒体类型
  • If-Match 比较实体标记 (ETag)
  • If-None-Match 比较实体标记 (ETag),与 If-Match 相反
  • If-Modified-Since 比较资源更新时间 (Last-Modified)
  • If-Unmodified-Since 比较资源更新时间 (Last-Modified), 与 If-Modified-Since 相反
  • Range 实体的字节范围请求
  • User-Agent 客户端信息

响应首部字段

  • Accept-Ranges 能接受的字节范围
  • Location 命令客户端重定向的 URI
  • ETag 能够表示资源唯一资源的字符串
  • Server 服务器的信息

实体首部字段

  • Allow 资源可支持 HTTP 请求方法
  • Last-Modified 资源最后修改时间
  • Expires 实体主体过期时间
  • Content-Language 实体资源语言
  • Content-Encoding 实体编码格式
  • Content-Length 实体大小
  • Content-Type 实体媒体类型

HTTP状态码

1xx

  • 1xx:请求已经接收到,需要进一步处理才能完成,HTTP/1.0 不支持
  • 100 Continue:上传大文件前使用
  • 101 Switch Protocols:协议升级使用
  • 102 Processing:服务器已经收到并正在处理请求,但无响应可用

2xx

  • 2xx:成功处理请求
  • 200 OK:成功返回响应
  • 201 Created:有新资源在服务器端被成功创建
  • 202 Accepted:服务器接受并开始处理请求,但请求未处理完成
  • 206 Partial Content:使用range协议时返回部分响应内容时的响应码

3xx

301 302

301,永久重定向:在请求的URL已被移除时使用,响应的location首部中应包含资源现在所处的URL
302,临时重定向:和永久重定向类似,客户端应用location给出URL临时定位资源,将来的请求仍为原来的URL。

所以浏览器会进行2次请求。第一次返回301/302

服务器响应 状态码为301+ location为新的url

浏览器会再次请求新的URL

一般使用301的情况有下面几种

http网站跳转到https网站
二级域名跳转到主域名,http://www.abc.com跳转到http://abc.com
404页面失效跳转到新的页面
老的域名跳转到新的域名

所以301跳转,对用户体验和谷歌蜘蛛都是比较友好的,权重发生了传递,当然对SEO也是有好处的。

302使用的情况不太常见,因为这是个临时性的跳转,暂时性的把页面A跳转到页面B,但是最终还会使用页面A,这个情况一般就是网站短时间内进行改版,在不影响用户体验的情况下,临时把页面跳转到临时页面

4xx

  • 4xx:客户端出现错误
  • 400 Bad Request:服务器认为客户端出现了错误,但不明确,一般是 HTTP 请求格式错误
  • 401 Unauthorized:用户认证信息确实或者不正确
  • 403 Forbidden:服务器理解请求的含义,但没有权限执行
  • 407 Proxy Authentication Required:对需要经由代理的请求,认证信息未通过代理服务器的验证
  • 404 Not Found:服务器没有找到对应的资源
  • 408 Request Timeout:服务器接收请求超时

5xx

  • 5xx:服务器端出现错误
  • 500 Internal Server Error:服务器内部错误,且不属于以下错误类型
  • 502 Bad Gateway:代理服务器无法获取到合法响应
  • 503 Service Unavailable:服务器资源尚未准备好处理当前请求
  • 505 HTTP Version Not Supported:请求使用的 HTTP 协议版本不支持

埋点数据发送请求

埋点是通过img src 实现的
前端通过window.[__bfi].push()方法,ubt将push方法声明为一个函数,函数的功能是对argument进行处理

Referrer-Policy 首部用来监管哪些访问来源信息——会在 Referer 中发送——应该被包含在生成的请求当中。

Referrer-Policy: no-referrer
Referrer-Policy: no-referrer-when-downgrade
Referrer-Policy: origin
Referrer-Policy: origin-when-cross-origin
Referrer-Policy: same-origin
Referrer-Policy: strict-origin
Referrer-Policy: strict-origin-when-cross-origin
Referrer-Policy: unsafe-url

no-referrer
整个 Referer 首部会被移除。访问来源信息不随着请求一起发送。
no-referrer-when-downgrade (默认值)
在没有指定任何策略的情况下用户代理的默认行为。在同等安全级别的情况下,引用页面的地址会被发送(HTTPS->HTTPS),但是在降级的情况下不会被发送 (HTTPS->HTTP)。
origin
在任何情况下,仅发送文件的源作为引用地址。例如 https://example.com/page.html 会将 https://example.com/ 作为引用地址。
origin-when-cross-origin
对于同源的请求,会发送完整的URL作为引用地址,但是对于非同源请求仅发送文件的源。
same-origin
对于同源的请求会发送引用地址,但是对于非同源请求则不发送引用地址信息。
strict-origin
在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS),但是在降级的情况下不会发送 (HTTPS->HTTP)。
strict-origin-when-cross-origin
对于同源的请求,会发送完整的URL作为引用地址;在同等安全级别的情况下,发送文件的源作为引用地址(HTTPS->HTTPS);在降级的情况下不发送此首部 (HTTPS->HTTP)。
unsafe-url
无论是同源请求还是非同源请求,都发送完整的 URL(移除参数信息之后)作为引用地址。

OPTIONS 请求

使用OPTIONS方法对服务器发起请求,可以检测服务器支持哪些 HTTP 方法

规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。

所以这个跨域请求触发了浏览器自动发起OPTIONS请求,看看此次跨域请求具体触发了哪些条件。

跨域请求时,OPTIONS请求触发条件

CORS预检请求触发条件
1. 使用 HTTP方法 PUT/DELETE/CONNECT/OPTIONS/TRACE/PATCH
2. 人为设置了以下集合之外首部字段:Accept/Accept-Language/Content-Language/Content-Type/DPR/Downlink/Save-Data/Viewport-Width/Width
3. Content-Type 的值不属于下列之一: application/x-www-form-urlencoded、multipart/form-data、text/plain

优化OPTIONS请求:Access-Control-Max-Age 或者 避免触发

Access-Control-Max-Age这个响应首部表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息) 可以被缓存的最长时间,单位是秒。(MDN)

JavaScript设计模式

面向对象

  • 可维护
  • 可扩展
  • 可复用
  • 灵活性好

单一职责原则

一个对象或者方法只做一件事

开放封闭原则

软件实体对扩展开放,对修改关闭。设计人员必须对他设计的模块应该对哪种变化封闭作出选择。必须先猜测出最优可能发生的变化种类,然后构造抽象来隔离那些变化。
面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。

依赖倒置原则

高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。作用,降低了客户与实现模块间的耦合。
针对接口编程,不要对实现编程

里氏替换原则

子类必须能够替换他们的父类型
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能在父类的基础上增加新的行为

接口分离原则

使用多个专门的接口来取代一个统一的接口。

合成复用原则

在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。简单来说就是,要尽量使用组合,尽量不要使用继承

策略模式

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
计算奖金

const performanceS = function () {
}
performanceS.prototype.calculate = function (salary) {
    return salary * 4
}

const performanceA = function () {

}
performanceA.prototype.calculate = function (salary) {
    return salary * 3
}

const performanceB = function () {

}
performanceB.prototype.calculate = function (salary) {
    return salary * 2
}


const Bouns = function () {
    this.salary = null
    this.strategy = null
}
Bouns.prototype.setSalary = function (salary) {
    this.salary = salary
}
Bouns.prototype.setStrategy = function (strategy) {
    this.strategy = strategy
}
Bouns.prototype.getBonus = function () {
    return this.strategy.calculate(this.salary)
}

const bonus = new Bouns()
bonus.setSalary(10000)
bonus.setStrategy(new PerformanceS())
console.log(bonus.getBonus())

优化
context 并没有计算奖金的能力,而是把这个职责委托给某个策略对象

const strategies = {
    "S": function (salary) {
        return salary * 4
    },
    "A": function (salary) {
        return salary * 3
    },
    "B": function (salary) {
        return salary * 2
    }
}
const calculateBonus = function (level, salary) {
    return strategies[level](salary)
}

console.log(calculateBonus('S', 2000))

验证表单

const strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === '') {
            return errorMsg
        }
    },
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg
        }
    },
    isMobile: function (value, errorMsg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg
        }
    }
}

const validateFunc = function () {
    const validator = new Validator()
    validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空')
    validator.add(registerForm.password, 'minLength:6', '密码长度不能少于6位')
    validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不对')
    const errorMsg = validator.start()
    return errorMsg
}

const registerForm = document.getElementById('registerForm')
registerForm.onsubmit = function () {
    const errorMsg = validateFunc()
    if (errorMsg) {
        console.log(errorMsg)
        return false
    }
}


const validator = function () {
    this.cache = []
}
Validator.prototype.add = function (dom, rule, errorMsg) {
    const ary = rule.split(':')
    this.cache.push(function () {
        const strategy = ary.shift()
        ary.unshift(dom.value)
        ary.push(errorMsg)
        return strategies[strategy].apply(dom, ary)
    })
}
Validator.prototype.start = function () {
    for (let i = 0, validatorFunc; validatorFunc = this.cache[i++]) {
        const msg = validatorFunc()
        if (msg) {
            return msg
        }
    }
}

代理模式

const Flower = function () {

}
const xiaoming = {
    sendFlower: function (target) {
        const flower = new Flower()
        target.receiveFlower(flower)
    }
}
const A = {
    receiveFlower: function (flower) {
        console.log('收到花 ' + flower)
    }
}
xiaoming.sendFlower(A)
const Flower = function () {

}
const xiaoming = {
    sendFlower: function (target) {
        const flower = new Flower()
        target.receiveFlower(flower)
    }
}
const A = {
    receiveFlower: function (flower) {
        console.log('收到花 ' + flower)
    },
    listenGoodMood: function (fn) {
        setTimeout(function () {
            fn()
        }, 10000)
    }
}
xiaoming.sendFlower(A)


const B = {
    receiveFlower: function (flower) {
        A.listenGoodMood(function () {
            A.receiveFlower(flower)
        })
    }
}

xiaoming.sendFlower(B)
  • 虚拟代理实现图片预加载
const myImage = (function () {
    const imgNode = document.createElement('img')
    document.body.append(imgNode)
    return {
        setSrc: function (src) {
            imgNode.src = src
        }
    }
})()

const proxyImage = (function () {
    const img = new Image
    img.onload = function () {
        myImage.setSrc(this.src)
    }
    return {
        setSrc: function (src) {
            myImage.setSrc('file://')
            img.src = src
        }
    }
})
proxyImage.setSrc('https://')

现在我们通过 proxyImage 间接地访问 MyImage。proxyImage 控制了客户对 MyImage 的访问,并 且在此过程中加入一些额外的操作,比如在真正的图片加载好之前,先把 img 节点的 src 设置为 一张本地的 loading 图片。

  • 代理模式合并HTTP请求
<div>
    <input type="checkbox" id="1"/>1
    <input type="checkbox" id="2"/>2
    <input type="checkbox" id="3"/>3
    <input type="checkbox" id="4"/>4
    <input type="checkbox" id="5"/>5
    <input type="checkbox" id="6"/>6
    <input type="checkbox" id="7"/>7
</div>
<script>
    const synchronousFile = function (id) {
        console.log('开始同步文件,id为:' + id)
    };
    const checkbox = document.getElementsByTagName('input')
    for (let i = 0, c; c = checkbox[i++];) {
        c.onclick = function () {
            if (this.checked === true) {
                proxySynchronousFile(this.id)
            }
        }
    }
    const proxySynchronousFile = (function () {
        let cache = [], timer;
        return function (id) {
            cache.push(id);
            if (timer) return;
            timer = setTimeout(function () {
                synchronousFile(cache.join(','));
                clearTimeout(timer);
                timer = null;
                cache.length = 0;
            }, 2000)
        };

    })()
</script>

迭代器模式

const Iterator = function (obj) {
    let current = 0;
    const next = function () {
        current += 1
    };
    const isDone = function () {
        return current >= obj.length
    };
    const getCurrItem = function () {
        return obj[current]
    };
    return {
        next: next,
        isDone: isDone,
        getCurrItem: getCurrItem
    }
};

const compare = function (iterator1, iterator2) {
    while (!iterator1.isDone() && !iterator2.isDone()) {
        if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {
            throw  new Error('不相等')
        }
        iterator1.next();
        iterator2.next()
    }
    console.log('iterator1 和 iterator2 相等')
};
const iterator1 = Iterator([1, 2, 3]);
const iterator2 = Iterator([1, 2, 3]);
compare(iterator1, iterator2);

发布订阅模式

const salesOffices = {};
salesOffices.clientList = [];
salesOffices.listen = function (fn) {
    this.clientList.push(fn)
};
salesOffices.trigger = function () {
    for (let i = 0; i < this.clientList.length; i++) {
        this.clientList[i].apply(this, arguments)
    }
};

salesOffices.listen(function (price, squareMeter) {
    console.log('价格= ' + price);
    console.log('squareMeter= ' + squareMeter)
});

salesOffices.listen(function (price, squareMeter) {
    console.log('价格= ' + price);
    console.log('squareMeter= ' + squareMeter)
});

salesOffices.trigger(2000, 88);
salesOffices.trigger(3000, 110);

命令模式

<body>
<button id="button1">刷新主菜单</button>
<button id="button2">增加子菜单</button>
<button id="button3">删除子菜单</button>
<script>
    const button1 = document.getElementById('button1');
    const button2 = document.getElementById('button2');
    const button3 = document.getElementById('button3');
    const MenuBar = {
        refresh: function () {
            console.log('刷新菜单目录')
        }
    };
    const SubMenu = {
        add: function () {
            console.log('增加子菜单')
        },
        del: function () {
            console.log('删除子菜单')
        }
    };

    const RefreshMenuBarCommand = function (receiver) {
        this.receiver = receiver
    };
    RefreshMenuBarCommand.prototype.execute = function () {
        this.receiver.refresh()
    };

    const AddSubMenuCommand = function (receiver) {
        this.receiver = receiver
    };

    AddSubMenuCommand.prototype.execute = function () {
        this.receiver.add()
    };
    const DelSubMenuCommand = function (receiver) {
        this.receiver = receiver
    };
    DelSubMenuCommand.prototype.execute = function () {
        console.log('删除子菜单')
    };
    const setCommand = function (button, command) {
        button.onclick = function () {
            command.execute()
        }
    };
    const refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar);
    const addSubMenuCommand = new AddSubMenuCommand(SubMenu);
    const delSubMenuCommand = new DelSubMenuCommand(SubMenu);
    setCommand(button1, refreshMenuBarCommand);
    setCommand(button2, addSubMenuCommand);
    setCommand(button3, delSubMenuCommand)

</script>
</body>

宏命令

const closeDoorCommand = {
    execute: function () {
        console.log('关门')
    }
}
const openPcCommand = {
    execute: function () {
        console.log('开电脑')
    }
}

const openQQCommand = {
    execute: function () {
        console.log('登录QQ')
    }
}

const MarcoCommand = function () {
    return {
        commandList: [],
        add: function (command) {
            this.commandList.push(command)
        },
        execute: function () {
            for (let i = 0, command; command = this.commandList[i++];) {
                command.execute()
            }
        }
    }
}
const marcoCommand = MarcoCommand()
marcoCommand.add(closeDoorCommand)
marcoCommand.add(openPcCommand)
marcoCommand.add(openQQCommand)
marcoCommand.execute()

组合模式

const Folder = function (name) {
    this.name = name
    this.files = []
}
Folder.prototype.add = function (file) {
    this.files.push(file)
}
Folder.prototype.scan = function () {
    console.log('开始扫描' + this.name)
    for (let i = 0, file, files = this.files; file = files[i++]) {
        file.scan()
    }
}


const File = function (name) {
    this.name = name
}
File.prototype.add = function () {
    throw  new Error('文件下面不能再添加文件')
}
File.prototype.scan = function () {
    console.log('开始扫描文件' + this.name)
}

const folder = new Folder('学习资料')
const folder1 = new Folder('JavaScript')
const folder2 = new Folder('jQuery')

const file1 = new File('JavaScript设计模式')
const file2 = new File('精通jQuery')
const file3 = new File('重构和模式')


folder1.add(file1)
folder2.add(file2)
folder.add(folder1);
folder.add(folder2);
folder.add(file3);

装饰者模式

AOP分离将打开浮层和上报功能分开

<html>
<button tag="login" id="button">按钮</button>
<script>
    Function.prototype.after = function (afterfn) {
        const _self = this;
        return function () {
            const ret = _self.apply(this, arguments);
            afterfn.apply(this, arguments);
            return ret
        }
    };
    let showLogin = function () {
        console.log('打开登录浮层')
    };
    const log = function () {
        console.log('上报标签为:' + this.getAttribute('tag'))
    };
    showLogin = showLogin.after(log);
    document.getElementById('button').onclick = showLogin
</script>
</html>

适配器模式

const googleMap = {
    show: function () {
        console.log('渲染谷歌地图')
    }
}
const baiduMap = {
    show: function () {
        console.log('渲染百度地图')
    }
}

const renderMap = function (map) {
    if (map.show instanceof Function) {
        map.show()
    }
}

renderMap(googleMap)
renderMap(baiduMap)

这段程序得以顺利运行的关键是 googleMap 和 baiduMap 提供了一致的 show 方法,但第三方的 接口方法并不在我们自己的控制范围之内,假如 baiduMap 提供的显示地图的方法不叫 show 而叫 display 呢

const googleMap = {
    show: function () {
        console.log('渲染谷歌地图')
    }
}
const baiduMap = {
    display: function () {
        console.log('渲染百度地图')
    }
}

const renderMap = function (map) {
    if (map.show instanceof Function) {
        map.show()
    }
}


const baiduMapAdapter = {
    show: function () {
        return baiduMap.display()
    }
}
renderMap(googleMap)
renderMap(baiduMapAdapter)
const getGuangdongCity = function () {
    const guangdongCity = [
        {
            name: 'shenzhen',
            id: 11,
        },
        {
            name: 'guangzhou',
            id: 12
        }
    ]
    return guangdongCity
}
const render = function (fn) {
    console.log(JSON.stringify(fn()))
}
const addressAdapter = function (oldAddressfn) {
    const address = {}
    const oldAddress = oldAddressfn()
    for (let i = 0, c; c = oldAddress[i++];) {
        address[c.name] = c.id
    }
    return function () {
        return address
    }
}
render(addressAdapter(getGuangdongCity))

React原理

react render 原理

在页面一开始打开的时候,React会调用render函数构建一棵Dom树,在state/props发生改变的时候,render函数会被再次调用渲染出另外一棵树,接着,React会用对两棵树进行对比,找到需要更新的地方批量改动。

diff算法

Diff算法只会对同层的节点进行比较。也就是说如果父节点不同,React将不会在去对比子节点。因为不同的组件DOM结构会不相同,所以就没有必要在去对比子节点了。

  • 不同节点类型
    直接删去旧的节点,新建一个新的节点
  • 相同节点类型
    这里分为两种情况,一种是DOM元素类型,对应html直接支持的元素类型:div,span和p,还有一种是React组件。
    1. DOM元素类型,react会对比它们的属性,只改变需要改变的属性。

    2. 由于React此时并不知道如何去更新DOM树,因为这些逻辑都在React组件里面,所以它能做的就是根据新节点的props去更新原来根节点的组件实例,触发一个更新的过程

      • getDerivedStateFromProps
      • shouldcomponentUpdate
      • render
      • getSnapShotBeforeUpdate
      • componentDidUpdate

react 核心**

内存中维护一个虚拟DOM,数据变化时(setState),自动更新虚拟DOM。得到一个新树,然后diff新老虚拟DOM树,找到有变化的部分。得到一个patch(change),将patch加入队列,最终批量更新patch到dom中.

react Fiber

React渲染页面分为两个阶段:

  • 调度阶段 (reconcilation)
    更新数据生成新的Virtual DOM,通过Diff算法,快速找出需要更新的元素,放到更新队列中
  • 渲染阶段
    遍历更新队列,将所有的变更更新到DOM上

问题

随着 React 同步任务的执行,主线程将会一直被占用,无暇顾及其他任务。

Fiber解决方案

Fiber 的中文解释是纤程,是线程的颗粒化的一个概念。也就是说一个线程可以包含多个 Fiber。
Fiber 的出现使大量的同步计算可以被拆解、异步化,使浏览器主线程得以调控。从而使我们得到了以下权限:

  • 暂停运行任务。
  • 恢复并继续执行任务。
  • 给不同的任务分配不同的优先级

Fiber的实现原理

  • 将一个state更新需要执行的同步任务拆分成一个Fiber任务队列
  • 在任务队列中选出优先级高的Fiber执行,如果执行时间超过了deathLine,则设置为pending状态挂起状态
  • 一个Fiber执行结束或挂起,会调用基于requestIdleCallback/requestAnimationFrame实现的调度器,返回一个新的Fiber任务队列继续进行上述过程
    requestIdleCallback会让一个低优先级的任务在空闲期被调用,而requestAnimationFrame会让一个高优先级的任务在下一个栈帧被调用,从而保证了主线程按照优先级执行Fiber单元。

Fiber任务的优先级顺序为
文本框输入 > 本次调度结束需完成的任务 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防将来会显示的任务
参考文献

CSS基础知识

CSS.png

非替换元素

margin

百分数和外边距

百分数是相对于父元素的width计算的

值复制

  • 如果缺少左外边距的值,则使用右外边距的值
  • 如果缺少下外边距的值,则使用上外边距的值
  • 如果缺少右外边距的值,则使用上外边距的值

负外边距和合并外边距

为元素设置负外边距,会导致元素框超出其父元素

正常流中垂直相邻外边距会合并

外边距和行内元素

由于在向一个行内非替换元素应用外边距,他对行高没有影响

浮动和定位

  • 浮动元素会以某种方式将浮动元素从文档的正常流中删除。一个元素浮动时,其他内容会"环绕"该元素。
  • 浮动元素周围的外边距不会合并
  • 如果要浮动一个非替换元素,则必须为该元素声明一个width,否则,元素的宽度趋于0
  • 浮动元素会生成一个块级框,不论这个元素本身是什么
  • 浮动元素包含块,是其最近的块级祖先元素

清除浮动

不允许h3左侧出现浮动元素

h3{ clear: left}

定位

position:static/relative/absolute/fixed/inherit

文本阴影

text-shadow
color ?
前俩长度值确定了阴影与文本的偏移距离,第三个长度值可选,定义了阴影的模糊半径。

空白符

white-space

空白符 换行符 自动换行
pre-line 合并 保留 允许
normal 合并 忽略 允许
nowrap 合并 忽略 不允许
pre 保留 保留 不允许
pre-wrap 保留 保留 允许

伪类和伪元素

伪类选择器

链接伪类

  • :link
  • :visited

动态伪类

  • :focus
  • :hover
  • :active

第一个子元素

  • :first-child

伪元素选择器

  • :first-letter
  • :first-line
  • :before
  • :after

继承

可继承 不可继承
h1
ul

Hooks原理

前言

参考文章

useState 原理

function Count() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <div>{count}</div>
      <Button onClick={() => { setCount(count + 1) }}>点击</Button>
    </div>
  )
}

let _state
function useState(initialValue) {
  _state = initialValue
  function setState(newValue) {
    _state = newValue
    render()
  }
  return [_state, setState]
}

useEffect 原理

useEffect(() => {
  console.log(count)
}, [count])


let _deps
function useEffect(callback, depArray) {
  const hasNoDeps = !depArray
  const hasChangedDeps = _deps
    ? !depArray.every((el, i) => el === _deps[i])
    : true
  if (hasNoDeps || hasChangedDeps) {
    callback()
    _deps = depArray
  }
}

但是如果页面中需要使用多个useState就会有问题,因为useState需要公用全局变量_state,我们需要可以存储多个_state 和 _deps

关键在于:

  1. 初次渲染的时候,按照useState、useEffect的顺序,把state,deps等按顺序塞到 memoizedState 数组中
  2. 更新的时候,按照顺序,从 memoizedState 中把上次记录的值拿回来。
let memoizedState = []
let cursor = 0

function useState(initialValue) {
  memoizedState[cursor] = memoizedState[cursor] || initialValue
  const currentCursor = cursor
  function setState(newState) {
    memoizedState[currentCursor] = newState
    render()
  }
  return [memoizedState[cursor + 1], setState] //  返回当前 state,并把 cursor 加 1
}

function useEffect(callback, depArray) {
  const hasNoDeps = !depArray
  const deps = memoizedState[cursor]
  const hasChangedDeps = deps
    ? !depArray.every((el, i) => el === deps[i])
    : true
  if (hasNoDeps || hasChangedDeps) {
    callback()
    memoizedState[cursor] = depArray
  }
  cursor++
}

Proxy、defineproperty实现简单的数据绑定

<input type="text" id="input"/>
<h2>您输入的内容是<i id="txt"></i></h2>
<script>
    let oInput = document.getElementById('input');
    let oTxt = document.getElementById('txt');
    let obj = {};
    let newProxy = new Proxy(obj, {
        get: (target, key, receiver) => {
            return Reflect.get(target, key, receiver)
        },
        set: (target, key, value, receiver) => {
            if (key === 'text') {
                oTxt.innerHTML = value
            }
            return Reflect.set(target, key, value, receiver)
        }
    });
    oInput.addEventListener('keyup', e => {
        newProxy.text = e.target.value
    })
</script>

ES6 方法小记

取整 -> | 0作用是取整数

变量提升

JavaScript 中,变量的声明都将被提升到当前作用域的最顶部。
变量提升的优先级高于函数提升

为什么会有变量提升和函数提升呢?

函数提升本来就是为了解决相互递归的问题而设计出来的,如下的例子,如果没有函数提升的特性,那么 isOdd 方法的调用是会报错的。

// 验证偶数
function isEven(n) {
  if (n === 0) {
    return true
  }
  return isOdd(n - 1)
}

console.log(isEven(2)) // true

// 验证奇数
function isOdd(n) {
  if (n === 0) {
    return false
  }
  return isEven(n - 1)
}

但是变量提升其实是可有可无的,**其实是由于第一代JS虚拟机解析出现纰漏导致的 **,不过由于不影响,一直沿用到现在。

for...of vs for...in

for..of和for..in均可迭代一个列表;但是用于迭代的值却不同,for..in迭代的是对象的 键 的列表,而for..of则迭代对象的键对应的值。

for...of循环、扩展运算符、解构赋值、Array.from方法内部调用的都是遍历器接口。这意味着,它们可以将Generator函数返回的Iterator对象作为参数

function* numbers() {
    yield 1
    yield 2
    return 3
    yield 4
}

[...numbers()]
Array.from(numbers())
let [x, y] = numbers()

合并数组

concat、扩展运算符、push.apply、for循环

合并对象

Object.assign、扩展运算符、深浅拷贝

解构赋值

对于 Set 结构,也可以使用数组的解构赋值

let [a, b, c] = new Set(["a", "b", "c"])

只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。

function* fibs() {
    let a = 0;
    let b = 1
    while (true) {
        yield a;
        [a, b] = [b, a + b]
    }
}
var [one, two, three, four, five, six] = fibs()

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值或者布尔值,则会先转为对象。由于 undefined 和 null 都无法转为对象,所以对他们进行解构赋值会报错。

👇数值和布尔值的包装对象都有toString属性

let { toString: s } = 123
s === Number.prototype.toString
let { toString: s } = true
s === Boolean.prototype.toString

遍历 map 结构

let map = new Map()
map.set("first", "hello")
map.set("second", "world")
for (let [key, value] of map) {
  console.log(key + " , " + value)
}

for (let [key] of map) {
  console.log(key)
}

for (let [, value] of map) {
  console.log(value)
}

Map方法

  • size
  • has
  • get
  • set
  • delete
  • clear
  • values()
  • entries()
  • forEach()

Set 方法

  • size
  • add
  • delete
  • has
  • clear
  • keys()
  • values()
  • entries()
  • forEach()

数组的扩展

Array.from 将两类对象转换成数组👇

  • 类似数组对象 (本质上:必须有 length 属性)
  • 可遍历对象
let arrayLike = {
  0: "a",
  1: "b",
  2: "c",
  length: 3
}

let arr1 = [].slice.call(arrayLike) // ES5
let arr2 = Array.from(arrayLike)  // ES6
console.log(arr1, arr2)

👉扩展运算符也可将某些数据结构转为数组

扩展运算符调用的是遍历器接口 Symbol.iterator

function foo() {
  const args = [...arguments]
}

[...document.querySelectorAll('div')]

Array.from 接收第二个参数

Array.from(arr, (x) => x * x)

Array.of

将一组值转为数组

Array 只有当参数个数不少于2个,Array才返回由参数组成的新数组。参数只有一个时,代表的是数组的长度。

Object.is

判断俩数是否相等

Object.is(isNaN, isNaN) === true
Object.is(-0, +0) === false

indexOf 内部使用 === 进行判断

entries, keys, values

includes

[NaN].includes(NaN) //true

has

Map 的 has 是用来查找键名的
Set 的 has 是用来查找键值的

模块加载方案

最主要的有 CommonJS 和 AMD,CommonJS 用于服务器,AMD用于浏览器。ES6实现简单,完全可以取代 CommonJS 和 AMD,成为浏览器和服务器通用的模块解决方案

ES6 模块的设计**是尽量静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。 CommonJS 和 AMD 只能在运行时确定这些关系。

CommonJS 模块就是对象,输入时必须查找对象属性

// CommonJS模块
let { stat, exists, readfile } = require('fs');

// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

ES6 模块不是对象,而是通过export命令显式指定输出的代码
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。

你应该知道的13个有用的JavaScript数组技巧

数组是Javascript最常见的概念之一,它为我们提供了处理数据的许多可能性。您可以在编程开始之初就了解它,在本文中,我想向您展示一些您可能不知道并且可能非常有用的技巧。有助于编码!让我们开始吧。

1. 数组去重

这是一个非常流行的关于Javascript数组的采访问题,数组去重。这里有一个快速简单的解决方案,可以使用一个新的Set()。我想向您展示两种可能的方法,一种是使用.from()方法,另一种是使用spread操作符(…)。

var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"];
 
// First method
var uniqueFruits = Array.from(new Set(fruits));
console.log(uniqueFruits); 
// returns ["banana", "apple", "orange", "watermelon", "grape"]

// Second method
var uniqueFruits2 = […new Set(fruits)];
console.log(uniqueFruits2); 
// returns ["banana", "apple", "orange", "watermelon", "grape"]

2. 替换数组中的特定值

有时在创建代码时需要替换数组中的特定值,有一种很好的简单的方法可以做到这一点,我们可以使用.splice(start、valueToRemove、valueToAdd),并将所有三个参数传递给它,这些参数可以指定我们希望从哪里开始修改、希望修改多少值和新值。

var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"];
fruits.splice(0, 2, "potato", "tomato");
console.log(fruits); 
// returns ["potato", "tomato", "orange", "watermelon", "apple", "orange", "grape", "apple"]

3. 没有map()的映射数组

也许每个人都知道数组的map()方法,但是有一个不同的解决方案,它可以用来获得类似的效果和非常干净的代码。我们可以使用.from()方法。

var friends = [
    { name: "John", age: 22 },
    { name: "Peter", age: 23 },
    { name: "Mark", age: 24 },
    { name: "Maria", age: 22 },
    { name: "Monica", age: 21 },
    { name: "Martha", age: 19 },
]
 

var friendsNames = Array.from(friends, ({name}) => name);
console.log(friendsNames);
// returns ["John", "Peter", "Mark", "Maria", "Monica", "Martha"]

4. 空数组

您是否有一个满是元素的数组,但是您需要出于任何目的对其进行清理,并且您不想逐个删除项? 很容易就可以在一行代码中完成。要清空一个数组,您需要将数组的长度设置为0,就是这样!

var fruits = ["banana", "apple", "orange", "watermelon", "apple", "orange", "grape", "apple"];
 

fruits.length = 0;
console.log(fruits);
 // returns []

5. 将数组转换为对象

我们有一个数组,但出于某种目的,我们需要一个对象来处理这些数据,而将数组转换为对象的最快方法是使用众所周知的spread运算符(…)。

var fruits = ["banana", "apple", "orange", "watermelon"];
var fruitsObj = { …fruits };
console.log(fruitsObj);
// returns {0: "banana", 1: "apple", 2: "orange", 3: "watermelon", 4: "apple", 5: "orange", 6: "grape", 7: "apple"}

6. 用数据填充数组

在某些情况下,当我们创建一个数组时,我们希望用一些数据来填充它,或者我们需要一个具有相同值的数组,在这种情况下,.fill()方法提供了一个简单明了的解决方案。

var newArray = new Array(10).fill("1");
console.log(newArray); 
// returns ["1", "1", "1", "1", "1", "1", "1", "1", "1", "1", "1"]

7. 合并数组

您知道如何不使用.concat()方法将数组合并到一个数组中吗?有一种简单的方法可以用一行代码将任意数量的数组合并。正如您可能已经意识到的,spread操作符(…)在处理数组时非常有用,在本例中也是如此。

var fruits = ["apple", "banana", "orange"];
var meat = ["poultry", "beef", "fish"];
var vegetables = ["potato", "tomato", "cucumber"];
var food = […fruits, …meat, …vegetables];
console.log(food); 
// ["apple", "banana", "orange", "poultry", "beef", "fish", "potato", "tomato", "cucumber"]

8. 求两个数组的交集

这也是Javascript面试中最受欢迎的题目之一,因为它考察了你是否可以使用数组方法以及你的逻辑是什么。为了找到两个数组的交集,我们将使用本文前面展示的方法之一,以确保数组中的值不重复,并使用.filter方法和.include方法。最后,将得到两个数组的交集。例:

var numOne = [0, 2, 4, 6, 8, 8];
var numTwo = [1, 2, 3, 4, 5, 6];
var duplicatedValues = […new Set(numOne)].filter(item => numTwo.includes(item));
console.log(duplicatedValues); 
// returns [2, 4, 6]

9. 从数组中删除假值

首先,让我们定义假值。在Javascript中,假值是false, 0, " ", null, NaN, undefined。现在我们可以来看看如何从数组中删除这类值。为此,我们将使用.filter()方法。

var mixedArr = [0, "blue", "", NaN, 9, true, undefined, "white", false];
var trueArr = mixedArr.filter(Boolean);
console.log(trueArr); 
// returns ["blue", 9, true, "white"]

10. 从数组中获取随机值

有时我们需要从数组中随机选择一个值。要以一种简单、快速、简短的方式创建它,并保持代码整洁,我们可以根据数组长度获得一个随机索引号。让我们看看代码:

var colors = ["blue", "white", "green", "navy", "pink", "purple", "orange", "yellow", "black", "brown"];
var randomColor = colors[(Math.floor(Math.random() * (colors.length + 1)))]

11. 数组反转

当我们需要反转我们的数组时,没有必要通过复杂的循环和函数来创建它,有一个简单的数组方法可以为我们做所有的事情,只需一行代码,我们就可以使我们的数组反转。让我们检查一下:

var colors = ["blue", "white", "green", "navy", "pink", "purple", "orange", "yellow", "black", "brown"];
var reversedColors = colors.reverse();
console.log(reversedColors); 
// returns ["brown", "black", "yellow", "orange", "purple", "pink", "navy", "green", "white", "blue"]

12. .lastIndexOf()方法

在Javascript中,有一个有趣的方法,它允许查找给定元素的最后一次出现的索引。例如,如果我们的数组有重复的值,我们可以找到它最后一次出现的位置。让我们看看代码示例:

var nums = [1, 5, 2, 6, 3, 5, 2, 3, 6, 5, 2, 7];
var lastIndex = nums.lastIndexOf(5);
console.log(lastIndex); 
// returns 9

13. 将数组中的所有值相加

这个也是面试中经常被问到的问题,将数组中的所有值相加;它可以在一行代码中使用.reduce方法来解决。让我们看看代码:

var nums = [1, 5, 2, 6];
var sum = nums.reduce((x, y) => x + y);
console.log(sum); 
// returns 14

结论

练习题

练习题

  • 对于箭头函数,this 关键字指向的是它当前周围作用域(简单来说是包含箭头函数的常规函数,如果没有常规函数的话就是全局对象),这个行为和常规函数不同。

  • new Number() 是一个内建的函数构造器。虽然它看着像是一个 number,但它实际上并不是一个真实的 number:它有一堆额外的功能并且它是一个对象。

当我们使用 == 操作符时,它只会检查两者是否拥有相同的值。因为它们的值都是 3,因此返回 true

  • 静态方法被设计为只能被创建它们的构造器使用(也就是 Chameleon),并且不能传递给实例。因为 freddie 是一个实例,静态方法不能被实例使用,因此抛出了 TypeError 错误。

因为函数是对象!(除了基本类型之外其他都是对象)
函数是一个特殊的对象。你写的这个代码其实不是一个实际的函数。函数是一个拥有属性的对象,并且属性也可被调用。

  • 20

引入的模块是 只读 的: 你不能修改引入的模块。只有导出他们的模块才能修改其值。
是通过 var, const 或 let 关键字声明的变量无法用 delete 操作符来删除。

通过defineProperty方法,我们可以给对象添加一个新属性,或者修改已经存在的属性。而我们使用defineProperty方法给对象添加了一个属性之后,属性默认为 不可枚举(not enumerable). Object.keys方法仅返回对象中 可枚举(enumerable) 的属性,因此只剩下了"name".

用defineProperty方法添加的属性默认不可变。你可以通过writable, configurable 和 enumerable属性来改变这一行为。这样的话, 相比于自己添加的属性,defineProperty方法添加的属性有了更多的控制权。

  • 69
    使用padStart方法,我们可以在字符串的开头添加填充。传递给此方法的参数是字符串的总长度(包含填充)。字符串Lydia Hallie的长度为12, 因此name.padStart(13)在字符串的开头只会插入1(13 - 12 = 1)个空格。

如果传递给padStart方法的参数小于字符串的长度,则不会添加填充。

  • 72
    String.raw函数是用来获取一个模板字符串的原始字符串的,它返回一个字符串,其中忽略了转义符(\n,\v,\t等)。但反斜杠可能造成问题,因为你可能会遇到下面这种类似情况:

  • 97
    Symbol类型是不可枚举的。Object.keys方法返回对象上的所有可枚举的键属性。Symbol类型是不可见的,并返回一个空数组。 记录整个对象时,所有属性都是可见的,甚至是不可枚举的属性。

  • 114

  • 128
    通过方法 Number.isNaN,你可以检测你传递的值是否为 数字值 并且是否等价于 NaN
    通过方法 isNaN, 你可以检测你传递的值是否一个 number

  • 133

  • 143

  • 输出结果,如果希望1s输出1个,如何改

const list = [1, 2, 3]
const square = num => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(num * num)
    }, 1000)
  })
}

function test() {
  list.forEach(async x=> {
    const res = await square(x)
    console.log(res)
  })
}
test()
async  function test() {
    for(let i =0;i<list.length;i++){
        const res = await square(list[i])
        console.log(res)
    }
}
test()

React服务端渲染

参考文献
参考文献
React官方提供了服务端渲染的API
- React.renderToString
把react元素转成HTML字符串,并在服务端标志reactid,所以在浏览器端再次渲染时,React 只是做事件绑定等前端相关的操作,而不会重新渲染 整个 DOM 树,这样能带来高性能的页面首次加载
- React.renderToStaticMarkup
如果应用基本上是静 态文本,建议用这个方法。少了大批的 reactid,DOM 自然精简了不少,在 IO 流传输上 也节省了流量。

同构的种类

  • 内容同构
function add(a, b) {
    return a + b
}
  • 形式同构
function doSomething() {
    if (server) {
        //    do something in server side
    } else if (client) {
        //    do something in client side
    }
}

同构的层次

  • function: setTimeout,lodash,moment
  • feature:react,redux
  • framework: isomorphic-mvc,next.js,flux,mvvm

client-side rendering

  • User connection
  • Downloading html page and Javascript bundle
  • Client-side rendering
  • The user sees the page

server-side rendering

  • User connection
  • Server-side rendering
  • Downloading html page
  • The user sees the page
  • Downloading javascript bundle

同构的实现策略

  • 只在client/server有交集的部分做同构
  • 能够内容同构的直接复用
  • 内容不同构的封装成形式同构

形式同构实现思路

user-agent.jpg
cookie.jpg
redirects.jpg

isomorphic-mvc目标

  • 既是单页应用,又是多页应用(SPA + SSR)

Isomorphic-MVC 的技术选型

  • Router: create-app = history + path-to-regexp
  • View: React = renderToDOM || renderToString
  • Model: relite = redux-like library
  • Ajax: isomorphic-fetch

create-app 的同构理念

create-app.jpg

如何处理 CSS 按需加载?

  • 问题根源:浏览器只在 dom-ready 之前会等待 css 资源加载后再渲染页面

  • 问题描述:当单页跳转到另一个 url,css 资源还没加载完,页面显示成混乱布局

  • 处理办法:将 css 视为预加载的 ajax 数据,以 style 标签的形式按需引入

  • 优化策略:用 context 缓存预加载数据,避免重复加载

react中this指向

class component中声明函数的几种方法

  • 方法1
class ProfilePage extends React.Component {
  showMessage = () => {
    console.log("Followed ");
  };

  handleClick = () => {
    setTimeout(this.showMessage, 0);
  };

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
  • 方法2
class ProfilePage extends React.Component {
  constructor(props) {
    super(props);
    this.showMessage = this.showMessage.bind(this);
    this.handleClick = this.handleClick.bind(this);
  }
  showMessage() {
    console.log("Followed ");
  }

  handleClick() {
    setTimeout(this.showMessage, 0);
  }

  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}
  • 方法3
export default class ProfilePage extends React.Component {
  render() {
    const showMessage = () => {
      console.log("Followed ");
    };
    const handleClick = () => {
      setTimeout(showMessage, 0);
    };
    return <button onClick={handleClick}>Follow</button>;
  }
}
  • 方法4
export default class ProfilePage extends React.Component {
  showMessage() {
    console.log("Followed ");
  }
  render() {
    const handleClick = () => {
      setTimeout(this.showMessage, 0);
    };
    return <button onClick={handleClick}>Follow</button>;
  }
}

为什么要在React类组件中为事件处理函数绑定this

原文地址

  • 如果不绑定方法,this值为window全局对象,严格模式下为undefined

此时,handleClick方法丢失了上下文(组件实例),即this

export default class ProfilePage extends React.Component {
  constructor(props) {
    super(props);
  }
  handleClick(event) {
    console.log(this);
  }
  render() {
    return <button onClick={this.handleClick}>Follow</button>;
  }
}

默认绑定

function display() {
    console.log(this)
}

display()

隐式绑定

const obj = {
    name: 'Saurabh',
    display: function () {
        console.log(this.name)
    }
}
obj.display()

事件处理程序方法会丢失其隐式绑定的上下文。当事件被触发并且处理程序被调用时,this的值会回退到默认绑定,即值为 undefined,这是因为类声明和原型方法是以严格模式运行。
当我们将事件处理程序的 this 绑定到构造函数中的组件实例时,我们可以将它作为回调传递,而不用担心会丢失它的上下文。
箭头函数可以免除这种行为,因为它使用的是词法 this 绑定,会将其自动绑定到定义他们的函数上下文。

跨域

参考文献
软一峰
CORS

JSONP

  • server端
const url = require('url');
require('http').createServer((req, res) => {
    const data = {
        x: 10
    };
    const callback = url.parse(req.url, true).query.callback;
    console.log({callback});
    res.writeHead(200);
    res.end(`${callback}(${JSON.stringify(data)})`)
}).listen(3000, '127.0.0.1');
console.log('启动服务器,监听端口3000');
  • 客户端
<script>
    function jsonpCallback(data) {
        alert('获得x数据:' + data.x)
    }

</script>
<script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>

优点:兼容性好,对浏览器没有依赖性
缺点:

  • 支持Get,不支持 POST 等其他类型的 HTTP 请求
  • 只支持跨域 HTTP 请求这种情况,不能解决不同域的两个页面通信
  • 无法捕获 JSONP 请求连接异常,只能通过超时进行处理

CORS

CORS 跨域资源共享(cross-origin resource sharing)

CORS 需要浏览器和服务器同时支持才可以生效。浏览器一旦发现请求是跨域的,就会自动添加一些附加的头信息,有时还会多出一次附加的请求。
因此,CORS通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨域通信。

  • 启动 8080 服务
var express = require('express');
var path = require('path');
var app = express();
app.use(express.static(path.join(__dirname,'public')));
var server = app.listen(8080,function () {
    console.log('启动服务器,监听端口8080');
});
  • 8080 静态页面
<h2>hello</h2>
<script>
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            alert(xhr.responseText)
        }
    };
    xhr.open('GET', 'http://127.0.0.1:3000', true);
    xhr.send()
</script>
  • 跨域请求的服务器
require('http').createServer((req, res) => {
    res.writeHead(200,{
        'Access-Control-Allow-Origin':'http://localhost:8080',
        'Content-Type':'text/html;charset=utf-8'
    });
    res.end('这是你要的数据:1111')
}).listen(3000, '127.0.0.1');
console.log('启动服务器,监听端口3000');

优点:

  • 使用方便,更为安全
  • 支持 POST 请求方式

缺点:

  • 存在兼容性问题

服务器代理

请求后端,让其对该请求代为转发

const url = require('url');
const http = require('http');
const https = require('https');
const server = http.createServer((req, res) => {
    const path = url.parse(req.url).path.slice(1);
    if (path === 'topics') {
        https.get('https://cnodejs.org/api/v1/topics', resp => {
            let data = '';
            resp.on('data', chunk => {
                data += chunk
            });
            resp.on('end', () => {
                res.writeHead(200, {
                    'Content-Type': 'application/json; charset=utf-8'
                });
                res.end(data)
            })
        })
    }
}).listen(3000, '127.0.0.1');
console.log("启动服务");

埋点数据上报

  • AJAX 请求
  • img
    优势:跨域友好,不占用ajax请求,执行无阻塞
  • script

简单请求

类数组对象如何转为数组

arguments是一个对象,只不过它的属性从0开始排,依次为0,1,2...最后还有callee和length属性。我们也把这样的对象称为类数组。

常见的类数组还有:

  • 用getElementByTagName/ClassName/Name()获得的HTMLCollection
  • 用querySlector获得的nodeList

类数组对象转为数组的方法

  • 使用扩展运算符
[...arguments]
  • Array.from
Array.from(arguments)
  • Array.prototype.slice.call()
Array.prototype.slice.call(arguments)
  • Array.prototype.concat.apply()
Array.prototype.concat.apply([],arguments)

❓为什么 Array.prototype.slice.call()方法可以将任何非数组对象转换为数组对象?
因为call改变了执行的上下文环境

Promise实现

参考文章
神三元promise实现

function Promise(executor) {
    var self = this
    self.status = 'pending'
    self.data = undefined
    self.onResolvedCallback = []
    self.onRejectedCallback = []
    executor(resolve, reject)

    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'resolved'
            self.data = value
            for (let i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](value)
            }
        }
    }

    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected'
            self.data = reason
            for (let i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason)
            }
        }
    }

    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }

}


Promise.prototype.then = function (onResolved, onRejected) {
    var self = this
    var promise2
    onResolved = typeof onResolved === 'function' ? onResolved : function (v) {

    }
}

react生命周期

官方文档

挂载

constructor

在 React 中,构造函数仅用于以下两种情况

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例
constructor(props) {
    super(props)
    
    this.state = {
      select,
      height: 'atuo',
      externalClass,
      externalClassText
    }

    this.handleChange1 = this.handleChange1.bind(this)
    this.handleChange2 = this.handleChange2.bind(this)
}

如果没有显示定义它,我们会拥有一个默认的构造函数
如果显示定义了构造函数,我们必须在构造函数第一行执行super(props),否则我们无法在构造函数里拿到this对象,这些都属于ES6的知识

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

static getDerivedStateFromProps(nextProps, prevState)

- 他应该返回一个对象来更新state,如果返回null则不更新任何内容。
- 适用于 state 的值在任何时候都取决于 props
- 特点:每次渲染前触发此方法。UNSAFE_componentWillReceiveProps仅在父组件更新渲染时触发,而不是在内部调用setState时。

该函数会在挂载时,接收到新的props,调用了setState和forceUpdate时被调用
getDerivedStateFromProps 与 componentDidUpdate一起将会替换掉所有的 componentWillReceiveProps。

render

render方法中不要做异步请求、改变state、引起副作用

componentDidMount

更新

static getDerivedStateFromProps

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate生命周期中不能做setState

render

render生命周期中不能setState

getSnapshotBeforeUpdate(prevProps, prevState)

- 在最近一次渲染输出之前调用,在组件发生改变之前从DOM捕获一些信息。此生命周期的任何返回值将作为参数传递给componentDidUpdate,getSnapshotBeforeUpdate 配合 componentDidUpdate 可以取代 componentWillUpdate。

componentDidUpdate(prevProps, prevState, snapshot)

可以在 componentDidUpdate 中 setState, 但是 必须被终止条件包裹,否则会引起无限循环

卸载

  • componentWillUnmount

移除 API ❓

  • UNSAFE_componentWillMount

此方法中调用 setState 不会触发额外的渲染。建议使用 constructor 来初始化 state
应该避免在此方法中引入副作用和订阅
在componetWillMount中请求数据,至少有一次会用空数据渲染,因为在第一次渲染之前,fetch或者axios还没有返回
针对这种情况,应该设置 component 的初始状态
如果需要再服务端请求接口,componentWillMount 会被调用两次,一次在服务端,一次在客户端

  • UNSAFE_componentWillUpdate

替换为getSnapshotBeforeUpdate

  • UNSAFE_componentWillReceiveProps

替换为getDerivedStateFromProps

fetch data 生命周期

  • componentWillMount
  • componentDidMount

Why

参考文献

为何移除 componentWillMount

因为在 React 未来的版本中,异步渲染机制可能会导致单个组件实例可以多次调用该方法。很多开发者目前会将事件绑定、异步请求等写在 componentWillMount 中,一旦异步渲染时 componentWillMount 被多次调用,将会导致:

  • 进行重复的时间监听,无法正常取消重复的 Listener,更有可能导致内存泄漏
  • 发出重复的异步网络请求,导致 IO 资源被浪费
  • 在服务端渲染时,componentWillMount 会被调用,但是会因忽略异步获取的数据而浪费 IO 资源

为何移除 componentWillUpdate

大多数开发者使用 componentWillUpdate 的场景是配合 componentDidUpdate,分别获取 rerender 前后的视图状态,进行必要的处理。但随着 React 新的 suspense、time slicing、异步渲染等机制的到来,render 过程可以被分割成多次完成,还可以被暂停甚至回溯,这导致 componentWillUpdate 和 componentDidUpdate 执行前后可能会间隔很长时间,足够使用户进行交互操作更改当前组件的状态,这样可能会导致难以追踪的 BUG。

React 新增的 getSnapshotBeforeUpdate 方法就是为了解决上述问题,因为 getSnapshotBeforeUpdate 方法是在 componentWillUpdate 后(如果存在的话),在 React 真正更改 DOM 前调用的,它获取到组件状态信息更加可靠。

除此之外,getSnapshotBeforeUpdate 还有一个十分明显的好处:它调用的结果会作为第三个参数传入 componentDidUpdate,避免了 componentWillUpdate 和 componentDidUpdate 配合使用时将组件临时的状态数据存在组件实例上浪费内存,getSnapshotBeforeUpdate 返回的数据在 componentDidUpdate 中用完即被销毁,效率更高。

forceUpdate

函数式组件使用forceUpdate

const [ignored, forceUpdate] = useReducer(x => x + 1, 0);

forceUpdate 直接对组件进行 re-render
forceUpdate 将跳过 shouldComponentUpdate 生命周期。但是对子组件,还是会正常触发生命周期的,包括 shouldComponentUpdate

setState 会触发哪些生命周期

  • static getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

map一个组件,向其中插入一个内容 会触发哪些生命周期

生命周期的执行原理

生命周期相当于内置的一些方法,甚至可以自定义生命周期。
执行原理:

let promiseList = []
if (this.shouldComponentCreate) {
    let shouldCreate = await this.shouldComponentCreate()
    if (shouldCreate === false) {
        return null
    }
}
if (this.componentWillCreate) {
    promiseList.push(this.componentWillCreate())
}
if (promiseList.length) {
    await Promise.all(promiseList)
}

interface与type

共同点

都可以描述一个对象或者函数

  • interface
interface User {
    name: string,
    age: number
}

interface SetUser {
    (name: string, age: number): void
}
  • type
type User = {
    name: string,
    age: number
}
type SetUser = (name: string, age: number): void;

扩展与交叉类型

interface 可以 extends ,type 不允许 extends 和 implement,但 type 可以通过交叉类型实现 interface 和 extends 的行为

  • interface extends interface
interface Name {
    name: string
}

interface User extends Name {
    age: number
}
  • interface extends type
type Name = {
    name: string
}

interface User extends Name {
    age: number
}
  • type 与 type 交叉
type Name = {
    name: string
}
type User = Name & {
    age: number
}
  • type 与 interface 交叉
interface Name {
    name: string
}

type User = Name & {
    age: number
}

不同点

type 可以, interface 不行

  • type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Number = string
interface Dog {
    Wof()
}
interface Cat {
    Miao()
}
// 联合类型
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog,Pet]
  • type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div

interface 可以而 type 不行

  • interface 能够声明合并
interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

JavaScript 继承

原型链继承

重写原型对象

SubType.prototype = new SuperType()

借用构造函数

function SuperType() {
    this.colors = ['red', 'yellow', 'blue']
}

function SubType() {
    SuperType.call(this)
}

优势:传递参数

function SuperType(name) {
    this.name = name
}

function SubType() {
    SuperType.call(this, 'Nichal')
    this.age = 29
}

劣势:无法复用

组合继承

function SuperType(name) {
    this.name = name
    this.colors = ['red', 'blue', 'yellow']
}

SuperType.prototype.sayName = function () {
    console.log(this.name)
}

function SubType(name, age) {
    SuperType.call(this, name)
    thia.age = age
}

SubType.prototype = new SuperType()
SubType.prototype.sayAge = function () {
    console.log(this.age)
}

原型式继承

function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

寄生式继承

function object(o) {
    function F() {
    }

    F.prototype = o
    return new F()
}

function createAnother(original) {
    const clone = object(original)
    clone.sayHi = function () {
        console.log('hi')
    }
    return clone
}

const person = {
    name: 'Nicholas',
    friends: ["Shelby", 'Court', 'Van']
}
const annotherPerson = createAnother(person)
annotherPerson.sayHi()

CSS清除浮动

清除浮动影响方法很多,添加包裹属性的元素,例如:position:absolute,display:inline-block,float:left,overflow:hidden

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.