Giter Club home page Giter Club logo

notes's People

Stargazers

 avatar  avatar

Watchers

 avatar  avatar

notes's Issues

ES6

函数

  1. 参数默认值
function log(x, y = 0) {
  console.log(x, y);
}
// undefined才能触发函数默认值
log(1) // 1, 0
log(1, undefined) // 1, 0
log(1, 2) // 1, 2
log(1, null) // 1, null
  1. 参数默认值与解构赋值
function foo({x, y = 5}) {
  console.log(x, y);
}
foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

3.定义了默认值的参数应该放在尾部
4.函数length属性
定义:将返回没有指定默认值的参数个数

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
// 函数的length属性,不包括 rest 参数
(function(...args) {}).length // 0 
// 如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1

5.作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。

var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

6.应用

  • 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter

参数mustBeProvided的默认值等于throwIfMissing函数的运行结果(注意函数名throwIfMissing之后有一对圆括号),这表明参数的默认值不是在定义时执行,而是在运行时执行。如果参数已经赋值,默认值中的函数就不会运行。

  • 将参数默认值设为undefined,表明这个参数是可以省略的
function foo(optional = undefined) { ··· }

Webpack

plugins 插件

webpack-dev-middleware

wepack-hot-middleware

Vuex

Vuex

vuex集中式的存储应用中所有组件的状态,并以相应的规则保证,状态以可预测的方式发生变化。

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules,
  getters
})

// 根实例
new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
  • Vuex是一个插件,Vue.use(Vuex)会调用Vuex的install方法,install方法接收Vue作为参数,并将参数保存在一个变量中,这样这个插件就不用依赖Vue,构建的插件包也不会包含Vue;
  • install方法中使用Vue.mixin({beforeCreate: function() {}}),全局混入beforeCreate函数,在函数中,将根实例传入的store实例注入到每个组件实例中;
  • commit和dispatch是store实例上的两个方法,创建store实例时,会传入state,mutation,actions,getters等选项数据,所以在调用commit和dispatch时,可以向这两个方法内部传入state等参数;
  • 创建store实例时,会把传入的state设置成响应式数据,拦截其get和set函数,只有通过commit提交的mutation才能修改state;
  • actions会返回一个promise
  • 对传入的getters对象,遍历这个对象,并使用Object.defineProperty拦截其get和set,将get设置成key对应的函数,在set中抛出错误

计算机基础

数据请求过程

  1. 请求 baidu.com

  2. DNS 解析 baidu.com,得到 IP 地址。

    IP地址和域名的对应关系通常会被客户端缓存,只有当在DNS缓存都没有命中的时候,才去请求DNS服务器,获取对应的IP地址。

    域名解析步骤:

  • 判断浏览器是否有缓存 IP 地址。
  • 判断本机是否有缓存该 IP 地址,如:检查 Host 文件。
  • 判断本地域名解析服务器是否有缓存 IP 地址,如:电信,联通等运营商。
  • 向 DNS 根域名解析服务器,解析域名 IP 地址。
  • 向 DNS 二根域名解析服务器,解析域名 IP 地址。
  • 以此类推,最终获得 IP 地址。
  1. 建立 TCP 连接。

    得到IP地址之后,客户端就能和服务端建立连接,首先是建立TCP连接;
    TCP连接是一种面向连接的,可靠的,基于字节流的传输层通信协议;

    建立 TCP 连接需要 3 个步骤,俗称三次握手。

  • 第一次握手:客户端向服务器端发送序列号 seq=x 的标识,表示开始建立连接。
  • 第二次握手:服务器端回发一个 ack=x+1 的标识,表示确认收到第一次握手,同时发送自己的标识 seq=y。
    客户端确认自己发出的数据能够被服务器端收到。
  • 第三次握手:客户端发送 ack=y+1 的标识,标识确认收到第二次握手。
    服务器端确认自己发出的数据能够被客户端收到。
    经过了 3 次握手,即保证了客户端和服务器端都能正常发送和接收数据,TCP 连接也就建立成功了。
  1. IP 协议通过算法,计算出一条通往服务器最优路径。

  2. IP 沿着路径跳转时,会通过 ARP 协议把 IP 地址转换成 Mac 地址。

  3. 以太网通过 Mac 地址,找到通信双方的硬件接口。

  4. 物理层通过网线作为载体,在两个硬件接口之间传输比特信号。

  5. TCP 连接建立完毕。

  6. 建立 SSL 安全层。

  7. 发送 HTTP 请求。

Axios

axios的创建过程

'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var defaults = require('./defaults'); // 默认配置

/**
 * Create an instance of Axios
 *
 * @param {Object} defaultConfig The default config for the instance
 * @return {Axios} A new instance of Axios
 */
function createInstance(defaultConfig) {
  // 创建一个Axios的实例context
  var context = new Axios(defaultConfig); 
  // bind返回一个新函数,函数内的this指向context
  // instance就是request函数,可以用于发送请求
  var instance = bind(Axios.prototype.request, context); 

  // Copy axios.prototype to instance 将Axios原型上的方法和属性都拷贝到instance上
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance 将Axios实例context上的属性和方法都拷贝到instance上
  utils.extend(instance, context);

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults); // 使用默认配置创建的实例

// Expose Axios class to allow class inheritance
axios.Axios = Axios; // axios的Axios属性指向Axios

