notes's People
notes's Issues
ES6
函数
- 参数默认值
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
- 参数默认值与解构赋值
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中抛出错误
计算机基础
数据请求过程
-
请求 baidu.com
-
DNS 解析 baidu.com,得到 IP 地址。
IP地址和域名的对应关系通常会被客户端缓存,只有当在DNS缓存都没有命中的时候,才去请求DNS服务器,获取对应的IP地址。
域名解析步骤:
- 判断浏览器是否有缓存 IP 地址。
- 判断本机是否有缓存该 IP 地址,如:检查 Host 文件。
- 判断本地域名解析服务器是否有缓存 IP 地址,如:电信,联通等运营商。
- 向 DNS 根域名解析服务器,解析域名 IP 地址。
- 向 DNS 二根域名解析服务器,解析域名 IP 地址。
- 以此类推,最终获得 IP 地址。
-
建立 TCP 连接。
得到IP地址之后,客户端就能和服务端建立连接,首先是建立TCP连接;
TCP连接是一种面向连接的,可靠的,基于字节流的传输层通信协议;建立 TCP 连接需要 3 个步骤,俗称三次握手。
- 第一次握手:客户端向服务器端发送序列号 seq=x 的标识,表示开始建立连接。
- 第二次握手:服务器端回发一个 ack=x+1 的标识,表示确认收到第一次握手,同时发送自己的标识 seq=y。
客户端确认自己发出的数据能够被服务器端收到。- 第三次握手:客户端发送 ack=y+1 的标识,标识确认收到第二次握手。
服务器端确认自己发出的数据能够被客户端收到。
经过了 3 次握手,即保证了客户端和服务器端都能正常发送和接收数据,TCP 连接也就建立成功了。
-
IP 协议通过算法,计算出一条通往服务器最优路径。
-
IP 沿着路径跳转时,会通过 ARP 协议把 IP 地址转换成 Mac 地址。
-
以太网通过 Mac 地址,找到通信双方的硬件接口。
-
物理层通过网线作为载体,在两个硬件接口之间传输比特信号。
-
TCP 连接建立完毕。
-
建立 SSL 安全层。
-
发送 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
};
Vue3
React
事件处理
- 小驼峰命名,例如:
<button onClick={handleClick}></button>
- 阻止默认行为,必须显示的调用preventDefault
function handleClick(e) {
// react中的e是合成事件,不需要我们担心跨浏览器兼容的问题
e.preventDefault()
//...
}
- 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>
)
}
}
- 参数传递
// 事件对象e通常作为函数的第二个参数传递
// 箭头函数中的e需要显示的传递
<button onClick={(e) => this.handleClick(id, e)}></button>
// bind方式,e被隐式的传递,不应该在jsx中使用bind,bind也会返回一个新函数
<button onClick={this.handleClick.bind(this, id)}></button>
条件渲染
- if语句
- 在jsx中,通过花括号{}包裹代码,可以在里面嵌入任何表达式:
- &&
- 三目运算
- render函数返回null阻止组件渲染
列表 & key
- key不会当做prop传入子组件,只有react内部才会使用到key
表单
- 受控组件:指被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>
- 非受控组件
// 允许用户从本地选择一个或者多个文件,value是只读的,且只能有用户主动发起读取文件的行为
<input type="file" />
- 处理表单:Formik
状态提升
- 多个子组件状态需要共享时,可以将状态提升至共同的父组件中
组合
- 特殊的prop:children,类似vue的默认插槽
- react组件的prop可以是任意数据,包含事件,react元素等
- 组合:将组件A写入组件B的内容中,B包裹A,B将组件A的内容通过prop.children传入,类似vue插槽
- 使用场景:底层组件需要从顶层组件获取数据,中间层次不关心这些数据,为了避免需要一层层传递这些数据,将底层组件放在顶层组件中创建,使用顶层组件的作用域,并把底层组件当做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或者一些缓存的用户信息等
- Context.Provider
// 只有当所处组件树中没有匹配到Provider时,defaultValue才会生效
const MyContext = React.createContext(defaultValue);
// 每个context对象都会返回一个Provider React组件,它允许消费组件订阅context的变化
// 一个Provider组件可以对应多个消费组件,多个Provider组件可以嵌套使用,里层的数据会覆盖外层的数据
// 当Provider的value值发生变化,它内部的所有消费组件都会重新渲染,且不受shouldComponentUpdate函数的影响
<MyContext.Provider value={/* 某个值 */}>
- 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;
/* 基于这个值进行渲染工作 */
}
}
- Context.Consumer - functional
function MyClass() {
return (<MyContext.Consumer>
{value => {
// 基于context的值进行渲染
}}
</MyContext.Consumer>)
}
- Context.displayName
定义在DevTools中如何显示创建的context
const MyContext = React.createContext(defaultValue)
MyContext.displayName = 'MyDisplayName'
<MyContext.Provider /> // 在DevTools中显示为 MyDisplayName.Provider
<MyContext.Consumer /> // 在DevTools中显示为 MyDisplayName.Consumer
- 动态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
生命周期
- 挂载阶段,生命周期调用顺序
- 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()
- 更新阶段,生命周期调用顺序
- 不常用 static getDerivedStateFromProps()
- 不常用 shouldComponentUpdate()
- render()
- 不常用 getSnapshotBeforeUpdate()
- ComponentDidUpdate(prevProps, prevState, snapshot)
- 更新后立即被调用,首次渲染不会触发
- 在这里触发网络请求数据
- 如果要在这里调用setState函数,那么必须包裹在一个条件语句中,避免导致死循环
- 如果shouldComponentUpdate()返回false,则不会调用ComponentDidUpdate()
即将过时,避免使用,以下方法可能在一次更新中被调用多次
- componentWillUpdate(nextProps, nextState)
- componentWillReceiveProps(nextProps)
- 卸载阶段
- componentWillUnmount()
- 在组件卸载及销毁之前直接调用
- 执行必要的清理操作:清除定时器,取消网络请求,清除订阅等
- 避免在这里执行setState,因为组件将不会再重新渲染
- 错误处理
渲染过程中,生命周期,或子组件的构造函数中抛出错误时,会调用以下方法
- 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不发生变化)
- querySelector
语法:
element = baseElement.querySelector(selectors);
参数:
element
和 baseElement
是 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
- webpack HMR 原理 链接
- JavaScript 单线程 事件循环(event loop),异步任务 同步任务,微任务(promise.then) 宏任务(setTimeout);
执行顺序 宏任务 - 同步任务 - 微任务 -宏任务
异步编程
Event Loop - JavaScript 基本类型和基本包装类型:Boolean, Number, String; 每次访问基本类型的某个方法或者属性时,先用基本包装类型
新建一个实例,通过实例访问对应的属性或者方法,将返回值返回,并删除实例;该实例存在的生命周期只在访问的一瞬间;
链接
2021/02/04
- object.defineProperty, Proxy, Reflect 链接
- JavaScript模块化规范:模块化是为了解决js文件之间的不确定依赖关系;CommonJS, ES6
链接 - git pull 和 git fetch 链接
- 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)
浅拷贝与深拷贝
浅拷贝
- object.assign()
- 遍历赋值
- ES6扩展运算符
深拷贝
- 递归遍历(栈溢出)
- JSON.parse(JSON.stringify(obj))
序列化之后,
- Date -> 字符串类型
- RegExp、Error对象 -> 空对象{}
- function, undefined, 构造函数 -> 会被丢失
- NaN、Infinity和-Infinity -> null
- 存在循环引用会报错
- 代码实现
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
}
- 扩展:还可以考虑symbol类型的key的拷贝,
Object.getOwnPropertySymbols(obj)
Javascript
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.