awesomedevin / blog Goto Github PK
View Code? Open in Web Editor NEWWelcome to Devin's blog,I'm trying to be a fullstack developer and sticking with it !!!
License: MIT License
Welcome to Devin's blog,I'm trying to be a fullstack developer and sticking with it !!!
License: MIT License
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
arr
,定义返回值result=[]
,第三方遍历tmp=[]
get(arr,tmp,result)
tmp
数组长度与目标数组arr
一致时,将tmp
添加到result
中,否则,对目标数组arr
进行遍历,如果tmp
中不存在arr[index]
,则将arr[index]
添加到tmp中get(arr,tmp,result)
进行递归,递归时需要使用tmp.pop()
来保证每轮递归中的tmp
长度不变,发生变化的只是tmp
的下标值tmp
参数需要进行deepcopy
,这样下一轮的tmp
才会保留本轮添加的arr[index]
,否则返回值为[[],[],[],[],...]
function main(arr){
var result = []
get(arr,[],result)
return result
}
function get(arr,tmp,result){
if(tmp.length === arr.length) //tmp与参数的数组长度相同时,push到result中
{
result.push(tmp)
return
}
for(var index in arr)
{
if(!tmp.includes(arr[index]))
{
tmp.push(arr[index])
get(arr,JSON.parse(JSON.stringify(tmp)),result) //tmp需要进行深deepclone,否则会出现子数组长度为0
tmp.pop()
}
}
}
#coding=utf-8
import copy
def get(arr,tmp,result):
if len(tmp) == len(arr):
result.append(tmp)
return
for item in arr:
if item not in tmp:
tmp.append(item)
# tmp1 = tmp
get(arr,copy.deepcopy(tmp),result)
tmp.pop()
def main(arr):
result = list()
get(arr,[],result)
return result
if __name__ == "__main__":
print(main([1,2,3]))
在h5开发中,我们经常会需要实现一些动效来让页面视觉效果更好,谈及动效便不可避免地会想到动效性能优化这个话题:
那么,CSS3与动效优化有什么关系呢,本文将从浏览器渲染层面讲述CSS3的动效优化原理
Composite
过程。在讨论 Composite
之前,我们还需要了解一下浏览器渲染原理
从该图中,我们可以发现:
DOM 元素
与 Layout Object
存在一一对应的关系Layout Object
属于同一个 Paint Layer (渲染层)
,通过 position、opacity、filter
等 CSS 属性可以创建新的 Paint LayerPaint Layer
会被认为是 Composite Layer (合成层/复合层)
,Composite Layer 拥有单独的 Graphics Layer (图形层)
,而那些非 Composite Layer 的 Paint Layer,会与拥有 Graphics Layer 的父层共用一个我们日常生活中所看到屏幕可视效果可以理解为:由多个位图通过 GPU
合成渲染到屏幕上,而位图的最小单位是像素。如下图:
那么位图是怎么获得的呢,Graphics Layer
便起到了关键作用,每个 Graphics Layer
都有一个 Graphics Context
, 位图是存储在共享内存中,Graphics Context
会负责将位图作为纹理
上传到GPU
中,再由GPU进行合成渲染。如下图:
大多数人对于CSS3的第一印象,就是可以通过3D(如transform)属性来开启硬件加速,许多同学在重构某一个项目时,考虑到动画性能问题,都会倾向将2D属性改为3D属性,但开启硬件加速的底层原理
其实就在于将 Paint Layer 提升到了 Composite Layer
以下的几种方式都用相同的作用:
我们来写两段demo代码,带大家具体分析一下实际情况
demo1. 3D属性开启硬件加速(3d-transform)
.composited{
width: 200px;
height: 200px;
background: red;
transform: translateZ(0)
}
</style>
<div class="composited">
composited - 3dtransform
</div>
可以看到是因为使用的CSS 3D transform,创建了一个复合层
demo2. 对opacity、transform、filter应用 animation(actived) or transition(actived)
<style>
@keyframes move{
0%{
top: 0;
}
50%{
top: 600px;
}
100%{
top: 0;
}
}
@keyframes opacity{
0%{
opacity: 0;
}
50%{
opacity: 1;
}
100%{
opacity: 0;
}
}
#composited{
width: 200px;
height: 200px;
background: red;
position: absolute;
left: 0;
top: 0;
}
.both{
animation: move 2s infinite, opacity 2s infinite;
}
.move{
animation: move 2s infinite;
}
</style>
<div id="composited" class="both">
composited - animation
</div>
<script>
setTimeout(function(){
const dom = document.getElementById('composited')
dom.className = 'move'
},5000)
</script>
这里我们定义了两个keyframes(move、opacity)
,还有两个class(both、move)
,起初 #composited
的 className = 'both'
,5秒延时器后,className = 'move'
,我们来看看浏览器的实际变化。
起初:#composited 创建了一个复合层,并且运动时 fps 非常稳定
之前,我们提到了页面呈现出来所经历的渲染流水线,其实从性能方面考虑,最理想的渲染流水线是没有布局和绘制环节的
,为了实现上述效果,就需要只使用那些仅触发 Composite
的属性。
目前,只有两个属性是满足这个条件的:transforms
和 opacity
。
相关信息可查看:css Triggers
提升为合成层简单说来有以下几点好处:
缺点:
大多数人都很喜欢使用3D属性 translateZ(0) 来进行所谓的硬件加速,以提升性能。但我们还需要切实的去分析页面的实际性能表现,不断的改进测试,这样才是正确的性能优化途径。
输入一个链表,返回一个反序的链表。
使用generateLinkList生成链表linklist
,对链表linklist
进行reverse
class ListNode{
constructor(nextIndex,val){
this.nextIndex = nextIndex
this.val = val
}
}
function generateLinkList(length){ //generate linklist
var list = []
for(var i=0;i<length;i++)
{
// console.log(i)
var listnode = new ListNode(i+1,i)
list.push(listnode)
// console.log(listnode)
}
return list
}
const linklist = generateLinkList(10)
function main(linkList){ //reverse linklist
const result = []
for(var index=0;index<linkList.length;index++)
{
// linkList[index].val
var listnode = new ListNode(linkList[index].nextIndex,linkList[linkList.length-index-1].val)
result.push(listnode)
}
return result
}
console.log(main(linklist))
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function printListFromTailToHead(head)
{
if(!head)
{
return 0
}
var res = []
while(head)
{
res.push(head.val)
head = head.next
}
return res.reverse()
}
我们在做一个大型的复杂应用时,往往有很多数据会在多个页面多个组件中同时被使用,这时如果仍然使用props
传参的方式,就会显得组件之间耦合度过高
,且开发效率低
。
为了解决这个问题,2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
hooks 是react 16.8 引入的特性,他允许你在不写class的情况下操作state 和react的其他特性。
hooks 只是多了一种写组件的方法,使编写一个组件更简单更方便,同时可以自定义hook把公共的逻辑提取出来,让逻辑在多个组件之间共享。
const { state, setState } = useState(initialState)
useEffect(didUpdate,[]) //didUpdate为要做的更新,[]为要监听的变量
Context
对象(React.createContext
返回的值)并且返回当前<MyContext.Provider>
的值, 返回值取决于最近的 <MyContext.Provider>
。const store = useContext(Context)
(state, action) => newState
类型的reducer,并且返回一个与dispatch
方法配对的state
对象(如果你熟悉redux,那么你已经会了)const [state,dispatch] = useReducer(reducers,initialState)
userEffect
,与前者相比不能执行副作用,返回缓存的函数userEffect
,与前者相比不能执行副作用,返回缓存的变量vue中的$refs
,用于操作domuseImperativeHandle
返回了一个回调函数帮助我们解决此类问题function Com(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
Com = forwardRef(Com);
componentDidMount/Update, componentWillUnmount
,会在dom元素更新后立即执行回调函数自定义Hooks
Redux 规定,将模型的更新逻辑全部集中于一个特定的层(Flux 里的 store,Redux 里的 reducer)。Flux 和 Redux 都不允许程序直接修改数据,而是用一个叫作 “action” 的普通对象来对更改进行描述。
需要用到的API有createContext
,useContext
,useReducer
state
state.js
export default {
username:''
}
action
的普通对象来对state进行更改action.js
export default {
setUserName:function(payload){
return {
type:'setUserName',
payload
}
}
}
reducer
)reducer.js
export default function reducer(state,action){
const { payload } = action
switch(action.type)
{
case 'setUserName': // 匹配action中的type,return新的state
return {
...state,
username: payload
}
default:
return state
}
}
useReducer
获取state以及用于事件派发更新state的dispatch
函数index.js
const [state,dispatch] = useReducer(reducers,initialState)
这里我们需要排除props传参
的方式,因为这样就失去了使用redux进行状态管理的意义,这里就需要用到Context
index.js
import React,{createContext, useReducer} from 'react'
import reducers from './reducers'
import initialState from './state'
export const AppContext = createContext({})
export const Consumer = AppContext.Consumer
export function Provider(props){
const [state,dispatch] = useReducer(reducers,initialState)
const store = { state, dispatch }
return (
<AppContext.Provider value={ store }>
{props.children}
</AppContext.Provider>
)
}
这里将useReducer
的返回值放到Provider
的value中,子组件通过Consumer
或useContext
就能访问到了
整个Redux由state、action、reducer、index
构成
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App';
import { Provider } from './redux'
ReactDOM.render(<Provider><App /></Provider>, document.getElementById('root'))
import React, { useContext } from 'react';
import { AppContext } from './redux'
import Child from './child'
import actions from './redux/actions'
function App() {
const { dispatch } = useContext( AppContext ) // 获取事件派发函数
function handleInput(e){
dispatch(actions.setUserName(e.target.value)) // 派发更新事件并传值
}
return (
<div className="App">
<input onInput={handleInput} type='text' />
<Child />
</div>
);
}
export default App;
import React, { useContext } from 'react';
import { AppContext } from './redux'
export default function Children(){
const { state } = useContext(AppContext)
return (
<div>Name:{state.username}</div>
)
}
相信大家都对Sketch有一定的了解和认识。除了基础的矢量设计功能以外,插件更是让Sketch保持强大的独门秘籍。Sketch开放了第三方插件接口,设计师可以在几百种的插件中轻松找到适合自己工作方式的插件,并且他们都非常容易获得和安装。这里主要介绍使用Javascript API for Sketch开发Sketch插件。
Sketch成为梦想中的“设计师工具箱”。但是每个人都有不同的需求,也许你需要一个我们还没有实现的功能。不要担心:插件已经可以满足您的需求,或者你可以轻松创建一个插件。
Sketch中的插件可以做任何用户可以做的事情(甚至更多!)。例如:
根据复杂的规则选择文档中的图层
操作图层属性
创建新图层
以所有支持的格式导出资产
与用户交互(要求输入,显示输出)
从外部文件和Web服务获取数据
与剪贴板交互
操作Sketch的环境(编辑指南,缩放等...)
通过从插件调用菜单选项来自动化现有功能
设计规格
内容生成
透视转换
Sketch 插件都是 *.sketchplugin 的形式,其实就是一个文件夹,通过右键显示包内容,可以看到最普通的内部结构式是这样的:
manifest.json用来声明插件配置信息,commands 定义所有可执行命令,每条 command 有唯一标志符,identifier,menu 定义插件菜单,通过 identifier 关联到执行命令。
my-commond.js是插件逻辑的实现代码实现文件。
这是Sketch的原型Javascript API。 原生Javascript,Sketch的完整内部结构的一个易于理解的子集。它仍然是一项正在进行中的工作。
Javascript API for Sketch 原理:
1、开发文档
https://developer.sketchapp.com/
2、API
https://developer.sketchapp.com/reference/api/
3、Action API
https://developer.sketchapp.com/guides/action-api/
https://developer.sketchapp.com/reference/action/
4、Sketch Source
https://github.com/BohemianCoding/SketchAPI/tree/develop/Source
5、Demo
https://github.com/BohemianCoding/SketchAPI/tree/develop/examples
Sketch模块,用于使用webview创建复杂的UI。有别于一般的插件页面,可以使用webview模块加载一个复杂的Web应用,使其与Sketch进行交互。
在浏览器窗口中创建和控制Sketch:
// In the plugin. const BrowserWindow = require('sketch-module-web-view'); const identifier = "identifier";//webview 标识
let win = new BrowserWindow({identifier, width: 800, height: 600, alwaysOnTop: true})
win.on('closed', () => {
win = null
})// Load a remote URL
win.loadURL('https://github.com')
// Or load a local HTML file
win.loadURL(require('./index.html'))
import { getWebview } from 'sketch-module-web-view/remote'; const = identifier = "identifier";
const existingWebview = getWebview(identifier);
if (existingWebview) {
if (existingWebview.isVisible()) {
// close the devtool if it's open
existingWebview.close()
}
}
const BrowserWindow = require('sketch-module-web-view')
let win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('http://github.com')
let contents = win.webContents
console.log(contents)
1)Sending a message to the WebView from your plugin command
On the WebView:
window.someGlobalFunctionDefinedInTheWebview = function(arg) {
console.log(arg)
}
On the plugin:
browserWindow.webContents
.executeJavaScript('someGlobalFunctionDefinedInTheWebview("hello")')
.then(res => {
// do something with the result
})
2)Sending a message to the plugin from the WebView
On the plugin:
var sketch = require('sketch')
browserWindow.webContents.on('nativeLog', function(s) {
sketch.UI.message(s)
})
On the webview:
window.postMessage('nativeLog', 'Called from the webview')
// you can pass any argument that can be stringified
window.postMessage('nativeLog', {
a: b,
})
// you can also pass multiple arguments
window.postMessage('nativeLog', 1, 2, 3)
使用Sketch webView的方式开发插件。用户通过操作插件界面,webview与Sketch通信解决用户的问题。这样插件界面可以使用现今所有的前端框架与组件库。
注:WebView框架也可以单独的工程与部署。
A、使用官方的sketch-dev-tools sketch内作为调试工具
下载代码,代码运行安装插件即可:
npm install
npm run build
调试界面如下:
B、使用浏览器的开发者模式调试webView。
首先,创建sketch-webview-kit插件工程:
npm install -g skpm
skpm create sketch-webview-kit //创建sketch-webview-kit插件工程
其次,依赖sketch-module-web-view:
npm install sketch-module-web-view
首先,创建webView工程目录,
$ mkdir webapp && cd webapp
然后,创建webView工程
yarn create umi
依次:
选择 app, 然后回车确认;
选上 antd 和 dva,然后回车确认;
最后,安装依赖:
$ yarn
A.部署打包配置
.umirc.js文件中,添加:
outputPath:'../src/dist', //打包后的目录
exportStatic: {
htmlSuffix: true,
dynamicRoot: true //静态自由部署
},
B.HTML 模板
由于Umi生成没有Html文件,可以自己配置。新建 src/pages/document.ejs,umi 约定如果这个文件存在,会作为默认模板,内容上需要保证有 <div id="root"></div>,比如:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Your App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
C.添加新页面
直接在pages文件夹下建立页面的js与css样式文件即可。
第一种方法:
直接部署webView工程,通过Url加载:
win.loadURL('https://github.com')
第二种方法:
加载webView工程打包后的文件:
win.loadURL(require('./dist/index.html'))
注意:
此方法,由umi打包后的静态资源(css、js)需要拷贝到
pannel3/pannel3.sketchplugin/Contents/Resources/_webpack_resources
下。
本地启动webView工程,本地webView工程会在8000端口起一个服务,加载此服务即可:
const Panel = `http://localhost:8000#${Math.random()}`;
win.loadURL(Panel)
文件目录如下:
)
是一个开源库,为设计系统量身定制。它通过将 React 元素渲染到 Sketch 来连接设计和开发之间的鸿沟。
Sketch Javascript API 是源生代码,React - SketchApp 使用react对Javascript API 进行了二次封装。
1)API
2)Demo
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
function rectCover(number)
{
// write code here
if(number<=2){
return number
}else{
var arr = []
arr[1] = 1
arr[2] = 2
for(var i = 3;i<=number;i++)
{
arr[i] = arr[i-1] + arr[i-2]
}
}
return arr[number]
}
该方案主要分为三步:1. 基于 hooks 构建 Store 2. 将 Store 基于 context 传递给子组件 3. 子组件更新 Store,并触发渲染,该方案不适用于复杂应用
userStore.tsx
import { useState } from 'react'
export default function useUserStore () {
const [user, setUser] = useState<IUser>({ })
return {
setUser,
user,
}
}
loginStore.tsx
import { useState } from 'react'
export default function useLoginStore () {
const [login, setLogin] = useState(false)
return {
login,
setLogin,
}
}
context.tsx
import useUserStore from "./userStore";
import useLoginStore from "./loginStore";
import { createContext } from "react";
export const context = createContext(null);
export default function Context({ children }) {
const userStore = useUserStore();
const loginStore = useLoginStore();
console.log("userStore", userStore);
const contextValue = {
userStore,
loginStore
};
return <context.Provider value={contextValue}>{children}</context.Provider>;
}
app.tsx
import Context from "./context";
import Child from "./child";
export default function Index() {
return (
<Context>
<Child />
</Context>
);
}
child.tsx
import { useContext, useEffect } from "react";
import { context } from "./context";
export default function Child() {
const store = useContext(context);
console.log(store);
const { user, setUser } = store?.userStore || {};
const { login, setLogin } = store?.loginStore || {};
useEffect(() => {
setTimeout(() => {
setUser({ name: "AwesomeDevin" });
setLogin(true);
}, 2000);
}, [setUser, setLogin]);
return (
<div>
<p>{user?.name || "未命名"}</p>
<p>{login ? "已登陆" : "未登录"}</p>
</div>
);
}
在线DEMO https://codesandbox.io/s/react-context-ease-store-lv1ibp?file=/src/App.js
实现1个队列
const stack1 = []
function push(node)
{
stack1.push(node)
// write code here
}
function pop()
{
return stack1.shift()
// write code here
}
用两个栈实现1个队列
var stack1 = []
var stack2 = []
function push(node)
{
stack1.push(node)
// write code here
}
function pop()
{
while(stack1.length>1)
{
stack2.push(stack1.pop())
}
var res = stack1[0]
stack1 = stack2.reverse()
stack2 = []
return res
// write code here
}
🚀 专注于不影响用户最佳体验的同时,提升应用首屏加载速度,灵感来自NextJS的预加载.
从上图可以发现,route-resource-preload 预加载后,视图上基本不会再出现 loading 的情况,相对于正常懒加载的 loading 时长,用户体验层面上有了较大的提升。
尽最大努力地去缩短动态导入组件的加载时间
(可以看作是 suspense loading 组件持续时间)以提供最佳交互体验.typescript
支持.route-resource-preload
在兼顾组件代码分割
的同时,通过支持对组件的自动预加载
及手动预加载
,避免因为组件渲染延迟导致组件交互体验差。
正常懒加载
普通组件 及 Module-Federationroute-resource-preload 预加载
普通组件 及 Module-Federation资源 | 正常懒加载(ms) | 预加载 (ms) |
---|---|---|
普通组件 (单个资源文件) | 184 | 1 |
Module-Federation 组件 (6个资源文件) | 405 | 8 |
从表中可以看出,route-resource-preload 预加载显着提升了组件的加载速度,尤其是对于复杂的组件,加载速度的提升更为明显。 这说明在复杂的业务场景下,
预加载可以显着提升组件加载速度和用户体验
.
以上是常规组件预加载时的对比效果
模态弹窗被常规的方式(如 react.lazy
)进行懒加载时,由于是渲染在视图内,会自动进行资源加载,基于route-resource-preload
,可以让模态弹窗组件 visible 为 true 时,才进行资源加载
,为 false 时,不自动加载以减少应用首屏 http 请求数,同时基于预加载机制,在保证页面性能的同时,不影响用户对模态弹窗的交互体验。
正常懒加载
模态弹窗route-resource-preload 预加载
模态弹窗此处有两个http请求,是因为预加载配置了两个组件。
const Modal = useMemo(()=> dynamic({
visible, // false 时,技术组件渲染在视图内,但不自动进行资源预加载,减少首屏非必要的 http 请求
loader: () => import('antd/es/modal'),
loading: () => <>loading...</>,
}),[visible])
<>
<Modal visible={visible} />
<PreloadLink flag="/A" onClick={()=>{setVisible(true)}}>
PreLoad Modal
</PreloadLink>
</>
new RouteResourcePreloadPlugin({
modulePreloadMap: {
"/A": ["../components/A",'antd/es/modal']
}
})
资源 | 正常懒加载(ms) | 预加载 (ms) |
---|---|---|
模态弹窗 (单个资源文件) | 271 | 45 |
route-resource-preload 的目标是Any code can be split
,在不影响用户交互体验的同时,尽可能的提升应用性能`。🚀 🚀 🚀
希望 route-resource-preload 能对你的项目有所帮助,后续还将持续探索 vite 中的使用以及对 vue 的支持,如果你有好的想法,请发表在此。
在js中,
new()
常被用来创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例
1.创建一个简单
JavaScript空对象
(即{});
2.链接该对象到另一个对象 (即设置该对象的_proto_
为构造函数的prototype
);
3.执行构造函数,将构造函数内的this作用域指向1步骤
中创建的空对象{};
4.如果构造函数有返回值,则return返回值
,否则return空对象obj
。
1.我们先来写一个构造函数
function Constructor(obj){
if(obj){
return obj
}
}
2.使用new得到的实例化对象并查看它的__proto__
console.log(new Constructor(123))
console.log(new Constructor({a:123}))
console.log((new Constructor()).__proto__)
可以看到,参数为非对象时,会返回{},参数为对象,返回object
3.现在我们来尝试实现一个new()
function subNew(){
var obj = {}
obj.__proto__ = Constructor.prototype
var res = Constructor.call(obj,...arguments)
return typeof(res) === 'object'&&res || obj
//当构造函数有return时,返回return值,没有则返回obj{}
}
4.使用 subNew()
创建实例并对比new()
var obj1 = new Constructor()
var obj2 = subNew()
console.log(new Constructor(123),subNew(123))
console.log(new Constructor({a:123}),subNew({a:123}))
console.log(obj1.__proto__,obj2.__proto__,Constructor.prototype)
可以发现 我们已经成功创建了new的实例,并且obj1,obj2的__proto__都指向Constructor的prototype
__proto__
指向构造函数的原型prototype
,同时使用call
调用父级的构造函数并传参,之后判断call返回值res,若为object类型,则返回res,否则返回obj在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
对数组list
进行遍历,再对sublist
进行二次遍历,因为是有序排列,如果当前sublist[index]
比目标值val
大,说明当前sublist[index]
之后,所有值都比val大,可进行continue,比较下一个sublist
var list = [[1,3],[2,5],[4,8],[6,9],[10,10]]
function main(list,val){
for(var sublist of list)
{
for(var index in sublist )
{
if(sublist[index] === val)
{
return true
}
else if(sublist[index]>val)
{
continue
}
}
}
return false
}
console.log(main(list,10))
// 方法装饰器 - 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
function decorateMethod(target: any,key: string,value: any){
return{
value: function(...args: any[]){
console.log('target',target) // 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('target === C.prototype',target === C.prototype) // true
console.log('key',key) // 方法名
console.log('value',value) // 成员的属性描述符 Object.getOwnPropertyDescriptor
console.log('args',args)
var a = args.map(a => JSON.stringify(a)).join();
var result = value.value.apply(this, args);
var r = JSON.stringify(result);
console.log(`Call: ${key}(${a}) => ${r}`);
return result;
}
}
}
// 类装饰器 - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function decorateClass(str: string){
return function(constructor: Function){
console.log(constructor) // 参数为类的构造函数
}
}
// 访问器装饰器 - 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
// 装饰器工厂 - 闭包
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
// 属性装饰器 - 返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性
function getFormat(target: any, propertyKey: string) {
console.log('target') //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('propertyKey') // 属性名
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
// 参数装饰器 - 参数装饰器的返回值会被忽略。
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
console.log('target') // //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('propertyKey') // 参数名
console.log('parameterIndex') // 参数在函数参数列表中的索引。
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
@decorateClass('decorateClass')
class C{
@format("Hello, %s")
great: string
@decorateMethod('decorateMethod')
sum(@required x: number,y: number){
return x + y
}
@configurable(false)
get x() { return this._x; }
}
方法日志装饰器底层实现 - __decorate()
首先通过
ast
将我们针对相同成员的装饰器处理为一个数组,ts规范规定装饰器工厂函数从上至下开始执行
,装饰器函数从下至上开始执行
,__decorate()
会通过reduce.right
从右到左依次执行这个数组中的函数。基于ts的规范不同的装饰器提供的参数不同
,所以ts代码被编译成js代码后,__decorate
需要根据入参数长度进行处理,执行不同的装饰器函数时传入不同的参数。
方法装饰器函数以及访问器装饰器有返回值会将返回值通过
defineProperty
设置为函数本身的value,函数没有返回值的话,则通过Object.getOwnPropertyDescriptor
获取函数自身的 value,并通过defineProperty
设置属性value,之所以会有defineProperty的步骤,是为了方便某些场景我们在装饰器中需要与函数本身进行耦合,如:log及报错捕捉。通过defineProperty,装饰器的逻辑可以做到与函数本身的逻辑进行耦合。
var __decorate =
(this && this.__decorate) ||
function (decorators, target, key, desc) {
var c = arguments.length,
r =
c < 3
? target
: desc === null
? (desc = Object.getOwnPropertyDescriptor(target, key))
: desc,
d;
console.log('r',r)
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if ((d = decorators[i]))
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
console.log(c)
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata =
(this && this.__metadata) ||
function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
// You could think of it in three ways:
//
// - A place to learn TypeScript in a place where nothing can break
// - A place to experiment with TypeScript syntax, and share the URLs with others
// - A sandbox to experiment with different compiler features of TypeScript
// To learn more about the language, click above in "Examples" or "What's New".
// Otherwise, get started by removing these comments and the world is your playground.
// 方法装饰器 - 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
function decorateMethod(str) {
return function (target, key, value) {
return {
value: function (...args) {
console.log('decorateMethod', str); // decorateMethod
console.log('target', target); // 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('target === C.prototype', target === C.prototype); // true
console.log('key', key); // 方法名
console.log('value', value); // 成员的属性描述符 Object.getOwnPropertyDescriptor
console.log('args', args);
var a = args.map(a => JSON.stringify(a)).join();
var result = value.value.apply(this, args);
var r = JSON.stringify(result);
console.log(`Call: ${key}(${a}) => ${r}`);
return result;
}
};
};
}
// 类装饰器 - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function decorateClass(str) {
return function (constructor) {
console.log(constructor); // 参数为类的构造函数
};
}
// 访问器装饰器 - 如果访问器装饰器返回一个值,它会被用作方法的属性描述符。
function configurable(value) {
return function (target, propertyKey, descriptor) {
descriptor.configurable = value;
};
}
// 装饰器工厂 - 闭包
function format(formatString) {
return Reflect.metadata('formatMetadataKey', formatString);
}
// 属性装饰器 - 返回值也会被忽略。因此,属性描述符只能用来监视类中是否声明了某个名字的属性
function getFormat(target, propertyKey) {
console.log('target'); //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('propertyKey'); // 属性名
return Reflect.getMetadata('formatMetadataKey', target, propertyKey);
}
// 参数装饰器 - 参数装饰器的返回值会被忽略。
function required(target, propertyKey, parameterIndex) {
console.log('target'); // //对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
console.log('propertyKey'); // 参数名
console.log('parameterIndex'); // 参数在函数参数列表中的索引。
}
let C = class C {
constructor() {
this.str = 'string';
}
sum(x, y) {
return x + y;
}
get x() { return this.str; }
};
__decorate([
format("Hello, %s"),
__metadata("design:type", String)
], C.prototype, "str", void 0);
__decorate([
decorateMethod('decorateMethod'),
__param(0, required),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Number, Number]),
__metadata("design:returntype", void 0)
], C.prototype, "sum", null);
__decorate([
configurable(false),
__metadata("design:type", Object),
__metadata("design:paramtypes", [])
], C.prototype, "x", null);
C = __decorate([
decorateClass('decorateClass'),
__metadata("design:paramtypes", [])
], C);
console.log(new C().sum(1, 2)); // 3
在【微信小程序】开发中 加密数据解密算法 报错,后来经过排查发现是由于session_key('1744eFm+jcmxMOKxMw==')中存在特殊符号'+',后端接收到的参数为'1744eFm jcmxMOKxMw==',前后不一致,导致解密报错
前端使用encodeURIComponent(key)对key进行编码,后端decodeURIComponent(key),这样就能拿到完整的数据
encodeURI 该方法的目的是对 URI 进行完整的编码,因此对以下在 URI 中具有特殊含义的 ASCII 标点符号,encodeURI() 函数是不会进行转义的:;/?:@&=+$,#
encodeURIComponent “; / ? : @ & = + $ , #”,这些在encodeURI()中不被编码的符号,在encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。
本文旨在对不同种类的装饰器进行学习, 了解装饰器及装饰器工厂的差别,举例应用场景,并浅析装饰器原理。
类装饰器在类声明之前声明, 类装饰器应用于类的构造函数,可用于观察、修改或替换类定义。
1.1 类装饰器的表达式将在运行时作为函数调用,被装饰类的构造函数将作为它的唯一参数。
function decorateClass<T>(constructor: T) {
console.log(constructor === A) // true
}
@decorateClass
class A {
constructor() {
}
}
上述代码可以看出类装饰器接收的参数constructor === A.prototype.constructor
,即constructor
为class A
的构造函数。
1.2 如果类装饰器返回一个构造函数, 它会使用提供的构造函数来替换类之前的声明。
function decorateClass<T extends { new (...args: any[]): {} }>(constructor: T){
return class B extends constructor{
name = 'B'
}
}
@decorateClass
class A {
name = 'A'
constructor() {
}
}
console.log(new A().name) // 输出 B
方法装饰器在方法声明之前声明。装饰器可以应用于方法的属性描述符,并可用于观察、修改或替换方法定义。
2.1 方法装饰器的表达式将在运行时作为函数调用,带有以下三个参数:
Object.getOwnPropertyDescriptor(target,key)
。function decorateMethod(target: any,key: string,descriptor: PropertyDescriptor){
console.log('target === A',target === A) // 是否类的构造函数
console.log('target === A.prototype',target === A.prototype) // 是否类的原型对象
console.log('key',key) // 方法名
console.log('descriptor',descriptor) // 成员的属性描述符 Object.getOwnPropertyDescriptor
}
class A {
@decorateMethod // 输出 true false 'staticMethod' Object.getOwnPropertyDescriptor(A,'sayStatic')
static staticMethod(){
}
@decorateMethod // 输出 false true 'instanceMethod' Object.getOwnPropertyDescriptor(A.prototype,'sayInstance')
instanceMethod(){
}
}
2.2 如果方法装饰器返回一个值,它会被用作方法的属性描述符。
function decorateMethod(target: any,key: string,descriptor: PropertyDescriptor){
return{
value: function(...args: any[]){
var result = descriptor.value.apply(this, args) * 2;
return result;
}
}
}
class A {
sum1(x: number,y: number){
return x + y
}
@decorateMethod
sum2(x: number,y: number){
return x + y
}
}
console.log(new A().sum1(1,2)) // 输出3
console.log(new A().sum2(1,2)) // 输出6
上述代码可以看出sum
被decorateMethod
装饰后,其返回值发生了变化
访问器装饰器在访问器声明之前声明。访问器装饰器应用于访问器的属性描述符,并可用于观察、修改或替换访问器的定义。
3.1 访问器装饰器与方法装饰器有诸多类似,接受3个参数:
Object.getOwnPropertyDescriptor(target,key)
。function configurable (target: any, key: string, descriptor: PropertyDescriptor) {
descriptor.configurable = false
};
class A {
_age = 18
get age(){
return this._age
}
@configurable
set age(num: number){
this._age = num
}
}
3.2 如果访问器装饰器返回一个值,它会被用作访问器的属性描述符。
function configurable (target: any, key: string, descriptor: PropertyDescriptor) {
return {
writable: false
}
};
class A {
_age = 18
@configurable
get age(){
return this._age
}
set age(num: number){
this._age = num
}
}
const a = new A()
a.age = 20 // 抛出 TypeError: Cannot assign to read only property 'age'
属性装饰器在属性声明之前声明,返回值会被忽略。
4.1 属性装饰器的表达式将在运行时作为函数调用,带有以下两个参数:
function decorateAttr(target: any, key: string) {
console.log(target === A)
console.log(target === A.prototype)
console.log(key)
}
class A {
@decorateAttr // 输出 true false staticAttr
static staticAttr: any
@decorateAttr // 输出 false true instanceAttr
instanceAttr: any
}
参数装饰器在参数声明之前声明,返回值会被忽略。
5.1 参数装饰器的表达式将在运行时作为函数调用,带有以下三个参数:
function required(target: any, key: string, index: number) {
console.log(target === A)
console.log(target === A.prototype)
console.log(key)
console.log(index)
}
class A {
saveData(@required name: string){} // 输出 false true name 0
}
不同类型装饰器本身参数是固定的,在运行时被调用,当我们需要自定义装饰器参数时,便可以来构造一个装饰器工厂函数,如下便是一个属性装饰器工厂函数,支持自定义传参name
、age
:
function decorateAttr(name: string, age: number) {
return function (target: any, key: string) {
Reflect.defineMetadata(key, {
name, age
}, target);
}
}
ts规范规定装饰器工厂函数从上至下
开始执行,装饰器函数从下至上
开始执行
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
控制台输出如下,类似中间件的洋葱模型
:
first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
function log (target: any, key: string, value: PropertyDescriptor){
return {
value: async function (...args) {
try{
await value.value.apply(this, args)
}catch(e){
console.log(e)
}
}
};
};
class A {
@log
syncHandle(){
return 3 + a
}
@log
asyncHandle(){
return Promise.reject('Async Error')
}
}
new A().syncHandle()
new A().asyncHandle()
function validate(){
return function (target: any, name: string, descriptor:PropertyDescriptor) {
let set = descriptor.set
descriptor.set = function (value) {
let type = Reflect.getMetadata("design:type", target, name);
console.log(type.name)
if (!(new Object(value) instanceof type)) {
throw new TypeError(`Invalid type, got ${typeof value} not ${type.name}.`);
}
set?.call(this, value);
}
}
}
class A {
_age: number
constructor(){
this._age = 18
}
get age(){
return this._age
}
@validate()
set age(value: number){
this._age = value
}
}
const a= new A()
a.age = 30
a.age = '30' // 抛出 TypeError: Invalid type, got string not Number.
“状态”是描述应用程序当前行为的任何数据。这可能包括诸如“从服务器获取的对象列表”、“当前选择的项目”、“当前登录用户的名称”和“此模式是否打开?”等值。
众所周知,我们在研发一个复杂应用
的过程中,一套好的状态管理方案
是必不可少的,既能提升研发效率,又能降低研发维护成本
,那么状态管理方案那么多,它们有什么不同,我们又该如何选择
适合当前应用的方案呢?
本期将主要就 react
的常用状态管理方案进行对比分析,希望对各位看客有帮助。
框架 | 原理 | 优点 | 缺点 |
---|---|---|---|
hooks context | 基于 react hook,开发者可实现内/外部存储 | 1. 使用简单 2. 不需要引用第三方库,体积最小 3. 支持存储全局状态,但在复杂应用中不推荐 4. 不依赖 react 上下文,可在组件外调用(外部存储的条件下) |
1. context value发生变化时,所有用到这个context的组件都会被重新渲染,基于 content 维护的模块越多,影响范围越大。 2.依赖 Context Provider 包裹你的应用程序,修改 store 无法在应用最顶层(App.tsx 层级)触发渲染 3. 受ui框架约束(react) 4. 依赖hooks调用 |
react-redux | Flux**,发布订阅模式,遵从函数式编程,外部存储 | 1. 不依赖 react 上下文,可在组件外调用 2. 支持存储全局状态 3. redux 本身是一种通用的状态解决方案 |
1. 心智模型需要一些时间来理解,特别是当你不熟悉函数式编程的时候 2. 依赖 Context Provider 包裹你的应用程序,修改 store 无法在应用最顶层(App.tsx 层级)触发渲染 3.受 ui 框架约束(react) |
mobx | 观察者模式 + 数据截止,外部存储 | 1. 使用简单,上手门槛低 2. 不依赖 react 上下文,可在组件外调用 3. 支持存储全局状态 4.通用的状态解决方案 |
1.可变状态模型,某些情况下可能影响调试 2. 除了体积相对较大之外,笔者目前未感觉到较为明显的缺点,3.99M |
zustand | Flux**,观察者模式,外部存储 | 1. 轻量,使用简单,上手门槛低 2. 不依赖 react 上下文,可在组件外调用 3. 支持存储全局状态 4. 通用的状态解决方案 |
1.框架本身不支持 computed 属性,但可基于 middleware 机制通过少量代码间接实现 computed ,或基于第三方库 zustand-computed 实现 |
jotai | 基于 react hook,内部存储 | 1. 使用简单 2. 组件颗粒度较细的情况下,jotai性能更好 3.支持存储全局状态 |
1. 依赖 react 上下文, 无法组件外调用,相对而言, zustand 在 react 环境外及全局可以更好地工作 2.受ui框架约束(react) |
recoil | 进阶版 jotai,基于 react hook + provider context,内部存储 | 相对于 jotai而言,会更重一些,但**基本不变,拥有一些 jotai 未支持的特性及 api,如: 1.监听 store 变化 2. 针对 atom 的操作拥有更多的 api,编程上拥有更多的可能性,更加有趣 |
拥有 jotai 所有的缺点,且相对于 jotai 而言: 1.使用 recoil 需要 < RecoilRoot > 包裹应用程序 2. 编写 selector 会复杂一些 |
valtio | 基于数据劫持,外部存储 | 1. 使用简单,类mobx(类vue)的编程体验 2.支持存储全局状态 3.不依赖 react 上下文,可在组件外调用 4. 通用的状态解决方案 |
1.可变状态模型,某些情况下可能影响调试 2.目前笔者没发现其它特别大的缺点,个人猜测之所以star相对zustand较少,是因为 valtio 的数据双向绑定**与 react 存在冲突。 |
import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"
// 状态及相关事件
class Timer {
secondsPassed = 0
constructor() {
makeAutoObservable(this)
}
increase() {
this.secondsPassed += 1
}
reset() {
this.secondsPassed = 0
}
}
const myTimer = new Timer()
// 构建可观擦组件
const TimerView = observer(({ timer }) => (
<button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
// 触发更新事件
setInterval(() => {
myTimer.increase()
}, 1000)
import { create } from 'zustand'
// 状态及相关事件
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
// 渲染视图
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here ...</h1>
}
// 触发更新事件
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
import { atom } from 'jotai'
const countAtom = atom(0)
function Counter() {
// 状态及相关事件
const [count, setCount] = useAtom(countAtom)
return (
<h1>
{count}
<button onClick={() => setCount(c => c + 1)}>one up</button>
</h1>
)
}
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0, text: 'hello' })
function Counter() {
const snap = useSnapshot(state)
return (
<div>
{snap.count}
<button onClick={() => ++state.count}>+1</button>
</div>
)
useState+useContext
的替代品,那么jotai
非常适合,即原子化
的组件状态管理或少量组件间
状态共享。redux
或喜欢react
的自然不可变更新,那么zustand
将非常适合。vue/ slute /mobx
,或者是JS/React的新手,valtio
的可变模型将很适合。zustand(redux/等不可变数据模型) + immer
,建议改用valtio(mobx)
mobx
有actions概念,而valtio
概念更为简单(自由),如果你希望工程更为规范
,可以使用mobx
,如果是希望工程更为自由便捷
,可以使用valtio
recoil
与jotai
的编程**类似,但提供了更多的 api 与 特性,针对原子状态拥有更多的可操作性,同时包体积也更大,但由于recoil
功能庞大,其使用相对于jotai
会繁琐一些,如果你希望工程轻巧便捷
可以选用jotai
,如果你想试试原子状态更多的可能性,那么试试recoil
吧。如果该文章对你有帮助,请给我点个👍吧~
下期将带来Vue状态管理工具优劣势分析, 欢迎关注我的Blog 🌟
在javascript中,存在调用栈 (call stack)
和内存堆(memory heap)
,程序中函数依次进入栈中等待执行,若执行时遇到异步方法,该异步方法会被添加到用于回调的任务队列(task queue)
中,【即JavaScript执行引擎的单线程拥有一个调用栈、内存堆和一个任务队列】
调用栈 (call stack):CallStack是用来处理函数调用与返回的。特点是先进后出,每次调用一个函数,Javascript运行时会生成一个新的调用结构压入CallStack。而函数调用结束返回时,JavaScript运行时会将栈顶的调用结构弹出。由于栈的LIFO特性,每次弹出的必然是最新调用的那个函数的结构。函数调用会形成了一个堆栈帧,存放基本数据类型的变量
内存堆(memory head):引用数据类型被存放在堆中,在我们进行浅复制时,我们改变的只是引用数据类型在栈内存中的引用地址,实际上它在堆内存中的引用地址仍然没有发生变化
任务队列(task queue):javaScript 运行时包含了一个待处理的任务队列。
javascript引擎是单线程的,它的并发模型基于Event Loop(事件循环)
当线程中的同步任务执行完,
执行栈
为空时,则从任务队列(task queue)
中取出异步任务进行处理。这个处理过程包含了调用与这个任务相关联的函数(以及因而创建了一个初始堆栈帧)。当执行栈
再次为空的时候,也就意味着该任务处理结束,从任务队列中取出下一个异步任务进行处理,不断重复,这个过程是循环不断的, 所以整个的这种运行机制又称为Event Loop(事件循环)
.
宏任务队列
与微任务队列
, setTimeout(()=>{
console.log('timer')
})
Promise.resolve('promise').then((res)=>{
console.log(res)
})
Promise.resolve('promise').then((res)=>{
console.log(res)
setTimeout(()=>{
console.log('timer')
})
})
Promise.resolve('promise1').then((res)=>{
console.log(res)
Promise.resolve('promise2').then((res)=>{
console.log(res)
Promise.resolve('promise3').then((res)=>{
console.log(res)
})
})
setTimeout(()=>{
console.log('timer')
})
})
我们在做一个大型的复杂应用时,往往有很多数据会在多个页面多个组件中同时被使用,这时如果仍然使用props
传参的方式,就会显得组件之间耦合度过高
,且开发效率低
。
为了解决这个问题,2014年 Facebook 提出了 Flux 架构的概念,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
hooks 是react 16.8 引入的特性,他允许你在不写class的情况下操作state 和react的其他特性。
hooks 只是多了一种写组件的方法,使编写一个组件更简单更方便,同时可以自定义hook把公共的逻辑提取出来,让逻辑在多个组件之间共享。
const { state, setState } = useState(initialState)
useEffect(didUpdate,[]) //didUpdate为要做的更新,[]为要监听的变量
Context
对象(React.createContext
返回的值)并且返回当前<MyContext.Provider>
的值, 返回值取决于最近的 <MyContext.Provider>
。const store = useContext(Context)
(state, action) => newState
类型的reducer,并且返回一个与dispatch
方法配对的state
对象(如果你熟悉redux,那么你已经会了)const [state,dispatch] = useReducer(reducers,initialState)
userEffect
,与前者相比不能执行副作用,返回缓存的函数userEffect
,与前者相比不能执行副作用,返回缓存的变量vue中的$refs
,用于操作domuseImperativeHandle
返回了一个回调函数帮助我们解决此类问题function Com(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
Com = forwardRef(Com);
componentDidMount/Update, componentWillUnmount
,会在dom元素更新后立即执行回调函数自定义Hooks
Redux 规定,将模型的更新逻辑全部集中于一个特定的层(Flux 里的 store,Redux 里的 reducer)。Flux 和 Redux 都不允许程序直接修改数据,而是用一个叫作 “action” 的普通对象来对更改进行描述。
需要用到的API有createContext
,useContext
,useReducer
state
state.js
export default {
username:''
}
action
的普通对象来对state进行更改action.js
export default {
setUserName:function(payload){
return {
type:'setUserName',
payload
}
}
}
reducer
)reducer.js
export default function reducer(state,action){
const { payload } = action
switch(action.type)
{
case 'setUserName': // 匹配action中的type,return新的state
return {
...state,
username: payload
}
default:
return state
}
}
useReducer
获取state以及用于事件派发更新state的dispatch
函数index.js
const [state,dispatch] = useReducer(reducers,initialState)
这里我们需要排除props传参
的方式,因为这样就失去了使用redux进行状态管理的意义,这里就需要用到Context
index.js
import React,{createContext, useReducer} from 'react'
import reducers from './reducers'
import initialState from './state'
export const Context = createContext({})
export const Consumer = Context.Consumer
export function Provider(props){
const [state,dispatch] = useReducer(reducers,initialState)
const store = { state, dispatch }
return (
<Context.Provider value={ store }>
{props.children}
</Context.Provider>
)
}
这里将useReducer
的返回值放到Provider
的value中,子组件通过Consumer
或useContext
就能访问到了
整个Redux由state、action、reducer、index
构成
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App';
import { Provider } from './redux'
ReactDOM.render(<Provider><App /></Provider>, document.getElementById('root'))
import React, { useContext } from 'react';
import { AppContext } from './redux'
import Child from './child'
import actions from './redux/actions'
function App() {
const { dispatch } = useContext( AppContext ) // 获取事件派发函数
function handleInput(e){
dispatch(actions.setUserName(e.target.value)) // 派发更新事件并传值
}
return (
<div className="App">
<input onInput={handleInput} type='text' />
<Child />
</div>
);
}
export default App;
import React, { useContext } from 'react';
import { AppContext } from './redux'
export default function Children(){
const { state } = useContext(AppContext)
return (
<div>Name:{state.username}</div>
)
}
openpyxl模块用来读写Excel文件。openpyxl工作时,在内存中创建Excel工作簿和工作表,然后在工作表中的单元格中进行各种数据编辑和样式编辑操作,或在工作表中绘制图形,最后再保存文件写入到Excel中。
官方文档: http://openpyxl.readthedocs.io/en/default/
import openpyxl
wb = openpyxl.Workbook()
得到一个wb对象,它就是我们要操作的工作簿文件的对象。
>>> wb
<openpyxl.workbook.workbook.Workbook object at 0x0000000003543358>
wb = openpyxl.load_workbook(“test.xlsx”)
wb.save("test.xlsx")
文件名支持路径。
以上语句获得的一个空的工作簿,默认含有一个名为“Sheet”的工作表页面。
wb对象既是整个xlsx文件在内存中的存在形式,它一般由一个或多个sheet页面组成,我们对xlsx文件的操作,主要就是在sheet页面上操作;我们在Excel软件中,能对sheet页面进行什么操作,基本使用openpyxl也都能完成。
wb对象创建后,默认含有一个默认的名为 Sheet 的 页面,可以使用active来得到它
>>> ws1 = wb.active
>>> ws1
<Worksheet "Sheet">
或使用名称来得到它:
ws1 = wb.get_sheet_by_name('Sheet')
或
ws1 = wb["Sheet"]
名称有中文要使用unicode
也可以使用工作表序号获得该页面: ws1 = wb.worksheets[0]
注:序号从0开始
可以使用 wb.create_sheet() 来创建新的sheet页面。 创建时可以同时关联一个变量。
ws2 = wb.create_sheet()
如:
import openpyxl
wb = openpyxl.Workbook()
# ws1 = wb.active
ws1 = wb.get_sheet_by_name('Sheet')
ws2 = wb.create_sheet()
wb.create_sheet()
wb.create_sheet()
wb.create_sheet()
ws1['A1'] = "This is a test!"
wb.save("test.xlsx")
对于已有的工作表,我们也可以通过遍历名称、序号等方式拿到相应的对象地址。
wb.sheetnames
是所有工作表sheet页面的名称列表。
工作表sheet可以在创建时候直接指定名字,中文要使用unicode,也可以通过修改对象的title值,来修改名称。
import openpyxl
wb = openpyxl.Workbook()
# ws1 = wb.active
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
ws2 = wb.create_sheet("ABC")
wb.create_sheet(u"中文表名")
ws1['A1'] = "哇嘎嘎"
wb.save("test.xlsx")
>>> print wb.sheetnames
[u'Test', u'ABC', u'\u4e2d\u6587\u8868\u540d']
修改工作表的标签的颜色,等价于工作表标签右键菜单中“工作表标签颜色”功能:
ws1.sheet_properties.tabColor="0000FF"
颜色的值为颜色的RRGGBB格式。
import openpyxl
wb = openpyxl.Workbook()
#ws1 = wb.active
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
ws2 = wb.create_sheet("ABC")
wb.create_sheet(u"中文表名")
ws1.sheet_properties.tabColor="0000FF"
ws2.sheet_properties.tabColor="FF00FF"
ws1['A1'] = "哇嘎嘎"
ws1['B2'] = "嗯~ o(* ̄▽ ̄*)o"
wb.save("test.xlsx")
使用wb.remove_sheet()方法删除工作表
wb.remove_sheet(wb.get_sheet_by_name(“中文表名”)) wb.remove_sheet(ws2)
获得一个工作表对象后,就可以工作表中的单元格进行操作。
ws1['A1'] = "哇嘎嘎"
ws1['B2'] = "嗯~ o(* ̄▽ ̄*)o"
ws1.cell(row=3, column=2).value = "AAAAA"
有多种的方式可对多个单元格进行选定:
for col in ws1.iter_cols(min_row=2, min_col=2, max_row=7, max_col=6):
for cell in col:
for row in ws2["B2:F7"]:
for cell in row:
完整示例:
import openpyxl
wb = openpyxl.Workbook()
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
i = 0
# 遍历方式1:
ws1['A1'] = "遍历方式1"
for col in ws1.iter_cols(min_row=2, min_col=2, max_row=7, max_col=6):
for cell in col:
cell.value = i
i = i + 1
ws2 = wb.create_sheet("Test2")
i = 0
# 遍历方式2:
ws2['A1'] = "遍历方式2"
for row in ws2["B2:F7"]:
for cell in row:
cell.value = i
i = i + 1
wb.save("test.xlsx")
可以看出这两种遍历方式遍历的行列顺序是完全不一样的。
import openpyxl
wb = openpyxl.Workbook()
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
i = 0
# 遍历方式1:
ws1['A1'] = "遍历方式1"
print("遍历方式1")
j = 1
for col in ws1.iter_cols(min_row=2, min_col=2, max_row=7, max_col=6):
print("Cols No." + str(j) + ": ", end="")
print(col)
j = j + 1
for cell in col:
cell.value = i
i = i + 1
ws2 = wb.create_sheet("Test2")
i = 0
# 遍历方式2:
ws2['A1'] = "遍历方式2"
print("遍历方式2")
j = 1
for row in ws2["B2:F7"]:
print("Row No." + str(j) + ": ", end="")
print(row)
j = j + 1
for cell in row:
cell.value = i
i = i + 1
wb.save("test.xlsx")
执行后控制台输出:
遍历方式1
Cols No.1: (<Cell 'Test'.B2>, <Cell 'Test'.B3>, <Cell 'Test'.B4>, <Cell 'Test'.B5>, <Cell 'Test'.B6>, <Cell 'Test'.B7>)
Cols No.2: (<Cell 'Test'.C2>, <Cell 'Test'.C3>, <Cell 'Test'.C4>, <Cell 'Test'.C5>, <Cell 'Test'.C6>, <Cell 'Test'.C7>)
Cols No.3: (<Cell 'Test'.D2>, <Cell 'Test'.D3>, <Cell 'Test'.D4>, <Cell 'Test'.D5>, <Cell 'Test'.D6>, <Cell 'Test'.D7>)
Cols No.4: (<Cell 'Test'.E2>, <Cell 'Test'.E3>, <Cell 'Test'.E4>, <Cell 'Test'.E5>, <Cell 'Test'.E6>, <Cell 'Test'.E7>)
Cols No.5: (<Cell 'Test'.F2>, <Cell 'Test'.F3>, <Cell 'Test'.F4>, <Cell 'Test'.F5>, <Cell 'Test'.F6>, <Cell 'Test'.F7>)
遍历方式2
Row No.1: (<Cell 'Test2'.B2>, <Cell 'Test2'.C2>, <Cell 'Test2'.D2>, <Cell 'Test2'.E2>, <Cell 'Test2'.F2>)
Row No.2: (<Cell 'Test2'.B3>, <Cell 'Test2'.C3>, <Cell 'Test2'.D3>, <Cell 'Test2'.E3>, <Cell 'Test2'.F3>)
Row No.3: (<Cell 'Test2'.B4>, <Cell 'Test2'.C4>, <Cell 'Test2'.D4>, <Cell 'Test2'.E4>, <Cell 'Test2'.F4>)
Row No.4: (<Cell 'Test2'.B5>, <Cell 'Test2'.C5>, <Cell 'Test2'.D5>, <Cell 'Test2'.E5>, <Cell 'Test2'.F5>)
Row No.5: (<Cell 'Test2'.B6>, <Cell 'Test2'.C6>, <Cell 'Test2'.D6>, <Cell 'Test2'.E6>, <Cell 'Test2'.F6>)
Row No.6: (<Cell 'Test2'.B7>, <Cell 'Test2'.C7>, <Cell 'Test2'.D7>, <Cell 'Test2'.E7>, <Cell 'Test2'.F7>)
采用工作表的append()方法,可以向工作表中按行追加数据:
import openpyxl
wb = openpyxl.Workbook()
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
DATA = [
['第一天', 123, 12, 123, 900, 231, 7],
['第二天', 13, 56, 3, 900, 231, 90],
['第三天', 216, 38, 37, 543, 55, 376],
['第四天', 89, 99, 88, 453, 87, 527]
]
for row in DATA:
ws1.append(row)
wb.save("test.xlsx")
import openpyxl wb = openpyxl.load_workbook("test2.xlsx") ws = wb.get_sheet_by_name('Sheet') print("数据表最大行数:" + str(ws.max_row)) print("数据表最大列数:" + str(ws.max_column)) DATA = [] # 遍历读取 for row in ws.iter_rows(min_col=1, min_row=1, max_row=ws.max_row, max_col=ws.max_column): ROW = [] for cell in row: ROW.append(cell.value) DATA.append(ROW) print("表中读取到的数据:", end="") print(DATA)
读取指定位置
B2 = ws['B2'].value
print("B2 = " + B2)
执行结果:
数据表最大行数:2
数据表最大列数:2
表中读取到的数据:[['测试2', None], [None, 'B列2行']]
B2 = B列2行
可以直接在单元格中写入公式:
import openpyxl
wb = openpyxl.Workbook()
ws1 = wb.get_sheet_by_name('Sheet')
ws1.title = "Test"
DATA = [
['第一天', 123, 12, 123, 900, 231, 7],
['第二天', 13, 56, 3, 900, 231, 90],
['第三天', 216, 38, 37, 543, 55, 376],
['第四天', 89, 99, 88, 453, 87, 527]
]
ws1['A1'] = '这是一个测试用表格'
for row in DATA:
ws1.append(row)
ws1.append(['合计', '=sum(B2:B5)', '=sum(C2:C5)', '=sum(D2:D5)', '=sum(E2:E5)', '=sum(F2:F5)', '=sum(G2:G5)'])
wb.save("test.xlsx")
单元格样式的控制,依赖openpyxl.style包,其中定义有样式需要的对象,引入样式相关:
from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
以上五个基本可满足需要
基本用法是,将单元格对象的设置的属性赋为新的与默认不同的相应对象。
比如设置一个字体对象:
在 1.5.5. 的示例中,保存文件前先设置一下标题字体:
# 样式
font = Font(size=14, bold=True, name='微软雅黑', color="FF0000")
ws1['A1'].font = font
wb.save("test.xlsx")
边框Border对象,创建时可指定边框各边的Side对象,根据之前的执行结果,默认都是无边框
# 样式
font = Font(size=14, bold=True, name='微软雅黑', color="FF0000")
thin = Side(border_style="thin", color="0000FF")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
ws1['A1'].font = font
ws1['A1'].border = border
for row in ws1['A2:G6']:
for cell in row:
cell.border = border
wb.save("test.xlsx")
# 对齐
alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
ws1['A1'].alignment = alignment
采用 merge_cells
方法合并单元格
# 对齐
alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
ws1['A1'].alignment = alignment
# 合并单元格
ws1.merge_cells('A1:G1')
wb.save("test.xlsx")
# 填充
fill = PatternFill(patternType="solid", start_color="33CCFF")
ws1['A1'].fill = fill
for row in ws1.iter_rows(min_row=ws1.max_row, max_col=ws1.max_column):
for cell in row:
cell.fill = PatternFill(patternType="solid", start_color="0066FF")
cell.font = Font(bold=True, color="FFFFFF")
cell.alignment = Alignment(horizontal="center")
wb.save("test.xlsx")
markdown 是一种创作者通过输入简洁的文字便能够实现 自动排版 的排版语法,目前国内包括知乎、简书、掘金、v2ex,国外包括 stack overflow、github, 等各大内容创作平台都已经支持了 markdown 语法(当然也包括 jelly ),创作者也因为习惯了 markdown 语法,如果某个平台不支持 markdown 语法,那么必然会留下一个不好的印象,甚至可能被拉黑名单。
总而言之,拥有一个支持 markdown 语法的编辑器对于内容创作平台而言是尤为重要的。
主要做的事情有两部分:
1. 针对文本内容进行语法分析并自定义样式
2. 针对代码关键词需要高亮展示
就 markdown 语法而言,我们获取的输入值往往是:
# 这是标题
> 这是内容
#### 这是表格
表头1 | 表头2 | 表头3
------|-------|-------
内容1 | 内容2 | 内容3
这些输入值保存到数据库中到文本内容为:
# 这是标题↵> 这是内容↵#### 这是表格↵表头1 | 表头2 | 表头3↵------|-------|-------↵内容1 | 内容2 | 内容3
这里我们需要将对应markdown
转变为浏览器可以识别的html
标签:
# 文字
处理为<h1>文字</h1>
标签> 文字
处理为<blockquote><p>文字<p></blockquote>
标签xxx | xxx | xxx \n ---|---|---\n内容1 | 内容2 | 内容3
处理为<table><thead><tbody><th><td>
标签这里推荐使用第三方库showdown
,它是一个 Javascript
到 HTML
的转换器,可以在客户端(在浏览器中)或服务器端(NodeJs )使用。
需要注意的是 showdown
无法识别特殊符号↵
,在调用makeHtml()
前,需要先将特殊符号↵
处理为\n
换行符。
text.replace(/↵/g,'\n')
具体代码如下:
import showdown from 'showdown'
const converter = new showdown.Converter()
converter.setFlavor('github')
converter.makeHtml('# 这是标题↵> 这是内容↵#### 这是表格↵表头1 | 表头2 | 表头3↵------|-------|-------↵内容1 | 内容2 | 内容3'.replace(/↵/g,'\n'))
最终得到 HTML
结构:
<h1 id="这是标题">这是标题</h1>
<blockquote>
<p>这是内容</p>
</blockquote>
<h4 id="这是表格">这是表格</h4>
<table>
<thead>
<tr>
<th id="表头1">表头1</th>
<th id="表头2">表头2</th>
<th id="表头3">表头3</th>
</tr>
</thead>
<tbody>
<tr>
<td>内容1</td>
<td>内容2</td>
<td>内容3</td>
</tr>
</tbody>
</table>
使用标签选择器自定义样式
blockquote {
color: #7f8fa4;
font-size: inherit;
padding: 8px 24px;
margin: 16px 0;
border-left: 3px solid #eaeaea;
p {
margin: 0;
}
}
table {
thead {
background: #292c34;
color: #a9b0bd;
th {
border: 1px solid #292c34;
border-right-color: #a9b0bd;
padding: 10px 20px;
box-sizing: border-box;
&:nth-last-child(1) {
border-color: #292c34;
}
}
}
tbody {
td {
border: 1px solid #292c34;
padding: 10px 20px;
box-sizing: border-box;
}
}
}
我们使用 markdown 语法展示代码时,往往会带上语言类型,如javascript
:
转换效果如下:
<pre>
<code class="javascript language-javascript">
import showdown from 'showdown'
const converter = new showdown.Converter()
converter.setFlavor('github')
converter.makeHtml('# 这是标题
**这是加粗的文字**
*这是斜体*'.replace(/
/g,'\n'))</code>
</pre>
转换结果使用<pre>
、<code>
标签将整个代码块都给包裹在里面,并不能够区分代码块关键词(eg: 'import'、'class')
,无法满足我们的需求。 这时便需要借助highlight.js
来提取关键词
,同时给对应关键词
添加上className
。
highlight.js
是一个 javascript 编写的语法高亮器,可以同时运行在浏览器跟服务端,不依赖任何框架,且具备自动检测语言功能。
highlightAll()
方法可以自动识别页面上的
…
<pre><code
<span class="hljs-keyword">class</span>
="javascript language-javascript">
<span class="hljs-keyword">import</span>
showdown
<span class="hljs-keyword">from</span>
<span class="hljs-string">'showdown'</span>
const converter =
<span class="hljs-built_in">new</span>
showdown.Converter()
converter.setFlavor(
<span class="hljs-string">'github'</span>
)
converter.makeHtml(
<span class="hljs-string">'# 这是标题
**这是加粗的文字**
*这是斜体*'
</span>.replace(/
/g,
<span class="hljs-string">'\n'</span>))</code></pre>
该库支持的语言种类繁多,包括go
、python
、java
等,详情可查看:
https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md
代码高亮我们可以通过css自定义样式
,也可以尝试引入hightlighth.js
提供的官方样式(vscode
、atom
、xcode
、github
等)。
@import '../node_modules/highlight.js/styles/atom-one-dark.css';
《javascript高级程序设计》中有说到:
this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window ,而当函数被作为某个对象调用时,this等于那个对象。不过,
匿名函数具有全局性,因此this对象同常指向window
不过,在全局函数中,this等于window
,匿名函数具有全局性,因此this对象通常指向window
,针对于匿名函数this具有全局性的观点仍是有争议的,可参考 https://www.zhihu.com/question/21958425
关于闭包经常会看到这么一道题:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());//result:The Window
在这里,getNameFunc
return了1个匿名函数,可能你会认为这就是输出值为The Window
的原因
但是,我们再来尝试写1个匿名函数
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return this.funAA;
},
funAA:function(){
return this.name
}
};
console.log(object.getNameFunc()(),object.funAA())
可以发现,同样是匿名函数,却输出了The Window, My Object
在作用域链中,执行函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。
因为函数在全局作用域中被
object.getNameFunc()
独立调用,funAA的作用域链被初始化为undefined即window的[[Scope]]所包含的对象,导致输出结果为window.name
对作用域链不是很了解的同学,可以查看这边文章【Javascript】深入理解javascript作用域与作用域链
实践是检验真理的唯一标准,让我们用代码测试一下
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return this.funAA();
},
funAA:function(){
return this.name
}
};
console.log(object.getNameFunc(),object.funAA())
可以发现,输出了 My Object, My Object
getNameFunc仍为匿名函数,但是return的是this.funAA(),此时,this.funAA变成了由object调用,验证了我们之前的猜想:
函数执行环境影响了this作用域,对这个demo的代码不太理解的同学,可以看一下另一个比较简单的案例
this.x = 9;
var module = {
x: 81,
getX: function() { console.log(this.x) }
};
module.getX(); // 81
var retrieveX = module.getX;
function A(){
this.x = 22;
retrieveX() //22
}
A()
还是实践出真理,我们先来写一段代码
var a = 2
function test(){
this.a = 1
console.log(window.a)
}
new test()
test()
可以看出输出结果为2,1
new运算符改变了test函数内this的作用域,改变的原理是通过在函数内创建一个对象obj,并通过test.call(obj)
,执行obj.test()
,call函数原理:
Function.prototype.call1 = function(obj,...args){
obj.fn = this
obj.fn(...args)
delete obj.fn
}
这样test函数被对象obj调用,test复制的是obj的作用域链,而不是window
function subNew(){
var obj = {}
var res = test.call(obj,...arguments)
}
subNew() // 作用等于new test()
继续写代码通过事实来说明
var a = 1 // 全局作用域
let b = 1 // 块级作用域
const c = 1 // 块级作用域
function foo(){
var d = 1 // 函数作用域
this.a = 2
this.b = 2
this.c = 2
this.d = 2
console.log(a,b,c,d) // 2,1,1,1
}
foo()
a为全局作用域中的变量,可以被this对象访问,b/c/d则不行
可以发现,全局作用域中的a变量被改变,b变量与c变量都没有被改变,说明在fn()中通过this访问不到window作用域中的b/c变量
注:这里说的访问不到与const定义的变量是常量没有关系,因为如果访问到的话,是会报typeError的
var num = 1
const object = {
num:2,
foo: function(){
return ()=>{
console.log(this.num)
}
}
}
object.foo()() // 2
箭头函数 this 指向 所处环境的上下文的 this 值,与是否独立调用或作为属性被调用,没有关系。
箭头函数没有arguments
/prototype
,不能作为构造函数,不能使用new
函数执行时
所创建运行期上下文(execution context)
的内部对象,它与当前运行函数的[[scope]]所包含的对象组成了1个新的对象,这个对象就是活动对象
,然后此对象会被推入作用域链的前端被某一个对象所拥有
,那么该函数在调用时,内部的this指向该对象。全局作用域window
中被独立调用
,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。非严格模式
下,函数内的this对象有能力也仅能访问到全局作用域中定义的变量即window对象, 块级作用域/函数作用域内的变量都无法被访问所处环境的上下文的 this 值
,与是否独立调用或作为属性被调用,没有关系。不理解new的实践可以查看我的这篇文章【Javascript】彻底捋清楚javascript中 new 运算符的实现
对作用域链不是很了解的同学,可以查看这边文章【Javascript】深入理解javascript作用域与作用域链
在JavaScript中,函数也是对象,实际上,JavaScript里一切都是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
在函数add创建时,它的作用域链中会填入一个全局对象,该全局对象包含了所有全局变量,函数add的作用域将会在执行时用到
function add(num1,num2) {
var sum = num1 + num2;
return sum;
}
执行此函数时会创建一个称为“运行期上下文(execution context)”的内部对象,运行期上下文定义了函数执行时的环境。每个运行期上下文都有自己的作用域链,用于标识符解析,当运行期上下文被创建时,而它的作用域链初始化为当前运行函数的[[Scope]]所包含的对象。
这些值按照它们出现在函数中的顺序被复制到运行期上下文的作用域链中。它们共同组成了一个新的对象,叫“活动对象(activation object)”,该对象包含了函数的所有局部变量、命名参数、参数集合以及this,然后此对象会被推入作用域链的前端,当运行期上下文被销毁,活动对象也随之销毁。
在函数执行过程中,每遇到一个变量,都会经历一次标识符解析过程以决定从哪里获取和存储数据。该过程从作用域链头部,也就是从活动对象开始搜索,查找同名的标识符,如果找到了就使用这个标识符对应的变量,如果没找到继续搜索作用域链中的下一个对象,如果搜索完所有对象都未找到,则认为该标识符未定义。函数执行过程中,每个标识符都要经历这样的搜索过程。
从作用域链的结构可以看出,在运行期上下文的作用域链中,标识符所在的位置越深,读写速度就会越慢。如上图所示,因为全局变量总是存在于运行期上下文作用域链的最末端,因此在标识符解析的时候,查找全局变量是最慢的。所以,在编写代码的时候应尽量少使用全局变量,尽可能使用局部变量。一个好的经验法则是:如果一个跨作用域的对象被引用了一次以上,则先把它存储到局部变量里再使用。例如下面的代码:
function changeColor(){
document.getElementById("btnChange").onclick=function(){
document.getElementById("targetCanvas").style.backgroundColor="red";
};
}
这个函数引用了两次全局变量document,查找该变量必须遍历整个作用域链,直到最后在全局对象中才能找到。这段代码可以重写如下:
function changeColor(){
var doc=document;
doc.getElementById("btnChange").onclick=function(){
doc.getElementById("targetCanvas").style.backgroundColor="red";
};
}
那作用域链地作用仅仅只是为了标识符解析吗? 再来看一段代码
<script>
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
</script>
outer()内部返回了一个inner函数,当调用outer时,inner函数的作用域链就已经被初始化了(复制父函数的作用域链,再在前端插入自己的活动对象)
一般来说,当某个环境中的所有代码执行完毕后,该环境被销毁(弹出环境栈),保存在其中的所有变量和函数也随之销毁(全局执行环境变量直到应用程序退出,如网页关闭才会被销毁)
但是像上面那种有内部函数的又有所不同,当outer()函数执行结束,执行环境被销毁,但是其关联的活动对象并没有随之销毁,而是一直存在于内存中,因为该活动对象被其内部函数的作用域链所引用。
像上面这种内部函数的作用域链仍然保持着对父函数活动对象的引用,就是闭包(closure)
函数每次执行时对应的运行期上下文都是独一无二的,所以多次调用同一个函数就会导致创建多个运行期上下文,当函数执行完毕,执行上下文会被销毁。每一个运行期上下文都和一个作用域链关联。一般情况下,在运行期上下文运行的过程中,其作用域链只会被 with 语句和 catch 语句影响。
with语句是对象的快捷应用方式,用来避免书写重复代码。例如:
function initUI(){
with(document){
var bd=body,
links=getElementsByTagName("a"),
i=0,
len=links.length;
while(i < len){
update(links[i++]);
}
getElementById("btnInit").onclick=function(){
doSomething();
};
}
}
这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中,因此访问代价更高了
这里使用width语句来避免多次书写document,看上去更高效,实际上产生了性能问题。
另外一个会改变作用域链的是try-catch语句中的catch语句。当try代码块中发生错误时,执行过程会跳转到catch语句,然后把异常对象推入一个可变对象并置于作用域的头部。在catch代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中
try{
doSomething();
}catch(ex){
alert(ex.message); //作用域链在此处改变
}
请注意,一旦catch语句执行完毕,作用域链机会返回到之前的状态。try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。你可以通过优化代码来减少catch语句对性能的影响。一个很好的模式是将错误委托给一个函数处理,例如:
try{
doSomething();
}catch(ex){
handleError(ex); //委托给处理器方法
}
优化后的代码,handleError方法是catch子句中唯一执行的代码。该函数接收异常对象作为参数,这样你可以更加灵活和统一的处理错误。由于只执行一条语句,且没有局部变量的访问,作用域链的临时改变就不会影响代码性能了。
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问
作用域链前端,都是当前执行环境的代码所在环境的变量对象。
给出一个具有重复数字的列表,找出列表所有不同的排列。
样例
样例 1:
输入:[1,1]
输出:
[
[1,1]
]
样例 2:
输入:[1,2,2]
输出:
[
[1,2,2],
[2,1,2],
[2,2,1]
]
挑战
使用递归和非递归分别完成该题。
Frame | Principle | Advantage | Disadvantage |
---|---|---|---|
hooks context | Based on react hooks, developers can implement internal/external storage | 1. Simple to use 2. No need to refer to third-party libraries, the smallest size 3. Support global state storage, but in complex applications Not recommended in 4. Does not depend on the react context, can be called outside the component (under the condition of external storage) |
1. When the context value changes, all components that use this context will be re-rendered, based on content maintenance The more modules there are, the larger the area of influence. 2. Using a dependent provider, modifying the store cannot trigger rendering at the top level of the application (App.tsx level) 3. Constrained by the ui framework (react) 4. Dependent hooks call |
react-redux | Flux thinking, publish-subscribe mode, functional programming compliance, external storage | 1. Does not depend on the react context, can be called outside the component 2. Supports storing global state 3. Not affected by ui Framework Constraints |
1. Mental models take some time to understand, especially if you are not familiar with functional programming 2. Coding is cumbersome |
mobx | Observer mode + data cutoff, external storage | 1. Simple to use 2. Does not depend on the react context, can be called outside the component 3. Supports storing global state 4. Not affected ui framework constraints |
1. Variable state model, which may affect debugging in some cases 2. In addition to the relatively large size, the author currently does not feel any obvious shortcomings, 3.99M |
zustand | Flux idea, observer mode, external storage | 1. Lightweight, easy to use 2. Does not depend on the react context, can be called outside the component 3. Supports storage of global state |
1. The framework itself Computed attributes are not supported, but computed can be implemented indirectly with a small amount of code based on the middleware mechanism, or based on the third-party library zustand-computed 2. Constrained by the ui framework (react / vue) |
jotai | Based on react hook, internal storage | 1. Simple to use 2. In the case of finer component granularity, jotai has better performance 3. Supports storing global state, but it is not recommended in complex applications |
1. Rely on the react context and cannot be called outside the component. Relatively speaking, zustand can work better outside the react environment and globally 2. Constrained by the ui framework (react) |
valtio | Based on data hijacking, external storage | 1. Simple to use, mobx-like (vue-like) programming experience 2. Supports storage of global state 3. Does not depend on the react context, can be called outside the component br/> 4. Not constrained by the ui framework |
1. Variable state model, which may affect debugging in some cases 2. At present, the author has not found any other particularly serious shortcomings, and I personally guess that the reason why stars are relatively less than zustand , because there is a conflict between valtio's two-way data binding idea and react. |
import React from "react"
import ReactDOM from "react-dom"
import { makeAutoObservable } from "mobx"
import { observer } from "mobx-react"
// Build status and related events
class Timer {
secondsPassed = 0
constructor() {
makeAutoObservable(this)
}
increase() {
this.secondsPassed += 1
}
reset() {
this.secondsPassed = 0
}
}
const myTimer = new Timer()
// Build a observable component
const TimerView = observer(({ timer }) => (
<button onClick={() => timer.reset()}>Seconds passed: {timer.secondsPassed}</button>
))
ReactDOM.render(<TimerView timer={myTimer} />, document.body)
// Trigger update event
setInterval(() => {
myTimer.increase()
}, 1000)
import { create } from 'zustand'
// Build status and related events
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}))
// Render component
function BearCounter() {
const bears = useBearStore((state) => state.bears)
return <h1>{bears} around here ...</h1>
}
// Trigger update event
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
import { atom } from 'jotai'
const countAtom = atom(0)
function Counter() {
// Build status and related events
const [count, setCount] = useAtom(countAtom)
return (
<h1>
{count}
<button onClick={() => setCount(c => c + 1)}>one up</button>
</h1>
)
}
import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0, text: 'hello' })
function Counter() {
const snap = useSnapshot(state)
return (
<div>
{snap.count}
<button onClick={() => ++state.count}>+1</button>
</div>
)
useState+useContext
, jotai
is very suitable, that is, atomic
component state management or state sharing between a small number of components
.redux
or like the natural immutable update of react
, then zustand
will be very suitable.vue/slute/mobx
, or new to JS/React, the variable model of valtio
will be very suitable.zustand (immutable data model such as redux/) + immer
, it is recommended to use valtio(mobx)
mobx
has the concept of actions, and the concept of valtio
is simpler (free). If you want the project to be more standard
, you can use mobx
. If you want the project to be more free and convenient
, you can use valtio
If this article is helpful to you, please give me a 👍
The next issue will bring an analysis of the advantages and disadvantages of Vue state management tools, welcome to pay attention to myBlog](https://github.com/AwesomeDevin/blog) 🌟
在js中,new()常被用来创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例
本文,主要讲如何手写function new创建实例并实现js继承
【Javascript】彻底捋清楚javascript中 new 运算符的实现
function subNew(){
var obj = {}
将父级的原型prototype指向子级的隐式原型__proto__
obj.__proto__ = Parent.prototype
//创建实例的时候传参
var res = Parent.call(obj,...arguments)
//当构造函数有return时,返回return值,没有则返回obj{}
return typeof(res) === 'object'&&res || obj
}
var obj1 = new Constructor()
var obj2 = subNew()
console.log(new Constructor(123),subNew(123))
console.log(new Constructor({a:123}),subNew({a:123}))
console.log(obj1.__proto__,obj2.__proto__,Constructor.prototype)
可以发现 我们已经成功创建了new的实例,并且obj1,obj2的__proto__都指向Constructor的prototype
(继承的底层实原理其实是通过this.fn=Parent&&this.fn(),调用父类函数,改变this指向的同时,也改变了函数的内部对象(即运行时上下文) )
function Parent(sex)
{
this.sex = sex
}
Parent.prototype.saySex=function(){
console.log(this.sex)
}
function Child()
{
Parent.apply(this,[...arguments])
}
function subNew(){
var obj = {}
obj.__proto__ = Parent.prototype
var res = Parent.call(obj,...arguments)
return typeof(res) === 'object'&&res || obj
}
Child.prototype = subNew() // 等同于 Child.prototype = new Parent()
var c=new Child('男');
c.saySex();
这个世界对计算机人才的要求是供不应求的,所以,不要让自己为自己找各式各样的借口,让自己活在“玩玩具”、“搬砖”和“使蛮力加班”的境地。其实,我发现这世界上有能力的人并不少,但是有品味的人的确很少。所谓的有价值,就是,别人愿付高价的,高技术门槛的,有创造力的,有颠覆性的……
人要变得开放,千万不要做一个狭隘的民族主义者,做一个开放的人,把目光放在全人类这个维度,不断地把自己融入到世界上,而不是把自己封闭起来,这里,你的英文语言能力对你能不能融入世界是起决定性的作用。开放自己的心态,正视自己的缺点,你才可能往前迈进。你的视野决定了你的知不知道要去哪,你的开放决定了你想不想去。
面的维度会超过点的维点,空间的维度会超过面的维度,在更高维度上思考和学习,你会获得更多。整天在焦虑那些低维度的事(比如自己的薪水、工作的地点、稳不稳定、有没有户口……),只会让你变得越来越平庸,只要你站在更高的维度(比如: 眼界有没有扩大、可能性是不是更多、竞争力是不是更强、能不能解决更大更难的问题、能创造多大的价值……),时间会让你明白那些低维度的东西全都不是事儿。技术学习上也一样,站在学习编程语法特性的维度和站在学习编程范式、设计模式的维度是两种完全不一样的学习方式。
很多人其实不是很懂计算。绝大多数人都是在算计自己会失去多少,而不会算会得到多少。而一般的人也总是在算短期内会失去什么,优秀则总是会算我投入后未来会有什么样的回报,前者在算计今天,目光短浅,而后者则是舍在今天,得在明天,计算的是未来。精于计算得失的,就懂得什么是投资,不懂的只会投机。对于赚钱,你可以投机,但是对于自己最好还是投资。
有时候,跳出传统并不是一件很容易的事,因为大多数人都会对未知有恐惧的心理。比如:我看到很多人才都被大公司垄断了,其实,有能力的人都不需要加入大公司,有能力的人是少数,这些少数的人应该是所有的公司share着用的,这样一来,对于所有的人都是利益最大化的。这样的事现在也有,比如:律师、设计师……。但是,绝大多数有能力的技术人员是不敢走出这步。我在2015年到2016年实践过一年半,有过这些实践,做“鸡”的比“二奶”好多了,收入也好很多很多(不好意思开车了)……
井蛙不可以语于海者,拘于虚也;//空间局限
夏虫不可以语于冰者,笃于时也;//时间局限
曲士不可以语于道者,束于教也。//认识局限
别自己墙了自己,人最可悲的就是自己限制自己,想都不敢想,共勉!
# -*- coding: utf-8 -*- #解决编码问题
import urllib
import urllib2
import re
import os
import time
page = 1
url = 'http://www.qiushibaike.com/text/page/4/?s=4970196' #爬取的目标网站
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
request = urllib2.Request(url,headers = headers)
response = urllib2.urlopen(request)
# print response.read()
content = response.read().decode('utf-8') #解决编码问题
pattern = re.compile(r'<div.*?class="content".*?<span>(.*?)</span>.*?</div>',re.S) #第一个参数是匹配要爬取的内容,这里使用正则去匹配
items = re.findall(pattern,content)
f=open(r'.\article.txt','ab') #txt文件路径
nowTimes = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) #获取当前时间
f.write('时间:{}\n\n'.format(nowTimes),); #txt文件中写入时间
for i in items:
i.encode('utf-8')
agent_info = u''.join(i).encode('utf-8').strip()
f.writelines('段子:%s%s\n'%(str(agent_info),os.linesep)) #分行存入
# f.write('%s'%str(agent_info))
f.close()
# print items
except urllib2.URLError, e:
if hasattr(e,"code"):
print e.code
if hasattr(e,"reason"):
print e.reason
* */1 * * * /usr/bin/python /home/dengwen/desktop/echo2.py
fib性能不好主要是因为会多次计算重复n,因此需要对n的计算结果进行缓存
const fn = (() => {
const obj = {}
// obj变量形成闭包,用于缓存fn(n)
return function(n){
if(obj[n]) return obj[n]
if(n===1) return 0
else if(n===2) return 1
else {
res = fn(n-1) + fn(n-2)
obj[n] = res
return res
}
}
})()
什么情况下会出现死循环
import { useEffect, useState } from 'react'
function Form(props){
const [value, key] = useState('')
const { data } = props
return <div>form</div>
}
const fs = require('fs')
const input = 'test.mp4'
var file = fs.createReadStream(input);
var out = fs.createWriteStream('./test1.mp4');
let totalSize = fs.statSync( input ).size // 通过 fs.statSync 获取文件大小
let curSize = 0
file.on('data',function(chunk){
curSize += chunk.length
const percent = (curSize / totalSize * 100)
out.write(chunk)
console.log(`读取中,当前进度:${percent}`)
});
file.on('end',function(){
out.end();
})
Click on an image to enlarge
给你一个整数n. 从 1 到 n 按照下面的规则打印每个数:
比如 n = 15, 返回一个字符串数组:
[
"1", "2", "fizz",
"4", "buzz", "fizz",
"7", "8", "fizz",
"buzz", "11", "fizz",
"13", "14", "fizz buzz"
]
const fn = function (n) {
let arr = []
if(n%3 === 0 && n%5 === 0&&n!=0)
{
arr.push('fizz buzz')
}
else if(n%3 === 0&&n!=0)
{
arr.push('fizz')
}
else if(n%5 === 0&&n!=0)
{
arr.push('buzz')
}
else if(n!=0){
arr.push(n.toString())
}
if(n>1)
{
return [...arr,...fn(n-1)]
}
return arr
}
const fizzBuzz = function(n){
const arr = []
for(var i =1;i<=n;i++)
{
if(i%3 === 0 && i%5 === 0)
{
arr.push('fizz buzz')
}
else if(i%3 === 0)
{
arr.push('fizz')
}
else if(i%5 === 0)
{
arr.push('buzz')
}
else{
arr.push(i.toString())
}
}
return arr
}
Generator 函数是 ES6 提供的一种异步编程解决方案,执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态。
function* foo(x) {
yield 1
yield 2
yield 3
yield 4
return 5
}
必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
function* foo() {
yield 1
yield 2
return 3
}
var f = foo()
f.next() //{ value: 1, done: false }
f.next().value //2
f.next() //{ value: 3, done: true}
f.next() //{ value: undefined, done: true}
Generator 函数已经运行完毕,next方法返回对象的value属性为3,done属性为true,之后再执行next(),done都为true,value未undefined
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
function sayhello() {
setTimeout(()=>{
console.log(123)
},3000)
}
function helloworld() {
const data = sayhello();
console.log(data);
console.log(456)
}
helloworld()
function sayhello(){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(123)
console.log(123)
},3000)
})
}
async helloworld(){
const data = await sayhello()
console.log(data)
console.log(456)
}
function co(gen) {
if(!gen) return
return new Promise((resolve,reject)=>{
var it = gen();
try{
function step(next){
if(next.done)
{
return resolve(next.value)
}
else{
Promise.resolve(next.value).then((res)=>{
return step(it.next(res))
},(e)=>{
return step(it.throw(e))
})
}
}
step(it.next())
}
catch(e){
return reject(e)
}
})
}
function sayhello(){
// 之所以需要返回一个 promise ,是因为 co 函数会在底层使用 promise 同步执行 sayhello ,为了保证 sayhello 同步执行完该异步函数后获取到正确的返回值,需要使用promise包裹返回值。
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(123)
console.log(123)
},3000)
})
}
co(
function* helloworld(){
const data = yield sayhello()
console.log(data)
console.log(456)
}
)
为了更高效地开发、维护前端代码,我们最常听到也最常用的方式就是将通用性较强的组件或逻辑提取成公共函数或公共组件,减少重复代码,那么还有别的方式吗?本文将带你通过Custom Hook
与HOC
实现极致的逻辑复用,大幅提升代码开发及维护体验。
在了解具体细节前,我们需要先了解Custom Hook
与HOC
。
通过Custom Hook
,当两个函数之间存在共享逻辑时,我们可以把共享逻辑
提取到第三个函数中,而组件和 Hook 都是函数,所以也同样适用这种方式,即支持 Hook 内 调用其他 Hook
。
Custom Hook
必须以 “use”
开头,这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
示例代码如下, 下述组件A、B存在逻辑复用问题 :
组件A
function A() {
const [isShow, switchShow] = useState(false);
const switch = () => {
switchShow(!isShow)
}
return <div onClick={switch}>组件A</div> ;
}
组件B
function B() {
const [isShow, switchShow] = useState(false);
const switch = () => {
switchShow(!isShow)
}
return <div onClick={switch}>组件B</div> ;
}
解决方案:
自定义 hook useShowStatus
function useShowStatus(defaultStatus){
const [isShow, switchShow] = useState(defaultStatus);
const switch = () => {
switchShow(!isShow)
}
return { isShow, switch }
}
组件A
function A() {
const { isShow, switch }= useShowStatus(false);
return <div onClick={switch}>组件A</div> ;
}
组件B
function B() {
const { isShow, switch }= useShowStatus(false);
return <div onClick={switch}>组件B</div> ;
}
js逻辑重复的问题解决了!,那么如果是以下情况出现了组件引用的多次复用,又该怎么解决呢?
组件A
import { Modal, Button } from 'antd'
function A(){
const { isShow, switch }= useShowStatus(false);
return <div>
<Button onClick={switch}>组件A</Button>
<Modal show={isShow} />
</div> ;
}
组件B
import { Modal, Button } from 'antd'
function B(){
const { isShow, switch }= useShowStatus(false);
return <div onClick={switch}>
<Button onClick={switch}>组件B</Button>
<Modal show={isShow} />
</div> ;
}
这时便可以用到HOC
了。
HOC
又称高阶组件
,它接收一个函数作为参数,并且返回值也是一个函数,
高阶组件 HocCom
import { Modal } from 'antd'
function Hoc(Com){
return function HocCom(props){
const { isShow, switch }= useShowStatus(false);
const hocComProps = {
...props,
switch
}
return <>
<Modal show={isShow} />
<Com {...hocComProps} />
</>
}
}
export default Hoc
组件A
import Hoc from './HocCom'
function A(props){
const { switch }= props
return <div>
<Button onClick={switch}>组件A</Button>
</div> ;
}
Hoc(A)
组件B
import Hoc from './HocCom'
function B(props){
const { switch }= props
return <div>
<Button onClick={switch}>组件B</Button>
</div> ;
}
Hoc(B)
如此一来,公共组件及相关状态都放到了HOC中进行管理,大幅提升了代码简洁程度,同时也提升了代码维护效率及开发者开发体验。以上便是Custom Hook 与 HOC 让代码不再臃肿、冗余
的代码实践了,如果读者有不同的想法或意见
,欢迎在评论区提出,互相学习。
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
if(!list1) return list2
if(!list2) return list1
if(list1.val < list2.val){
list1.next = mergeTwoLists(list1.next, list2)
return list1
}else{
list2.next = mergeTwoLists(list1, list2.next)
return list2
}
}
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
n<=39
function Fibonacci(n){
if(n == 0){
return 0
}else if(n==1)
{
return 1
}else{
var arr = []
arr[0] = 0
arr[1] = 1
for(var i = 2;i<=n;i++)
{
arr[i]= arr[i-1] + arr[i-2]
}
return arr[n]
}
}
优化前
function Fibonacci(n){
if(n == 0){
return 0
}else if(n==1)
{
return 1
}else{
return generate(n-1)+generate(n-2)
}
}
优化方案1:
尾递归优化,节省内存空间
function Fibonacci(n)
{
// write code here
function get(index,last1=0,last2=1){
if(index <= 0){
return last1
}else if(n==1)
{
return last2
}else{
return get(index-1,last2,last2+last1)
}
}
return get(n)
}
优化方案2:
fib性能不好主要是因为会多次计算重复n,因此需要对n的计算结果进行缓存
const Fibonacci = (() => {
const obj = {}
// obj变量形成闭包,用于缓存fn(n)
return function(n){
if(obj[n]) return obj[n]
if(n===0) return 0
else if(n===1) return 1
else {
res = fn(n-1) + fn(n-2)
obj[n] = res
return res
}
}
})()
防抖、节流函数
在我们频繁做出某一行为(如用户input框输入或滚动事件
)时会经常用到,本篇文章将带你重新认识:
防抖:行为发生一定时间后触发事件,在这段时间内如果该行为再次发生,则重新等待一定时间后再触发
事件
// 防抖
function debounce(fn,time){
let timer = null
return function(){
timer && clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(this,arguments)
},time)
}
}
节流:一段时间内如果行为多次发生,只会触发一次事件
function throttle(fn,time){
let oldTimestamp = 0
return function(){
const curTimestamp = Date.now()
if(curTimestamp - oldTimestamp >= time){
oldTimestamp = curTimestamp
fn.apply(this,arguments)
}
}
}
这里有一段demo代码
const op = document.getElementById('demo')
op.addEventListener('click',throttle(sayHi,1000))
function sayHi(){
console.log('sayHi',this)
}
function debounce(fn,time){
let timer = null
return function(){
timer && clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(this,arguments)
},time)
}
}
接着我们继续玩下看
为什么需要使用fn.apply
fn.apply主要用于帮我们修改函数运行的this指向, 否则this将指向window
setTimeout为什么要使用箭头函数
不使用箭头函数的话,setTimeout内部this将指向window
setTimeout不使用箭头函数如何实现debounce
const self = this
fn.apply(self,arguments)
return function(){}
!== return ()=>{}
,这里如果return一个箭头函数,内部this指向也将指向window每次代码需要更新时,服务器必须重新部署,客户端也必须重新下载资源。因为从网络中获取资源会很慢,这显然非常低效。这就是为什么浏览器会缓存静态资源的原因。但是这样做有一个弊端:如果在部署新的版本中不修改文件名,浏览器会认为它没有更新,就会使用缓存中的版本。
webpack通过hash配置可以帮我们构建出文件名不同的静态资源文件,这样浏览器就能获取到最新的静态资源了。但是这样每次浏览器对静态资源的请求是全量的,如果可以只修改发生变化的文件的hash值,这样就能进行增量请求,可以大大提高我们的web性能,这就我们所说的持久化缓存。
##(搭建webpack过程)
npm init
//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack
1.在空目录下新建两个文件
将webpack.config.js拆分为webpack.development.config.js、webpack.production.config.js。【在webpack4中,添加了mode('development','production','none')配置项,同时会自动同步process.env.NODE_ENV的值。】(同命令行 --mode development)
webpack.development.config.js
module.exports = {
mode: 'deveopment',
entry: __dirname + "/src/index.js", //入口
output: { //出口
path:__dirname + "/public",
filename: "bundle.js"
},
devtool:'eval-source-map' //使得编译后的代码可读性更高,也更容易调试
}
webpack.production.config.js
module.exports = {
mode:'production',
entry: __dirname + "/src/index.js", //入口
output: { //出口
path:__dirname + "/public",
filename: "bundle.js"
},
devtool:'source-map' //使得编译后的代码可读性更高,也更容易调试
}
package.json 修改为:
{
"name": "document",
"version": "1.0.0",
"description": "",
"main": "webpack.development.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack --mode development --config webpack.development.config.js",
"build": "webpack --mode production --config webpack.production.config.js"
},
"author": "",
"license": "ISC"
}
2.在空目录下新建public,src目录
index.html 放在public目录下
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
index.js 放在src目录下
const greeter = require('./Greeter.js');
document.querySelector("#root").appendChild(greeter());
greeter.js 放在src目录下
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
npm run start
打包成功后,public目录打包出bundle.js,浏览器打开index.html
npm run build
.map为源码映射文件,此时查看bundle.js可以看出js代码已被压缩,因为webpack4在production环境下默认配置了压缩plugin:uglifyjs-webpack-plugin,其它默认插件有:
development环境
const webpack = require('webpack')
module.exports = {
mode: 'development',
entry: __dirname + "/src/index.js", //入口
output: { //出口
path:__dirname + "/public",
filename: "bundle.js"
},
devtool:'eval-source-map', //使得编译后的代码可读性更高,也更容易调试
plugins:[
// new webpack.NamedModulesPlugin(), //已默认配置
// new webpack.NamedChunksPlugin(), //已默认配置
// new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }), //已默认配置
]
}
production环境
const webpack = require('webpack')
module.exports = {
mode:'production',
entry: __dirname + "/src/index.js", //入口
output: { //出口
path:__dirname + "/public",
filename: "bundle.js"
},
devtool:'source-map', //使得编译后的代码可读性更高,也更容易调试
plugins:[
// new UglifyJsPlugin(/* ... */), //已默认配置 js压缩
// new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }), //已默认配置 定义变量
// new webpack.optimize.ModuleConcatenationPlugin(), //已默认配置 作用域(scope hoisting)提升
// new webpack.NoEmitOnErrorsPlugin() //已默认配置 打包过程中报错不会退出
]
}
mode 为 none时,默认无plugin配置
npm install webpack-dev-server --save
webpack.development.config.js 添加
...
devServer:{
contentBase:'./public', //设置本地服务器目录
historyApiFallback:true, //设置为true,所有的跳转将指向index.html
inline:true, //实时刷新
port:3000, //端口
},
...
package.json 添加
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "webpack --mode development --config webpack.development.config.js",
"build": "webpack --mode production --config webpack.production.config.js",
"dev": "webpack-dev-server --mode development --config webpack.development.config.js --watch "
},
npm run dev
浏览器打开localhost:3000
此时我们的本地服务器就构建成功了
1.添加config.json文件
{
"greetText": "Hello World!"
}
2.修改greeter.js
import React, {Component} from 'react'
import config from './config.json';
class Greeter extends Component{
render() {
return (
<div>
{config.greetText}
</div>
);
}
}
export default Greeter
3.修改index.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './greeter';
console.log(document.getElementById('root'))
render(<Greeter />, document.getElementById('root'));
4.安装webpack所需loader、babel
npm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react
5.安装react所需库
npm install --save react react-dom
6.配置webpack
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react" //babel
]
}
},
exclude: /node_modules/
}
]
}
...
npm run dev
此时react运行环境已配置好
1.src新建greeter.css文件
.root {
background-color: #eee;
padding: 100px;
border: 3px solid #ccc;
}
2.修改greeter.js
...
import styles from './greeter.css';
...
3.安装css loader
npm install css-loader style-loader
4.配置loader
...
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react" //babel
]
}
},
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader"
}, {
loader: "css-loader",
}
]
}
]
},
...
npm run dev
可以看到css已经可以打包运行了
webpack4中已经remove了 extract-text-webpack-plugin,官方推荐使用mini-css-extract-plugin
npm install --save mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
...
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
uglifyOptions: {
compress: true,
}
}),
new OptimizeCSSAssetsPlugin({})
],
},
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"env", "react" //babel
]
}
},
exclude: /node_modules/
},
{
test: /\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader']
}
]
},
plugins[
new MiniCssExtractPlugin({
filename: "[name]-[contenthash].css",
chunkFilename: "[id].css"
}),
]
...
npm run build
可以看到css已经被单独提取出来,并且js、css都已经被压缩
不过此时我们可以看到react仍然被打包到bundle中去了,导致bundle.js仍然很大,像这种npm包一般不会有修改,所以我们也应该提取出来
在webpack4中,commonsChunkPlugin已被removed,官方推荐使用SplitChunksPlugin
...
output: { //出口
path:__dirname + "/public",
filename: "[name]-[contenthash].bundle.js",
chunkFilename: '[name].[chunkhash:5].chunk.js'
},
...
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
uglifyOptions: {
compress: true,
}
}),
new OptimizeCSSAssetsPlugin({})
],
splitChunks: {
chunks: "all", //initial、async和all
minSize: 30000, //形成一个新代码块最小的体积
minChunks: 1, //在分割之前,这个代码块最小应该被引用的次数
maxAsyncRequests: 5, //按需加载时候最大的并行请求数
maxInitialRequests: 3, //最大初始化请求数
automaticNameDelimiter: '~', //打包分割符
name:'commons',
//打包后的名字
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/, //用于控制哪些模块被这个缓存组匹配到、它默认会选择所有的模块
priority: -10, //缓存组打包的先后优先级
name: 'vendors',
minSize:3000
},
default: {
minChunks: 2,
priority: -20,
minSize:3000
}
}
}
},
...
npm install html-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
...
plugins:[
...
new HtmlWebpackPlugin({
template: __dirname + "/src/index.tmpl.html",//new
}),
...
]
...
src目录新建index.tmpl.html
index.tmpl.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
npm run build
##总结
此时我们可以发现react已被打包到vendors中,bundle.js小了很多,并且多次打包,文件hash值不再修改
修改.css文件
可以发现只有css的hash值发生改变,其它文件名并未发生变化,当发布静态文件时,浏览器就对未变化的文件仍然使用缓存,这样就符合我们要实现持久化缓存的预期。
从本文中,我将从是什么
,为什么
,怎么做
来为大家阐述 NextJS 以及如何优化 NextJS 应用体验。
NextJS
是一款基于 React 进行 web 应用开发的框架,它以极快的应用体验而闻名,内置 Sass、Less、ES 等特性,开箱即用。SSR 只是 NextJS 的一种场景而已,它拥有4种渲染模式
,我们需要为自己的应用选择正确的渲染模式:
SSG / ISR 都是非常适合博客类应用的,区别在于SSG是构建时生成,效率较低,ISR是基于已有的缓存按需生成,效率更高
。
首屏加载速度快
我们的内嵌场景比较丰富,因此比较追求页面的一个首屏体验
,NextJS 的产物类似 MPA(多页面应用)
,在请求页面时会对当前页面的资源进行按需加载
,而不是去加载整个应用, 相对于 SPA 而言,可以实现更为极致的用户体验。
SEO优化好
SSR \ SSG \ ISR 支持页面内容预加载
,提升了搜索引擎的友好性。
内置特性易用且极致
NextJS 内置 getStaticProps
、getServerSideProps
、next/image
、next/link
、next/script
等特性,充分利用该框架的这些特性,为你的用户提供更高层次的体验,这些内容后文会细讲。
页面卡顿
。作为一名开发者,我们追求的不应该是应用能用就好,而是好用,那么如何评价我们的应用是否好用呢?
performance
与lighthouse
来评判如你所见,由于应用模块的一个复杂性,我们的 NextJS 应用起初性能并不是很好,甚至谈得上是差
FCP: 首次内容绘制时间1.8s
lighthouse: 性能评分报告 55分,Time to Interactive(TTI) 可交互时间为 7.3s,通常是发生在页面依赖的资源已经加载完成。
network: 我们每次进行路由跳转都要按需加载资源,因此我们需要单个页面的 DomContentLoaded/Load 尽可能快。
页面构建时间
这些指标都间接反馈出应用的体验问题亟待解决。
优化用户体验
2. 针对非首屏组件基于 dynamic 动态加载
在页面加载过程中,针对一些不可见组件,我们应该动态导入,而不是正常导入,确保只有需要该组件的场景下,才 fetch 对应资源
, 通过 next/dynamic
,在构建时,框架层面会帮我们进行分包
import dynamic from 'next/dynamic'
const Modal = dynamic(() => import('../components/mModal'));
export default function Index() {
return (
{showModal && <Modal />}
)
}
打开Network。当条件满足时,你将看到一个新的网络请求被发出来获取动态组件(单击按钮打开一个模态)。
3 . next/script 优化 script 加载时
next/script
可以帮助我们来决定 js 脚本加载的时机
strategy | 描述 |
---|---|
beforeInteractive | 可交互前加载脚本 |
afterInteractive | 可交互后加载脚本 |
lazyOnload | 浏览器空闲时加载脚本 |
<Script strategy="lazyOnload" src="//wl.jd.com/boomerang.min.js" />
4. next/image 优化图片资源
next/image
可帮助我们对图片进行压缩(尺寸 or 质量),且支持图片懒加载
,默认 loader 依赖 nextjs 内置服务,也可以通过{loader: custom}
自定义loader
import Image from 'next/image'
const myLoader = ({ src, width, quality }) => {
return `https://example.com/${src}?w=${width}&q=${quality || 75}`
}
const MyImage = (props) => {
return (
<Image
loader={myLoader}
src="me.png"
alt="Picture of the author"
width={500}
height={500}
/>
)
}
5. next/link 预加载
基于 hover 识别用户意图
,当用户 hover 到 Link 标签时,对即将跳转的页面资源进行预加载
,进一步防止页面卡顿
import Link from 'next/link'
<Link prefetch={false} href={href}>目标页面</Link>
6. 静态内容预加载
基于 getStaticProps
对不需要权限的内容进行预加载,它将在 NextJS 构建时被编译到页面中,减少了 http 请求数量
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export async function getStaticProps() {
const res = await fetch('https://.../posts')
const posts = await res.json()
return {
props: {
posts,
},
}
}
export default Blog
7. 第三方 library 过大时,基于 umd 按需加载
当第三方 library 过大时,以 umd 进行引入
,在需要的场景下通过 script 进行加载。
// 封装记载umd模块的hoc
function loadUmdHoc(Comp: (props) => JSX.Element, src: string) {
return function Hoc(props) {
const [isLoaded, setLoaded] = useState(
!!Array.from(document.body.getElementsByTagName('script')).filter(
(item) => item.src.match(src)
).length
)
useEffect(() => {
if (isLoaded) return
const script = document.createElement('script')
script.src = src
script.onload = () => {
setLoaded(true)
}
document.body.append(script)
}, [])
if (isLoaded) {
return <Comp {...props} />
}
return <></>
}
}
function Upload(){
// todo 使用umd模块
return <></>
}
// 使用该组件时,加载hoc
export default loadUmdHoc(
Upload,
'xxx.umd.js'
)
优化研发体验
urlImport
是 NextJS 提供的一个实验特性,支持加载远程 esmodule影响 treeShaking
的一个效果,因此在生产环境,建议通过NormalModuleReplacementPlugin
对 urlimport 的依赖进行一个本地替换2. webpack 配置选择性忽略
针对一些生成环境的配置我们可以通过区分环境来进行选择性忽略部分配置
,如 module federation exposes 在开发环境我们就可以忽略掉。
3. 开启 SWC 编译
SWC 是基于 Rust 实现的一款开发工具,既可用于编译也可用于打包,据官方言,它比 Babel 快了 20~70倍,NextJS 在 12 版本默认打开了 SWC 的支持。开启 SWC 后,应用的编译速度将比 Babel 快 17 倍,刷新速度快 5 倍。需要注意的是如果你通过.babelrc
自定义 babel 配置,SWC 的一些特性将会被关闭。
从以下指标可以看出我们应用的体验得到了很大提升, 实际的一个交互体验也好了不少,在路由跳转上实现了类似 SPA 的一个体验,不会再出现页面卡顿的情况。
FCP: 首次内容绘制时间 从 1.8s 优化到 0.35s,提升了近 80%
lighthouse: 评分从55提升到了80,TTI 从7.3s 优化到了 2.6s, 分别提升了 30% / 64%,chrome 的最佳实践分达到了满分
💯
network: DomContentLoaded 从 2.42s 优化到 0.68s,Load从 3.77s 优化到 1.47s ,分别提升了 77% / 61%
页面构建时间: 基本满足了毫秒级实现页面编译的需求,提升了 70% 以上
为了实现更为极致的用户体验,我们后续计划将资源上CDN,减少Waiting for server response
的性能损耗,并加入PWA
的离线缓存特性。
参考文章
Optimize Next.js App Bundle and Improve Its Performance
我看Next.js:一个更现代的海王
给定一个只含非负整数的m*n网格,找到一条从左上角到右下角的可以使数字和最小的路径。
输入: [[1,3,1],[1,5,1],[4,2,1]]
输出:7
路线为: 1 -> 3 -> 1 -> 1 -> 1。
function fn(arr){
for(var i in arr)
{
for(var j in arr[0]){
if(i==0&&j>0)
{
arr[i][j] += arr[i][j-1]
}
else if(j==0&&i>0)
{
arr[i][j] += arr[i-1][j]
}
else if(i>0&&j>0)
{
arr[i][j] += Math.min(arr[i][j-1],arr[i-1][j])
}
}
}
return arr[arr.length-1][arr[0].length-1]
}
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
function minNumberInRotateArray(rotateArray)
{
// write code here
if(rotateArray.length == 0)
{
return 0
}
return Math.min(...rotateArray)
}
在web应用中,前端同学在实现动画效果时往往常用的几种方案:
在大多数需求中,css3的 transition / animation
都能满足我们的需求,并且相对于js实现,可以大大提升我们的开发效率,降低开发成本。
本篇文章将着重对 animation
的使用做个总结,如果你的工作中动画需求较多,相信本篇文章能够让你有所收获:
介绍完 animation 常用属性,为了将这些属性更好地理解与运用,下面将手把手实现一些DEMO具体讲述
通过修改内容在父元素中的y轴的位置来实现广播效果
@keyframes scroll {
0%{
transform: translate(0, 0);
}
100%{
transform: translate(0, -$height);
}
}
.ul {
animation-name: scroll;
animation-duration: 5s;
animation-timing-function: linear;
animation-iteration-count: infinite;
/* animation: scroll 5s linear infinite; 动画属性简写 */
}
此处为了保存广播滚动效果的连贯性,防止滚动到最后一帧时没有内容,
需要多添加一条重复数据进行填充
<div class="ul">
<div class="li">小刘同学加入了凹凸实验室</div>
<div class="li">小邓同学加入了凹凸实验室</div>
<div class="li">小李同学加入了凹凸实验室</div>
<div class="li">小王同学加入了凹凸实验室</div>
<!-- 插入用于填充的数据数据 -->
<div class="li">小刘同学加入了凹凸实验室</div>
</div>
通过将过渡动画拆分为多个阶段,每个阶段的top属性停留在不同的位置来实现
/* 规定动画,改变top,opacity */
@keyframes animate {
0% {
top: -100%;
opacity: 0;
}
25% {
top: 60;
opacity: 1;
}
50% {
top: 48%;
opacity: 1;
}
75% {
top: 52%;
opacity: 1;
}
100%{
top: 50%;
opacity: 1;
}
}
为了让过渡效果更自然,这里通过
cubic-bezier()
函数定义一个贝塞尔曲线来控制动画播放速度
过渡动画执行完后,为了将让元素应用动画最后一帧的属性值,我们需要使用
animation-fill-mode: forwards
.popup {
animation-name: animate;
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.21, 0.85, 1, 1);
animation-iteration-count: 1;
animation-fill-mode: forwards;
/* animation: animate 0.5s cubic-bezier(0.21, 0.85, 1, 1) 1 forwards; 动画属性简写 */
}
相信大多数同学都知道点赞效果,本文章会实现一个简易版的点赞效果,主要讲述一下实现思路:
1.为了让气泡可以向上偏移,我们需要先实现一个y轴方向上移动的 @Keyframes 动画
/* 规定动画,改变y轴偏移距离*/
@keyframes animation-y {
0%{
transform: translate(-50%, 100px) scale(0);
}
50%{
transform: translate(-50%, -100px) scale(1.5);
}
100%{
transform: translate(-50%, -300px) scale(1.5);
}
}
2.为了让气泡向上偏移时显得不太单调,我们可以再实现一个x轴方向上移动的 @Keyframes 动画
/* 规定动画,改变x轴偏移距离 */
@keyframes animation-x {
0%{
margin-left: 0px;
}
25%{
margin-left: 25px;
}
75%{
margin-left: -25px;
}
100%{
margin-left: 0px;
}
}
这里我理解:
修改 margin
来改变x轴偏移距离,但实际上与修改 transform
没有太大的性能差异@keyframes animation-y
中的 transform
已经新建了一个渲染层 ( PaintLayers )
animation
属性 可以让该渲染层提升至 合成层(Compositing Layers)
拥有单独的图形层 ( GraphicsLayer )
,与开启了硬件加速的效果一样 ,不会影响其他渲染层的 paint、layout
如笔者这里理解有误,还请读者大佬指出,感激不尽~
3.给气泡应用上我们所实现的两个 @Keyframes 动画
.bubble {
animation: animation-x 3s -2s linear infinite,animation-y 4s 0s linear 1;
/* 给 bubble 开启了硬件加速 */
}
4.在点赞事件中,通过 js 操作动态添加/移除气泡元素
function like() {
const likeDom = document.createElement('div');
likeDom.className = 'bubble'; // 添加样式
document.body.appendChild(likeDom); // 添加元素
setTimeout( () => {
document.body.removeChild(likeDom); // 移除元素
}, 4000)
}
1.首先,我们使用 svg 绘制一个圆周长为2 * 25 * PI = 157
的圆
<svg with='200' height='200' viewBox="0 0 100 100" >
<circle cx="50" cy="50" r="25" fill="transparent" stroke-width="4" stroke="#0079f5" ></circie>
</svg>
2.将实线圆绘制成虚线圆,这里需要用 stoke-dasharray:50, 50 (可简写为50)
属性来绘制虚线, stoke-dasharray 参考资料
短划线(50px)
和缺口(50px)
的长度。50(短划线) + 50(缺口) + 50(段划线) = 150, 150 < 157
,无法绘制出完整的圆,所以会导致右边存在缺口(7px)
<svg with='200' height='200' viewBox="0 0 100 100" >
<circle cx="50" cy="50" r="25" fill="transparent" stroke-width="4" stroke-dasharray="50" stroke="#0079f5" ></circie>
</svg>
3.stroke-dashoffset
属性可以使圆的短划线和缺口产生偏移,添加 @Keyframes 动画后能够实现从无到有的效果,stoke-dashoffset参考资料
stroke-dasharray="157 157
",指定 短划线(157px)
和 缺口(157px)
的长度。修改stroke-dashoffset值
, 值为正数
时逆时针偏移
🔄,, 值为负数
时,顺时针偏移
🔃@keyframes loading {
0%{
stroke-dashoffset: 0;
}
100%{
stroke-dashoffset: -157; /* 线条顺时针偏移 */
}
}
circle{
animation: loading 1s 0s ease-out infinite;
}
4.修改短划线和缺口值
50px
的短划线,设置 stroke-dasharray="50"
缺口需要大于或等于圆周长157
,设置 stroke-dasharray="50 157"
动画结束时仍处理动画开始位置
,需要修改 stroke-dashoffset:-207(短划线+缺口长度)
stroke-dashoffset
属性,具体实现请查看示例@keyframes loading {
0%{
stroke-dashoffset: 0;
}
100%{
stroke-dashoffset: -207; /* 保证动画结束时仍处理动画开始位置 */
}
}
circle{
animation: loading 1s 0s ease-out infinite;
}
steps()
是 animation-timing-function
的属性值
animation-timing-function : steps(number[, end | start])
两个参数
第一个参数接受一个整数值
,表示两个关键帧之间分几步完成第二个参数有两个值 start or end
。默认值为 endsteps 适用于关键帧动画,第一个参数将两个关键帧
细分为N帧
,第二个参数决定从一帧到另一帧的中间间隔是用开始帧
还是结束帧
来进行填充。
看下图可以发现:
steps(N, start)
将动画分为N段
,动画在每一段的起点
发生阶跃(即图中的空心圆 → 实心圆),动画结束时停留在了第N帧steps(N, end)
将动画分为N段
,动画在每一段的终点
发生阶跃(即图中的空心圆 → 实心圆),动画结束时第N帧已经被跳过(即图中的空心圆 → 实心圆),停留在了N+1帧。13
个字符。[经测试,多数中文字体每个字符宽高都相等]steps(13)
可以将 @Keyframes 动画分为13阶段
运行,且每一阶段运行距离相等
。效果如下:
/* 改变容器宽度 */
@keyframes animate-x {
0%{
width: 0;
}
}
p {
width: 125px;
overflow: hidden;
border-right: 1px solid transparent;
animation: animate-x 3s 0s steps(13) 1 forwards;
}
每个字符的width与动画每一阶段运行的距离相等
Monaco
字体属性,用以保证每个字符的 width 相同
,具体像素受fontSize
属性影响,示例中的字体宽度约为 9.6px,9.6px * 13(段数) = 124.8px (125px)
,所以当我们设置容器宽度为 125px,即可的达成目的:每个字符的 width 与动画每一阶段运行的距离相等(约为 9.6px )
。p {
/* 设置 Monaco 字体属性,字体大小为16px,用以保证每个字符的 width 相同,width 约为9.6p */
font-family: Monaco;
/* 9.6px * 13 = 124.8px (125px) */
width: 125px ;
font-size: 16px;
overflow: hidden;
border-right: 1px solid transparent;
/* 同时应用动画 animate-x、cursor-x */
animation: animate-x 3s 0s steps(13) 1 forwards,cursor-x 0.4s 0s linear infinite;
}
47帧
的雪碧图(css spirit),设置背景图.main {
width: 260px;
height: 200px;
background: url(url) no-repeat;
background-size: 100%;
background-position: 0 0;
}
修改 background-position
,让背景图移动@keyframes animate {
0% {
background-position: 0 0;
}
100% {
background-position: 0 100%;
}
}
.main{
width: 260px;
height: 200px;
background: url(url) no-repeat;
background-size: 100%;
background-position: 0 0;
animation: animate 2s 1s steps(47) infinite alternate;
}
animation-play-state
用于控制动画是否暂停input:checked+.main{
animation-play-state: paused;
}
CSS 参考手册
SVG学习之stroke-dasharray 和 stroke-dashoffset 详解
理解CSS3 Animation中的steps()
【译】css动画里的steps()用法详解
CSS Will Change
无线性能优化:Composite
DI—Dependency Injection,即“依赖注入”:对象之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个对象注入到对象属性之中
。依赖注入的目的并非为软件系统带来更多功能,而是为了提升对象重用的频率,并为系统搭建一个灵活、可扩展的框架。
首先看一下常用依赖注入 (DI)的方式:
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayHello(){
console.log('hello')
}
}
class B {
@Inject // 编译后等同于执行了 @Reflect.metadata("design:type", A)
a: A
say(){
this.a.sayHello() // 不需要再对class A进行实例化
}
}
new B().say() // hello
TS在编译装饰器的时候,会通过执行__metadata函数
多返回一个属性装饰器@Reflect.metadata
,它的目的是将需要实例化的service
以元数据'design:type'
存入reflect.metadata
,以便我们在需要依赖注入时,通过Reflect.getMetadata
获取到对应的service
, 并进行实例化赋值给需要的属性。
@Inject
编译后代码:
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
// 由于__decorate是从右到左执行,因此, defineMetaData 会优先执行。
__decorate([
Inject,
__metadata("design:type", A) // 作用等同于 Reflect.metadata("design:type", A)
], B.prototype, "a", void 0);
即默认执行了以下代码:
Reflect.defineMetadata("design:type", A, B.prototype, 'a');
Inject
函数需要做的就是从metadata
中获取对应的构造函数并构造实例对象赋值给当前装饰的属性
function Inject(target: any, key: string){
target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
不过该依赖注入方式存在一个问题:
Inject函数
在代码编译阶段便会执行,将导致B.prototype
在代码编译阶段被修改,这违反了六大设计原则之开闭原则(避免直接修改类,而应该在类上进行扩展)
TypeDI
的**。typedi 的依赖注入**是类似的,不过多维护了一个container
在了解其container
前,我们需要先了解 typedi 中定义的metadata
,这里重点讲述一下我所了解的比较重要的几个属性。
id: service的唯一标识
type: 保存service构造函数
value: 缓存service对应的实例化对象
const newMetadata: ServiceMetadata<T> = {
id: ((serviceOptions as any).id || (serviceOptions as any).type) as ServiceIdentifier, // service的唯一标识
type: (serviceOptions as ServiceMetadata<T>).type || null, // service 构造函数
value: (serviceOptions as ServiceMetadata<T>).value || EMPTY_VALUE, // 缓存service对应的实例化对象
};
function ContainerInstance() {
this.metadataMap = new Map(); //保存metadata映射关系,作用类似于Refect.metadata
this.handlers = []; // 事件待处理队列
get(){}; // 获取依赖注入后的实例化对象
...
}
@service
会将service构造函数
以metadata形式保存到this.metadataMap
中。
@inject
会将依赖注入操作的对象
、目标
、行为
以 object 形式 push 进 handlers 待处理数组。
构造函数
与静态类型
及属性
间的映射关系。{
object: target, // 当前等待挂载的类的原型对象
propertyName: propertyName, // 目标属性值
index: index,
value: function (containerInstance) { // 行为
var identifier = Reflect.getMetadata('design:type', target, propertyName)
return containerInstance.get(identifier);
}
}
@inject
将该对象 push 进一个等待执行的 handlers 待处理数组里,当需要用到对应 service 时执行 value函数 并修改 propertyName。
if (handler.propertyName) {
instance[handler.propertyName] = handler.value(this);
}
typedi
中的实例化操作不会立即执行, 而是在一个handlers
待处理数组,等待Container.get(B)
,先对B进行实例化,然后从handlers
待处理数组取出对应的value函数
并执行修改实例化对象的属性值,这样不会影响Class B 自身metadata.value
(typedi 的单例服务特性)。相关问题案例可查看: https://stackoverflow.com/questions/55684776/typedi-inject-doesnt-work-but-container-get-does
new B().say() // 将会输出sayHello is undefined
Container.get(B).say() // hello word
此处代码依赖TS
,不支持JS环境
interface Handles {
target: any
key: string,
value: any
}
interface Con {
handles: Handles [] // handlers待处理数组
services: any[] // service数组,保存已实例化的对象
get<T>(service: new () => T) : T // 依赖注入并返回实例化对象
findService<T>(service: new () => T) : T // 检查缓存
has<T>(service: new () => T) : boolean // 判断服务是否已经注册
}
var container: Con = {
handles: [], // handlers待处理数组
services: [], // service数组,保存已实例化的对象
get(service){
let res: any = this.findService(service)
if(res){
return res
}
res = new service()
this.services.push(res)
this.handles.forEach(handle=>{
if(handle.target !== service.prototype){
return
}
res[handle.key] = handle.value
})
return res
},
findService(service){
return this.services.find(instance => instance instanceof service)
},
// service是否已被注册
has(service){
return !!this.findService(service)
}
}
function Inject(target: any, key: string){
const service = Reflect.getMetadata('design:type',target,key)
// 将实例化赋值操作缓存到handles数组
container.handles.push({
target,
key,
value: new service()
})
// target[key] = new (Reflect.getMetadata('design:type',target,key))()
}
class A {
sayA(name: string){
console.log('i am '+ name)
}
}
class B {
@Inject
a: A
sayB(name: string){
this.a.sayA(name)
}
}
class C{
@Inject
c: A
sayC(name: string){
this.c.sayA(name)
}
}
// new B().sayB(). // Cannot read property 'sayA' of undefined
container.get(B).sayB('B')
container.get(C).sayC('C')
bind()
来修改this指向
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
我们来看1个demo
var module = {
x: 42,
getX: function() {
return this.x;
}
}
module.getX.prototype.sayHi = function(name){
return 'I am ' + name
}
var unboundGetX = module.getX
console.log(module.getX(),new module.getX().sayHi('module.getX')) // 42 "I am module.getX"
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX')) // undefined "I am unboundGetX"
unboundGetX()的this指向window,所以输出this.x为undefined
,现在,我们使用bind来修改unboundGetX()的this指向
var boundGetx = module.getX.bind(module) //this指向变量module
console.log(boundGetx(),new boundGetx().sayHi('boundGetx')) //42 "I am boundGetx"
可以看到bind()返回的函数,其this指向已经指向了module,this.x为=42
不了解this作用域的同学可以先看一下这篇文章【Javascript】深入理解this作用域问题并探究new/let/var/const对this作用域的影响
现在,我们来尝试实现一个newbind(),使得boundGetx的this指向变量module
var boundGetx = module.getX.newbind(module)
Function.prototype.newbind = function(){
const oThis = Array.prototype.shift.call(arguments) // 获取参数module
const params = Array.prototype.slice.call(arguments)
oThis.fn = this //函数function
const res = function(){
return oThis.fn.apply(
oThis,params.concat(Array.prototype.slice.call(arguments))
) //修改函数function的this指向并执行
// return oThis.fn(...params.concat(Array.prototype.slice.call(arguments))) //一样
}
if(this.prototype)
{
const fn = function(){}
fn.prototype = this.prototype //维护原型关系
res.prototype === new fn()
}
return res
}
此时newbind已经返回了1个新的函数
var unboundGetX = module.getX
var boundGetx = module.getX.newbind(module)
console.log(module.getX(),new module.getX().sayHi('module.getX')) // 42 "I am module.getX"
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX')) // undefined "I am unboundGetX"
console.log(boundGetx(),new boundGetx().sayHi('boundGetx')) //42 "I am boundGetx"
可以发现unboundGetX()仍然输出undefined,但是boundGetx()其this已经指向了module,this.x为42
res.prototype = this.prototype
存在问题,由于对象属于引用类型,这样的写法会导致module.getX.prototype会随着boundGetx.prototype
改变,我们尝试修改一下boundGetx.prototypeboundGetx.prototype.sayHi = function(name){
return 'changed,I am ' + name
}
console.log(new boundGetx().sayHi('boundGetx')) // changed,I am boundGetx
console.log(new module.getX().sayHi('module.getX')) //changed,I am module.getX
可以发现
boundGetx()
与module.getX()
的sayHi()
都被改变了,我们需要将代码改为
res.prototype = new this() //维护原型关系
// or
// const fn = function(){}
// fn.prototype = this.prototype
// res.prototype = new fn()
bind返回的是1个函数,call,apply返回值为undefined
Function.prototype.call = function(obj,...args){
obj.fn = this
obj.fn(...args)
delete obj.fn
}
Function.prototype.newbind = function(){
const oThis = Array.prototype.shift.call(arguments) // 第1个参数
const params = Array.prototype.slice.call(arguments)
oThis.fn = this //函数function
const res = function(){
return oThis.fn.apply(
oThis,params.concat(Array.prototype.slice.call(arguments))
) //修改函数function的this指向并执行
}
if(this.prototype)
{
res.prototype = new this() //维护原型关系
}
return res
}
var module = {
x: 42,
getX: function(val) {
return val ? val : this.x
}
}
module.getX.prototype.sayHi = function(name){
return 'I am ' + name
}
var unboundGetX = module.getX;
//使用bind修改unboundGetX的this指向
var boundGetx = module.getX.newbind(module)
console.log(module.getX(),new module.getX().sayHi('module.getX')) //this指向变量module
console.log(unboundGetX(),new unboundGetX().sayHi('unboundGetX')) //this指向window,所以为undefined
console.log(boundGetx(),new boundGetx().sayHi('boundGetx')) //this指向变量modul
console.error('---------sayHi was changed------')
boundGetx.prototype.sayHi = function(name){
return 'changed,I am ' + name
}
console.log(new boundGetx().sayHi('boundGetx'))
console.log(new module.getX().sayHi('module.getX'))
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
function reConstructBinaryTree(pre, vin)
{
var result = null
if(pre.length>1)
{
result = {
val:pre[0]
}
const index = vin.indexOf(pre[0])
var vinLeft = vin.slice(0,index)
var vinRight = vin.slice(index+1)
pre.shift()
var preLeft = pre.slice(0,vinLeft.length)
var preRight = pre.slice(vinLeft.length)
result = {
left: reConstructBinaryTree(preLeft,vinLeft),
right:reConstructBinaryTree(preRight,vinRight),
val:result.val
}
}
else if(pre.length === 1)
{
result = {
left:null,
right:null,
val:pre[0]
}
}
return result
}
预加载意味着会发更多的请求,并且很可能用户最终也不会使用,这些流量会造成更大的服务器压力,换句话就是你会为此花更多钱。
好处就是用户体验会好点,对于网速很快的用户来说提升只是一点点,对于网速稍慢的用户来说提升会更明显。
关于 React.lazy 和动态 import 的一些测试
参考 Lazy loading (and preloading) components in React 16.6 | by Rodrigo Pombo | HackerNoon.com | Medium 做了一些测试。
import { useState } from 'react'
// 这种是把 Desc 和当前页面其它代码打包到一个文件了
// 访问这个页面时就会下载这个文件然后显示界面
// 点击按钮后 Desc 会马上显示
import Desc from './Desc'
function App() {
const [showDesc, setShowDesc] = useState(false)
return (
<div>
<button onClick={() => setShowDesc(true)}>显示描述</button>
{showDesc && <Desc />}
</div>
)
}
export default App
import { useState, lazy, Suspense } from 'react'
// 这种是把 Desc 单独打包了,但是在点击按钮的时候才会去下载文件
// 所以会先显示 loading... 然后文件下载完后显示 Desc
const Desc = lazy(() => import('./Desc'))
function App() {
const [showDesc, setShowDesc] = useState(false)
return (
<div>
<button onClick={() => setShowDesc(true)}>显示描述</button>
<Suspense fallback={<div>loading...</div>}>
{showDesc && <Desc />}
</Suspense>
</div>
)
}
export default App
import { useState, lazy, Suspense } from 'react'
// 会在加载这个页面时就去下载 Desc 文件
// 当点击按钮的时候一般 Desc 文件已经下载完了,所以会直接显示
const descPromise = import('./Desc')
const Desc = lazy(() => descPromise)
function App() {
const [showDesc, setShowDesc] = useState(false)
return (
<div>
<button onClick={() => setShowDesc(true)}>显示描述</button>
<Suspense fallback={<div>loading...</div>}>
{showDesc && <Desc />}
</Suspense>
</div>
)
}
export default App
import { useState, lazy, Suspense } from 'react'
const importDesc = () => import('./Desc')
const Desc = lazy(importDesc)
function App() {
const [showDesc, setShowDesc] = useState(false)
// 在鼠标移入时再去下载 Desc
const onMouseEnter = () => {
importDesc()
}
return (
<div>
<button onClick={() => setShowDesc(true)} onMouseEnter={onMouseEnter}>
显示描述
</button>
<Suspense fallback={<div>loading...</div>}>
{showDesc && <Desc />}
</Suspense>
</div>
)
}
export default App
import { useState, lazy, Suspense } from 'react'
function lazyWithPreload(importFunc) {
const Component = lazy(importFunc)
// 加上一个 preload 属性,方便调用
Component.preload = importFunc
return Component
}
const Desc = lazyWithPreload(() => import('./Desc'))
function App() {
const [showDesc, setShowDesc] = useState(false)
// 在鼠标移入时再去下载 Desc
const onMouseEnter = () => {
Desc.preload()
}
return (
<div>
<button onClick={() => setShowDesc(true)} onMouseEnter={onMouseEnter}>
显示描述
</button>
<Suspense fallback={<div>loading...</div>}>
{showDesc && <Desc />}
</Suspense>
</div>
)
}
export default App
这种情况的话,是会在需要展示组件 B 的时候才去下载组件 B 的代码,因为你在鼠标移上去的时候只预加载了组件 A 。
// App.js
import { useState, Suspense } from 'react'
import lazyWithPreload from './lazyWithPreload'
const Desc = lazyWithPreload(() => import('./Desc'))
function App() {
const [showDesc, setShowDesc] = useState(false)
// 在鼠标移入时再去下载 Desc
const onMouseEnter = () => {
Desc.preload()
}
return (
<div>
<button onClick={() => setShowDesc(true)} onMouseEnter={onMouseEnter}>
显示描述
</button>
<Suspense fallback={<div>loading...</div>}>
{showDesc && <Desc />}
</Suspense>
</div>
)
}
export default App
// Desc.js
import { Suspense } from 'react'
import lazyWithPreload from './lazyWithPreload'
const SubDesc = lazyWithPreload(() => import('./SubDesc'))
function Desc() {
return (
<div>
<div>这是一段描述,假装这是一个很复杂的组件。</div>
{/* 这里会先显示 loading 然后再显示 SubDesc 内容 */}
<Suspense fallback={<div>loading...</div>}>
<SubDesc />
</Suspense>
</div>
)
}
export default Desc
react-router 推荐的 code splitting 库是 loadable-components ,这个库是支持 预加载 功能的。
import { useState } from 'react'
import loadable from '@loadable/component'
const Desc = loadable(() => import('./Desc'), {
fallback: <div>loading...</div>,
})
function App() {
const [showDesc, setShowDesc] = useState(false)
// 在鼠标移入时再去下载 Desc
const onMouseEnter = () => {
Desc.preload()
}
return (
<div>
<button onClick={() => setShowDesc(true)} onMouseEnter={onMouseEnter}>
显示描述
</button>
{showDesc && <Desc />}
</div>
)
}
export default App
上面说的都是页面中某个次要内容的代码分割和预加载,下面来进入正题,按路由切割代码后,能在当前路由预加载其它路由的代码吗?
我们可以把 react-router-dom 库的 Link 组件再封装一层来实现预加载。
把路径以及对应的组件定义为数组,方便我们封装的 LinkWithPreload 去遍历数组找到组件,然后去执行组件的 preload 就好了。
// App.js
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import loadable from '@loadable/component'
export const routes = [
{ path: '/', Component: loadable(() => import('./List')) },
{ path: '/detail/:id', Component: loadable(() => import('./Detail')) },
]
function App() {
return (
<Router>
<Switch>
{routes.map((item) => {
const { path, Component } = item
return (
<Route key={path} exact path={path}>
<Component />
</Route>
)
})}
</Switch>
</Router>
)
}
export default App
// LinkWithPreload.js
import { Link, matchPath } from 'react-router-dom'
import { routes } from './App'
function LinkWithPreload(props) {
const { to } = props
const onMouseEnter = () => {
const find = routes.find((item) => {
const { path } = item
const match = matchPath(to, {
path,
exact: true,
})
return Boolean(match)
})
if (find) {
find.Component.preload()
}
}
return <Link {...props} onMouseEnter={onMouseEnter} />
}
export default LinkWithPreload
在需要使用 Link 的地方就 <LinkWithPreload to='/xxx'>
查看</LinkWithPreload>
就可以了。
这里是鼠标移上去的时候预加载,如果你想,也可以改为使用 Intersection Observer ,判断 Link 组件进入可见区域时就预加载。
在什么时候进行预加载也是一种权衡,尽早预加载可以保证跳转页面的时候资源已经加载好了,但是会不可避免造成一些不必要的加载,因为你不知道用户会访问哪些页面。(当然如果你想你可以结合统计工具的数据,只对用户经常访问的页面做预加载来增加命中率 hhh)
直接使用 quicklink 库
https://github.com/GoogleChromeLabs/quicklink
它是监听的 Link 进入可视区域就进行预加载。
在 create-react-app 中使用:https://github.com/GoogleChromeLabs/quicklink/blob/master/demos/spa/README.md
使用这个库会需要配置 webpack-route-manifest 插件,这个插件会生成下面这个东西,然后就可以根据路由去预加载了。
quicklink 的相关实现见 https://github.com/GoogleChromeLabs/quicklink/blob/master/src/react-chunks.js#L61 和 https://github.com/GoogleChromeLabs/quicklink/blob/master/src/index.mjs#L60 。
它是等路由组件进入可视区域后,然后拿到路由组件中所有 a 标签,然后再对应去做预加载。
timeoutFn(() => {
// Find all links & Connect them to IO if allowed
(options.el || document).querySelectorAll('a').forEach(link => {
// If the anchor matches a permitted origin
// ~> A `[]` or `true` means everything is allowed
if (!allowed.length || allowed.includes(link.hostname)) {
// If there are any filters, the link must not match any of them
isIgnored(link, ignores) || observer.observe(link);
}
});
}, {
timeout: options.timeout || 2000,
});
为了减少加载一个页面时需要下载的代码,我们可以:
按路由切割代码,并预加载其它路由代码(Link hover 时或者进入可视区域时),这样跳转时下个页面加载会更快;
对弹窗、Tab 等当前不需要展示或者低优先级内容做代码切割,并预加载。
代码切割是为了减少必要代码的体积,预加载是为了低优先级组件代码在需要时也能尽快展示。
preload 和 prefetch 是 HTML link 标签的一个用法,用于提示浏览器去提前下载资源。preload 是希望提前下载当前页面的资源。prefetch 是希望提前下载其它页面的资源。
动态 import 是 JS 的一个语法,Webpack 打包时会把动态 import 的部分打包为单独的文件。可以用于实现按路由切割代码,或者把弹窗等低优先级界面代码从主界面代码切割出去,这样来加快主界面的加载速度。
当你希望预加载资源时,是使用 link 的 prefetch 还是说动态 import ,其实结果都是一样的,可以结合项目用的库来看怎么实现简单怎么来。
# -*- coding: utf-8 -*- #解决编码问题
import urllib
import urllib2
import re
import os
import time
page = 1
url = 'http://www.qiushibaike.com/text/page/4/?s=4970196' #爬取的目标网站
user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)'
headers = { 'User-Agent' : user_agent }
try:
request = urllib2.Request(url,headers = headers)
response = urllib2.urlopen(request)
# print response.read()
content = response.read().decode('utf-8') #解决编码问题
pattern = re.compile(r'<div.*?class="content".*?<span>(.*?)</span>.*?</div>',re.S) #第一个参数是匹配要爬取的内容,这里使用正则去匹配
items = re.findall(pattern,content)
f=open(r'.\article.txt','ab') #txt文件路径
nowTimes = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) #获取当前时间
f.write('时间:{}\n\n'.format(nowTimes),); #txt文件中写入时间
for i in items:
i.encode('utf-8')
agent_info = u''.join(i).encode('utf-8').strip()
f.writelines('段子:%s%s\n'%(str(agent_info),os.linesep)) #分行存入
# f.write('%s'%str(agent_info))
f.close()
# print items
except urllib2.URLError, e:
if hasattr(e,"code"):
print e.code
if hasattr(e,"reason"):
print e.reason
* */1 * * * /usr/bin/python /home/dengwen/desktop/echo2.py
【Javascript】深入理解async/await的实现,Generator+Promise = Async/Await
【Javascript】深入理解this作用域问题以及new运算符对this作用域的影响
【Javascript】手写运算符new创建实例并实现js继承
index.js (createStore)
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import Header from './Header'
import Content from './Content'
import './index.css'
import { Provider } from './connect'
function createStore (reducer) {
let state = null
const listeners = []
const subscribe = (listener) => listeners.push(listener)
const getState = () => state
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach((listener) => listener())
}
dispatch({}) // 初始化 state
return { getState, dispatch, subscribe }
}
const themeReducer = (state, action) => {
if (!state) return {
themeColor: 'red'
}
switch (action.type) {
case 'CHANGE_COLOR':
return { ...state, themeColor: action.themeColor }
default:
return state
}
}
const store = createStore(themeReducer)
class Index extends Component {
// static childContextTypes = {
// store: PropTypes.object
// }
// getChildContext () {
// return { store }
// }
render () {
return (
<div>
<Header />
<Content />
</div>
)
}
}
ReactDOM.render(
<Provider store={store}>
<Index />
</Provider>,
document.getElementById('title')
)
connect.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export const connect = (mapStateToProps,mapDispatchToProps) => (WrappedComponent) => {
class Connect extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = {
allProps: {}
}
}
componentWillMount () {
const { store } = this.context
this._updateProps()
store.subscribe(() => this._updateProps())
}
_updateProps () {
const { store } = this.context
let stateProps = mapStateToProps
? mapStateToProps(store.getState(), this.props)
: {} // 防止 mapStateToProps 没有传入
let dispatchProps = mapDispatchToProps
? mapDispatchToProps(store.dispatch, this.props)
: {} // 防止 mapDispatchToProps 没有传入
this.setState({
allProps: {
...stateProps,
...dispatchProps,
...this.props
}
})
}
render () {
const { store } = this.context
let stateProps = mapStateToProps(store.getState())
// {...stateProps} 意思是把这个对象里面的属性全部通过 `props` 方式传递进去
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect
}
export class Provider extends Component {
static propTypes = {
store: PropTypes.object,
children: PropTypes.any
}
static childContextTypes = {
store: PropTypes.object
}
getChildContext () {
return {
store: this.props.store
}
}
render () {
return (
<div>{this.props.children}</div>
)
}
}
themeswitch.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from './connect'
class ThemeSwitch extends Component {
static contextTypes = {
store: PropTypes.object
}
constructor () {
super()
this.state = { themeColor: '' }
}
// componentWillMount () {
// const { store } = this.context
// this._updateThemeColor()
// store.subscribe(() => this._updateThemeColor())
// }
// _updateThemeColor () {
// const { store } = this.context
// const state = store.getState()
// this.setState({ themeColor: state.themeColor })
// }
handleSwitchColor (color) {
const { store } = this.context
if (this.props.onSwitchColor) {
this.props.onSwitchColor(color)
}
}
render () {
return (
<div>
<button onClick={this.handleSwitchColor.bind(this, 'red')} style={{color:this.state.themeColor}}>Red</button>
<button onClick={this.handleSwitchColor.bind(this, 'blue')} >Blue</button>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
themeColor: state.themeColor
}
}
const mapDispatchToProps = (dispatch) => {
return {
onSwitchColor: (color) => {
dispatch({ type: 'CHANGE_COLOR', themeColor: color })
}
}
}
ThemeSwitch = connect(mapStateToProps, mapDispatchToProps)(ThemeSwitch)
export default ThemeSwitch
组内每周都会有分享总结会,昨晚分享的课题是Event Loop
,我很积极且有点自信地答了几道题,结果被虐的体无完肤
,果然有些东西不经常回顾就容易忘,于是花一晚上挑灯夜战
重新做了一份有关event loop
的知识总结,在此分享给大家,希望对各位看官有所帮助,看完有收获的同学还请积极点赞,讲的不对的地方,望指出,我会及时修正,谢谢~
浏览器端的event loop基于javascript中的
堆/栈/任务队列
,任务队列又分为宏任务
与微任务
相同作用域下
,会先执行微任务,再执行宏任务宏任务处于微任务作用域下
,会先执行微任务,再执行微任务中的宏任务微任务处于宏任务作用域下时
,会先执行宏任务队列中的任务,然后再执行微任务队列中的任 务,在当前的微任务队列没有执行完成时,是不会执行下一个宏任务的。本文主要讲解的还是Node,对于浏览器端event loop的具体分析及证明可以查看这篇文章探究javascript中的堆/栈/任务队列与并发模型 event loop的关系
6个阶段
,每个阶段都有1个任务队列,微任务在事件循环的各个阶段之间执行一个timer
指定一个下限时间而不是准确时间,在达到这个下限时间后执行回调。在指定的时间过后,timers
会尽早的执行回调,但是系统调度或者其他回调的执行可能会延迟它们。
从技术上来说,
poll
阶段控制timers
什么时候执行,而执行的具体位置在timers
。
下限的时间有一个范围:[1, 2147483647],如果设定的时间不在这个范围,将被设置为1。
poll阶段有两个主要的功能:
1. 是执行下限时间已经达到的timers的回调
2. 是处理poll队列里的事件。
注:Node很多API都是基于
事件订阅
完成的,这些API的回调应该都在poll
阶段完成。
当事件循环进入poll阶段:
poll队列不为空的时候,事件循环肯定是先遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
poll队列为空的时候,这里有两种情况。
如果代码已经被setImmediate()设定了回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调。
如果代码没有被设定setImmediate()设定回调:
这个阶段允许在poll阶段结束后立即执行回调。如果poll阶段空闲,并且有被setImmediate()设定的回调,那么事件循环直接跳到check执行而不是阻塞在poll阶段等待回调被加入。
注:事件循环运行到
check
阶段的时候,setImmediate()
具有最高优先级,只要poll
队列为空,代码被setImmediate()
,无论是否有timers
达到下限时间,setImmediate()
的代码都先执行
如果一个socket
或handle
被突然关掉(比如socket.destroy()
),close事件将在这个阶段被触发,否则将通过process.nextTick()
触发。
setTimeout(()=>{
console.log('timer')
})
setImmediate(()=>{
console.log('immediate')
})
首先进入的是timers
阶段,如果我们的机器性能一般,那么进入timers阶段,一毫秒已经过去了,那么setTimeout的回调会首先执行。
如果没有到一毫秒,那么在timers阶段的时候,下限时间没到,setTimeout回调不执行,事件循环来到了poll阶段,这个时候队列为空,此时有代码被setImmediate(),所以进入check阶段,先执行了setImmediate()的回调函数,之后在下一个事件循环再执行setTimemout的回调函数。
而我们在执行代码的时候,进入timers的时间延迟其实是随机的,并不是确定的,所以会出现两个函数执行顺序随机的情况。
fs.readFile('./main.js',()=>{
setTimeout(()=>{
console.log('timer')
})
setImmediate(()=>{
console.log('immediate')
})
})
可以发现setImmediate
永远先于setTimeout
执行
fs.readFile
的回调是在poll
阶段执行的,回调执行完毕后poll阶段的队列为空,于是进入check
阶段,执行setImmediate
回调,而setTimeout
的回调需要等到下一个事件循环的timers
阶段才去执行
对于这两个,我们可以把它们理解成一个微任务。也就是说,它其实不属于事件循环的一部分。
那么他们是在什么时候执行呢?
不管在什么地方调用,他们都会在其所处的事件循环最后,在事件循环进入下一个循环的阶段前执行,但是
nextTick
优先于promise
执行。
process.nextTick()
会在各个事件阶段之间执行,一旦执行,要直到nextTick
队列被清空,才会进入到下一个事件阶段,所以如果递归调用process.nextTick()
/promise
,会导致出现I/O starving(饥饿)的问题,推荐使用setImmediate()
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(()=>{
console.log('reslove1')
})
})
setTimeout(() => {
console.log('timeout2')
Promise.resolve().then(()=>{
console.log('reslove2')
})
})
setImmediate(()=>{
console.log('setImmediate1')
})
setImmediate(()=>{
console.log('setImmediate2')
})
setTimeout(() => {
console.log('timeout1')
Promise.resolve().then(()=>{
console.log('reslove1')
})
})
setTimeout(() => {
console.log('timeout2')
Promise.resolve().then(()=>{
console.log('reslove2')
})
})
setImmediate(()=>{
console.log('setImmediate1')
})
setImmediate(()=>{
console.log('setImmediate2')
})
Promise.resolve('resolve3').then((data)=>{
console.log(data)
})
<canvas class="poster" canvas-id="poster" style="width:300rpx;height:300rpx;"></canvas>
initCanvas(){
var ctx = wx.createCanvasContext('poster')
ctx.drawImage('https://image.watsons.com.cn//upload/0851366c.png', 0, 0, 241, 368) //画海报
ctx.drawImage(qrCodeUrl, 70, 240, 120,120) //画二维码
ctx.draw()
this.save() //生成微信临时模板文件path
}
save(){
var self = this;
setTimeout(()=>{
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 241,
height: 368,
destWidth: 241,
destHeight: 368,
canvasId: 'poster',
success: function(res) {
console.log('save',res.tempFilePath)
self.saveUrl = res.tempFilePath //保存临时模板文件路径
},
fail:function(res){
// wx.showToast({
// title:'网络繁忙',
// icon:'none'
// })
return
}
})
},500)
}
saveImageToPhotosAlbum(){
console.log(this.saveUrl)
wx.showLoading({
title:'下载中...'
})
var self = this;
wx.downloadFile({
url: self.saveUrl,
success: function(res) {
// 只要服务器有响应数据,就会把响应内容写入文件并进入 success 回调,业务需要自行判断是否下载到了想要的内容
if (res.statusCode === 200) {
wx.saveImageToPhotosAlbum({
filePath:res.tempFilePath,
success(result) {
wx.hideLoading()
wx.showToast({
title:'已保存至相册',
icon:'none',
})
},
fail(result){
wx.hideLoading()
wx.showToast({
title:'下载失败',
icon:'none',
})
}
})
}
},
fail(result){
wx.hideLoading()
wx.showToast({
title:'下载失败',
icon:'none',
})
}
})
},
<canvas class="poster" canvas-id="poster" style="width:300rpx;height:300rpx;"></canvas>
<canvas class="generate" canvas-id="generate" style="width:1500rpx;height:2296rpx;"></canvas>
var ctx = wx.createCanvasContext('generate')
ctx.drawImage('https://image.watsons.com.cn//upload/0851366c.png', 0, 0, 750, 1148) //画海报
ctx.drawImage(QrCodeUrl, 300, 886, 150,150) //画二维码
ctx.draw()
this.save()
save(){ //保存二维码
var self = this;
setTimeout(()=>{
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 750,
height: 1148,
destWidth: 750,
destHeight: 1148,
canvasId: 'generate',
success: function(res) {
this.tempFilePath = res.tempFilePath
console.log('save',res.tempFilePath)
var ctx = wx.createCanvasContext('poster') //加载展示用的海报
ctx.drawImage(res.tempFilePath, 0, 0, 241, 368)
ctx.draw()
self.saveUrl = res.tempFilePath //保存临时模板文件路径
},
fail:function(res){
return
}
})
},500)
}
代码跟之前描述的一样
【Javascript】深入理解async/await的实现,Generator+Promise = Async/Await
【Javascript】深入理解this作用域问题以及new运算符对this作用域的影响
【Javascript】手写运算符new创建实例并实现js继承
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
function replaceSpace(str)
{
// write code here
return str.replace(/ /g,'%20')
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.