// Factory for creating new instances
// axios的工厂函数,用于创建自定义配置的axios
axios.create = function create(instanceConfig) {
  return createInstance(utils.merge(defaults, instanceConfig));
};
// ====================================
// axios.create([config])创建的新的axios没有下面的功能
// ====================================
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread 用于批量执行多个异步请求
axios.all = function all(promises) {
  return Promise.all(promises);
};
// axios.spread用来指定接收axios.all中每个请求返回的响应
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

Axios

'use strict';

var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');

/**
 * Create a new instance of Axios
 *
 * @param {Object} instanceConfig The default config for the instance
 */
function Axios(instanceConfig) {
  this.defaults = instanceConfig; // 默认配置
  // InterceptorManager的实例上有一个handlers属性,默认是一个空数组,用于存放成功和失败的回调函数
  this.interceptors = { // 拦截器
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  // 如果config是字符串,默认是get请求,第一个参数是URL,第二个参数是请求参数
  if (typeof config === 'string') {
    config = utils.merge({
      url: arguments[0]
    }, arguments[1]);
  }

  config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
  config.method = config.method.toLowerCase(); // 转小写

  // Hook up interceptors middleware
  // chain : promise回调函数链
  /**
   * dispatchRequest分发请求,判断是用XHR(浏览器环境)还是HTTP(node环境)去发送请求;
   * undefined 用于占位,指请求失败的error回调函数
   * 为undefined表示promise状态为rejected时,在此不处理,透传到下一个promise.reject去处理
   * 通常是在响应拦截器中
   */
  var chain = [dispatchRequest, undefined];
  // config是一个常量,该promise变量调用then方法时,一定会调用成功的回调函数,即fulfilled
  var promise = Promise.resolve(config); 

  /**
   * this.interceptors.request.forEach:这个forEach是InterceptorManager原型上的方法,用于遍历请求拦截器中的handlers
   * 实际应用中使用时,axios.interceptors.request.use(fulfilled, rejected)
   * fulfilled:成功的回调
   * rejected:失败的回调
   * use是InterceptorManager原型上的方法,用于将{fulfilled, rejected}一组回调push到handlers中
   * 使用unshift方法,将请求拦截器中的handler一次放在chain的前面
   */
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
   /**
    * 使用push方法,将请求拦截器中的handler一次放在chain的后面
    */
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  /**
   * 循环处理chain,chain = [请求拦截器2-成功, 请求拦截器2-失败, 请求拦截器1-成功, 请求拦截器1-失败, dispatchRequest, undefined, 响应拦截器1-成功,  响应拦截器1-失败, 响应拦截器2-成功, 响应拦截器2-失败]
   * 所以请求拦截器是先定义的后执行
   */
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

module.exports = Axios;

axios与Axios

axios严格来说不算是Axios的实例,axios是一个函数,且拥有Axios原型上的方法,和Axios实例上的属性;可以直接调用axios(config)发送请求,也可以axios.get(url, [,config]),axios.post(url, [,config])...发送请求(这是属于Axios原型上的方法,有被拷贝到axios上);
Axios实例上的属性default和interceptors也被拷贝到axios中,所以可以通过axios.interceptors.response.use()添加拦截器

解决方案

大型文件断点续传-思路

  • 文件切片 Blob.slice
  • 给每个切片一个标识,用于后续merge切片
  • 创建一个Web Worker,负责上传,避免影响JS主线程运行
  • 由于一个网页有最大tcp可连接数的限制,每个http请求都会有一个tcp链接
  • 所以每次并行发起n个请求(假设n=3),promise.all([req1, req2, req3])
  • 当所有请求都返回结果,再发起下一次3个并行请求
  • 由于http是无状态传输协议,切片标识还有一个作用,就是服务器可以知道这个切片是否已经上传
  • 当发送完所有的切片之后,最后要向服务器发送一个merge请求,表示切片发送完毕,可以合并
  • 服务器合并切片,输出成文件,然后删除切片文件和切片标识

Git

git pull 和 git fetch

  • git pull 将远程仓库拉取到本地的远程仓库副本(缓存)中,然后在将本地的远程仓库副本合并(merge)到本地分支;会影响工作区代码
  • git fetch 仅仅是将远程仓库拉取到本地的远程仓库副本(缓存)中,更新本地远程仓库副本的commitId为最新的commitId;不会影响工作区代码
  • git pull = git fetch + git merge

git merge 和 git rebase

  • feature 分支 和 origin 分支
  • git merge:将feature分支的最新提交和origin分支的最新提交合并,并生产一个新的commit
  • git rebase:先将feature分支的提交取消并暂存,然后更新feature分支到最新的提交(origin的最新的提交),最后将暂存的提交拼接到feature最新的提交后面;这样不会新生成一个“提交记录”

git reset 和 git revert

  • git reset:回滚到某个commit,这个commit之后的提交会被取消掉
  • git revert:回滚某个commit,不影响这个commit后的提交,只是对这个commit逆向操作,取消这个commit的修改,并生成一个新的commit;
  • 通常建议用git revert

Promise

promise 中抛出的异常能否被 try..catch 捕获,为什么

// 以下代码中,catch不会捕获到promise中的错误,运行到console.log(a)时,会直接报错
try {
  let p = new Promise((resolve) => {
    console.log(a) // ReferenceError: a is not defined
    resolve()
  })
  p.then(() => {
    console.log(1)
  })
} catch (error) {
  console.log(3)
  console.log(error)
}

try..catch 只会捕获同步函数的异常,如果 try 里面包含了异步函数,且异步函数抛出异常,catch是不能捕获到这个异常的;promise的异常只能由reject抛出,由promise.catch捕获;
原因:
Event Loop 事件循环机制
宏任务和微任务

架构与设计模式

MVVM、MVC、MVP

这三个模式都是为了处理model和view的耦合

  • MVC:Model-View-Controller
  • MVP:Model-View-Presenter
  • MVVM:Model-View-ViewModel

MVC

  • 在MVC中,所有通信都是单向的,model-->view-->controller-->model
  • 当数据改变后,model将新的数据发送到view,view更新
  • 当视图中有事件发生时,view会发送指令到controller,通知controller做一些业务逻辑
  • controller处理业务逻辑之后,要求model改变状态

MVP

  • 在MVP中,通信都是双向的,但是model和view不直接发生联系,model<==>presenter<==>view
  • 所有的业务逻辑都放在presenter中处理

MVVM

  • MVVM:model<==>view model<-->view
  • 与MVP类似,model和view不直接发生联系,区别在于 MVVM中view 和 view model 采用双向数据绑定,view的变动会自动反应在view model中,反之亦然;
  • view model 功能:当model更新后,自动去更新view;当view改变(发生事件),去更新数据;model和view完全解耦

应用

  • mvc单向数据绑定,数据改变 影响视图,视图改变需要手动执行change事件去改变数据;--react
  • mvvm双向数据绑定,vm-viewmodel 帮我们做了 当数据更新 去更新视图,当视图改变,发生事件,去更新数据,视图view和数据model之间完全解耦; --vue/angular

Vue

Vue响应式数据原理

vue在初始化数据时,通过Object.defineProperty将数据对象转换成getter/setter的形式来追踪变化,读取数据的时候会触发getter,并在getter中进行依赖收集(收集当前组件的watcher);修改数据的时候会触发setter,setter去通知getter中收集的依赖(watcher);watcher接收到通知后,去触发视图更新或者触发某个回调函数。(发布订阅设计模式)

Array变化侦测:

Array也是在getter中收集依赖,但是是在拦截器中触发依赖;
因为数组是通过方法来改变内容的,所以vue通过创建拦截器(自定义的方法)去覆盖数组原型方法的方式来追踪数组的变化的;
覆盖原型,即覆盖数组的_proto_属性,

// 自定义拦截方法methods,并缓存原始原型方法,拦截操作触发依赖后,调用原始原型方法实现功能
array._proto_ = methods 

如果某些浏览器不支持_proto_属性,那么直接将拦截方法设置到被侦测的数组array上,也是有效的;因为当访问一个对象的方法时,只有其自身不存在这个方法时,才会去他的原型上查找这个方法。
数组的新元素赋值操作,数组设置length操作,不能被拦截到,因为不属于原型上的方法;

let list = []
list[0] = 'a'

list.length = 0 // 清空数组

proxy的优势:

由于Object.defineProperty只能追踪一个数据是否被修改,无法追踪新增和删除属性的变化,只能通过vm.$set(确保新增的属性是响应式的)和vm.$delete(确保数据被删除之后能通知到watcher)解决。
使用proxy代理对象,直接拦截对象的所有操作,包括对象的新增和删除属性,数组的新元素赋值操作,数组设置length操作

浏览器

Window.history - 只读属性

History 对象提供了操作浏览器会话历史的接口

history.back();     // 等同于点击浏览器的回退按钮
history.forward();     // 等同于点击浏览器的前进按钮
history.go(-1);     //等同于history.back();
history.go(1);     //等同于history.forward();
history.go(n);  // 如果n超出界限或者n不为整数或者不传,那么这个方法没有任何效果也不会报错

History.pushState 和 History.replaceState

可以改变网址(存在跨域限制)而不刷新页面,vue-router基于此实现

window.history.pushState(data, title, targetURL);
//状态对象:传给目标路由的信息,可为空
//页面标题:目前所有浏览器都不支持,填空字符串即可
//可选url:目标url,不会检查url是否存在,且不能跨域。如不传该项,即给当前url添加data
window.history.replaceState(data, title, targetURL);
//类似于pushState,但是会直接替换掉当前url,而不会在history中留下记录

popstate事件

调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退和前进按钮(或调用history.back()、history.forward()、history.go()方法)时触发;

window.addEventListener('popstate', (event) => {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
});
history.pushState({page: 1}, "title 1", "?page=1");
history.pushState({page: 2}, "title 2", "?page=2");
history.replaceState({page: 3}, "title 3", "?page=3");
history.back(); // Logs "location: http://example.com/example.html?page=1, state: {"page":1}"
history.back(); // Logs "location: http://example.com/example.html, state: null
history.go(2);  // Logs "location: http://example.com/example.html?page=3, state: {"page":3}

hash/hashchange

如果location.hash发生变化,也会导致历史记录栈的变化,并触发popstate事件;

hashchange是老API, 浏览器支持度高, 本来是用来监听hash变化的, 可以被利用来做客户端前进后退, 但应该不是这个API的存在的主要目的。而popstate, 及相关api, pushState等属于HTML5新标准, 产生的目的就是做客户端前进后退的, 不仅可以支持hash, 非hash的同源url也支持.所以一般用法是浏览器支持就用popstate, 不支持再降级使用hashchange。

正则

  • 匹配模式:两种模糊匹配、字符组、量词、分支结构

  • 两种模糊匹配:横向匹配&纵向匹配,横向/a{2,5}/g,表示一个字符可能出现2-5次,纵向/[abc]/,表示一个字符可能是abc中的任意一个

  • 惰性匹配:量词后面加个问号,标识尽可能少的匹配

  • 分支结构:/p1|p2|p3/,管道符隔离多个子模式,分支匹配也是惰性匹配,只要前面的匹配上了,后面的就不会再尝试了

  • 匹配

    中的id="...",该例子:贪婪匹配&惰性匹配&回溯

    • var regex = /id=".*"/ 贪婪匹配,发生回溯;
    • var regex = /id=".*?"/ 惰性匹配,发生回溯 ;
    • var regex = /id="[^"]*?"/ 最优解
  • 位置匹配:p是子模式,(?=p)匹配p前面的位置 (?!p)匹配不是p前面的位置 (?<=p)匹配p后面的位置 (?<!p) 匹配不是p后面的位置

    • 1、数字的千位分隔符表示法:
    • '123456789' -> '123,456,789', '12345678' -> '12,345,678'
    • var regex = /(?!^)(?=(\d{3})+$)/g 解析:()表示分组,该正则匹配一个位置,是三个连续数字的前面,但不能是字符串开头前面的位置,且后一个分组/\d{3}+/表示该分组至少一次以上,即至少3个数字以上;
    • '123456789'.replace(regex, ',') 替换
    • 2、密码验证:密码长度 6-12 位,由数字、小写字符和大写字母组成,但必须至少包括 2 种字符
    • /((?=.*[0-9])(?=.*[A-Z])|(?=.*[0-9])(?=.*[a-z])|(?=.*[a-z])(?=.*[A-Z]))^[0-9A-Za-z]{6,12}$/
    • 解析:(?=.*[0-9])(?=.*[A-Z]),(?=.*[0-9])(?=.*[a-z]),(?=.*[a-z])(?=.*[A-Z]),^,[0-9A-Za-z]{6,12},$
    • 表示开头位置之前还有一个位置,该位置后面的字符满足(?=.*[0-9])(?=.*[A-Z]),即包含数字或者大写字母
    • 另解:至少包含两种字符,即不能全是一种字符
    • /(?!^[0-9]{6,12}$)(?!^[a-z]{6,12}$)(?!^[A-Z]{6,12}$)^[0-9A-Za-z]{6,12}$/
    • 表示开头位置之前还有一个位置,该位置后面的字符满足(?!^[0-9]{6,12}$),即不能全是数字

Tools

vue add 和 npm install

vue add 可能会改变现有的项目结构,npm install 仅仅是安装包;

LeetCode

二叉树的深度(https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/)

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */

思路一:二叉树深度 = 左子树深度 + 右子树深度 + 子树根节点(1)

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    const depth = (node) => {
        if (node === null) {
            return 0
        }
        return Math.max(depth(node.left), depth(node.right)) + 1
    }
    return depth(root)
};

