clumsyme / blogs Goto Github PK
View Code? Open in Web Editor NEWMarkDown files for blog articles
MarkDown files for blog articles
// 字符串原型比较方法
a.localeCompare(b, 'zh-Hans-CN')
// 字符串数组排序
sa.sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'))
// 或者比较属性
sa.sort((a, b) => a.name.localeCompare(b.name, 'zh-Hans-CN'))
// 大量数据使用Intl.Collator对象
var ic = new Intl.Collator('zh-Hans-CN')
sa.sort(ic.compare)
// 或者比较属性
sa.sort((a, b) => ic.compare(a.name, b.name))
state
与props
的改变都可引起组件的render
,
如果在接收到数据后还要对数据进行处理,则使用state
管理数据可知道什么时候数据改变以更改状态,可减少数据处理代码的运行。
使用props
直接在render
中处理数据可保证数据改变时直接反映在组件内,cmd、cwm
等周期函数则不行,数据处理函数在数据不改变的情况下作了无用处理,一个方法是在cwrp
周期函数检查props
,在属性变化时再处理数据。
最好是在数据改变的地方处理数据,作为props
传递给子组件。如原始数据在redux
处直接处理,传递给子组件,就减少了不必要的数据处理。
// this is like __iter__ method in python
class MyIter {
constructor(limit) {
this.now = 0
this.limit = limit
this[Symbol.iterator] = () => ({
next: () => {
if (this.now < this.limit) {
this.now += 1
return { value: this.now**2, done: false }
} else {
return { done: true }
}
}
})
}
}
let mi = new MyIter(5)
[...mi] // [1, 4, 9, 16, 25]
// Then again
[...mi] // []
let nmi = new MyIter(3)
for (let i of nmi) {
console.log(i)
}
// 1
// 4
// 9
class MyIter {
constructor(limit) {
this.now = 0
this.limit = limit
}
[Symbol.iterator]() {
return {
next: () => {
if (this.now < this.limit) {
this.now += 1
return { value: this.now ** 2, done: false }
} else {
return { done: true }
}
}
}
}
}
子组件在运行componentWillReceiveProps
函数时,若属性值是可变类型(JS对象,如数组),修改只是对原对象的修改,则this.props
和nextProps
将指向同一个对象。
典型的,
class Parent extends Component {
state = {
i: 0,
nums: []
}
onClick = () => {
let { nums, i } = this.state
nums.push(i++)
this.setState({ nums, i })
}
render() {
return (
<div>
<button onClick={this.onClick}>{this.state.i}</button>
<Child nums={this.state.nums} />
</div>
)
}
}
Child
组件每次在nums
属性变化时,this.props
和nextProps
都指向同一个对象this.stat.nums
。如何实现对比?
将this.props
缓存,如
componentWillReceiveProps(nextProps) {
if (nextProps.nums.length !== this.state.nums.length) {
this.setState({
nums: JSON.parse(JSON.stringify(nextProps.nums))
})
// 已对比出变化,可执行自己的代码
}
}
该方法可不依赖父级组件的代码,即使父组件每次传递过来的都是同一个对象也没关系。
但属性传递逻辑较复杂。
在父组件内设置状态时,避免新旧状态指向同一对象:
手动解决:
onClick = () => {
let { nums, i } = this.state
nums.push(i++)
nums = JSON.parse(JSON.stringify(nums))
this.setState({ nums, i })
}
一个指定了flex属性的容器,在其子元素尺寸过大时会撑大该容器,即使指定其flex: 0 0
。
这样的话,就会使容器及其各父元素内容overflow
。
而值得注意的是,如果指定了容器的width
,则只需设置可能过大的子元素的overflow
即可避免父元素overflow
,而单指定flex-basis
不会产生width
效果,也就是即使指定过大子元素的overflow
,父元素仍然overflow
。
因此如果只想让子元素overflow: auto
,即子元素过大时自己滚动,不会导致父元素被撑大,首先设置子元素overflow: auto
,然后:
overflow: hidden
width
。(若希望子元素未指定overflow
属性时不被hidden
时用这个)commonjs 在 require 时会先执行引入的模块,所以循环引用时:被引入文件再引入原文件的时候,只引入了 require 语句之前那部分。
// a.js
let { b } = require('./b')
let a = 'a'
exports.a = a
console.log('in a:', a, b)
setTimeout(() => {
console.log('in a timeout:', a, b)
})
//b.js
let { a } = require('./a')
let b = 'b'
exports.b = b
console.log('in b:', a, b)
setTimeout(() => {
console.log('in b timeout:', a, b)
})
node a
输出:
in b: undefined b
in a: a b
in b timeout: undefined b
in a timeout: a b
直接 require 变量,在该模块内,该变量不会变,类似闭包。
但是 require 整个包的话,包可以作为引用:
// a.js
let moduleB = require('./b')
let a = 'a'
exports.a = a
console.log('in a:', a, moduleB.b)
setTimeout(() => {
console.log('in a timeout:', a, moduleB.b)
})
// b.js
let moduleA = require('./a')
let b = 'b'
exports.b = b
console.log('in b:', moduleA.a, b)
setTimeout(() => {
console.log('in b timeout:', moduleA.a, b)
})
node a
输出:
in b: undefined b
in a: a b
- in b timeout: undefined b
+ in b timeout: a b
in a timeout: a b
写React App时,用Dan Abramov介绍的模式将组件分为容器组件与展示组件确实更利于理解app逻辑,但是有时候写组建的时候难免会纠结一个组件是写作容器组件还是写作展示组件。
写作容器组件的话,可能这个组件只是一个容器组件的子组件,从父组件直接获取props
可能更简单,而如果该组件写作容器组件,该组件的子元素同样面临这个问题,一级一级往下究竟到哪一步可以写作展示组件呢?;
写作展示组件的话(通常推荐用函数构建,所以没有周期函数,没有内部状态),组件自身也可能有状态,也有可能有各种回调,可能会改变父组件状态,也有可能发送http请求等。
结合一直以来的经验,总结几点选择依据:
以上只是一些参考依据,实际中可能不完全对。而且应该要明白:编程模式是用来方便我们编写程序的,而不是束缚手脚让我们的代码来适应模式的。容器组件和展示组件都可能有容器子组件和展示子组件,它们的界限也不是那么明确,所以不一定要在一开始就确定好一个组件是哪种类型;如果一开始分不出用哪种类型,大可暂时不管这个问题。
[]
等于false
么JavaScript中视为false
的有false, null, undefined, 0, "", NaN
。
那么,看下边的表达式:
[] == false
// true
if ([]) {
console.log('啦啦啦')
}
// 啦啦啦
额?[]
不是等于false
么?
而且,再看这里:
![] == false
// true
=_=!
不过这不是bug,而是由于JavaScript语言对==
的处理方式引起的。
==
===
Object.is
先说后两个:
===
全等操作符比较两个值是否相等,两个被比较的值在比较前都不进行隐式转换。如果两个被比较的值具有不同的类型,这两个值是不全等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是 number 类型时,两个值全等。最后,如果两个值都是 number 类型,当两个都不是 NaN,并且数值相同,或是两个值分别为 +0 和 -0 时,两个值被认为是全等的。
在日常中使用全等操作符几乎总是正确的选择。对于除了数值之外的值,全等操作符使用明确的语义进行比较:一个值只与自身全等。对于数值,全等操作符使用略加修改的语义来处理两个特殊情况:第一个情况是,浮点数 0 是不分正负的。区分 +0 和 -0 在解决一些特定的数学问题时是必要的,但是大部分境况下我们并不用关心。全等操作符认为这两个值是全等的。第二个情况是,浮点数包含了 NaN 值,用来表示某些定义不明确的数学问题的解,例如:正无穷加负无穷。全等操作符认为 NaN 与其他任何值都不全等,包括它自己。(等式 (x !== x) 成立的唯一情况是 x 的值为 NaN)
Object.is()
Object.is 在三等号判等的基础上特别处理了 NaN 、 -0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会返回 true。
==
与全等操作符不同的是,非严格相等操作符在比较两个值A == B
的时候,如果两者类型不同,会先进行类型转换,转换为同一类型后再进行比较。具体的转换规则如下:
Undefined | Null | Number | String | Boolean | Object | |
---|---|---|---|---|---|---|
Undefined | true | true | false | false | false | IsFalsy(B) |
Null | true | true | false | false | false | IsFalsy(B) |
Number | false | false | A === B | A === ToNumber(B) | A=== ToNumber(B) | A=== ToPrimitive(B) |
String | false | false | ToNumber(A) === B | A === B | ToNumber(A) === ToNumber(B) | ToPrimitive(B) == A |
Boolean | false | false | ToNumber(A) === B | ToNumber(A) === ToNumber(B) | A === B | false |
Object | false | false | ToPrimitive(A) == B | ToPrimitive(A) == B | ToPrimitive(A) == ToNumber(B) | A === B |
ToNumber(A) 尝试在比较前将参数 A 转换为数字,这与 +A(单目运算符+)的效果相同。通过尝试依次调用 A 的A.toString 和 A.valueOf 方法,将参数 A 转换为原始值(Primitive)。
第一列为被比较值A的类型,第一排为被比较值B的类型。
[] == false
如此我们看[] == false
,[]
是Object
类型,false
是Boolean
类型,所以真正的比较表达式是:
ToPrimitive([]) == ToNumber(false)
ToPrimitive([])
通过调用[].toString()得到空字符串""
,ToNumber(false)
将false
转换为数字0
,因此:
"" == 0
然后是字符串与数字进行非严格相等比较,又进行了类型转换:
ToNumber("") === 0
也就是:
0 === 0
因此返回true
。
所以,[] == false
表达式的值为true
,但空数组最想本身[]
是不被视为false
的。
![] == false
逻辑非操作符!
的优先级(15)高于等号操作符优先级(10),因此在比较前先进行的是![]
,结果是false
,因此实际比较是:
false == false
因此返回true
。
Explicit is better than implicit.
Readability counts.
好的代码应该是清晰明了、可读性好的,语言最好也是。而在一个相等操作符==
上,JavaScript进行了如此多的内部操作,不仅新手容易迷惑,就连有经验的程序员也可能一时搞不明白一个表达式到底出了什么问题。因此也有了如下观点:
最好永远都不要使用相等操作符。全等操作符的结果更容易预测,并且因为没有隐式转换,全等比较的操作会更快。
backdrop-filter
属性可以很方便的实现毛玻璃效果,其作用是将其值作用于在该元素后边的元素上。
.cover {
backdrop-filter: blur(5px);
}
如此就可以使.cover
具有了毛玻璃效果。
<div style={{position: this.state.collapsed ? 'static' : 'sticky'}}></div>
不管state
如何,上述div
从来没有定位为sticky
,其它定位可以,原生js
设置CSS position属性也不能设置为sticky
,应该是Safari的问题。
Chrome, FireFox正常
rgba
的 a
为 Alpha 合成,可以产生部分透明或全透明的视觉效果。
opacity
为透明度。
用法与区别:
rgba
:作用于一个元素,应用于color
、backgroundColor
、borderColor
等属性
opacity
: 作用于整个元素及其子元素
@supports <supports_condition> {
/* specific rules */
}
在supports_condition
为true
时应用specific rules
。
@support (--css-variables: 1) {
.test {
--main-color: lime
}
}
上例在浏览器支持--css-variables: 1
这样的CSS样式格式时,test
类会应用--main-color: lime
这一条样式规则。
and
@support((--a: 1) and (dispaly: flex)) {
.test {
--flex: flex;
dispaly: var(--flex)
}
}
or
@support((display: flex) and (dispaly: grid)) {
.test {
/* lalala */
}
}
not
@support(not (--a: 1) ) {
.test {
/* lalala */
}
}
@support(not ((--a: 1) and (display: grid))) {
.test {
/* lalala */
}
}
有width
属性的元素,margin-left
和margin-right
为负值会移动元素,
没有width
属性的元素,margin-left
和margin-right
为负值增加元素宽度,
margin-top
为负值会向上移动元素
margin-bottom
为负值会向上移动下边的元素
haskell 内的 Functor 定义:
class Functor f where
fmap :: (a -> b) -> f a -> f b
简单来说,Functor 就是一个定义了 fmap 方法的对象,它可以被作为 map
函数的参数。
map
接收一个 a -> b
类型的函数和一个函子 f a
,返回一个新的函子 f b
类型的函数。
function plusOne(x) {
return x + 1
}
plusOne([3]) // ! wrong
R.map(plusOne, [3]) // [4]
// ---> [3] -> [plsuOne(3)]
R.map(plusOne, MyObject(1)) // MyObject(plusOne(1))
//=> MyObject(2)
R.map(function(x) { return 'I am ' + x}, MyObject("Yo")) // MyObject("I am Yo")
R.map(function(x) { return x.id}, MyObject({id: 2718})) // MyObject(2718)
// map 方法就像是打开 MyObject,对 MyObject 内的值进行操作,再关闭 MyObject
class MyObject {
constructor(value) {
this.__value = value
}
map(func) {
return new MyObject(func(this.__value))
}
}
// 我们通过对 MyObject 定义 map, MyObject 可以看做一个 Functor
// some use of Functor
// type safe
class Maybe {
constructor(value) {
this.__value = value
}
map(func) {
return this.__value ? new Maybe(func(this.__value)) : new Maybe(null)
}
}
R.map(plusOne, new Maybe(1)) // Maybe(2)
R.map(plusOne, new Maybe(null) // Maybe(null)
// 对于 Functor,我们需要调用 map 方法来修改它的值,而不能直接修改它的值。
//! 动态的类型安全
// default value
class Either {
// left as default
constructor(left, right) {
this.__left = left
this.__right = right
}
map(func) {
return this.right
? new Either(this.__left, func(this.__right))
: new Either(func(this.__left), this.__right)
}
}
R.map(plusOne, new Either(1, 2)) // Either(1, 3)
R.map(plusOne, new Either(1, null)) // Either(2, null)
// auto handle Promise
Promise.prototype.map = function(func) {
let thisPromise = this
return this.then(function(response) {
return func(response)
})
}
function handleResponse(response) {
return response.json().then(json => {
// doSthWithJson
return 'done'
})
}
let mapped = R.map(handleResponse, fetch('/someAPI'))
await mapped // 'done'
// DEMO
// we have a side effect
let setProp = function (prop, value, obj) {
obj[prop] = value
return obj
}.autoCurry()
let title = document.getElementById('title')
// getGreeting :: User -> String
let getGreeting = compose(R.concat("Welcome "), R.prop("name"))
// updateGreetingHtml :: User -> undefined
let updateGreetingHtml = compose(setProp('textContent', R.__, title), getGreeting)
updateGreetingHtml(currentUser)
// what if we dont have a currentuser
map(updateGreetingHtml, Either({name: 'Guest'}, currentUser))
// 使用 Functor,你不能直接操作 Functor 的值,在程序其他地方必须使用 map(包括 filter) 来进行操作,
// 你不必关心其内部结构,只用知道他可以被 map
function App() {
return (
<div>
<h1>0</h1>
<button>Click</button>
</div>
)
}
stateDiagram
FiberRootNode: ReactDom.createRoot 生成
HostRootFiber: typeof:FiberNode
HostRootFiber: tag:HostRoot
HostRootFiber: return:null
container: 渲染 Dom 节点,#root
FiberRootNode --> HostRootFiber: current
HostRootFiber --> FiberRootNode: stateNode
container --> HostRootFiber: __reactContainere$random
FiberRootNode --> container: containerInfo
HostRootFiber --> App: child
App --> HostRootFiber: return
App --> div: child
div --> App: return
div --> h1: child
h1 --> div: return
h1 --> button: sibling
button --> div: return
FiberRootNode --> HostRootFiber:alter: Nextcurrent
HostRootFiber:alter --> App:alter: child
App:alter --> HostRootFiber:alter: return
App:alter --> div:alter: child
div:alter --> App:alter: return
div:alter --> h1:alter: child
h1:alter --> div:alter: return
h1:alter --> button:alter: sibling
button:alter --> div:alter: return
React Hooks
如何知道每次渲染对应正确的 hooks?
React Hooks的结构是一个链表:
每一个 Hooks 对象结构为:
{
memoizedState: null,
queue: null,
next: null,
}
有一个链表的头部,在 React 内部称为 firstWorkInProgressHook
,保存着组件内所有的 Hooks。
还有一个链表,workInProgressHook
,对应的是上述链表的一个节点,在组件内部就是调用useMyHook()
的时候的当前的 Hooks。每一次我们调用useMyHook()
,React 内部会调用一个方法来生成一个 workInProgressHook
,其源代码为(React-Dom/server的版本,与 ReactFiberHooks 也就是客户端版本的原理是一致的):
function createWorkInProgressHook(): Hook {
if (workInProgressHook === null) {
// This is the first hook in the list
if (firstWorkInProgressHook === null) {
isReRender = false;
firstWorkInProgressHook = workInProgressHook = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = firstWorkInProgressHook;
}
} else {
if (workInProgressHook.next === null) {
isReRender = false;
// Append to the end of the list
workInProgressHook = workInProgressHook.next = createHook();
} else {
// There's already a work-in-progress. Reuse it.
isReRender = true;
workInProgressHook = workInProgressHook.next;
}
}
return workInProgressHook;
}
我们以组件的两次 render 来介绍该链表是如何初始化以及如何应对更新的:
firstWorkInProgressHook
和 workInProgressHook
都是 null
0️⃣firstWorkInProgressHook = workInProgressHook = createHook()
1️⃣workInProgressHook = workInProgressHook.next = createHook()
2️⃣workInProgressHook = workInProgressHook.next = createHook()
3️⃣finishHooks
,重置 workInProgressHook
为 null
4️⃣0️⃣:
// firstWorkInProgressHook = workInProgressHook = null
firstWorkInProgressHook
↘
null
↗
workInProgressHook
1️⃣:
// workInProgressHook = workInProgressHook.next = createHook()
firstWorkInProgressHook
↘
null Hook1
↗
workInProgressHook
2️⃣:
// workInProgressHook = workInProgressHook.next = createHook()
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2
↗
workInProgressHook
3️⃣:
// workInProgressHook = workInProgressHook.next = createHook()
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
4️⃣:
// workInProgressHook = null
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
下一次 render:
workInProgressHook = firstWorkInProgressHook
1️⃣workInProgressHook = workInProgressHook.next
2️⃣workInProgressHook = workInProgressHook.next
3️⃣finishHooks
,重置 workInProgressHook
为 null
4️⃣0️⃣:
// firstWorkInProgressHook = Hook1
// workInProgressHook = null
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
1️⃣:
// workInProgressHook = firstWorkInProgressHook(Hook1)
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
2️⃣:
// workInProgressHook = workInProgressHook.next(Hook2)
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
3️⃣:
// workInProgressHook = workInProgressHook.next(HookN)
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
4️⃣:
// workInProgressHook = null
firstWorkInProgressHook
↘
null Hook1 ⟶️ Hook2 ...⟶️... HookN
↗
workInProgressHook
所以:
useMyHook()
的时候都会新建一个 Hooks 作为当前的 Hooks(workInProgressHook)这也是为什么我们需要保证在每次渲染的时候各个 Hooks 以相同的顺序被调用。
Window.getComputedStyle() 方法给出应用活动样式表后的元素的所有CSS属性的值,并解析这些值可能包含的任何基本计算。
getComputedStyle 的返回值是 resolved values, 通常跟CSS2.1中的computed values是相同的值。 但对于一些旧的属性,比如width, height, padding 它们的值又为 used values。 最初, CSS2.0定义的计算值Computed values 就是属性的最终值。 但是CSS2.1 重新定义了 computed values 为布局前的值, used values布局后的值。布局前与布局后的区别是, width 或者 height的 百分比可以代表元素的宽度,在布局后,会被像素值替换.
计算值可能是百分数,根据实际布局才能决定数值(像素)。应用值为最终布局后的值,单位为像素。
<div id="out">
<div id="in">
<div id="mostin">
</div>
</div>
</div>
#out
与#in
均有定位,#mostin
为fixed
定位;
若设置#in
最初transform: scale(0)
,后续因事件改变为transform: scale(1)
,则此时#mostin
的定位将会是相对于#in
而不是相对于屏幕
foo.com 页面内发送了一个 bar.com 的 GET 请求, 默认不会携带 cookie,如何解决呢?
fetch
请求允许携带认证信息fetch('http://bar.com', {
credentials: 'include',
})
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://foo.com
此处不能使用通配符*
如果没有2、3,既是携带 cookie,返回信息也会被浏览器阻止
跨域 cookie 被视为第三方 cookie,如果浏览器被用户设置为不接受第三方 cookie,那么上述fetch
请求不会设置 cookie:
Set-Cookie: pageAccess=3;
关闭第三方 cookie 的情况下,该 cookie 将不能被设置
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.