- 👋 Hi, I’m @ASkyBig
- 👀 I’m interested in basketball and dota1.
- 🌱 I’m currently learning low code.
- 📫 How to reach me empzd2s4MTg=
askybig / blog Goto Github PK
View Code? Open in Web Editor NEWblog
blog
js中是没有类的概念的,ES6中的class其实也是函数实现的,是一个语法糖。js之父布兰登・艾克只花了10天就完成了js这门语言,可能就是想要一个简单的语法。
传统面向对象的语言
我们以 Java 为例:
class Person {
String name;
public Person (String name) {
this.name = name;
}
public void sayHi() {
System.out.println("hi");
}
}
可以看到,Person 类里面有一个 name 属性,还有一个 Person 构造函数(这个构造函数不是必须的,如果不写有一个默认的构造函数),还有一个 sayHi 方法。这样我们新建一个对象的时候,就会自动调用构造函数,然后就有 sayHi 方法了,还有 name 属性。
JavaScript
由于js没有类的概念,所以我们只能通过函数实现。js 里面的函数是可以当做构造函数使用的,前提是在函数前面加上 new 操作符。
function Person(name) {
this.name = name;
}
const p1 = new Person('jack');
可以看到,p1 已经有了 name 这个属性了。(眼尖的同学可能已经发现,p1 实例还有一个 proto 属性,这个我们稍后再说)
现在我们已经有了带属性的实例了,还有一个 sayHi 方法,这个该怎么办呢?不慌,js 已经给我们想好了,给 Person函数添加一个 prototype 属性(函数也是对象,当然可以添加属性),我们把 sayHi 方法挂载在 Person.prototype 对象上。当我们调用 p1.sayHi() 的时候,js 发现 p1 上没有sayHi这个方法,就会去它的__proto__属性上去找,知识点来了:实例的__proto__属性指向的就是构造函数的prototype属性。所以就可以调用sayHi方法了:
当然,如果我们调用sayHello方法的话是没有用的,因为prototype上并没有定义这个方法。
当我们调用 p1.sayHello() 时,首先会去看p1这个对象有没有sayHello这个方法,没有。那么就去它的构造函数的prototype上找,也没有。那么继续去它的构造函数的 prototype 的构造函数的prototype上找,即Person.prototype的构造函数的prototype上找。参考上面提到的知识点,所以就是去Person.prototype.proto(即 Object.prototype)上找。如果Object.prototype上也没有,再往上就是null了(即Object.prototype.proto === null)。此时,p1.sayHello就是undefined,调用的话就会报sayHello is not a function 错误了。
我们可以试验一下,给Object.prototype加上sayHello这个方法,看看p1能不能调用:
可以看到,hello 已经成功调用了!如果上面的你看懂了,那你就理解什么是原型链了。
至此,我们已经知道 new 大概的作用了:
首先,它会新建一个对象
然后,对新对象执行 prototype 连接。(这一步是为了调用构造函数的方法。)
然后,将新对象绑定到函数调用的 this。(关于this,篇幅原因就不介绍了。)
最后,如果函数没有返回其他对象,new 表达式中的函数调用会自动返回这个新对象。
上面的步骤是从 《you don't know js》中摘的。我们来看下最后一句,这句有点意思:如果没有返回其他对象,就自动返回新对象。
那么问题来了,我们偏要返回其它对象呢?我们试试看:
1、如果构造函数不返回对象(这句有两层意思哈,不返回对象和不返回对象),则返回默认新建的对象
2、如果构造函数返回了对象(null不算血统纯正的对象),那构造函数啥也不是,没用
知道了 new 的原理,手写下扎实基础(还不是为了面试:D)
function _new (fn, ...args) {
// 新建一个对象
const obj = {};
// 执行构造函数
const res = fn.call(obj, ...args);
// 将__proto__指向构造函数的prototype
obj.__proto__ = fn.prototype;
// 看看返回值是不是叛徒(null长得像叛徒,但不是叛徒)
const isObject = Object.prototype.toString.call(res) === "[object Object]" && res !== null;
const isFunction = Object.prototype.toString.call(res) === "[object Function]";
// 是叛徒
if (isObject || isFunction) return res;
// 不是叛徒
return obj;
}
1 、状态逻辑复用困难
2、 复杂难以维护
3、this 指向麻烦
对于一个按钮点击count加一,分别用 class
和 function
形式写一遍:
class
形式 codesandbox
import React from "react";
export default class App extends React.Component {
state = {
count: 0
};
render() {
const { count } = this.state;
return (
<button onClick={() => this.setState({ count: count + 1 })}>
Count{count}
</button>
);
}
}
function
形式codesandbox
import React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count{count}</button>;
}
可以看到,function
形式的组件比 class
形式的组件简洁很多。
count
为0, setCount(0)
不会触发重新渲染 codesandboximport React, { useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(0)}>Click({count})</button>;
}
useState
参数作为函数返回值,只会执行一次codesandboximport React, { useState } from "react";
export default function App(props) {
const [count, setCount] = useState(() => {
console.log("get props");
return props.defaultCount || 0;
});
return <button onClick={() => setCount(count + 1)}>Count{count}</button>;
}
class
形式的demo
codesandboximport React from "react";
class Count extends React.Component {
state = {
count: 0,
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
};
onResize = () => {
this.setState({
size: {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
}
});
};
// resize 的监听和解绑分散在不同生命周期
componentDidMount() {
// title 和 resize 耦合在一起
document.title = this.state.count;
window.addEventListener("resize", this.onResize, false);
}
componentWillUnmount() {
console.log("remove listener");
window.removeEventListener("resize", this.onResize, false);
}
componentDidUpdate() {
document.title = this.state.count;
}
render() {
console.log("render");
const { count, size } = this.state;
return (
<button onClick={() => this.setState({ count: count + 1 })}>
Count({count}) Size: {size.width}*{size.height}
</button>
);
}
}
export default class App extends React.Component {
state = {
id: 1
};
render() {
const { id } = this.state;
return (
<div>
{id & 1 ? <button>111</button> : <Count />}
<button onClick={() => this.setState({ id: id + 1 })}>appClick</button>
</div>
);
}
}
function
形式的 demo
codesandboximport React, { useState, useEffect } from "react";
export default function App() {
const [id, setId] = useState(0);
return (
<div>
{id & 1 ? <button>111</button> : <Count />}
<button onClick={() => setId(id + 1)}>app click</button>
</div>
);
}
function Count() {
console.log("render");
const [count, setCount] = useState(0);
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
const onResize = () => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
});
};
// 关注点分离,title 和 resize 各自处理各自逻辑
useEffect(() => {
console.log("title");
document.title = count;
});
useEffect(() => {
console.log("resize");
window.addEventListener("resize", onResize, false);
return () => {
console.log("remove listener");
window.removeEventListener("resize", onResize, false);
};
}, []);
return (
<button onClick={() => setCount(count + 1)}>
Count({count}) Size({size.width}* {size.height})
</button>
);
}
useEffect 的一个错误的示例 codesandbox,本意是想让 count
累加到10,然后自动停止。
import React, { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
let timer;
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count > 10) {
clearInterval(timer);
}
});
return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}
需要像下面这样:codesandbox
import React, { useState, useEffect } from "react";
export default function App() {
const [count, setCount] = useState(0);
let timer;
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count > 10) {
clearInterval(timer);
}
});
return <button onClick={() => setCount(count + 1)}>Count({count})</button>;
}
还有一个问题,如果上面代码中
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
变成下面这样,会导致什么问题呢?
useEffect(() => {
timer = setInterval(() => {
console.log("setInterval", count);
setCount( count + 1);
}, 1000);
}, [count]);
目前理解不是很深刻
有记忆功能,点击 age
的时候不会改变 count
,所以就不会输出 calc count
。codesandbox
import React, { useState, useMemo } from "react";
const Counter = function(props) {
console.log("Counter");
return <div>{props.count}</div>;
};
export default function App() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(20);
const dobule = useMemo(() => {
console.log("calc count");
if (count > 3) {
return count * 2;
}
}, [count]);
return (
<div>
<Counter count={dobule} />
<button onClick={() => setAge(age + 1)}>age: {age}</button>
<button onClick={() => setCount(count + 1)}>
Count({count}) double({dobule})
</button>
</div>
);
}
不过有一个问题, Counter
组件会一直渲染,其实该组件只依赖于 count
,不应该点击 age
时重新渲染它。可以用 memo
包裹一下就好了,这样你点击 age
就不会重渲染 Counter
。 codesandbox
import React, { useState, useMemo, memo } from "react";
const Counter = memo(props => {
console.log("Counter");
return <div>Counter({props.count})</div>;
});
export default function App() {
const [count, setCount] = useState(0);
const [age, setAge] = useState(20);
const dobule = useMemo(() => {
console.log("calc count");
if (count > 3) {
return count * 2;
}
}, [count]);
return (
<div>
<Counter count={dobule} />
<button onClick={() => setAge(age + 1)}>age: {age}</button>
<button onClick={() => setCount(count + 1)}>
Count({count}) double({dobule})
</button>
</div>
);
}
把上面的例子用自定义hook重写:codesandbox
import React, { useState, useEffect, useRef } from "react";
function useCount1(count) {
return <h1>{count}</h1>
}
function useCount(defaultCount) {
const [count, setCount] = useState(defaultCount);
let timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
console.log("setInterval", count);
setCount(count => count + 1);
}, 1000);
}, []);
useEffect(() => {
if (count >= 10) {
clearInterval(timer.current);
}
});
return [count, setCount];
}
export default function App() {
const [count, setCount] = useCount(0);
const Count1 = useCount1(count)
return (
<div>
{Count1}
<button onClick={() => setCount(count + 1)}>Count({count})</button>;
</div>
)
}
最近做的中台项目里面网络请求混乱,有页面内的
fetch
、axios
,还有service文件夹下专门处理请求,需要统一
例如,项目目前的 axios
的请求方式:
setLoading(true)
axios
.get(url, {
params: {
...
}
})
.then(res => {
if (res?.code === 0) {
const {data} = res
setDataSource(data)
} else {
message.error(res.message || '请求失败')
}
})
.catch(err => {
message.error(err.message || '请求失败')
})
.then(() => {
setLoading(false)
})
代码中值得称道的地方就是在 catch
下面的 then
里面统一移除 loading
效果,这样避免了分别在 success
和 fail
下隐藏 loading
。很多人不知道 catch
下面还可以继续 then
。
但是问题是,我们的请求很多,每次都这样写,需要每一次都判断 code
,每一次都添加 catch
。并且,有些场景是需要嵌套请求,例如某个页面需要初始化数据,然后根据初始化数据再进行请求,那就需要在 then
里面再复制一遍请求。
代码嵌套变深,不利于维护,所以需要统一封装请求。
可以这样封装:
const $fetch = async (url, type, params) => {
try {
const res = await axiosFn(url, type, params)
return [null, res]
} catch (e) {
return [e, null]
}
}
但是这样的不足是我们还是需要判断 code
是否为0的情况:
setLoading(true)
const [err, res] = await $fetch(url, type, params)
if (res) {
if (res.code === 0) {
const {data} = res
setDataSource(data)
} else {
message.error(res.message)
}
} else {
message.error(err.message)
}
setLoading(false)
所以我们继续优化:
const $fetch = async (url, options) => {
let res = {}
let err = null
try {
res = await axios(url, options)
} catch (error) {
err = error
}
if (res.code !== 0) err = res;
if (err !== null) message.error(err.errmsg || err.message || '请求失败')
return [res, err] // 因为这里只有两个返回值,所以数组即可,否则需要返回对象,方便调用方接收处理
}
这样的好处是,更加扁平化,并且我们不需要单独处理 code
不为0的情况,也不需要在失败时 toast
:
setLoading(true)
const [res, err] = $fetch(url, params)
if (err !== null) return
// 这里可以这样写的原因是 data 不会为 null,否则需要 const data = res.data || {}
const {data = {}} = res
setDataList(data)
setLoading(false)
新功能可以采取这样的方式,但是旧功能的重构,就像维修飞驰的火车,有点难度,需要阅读下《重构》
最近在做中台项目,优化一下历史代码
Select
数据源,希望返回 {label: xx, value: xx}
的格式mock
正常,服务自测也正常,但是中台服务挂了,这种就会浪费时间找问题。else
:修改前:
if (item.cbid) {
return (
<Link to={`/tagrulebook/booktag/${item.cbid}`} className='c404aff' target='_blank'>
{text}
</Link>
);
} else {
return text;
}
修改后:
if (item.cbid) {
return (
<Link to={`/tagrulebook/booktag/${item.cbid}`} className='c404aff' target='_blank'>
{text}
</Link>
);
}
return text;
修改前:
if (value && value.length === 0) {
// do something
}
修改后:
if (value?.length) {
// do something
}
[].concat(arr)
直接利用扩展运算符就好了
修改前:
dataRem[dataRem.indexOf(item)].status = 2
setDataRem([].concat(dataRem))
修改后:
dataRem[dataRem.indexOf(item)].status = 2
setDataRem([...dataRem])
??
代替 ||
和 三目运算符代替 ||
:
||
就有问题了,'' || 1
的结果是 10
,0 || '暂无金额'
的结果是 暂无金额
修改前:
<div>标签描述:{item?.description || '暂无描述'}</div>
修改后:
<div>标签描述:{item?.description ?? '暂无描述'}</div>
代替 ?...:
:
修改前:
const val = value ? value : ''
修改后:
const val = value ?? ''
? :
取代 if...else
可以让代码更加简洁
修改前:
if (item.value === '0') {
const name = platform[0][0]['name']
const value = platform[0][0]['value']
onFilter({[name]: value})
} else {
const name = platform[1][0]['name']
const value = platform[1][0]['value']
onFilter({[name]: value})
}
修改后:
const [name, value] = item.value === '0'
? [platform[0][0]['name'], platform[0][0]['value']]
: [platform[1][0]['name'], platform[1][0]['value']]
onFilter({[name]: value})
catch
的 then
后面做可能有些人不知道 catch
后面返回的还是一个 promise
修改前:
setLoading(true)
axios
.get(url)
.then(res => {
setLoading(false)
// do something
})
.catch(err => {
setLoading(false)
// do something
})
修改后:
setLoading(true)
axios
.get(url)
.then(res => {
// do something
})
.catch(err => {
// do something
})
.then(() => {
setLoading(false)
})
有时候,我们会写出一些冗余代码(当然,有时候适当冗余是好的)
修改前:
function foo () {
if (val === null || val === undefined) return
if (val === '') return
// do something
}
修改后:
function foo () {
if (!!val) return
// do something
}
修改前:
const search = value => {
if (!value) {
initBookInfo(1, 10)
return
}
initBookInfo(1, 10)
}
修改后:
const search = value => {
initBookInfo(1, 10)
}
下面的代码是分别点击黑白名单的时候展示对应的数据,代码基本上都是通用的:
修改前:
if (bookRadioBox.includes('1')) {
const [res, err] = await $fetch(`/recSubject/subset_contents?subset_id=${sublibId}&page_size=10&page_num=1`, {
method: 'GET'
})
if (err !== null) return
setBookInfoLoading(false)
setBookInfo(res.data)
} else if (bookRadioBox.includes('2')) {
const [res, err] = await $fetch(`/recSubject/whiteBooks?subset_bw_id=${formData.whitelist}&page_size=10&page_num=1`, {
method: 'GET'
})
if (err !== null) return
setBookInfoLoading(false)
setBookInfo(res.data)
} else if (bookRadioBox.includes('3')) {
const [res, err] = await $fetch(`/recSubject/blackBooks?subset_bw_id=${formData.whitelist}&page_size=10&page_num=1`, {
method: 'GET'
})
if (err !== null) return
setBookInfoLoading(false)
setBookInfo(res.data)
}
修改后:
let url
if (bookRadioBox.includes('1')) {
url = `/recSubject/subset_contents?subset_id=${sublibId}&page_size=10&page_num=1`
} else if (bookRadioBox.includes('2')) {
url = `/recSubject/whiteBooks?subset_bw_id=${formData.whitelist}&page_size=10&page_num=1`
} else if (bookRadioBox.includes('3')) {
url = `/recSubject/blackBooks?subset_bw_id=${formData.whitelist}&page_size=10&page_num=1`
}
const [res, err] = await $fetch(url, { method: 'GET' })
if (err !== null) return
setBookInfoLoading(false)
setBookInfo(res.data)
不能单纯利用 str.length > 0
判断字段,因为可能是 number
。可以统一成 String(str).length > 0
今天在开发智能的需求的时候,偶然发现了一个问题,就是两种类型的数据切换的时候有点慢,调试了一下,发现了问题,就进行了一下优化。
今天开发的一个功能和这个类似,查看这个功能的时候,发现了一个问题:切换 CheckBox 的时候,有一点点卡顿的感觉。
测试环境还好,但是在本地调试的时候,延迟已经挺严重了。
通过代码审查,发现了产生这个现象的原因:每次切换都会重新计算数据:
代码:
index.js
我们请求的数据结构是下面这样的,一共有好几十个类别,每个类别下又有几十上百个标签,测试环境的数据就有好多万个,真实情况下数据量更大。
每次点击都对这么多数据进行处理,所以会造成卡顿。
这种情况下,我们应该有下面两种解决方案:
可能是由于历史原因,以前的接口都是前端排序的。由于现代浏览器性能已经比较优秀了,那我们就在不浪费后端人力的情况下,前端处理。
我们利用高阶函数,对纯函数进行缓存,利用 key 是否存在进行判断:
此时,我们将 IpCard.js 中的计算移到父组件,并对数据进行缓存:
这样我们每次切换就好多了:)
原理
主要是利用了闭包:
(完)
今天下雨,打不了球了: (
看了会 HTTP 的 Cookie 知识,记录一下。
因为 HTTP 是无状态的,服务器不知道将多个商品添加到购物车的是不是同一个用户。为了解决这个问题,网景的工程师 Lou Montulli 发明了 Cookie。
我们启动一个 node 服务来了解一下 Cookie 里面的各个字段:
// 导入 http 内置模块
const http = require('http')
// 这个核心模块,能够帮我们解析 URL地址,从而拿到 pathname query
const url = require('url')
// 创建一个 http 服务器
const server = http.createServer()
// 监听 http 服务器的 request 请求
server.on('request', function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('cookieLearn')
})
// 指定端口号并启动服务器监听
server.listen(3000, function () {
console.log('server listen at http://127.0.0.1:3000')
})
毕竟也是持久化的一种,所以 Cookie 是存在于 Application 下的 Storage 里面,红框里就是常见的几个字段。
我们分别看下:
但是我在 Chrome 里面的确可以用括号的,浏览器支持。
// 导入 http 内置模块
const http = require('http')
// 这个核心模块,能够帮我们解析 URL地址,从而拿到 pathname query
const url = require('url')
// 创建一个 http 服务器
const server = http.createServer()
// 监听 http 服务器的 request 请求
server.on('request', function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('cookieLearn:4000')
})
// 指定端口号并启动服务器监听
server.listen(4000, function () {
console.log('server listen at http://127.0.0.1:4000')
})
/
。// 导入 http 内置模块
const http = require('http')
// 这个核心模块,能够帮我们解析 URL地址,从而拿到 pathname query
const url = require('url')
// 创建一个 http 服务器
const server = http.createServer()
// 监听 http 服务器的 request 请求
server.on('request', function (req, res) {
const parseUrl = url.parse(req.url, true)
res.writeHead(200, {'Content-Type': 'text/plain', 'Set-Cookie': 'age=aaa; path=/doc'})
res.end('cookieLearn:4000')
})
// 指定端口号并启动服务器监听
server.listen(4000, function () {
console.log('server listen at http://127.0.0.1:4000')
})
如下图所示,当我们访问 /doc 这个路径时,Cookie 是有的。但是访问 /doc1 这个路径,Cookie 就没有了。
这里注意一个问题,这里设置的不是本地时间,所有可能有 Cookie 没有过期的情况,因为这个时间并没有过服务器的时间呢:
res.writeHead(200, {'Content-Type': 'text/plain', 'Set-Cookie': 'age=aaa; path=/; Expires=Mon Dec 02 2019 20:00:00 GMT'})
HttpOnly
这个是防止遭受了 XSS 攻击之后不让别人获取 Cookie。
注意,这个可不能防止 XSS 攻击,只是降低损失,不让他做一些操作而已。
res.writeHead(200, {'Content-Type': 'text/plain', 'Set-Cookie': 'agee=aaa; Httponly'})
这部分是别人写的,应该是后端的范畴了,顺便了解一下。
在互联网公司为了可以支撑更大的流量,后端往往需要多台服务器共同来支撑前端用户请求,那如果用户在 A 服务器登录了,第二次请求跑到服务 B 就会出现登录失效问题。
分布式 Session 一般会有以下几种解决方案:
Nginx ip_hash 策略,服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象。
Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点。
共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件来统一管理,保障分发到每一个服务器的响应结果都一致。
(完)
背景:由于作家反映在手机上创作,输入大段内容比较麻烦,需要一个
PC
页来创作。我们花了一周时间上线了一个简易版的pc
页。团队规模变大之后,从 0 到 1 搭建一个项目的机会是很难得的,作为主要开发者之一,在这个项目中收获了很多。
项目地址
js
、jQuery
、React
、Vue
等, 任何技术都有它的使用场景,挑一个合适的语言。React(全部使用了 hooks)
+ styled-component(css解决方案)
+ axios(网络请求)
+ yapi(mock接口)
sdk
要找谁 、运维上线要找谁、让产品提供一些协议要找谁等等。当然这个需要通过经验积累才能考虑完善。对于新手,如果没有把握可以让 leader
来把关。pc
页,拆分为框架搭建1天、登录相关联调2天、业务开发3天(界面及 UI
动画1天、内容输入逻辑1天、其它逻辑1天),联调提测1天。你估计的越详细,准确性就越高。assets
、img
、css
、utils
、lib
等等。mock
数据。由于 create-react-app
封装了 webpack
,我们不想 eject
出来,于是使用了 http-proxy-middleware
实现代理。在 src
目录下新建 setupProxy.js
:const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function (app) {
app.use(createProxyMiddleware('/api', {
target: your mock url,
pathRewrite: {
"^/api": "/api"
},
changeOrigin: true, // target是域名的话,需要这个参数,
}));
};
js
相关emoji
是占两个字符的,对它进行一些处理的时候需要注意: encodeURI('😂'.charAt(0)) // Uncaught URIError: URI malformed
正则里面的\s
是匹配任何空白字符,包括空格、制表符、换页符等等,等价于 [\f\n\r\t\v]
。用户输入的文字多个空格要替换成一个,但是不能将换行替换掉,所以需要用str.replace(/[ ]+/g, ' ')
生产模式下移除console.log。上面说了,我们没有 eject
,所以采用下面的方式:
function noop() {}
if (process.env.NODE_ENV !== 'development') {
console.log = noop;
console.warn = noop;
console.error = noop;
}
react
中 setState
后光标是不会 focus
在原来的输入框的,所以我们需要记住它的位置,然后根据自己的需要重新插到输入框中。 setSelectionObj({start: e.target.selectionStart, end: e.target.selectionEnd})
ajax
只能应用最后一个数据 useEffect(() => {
const currentCount = countRef.current
Axios.$get(`url?time=${Date.now()}`)
.then(res => {
if (currentCount !== countRef.current) {
return
}
// do something
}
})
return () => {
countRef.current += 1
}
}, [bookId])
css
相关transform
和 translate
连用要小心。比如一个用了 translate(-50%, -50%)
居中的元素,让它旋转需要这样写:const ImgRotate = keyframes`
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
`
const ImageBox = styled.img`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%)
width: 200px;
height: 200px;
animation: 2s ${ImgRotate} infinite linear;
`
IE
下面每行后面都有一个问号,是因为 lato
字体不支持,就把这个字体删了。width 100% padding
宽度溢出
box-sizing
的默认值是 content-box
,设置成 border-box
。
类选择器间的空格
.a .b
代表 class
包含 a
下面的 class
包含 b
的元素
.a.b
代表 class
同时包含 a
和 b
的元素,如<span class="a b"></span>
http
相关我们在登录这块遇到了一些问题。
safari
不支持第三方 iframe setCookie
和 window.close
chrome
80版本:samesite
是灰度的,涉及第三方需要判断浏览器版本并 setCookie: none
其实收获的不止是一些特定的 api
,还有很多我感觉更重要的东西。比如:
那如果产品后面需要一个竖型展示的 <BookItem />
组件,那命名是叫 <BookItem2 />
还是<BookItemColumn />
呢。
适当的冗余
还是这张图,我们一开始可能只需要传递书名、章节数、字数、书封给子组件就行了:
<BookInfo key={item.bookId} name={item.bookName} chapterCount={item.chapterCount} avatar={item.avatar} onClick={itemClick} />
但是后面万一产品需要加个作者,加个是否已完结,等等等等。。。我们可以一开始就直接把这个信息带过去:
<BookInfo key={item.bookId} bookInfo={item} onClick={itemClick} />
思考问题的方式
比如找 bug
的时候,我们可以通过二分法查找;
比如极限的**,一般出问题的都不是连续的中间,而是边界或者两种情况之间;
比如反向思考,看到大想到小,看到明想到暗;
比如类比思考,看到长想到宽,看到横想到竖;
比如否定自己,我们很容易就找各种理由否定别人的想法,试着否定自己的想法;
发现自己没有仔细看过 react 官网的内容,需要好好过一遍
我们知道组件是可以复用的,但是如何复用通用状态和行为呢?
例如,有一个组件,可以展示鼠标当前在屏幕的坐标:
import React, { useState } from "react";
const MouseTrack = () => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
鼠标位置:({mouseObj.x}, {mouseObj.y})
</div>
);
};
export default MouseTrack;
图片: https://uploader.shimo.im/f/QGbLyvn4rVzPtPE4.png
那么问题来了,我们有另一个组件,比如一个猫咪图片,它的位置可以跟着鼠标位置变化。希望复用上面组件的能力,怎么办呢?
有一种办法,我们看看代码:
// App.js
import React from "react";
import MouseTrack from "./mouseTrack";
export default function App() {
return (
<div className="App">
<h2>移动鼠标</h2>
<MouseTrack />
</div>
);
}
// Cat.js
import React from "react";
export const Cat = ({ x = 0, y = 0 }) => {
return (
<div>
<img
alt="cat" src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1124621307,1373233430&fm=26&gp=0.jpg"
style={{
width: "100px",
height: "100px",
position: "absolute",
left: x,
top: y
}}
/>
</div>
);
};
// MouseTrack.js,这个组件准确地说叫做 MouseTrackWithCat,不够纯洁
import React, { useState } from "react";
import { Cat } from "./Cat";
const MouseTrack = () => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
鼠标位置:({mouseObj.x}, {mouseObj.y})
<Cat x={mouseObj.x} y={mouseObj.y} />
</div>
);
};
export default MouseTrack;
图片: https://uploader.shimo.im/f/CL8Nsq2Hrr5e3j88.png
乍一看好像是复用了。但是,考虑一下扩展性,如果现在有另一个组件,同样需要复用这个能力,就叫小狗组件吧:
// App.js
import React from "react";
import MouseTrack from "./mouseTrack";
export default function App() {
return (
<div className="App">
<h2>移动鼠标</h2>
<MouseTrack />
<MouseTrackWithDog />
</div>
);
}
// Cat.js
import React from "react";
export const Cat = ({ x = 0, y = 0 }) => {
return (
<div>
<img
alt="cat" src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1124621307,1373233430&fm=26&gp=0.jpg"
style={{
width: "100px",
height: "100px",
position: "absolute",
left: x,
top: y
}}
/>
</div>
);
// Dog.js
import React from "react";
export const Dog = ({ x = 0, y = 0 }) => {
return (
<div>
<img
alt="cat" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic2.zhimg.com%2F50%2Fv2-de3e5710753e5760c9d1a3aee075164f_hd.jpg&refer=http%3A%2F%2Fpic2.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1613464244&t=c32fe626cbd5a0182108215399a599f8"
style={{
width: "100px",
height: "100px",
position: "absolute",
left: x + 100,
top: y + 100
}}
/>
</div>
);
};
// MouseTrack.js,这个组件准确地说叫做 MouseTrackWithCat,不够纯洁
import React, { useState } from "react";
import { Cat } from "./Cat";
const MouseTrack = () => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
鼠标位置:({mouseObj.x}, {mouseObj.y})
<Cat x={mouseObj.x} y={mouseObj.y} />
</div>
);
};
export default MouseTrack;
// MouseTrackWithDog.js
import React, { useState } from "react";
import { Dog } from "./Dog";
const MouseTrackWithDog = () => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
鼠标位置:({mouseObj.x}, {mouseObj.y})
<Dog x={mouseObj.x} y={mouseObj.y} />
</div>
);
};
export default MouseTrackWithDog;
图片: https://uploader.shimo.im/f/VSAl2nghXwghLzJj.png
注:这里其实有个问题,我们会有两个div : (
// App.js
import React from "react";
import MouseTrack from "./mouseTrack";
export default function App() {
return (
<div className="App">
<h2>移动鼠标</h2>
<MouseTrack />
</div>
);
}
// Cat.js
import React from "react";
export const Cat = ({ x = 0, y = 0 }) => {
return (
<div>
<img
alt="cat" src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1124621307,1373233430&fm=26&gp=0.jpg"
style={{
width: "100px",
height: "100px",
position: "absolute",
left: x,
top: y
}}
/>
</div>
);
// Dog.js
import React from "react";
export const Dog = ({ x = 0, y = 0 }) => {
return (
<div>
<img
alt="cat" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic2.zhimg.com%2F50%2Fv2-de3e5710753e5760c9d1a3aee075164f_hd.jpg&refer=http%3A%2F%2Fpic2.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1613464244&t=c32fe626cbd5a0182108215399a599f8"
style={{
width: "100px",
height: "100px",
position: "absolute",
left: x + 100,
top: y + 100
}}
/>
</div>
);
};
// MouseTrack.js,这个组件准确地说叫做 MouseTrackWithCat,不够纯洁
import React, { useState } from "react";
import { Cat } from "./Cat";
import { Dog } from "./Dog";
const MouseTrack = () => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
鼠标位置:({mouseObj.x}, {mouseObj.y})
<Cat x={mouseObj.x} y={mouseObj.y} />
<Dog x={mouseObj.x} y={mouseObj.y} />
</div>
);
};
export default MouseTrack;
图片: https://uploader.shimo.im/f/WKFD5bfjTMC2o5JT.png
这下的确可以了,但是有个问题,为了复用这个状态,我们把组件都写在了这个状态里面,是不是感觉很奇怪。我要用一个东西,还得给别人当儿子?
解决方案
这里,就进入主题了。让我们看看如何利用 render props 来重写这个功能:
// app.js
import React from "react";
import MouseTrack from "./mouseTrack";
export default function App() {
return (
<div className="App">
<h2>移动鼠标</h2>
<MouseTrack />
</div>
);
}
// mouseTrack.js
import React from "react";
import { Cat } from "./Cat";
import { Dog } from "./Dog";
import Mouse from "./mouse";
// mouseTrack.js
const MouseTrack = () => {
return (
<div>
<Mouse
render={(mouse) => (
<div>
<Dog x={mouse.x} y={mouse.y} />
<Cat x={mouse.x} y={mouse.y} />
</div>
)}
/>
</div>
);
};
export default MouseTrack;
// mouse.js
import React, { useState } from "react";
const Mouse = (props) => {
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
{/* 咱老实做人,只做本分的事 */}
{props.render(mouseObj)}
</div>
);
};
export default Mouse;
图片: https://uploader.shimo.im/f/WVvrjbyz4jbsEUQf.png
完美,就解决了状态复用的问题。
这里的关键在于,我们给 组件传递了一个函数来动态渲染组件,而不是特定数据!怎么渲染时爸爸决定的,儿子只需要执行就好。
这里我们注意到,渲染一个函数,是不是会造成重复渲染?
// mouseTrack.js
import React, { useEffect, useCallback } from "react";
import { Cat } from "./Cat";
import { Dog } from "./Dog";
import Mouse from "./mouse";
const MouseTrack = () => {
useEffect(() => {
console.log("render mouseTrack");
});
const renderMouse = useCallback(
(mouse) => (
<div>
鼠标位置:({mouse.x}, {mouse.y})
<Dog x={mouse.x} y={mouse.y} />
<Cat x={mouse.x} y={mouse.y} />
</div>
),
[]
);
return (
<div>
<Mouse children={renderMouse} />
</div>
);
};
export default MouseTrack;
// mouse.js
import React, { useState, useEffect, memo } from "react";
const Mouse = memo((props) => {
useEffect(() => {
console.log("render mouse", props);
});
const [mouseObj, setMouseObj] = useState({ x: 0, y: 0 });
const mouseMove = (event) => {
setMouseObj({
x: event.clientX,
y: event.clientY
});
};
return (
<div style={{ height: "100vh" }} onMouseMove={mouseMove}>
{props.children(mouseObj)}
</div>
);
});
export default Mouse;
(完)
上篇 JSONP 的文章里提到过利用 Nginx 也可以解决跨域问题。趁着自己以前没有接触过 Nginx,熟悉了一下,顺带写了一个非常非常简单的 demo 实验下。
提到代理,肯定要说一下这两个的区别。
举个正向代理的例子:我打球累了走不动了,找看球的小朋友帮我去旁边的商店买瓶水。商店老板是不知道到底是谁需要喝水的,隐藏了客户端。当然,小朋友可以告诉老板就是那个打球像蔡徐坤的人要喝水。还有,VPN 就是正向代理。
反向代理的例子:我打球累了,找看球的小朋友要瓶水喝(当然我肯定会给钱的:D)。我不需要知道小朋友的水是从旁边的商店还是两公里外的超市买的。隐藏了服务端。还有,我们连好了 VPN 访问谷歌的时候,浏览的那些页面,我们是不会知道具体是哪台服务器的资源。
服务接口
既然请求,肯定需要先写一个服务接口,我们用 node 起一个服务:
// index.js
const http = require('http');
const fs = require('fs');
const url = require('url');
const server = http.createServer(function (req, res) {
if (req.url === '/favicon.ico') {
return;
}
const parseUrl = url.parse(req.url, true);
console.log('parseUrl', parseUrl.pathname)
if (parseUrl.pathname === '/api/getList') {
const list = {'a': 1, 'b': 2}
res.writeHead(200, {'content-Type':'text/html;charset=UTF-8'})
res.end(JSON.stringify(list))
}else {
res.write(`
port: 666
`)
res.end()
}
});
server.listen(666, function () {
console.log('server is starting on port 666');
});
测试页面
然后,我们写一个简单的 ajax 请求页面。你可以本地用http-server启动访问下,可以发现请求跨域了:
<html>
<head>
<title></title>
</head>
<body>
<button onclick="sendAjax()">sendAjax</button>
<script type="text/javascript">
var sendAjax = () => {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://localhost:666/api/getList', true);
xhr.send();
xhr.onreadystatechange = function (e) {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
};
}
</script>
</body>
</html>
安装 Nginx
这个时候,你可以通过设置响应头来允许跨域。或者用 Nginx 来解决这个问题了。首先肯定需要安装 Nginx。这个按照对应的平台安装就行了。
brew update
brew install nginx
nginx
nginx -s reload // 重启
配置
然后我们配置一下代理,这个意思就是我们请求中有 api 这样的就会代理到 http://127.0.0.1:666,所以我们只要访问 http://localhost:9999/api/getList 这个不跨域的接口,然后就会由服务器反向代理到 http://localhost:666/api/getList。
listen 9999;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /api/ {
proxy_pass http://127.0.0.1:666;
}
配置好之后我们需要重启一下 Nginx 服务。注意一点,重启时可能会报这么一个错误:
nginx: [error] open() "/usr/local/var/run/nginx.pid" failed (2: No such file or directory)
这是 sudo nginx -s stop 这个命令会删除 pid 文件,可以执行 sudo nginx
重新添加这个文件。
测试结果
这个时候,我们不用绝对地址了,我们把ajax请求里面的接口换成相对地址:
// xhr.open('GET', 'http://localhost:666/api/getList', true);
xhr.open('GET', '/api/getList', true);
美滋滋,这就不跨域了呢。
当然,还可以更加真实一点,我们随便用一个域名测试一下。Nginx 重新配置下:
listen 80;
server_name yumingtest;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
root html;
index index.html index.htm;
}
location /api/ {
proxy_pass http://127.0.0.1:666;
}
然后在 hosts 文件里面添加这条:127.0.0.1 yumingtest.com
,重启下 Nginx。
这下是不是更加真实了。关于hosts 文件的作用,就是我们输入域名不是要需要经过DNS解析IP嘛,这个里面就存了一些。首先自动从 Hosts 文件中寻找对应的 IP 地址,一旦找到,系统会立即打开对应网页,如果没有找到, 则系统再会将网址提交 DNS 域名解析服务器进行 IP 地址的解析。
还要一个问题,关于 HTTP 502 状态码,我是把接口服务停了,于是就报 502了。
也不一定是网上说的什么”连接超时 我们向服务器发送请求 由于服务器当前链接太多,导致服务器方面无法给于正常的响应,产生此类报错“那样。
(完)
记录下开发中遇到的问题(包括 error和 warning)
Expected to return a value in arrow function
:箭头函数需要返回值发生问题的原因:
是遍历数组的时候用了 map
,其实是直接操作了数组,但是没有 return
每个元素。
解决方法:
改成 forEach
,或者 return
每个元素
let arr = [
{label: 'a', value: 1},
{label: 'b', value: 2},
{label: 'c', value: 3}
]
arr.forEach(item => item.value *= 2)
undefined
后,new Date(undefined)
就会报错:new Date(0) // Thu Jan 01 1970 08:00:00 GMT+0800 (**标准时间)
new Date(null) // Thu Jan 01 1970 08:00:00 GMT+0800 (**标准时间)
new Date(undefined) // Invalid Date
arr[Symbol.iterator] is not a function
原因:
异步请求忘了加 await
,直接赋值给一个数组了。请求返回的是一个 Promise
,不可迭代:
// 错误代码
const [res, err] = $fetch(`/recSubject/subset_contents?subset_id=${sublibId}&page_size=10&page_num=1`, {
method: 'GET'
})
if (err !== null) return
setBookInfoLoading(false)
setBookInfo(res.data)
can't read property 'join' of null
原因:
当用 fetch 请求时:
fetch(url, { method: 'POST', body: JSON.stringify(data)})
.then(response => {
return response.json() // 这里会报错 Unexpected token < in JSON at position 0
})
.then(res => {})
.catch(err => {})
因为可能请求错误时返回的不是一个 json 格式,要对错误兼容一下:
fetch(url, { method: 'POST', body: JSON.stringify(data)})
.then(response => {
if (response.ok) {
return response.json()
}
return Promise.reject(response)
})
.then(res => {})
.catch(err => {})
今天后端突然把接口很多字段变了,我修改了半天,很僵。本来想加一层对字段进行 map,不影响我已经使用的,但是发送的时候还要转一遍,而且如果结构变了,还是要改很多,就直接改了。真是一个惨痛的教训。
我以为只有几个字段,原来是全部的,本来应该是后端和我对接完,再和服务对接,抹平接口差异。后端好像没有时间,只做了一层转发,导致这个结果。既然答应了,就得硬着头皮做完。所以,不能随口答应别人,得考虑成本。早知道这个结果,我肯定不能同意。
我觉得正常的开发流程应该这样:
同事遇到了这样一个场景,用正则进行循环判断,简化的代码大概是下面这样:
var reg = /ab/g
console.log(reg.test('ab')) // true
console.log(reg.test('ab')) // false
这里是不是有人会觉得奇怪,为什么第二个是 false 呢。
其实如果我们仔细看过MDN上关于test的描述,应该就知道了:
关于这个lastIndex,很多小伙伴可能没有听过也没有用过,让我们看看下面的代码:
var reg = /ab/g
console.log(Object.getOwnPropertyDescriptors(reg))
//{lastIndex: {value: 0, writable: true, enumerable: false, configurable: false}}
可以看到,reg 含有一个不可枚举的 lastIndex 属性。当然,如果你直接用 Object.keys(reg) 是看不到的:
var reg = /ab/g
console.log(Object.keys(reg)) // []
(知识点:getOwnPropertyDescriptors() 和 keys() 的区别是什么呢。keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组。)
这个就是造成上面第二个是false的原因。因为每执行一次,其实我们的lastIndex都会变化:
var reg = /[ab]/g
console.log('lastIndex', reg.lastIndex) // lastIndex 0
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 1
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 2
reg.test('ab')
console.log('lastIndex', reg.lastIndex) // lastIndex 0
根据上面的代码可以看到,这个lastIndex是会循环的。
所以第二次就出了问题。如果我们想让一开始的代码正常工作,该怎么办呢?
var reg = /ab/g
console.log(reg.test('ab')) // true
reg.lastIndex = 0
console.log(reg.test('ab')) // true
我们需要在每次执行后手动设置lastIndex。
我们举一个应用场景:后端传回来了一个图片 url 的数组,有 jpg、png 各种格式的,我们需要拿到以 jpg 结尾的图片:
首先,我们看看下面的代码:
const reg = /\.jpg/g;
const arr = [
'test0.png',
'test1.jpg',
'test2.jpg',
'test3.jpg',
'test4.jpg',
'test5.jpg',
];
const res = arr.filter(item => reg.test(item));
(关于判断图片格式的这个正则大家不要纠结,其实是不完善的,比如url的名字是 ajpg.jpg 这种也可以匹配到,这个不重要,不是本文重点)
乍一看,觉得没毛病。我们看看结果:
WTF???
你肯定反应过来了,因为lastIndex变了,所以和预期不一致了,我们输出一下看看:
可以看到,true和false是依次输出的,所以我们没有拿到全部的jpg格式的url。
还有,就是如果当前匹配失败了,lastIndex也会重置为0,这一点也很重要,不然后面的就匹配不到了。
所以解决方法之一就是手动重置lastIndex:
其实,最好的方法就是直接去掉g,因为这种场景其实是不需要加上g的。这样lastIndex就一直是0,不会变了。结果也是符合预期的:
此外,lastIndex
还和另一个正则方法 exec()
也是好兄弟,关系很近。用法和test()
原理一样。
很多人分不清 exec
和 match
的关系,或者可能没有用过 exec
。
match
和 exec
的关系:
首先,两个方法的来源是不一样的:
String.prototype.match()
RegExp.prototype.exec()
然后,不加g的时候是一样的:
然后,加了g的话:
match就没有捕获组了:
这个时候我们就需要用exec()方法了。
关于捕获组,这里简单介绍一下。
捕获组分为四类:
捕获 ()
不捕获 (?:)
正向捕获 (?=)
反向捕获 (?!)
捕获组的功能很强大,这里就举两个例子:
例1:对日期进行任意格式的转换
var reg = /(\d{2})\/(\d{2})\/(\d{4})/
var data = '10/24/2017'
data = data.replace(reg, '$3/$1.$2')
console.log(data) //2017/10.24
例2: 千分位数字
'12345678'.replace(/(?=(\d{3})+$)/g, ',') // 12,345,678
因此,exec结合捕获组就可以实现很多复杂的逻辑。
举个例子,我们要把一个字符串 a1b2c3d4e5
里面的偶数的数字变成0。(方法肯定很多,这里只考虑用正则的方式)
肯定有同学说直接用 replace
就行了:
'a1b2c3d4e5'.replace(/[2468]/g,0) // "a1b0c3d0e5"
(因为这里的转换条件比较简单,如果是很复杂的转换,比如我要把偶数的数字先转成ASCII码,然后反转呢?主要就是意思一下哈。)
我个人觉得exec比match强大的地方在于它可以深入到内部,暂停执行,类似 generate 函数,而不像match一样是个黑盒。
关于这个,其实业务中感觉用的不多,就举个例子吧:
var regex = /[a-z]_[a-z]/ig;
var str = 'a_b_c';
var match;
while ((match = regex.exec(str)) !== null) {
console.log('match', match)
regex.lastIndex--
}
// a_b
// b_c
代码大家肯定看得懂,我就不说了。match
是无法实现这样的功能的,因为匹配完就丢弃了。
使用exec() 要注意的点就是不要写出死循环(毕竟有while)
redux 的使用:redux
// 创建仓库
const store = createStore(reducer)
// reduce: 初始化数据的参数、根据不同的action处理返回数据
const reducer = function(state={value: 0}, action) {
switch (action.type) {
case 'plus':
state.value++;
break;
case 'minus':
state.value--;
break;
default:
break;
}
return {...state}
}
let store = store.getState()
store.dispatch({type: 'plus', content:{id: 1}})
// 监听的是一个函数
store.subscribe(() => (ReactDOM.render(<App />, document.getElementById('root'))))
react-redux 的使用:react-redux
// 创建仓库
const store = createStore(reducer)
// reduce: 初始化数据的参数、根据不同的action处理返回数据
const reducer = function(state={value: 0}, action) {
switch (action.type) {
case 'plus':
state.value++;
break;
case 'minus':
state.value--;
break;
default:
break;
}
return {...state}
}
将state、修改数据的方法映射到组件props里
// 将state映射到props
function mapStateToProps(state) {
return {
value: state.value
}
}
// 将修改 state 的方法映射到 props
function mapDispatchToProps(dispatch) {
return {
plus: () => {dispatch(plusAction)},
minus: () => {dispatch(minusAction)}
}
}
// 关联(顺序不能变)
const App = connect(mapStateToProps, mapDispatchToProps)(Counter)
function reducer(state = {value: 0}, action) {
if (action.type === 'plus') {
state.value++
} else if (action.type === 'minus') {
state.value--
}
return {...state}
}
function createStore(reducer) {
let state
let listeners = []
function getState () {
return reducer(state, {type: "@@redux/INITq.f.4.i.v"})
}
function dispatch(action) {
state = reducer(state, action)
listeners.forEach(listen => {
listen()
})
}
function subscribe(fn) {
listeners.push(fn)
}
return { getState, dispatch, subscribe }
}
const store = createStore(reducer)
store.getState() // {value: 0}
store.subscribe(() => console.log(store.getState()))
store.subscribe(() => console.log('1'))
store.subscribe(() => console.log('2'))
store.dispatch({type: 'plus'}) // {value: 1}, 1, 2
您好,我用您给的api,其实并不是网络时间,而是返回的本地时间戳。
如果从1到100,我随便写一个数,让你猜,你咋样才能快速猜出来。
function search(arr, target) {
let start = 0;
let end = arr.length - 1;
while (start <= end) { // = 号不能丢,你想要是只有一个元素,刚好 target 就是
let mid = Math.floor((start + end) / 2);
if (arr[mid] < target) {
start = mid + 1;
} else if (arr[mid] > target) {
end = mid - 1;
} else {
return mid;
}
}
return -1;
}
post传给后端可以用payload、formdata、query
【注意】简单请求的Content-Type:只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
。如果是application/json
则会多发一个option请求。
import axios from 'axios'
import qs from 'qs'
const CSRF_KEY = '_csrfToken'
const instance = axios.create()
export function getCsrfToken () {
const cookie = document.cookie
const csrfToken = parseCookie(cookie || '')[CSRF_KEY]
return {
[CSRF_KEY]: csrfToken
}
}
function parseCookie (cookie) {
const cookieObj = {}
const cArr = cookie.split(';')
for (const str of cArr) {
const iArr = str.trim().split('=')
cookieObj[iArr[0]] = iArr[1]
}
return cookieObj
}
instance.interceptors.request.use(config => {
const csrfToken = getCsrfToken()
const devParams =
process.env.NODE_ENV === 'development' ? { nocsrf: 1, nologin: 1 } : {}
config.params = Object.assign(devParams, csrfToken, config.params || {})
return config
})
instance.interceptors.response.use(
(response) => {
return response
},
(error) => {
return Promise.reject(error.response)
}
)
instance.$get = function (url, data, config = {}) {
!config.params && (config.params = {})
data && (Object.assign(config.params, data))
return this.get.call(this, url, config).then((response) => {
if (response) {
return response
}
throw new Error('网络不畅,请稍候再试')
}, (err) => {
return Promise.reject(err.data)
}
instance.$postForm = function (url, data = {}, config = {}) {
!config.params && (config.params = {})
!config.headers && (config.hearders = {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
})
const csrfToken = getCsrfToken()
return this.post.call(this, url, qs.stringify(Object.assign(csrfToken, data)), config).then((response) => {
if (response) {
return response
}
throw new Error('网络不畅,请稍候再试')
}, (err) => {
return Promise.reject(err.data)
}
instance.$postFormData = function (url, data = {}, config = {}) {
!config.params && (config.params = {})
!config.headers && (config.hearders = {
'Content-Type': 'multipart/form-data; charset=UTF-8'
})
const csrfToken = getCsrfToken()
const formData = new FormData()
csrfToken[CSRF_KEY] && formData.set(CSRF_KEY, csrfToken[CSRF_KEY])
for (const key of Object.keys(data)) {
formData.set(key, data[key])
}
return this.post.call(this, url, formData, config).then((response) => {
if (response) {
return response
}
throw new Error('网络不畅,请稍候再试')
}, () => {
throw new Error('网络不畅,请稍候再试')
})
}
instance.$post = function (url, data = {}, config = {}) {
!config.params && (config.params = {})
const csrfToken = getCsrfToken()
return this.post.call(this, url, Object.assign(csrfToken, data), config).then((response) => {
if (response) {
return response
}
throw new Error('网络不畅,请稍候再试')
}, () => {
throw new Error('网络不畅,请稍候再试')
})
}
instance.$postQuery = function (url, data = {}, config = {}) {
!config.params && (config.params = data)
const csrfToken = getCsrfToken()
return this.post.call(this, url, qs.stringify(csrfToken), config).then((response) => {
if (response) {
return response
}
throw new Error('网络不畅,请稍候再试')
}, () => {
throw new Error('网络不畅,请稍候再试')
})
}
export default instance
哥们今天的面试题,对文件夹和文件遍历以大小排序
文件遍历得用 node 做,简单起见,直接拿结果操作:
遇到的问题:
var obj = {a: '2M', b: '3G', c: '100KB', d: '3T', e: '0', f: '1B', g: '100T', h: '1023M'}
function sortBySize (obj) {
let temp = []
for (key in obj) {
switch (true) {
case /T/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 * 1024 * 1023 + 'KB'])
break
case /G/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 * 1023 + 'KB'])
break
case /M/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 + 'KB'])
break
case /KB/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) / 1 + 'KB'])
break
default:
temp.push([key, parseInt(obj[key]) / 1024 + 'KB'])
}
}
temp.sort((a, b) => {
return a[1].slice(0, a[1].length - 2) - b[1].slice(0, b[1].length - 2)
})
temp.forEach(item => console.log(item[0], obj[item[0]]))
}
sortBySize(obj)
// e 0
// f 1B
// c 100KB
// a 2M
// h 1023M
// b 3G
// d 3T
// g 100T
var obj = {a: '2M', b: '3G', c: '100KB', d: '3T', e: '0', f: '1B', g: '100T', h: '1023M'}
function sortBySize (obj) {
let temp = []
for (key in obj) {
switch (true) {
case /T/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 * 1024 * 1023 + 'KB'])
break
case /G/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 * 1023 + 'KB'])
break
case /M/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) * 1024 + 'KB'])
break
case /KB/i.test(obj[key]):
temp.push([key, parseInt(obj[key]) / 1 + 'KB'])
break
default:
temp.push([key, parseInt(obj[key]) / 1024 + 'KB'])
}
}
// 此处应该用 quicksort
for (let i = 0; i < temp.length; i++) {
for (let j = i + 1; j < temp.length; j++) {
if (!/\./.test(temp[i][1]) && !/\./.test(temp[j][1])) {
if (parseInt(temp[i][1].slice(0, temp[i][1].length - 2)) > parseInt(temp[j][1].slice(0, temp[j][1].length - 2))) {
[temp[i], temp[j]] = [temp[j], temp[i]]
}
} else {
if (temp[i][1].slice(0, temp[i][1].length - 2) > temp[j][1].slice(0, temp[j][1].length - 2)) {
[temp[i], temp[j]] = [temp[j], temp[i]]
}
}
}
}
temp.forEach(item => console.log(item[0], obj[item[0]]))
}
sortBySize(obj)
// e 0
// f 1B
// c 100KB
// a 2M
// h 1023M
// b 3G
// d 3T
// g 100T
最近要在组内做一个
xss
的分享,遇到跨域的时候就想到了JSONP
,就想着很久以前都是用jQuery
直接调用,这次有时间自己实现了一下。
首先,看到 JSONP
这个词,很多人就会联想到它和 JSON
肯定有千丝万缕的关系。我不太清楚这个历史,不过个人感觉关系也不是很大(雷锋和雷锋塔?)。
言归正传,开始实现。关于 JSONP
的原理网上一大堆,我就不啰嗦了。主要是记录一下个人实现时的心路历程。
肯定有一个 .html
文件,用来接收服务端的数据,代码很简单:
<!-- jsonDemo.html -->
<html>
<head>
<title>jsonp_demo</title>
</head>
<body>
<script>
function handleJsonp (data) {
console.log('data', data)
}
</script>
<script src="http://localhost:3002?callback=handleJsonp"></script>
</body>
</html>
然后,肯定还有一个服务器。我用 node
跑的,代码同样也很简单:
// index.js
const http = require('http');
const fs = require('fs');
const url = require('url');
const server = http.createServer(function (req, res) {
if (req.url === '/favicon.ico') {
return;
}
const parseUrl = url.parse(req.url, true);
console.log('parseUrl', parseUrl)
res.writeHead(200, {'content-Type':'text/plain;charset=UTF-8'})
const data = "age: 27"
const str = parseUrl.query.callback + '("' + data + '")'
res.end(str)
});
server.listen(3002, function () {
console.log('server is starting on port 3002');
});
再然后,我们跑一下 node index.js
,刷新一下浏览器,可以看到控制台的确把后端数据吐给前端了。
这里遇到的问题是 http.createServer
执行了两次,node
我不是很熟,查了一下,别人是这么解释的:
因为浏览器默认一次会请求 favicon.ico,添加下面代码就行:
if (req.url === '/favicon.ico') {
return;
}
试了一下,可以的。对了,还有一点,就是返回数据的时候,可以这样:
const data = "{age: 27}"
const str = parseUrl.query.callback + '(' + JSON.stringify(data) + ')'
那么前端接收的就是一个 JSON 对象。(所以这就是 JSONP
的由来?那接受字符串也可以叫 StringP
)
以前我没有理解为什么后端这么返回给前端然后就能拿到数据了,后来理解了:
因为浏览器加载完脚本是自动执行的,比如我们加载一个脚本,里面只有一行alert(1)
,那浏览器就会自动弹出1。所以我们请求的结果其实是一个函数 callback(data)
,然后会自动执行,刚好上面定义了这个函数,于是就输出了结果。
只能支持 GET
请求,说加载脚本却拿数据,欺骗浏览器的感情。
因为现在的浏览器都支持 CORS(跨域资源共享)
,不一定需要 JSONP
这种方式。实验了一下这个东西,然后发现响应头 Access-Control-Allow-Origin
只支持单域名。你要是不想设置 *
号的话咋办呢?这样写:
if (req.headers.origin === 'http://localhost:2999' || req.headers.origin === 'http://localhost:3000') {
res.writeHead(200, {'content-Type':'text/html;charset=UTF-8', 'Access-Control-Allow-Origin': '*'})
}
把你需要添加的白名单都判断一下。
(完)
最近我正在学习 React hooks 的 API,已经被它的表现惊呆了。Hooks 让我可以通过极少的行数来重写数十行的样板代码。不幸的是,这种便利性需要付出一些代价,我发现一些更高级的 hooks 例如 useCallback
和 useMemo
难以学习,在一开始有点反直觉。
在本文中,我将通过一些简单的例子来说明为什么我们需要这些 hooks,在什么时候需要使用它们以及怎么使用。这不是一篇关于 hooks 的介绍,你需要熟悉 useState 以了解下面的内容,
在我们开始之前,让我们引入一个帮助按钮组件。我们将使用 React.memo 把它放入一个记忆组件中。这将强制让 React
不再重新渲染它,除非它的某些属性值改变了。我们还需要添加一个随机颜色作为背景以便我们能够在组件重新渲染时跟踪它。
import React, { useState, useCallback } from 'react';
// 当被调用时随机生成颜色
const randomColour = () => '#'+(Math.random()*0xFFFFFF<<0).toString(16);
// props 的类型
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
// 一个有着随机背景色的记忆按钮
const Button = React.memo((props: ButtonProps) =>
<button onClick={props.onClick} style={{background: randomColour()}}>
{props.children}
</button>
)
现在让我们看一看下面的简单的 app
例子,它展示了两个数字 - c
和 delta
。一个按钮允许用户对 delta
加一。另一个按钮允许用户通过添加 delta
以增加 Counter
。我们将创建 increment
和 incrementDelta
两个方法,并把它们绑定到按钮的点击事件。让我们看看当用户点击按钮时,这样的函数会被创建多少次:
import React, { useState } from 'react';
// 跟踪 app 存在时所有创建的函数
const functions: Set<any> = new Set();
const App = () => {
const [delta, setDelta] = useState(1);
const [c, setC] = useState(0);
const incrementDelta = () => setDelta(delta => delta + 1);
const increment = () => setC(c => c + delta);
// 注册函数以便计算它们
functions.add(incrementDelta);
functions.add(increment);
return (<div>
<div> Delta is {delta} </div>
<div> Counter is {c} </div>
<br/>
<div>
<Button onClick={incrementDelta}>Increment Delta</Button>
<Button onClick={increment}>Increment Counter</Button>
</div>
<br/>
<div> Newly Created Functions: {functions.size - 2} </div>
</div>)
}
当我们运行 app
并且开始点击按钮,我们观察到一些有趣的事情。每次点击按钮都会创建两个新的函数!并且,每次改变两个按钮都会重新渲染!
组件的每次重新渲染,都会创建两个新的函数。每次改变都会重新渲染两个按钮
换句话说,在每次重新渲染我们都创建了两个新的函数。如果我们增加 c
,我们为什么需要重新创建 incrementDelta
函数呢?这不仅是记忆 - 它导致了子组件不必要地重新渲染。这将迅速成为一个性能问题。
一个可能的解决方案是将这两个函数移到功能组件 App
的外面。不幸的是,这样并不会奏效,因为它们使用了 App
作用域外的状态变量。
这是为什么需要引入 useCallback
的原因。它以一个函数作为参数,并返回一个缓存的\记忆的版本。它还需要第二个参数,稍后再做介绍。让我们用 useCallBack
重写:
const App = () => {
const [delta, setDelta] = useState(1);
const [c, setC] = useState(0);
// No dependencies (i.e. []) for now
const incrementDelta = useCallback(() => setDelta(delta => delta + 1), []);
const increment = useCallback(() => setC(c => c + delta), []);
// Register the functions so we can count them
functions.add(incrementDelta);
functions.add(increment);
return (<div>
<div> Delta is {delta} </div>
<div> Counter is {c} </div>
<br/>
<div>
<Button onClick={incrementDelta}>Increment Delta</Button>
<Button onClick={increment}>Increment Counter</Button>
</div>
<br/>
<div> Newly Created Functions: {functions.size - 2} </div>
</div>)
}
这将阻止新函数的实例化以及不必要的重新渲染。然而,当我们重新运行 app
,我们注意到我们以及引入了一个 bug
。如果我们把 detla
增加到2,然后试着增加计数器,它的值增加了1而不是2:
无论 delta 的状态有没有改变,不会有新的函数被创建。在初始化的渲染中, useCallback
创建了一个单独、缓存的 “increment” 版本,封装了 detla 的状态值,在后面的每次重新渲染时都重复使用。
这是因为在 increment
函数的初始化时,delta
的值是1,该值会被函数的作用域捕获。由于我们缓存了 increment
实例,它不会重新创建并会使用初始的作用域的值 detla = 1
。.
useCallback
创建了一个单独、缓存的 increment
版本,封装了 delta
的初始值。当 App
使用不同的 detla
值重新渲染时, useCallback
返回一个先前的 increment
函数的版本,该函数保留第一次渲染时的delta旧值。
我们需要告诉 useCallback
在每次 delta
改变时创建新的、缓存的 increment
版本。
这是 useCallback
第二个参数出现的地方。它是一系列值的数组,代表了缓存的依赖。如果依赖项的值相等,则在随后的任何两个重渲染中,useCallback将返回相同的缓存函数实例。
我们可以使用依赖以解决前面的 bug
:
const incrementDelta = useCallback(() => setDelta(delta => delta + 1), []);
// Recreate increment on every change of delta!
const increment = useCallback(() => setC(c => c + delta), [delta]);
现在我们可以看到,只有 delta
变化时,新的 increment
函数才会创建。所以,counter
按钮只会在 delta
改变时才会重新渲染,因为新的 onClick
属性实例被添加。换句话说,我们只会创建一个新的回调,如果它使用的闭包部分(即依赖)自上一次渲染时改变了。
每次 delta
的改变都会创建一个新的 increment
函数。仅重新创建依赖改变的函数
useCallback
的一个很有用的特性是,如果依赖没有改变,它会返回了相同的函数实例。因此我们可以在其它的 hooks 的依赖列表中使用它。例如,让我们创建一个缓存/记忆函数来增加全部数字:
const incrementDelta = useCallback(() => setDelta(delta => delta + 1), []);
const increment = useCallback(() => setC(c => c + delta), [delta]);
// Can depend on [delta] instead, but it would be brittle
const incrementBoth = useCallback(() => {
incrementDelta();
increment();
}, [increment, incrementDelta]);
新的 incrementBoth
函数依赖 delta
,我们可以使用 useCallback(... ,[delta])
。然而,这是一个十分脆弱的方法!如果我们改变 increment
或者 incrementDelta
的依赖,我们不得不记住 incrementBoth
的依赖的改变。
由于 increment
或者 incrementDelta
的引用不会改变,除非它们的依赖改变了,我们才能使用它们。依赖可以被忽略!这是一个简单的规则:
在功能组件范围内声明的每个函数都必须使用 useCallback
进行存储/缓存。如果它从组件作用域引用函数或其他变量,则应在其依赖列表中列出它们。
可以由linter强制执行此规则,以检查useCallback缓存相关性是否一致。
两个相似的 hooks - useCallback 和 useMemo
React 引入了另一个相似的 hook,叫做 useMemo
。它有相同的签名,但是工作方式不同。不像 useCallback
缓存提供的函数实例,useMemo
调用提供的函数并缓存其结果:
const [c, setC] = useState(0);
// This value will not be recomputed between re-renders
// unless the value of c changes
const sinOfC: number = useMemo(() => Math.sin(c) , [c])
与使用 useCallback
一样,useMemo
返回的值可以用作其它 hooks 的依赖项。
有趣的是,useMemo
也可以缓存函数值。换句话说,它是 useCallback
的通用版本,在以下示例可以替换
// Some function ...
const f = () => { ... }
// The following are functionally equivalent
const callbackF = useCallback(f, [])
const callbackF = useMemo(() => f, [])
在编写 js 的过程中,我们肯定会用到条件语句。
大部分的条件语句都是用 if...else
,适用于“非黑即白”的情况:
根据不同的登录态做不同的行为:
if (isLogin) {
// do something
} else {
this.login()
}
根据不同的性别做不同的行为:
if (sex === 'male') {
// do something
} else {
// do something
}
也有一些时候条件的情况可能超过两种,这个时候可以用 if...else if...else:
根据不同的等级做不同的行为:
if (grade === 'A') {
// do something
} else if (grade === 'B') {
// do something
} else {
// do something
}
当然,还会有某些情况下,条件可能会更多,这个时候就有很多种选择方案了:
根据不同的日期吃不同的食物:
function eat(food) {
console.log('eat food:', food)
}
function getFood(day) {
if (day === 'day0') {
eat('apple')
} else if (day === 'day1') {
eat('banana')
} else if (day === 'day2') {
eat('orange')
} else if (day === 'day3') {
eat('noodle')
} else if (day === 'day4') {
eat('milk')
} else if (day === 'day5') {
eat('rice')
} else if (day === 'day6') {
eat('water')
}
}
(插一句,我们可以尽可能把出现概率高的放在前面,这样可以提高效率。)
这种情况下有七个判断条件,可以看到可能有一点乱,这个时候,就可以考虑用 switch 语句了,这是针对较多条件下的判断方式:
function eat(food) {
console.log('eat food:', food)
}
function getFood(day) {
switch (day) {
case 'day0':
eat('apple')
break;
case 'day1':
eat('banana')
break;
case 'day2':
eat('orange')
break;
case 'day3':
eat('noodle')
break;
case 'day4':
eat('milk')
break;
case 'day5':
eat('rice')
break;
default:
eat('water')
}
}
可以看到,虽然代码行数多了一点,但是更加简洁易懂。
另:关于 switch,有几个注意点。
switch (true) {
case age > 0 && age < 20:
// do something
break;
// ...
}
一般讲到这里基本就结束了,不过...
比如我们可以把上面的“一周的日期”这个场景重写一下:
function eat(food) {
console.log('eat food:', food)
}
const foodObj = {
'day0': 'apple',
'day1': 'banana',
'day2': 'orange',
'day3': 'noodle',
'day4': 'milk',
'day5': 'rice',
'day6': 'water'
}
function eatFood(day) {
eat(foodObj[day])
}
可以发现,代码量是不是少了很多。将条件变成对象键值对。
当然,可以用对象就可以用 map 结构:
function eat(food) {
console.log('eat food:', food)
}
const foodMap = new Map([
['day0', 'apple'],
['day1', 'banana'],
['day2', 'orange'],
['day3', 'noodle'],
['day4', 'milk'],
['day5', 'rice'],
['day6', 'water']
])
function eatFood(day) {
eat(foodMap.get(day))
}
对象和 map 的差别主要是顺序不同,然后 map 的数量可以通过 map.size 拿到,并且 map 的键不止是字符串。
这种方式的好处在多条件下更加明显,回到上面的一个例子:
function eat(food) {
console.log('eat food:', food)
}
function play(item) {
console.log('play', item)
}
function getFood(sex, day) {
if (sex === 'boy') {
if (day === 'day0') {
eat('apple')
play('computer')
} else if (day === 'day1') {
eat('banana')
play('basketball')
} else if (day === 'day2') {
eat('orange')
play('baseball')
} else if (day === 'day3') {
eat('noodle')
play('football')
} else if (day === 'day4') {
eat('milk')
play('pingpong')
} else if (day === 'day5') {
eat('rice')
play('golf')
} else if (day === 'day6') {
eat('water')
play('volleyball')
}
} else {
if (day === 'day0') {
eat('apple1')
} else if (day === 'day1') {
eat('banana1')
} else if (day === 'day2') {
eat('orange1')
} else if (day === 'day3') {
eat('noodle1')
} else if (day === 'day4') {
eat('milk1')
} else if (day === 'day5') {
eat('rice1')
} else if (day === 'day6') {
eat('water1')
}
play('makeUp') // 女生肯定天天化妆
}
}
在多个条件的情况下,代码量会成倍增加,很是难看。但是如果我们用 map 来做的话:
function eat(food) {
console.log('eat food:', food)
}
function play(item) {
console.log('play', item)
}
const map1 = new Map([
['day0_boy', ['apple', 'computer']],
['day1_boy', ['banana', 'basketball']],
['day2_boy', ['orange', 'baseball']],
['day3_boy', ['noodle', 'football']],
['day4_boy', ['milk', 'pingpong']],
['day5_boy', ['rice', 'golf']],
['day6_boy', ['water', 'volleyball']],
['day0_girl', ['apple1', 'makeUp']],
['day1_girl', ['banana1', 'makeUp']],
['day2_girl', ['orange1', 'makeUp']],
['day3_girl', ['noodle1', 'makeUp']],
['day4_girl', ['milk1', 'makeUp']],
['day5_girl', ['rice1', 'makeUp']],
['day6_girl', ['water1', 'makeUp']]
])
function foodAndPlay(day, sex) {
eat(map1.get(`${day}_${sex}`)[0])
play(map1.get(`${day}_${sex}`)[1])
}
这样写简洁易懂,上面的那种用对象也可以实现,下面这种就只能通过 map 了,因为对象的 key 只能是字符串:
function eat(food) {
console.log('eat food:', food)
}
function play(item) {
console.log('play', item)
}
const map2 = new Map([
[{day: 'day0', sex: 'boy'}, ['apple', 'computer']],
[{day: 'day1', sex: 'boy'}, ['banana', 'basketball']],
[{day: 'day2', sex: 'boy'}, ['orange', 'baseball']],
[{day: 'day3', sex: 'boy'}, ['noodle', 'football']],
[{day: 'day4', sex: 'boy'}, ['milk', 'pingpong']],
[{day: 'day5', sex: 'boy'}, ['rice', 'golf']],
[{day: 'day6', sex: 'boy'}, ['water', 'volleyball']],
[{day: 'day0', sex: 'girl'}, ['apple1', 'makeUp']],
[{day: 'day1', sex: 'girl'}, ['banana1', 'makeUp']],
[{day: 'day2', sex: 'girl'}, ['orange1', 'makeUp']],
[{day: 'day3', sex: 'girl'}, ['noodle1', 'makeUp']],
[{day: 'day4', sex: 'girl'}, ['milk1', 'makeUp']],
[{day: 'day5', sex: 'girl'}, ['rice1', 'makeUp']],
[{day: 'day6', sex: 'girl'}, ['water1', 'makeUp']]
])
function foodAndPlay(day, sex) {
const result = [...map2].filter(([key, value]) => (key.day === day && key.sex === sex))
result.forEach(([key, value] => {
eat(value[0])
play(value[1])
}))
}
上面的result.forEach方法可以直接换成下面这个,不过可能有点语义上的不友好:
eat(result[0][1][0])
play(result[0][1][1])
现在需求改了,小姐姐们为了减肥,决定每天都只喝水,那么 map 就会变成下面这样:
const map3 = new Map([
[{day: 'day0', sex: 'boy'}, ['apple', 'computer']],
[{day: 'day1', sex: 'boy'}, ['banana', 'basketball']],
[{day: 'day2', sex: 'boy'}, ['orange', 'baseball']],
[{day: 'day3', sex: 'boy'}, ['noodle', 'football']],
[{day: 'day4', sex: 'boy'}, ['milk', 'pingpong']],
[{day: 'day5', sex: 'boy'}, ['rice', 'golf']],
[{day: 'day6', sex: 'boy'}, ['water', 'volleyball']],
[{day: 'day0', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day1', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day2', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day3', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day4', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day5', sex: 'girl'}, ['water1', 'makeUp']],
[{day: 'day6', sex: 'girl'}, ['water1', 'makeUp']]
}
可以看到,小姐姐们每一天都是重复的,那这样写其实很浪费,怎么办呢:
function eat(food) {
console.log('eat food:', food)
}
function play(item) {
console.log('play', item)
}
const map3 = new Map([
[/^boy_day0$/, ['apple', 'computer']],
[/^boy_day1$/, ['banana', 'basketball']],
[/^boy_day2$/, ['orange', 'baseball']],
[/^boy_day3$/, ['noodle', 'football']],
[/^boy_day4$/, ['milk', 'pingpong']],
[/^boy_day5$/, ['rice', 'golf']],
[/^boy_day6$/, ['water', 'volleyball']],
[/^girl_day[0-6]$/, ['water1', 'makeUp']]
])
function foodAndPlay(day, sex) {
const result = [...map3].filter(([key, value]) => (key.test(`${sex}_${day}`)))
result.forEach(([key, value]) => {
eat(value[0])
play(value[1])
})
}
利用正则来实现条件,是不是很赞!
其实我以前就写过,比如对代码转义来减少 XSS 攻击:
####常规写法:
function transformHtml(str) {
return str.replace(/[&<>'"]/g, (item) => {
switch (item) {
case '&':
return '&';
case '<':
return '<';
case '>':
return '>';
case '’':
return ''';
case '"':
return '"'
}
})
}
escapeHTML('<script>1</script>') // "<script>1</script>"
const escapeHTML = str =>
str.replace(
/[&<>'"]/g,
tag =>({'&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }[tag] || tag)
);
escapeHTML('<script>1</script>') // "<script>1</script>"
// 记住一句,有花括号就要 return
let fn1 = function (a) {
a
}
let fn2 = function (a) {
return a
}
let fn3 = (a) => (a)
let fn4 = (a) => { a }
let fn5 = (a) => {return a}
fn1(1) // undefined
fn1(2) // 1
fn1(3) // 1
fn1(4) // undefined
fn1(5) // 1
完。
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.