思路二:二叉树深度 = 树的层数

/**
 * @param {TreeNode} root
 * @return {number}
 */
var maxDepth = function(root) {
    let len = 0
    let queue = []
    root && queue.push(root)
    while(queue.length > 0) {
        len++
        let list = []
        queue.forEach((node) => {
            node.left && list.push(node.left)
            node.right && list.push(node.right)
        })
        queue = list
    }
    return len
};

React

事件处理

  1. 小驼峰命名,例如:
<button onClick={handleClick}></button>
  1. 阻止默认行为,必须显示的调用preventDefault
function handleClick(e) {
  // react中的e是合成事件,不需要我们担心跨浏览器兼容的问题
  e.preventDefault()
  //...
}
  1. this绑定
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isToggleOn: true
    }
    // 方法一:手动绑定
    this.handleClick = this.handleClick.bind(this)
  }
  // class中的方法默认不会绑定this
  handleClick() {
    this.setState((state) => ({
      isToggleOn: !state.isToggleOn
    }))
  }
  // 方法二:使用public class fields语法
  handleClick = () => {
    this.setState((state) => ({
      isToggleOn: !state.isToggleOn
    }))
  }
  render() {
    return (
      // 如果直接使用没有绑定this的handleClick,handleClick中的this为undefined
      <button onClick={this.handleClick}></button>
      // 方法三:此方法在每次渲染(调用render函数)的时候,都会创建一个新的函数,如果被当做prop传给子组件,子组件可能会进行额外的渲染,容易导致性能问题
      <myButton onClick={() => this.handleClick()}></myButton>
    )
  }
}
  1. 参数传递
// 事件对象e通常作为函数的第二个参数传递
// 箭头函数中的e需要显示的传递
<button onClick={(e) => this.handleClick(id, e)}></button>
// bind方式,e被隐式的传递,不应该在jsx中使用bind,bind也会返回一个新函数
<button onClick={this.handleClick.bind(this, id)}></button>

条件渲染

  1. if语句
  2. 在jsx中,通过花括号{}包裹代码,可以在里面嵌入任何表达式:
  • &&
  • 三目运算
  • render函数返回null阻止组件渲染

列表 & key

  1. key不会当做prop传入子组件,只有react内部才会使用到key

表单

  1. 受控组件:指被react控制取值和输入的表单元素,react中state是唯一数据源,并且只能通过setState更新
<input type="text" value={this.state.value} onChange={this.handleChange} />
<select value={this.state.value} onChange={this.handleChange}>
  <option>...</option>
</select>
// 多选,value为数组
<select multiple={true} value={['a', 'b']} onChange={this.handleChange}>
  <option>...</option>
</select>
  1. 非受控组件
// 允许用户从本地选择一个或者多个文件,value是只读的,且只能有用户主动发起读取文件的行为
<input type="file" />
  1. 处理表单:Formik

状态提升

  1. 多个子组件状态需要共享时,可以将状态提升至共同的父组件中

组合

  1. 特殊的prop:children,类似vue的默认插槽
  2. react组件的prop可以是任意数据,包含事件,react元素等
  3. 组合:将组件A写入组件B的内容中,B包裹A,B将组件A的内容通过prop.children传入,类似vue插槽
  4. 使用场景:底层组件需要从顶层组件获取数据,中间层次不关心这些数据,为了避免需要一层层传递这些数据,将底层组件放在顶层组件中创建,使用顶层组件的作用域,并把底层组件当做prop传递下去
function Page(props) {
  const userLink = (
    <Link href={user.permalink}>
      <Avatar user={props.user} size={props.avatarSize} />
    </Link>
  )
  return <PageLayout userLink={userLink} />
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}

Context 组件之间共享

向当前组件树下的所有子组件“广播”需要共享的数据,例如local, theme或者一些缓存的用户信息等

  1. Context.Provider
// 只有当所处组件树中没有匹配到Provider时,defaultValue才会生效
const MyContext = React.createContext(defaultValue);
// 每个context对象都会返回一个Provider React组件,它允许消费组件订阅context的变化
// 一个Provider组件可以对应多个消费组件,多个Provider组件可以嵌套使用,里层的数据会覆盖外层的数据
// 当Provider的value值发生变化,它内部的所有消费组件都会重新渲染,且不受shouldComponentUpdate函数的影响
<MyContext.Provider value={/* 某个值 */}>
  1. Context.Consumer - class
class MyClass extends React.Component {
  // class的静态属性contextType会被赋值为一个Context对象
  // 组件内部可以通过this.context来消费最近Context对象上的值
  // this.context可以在任意生命周期中访问到,render函数中也能访问到
  static contextType = MyContext;
  render() {
    let value = this.context;
    /* 基于这个值进行渲染工作 */
  }
}
  1. Context.Consumer - functional
function MyClass() {
  return (<MyContext.Consumer>
  {value => {
    // 基于context的值进行渲染
  }}
  </MyContext.Consumer>)
}
  1. Context.displayName
    定义在DevTools中如何显示创建的context
const MyContext = React.createContext(defaultValue)
MyContext.displayName = 'MyDisplayName'

<MyContext.Provider /> // 在DevTools中显示为 MyDisplayName.Provider
<MyContext.Consumer /> // 在DevTools中显示为 MyDisplayName.Consumer
  1. 动态Context
toggleTheme() {
  // 修改state.theme
}
<ThemeContext.Provider value={this.state.theme}>
  <Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>

也可以将更新context的函数放在context中,Consumer组件可以直接调用Provider传进来的context中的函数,更新context的值,
这样嵌套层级深的组件也可以方便的修改context值

export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {/*...*/}
})

嵌套使用Provider和Consumer,可以实现一个组件消费多个context

错误边界组件

如果一个class组件中,定义了static getDirevedStateFromError()或者componentDidCatch中的任意一个或者两个,那么它就变成一个
错误边界组件,错误边界组件只能捕获其子组件的错误,不能捕获自身的错误;错误边界组件可以嵌套使用,如果一个错误边界组件无法渲染
错误信息,那么它会冒泡至最近的上层错误边界组件去处理。
错误边界只能处理渲染期间的错误,不能捕获事件处理器内部的错误,因为事件处理器不会在渲染期间触发,比如点击事件等。

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      hasError: false
    }
  }

  static getDirevedStateFromError(error) {
    // 更新 state 使下一次渲染能够显示降级后的UI
    return { hasError: true }
  }
  componentDidCatch(error, errorInfo) {
    // 可以在这里将错误日志上报给服务器
    logErrorToService(error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>
    }
    return this.props.childen
  }
}
// 使用
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

错误追踪

在开发环境中,React 16会把渲染期间发生的所有错误打印到控制台,可以定位到具体某个组件(文件名)的错误,
在babel配置中添加插件 babel-plugin-transform-react-jsx-source,可以定位到具体行号的错误信息
使用create-react-app创建,默认是开启错误信息可追踪到文件名和行号的。

Refs转发

React ref 用于实现对组件实例的引用 或者 指向具体的DOM元素
使用React.createRef()创建React ref,只有React.forwardRef()定义的组件才能接受ref参数,
普通函数组件和class组件 不接受 ref参数

const FancyButton = React.forwardRef((props, ref) => (
  // ref转发到DOM元素上
  <button ref={ref} className="fancy-button">{props.children}</button>
))

父组件

const ref = React.createRef()
// 向下传递ref
<FancyButton ref={ref}>click me</FancyButton>

在高阶组件中转发ref

高阶组件:参数为组件,返回值为新组件的函数

function logProps(wrappedComponent) {
  class LogProps extends React.Component {
    constructor(props) {
      super(props)
    }
    render() {
      const {forwardedRef, ...rest} = this.props
      return <wrappedComponent ref={forwardedRef} {...rest}></wrappedComponent>
    }
  }
  return React.forwardRef((props, ref) => {
    // 将ref作为props属性forwardedRef传递下去
    return <LogProps {...props} forwardedRef={ref} />
  })
}

// FancyButton.js
class FancyButton extends React.Component {
  // ...
}
export default logProps(FancyButton)

// 父组件
import FancyButton from './FancyButton'
const ref = React.createRef()

<FancyButton ref={ref} label="click me" handleClick={handleClick} />

ref

  • ref作用于HTML元素时,ref.current指向DOM元素

  • ref作用于自定义class组件时,ref.current指向组件的挂载实例

  • ref不能直接作用于函数式组件,因为函数式组件没有实例,需要配合React.forwardRef使用

  • 回调Refs
    可以将一个函数绑定到ref上,挂载的时候会将DOM或者组件实例当做函数的参数,在函数中可以保存ref值

Fragments

类似vue中的 angular中的,react中使用<React.Fragment>将元素分组,而无需向DOM添加额外的元素。
key是唯一可以传递给Fragment的属性。

// 用在循环中时,可以在React.Fragment上指定key属性
<React.Fragment key={key}>
  <li></li>
  <li></li>
  <li></li>
</React.Fragment>
// 短语法-不支持key属性
<>
  <li></li>
  <li></li>
  <li></li>
</>

高阶组件HOC

高阶组件:参数为组件,返回值为新组件的函数
高阶组件函数应该是一个纯函数,没有任何副作用,不应该对传入的组件做任何修改
传入的组件应该是被包裹在新组件的render函数中(组件组合)
不应该在render函数中调用HOC函数,容易导致组件更新时,丢失子组件的状态

使用HOC返回的被包装的新组件,会丢失原组件的静态方法,需要复制静态方法到新组件上,手动复制需要知道具体有哪些方法
可以使用hoist-non-react-statics自动拷贝所有原组件的静态方法

JSX

  • 是React.createElement(component, props, ...children)函数的语法糖
  • component可以是HTML内置元素,也可以是用户自定义的组件,自定义组件名必须首字母大写
  • 可以使用点语法来引用组件<MyComponents.DatePicker />,但不能使用表达式<MyComponents[props.type] />,应该定义一个大写字母开头的变量 const SpecialComponent = MyComponents[props.type],然后

JSX 中 props

  • 可以使用表达式,或者字符串字面量
    <MyComponent msg={'world'} count={1+2} name="hello" />
  • 属性展开运算符
    const props = {name: 'hello', msg: 'world', coung: 3};
    <MyComponent {...props} />

JSX 中 子元素

  • 子元素可以是任意类型的数据,内容存放在特定属性props.children中
  • 可以是字符串,表达式,嵌套JSX,甚至是函数
  • 只要在组件render之前,保证能将传入的props.children转换成React元素就行
  • boolean, null, undefined是合法的子元素,但是不会渲染任何内容,通常用于条件判断
    <div>
      {isShow && <Content />}
      <Footer />
    </div>

Render Props

定义:组件接收一个函数prop,该函数用于告知组件需要渲染什么内容,这种技术称为"Render Props"

PropTypes 类型检查

import PropTypes from 'prop-types';
class MyComponent extends React.Component {
  // 设置默认值-方法二 需要配合Bable转换工具`transform-class-properties`使用
  static defaultProps = {
    name: 'stranger'
  }
}
// 设置默认值-方法一
MyComponent.defaultProps = {
  name: 'stranger'
}
MyComponent.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number
}

处理表单数据

  • 受控组件:表单数据由React组件来管理
  • 非受控组件:表单数据由DOM节点来处理,ref
// 受控组件使用value设置默认值,且value会控制组件的后续更新
// 非受控组件使用defaultValue设置默认值,这样react不会控制非受控组件的更新
render() {
  defaultVal = ''
  return (
    <form>
     <input value={defaultVal} />
     <input defaultValue={defaultVal} />
    </form>
  )
}

React State 初始化两种方法

class MyComponent extends React.Component {
  constructor(props) {
    // 构造函数中必须先调用父类构造函数super(),然后才可以在构造函数中访问this
    super(props)
    this.state = {
      // ...
    }
  }
}
class MyComponent extends React.Component {
  // 
  state = {
    // ...
  }
}

React内置的PureComponent组件

在渲染之前,对props和state进行浅层比较,跳过不必要的更新

React.Component

生命周期

  1. 挂载阶段,生命周期调用顺序
  • constructor(props)
    • 如果不需要初始化state或者不进行方法this绑定,则不需要为react组件实现构造函数
    • 在构造函数外部,要是用setState更新state
    • 如果没有声明构造函数,js会默认提供一个,且会自动调用super(...args)
    • 避免将props的值赋值给state
    • 避免在构造函数中引入任何副作用或者订阅
  • 不常用 static getDerivedStateFromProps()
  • render()
    • class组件唯一必须实现的方法
    • 当render函数被调用时,会检查state和props的变化
    • 返回值:react元素,数组或者fragments,portals,字符串或数值(会被渲染成文本节点),布尔值或null(不渲染任何内容)
    • 如果shouldComponentUpdate()返回false,则不会调用render()
  • componentDidMount()
    • 组件挂载到DOM上之后立即调用
    • 在这里触发网络请求数据,添加订阅事件
    • 也可以在这里调用setState函数更新state,它将触发额外的渲染,但此渲染会发生在浏览器更新屏幕之前,所以保证了即使发生两次render,用户也不会看到中间状态,但是容易导致性能问题,谨慎使用。

即将过时,避免使用

  • componentWillMount()
  1. 更新阶段,生命周期调用顺序
  • 不常用 static getDerivedStateFromProps()
  • 不常用 shouldComponentUpdate()
  • render()
  • 不常用 getSnapshotBeforeUpdate()
  • ComponentDidUpdate(prevProps, prevState, snapshot)
    • 更新后立即被调用,首次渲染不会触发
    • 在这里触发网络请求数据
    • 如果要在这里调用setState函数,那么必须包裹在一个条件语句中,避免导致死循环
    • 如果shouldComponentUpdate()返回false,则不会调用ComponentDidUpdate()

即将过时,避免使用,以下方法可能在一次更新中被调用多次

  • componentWillUpdate(nextProps, nextState)
  • componentWillReceiveProps(nextProps)
  1. 卸载阶段
  • componentWillUnmount()
    • 在组件卸载及销毁之前直接调用
    • 执行必要的清理操作:清除定时器,取消网络请求,清除订阅等
    • 避免在这里执行setState,因为组件将不会再重新渲染
  1. 错误处理
    渲染过程中,生命周期,或子组件的构造函数中抛出错误时,会调用以下方法
  • static getDerivedStateFromError() 在这个函数中调用setState,更新state,使下一次渲染降级UI
  • componentDidCatch()

不常用生命周期方法

shouldComponentUpdate(nextProps, nextState): boolean

  • shouldComponentUpdate方法返回false,告诉react可以跳过此次更新,且不会调用componentWillUpdate(), render(), componentDidUpdate()
  • 当props或者state发生变化时,shouldComponentUpdate函数会在渲染之前调用
  • 首次渲染或使用forceUpdate()时不会调用shouldComponentUpdate()

static getDerivedStateFromProps(props, state)

  • 让组件在props变化的时候更新state
  • 在初始挂载和后续更新时,每次调用render函数之前,都会调用getDerivedStateFromProps
  • 此方法中无权访问组件实例

getSnapshotBeforeUpdate(prevProps, prevState)

  • getSnapshotBeforeUpdate在更新之前调用,其返回值作为第三个参数传给componentDidUpdate(prevProps, prevState, snapshot)
  • 通常不需要知道snapshot,但是在重新渲染的过程中手动保留上次滚动位置的情况下非常有用

第三方库

  • memoize-one 根据传入的形参,缓存上一次调用的结果,重新渲染时,如果形参没变,则使用缓存结果,而不用重新计算。

react组件中可以调用的方法只有下面两个,生命周期是react主动调用的

setState(updater, [callback])

  • 不是立即更新,而是将state的更新加入一个队列中
  • 在setState的回调函数callback中,和componentDidUpdate生命周期函数中,可以获取更新后的state
  • setState后会触发重新渲染,如果shouldComponentUpdate函数返回false,则不会重新渲染

forceUpdate(callback)

强制调用组件的render方法,跳过shouldComponentUpdate函数,但是不影响子组件的shouldComponentUpdate

React.memo

  • 高阶组件,记忆组件的渲染结果来提高组件的性能表现
  • 只适用于函数组件
  • 使用条件:组件在给定相同的props的情况下,渲染相同的结果,此时可以将其包装在React.memo
  • React.memo仅检查props的变更,默认进行浅比较,也可以自定义比较函数
  • React.memo(MyComponent, areEqualFn)

React.Children

React.Children.map(children, function[(thisArg)])

React.Children.forEach(children, function[(thisArg)])

React.Children.count(children)

React.Children.only(children)

React.Children.toArray(children) 返回扁平数组,每项增加key值

React.lazy, React.Suspense

动态加载组件
React.Suspense包裹需要动态加载的组件,可以在等待lazy组件加载时做优雅降级,如loading指示器等

const OtherComponent = React.lazy(() => import('./OtherComponent'))
<React.Suspense fallback={<div>Loading...</div>}>
  <OtherComponent></OtherComponent>
</React.Suspense>

ReactDOM

  • ReactDOM.render(element, container[, callback])
    在提供的container中渲染element元素,并返回对该组件的引用(无状态组件返回null),如果提供了回调函数,那么在该组件被渲染或者更新之后执行。
  • ReactDOM.hydrate(element, container[, callback])
    服务端渲染
  • ReactDOM.unmountComponentAtNode(container)
    从DOM中卸载组件,将组件的事件处理器和state一并清除,返回布尔值
  • ReactDOM.findDOMNode(component)
    如果组件已经被挂载到DOM上,怎返回对应的原生DOM元素,如果组件没有渲染任何内容则返回null,如果组件未被挂载,会引发异常,不能用于函数组件。
  • ReactDOM.createPortal(child, container)
    Portal将child渲染到container中

CSS

CSS定义及作用

定义:层叠样式表(Cascading Style Sheets)

作用:用来描述HTML文档的呈现,给网页中的每一个元素设置样式,排版布局,让网页更精美。

HTML5

实现锚点定位及跳转,且url不发生变化)

  1. querySelector
    语法:
element = baseElement.querySelector(selectors);

参数:
elementbaseElement element 对象.
selectors 是一个CSS选择器字符串
返回值:
基础元素(baseElement)的子元素中满足指定选择器组的第一个元素
2. scrollIntoView

<template>
   <div>
       <div id='header'></div>
       <div class='footer' @click='returnTop'></div>
   </div>
</template>
methods: {
  returnTop() {
     document.querySelector("#header").scrollIntoView(true);
  }
}

questions

2021/02/03

  1. webpack HMR 原理 链接
  2. JavaScript 单线程 事件循环(event loop),异步任务 同步任务,微任务(promise.then) 宏任务(setTimeout);
    执行顺序 宏任务 - 同步任务 - 微任务 -宏任务
    异步编程
    Event Loop
  3. JavaScript 基本类型和基本包装类型:Boolean, Number, String; 每次访问基本类型的某个方法或者属性时,先用基本包装类型
    新建一个实例,通过实例访问对应的属性或者方法,将返回值返回,并删除实例;该实例存在的生命周期只在访问的一瞬间;
    链接

2021/02/04

  1. object.defineProperty, Proxy, Reflect 链接
  2. JavaScript模块化规范:模块化是为了解决js文件之间的不确定依赖关系;CommonJS, ES6
    链接
  3. git pull 和 git fetch 链接
  4. ES6
  • 模板字符串/模板字面量:反引号( `` ),多行字符串,插入表达式,嵌套模板字符串,带标签模板字符串(函数)
    模板字符串
  • class 继承:extend,super()
  • ES5 顶层作用域和函数作用域;ES6 新增 块级作用域
  • 箭头函数:this指向定义函数的地方;函数没有arguments对象;不能用作构造函数,不能使用new;不能使用yield,即不能用作Generator函数
  • promise:pending, fulfileld, reject;reject后的东西,一定会进入then中的第二个回调(error回调),如果then中没有写第二个回调,则进入catch; 网络异常(比如断网),会直接进入catch而不会进入then的第二个回调;
  • reject 和 catch
    reject 是用来抛出异常,catch 是用来处理异常;
    reject 和 resolve 是 Promise 的方法 (Promise.reject),而 catch, then, finally 是 Promise 实例(Promise 原型)的方法(Promise.prototype.catch)

浅拷贝与深拷贝

浅拷贝

  1. object.assign()
  2. 遍历赋值
  3. ES6扩展运算符

深拷贝

  1. 递归遍历(栈溢出)
  2. JSON.parse(JSON.stringify(obj))

序列化之后,

  • Date -> 字符串类型
  • RegExp、Error对象 -> 空对象{}
  • function, undefined, 构造函数 -> 会被丢失
  • NaN、Infinity和-Infinity -> null
  • 存在循环引用会报错
  1. 代码实现
deepClone(obj) {
  // 过滤特殊情况
  if (obj === null) {
    return null
  }
  if (typeof obj !== 'object') {
    // 函数/基本类型
    return obj
  }
  const type = Object.prototype.toString.call(obj)
  if (type === '[object RegExp]') {
    return new RegExp(obj)
  } else if (type === '[object Date]') {
    return new Date(obj)
  }
  // new obj.constructor():保证克隆结果与之前保持相同的所属类
  let newObj = obj.constructor ? new obj.constructor() : {}
  
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      newObj[key] = deepClone(obj[key]) // 递归
    }
  }
  return newObj
}
  1. 扩展:还可以考虑symbol类型的key的拷贝,Object.getOwnPropertySymbols(obj)

Javascript

判断对象的数据类型

image

使用 Object.prototype.toString 配合闭包,通过传入不同的判断类型来返回不同的判断函数,一行代码,简洁优雅灵活(注意传入 type 参数时首字母大写)

不推荐将这个函数用来检测可能会产生包装类型的基本数据类型上,因为 call 始终会将第一个参数进行装箱操作,导致基本类型和包装类型无法区分

vue-router

1. 安装和配置方式

// 1. 导入Vue和VueRouter
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

// 2. 路由映射配置
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. 创建router实例
// 每个路由应该映射一个组件。 其中"component" 可以是
// 通过 Vue.extend() 创建的组件构造器,
// 或者,只是一个组件配置对象。
const router = new VueRouter({
  routes
})

// 4. 创建和挂载根实例。
// 记得要通过 router 配置参数注入路由,
// 从而让整个应用都有路由功能
const app = new Vue({
  router
}).$mount('#app')
// 通过注入路由器,我们可以在任何组件内通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由

拓展:Vue.use

2. vue router 原理

  • vue router 是一个插件,插件用于增强功能,插件都有一个install方法,当调用Vue.use(VueRouter)使用这个插件时,use方法会自动去调用插件的install方法
let _Vue;
function install(Vue) {
    if (install.installed && Vue === _Vue) return
    _Vue = Vue;

  Vue.mixin({
    beforeCreate () {
      if (this.$options.router) {
        this._routerRoot = this
        this._router = this.$options.router
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
}

vue router 插件的install方法做了以下事情:

  • install方法接收Vue作为唯一的参数,并将Vue保存在_Vue中,每次调用install方法时,都会检测,如果插件已经注册过,并且Vue与_Vue相等,那么直接return
  • 调用Vue.mixin()函数,全局混入beforeCreate方法,所以每个vue组件(vue实例)创建前都会调用这个方法,最先创建的是vue根实例 new Vue({router}),所以根实例的$options上存在router,这就是创建根实例时为什么要传入router的原因;
  • 在混入的beforeCreate方法中,判断如果组件实例的$options上存在router,就保存在组件实例的_routerRoot中,如果不存在router,就将父组件的router引用过来;
  • 然后在Vue的原型上定义两个只读属性$router和$route,默认读取当前组件实例的_routerRoot的_router和_route;
  • 使用Vue.component注册两个全局组件RouterView和RouterLink,RouterLink默认渲染一个a标签,href属性的值为RouterLink组件的to属性的值;RouterView中监听URL的变化,并根据当前URL从路由表中找出对应的组件渲染;RouterView组件也是一个vue实例,在创建的时候,会调用全局混入的beforeCreate方法,可以获取到router实例,而创建router实例的时候,将路由表传入,所以router实例可以访问到路由表;

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.