str.indexOf(substr, pos):从给定位置 pos
开始,在 str
中查找 substr
,如果没有找到,则返回 -1
,否则返回匹配成功的位置。
str.lastIndexOf(substr, position):从字符串的末尾开始搜索到开头。
str.includes(substr, pos):根据 str
中是否包含 substr
来返回 true/false
。
str.startsWith(substr):返回 true/false
。
str.endsWith(substr):返回 true/false
。
str.slice(start [, end]):返回字符串从 start
到(但不包括)end
的部分。
- 对原数据 无修改
start/end
可以是负值,表示起始位置从字符串结尾计算
str.substring(start [, end]):返回字符串从 start
到(但不包括) end
的部分。这与 slice
几乎相同,但它允许 start
大于 end
。
- 对原数据 无修改
- 不支持负参数,会被视为
0
。
let str = "stringify"; |
str.substr(start [, length]):返回字符串从 start
开始的给定 length
的部分。
- 对原数据 无修改
start
可以是负数,从结尾算起。- 非核心规范内容,而是在附录 B 中
arr.at(i)
:返回数组 arr
索引 index
对应的元素。如果 i >= 0
,则与 arr[i]
完全相同。i
为负数则从数组的末端向前数。
arr.push(item1, item2, ...)
:在末端添加元素 item
,return arr.length
- 对原数据 有修改
arr.pop()
:从末端取出一个元素,return 取出的元素
- 对原数据 有修改
arr.shift()
:从首端取出一个元素,return 取出的元素
- 对原数据 有修改
arr.unshift(item1, item2, ...)
:在首端添加元素 item
,return arr.length
- 对原数据 有修改
arr.indexOf/lastIndexOf(item, from)
:从索引 from
开始搜索 item
,如果找到则返回索引,否则返回 -1
。
arr.includes(item, from)
:从索引 from
开始搜索 item
,如果找到则返回 true
,否则返回 false
。
- 可以正确识别
NaN
,但indexOf/lastIndexOf
不行
arr.splice(start[, deleteCount, elem1, ..., elemN])
:从索引 start
开始修改 arr
:删除 deleteCount
个元素并在当前位置插入 elem1, ..., elemN
。最后返回被删除的元素所组成的数组。
- 对原数据 有修改
- 允许负数索引,从数组末尾开始计算位置
arr.slice([start], [end])
:索引 start
到 end
(不包括 end
)的数组项复制到一个新的数组并返回。
- 对原数据 无修改
- 两个索引均可为负数,从末尾计算索引。
arr.concat(arg1, arg2...)
:接受任意数量的参数(数组或值都可以),创建一个新数组,其中包含所有参数的值。
- 对原数据 无修改
- 通常只复制数组中的元素。其他对象,即使它们看起来像数组一样,但仍然会被作为一个整体添加:
let arr = [1, 2]; |
- 除非类数组对象具有
Symbol.isConcatSpreadable
属性,那么它就会被concat
当作一个数组来处理:
let arr = [1, 2]; |
arr.forEach((item, index, array) => { ...对 item 进行处理 })
:无返回值
arr.find((item, index, array) => {})
:返回 true
则停止搜索并返回 item
,否则返回 undefined
。
arr.findIndex/findLastIndex((item, index, array) => {})
:与 arr.find
基本相同,但返回找到的元素的 index
而不是 item
,没找到则返回 -1
。
let results = arr.filter((item, index, array) => {});
:处理函数中 return true
则会将 item push
到 results
,如果没有符合条件的,则 results
为空数组。
map((item, index, array) => {})
:返回结果数组
arr.sort(func)
:对数组进行原位排序
- 对原数据 有修改
arr.reverse()
:颠倒 arr
中元素的顺序
- 对原数据 有修改
arr.split(delim)
arr.join(glue)
arr.reduce((accumulator, item, index, array) => {}, [initial])
:
accumulator
是上一个函数调用的结果,第一次等于initial
(如果提供了initial
)arr.reduceRight
和arr.reduce
方法功能一样,只是遍历从右到左
2021 年 01 月 18 日,我作为签定了三方的同学入职了阿里云(杭州)实习。2021 年 07 月 05 日,结束实习,正式入职。2022 年 07 月 06 日,正式离职,结束了这段阿里之旅,共计 367 天(535 天)。
离职前的这一周,收到了 Leader 送的周年纪念品和公司给的“一年香”徽章及工牌带,谢谢 Leader,谢谢公司。
还记得我参加校招面试的时候,有面试官问过我:“你工作之后最想获得什么?”我答:“加深技术深度,拓宽技术广度。”但面试官语重心长地说:“你工作之后会发现,技术并不是最重要的。”现在的我切实感受到了,技术重要,但确实不是最重要的。技术要为业务服务,通过业务拿到了好结果,才能更好地体现技术的价值。
还记得参加工作之前,我认为赚钱是最重要的,但工作之后恰逢疫情反复,切身感受到一家人平安、幸福、快乐才是最重要的。在过去一年多的时间里,我和老婆分居两地,平均两周见一次,聚少离多。每次见面最头疼的不仅是往返近 18 小时的距离,还有疫情政策的变幻无常。因为杭州和广州在两个省,属于跨省通行,所以在政策上更为严格。每次见面不仅要安排出行时间、买票,还要分别查询两个城市的疫情政策,且不乏定好了行程之后出现突发状况导致无法出行。所以,我辞职来广州了。
在过去一年多的时间里,因为异地,我们两个人成了飞机、高铁的“常客”。一开始她趁着公司有苏州的出差机会,申请到苏州出差了两个多月,这样我们每周坐一个多小时的高铁就能见面了。项目结束后,我们就大多乘飞机见面。下图是我的乘机航线图,她的乘机次数比我的还多。在异地的这段时间里,她来找我的次数比我找她的次数多很多,以至于有朋友说“一直都是看你老婆来找你”,哈哈哈。
记得有一次,她从杭州回广州,坐地铁到了萧山机场却发现必须要核酸报告,因为前一天更新了政策,但航空公司没通知我们,我们也没注意到。如果是我遇到这样的事,我肯定非常生气,甚至迁怒于对方。但她在告诉我这件事的时候,语气没有丝毫不好,而是说:“我们又可以待一天啦,晚上我们一起去吃猪肚鸡好不。”我知道她也烦、也累,她坐了两个小时地铁才到机场又得坐回来,明天还得再坐两个小时地铁过去。曾经有同事问我,是什么让你决定结婚,我没有回答,现在我想说,可能就是这样的点点滴滴吧。
当然,在职一年多,从熟悉团队、熟悉项目,到成为了项目 owner。离职则意味着失去了熟悉的团队,熟悉的项目,要重新找工作并融入。但是,哪有两全齐美的事,不都是有得有失嘛。新的风险和挑战,也就意味着新的机遇,我相信自己!
谈完了现状,那么也分享一下我接下来的安排。在过去一年多的时间里,我自认为无论是在技术上,还是在工作的方式方法上,都有了极大的成长,但也存在着知其然不知其所以然的部分。所以我想用一个月到一个半月的时间,整理这一年多以来的所学所想,并搞定之前不知其所以然的部分。再重新过一遍 HTML、CSS、JavaScript 巩固基础。学习学习算法、Node、Webpack、Vite 和计算机基础等相关知识。
此外,我维护的《现代 JavaScript 教程》也有一定的待更新量,这段时间我再把教程系统地更新一下。之后我计划以每月一版的更新节奏,形成月报发布出来。
学习是一方面,代码也不能放下。在学习的过程中也要多实际操练操练,多总结,多分享。
预计在八月中下旬,开始投递简历,寻找下一份工作。我期望能够找到在业务上有前景,我认可,也认可我的一份工作和一群人。
凡是过往,皆为序章。
学文来啦!
]]>注意:这几个命令在不同版本的 Git 中稍有差异。
git add .
:会将当前工作区中当前目录(包括子目录)下的所有新文件和对已有文件的改动提交至暂存区,但不包括被删除的文件。git add -u
:git add --update
的简写形式,它只会监控当前整个工作区中之前已被 add
的文件,即已被跟踪(tracked)的文件,也就是只会将当前整个工作区中被修改和被删除的文件提交至暂存区。而新文件因为未被跟踪(untracked),所以不会被提交至暂存区。git add -A
:git add --all
的简写形式,它会将当前整个工作区中所有的文件改动提交至暂存区,包括新增、修改和被删除的文件,不受当前所在目录限制。注意:你会看到有些文章说 git add -A
属于 git add .
和 git add -u
功能的合集,这是不对的。因为 git add .
只会提交当前目录(包括子目录)下的新文件和对已有文件的改动,而 git add -A
不受当前目录限制。也就是说,git add .
和 git add -u
功能的合集只能属于 git add -A
功能的子集。
总结详见下图:
Git Version 1.x | 新文件 | 被修改的文件 | 被删除的文件 | 是否受当前所在目录限制 | 说明 |
---|---|---|---|---|---|
git add -A. | ✅ | ✅ | ✅ | ❌ | 将当前整个工作区中所有的文件改动提交至暂存区,包括新增、修改和被删除的文件,不受当前所在目录限制 |
git add . | ✅ | ✅ | ❌ | ✅ | 将当前工作区中当前目录(包括子目录)下的所有新文件和对已有文件的改动提交至暂存区,但不包括被删除的文件 |
git add -u. | ❌ | ✅ | ✅ | ❌ | 将当前整个工作区中被修改和被删除的文件提交至暂存区。而新文件因为未被跟踪(untracked),所以不会被提交至暂存区 |
在 Git –version 2.x 中对 git add .
的功能做了改动,git add .
会提交当前工作区中当前目录(包括子目录)下所有的文件改动,不像在 Git –version 1.x 时那样不包括被删除的文件。
Git Version 2.x 中如果想在使用 git add .
时不提交被删除的文件,可以使用 git add --ignore-removal
加上匹配符 .
,即 git add --ignore-removal .
。
git add --ignore-removal
后的匹配符是可以更换的(但不能缺省),例如 git add --ignore-removal -A
可以实现在 git add -A
时不提交被删除的文件。
有些文章说在 Git –version 2.x 中 git add .
和 git add -A
的功能变得完全相同,这是不对的。因为我们之前提到过,git add .
提交的文件改动受当前所在目录限制,它只会提交当前工作区中当前目录(包括子目录)下的文件改动,而 git add -A
不受当前所在目录的限制,提交的是当前整个工作区中所有的文件改动。
git add *
表示添加当前目录(包括子目录)下的所有文件改动,但不包括文件名以 .
符号开头的文件的改动。这是 Shell 命令,git 只是接收文件列表。而 git add .
的功能与 git add *
基本相同,只是 git add .
会将文件名以 .
符号开头的文件的改动也提交至暂存区。
总结详见下图:
Git Version 2.x | 新文件 | 被修改的文件 | 被删除的文件 | 是否受当前所在目录限制 | 说明 |
---|---|---|---|---|---|
git add -A | ✅ | ✅ | ✅ | ❌ | 将当前整个工作区中所有的文件改动提交至暂存区,包括新增、修改和被删除的文件,不受当前所在目录限制 |
git add . | ✅ | ✅ | ❌ | ✅ | 将当前工作区中当前目录(包括子目录)下的所有的文件改动提交至暂存区,包括新增、修改和被删除的文件 |
git add -u. | ❌ | ✅ | ✅ | ❌ | 将当前整个工作区中被修改和被删除的文件提交至暂存区。而新文件因为未被跟踪(untracked),所以不会被提交至暂存区 |
git add * | ❌ | ✅ | ✅ | ✅ | 将当前工作区中当前目录(包括子目录)下的所有的文件改动提交至暂存区,包括新增、修改和被删除的文件,但不包括文件名以 . 符号开头的文件的改动 |
我是学文同学,2021 应届生,全网统一 ID:LeviDing,95 后,东北人。现为 现代 JavaScript 教程 负责人,公众号:技术漫谈 和 编程每日一题 运营者,前 掘金翻译计划 负责人,在 阿里云、掘金 和 墨刀 实习过。本科做的嵌入式开发,硕士做的机器学习,现从事前端开发,努力成为一名全栈开发者,希望能够做出对世界有一点点贡献的产品。喜欢旅行,摄影,骑行,读书,电影,掌机游戏。
今年算是真正系统学习前端知识,把 JavaScript 过了一遍,系统学习了 React 相关内容。但还有很多欠缺和不足,2021 要在 React 相关项目、ECMAScript 规范、CSS、Node.js 等方面多下功夫。
今年我没有参加春招,直接参加了秋招。我觉得自己算是很幸运的,八月初开始参加笔试,中旬开始陆续面试,下旬开始陆续收到意向书和 offer。九月中旬完成了所有面试并收到了大部分意向书,下旬收到了所有意向书。十月、十一月谈薪和签约,十二月签三方。
总体上我就投递了一个批次,面试了大概十家公司,拿到阿里云、拼多多、网易、京东、好未来、斗鱼、顺丰和小鹏汽车 8 个 offer,包括多个 SP,最后签了阿里云。拿到了自己想去的公司,所以就没再投其他公司了,像腾讯、百度、快手、滴滴等公司都没投。感觉自己的技术水平其实挺菜的,感谢面试官对我的认可,面试体验非常好,受益匪浅。
在这里感谢其昌大哥,心雨同学,勤恩师兄,叔平老兄,三金哥,科总,涛哥,涵兄,所有面试官和校招组的同学,还有很多很多人 👏👏👏
今年我在开源项目上的投入主要是 现代 JavaScript 教程。项目创建于 2018 年 04 月,到现在有 Watch:101,Star:4133,Fork:612。在过去的一年里,Star 数从 2000 多涨到了 4000 多,翻了一倍。
今年我把整个教程重新翻译了一遍,花费了很多时间和精力,当然也收获了很多,帮助到了很多同学。目前还剩最后一小部分没优化完,接下来需要做的就是优化完剩余内容,并及时对教程进行跟进和更新,并修复发现的一些问题。
今年在这方面做的事主要有三个:
从 2020-06-25 号开始持续运营微信公众号 技术漫谈,截至撰写本文时有 4000+ 关注者;
组织并参与翻译的《Python 机器学习》一书进入了最后的出版校订环节。
在这儿也对今年在社区各渠道的数据做一个记录:
自媒体包括但不限于微信公众号。2020 年 07 月 06 号开始通过自媒体有了第一笔收入,截至目前,这 7 个月内收入 19910 元。我接的广告都是筛选过的,坑人的广告一律不接。
当然,我坚信人要舍得,有舍才有得。所以我今年通过公众号和朋友圈赠送了约 1500 元的书,发了 3000 块左右的红包。在接下来的这一年里,希望能给关注我的人带来更多的福利。
今年做了 6 次送书活动,做了 6 次公众号互推(2020-10-29 第一次互推),做了大概 5 次朋友圈互推。
在学校做的东西写过研究所内的年度总结,对我个人来讲有意义的就是,完成了自己的一篇大论文并投稿了 IEEE Transactions on Cognitive and Developmental Systems,期望能有一个好的结果。
我认为,对理财收益影响最大的因素是市场,其次是个人的价值取向和对趋势的判断。在疫情爆发之初,股市大跌,我没有减仓,反而加仓,尤其是消费和医疗板块。
因为随着人们生活水平的提高和人口老龄化,对医疗的需求肯定会越来越大。而疫情导致消费被大幅削弱,但在疫情后的消费肯定会逐步恢复。并且在扩大内需的宏观调控下,消费板块未来可期。当然有赌的成分,但是有底层逻辑的。
2020 年行情不错,我一开始的仓位分布不是太好,逐步调整优化,年收益率 34.74%。2021 春节前这一个半月收益率 15.04%,总体也就是 50% 左右。
今年还是看了很多电影,到目前为止,国产的好电影基本都看过了,国外的大部分都看过了。之后再看书看电影啥的在豆瓣记一下,这样就能有个统计。
今年新买了 20 多个 Switch 大作卡带,现在一共有 50 多个,感觉收藏的占比大一些,根本没时间玩那么多 😂
今年玩儿的最多的游戏是塞尔达传说和动物森友会,两个游戏合计时常 700 小时左右吧。
这张图是玩儿塞尔达的时候印象最深也是最感动的一幕,这游戏太长,才玩儿了一小部分,森林里摸鱼、打怪、做菜,就是不去救公主 😂
下面这张图是我动森里的小家,中式园林 😛
今年新购设备:
结束了学校的学生会副主席的工作,好好搞技术,搞学习。
总结完了 2020,再展望一下 2021。下面列的不是 flag,而是今年的重点投入方向。
感谢一年来我所遇到的所有人,谢谢你们。也谢谢所有关注我的人,祝大家新的一年里身体健康,心想事成,天天开心,学习进步,工作顺利 😘
新年快乐 🧨🧨🧨🧨🧨🧨
]]>先上示意图:
React 在 V16.3 版本中,为下面三个生命周期函数加上了 UNSAFE
:
UNSAFE_componentWillMount
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillUpdate
标题中的废弃不是指真的废弃,只是不建议继续使用,并表示在 V17.0 版本中正式删除。先来说说 React 为什么要这么做。
主要是这些生命周期方法经常被误用和滥用。并且在 React V16.0 之前,React 是同步渲染的,而在 V16.0 之后 React 更新了其渲染机制,是通过异步的方式进行渲染的,在 render
函数之前的所有函数都有可能被执行多次。
长期以来,原有的生命周期函数总是会诱惑开发者在 render
之前的生命周期函数中做一些动作,现在这些动作还放在这些函数中的话,有可能会被调用多次,这肯定不是我们想要的结果。
有一个常见的问题,有人问为什么不在 UNSAFE_componentWillMount
中写 AJAX 获取数据的功能,他们的观点是,UNSAFE_componentWillMount
在 render
之前执行,早一点执行早得到结果。但是要知道,在 UNSAFE_componentWillMount
中发起 AJAX 请求,不管多快得到结果也赶不上首次 render,数据都是要在 render
后才能到达。
而且 UNSAFE_componentWillMount
在服务器端渲染也会被调用到(此方法是服务端渲染唯一会调用的生命周期函数。),你肯定不希望 AJAX 请求被执行多次,所以这样的 IO 操作放在 componentDidMount
中更合适。
尤其是在 Fiber 启用了异步渲染之后,更没有理由在 UNSAFE_componentWillMount
中进行 AJAX 请求了,因为 UNSAFE_componentWillMount
可能会被调用多次,谁也不会希望无谓地多次调用 AJAX 吧。
还有人会将事件监听器(或订阅)添加到 UNSAFE_componentWillMount
中,但这可能导致服务器渲染(永远不会调用 componentWillUnmount
)和异步渲染(在渲染完成之前可能被中断,导致不调用 componentWillUnmount
)的内存泄漏。
人们通常认为 UNSAFE_componentWillMount
和 componentWillUnmount
是成对出现的,但这并不能保证。只有调用了 componentDidMount
之后,React 才能保证稍后调用 componentWillUnmount
进行清理。因此,添加监听器/订阅的推荐方法是使用 componentDidMount
生命周期。
有时候组件在 props
发生变化时会产生副作用。与 UNSAFE_componentWillUpdate
类似,UNSAFE_componentWillReceiveProps
可能在一次更新中被多次调用。因此,避免在此方法中产生副作用非常重要。相反,应该使用 componentDidUpdate
,因为它保证每次更新只调用一次。
UNSAFE_componentWillReceiveProps
是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。新的 getDerivedStateFromProps
实际上与 componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。
有些人使用 UNSAFE_componentWillUpdate
是出于一种错误的担心,即当 componentDidUpdate
触发时,更新其他组件的 state
已经”太晚”了。事实并非如此。React 可确保在用户看到更新的 UI 之前,刷新在 componentDidMount
和 componentDidUpdate
期间发生的任何 setState
调用。
通常,最好避免这样的级联更新。当然在某些情况下,这些更新也是必需的(例如:如果你需要在测量渲染的 DOM 元素后,定位工具的提示)。不管怎样,在异步模式下使用 UNSAFE_componentWillUpdate
都是不安全的,因为外部回调可能会在一次更新中被多次调用。相反,应该使用 componentDidUpdate
生命周期,因为它保证每次更新只调用一次。
大多数开发者使用 UNSAFE_componentWillUpdate
的场景是配合 componentDidUpdate
,分别获取 rerender
前后的视图状态,进行必要的处理。但随着 React 新的 suspense
、time slicing
、异步渲染等机制的到来,render
过程可以被分割成多次完成,还可以被暂停甚至回溯,这导致 UNSAFE_componentWillUpdate
和 componentDidUpdate
执行前后可能会间隔很长时间,足够使用户进行交互操作更改当前组件的状态,这样可能会导致难以追踪的 BUG。
React 新增的 getSnapshotBeforeUpdate
方法就是为了解决上述问题,因为 getSnapshotBeforeUpdate
方法是在 UNSAFE_componentWillUpdate
后(如果存在的话),在 React 真正更改 DOM 前调用的,它获取到组件状态信息更加可靠。
除此之外,getSnapshotBeforeUpdate
还有一个十分明显的好处:它调用的结果会作为第三个参数传入 componentDidUpdate
,避免了 UNSAFE_componentWillUpdate
和 componentDidUpdate 配合使用时将组件临时的状态数据存在组件实例上浪费内存,getSnapshotBeforeUpdate
返回的数据在 componentDidUpdate
中用完即被销毁,效率更高。
更多问题详见:
React V16.3 中在废弃(这里的废弃不是指真的废弃,只是不建议继续使用,并表示在 V17.0 版本中正式删除)三个旧的生命周期函数的同时,React 还新增了两个生命周期函数:
static getDerivedStateFromProps
getSnapshotBeforeUpdate
在 React V16.3 版本中加入的 static getDerivedStateFromProps
生命周期函数存在一个问题,就是在生命周期的更新阶段只有在 props
发生变化的时候才会调用 static getDerivedStateFromProps
,而在调用了 setState
和 forceUpdate
时则不会。
React 官方也发现了这个问题,并在 React V16.4 版本中进行了修复。也就是说在更新阶段中,接收到新的 props
,调用了 setState
和 forceUpdate
时都会调用 static getDerivedStateFromProps
。具体在下面讲到这个函数的时候有详细说明。
React 生命周期主要分为三个阶段:
挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。这个阶段的过程如下:
constructor
getDerivedStateFromProps
UNSAVE_componentWillMount
render
componentDidMount
组件的构造函数,第一个被执行。如果在组件中没有显示定义它,则会拥有一个默认的构造函数。如果我们显示定义构造函数,则必须在构造函数第一行执行 super(props)
,否则我们无法在构造函数里拿到 this,这些都属于 ES6 的知识。
在构造函数中,我们一般会做两件事:
state
this
的绑定constructor(props) { |
使用方式:
//static getDerivedStateFromProps(nextProps, prevState) |
新的 getDerivedStateFromProps
是一个静态函数,所以不能在这函数里使用 this
,简单来说就是一个纯函数。也表明了 React 团队想通过这种方式防止开发者滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps
会被调用,这样我们有一个机会可以根据新的 props
和当前的 state
来调整新的 state
。
这个函数会返回一个对象用来更新当前的 state
,如果不需要更新可以返回 null
。这个生命周期函数用得比较少,主要用于在重新渲染期间手动对滚动位置进行设置等场景中。该函数会在挂载时,在更新时接收到新的 props
,调用了 setState
和 forceUpdate
时被调用。
新的 getDerivedStateFromProps
实际上与 componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。UNSAFE_componentWillReceiveProps
也是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。
UNSAVE_componentWillMount
UNSAFE_componentWillMount()
在挂载之前被调用。它在 render()
之前调用,因此在此方法中同步调用 setState()
不会触发额外渲染。通常,我们建议使用 constructor()
来初始化 state。
避免在此方法中引入任何副作用或订阅。如遇此种情况,请改用 componentDidMount()
。
此方法是服务端渲染唯一会调用的生命周期函数。UNSAFE_componentWillMount()
常用于当支持服务器渲染时,需要同步获取数据的场景。
这是 React 中最核心的方法,class 组件中唯一必须实现的方法。
当 render
被调用时,它会检查 this.props
和 this.state
的变化并返回以下类型之一:
render()
函数应该是一个纯函数,里面只做一件事,就是返回需要渲染的东西,不应该包含其它的业务逻辑,如数据请求,对于这些业务逻辑请移到 componentDidMount
和 componentDidUpdate
中。
componentDidMount()
会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法是比较适合添加订阅的地方。如果添加了订阅,请不要忘记在 componentWillUnmount()
里取消订阅
你可以在 componentDidMount()
里直接调用 setState()
。它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前。如此保证了即使在 render()
两次调用的情况下,用户也不会看到中间状态。
请谨慎使用该模式,因为它会导致性能问题。通常,你应该在 constructor()
中初始化 state
。如果你的渲染依赖于 DOM 节点的大小或位置,比如实现 modals
和 tooltips
等情况下,你可以使用此方式处理
更新阶段是指当组件的 props 发生了改变,或组件内部调用了 setState 或者发生了 forceUpdate,则进行更新。
这个阶段的过程如下:
UNSAFE_componentWillReceiveProps
getDerivedStateFromProps
shouldComponentUpdate
UNSAFE_componentWillUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillReceiveProps
是考虑到因为父组件引发渲染可能要根据 props
更新 state
的需要而设立的。UNSAFE_componentWillReceiveProps
会在已挂载的组件接收新的 props
之前被调用。如果你需要更新状态以响应 prop
更改(例如,重置它),你可以比较 this.props
和 nextProps
并在此方法中使用 this.setState()
执行 state
转换。
如果父组件导致组件重新渲染,即使 props
没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。在挂载过程中,React 不会针对初始 props
调用 UNSAFE_componentWillReceiveProps()
。组件只会在组件的 props
更新时调用此方法。调用 this.setState()
通常不会触发 UNSAFE_componentWillReceiveProps()
。
这个方法在挂载阶段已经讲过了,这里不再赘述。记住该函数会在挂载时,在更新时接收到新的 props
,调用了 setState
和 forceUpdate
时被调用。它与 componentDidUpdate
一起取代了以前的 UNSAFE_componentWillReceiveProps
函数。
shouldComponentUpdate(nextProps, nextState) { |
它有两个参数,根据此函数的返回值来判断是否进行重新渲染,true
表示重新渲染,false
表示不重新渲染,默认返回 true
。注意,首次渲染或者当我们调用 forceUpdate
时并不会触发此方法。此方法仅用于性能优化。
因为默认是返回 true
,也就是只要接收到新的属性和调用了 setState
都会触发重新的渲染,这会带来一定的性能问题,所以我们需要将 this.props
与 nextProps
以及 this.state
与 nextState
进行比较来决定是否返回 false
,来减少重新渲染,以优化性能。请注意,返回 false
并不会阻止子组件在 state
更改时重新渲染。
但是官方提倡我们使用内置的 PureComponent
来减少重新渲染的次数,而不是手动编写 shouldComponentUpdate
代码。PureComponent
内部实现了对 props 和 state
进行浅层比较。
如果 shouldComponentUpdate()
返回 false
,则不会调用 UNSAFE_componentWillUpdate()
,render()
和 componentDidUpdate()
。官方说在后续版本,React 可能会将 shouldComponentUpdate
视为提示而不是严格的指令,并且,当返回 false
时,仍可能导致组件重新渲染。
UNSAFE_componentWillUpdate
当组件收到新的 props
或 state
时,会在渲染之前调用 UNSAFE_componentWillUpdate()
。使用此作为在更新发生之前执行准备更新的机会。初始渲染不会调用此方法。但是你不能此方法中调用 this.setState()
。在 UNSAFE_componentWillUpdate()
返回之前,你也不应该执行任何其他操作(例如,dispatch
Redux 的 action
)触发对 React 组件的更新。
通常,此方法可以替换为 componentDidUpdate()
。如果你在此方法中读取 DOM 信息(例如,为了保存滚动位置),则可以将此逻辑移至 getSnapshotBeforeUpdate()
中。
这个方法在挂载阶段已经讲过了,这里不再赘述。
getSnapshotBeforeUpdate(prevProps, prevState) { |
getSnapshotBeforeUpdate
生命周期方法在 render
之后,在更新之前(如:更新 DOM 之前)被调用。给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot
,这个 snapshot
会作为 componentDidUpdate
的第三个参数传入。如果你不想要返回值,请返回 null
,不写的话控制台会有警告。
并且,这个方法一定要和 componentDidUpdate
一起使用,否则控制台也会有警告。getSnapshotBeforeUpdate
与 componentDidUpdate
一起,这个新的生命周期涵盖过时的 UNSAFE_componentWillUpdate
的所有用例。
getSnapshotBeforeUpdate(prevProps, prevState) { |
上面这段代码可以看出来这个 snapshot
怎么个用法,snapshot
乍一看还以为是组件级别的某个“快照”,其实可以是任何值,到底怎么用完全看开发者自己,getSnapshotBeforeUpdate
把 snapshot
返回,然后 DOM 改变,然后 snapshot
传递给 componentDidUpdate
。
官方给了一个例子,用 getSnapshotBeforeUpdate
来处理 scroll
,并且说明了通常不需要这个函数,只有在重新渲染过程中手动保留滚动位置等情况下非常有用,所以大部分开发者都用不上,也就不要乱用。
componentDidUpdate(prevProps, prevState, snapshot) { |
componentDidUpdate()
会在更新后会被立即调用。首次渲染不会执行此方法。在这个函数里我们可以操作 DOM,和发起服务器请求,还可以 setState
,但是注意一定要用 if
语句控制,否则会导致无限循环。
componentDidUpdate(prevProps) { |
如果组件实现了 getSnapshotBeforeUpdate()
生命周期,则它的返回值将作为 componentDidUpdate()
的第三个参数 snapshot
参数传递。否则此参数将为 undefined。
卸载阶段,这个阶段的生命周期函数只有一个:
componentWillUnmount()
会在组件卸载及销毁之前直接调用。我们可以在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount()
中创建的订阅等。注意不要在这个函数里调用 setState()
,因为组件不会重新渲染了。
还有两个很不常用的生命周期函数,在这也列一下。
详细使用示例请见:React 官方文档
static getDerivedStateFromError(error) { |
此生命周期会在后代组件抛出错误后被调用。它将抛出的错误作为参数,并返回一个值以更新 state
。getDerivedStateFromError()
会在渲染阶段调用,因此不允许出现副作用。如遇此类情况,请改用 componentDidCatch()
。
componentDidCatch(error, info) { |
此生命周期在后代组件抛出错误后被调用。它接收两个参数:
componentDidCatch()
会在“提交”阶段被调用,因此允许执行副作用。它应该用于记录错误之类的情况:
如果发生错误,你可以通过调用 setState
使用 componentDidCatch()
渲染降级 UI,但在未来的版本中将不推荐这样做。可以使用静态 getDerivedStateFromError()
来处理降级渲染。
本文参考了以下文章和官方文档,推荐阅读。
有人会说,现在都 Hooks 一把梭了,你总结整合这些内容有啥用。其实学习这些内容,能够帮助你加深对 React 的理解,深入领会 React 的思想。并且,目前 Class component 与 Hooks 是并存的,虽然新项目一般都直接用 Hooks,但是老项目中难免会遇到 Class component,所以还是要学会的。
]]>归结起来就是抽象能力。所以这就决定了 CSS 预处理器的主要目标:提供 CSS 缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性。这不是锦上添花,而恰恰是雪中送炭。
但是,CSS 预处理器也不是万金油,CSS 的好处在于简便、随时随地被使用和调试。预编译 CSS 步骤的加入,让我们开发工作流中多了一个环节,调试也变得更麻烦了。更大的问题在于,预编译很容易造成后代选择器的滥用。所以在使用 CSS 预处理器时,要注意避免出现此类问题。
Sass 中变量以 $ 打头比较不容易和 CSS 标准语法冲突。Less 中变量则以 @ 打头,虽说容易和后续规范更新的新语法冲突,但是理论上只要 CSS 规范不引入 @a: b
这样的规则,问题也不大。而且规范制定的时候也会参考很多现有的实现。
Sass 和 Less 的变量机制有很大的不同,Sass 是类似 JS 的块级作用域一样,可以在作用域内重新赋值而不影响外部,Less 是以全局的最后一次赋值为准。SASS 和 SCSS 只是两种语法风格而已,SCSS 更贴近 CSS 语法,前端写起来更舒服。Less 和 Sass 最常用的部分并没有明显的区别,不用太在意该用哪个,Just pick one。至于公司用哪个,跟着用就行,不出大问题不用考虑换。
下文复制自 浅谈 CSS 预处理器(一):为什么要使用预处理器?,文中的示例代码均采用 Stylus 作为 CSS 预处理语言。
页面越来越复杂,需要加载的 CSS 文件也越来越大,我们有必要把大文件切分开来,否则难以维护。传统的 CSS 文件切分方案基本上就是 CSS 原生的 @import
指令,或在 HTML 中加载多个 CSS 文件,这些方案通常不能满足性能要求。
CSS 预处理器扩展了 @import
指令的能力,通过编译环节将切分后的文件重新合并为一个大文件。这一方面解决了大文件不便维护的问题,另一方面也解决了一堆小文件在加载时的性能问题。
把文件切分的思路再向前推进一步,就是“模块化”。一个大的 CSS 文件在合理切分之后,所产生的这些小文件的相互关系应该是一个树形结构。
树形的根结节一般称作“入口文件”,树形的其它节点一般称作“模块文件”。入口文件通常会依赖多个模块文件,各个模块文件也可能会依赖其它更末端的模块,从而构成整个树形。
以下是一个简单的示例:
entry.less |
入口文件 entry.less
在编译时会引入所需的模块,生成 entry.css,然后被页面引用。
如果你用过其它拥有模块机制的编程语言,应该已经深有体会,模块化是一种非常好的代码组织方式,是开发者设计代码结构的重要手段。模块可以很清晰地实现代码的分层、复用和依赖管理,让 CSS 的开发过程也能享受到现代程序开发的便利。
选择符嵌套是文件内部的代码组织方式,它可以让一系列相关的规则呈现出层级关系。
在变更出现之前,CSS 中的所有属性值都是“幻数”。你不知道这个值是怎么来的、它的什么样的意义。有了变量之后,我们就可以给这些“幻数”起个名字了,便于记忆、阅读和理解。
接下来我们会发现,当某个特定的值在多处用到时,变量就是一种简单而有效的抽象方式,可以把这种重复消灭掉,让你的代码更加 DRY。
变量让开发者更容易实现网站视觉风格的统一,也让“换肤”这样的需求变得更加轻松易行。
光有变量还是不够的,我们还需要有运算。如果说变量让值有了意义,那么运算则可以让值和值建立关联。有些属性的值其实跟其它属性的值是紧密相关的,CSS 语法无法表达这层关系;而在预处理语言中,我们可以用变量和表达式来呈现这种关系。
举个例子,我们需要让一个容器最多只显示三行文字,在以前我们通常是这样写的:
.wrapper { |
大家可以发现,我们只能用注释来表达 max-height
的值是怎么来的,而且注释中 3
这样的值也是幻数,还需要进一步解释。未来当行高或行数发生变化的时候,max-height
的值和注释中的算式也需要同步更新,维护起来很不方便。
接下来我们用预处理语言来改良一下:
.wrapper |
乍一看,代码行数似乎变多了,但代码的意图却更加清楚了——不需要任何注释就把整件事情说清楚了。在后期维护时,只要修改那两个变量就可以了。
值得一提的是,这种写法还带来另一个好处。$line-height
这个变量可以是 .wrapper
自己定义的局部变量(比如上面那段代码),也可以从更上层的作用域获取:
$line-height = 1.5 // 全局统一行高 |
这意味着 .wrapper
可以向祖先继承行高,而不需要为这个“只显示三行”的需求把自己的行高写死。有了运算,我们就有能力表达属性与属性之间的关联,它令我们的代码更加灵活、更加 DRY。
把常用的运算操作抽象出来,我们就得到了函数。
开发者可以自定义函数,预处理器自己也内置了大量的函数。最常用的内置函数应该就是颜色的运算函数了吧!有了它们,我们甚至都不需要打开 Photoshop 来调色,就可以得到某个颜色的同色系变种了。
举个例子,我们要给一个按钮添加鼠标悬停效果,而最简单的悬停效果就是让按钮的颜色加深一些。我们写出的 CSS 代码可能是这样的:
.button { |
我相信即使是最资深的视觉设计师,也很难分清 #ff4466
和 #f57900
这两种颜色到底有什么关联。而如果我们的代码是用预处理语言来写的,那事情就直观多了:
.button |
此外,预处理器的函数往往还支持默认参数、具名实参、arguments
对象等高级功能,内部还可以设置条件分支,可以满足复杂的逻辑需求。
Mixin 是 CSS 预处理器提供的又一项实用功能。Mixin 的形态和用法跟函数十分类似——先定义,然后在需要的地方调用,在调用时可以接受参数。它与函数的不同之处在于,函数用于产生一个值,而 Mixin 的作用是产生一段 CSS 代码。
Mixin 可以产生多条 CSS 规则,也可以只产生一些 CSS 声明。
一般来说,Mixin 可以把 CSS 文件中类似的代码块抽象出来,并给它一个直观的名字。比如 CSS 框架可以把一些常用的代码片断包装为 mixin 备用,在内部按需调用,或暴露给使用者在业务层调用。
举个例子,我们经常会用到 clearfix 来闭合浮动。在原生 CSS 中,如果要避免 clearfix 代码的重复,往往只能先定义好一个 .clearfix
类,然后在 HTML 中挂载到需要的元素身上:
/* 为 clearfix 定义一个类 */ |
<!-- 挂载到这两个元素身上 --> |
把表现层的实现暴露到了结构层,是不是很不爽?而在预处理器中,我们还可以选择另一种重用方式:
// 为 clearfix 定义一个 mixin |
CSS 预处理语言无法直接运行于浏览器环境,这意味着我们编写的源码需要编译为 CSS 代码之后才能用于网页。这似乎是一个门槛,需要我们付出“额外”的成本。
但在目前的大环境下,大多数项目的前端开发流程已经包含了构建环节,比如选择任何一个脚本模块化方案都是需要在部署时走一道打包程序的。所以对大多数团队来说,这个门槛其实已经跨过去一大半了。
而一旦接受了这种设定,我们还可以享受到“额外”的福利。在给 CSS 的开发加入编译环节的同时,还可以顺道加入其它构建环节,比如代码校验、代码压缩、代码后处理等等。
“代码后处理”是指 PostCSS 平台上各类插件所提供的功能,光是 Autoprefixer 这一项就已经值回票价了。我们再也不需要在 CSS 代码中手工添加浏览器前缀了,直接使用标准写法,剩下的事情让工具搞定吧!
]]>本文中大部分内容直接复制整合了参考资料中的内容,因为这些文章写的很好,没必要再用自己的话复述,因此取长补短,进行了整合,版权归原作者所有。
因为可以进行组件化开发?社区强大?使用的人多?有很多好用的第三方库和插件?适用于单页面应用开发?这些都不是最本质的原因。
最本质的原因是保持 UI 和状态同步并不容易。
使用原生 JavaScript 编写代码的时候,一般静态内容是在 HTML 中创建,动态内容使用 JavaScript 创建。但是使用 JavaScript 构建 UI 代码量巨大,并且可读性不高,代码可复用性也差。
更重要的是,如果我们想要保持 UI 和状态的同步应该怎么做?例如,我们有一个列表,我们需要实现将列表与服务器同步的功能。我们需要将本地数据和服务器发来的数据进行比较。并且需要点对点的将每个变更同步到 DOM 节点中。如果这个过程中有每一步出现差错都直接导致 UI 同步失败。因此,维护 UI 与数据同步需要编写大量繁琐,脆弱和脆弱的代码。
为了解决以上问题,降低开发成本,前端社区出现了 React 等框架。到目前为止,这些框架提供的最大的改进是实现应用状态和 UI 同步。我们只需要定义一次 UI,不必为每一次动作都编写 UI。相同的状态总能得到相同的 UI 输出,即状态和 UI 同步,状态变更后会自动更新 UI。
很多人都说 React 上手要难一些,Vue 上手要简单一些。也有很多人在问 React 和 Vue 的区别是什么。我想说一下我的理解。
首先,在框架设计层面,React 是 MVC 软件架构中的 View,它只负责视图层的东西,而对于数据和路由等,则由 Redux 和 Router 等来完成。这也就是为什么 React 官方文档说 “React 是一个用于构建用户界面的 JavaScript 库”。而 Vue 则不同,Vue 是基于 MVVM 架构设计的一个框架,所以在架构层面,Vue 和 React 的思想就是不同的。
React 的设计哲学很简单,不可变(Immutable)思想贯穿了整个框架的设计。React 可以说没引进什么新的概念,让开发者能够以类似写原生 JavaScript 代码的方式来使用 React 进行开发。这也就是很多人说 React 难的原因,即 JavaScript 基础知识。
而很多人说 Vue 容易上手,可能有些同学就迷惑了,我觉得 Vue 不简单啊。笔者认为,说 Vue 简单是因为 Vue 将很多底层逻辑封装好了,直接给你对应的 API,你调用对应 API 就可以完成对应的工作。所以,你不需要有很扎实的 JavaScript 基础,你只要记住或者能够查阅到对应 API 就可以用 Vue 完成一些基础性的开发。所以有些后端的同学可以看看 Vue 官方文档就能上手基础的前端开发。
React 之我见,那一定要说我的看法,我喜欢 React。我喜欢其简洁的设计理念和类原生的开发体验,让我能够感觉到自己是在写代码。那么 Vue 我没有深入学习,但这是一个非常优秀的前端框架,目前也已经安排进了下一阶段的学习任务中。
JSX 和虚拟 DOM 是必说内容。JSX 也就是 JavaScript 的一个语法扩展,可以通过 Babel 对 JSX 进行转译。
const title = <h1 className="title">Hello, world!</h1>; |
例如这样一段 JSX 代码会被 Babel 转译成:
const title = React.createElement( |
从这里我们可以看出,React.createElement
方法的参数是这样的:
createElement( tag, attrs, child1, child2, child3 ); |
那么我们可以实现一下 createElement
:
function createElement( tag, attrs, ...children ) { |
然后这样就可以对其进行调用:
// 将上文定义的 createElement 方法放到对象 React 中 |
此处引用 hujiulong 的 从零开始实现一个 React(一):JSX 和虚拟 DOM,详细内容推荐看其原文。
也就是说,createElement
方法返回的东西就是所谓的虚拟 DOM。说到虚拟 DOM 就必说 diff 算法,这个我们之后再讲。
render
其实没有太多好说的,其作用就是将虚拟 DOM 渲染成真实的 DOM,只是在实现的过程中有非常多的细节情况要处理。
在 React 中,我们可以通过 Class 和 Function 两种方式来写组件。以 Class 的方式写组件的时候,我们需要继承 React.Component。这里主要涉及 React.Component 的模拟实现和 React 的生命周期。模拟实现推荐看 从零开始实现一个 React(二):组件和生命周期。至于 React 的生命周期,之后我会总结一篇文章,推荐大家去看 React 官方文档。
为什么要 diff?因为 DOM 操作十分昂贵,也就是非常耗时耗性能。但是 DOM 操作就一定很慢很耗性能吗?其实不一定,只是普遍来讲是这样的,详细内容推荐看 JJC 的知乎回答:前端为什么操作 DOM 是最耗性能的呢。简单讲,浏览器中 DOM 对象是使用 C++ 开发的,性能肯定不慢,有些情况下性能甚至高于操作 JavaScript 对象。而大多数时候操作 DOM 是很耗性能的,这是因为 JavaScript 对象的属性(properties)映射到的是 DOM 对象的特性(attributes)上。
我们操作 JavaScript 对象只是修改一个对象,而修改 DOM 对象时还会修改对象的 attributes,那么性能就消耗在 JavaScript 对象和 DOM 对象的转换和同步上。并且,操作 DOM 还会影响页面的渲染,进而会再一次降低了性能。虽然 DOM 的性能有可能比操作普通对象还要快,但 99% 的场景下,操作 DOM 对象还是昂贵的。所以,高效的 diff 势在必行。
我们可以通过实现高效的 diff 算法,对比出操作前后有差异的 DOM 节点,然后只对更改了的 DOM 进行更新。这样可以大大减少没有必要的 DOM 操作,进而大幅提升性能。
React 的 diff 算法其实很简单:对比当前真实 DOM 和虚拟 DOM,在对比过程中直接更新真实 DOM。并且这个比对是同级比对,当发现这一级的 DOM 不同时,会直接更新该节点及其所有子节点。这种 diff 策略实际上是出于性能上的取舍。首先,DOM 操作很少出现跨层级的情况,所以只需要同级比对就可以满足大多数情况,也就没有必要对比所有的 DOM 节点,因为那样需要 O(n^3) 的时间复杂度,代价太高。
React 虚拟 DOM 的 Diff 原理的思维导图如下所示:
React diff 算法的模拟实现推荐参见:从零开始实现一个 React(三):Diff 算法
首先明确,所谓异步的 setState 并不是真的异步,只是其行为类似于异步操作的行为。下面我们来详细说说。
首先,将 setState 的行为设置为所谓异步的形式的出发点依旧是性能优化,因为如果每次通过 setState 更改了状态都对组件进行一次更新,会很浪费性能。而通过将多次 setState 合并在一起,进行一次更新渲染,则会大大提升性能。
将多个 setState 的调用合并成一个来执行,也就意味着 state 并不会被立即更新。例如下面这例子:
class App extends Component { |
这里定义了一个组件 APP
,在组件挂载后,会循环 100 次。但当我们真的对这个组件进行渲染会发现,渲染结果为 1,并且控制台中输出了 100 次 0。也就是说,每次循环中拿到的 state 都还是更新之前的。也就是说 React 对 setState 进行了优化,但如果我们就是想要立刻获得更新后的 state 怎么办呢?React 提供了一种解决方案,setState 接收的参数还可以是一个函数,通过这个函数我们可以拿到更新之前的状态,然后通过这个函数的返回值得到下一个状态:
componentDidMount() { |
对于这个地方,有两个要点:一是如何对多个 setState 调用进行合并。二是我怎么判定一次合并哪些 setState 呢?接下来我们一一解答。
setState 的合并是通过队列实现的。通过创建一个队列来保存每次 setState 的数据,然后每隔一段时间,清空和这个队列并渲染组件。此处的模拟实现推荐阅读:从零开始实现一个 React(四):异步的 setState
接下来就是一次到底合并哪些 setState。换句话说,我们会将一段时间内的 setState 调用合并成在一起执行,那么这段时间的长短取决于什么?其实此处使用的是 JavaScript 的事件队列机制,也就是事件循环(Event Loop)。关于事件循环的详细内容可参见阮一峰的 JavaScript 运行机制详解:再谈 Event Loop。事件循环的核心概念就是同步任务,异步任务,微任务以及宏任务。
在 React 的 setState 中,利用 JavaScript 的事件循环机制对多个 setState 调用进行合并。首先创建一个队列保存每次 setState 的数据,在一次事件循环的所有同步任务之后,清空队列,将队列中的所有 setState 进行合并,并进行一次性更新渲染。这样在一次事件循环的,最多只会执行一次合并操作,并且只会渲染一次组件。
所以呢,这也就是为什么我说,所谓的 setState 的异步行为并不是真正的异步,只是不会对每一个 setState 操作进行实时更新,而是通过队列的方式对一次事件循环中的所有同步任务的 setState 调用进行合并,合并成一个之后进行一次更新和渲染,所以效果上看上去是异步的,但并不是 setTimeout 或 setInterval 这种真正的异步操作。
setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。
setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState ,setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
参考文章:https://juejin.im/post/5b45c57c51882519790c7441
React 中的 props(“properties” 的缩写)和 state 都是普通的 JavaScript 对象。它们都是用来保存信息的,这些信息可以控制组件的渲染输出,而它们的几个重要的不同点就是:
props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。 由于 props 是传入的,并且它们不能更改,因此我们可以将任何仅使用 props 的 React 组件视为 pureComponent,也就是说,在相同的输入下,它将始终呈现相同的输出。
state 是在组件中创建的,一般在 constructor 中初始化 state。state 是多变的、可以修改,一般通过 setState 被修改,并且一般是异步更新的。
至此,React 的基本概念已经梳理完毕,感谢阅读。笔者水平有限,如果你发现任何问题,欢迎评论指正!
Iterable 是指有 [Symbol.iterator]
属性的对象,这个属性 obj[Symbol.iterator]
就是 Iterator(迭代器)。也可以说可迭代对象是实现了 Symbol.iterator
方法的对象。
可迭代对象可以被 for..of
循环遍历,我们最常进行迭代操作的可迭代对象就是 Array,其实还有其他可迭代对象,例如 String、Set、Map、函数的 arguments 对象和 NodeList 对象等,这些对象都有默认的 Symbol.iterator
属性。
Iterator 必须有 next()
方法,它每次返回一个 {done: Boolean, value: any}
对象,这里 done:true
表明迭代结束,否则 value
就是下一个要迭代的值。
假设我们 range
对象,我们希望对其使用 for..of
循环:
let range = { |
为了让 range
对象可迭代,我们需要为对象添加一个名为 Symbol.iterator
的方法。当 for..of
循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。从此开始,for..of
仅适用于这个被返回的对象。当 for..of
循环希望取得下一个数值,它就调用这个对象的 next()
方法。next()
方法返回的结果的格式必须是 {done: Boolean, value: any}
,当 done=true
时,表示迭代结束,否则 value
是下一个值。
let range = { |
请注意可迭代对象的核心功能:关注点分离。range
自身没有 next()
方法。相反,是通过调用 range[Symbol.iterator]()
创建了另一个对象,即所谓的“迭代器”对象,并且它的 next
会为迭代生成值。因此,迭代器对象和与其进行迭代的对象是分开的。
从技术上说,我们可以将它们合并,并使用 range
自身作为迭代器来简化代码。
就像这样:
let range = { |
现在 range[Symbol.iterator]()
返回的是 range
对象自身:它包括了必需的 next()
方法,并通过 this.current
记忆了当前的迭代进程。这样更短,对吗?是的。有时这样也可以。
但缺点是,现在不可能同时在对象上运行两个 for..of
循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的 for..of
是很罕见的,即使在异步情况下。
此外,我们还可以将 range
设置为 range.to = Infinity
,这时 range
则成为了无穷迭代器。或者我们可以创建一个可迭代对象,它生成一个无穷伪随机数序列。也是可能的。next
没有什么限制,它可以返回越来越多的值,这是正常的。当然,迭代这种对象的 for..of
循环将不会停止。但是我们可以通过使用 break
来停止它。
Generator 是一个特殊函数,调用返回一个 Generator。
未完待续……
参考:
]]>6 月 13 号,作为从 2018 年 09 月工作至现在的学生兼职辅导员,需要参加学校组织的评优答辩,我自认为准备的还不错。是的,我在时间把控上可以做到,5 分钟的汇报,我能把控在 4:55-5:00 之间完成。在进行答辩准备的时候,我也有想过评委到底想看到什么,但是想的还不够,其实是很不够。
本次参加答辩的,全校共有 14 名学生兼辅,评委共六名。答辩结束后,学工部副部长把同学们留了下来,可以说是给同学们上了“一堂课”。一直以来我对这种领导并没有什么好感,但是这一次,这一位,真的让我感觉是一位实干的领导,干练、直接、讲真话,让我由衷的佩服。
本文主要记录如何在时间很短的 PPT 汇报中,迅速抓住评委的“心”。本文以兼职辅导员工作汇报为例,但其实其他工作汇报、学习进展汇报、学术汇报、晋升汇报等都是相通的。
在这么短的时间里,想要把方方面面都介绍到是不可能的。那你就要想,如何能够迅速抓住评委的“心”。首先思考评委最想看到什么?最不想看到什么?以兼职辅导员工作汇报为例,评委最想看到的就是你的工作成果,工作亮点。最不想看到的就是流水账式的工作记录汇报。
介绍自己:大部分的汇报,你都需要先把自己是谁介绍清楚,根据不同的汇报主题有所侧重。例如进行兼职辅导员汇报,你就需要介绍自己以往相关工作经历,并重点介绍担任兼职辅导员期间的学习成绩,将自己与评委的关注点相匹配。在取得丰硕工作成果的同时,能够取得优异的成绩,能够证明你的学习能力强,协调能力强,自律性强等等。
工作成果:这是证明你工作能力和工作效果最好的证明。
工作亮点:你是如何取得的工作成果,通过什么工作方法,有什么工作亮点。
数字量化:使用数字量化你的工作量和工作成果,简单直观,当然也需要你真的有足够的工作付出,以及能拿得出手的工作成果。
举例证要谨慎:举例证一定要谨慎,举正面例证,千万不要给自己挖坑。以兼职辅导员工作汇报为例,举早操相关的例证就是给自己挖坑,因为这两部分工作一直做的都不够好。早操的初衷是让大家早点起床早锻炼,而现在成了强迫大家早起的手段。迟到、旷到、无精打采等现象很普遍。并且目前早操后会进行早读,这也不是早操的初衷,目前只是没办法,食堂位子不够,只能通过早读进行分流,错开学生就餐时间。
图片要精选:图片不是用来证明你做过某项工作,而是用来点睛的。图片要能突出亮点,有特色,能“打动人”。
紧扣核心主题:针对各个分要点进行介绍,最后一定要点题。本次汇报是什么主题,核心内容是什么。例如兼职辅导员工作,核心是人的引领。辅导员是思政教师,无论你的日常工作多么繁杂,一定要展现思想上的引领,紧扣主题。
以上就是本次分享的一些 PPT 汇报技巧,希望对你有用。
后记:其实这一年的兼职辅导员工作也挺有意思的,恰逢学校本研一体化改革以及新校区建设,学校史无前例的人员调动,使得工作上情况更复杂。最后,也很感谢学院和学校能给我这样一个担任兼职辅导员的机会,是我有幸有这样一段工作体验,祝大家今后顺利。
至此,以为记。
]]>推荐读者:想学习如何撰写文案,想了解什么样的文案吸引人,为什么吸引人,想扩大知识面的人。也推荐给所有喜欢写文字的人,写文字其实就是一种表达,传递信息以达到目的,很多写广告文案的技巧,会对你写文章有帮助。
首先明确一点,文案的最终目的就是让你心甘情愿地花钱买产品或服务。作者认为平面广告是直销广告中最难写的广告之一,你只能通过纸上的文字,没有声音和其他信息,并需要吸引读者阅读广告并购买产品。因此,写文案需要有好的创意,而创意来自于经历。在学习写文案时,要锻炼自己的发散思维能力,也就是将各种毫无关联的概念联系子一起的能力。
在你开始为一个产品写文案前,你需要有足够的专业知识,足够了解你的顾客和产品的本质。挖掘产品的本质,提取顾客感兴趣的要素,发布广告后静待效果。切记文案中勿用恐吓的方式。
这本书分享了很多作者撰写文案的经验,并且结合具体实例进行了分析,对帮助了解如何撰写文案,什么样的文案吸引人,为什么吸引人等很有用。这本书的翻译一般,翻译腔比较浓,并且由于书的年代、技术的发展和国情等的不同,有些内容不太适用,但是总体是很有价值的,推荐阅读。
]]>全书共分为三卷,卷一讲群体的心理,包括群体的特征和精神统一率,群体的情感与道德,群体的观念、推理和想象力,群体的所有信仰都是宗教形式。卷二讲群体的主张与信念,包括群体信念与主张之间的间接因素,群体主张的直接因素,群体领袖及其说服别人的方式,群体信念与主张的变化范围。卷三先讲述了群体的分类和特点,并结合犯罪群体、法庭陪审团、选民群体和议会群体进行实例分析。这本书读起来并不轻松,但是内容非常值得你细细品味,如果遇到读不懂的地方,可以多读几遍。
推荐读者:对群体行为和群体心理感兴趣的人,如果你之前没有读过相关的书,相信这本书的内容会让你有较大的收获。
《乌合之众》中的群体指的是受某一事件、演说、激情、恐惧、爱恨的刺激而聚集在一起,为某个目标或某些精神需求而有所行动的一个特殊的心理群体。
勒庞在本书中所说的”群体“与弗洛伊德笔下的”流氓“是完全不一样的。弗洛伊德笔下的”流氓“总是服从于同一个领袖,而勒庞所指的”群体“只在某一激情燃烧或者事件发生的时间段内服从于某一领袖,当促使他们聚集成群的刺激物消失时,他们也就不再听从这个领袖了。
人一旦加入群体,原来的个性便会消失,不再独立思考而随大流,无意识占领上风,智力程度减弱。群体中的个人具有独处时所没有的特点,这就是勒庞所说的“群体精神统一规律”,也就是群体的精神灵魂。一个人独处时可能是个很有教养的人,但加入群体后的他可以变成凶残、易怒且充满暴力的野蛮人。
群体心理的诞生就像化学反应,没有因素的总和或平均,只有像化学反应那样的新特征的组合和创造。群体心理形成后,群体便会获得某些普遍的特征,这些特征虽然短暂但极其明确。
出现这种情况的主要原因有以下四点:
群体没有主见,缺乏头脑,常被人利用充当炮灰。同时群体很暴力很危险,极具破坏性,甚至常常犯罪。
群体的行为完全是无意识的,只服从自己的冲动,很容易受到外界刺激因素和一时激情的影响,情绪变幻无常,思想和愿望都不持久。作者表示,群体的这种无意识可能就是群体力量强大的秘密之一。
由于群体不讲理性,做事不经过大脑,缺乏判断力和批评精神,所以显得极其轻信。对他们来说,没有什么是不可能的。而群体不懂思考,所以暗示给群体的不管是什么观念,只有在形式变得绝对和简单的时候才能被群体理解和接纳。
感情和思想的简单化和夸大化使他们既不懂得怀疑,也不会犹豫,动不动就走极端,极易做出很坏的事情。群体常常服从低劣的本能,有时也可以作为品德高尚和道德崇高的典范。
群体中任何情绪和行为都具有感染性,很容易受别人的意见和主张影响,使得群体中的个人都有很强的从众心理,容易被人误导。集体观察是错误率最高的,它往往只是某个人的幻觉,通过传染,暗示给别人。说某事是成千人同时看见的,这往往意味着事实真相与人们所说的大相径庭。
群体喜欢幻觉而不喜欢真理,推理能力差,根本就不可能理解系统的逻辑推理,不会推理或者总是错误地推理。
容易受到古老世袭制的影响,保守,奴性,忠君守旧,害怕改变。群体的笃信具有宗教感情固有的特点:盲目服从,极其褊狭,渴望传播。
崇拜心目中的崇高者,害怕他身上所谓的神奇力量,盲目地服从命令,缺乏分析这些信条的能力,只想着传播它们。所有不接受这些信条的人都有可能被当作敌人。对群体来说,必须有个神,否则什么都谈不上。
受到暗示的群体,可以随时为了暗示给他们的理想而赴汤蹈火。并且彻底摧毁过于残旧的文明是群体最明确的任务。
综上所述,对于群体来说,真正正确的观念并不一定起作用,只有通过各种方式成为无意识,并成为一种情感才能真正起作用。但是使新观念进入人们大脑的过程非常漫长,但是一旦进入,想要排出它也需要同样长的时间。所以,就观念而言,群体总是落后学者和哲学家好几代人。
群体主要分为异质性群体和同质性群体两类。
A. 无名称(比如街头人群)
B. 有名称(陪审团、议会等)
A. 派别(政治派别、宗教派别等)
B. 身份团体(军人、僧侣、工人等)
C. 阶级(资产阶级、农民阶级等)
群体的信念与主张的间接因素包括种族、传统、时间、制度和教育。
例如,同样的制度在一些国家非常适用,而在另一些国家则完全相反。这其实和制度没有关系,而是种族和传统等因素造成了这种差异。
群体主张的直接因素包括形象、词汇、套话、幻觉、经验和理性。群体对形象很敏感,群里最喜欢形象化的事物,并且群体的想象力是极其丰富的,想象力丰富也就使得群体很容易通过想象而产生各种幻觉。此外,群体不喜欢长篇大论,更喜欢浓缩的词汇和套话,简单易懂就好。
群体的领袖一般分为两类,第一类粗暴、胆大、勇敢,第二类意志持久,尽管看起来没有第一类那么神气,但是影响要大得多,因为更符合群体的喜好。
想要领导群体,要做到断言、重复和传染,并且要有足够的声望。断言就是说断就断,说话做决定要果断。重复就是将断言重复到一定的次数,就可以获得大家的认可。对于保持声望,想要得到群众的敬仰,那就要永远与他们保持距离,保留神秘感和威严感。
每一种文明都有自己的规则,有纪律,有远见,有文化,是从本能过渡到理性的结果。历史上的大动荡往往都来自于基本观念的变化。
本书中提到了两个例子,很值得细细品味。一个是高等教育和职业教育相关的例子,一个是立法者加税的例子。
第一个例子是在教育方面,如果想稳固阶级,最重要的是职业教育。因为社会对高等教育人才的需求是有限的,如果过多的人接受了高等教育,而对这类人才的需求没有跟上,则会出现很多毕业生找不到工作(其实是对工作更挑剔),而很多岗位招不到人的状况,这就会影响社会的稳定。这里明白的人可以细品一下,我就不过多展开了。
第二个例子是立法者加税,立法者想增加一个税种,不会选择理论上最正确的税种。 群体接受的是表面上看起来正确的选择,而不是理论上正确的。 所以,间接税不管多么过分,总是能被民众所接受,因为每天几分钱的消费税,不影响他们的生活,不会引起注意。
如果我们代之以按工资或其他收入比例来纳税,要一次付清,即便理论上比别的税轻十倍,也会引起一致的反对。 要在规定的日期缴纳,数额相对就较大,看起来很多,因此也比较惊人。一点点支付,税才不会显得太重。然而,这种节约手段需要一定的远见,这是群体所缺乏的。
本书细致地考察了群体的一般性心理特征,对群体的道德观、情感、想象力和信念等诸多方面进行了探讨,指出个人进入群体之后容易丧失自我意识,在群体意识的压迫下成为盲目、冲动、狂热、轻信的“乌合之众”的一员。并且指出,影响大众想象力的不是事实本身,而是它的扩散和传播方式。
本书的意义在于,首次阐明了社会心理学中的一些重要问题;研究了群体特征和种族特征的不同之处;指出了群众运动的性质;分析了领袖与群众、民主与独裁的关系。
此外,本书也体现出了作者勒庞的一些局限性。例如歧视女性和拉丁民族,部分研究不够系统和深刻,观点有些片面,时有臆断和偏见甚至矛盾;作者是站在领英的立场上思考和行文,而非普通群众。
当然,瑕不掩瑜,依旧是一本值得细细品味的好书。本期好书导读至此结束,敬请期待下期分享。
]]>这本书讲的内容比较简单,通俗易懂,作者以很聊天式的方式,为你描述了什么是互联网市场营销,什么是运营。帮你了解互联网,帮你成为一名合格的互联网人。认真读,会有收获。
推荐读者:不了解互联网行业,想了解互联网相关工作,想进入到互联网公司工作,想转行到互联网行业的同学。以及在互联网公司工作了一段时间,认为自己有必要了解一下别人如何看到互联网相关工作的,想了解前辈经验的同学。不推荐互联网“老手”读这本书。
互联网公司员工规模可以大到几万人,也可以小到两三人,规模会根据业务需要而扩大或精简。但是总体来讲主要有技术岗、销售岗、职能岗、市场岗、运营岗、产品岗和设计岗这几个部分。互联网公司里,大家都各司其职,只有经验和能力上的话语权,并不存在太多官大一级压死人的现象。
本书讲的内容主要是关于互联网公司市场和运营人员的。互联网市场和运营岗位需要你有一定的文字表达能力(高考语文及格,语言表达通畅),熟练使用 Office 和 Photoshop 等办公软件。对数据敏感,沟通表达能力强,具备一定的数据分析能力,以结果为导向等基本能力。
运营岗可分为活动运营、用户运营、商家运营、品类运营、游戏运营、网店运营和内容运营等。
作为初入职场的市场和运营的年轻人,脑子比较活,更贴近用户,能够给出很多天马行空的点子,一旦你的想法被采纳,就会得到上级的赏识,职位晋升得也会更快。在互联网公司,根本无须心存“功高震主”的忌惮。
给互联网市场和运营人的十个建议:
1.永远不要说“我不知道”;
2.要具备职业精神;
3.学会写邮件,例如抄送功能是用来通知你的领导及需要与你跨部门合作的其他部门的领导,你们正在做什么样的工作;
4.向上汇报,积极主动向你的直属领导回报你的工作情况;
5.不要越俎代庖,做好自己负责的工作,对他人负责的部分提意见可以丢在大群里,他们需要自然会接受;
6.疯狂学习,低调处事,用好项目经费,锻炼身体;
7.让自己手里的资源流动起来,而不是牢牢捂着不让人碰;
8.不要指望面试官一眼看出你靠谱,要主动表达;
9.进入职场,你是来工作、成就事业的,不是来交朋友的。顾大局、就事论事,是每一个职场人必备的素养;
10.以结果为导向,用数据说话。
对第七点的补充:你可以通过撮合相互需要认识的人,相互了解并开展合作,以帮助你进一步消化闲置资源,加深你对他们的了解,如业务属性、需求、性格和能力、公司运营状况等,多维度地完善你的资源数据库,同时还可以博得圈内人士的好感,赢得好的口碑,做一个对他们“有用”的人,主动权就会到你手上更多了。
除此之外,作者在本章还讲到了一些互联网的常用英文缩略词和中文专用词等互联网相关的梗。在本章的最后,作者还以一个餐馆的例子来讲述什么是运营、营销、市场和产品。
一般写文案主要有三种目的:涨粉、刺激转发和提高转化率。
除此之外,作者还讲到一个好标题对一篇文案的重要程度,以及如何快速拟出一个好标题。并且还讲了一篇文章的排版有多重要。
此外,作者在本章还讲到了制作 HTML5 的海报,以及如何提出一个不会被砍掉的需求。
通过头脑风暴,你们可以获得很多好的点子。但是如果组织的不好,则会成为一场浪费生命,浪费感情的烂尾活动。给你一些组织一场不会烂尾的脑暴会的实用性建议:
BD 即 Business Development Manager,中文为商务拓展经理。
做 BD,第一件事情,是了解清楚公司的业务流程,产品特性,盈利方式(这是尽可能不要触及的底线),流量如何,注册用户数,客户数,用户画像,潜在价值在哪里,有哪些附加值,老板最愿意拿去交换的资源有哪些,价值多少,销售部门对外报价多少,哪些是绝对不能合作的公司,合作了危害有哪些,等等。下一步,梳理公司已有的资源库。
寻找品牌调性比较高,用户素质好,市场策划能力较强的公司强强联合,开展有深度的合作,是构筑 BD 核心竞争力的基本课题。
开始合作时,双方可以先互相聊聊宏观上的各自行业现状和公司地位、各自人群特点,然后聊聊各自的市场实力和操作流程、资源情况,表达一下各自的诚意和对对方公司的极大兴趣。双方合作,更多地是看品牌和影响力以及品牌叠加的化学反应如何。
办一场小沙龙、讲座、比赛或行业大会,会促使你的能力与日俱增。把自己变成资源主体,让四面八方主动上门来连接,而不是原来的优盘状态,只能不断从别人的母体蹭一点资源。
与程序员沟通,要指令明确,具有可操作性。而不要用如差不多、大概、稍微、好看等模糊词;而要明确,如“帮我买一杯 XX 家的珍珠奶茶,热的,七分甜,加椰果和红豆”。解释目的和目标要直观,让他有代入感和大局观。充分尊重,友好真诚,体现出你相信他们比你聪明得多(虽然这是事实)。
提交 bug 的正确方式:不要说你的代码有 bug。
你说他代码有 bug,他的第一反应会是:
正确的方式应该是委婉地说:这个程序和预期的有点不一致,你看看是不是我用错了?他本能地会想:是不是出 bug 了!另外注意,这不是一个 bug,而是一种“需求”。
产品经理负责调查并根据用户的需求,确定开发何种产品,选择何种商业模式等,并推动相应产品的开发组织。他还要根据产品的生命周期,协调研发、市场和运营等,确定和组织实施相应的产品策略,以及其他一系列相关的产品管理活动。
在早期产品新人还没有这个资本树立职场威信的时候,最好跟着前辈亦步亦趋,照葫芦画瓢。在和产品经理沟通合作时,若观念发生冲突,可以做一个内测版的用户体验调查问卷,用事实和数据告诉他们修改的意见和方向,并且让他们认识到后期推广的痛点在哪里。
给设计师提需求的时候也要注意,你要把图的尺寸,图片上需要的文字,图片风格等信息告诉设计师,并且提前约排期。而不是你需要图,去找设计师说:“我要做一个 XX 活动,给我做两张图!”
Dribble、站酷和 Behance,是三大设计师聚集的阵地。站酷偏商业创意设计,Dribble 主要为手机界面图标设计,Behance 则更多的是艺术灵感类设计。
一份打动人心的策划案,它起码要符合以下几个要素中的任意两条及以上:
此外,作者在本章还讲到了如何定制一份走心的礼物、如何让老司机心甘情愿地帮你成长。
市场公关行为主要分为三种:提升知名度、提升美誉度和提升数据。
当出现负面事件时,应该坦然地接受指责,感谢大家的指教,态度诚恳地道歉,甚至“过度道歉”,反而会让对方无从下口。一流的高手,都不会自己拼命试图解释,而会用各种手段让别人来替他说出他想要别人说的话。这个技巧,古代皇帝爱用,叫“罪己诏”
千万不要觉得帮企业圆场是自己的分内之事,这是欺骗,没有一个光荣的工作是以欺骗来立足的。公关更多的角色应该是一个桥梁,架在企业或公众人物和群众之间。
消除信息的不对称,更清晰地理清事实真相,做传递企业文化的人,甚至应该反过来代替群众去监督企业,向上管理老板,对企业的发展以更顺应民意和社会的角度给出战略发展意见是市场公关的职责。
提醒一下有志于从事公关行业的朋友们,不要完全信老板说的话,在动笔之前调查清楚事情的全部真相,不要盲目下笔,以致产生不利后果。做声明时有几个元素一定要注意:
在这场浩大的舆论战役中,如果实在做不到谨言慎行,那就关掉手机,不做任何回应,让它慢慢过去,都会好得多。
公关行为的本质就是将你想要传达的信息经过包装处理,再通过理想的渠道传播给你想传达的人群,并且让他们接受你的信息以及做出你期望的反应(提升好感,或者行为引导)。
想做出爆款内容,首先要记住一点:用户永远不会替你传播,他们只会替自己传播,给他一个标签,让他借你的作品表达自己。
网红大概分为几类:第一类,社会名人自带网红属性;第二类,草根号一心做大想接广告的;第三类,自娱自乐一不小心红了的无欲无求族。
第一种类型的人最难接近。因为社会地位悬殊,与他们基本没有平等对话的机会;
第二种类型的人,不要期盼对方有空陪你闲聊;
第三种类型的人,通常是最有趣也是最有交往价值的人,他们什么都不缺,没有太多功利心。当然他们也因此对私人联系会有洁癖,他们对智商和格调异常敏感,只会交往自己喜欢的同类,把自己变成他们的同类,或者充分展示你的有趣,是唯一能敲开他们私人世界大门的敲门砖。
对于网红来说,对来访者是否是自己的 follower 会格外敏感。如果连订阅或者扩散自己作品的举动都没有,却来索取或者求交往的,多半要吃闭门羹,可以先转发一条最新微博或者知乎上点个赞再去发私信,大多数人都会随手点开看一眼,这样的行为多少可以加点分,知道你不是个“伸手党”。
在本章,作者给出了当对手上门挑衅时,你要记住的三个重要原则:
此外,作者在本章还写到,在活动执行时,如何挑选靠谱的供应商,市面上的部分媒介的优缺点以及如何办一场线下活动。并且还通俗地讲了期权和加薪等基本的财务问题。
作者结合个人经历,讲到市场人有哪些职业病;在思路枯竭时,如何打开思路;有野心的员工才能进步地更快。作者认为写作是这个时代白手起家的最短路径,当然还有很多其他的赚钱方式。
在大城市,靠着每个月的工资买房?基本是不现实的。真正拉开差距的是正常工作以外的时间。大城市,机会很多,关键在于你是否能发现,发现了是否有能力承接,承接了是否能坚持做好。
不要吐槽北上广房价贵,很少有人真的能靠工资买房。拉开差距的,还是八小时以外啊。或充电,或赚钱,唯独时光与青春不可辜负。
在这一章,作者对豆瓣、知乎、微博、微信、直播平台、自制短视频等的过去和今天进行了分析。作者认为短视频是下一个战场,并结合自己的经历,讲了如何获取视频流量等。
短视频为什么会火,因为有大量学历不高的网民支撑,这部分网民的共同点是比较闲,工作压力不大,喜欢低俗无厘头且不用思考的视频短片,看完哈哈一笑之后便继续漫无目的地点击下一个视频或刷刷朋友圈,他们现在对视频短片质量要求不高。
作者还讲到,做市场营销时,如果实在是资源枯竭,也可以将老板“卖”出去,通过情怀牌、“逗比”牌和悲情牌等方式,对老板进行包装,打造老板的好形象。
有一点我印象很深刻,就是微博的口碑营销应该怎么做:
作者结合自己的经历,对如何选择一份不会后悔的 offer 给出了以下的建议:
作者结合自己做的一个精品高端社群项目讲到,一个优质的社群可以成为人脉结点,是连接高质量用户的桥梁,并且刺激高质量用户的输出和传播,使其甚至有可能会成为网站本身的产品使用者,从而提高整个网站的口碑、用户含金量和生态多样性,从而建立行业壁垒。
社群有按照职能区分的,如程序员群、产品群、运营群;也有按照目的区分的,如读书群、跑步群、分享干货群。可以有少量交叉,既能满足同业交流,又能满足跨界沟通。
虽然赚钱重要,但是涉及到金钱和生命、健康的东西,做市场、做媒体的,如果没有足够的专业知识,凭借自己的创意文案让人做出选择,背后的责任你承担不起。
在最后,作者分享了一些自己做用户运营、新媒体运营和渠道运营的一些坑。
]]>从 2015 年 11 月开始,我们发起了掘金翻译计划,现已成长为可能是世界最大最好的英译中技术社区和翻译平台。我们已有超过 1000 位 优秀的译者,今年翻译了 TensorFlow 官方中文文档,现代 JavaScript 教程,2018 年前端开发者指南 和 mlkit.cn,并与 Google 团队合作,完成了 Android 官方文档的翻译优化。
翻译计划目前已经翻译完成 1370 余篇文章,官方文档及手册 13 个,共有 1000 余名译者贡献翻译和校对。GitHub Star 18450+,Watch 1080+,Fork 3140+。
这不仅是掘金翻译计划的 2018,更是我们一起成长的故事~
现在,掘金翻译计划内容覆盖区块链、人工智能、Android、iOS、前端、后端、设计、产品和其他 等领域,以及各大型优质 官方文档及手册,读者为热爱新技术的新锐开发者。
目前已经翻译完成 1370 余篇文章,官方文档及手册 13 个,共有 1000 余名译者贡献翻译和校对。
掘金翻译计划是一个翻译优质互联网技术文章的社区,译者主要来自于自荐。想成为翻译计划译者的同学,可以在 GitHub 上发 Issue 进行申请。译者自荐后,需要进行翻译计划译者教程的学习,之后由管理员来审核加入。加入翻译计划后先校对至少一篇文章,熟悉流程,之后可以开始认领文章的翻译。
文章主要来源于国外优秀网站,主要有管理员筛选和译者及其他志愿者推荐。发布在翻译计划任务列表中的文章,需要经过一位译者翻译,两位译者校对,最后经过管理员审校,才能够合并到翻译计划中。
当译者及分子攒到一定数量后,可以兑换相关奖品,想要兑换奖品的译者,直接微信私聊管理员即可,管理员收到信息后,会在一周内将礼物寄出。掘金翻译计划礼物列表详见:https://github.com/xitu/gold-miner/wiki#%E5%B0%8F%E7%A4%BC%E5%93%81%E5%88%97%E8%A1%A8
排名 | 译者 | 年度积分 | 排名 | 译者 | 年度积分 | 排名 | 译者 | 年度积分 |
---|---|---|---|---|---|---|---|---|
1 | Starriers | 433 积分 | 11 | geniusq1981 | 77.5 积分 | 21 | hanliuxin5 | 57.5 积分 |
2 | zhmhhu | 105.5 积分 | 12 | sunhaokk | 73.5 积分 | 22 | zhusimaji | 57.5 积分 |
3 | ALVINYEH | 99 积分 | 13 | sisibeloved | 68 积分 | 23 | luochen1992 | 57 积分 |
4 | pkuwwt | 93.5 积分 | 14 | Raoul1996 | 67 积分 | 24 | steinliber | 57 积分 |
5 | CoolRice | 92 积分 | 15 | Moonliujk | 65 积分 | 25 | athena0304 | 55.5 积分 |
6 | lsvih | 87.5 积分 | 16 | jonjia | 64 积分 | 26 | changkun | 55 积分 |
7 | JohnJiangLA | 85.5 积分 | 17 | 7Ethan | 62.5 积分 | 27 | sqrthree | 55 积分 |
8 | EmilyQiRabbit | 82 积分 | 18 | haiyang-tju | 61.5 积分 | 28 | ssshooter | 54 积分 |
9 | rydensun | 80 积分 | 19 | lihanxiang | 59 积分 | 29 | MeFelixWang | 52 积分 |
10 | Colafornia | 79.5 积分 | 20 | pot-code | 58.5 积分 | 30 | cf020031308 | 50 积分 |
所有译者信息,详见 👉 译者积分表,感谢你们的辛勤付出 👍
2018 我们一直致力于新技术的生产和传播,2019 我们会继续提供更优质的内容给每一个清晨挤在的地铁 🚇、公交 🚌上的你,给午后埋头工作的你,给凌晨到午夜秉烛夜读的你。
在大家的共同努力下,掘金翻译计划又长了一岁,2018 年是掘金翻译计划飞速发展的一年,这一年无论是在译者团队的规模上,在输出译文的数量上,还是在翻译计划的影响力上,都有了极大的提升。
感谢各位译者这一年来对掘金翻译计划的支持,有了你们的支持,掘金翻译计划才能不断壮大,提高咱们的影响力,为更多开发者、更多的企业服务,营造更好的技术氛围。
2019,希望我们都能看到更好的自己 😊
对掘金翻译计划感兴趣的,同时对自身要求较高(有强迫症)、英文不错同时又会点技术(方向不限)的小伙伴,你们在哪,请用你的小手拿起鼠标 点击这里加入我们。
想与掘金翻译计划进行合作的公司或者其他合作商,可以加微信 517010193 详谈(备注翻译计划合作)。
]]>人们会给出五花八门的答案,在我看来,答案只有一个就是你有没有按照“道理”所述去践行。如果你真的认为它有道理,那你为什么不去践行呢?不去践行的原因可能有一下几个:
对一个正确的道理表现出懒惰,就是认知能力不足的体现。认为自己懒,说明自己认为自己能变得更好却不愿意变得更好。这里有两种情况:
明确认识到自己懒却不愿意变得“不那么懒”,证明他们对于如何正确做事的认知模糊不清,并不清楚付出更多的成本能否带来更好的结果。所以选择懒的这个行为,本质上是看不到正确做事能带来的巨大好处。
有人说不肯用功是自制力不足。这的确是一方面,但自知力不足的本质不也是认知不足吗?
例如学习:
因此才会选择不用功。
举个简单的例子,如果有人要跟你签一个合约,让你每周坚持消化两本书,每天早晨 5 点起来跑步一小时并从早上 7 点拼命工作到晚上 9 点,再进行一小时的深度思考然后才能休息,只要你坚持 10 年,就把这一个亿给你。我相信很多人在这种回报的诱惑下,是可以坚持下来的。因为什么?因为做这些事儿跟一个亿确实有着强关联性。但现在不和你签合约,而是告诉你这样的道理,说这么赶就肯定能赚大钱,相信 90% 的人就坚持不下来了。因为大多数人的认知看不到这么远。
当回报在某些人眼里不存在或不明显时,他们的动力就不足了自然表现出懒的特性。但是在另一些人眼里,未来的回报却是清晰可见的,于是他们选择努力。现在你还能说,自己天生就是懒吗?
懒并不是什么遮羞布,这恰恰是你跟别人在认知上的差距。
再举个例子,你办了一张健身卡,给自己设定了健身目标。但是你今天下班了有点累,就给自己找借口,说昨天练的太多了,今天浑身疼就不去了。但是如果明天上午你有一个商务会谈,你肯定会认真准备认真对待。这是因为在做选择和决策的时候,人们会考虑根据事情的性质、风险以及付出与收获来排出自己心里的优先级。
优先级是人主观设定的,有的人 60 分的事儿也会拼尽全力,有些人 90 分的事儿才会挪一挪屁股。
那怎样能够把事儿做得更好呢?
本文为《认知突围:做复杂时代的明白人》一书的读后感,本书作者为蔡垒磊。全书共分为七章,分别为重新认识自己、重新认识知识、重新认识金钱、重新认识时间、重新认识关系、重新认识人生和后记。书中有少部分内容还不错,只不过全书鸡汤感较足,可读可不读。
]]>下面是我对为什么我们从来不去感谢开源项目维护者的一些看法。
“蛤?这项目对我来说也太简单了吧。”
“我一周之内就能做一个更好的版本出来。”
确实,很多人都可以在黑客马拉松(hackathon)中建一个小工具,但是维护一个项目比建立一个项目要困难得多。开源项目维护者不仅仅需要写代码,更多时候还需要:
一个开源项目维护者必须一年到头无偿地做这么多复杂繁琐的工作。
我们如何使用一个开源项目:
开源项目就像水和空气一样,人们只是享受它带来的好处并且习以为常。我们不会对空气或水说感谢,因为我们不知道那是谁造的。但是我们知道是谁创建了这些非常棒的开源项目。但是我们知道这么棒的开源项目是谁维护的啊。
开源项目使用者可能会想:
“他们不需要这些,他们需要的是 pull request 和能够修复 bug 等实质性工作。”
“我想他们更喜欢收到捐款。”
但是他们真的很在意,有时候,开源项目维护者真的需要你对他说一声谢谢。当然,如果你能为项目捐款再好不过了,但是我知道的大多数开源项目维护者目的并不是赚钱。他们最终可能赚了钱,但与开发项目的动机相比,顶多算是一个副产品。我注意到,开源项目常常比非开源项目要好。因为最出色的开发者是出于激情和利他主义来开发产品的。如果你没能力或者不想捐钱,你可以给他们写句感谢的话。
“我们正在用双手改变世界。”
“我的项目必须在一周内启动。”
“我们每天都有数百封邮件需要回复。”
“我们只是没有时间。”
这些开源项目有更大的潜力去改变世界;或许其中的哪个项目已经改变了世界。
Github 在 Ruby 上使用 Rails
Instagram 使用 django
我们很多服务器都在运行着 Linux
可能没有这些开源项目就没有我们现在的这些项目。
十分钟,你可能做不了什么大事。现在社交媒体让我们养成了一种习惯,就是更愿意点击一个 upvote 或者类似的按钮,而不是花十分钟来写一封感谢信。尽管一个好的开源项目可能节省了不仅仅十分钟的时间。我在 ProductHunt 上公开 www.thankyouopensource.com 这个网站后,很多用户抱怨最低 300 个字符的限制条件。我设置这个条件是想避免像下面这样简单的话:
“谢谢,你们太棒了。”
“非常感谢。”
“我很喜欢你的开源项目。”
我就得这些用户太“忙”了,没空写一封感谢信。但是这对我来说看起来像没有意义的垃圾邮件,我认为读这些就是在浪费时间。我们希望看到的感谢信是处于自愿的而不是义务的。这之间的不同对我们来说显而易见。我们希望维护者们知道我们为什么感谢他们的付出,他们的项目怎么帮助了我们,我们这些发自内心的感谢也是维护者们继续付出的强大动力之一。最重要的是,我们欢迎所有对维护项目感兴趣的人的加入,因为这确实是一件很棒的工作。一旦你成为了维护者,你会在感谢心中看到很多人对你付出的真诚的感谢。我建议在 GitHub 上加一个 感谢 的标签,不仅仅是感谢和激励维护者,更是邀请更多的人加入到其中。
开源项目维护者是我们交朋友的好选择,也是公司雇佣员工时的明智之选。他们有激情,愿意分享,能够坚持。他们是真正的 MVP,他们应该得到真诚的感谢。
这是一个非官方的,为大家向开源项目作者和维护者表达感谢的平台。同时它还为项目维护者们宣传其他项目提供了一个平台 🤙
]]>
- 原文地址:Why we never thank open source maintainers
- 原文作者:Windson Yang
- 译文出自:掘金翻译计划
- 译者:LeviDing
但是你可能发现,为什么我也像朋友那样发了一条 @微信官方 的朋友圈,但是我等了很长时间还是没有圣诞帽呢 😭
那应该就是你的姿势不对,没有触发到自动戴圣诞帽程序,下面我就告诉你让微信为你戴上圣诞帽的正确姿势:
是不是挺麻烦的,那我就给你举个例子,你可以直接复制发朋友圈,让微信为你戴圣诞帽咯
祝福语 + 请给我一顶圣诞帽 @微信官方
例子:圣诞快乐,请给我一顶圣诞帽 @微信官方
现在你明白了吧,是不是很简单啊,赶紧复制上面的例子发送朋友圈,为自己戴上圣诞帽吧 😋
什么?没反应?你要等两分钟啊,戴帽子需要一些时间的,毕竟用户量这么大。
什么?还是不行?是不是复制错了啊,再检查检查吧 😧
你说啥?还是不行?
能行就怪了,上面我都是骗你的 😳
那为啥我朋友 @微信官方 就戴上了圣诞帽?那只能说明他是骗你的,他提前 P 好了戴帽子的头像,发完朋友圈以后手动换了头像 😂
哈哈哈,开个玩笑,提前祝大家圣诞快乐 👏
]]>想要圣诞帽的同学,关注微信公众号『LeviDing』,点击本文同名文章的 阅读原文 即可为自己 戴上一顶圣诞帽 吧,不要用回来打我 🤣
在公众号后台回复『圣诞』即可获得 专属圣诞帽表情包,快来试试吧 🤣
与 Chrome 应用类似,Chrome 扩展主要是用于扩充 Chrome 浏览器的功能。他体现为一些文件的集合,包括前端文件(HTML/CSS/JS),配置文件 manifest.json。主要采用 JavaScript 语言进行编写。个别扩展可能会用到 DLL 和 SO 动态库,不过出于安全以及职责分离的考虑,在后续的标准中,将会被舍弃,这里不再赘述。
Chrome 扩展能做的事情:
这里需要着重提一下,前端开发者喜欢的 DevTools 工具,在这里也能进行自定义
上面转自 Chrome 插件开发简介(一)——开发入门本文就记录下开发插件必备的 mainfest.json 的相关知识,以备后用。
{ |
在这篇文章中,我们将介绍一些最常见的错误学习方法,并了解如何避免这些错误。许多技巧不仅适用于 JavaScript,甚至可以用到 web 开发上,所以也算是一种福利。
我们来吧!
开始学习 JavaScript 之前,你可以找到很多相关的信息。如果你去看,就会发现一些 JavaScript 是最好的或者是最坏的、你是需要这个框架还那个框架的相关信息。你也可能会听到你需要以某种方式编写 JavaScript,否则你永远不会成为“真正”的开发人员等。
不管这些说的正确与否,没有什么比浪费六个月到一年还没有开始更糟糕。
开始敲代码吧,它不一定完美,可能很糟糕。但如果你开始了,就通过了阻碍很多人的障碍之一了。
JavaScript 框架建立在原生 JavaScript 之上,因此如果你理解了 JavaScript,你也就自然而然的知道如何使用任何 JavaScript 框架的基本原理。
然而,如果你直接学习一个框架,最后也只是记住了它的语法却不理解它的原理。这就像在不知道词语意思的情况下造句,最终你只是随便地记住了一些词语,却不知道这些词语的意思并且不会组织这些词语来学以致用。
如果你直接进入一个框架,那将会更难学习,当你需要另一个框架你会更难适应。如果你首先学习基础的 JavaScript,那么你将有一个坚实的基础来了解所有的框架。
最常见的错误之一就是在理解概念之后立即采取行动。
我一直在努力解决这个问题,因为一旦了解某些东西,你就想更进一步。
像对待新玩具一样对待每个概念是很有帮助的;这意味着你需要花一些时间来享受你刚学到的东西。玩耍、实验,看看你能不能做一些新的事情。你会学到很多,你会记得更好。
当你感觉自己闭着眼睛都能运用自如的时候再继续向下学习。可能在达到这一步之前,你需要更多的时间,但是这将是你接下来的学习变得更快。
另一方面,如果你过于急躁,你就不会太注意细节。但令人沮丧的是,这会使你之后的学习成本大幅提升。其实这也是人们常说要放弃学习 JavaScript 的常见原因之一。
学习就像爬楼梯:如果你能走一步,你可以继续采取更多的步骤,直到你达到目标。当有些东西难以理解时,往往是因为你想要进行一次飞跃,而不是一次走一步。当然这是痴心妄想!
在实际场景中,我看到人们对某段代码不理解的时候,我会请他们解释一下,他们会试图一下解释清整个问题。那我会请他们再一行一行的解释一遍,这样是有道理的。
如果有些部分很让人费解,那经常是因为跳过了某些东西,那么这也将有助于你去关注细节,直到找出症结所在。如果一个概念在分解之后仍然没有意义,那你也会有更容易找到相关解决方法,因为查找特定的主题比胡乱搜索更容易。
刚开始学习 JavaScript 的人经常会说“我就随便定个小目标,写一个 Facebook 那样的网站算了”,没有意识到项目所涉及的深度。当项目逐渐深入时,他们就放弃学习 JavaScript 了。
我更详细地介绍了关于项目,但是在学习的时候,从一些基本概念开始会更容易。当你开始做项目时,你可以在工具包中添加一些构建工具。
更明确地说,我不是要那种越旷日持久的项目。我刚刚发现,如果我先做了一些简单的部分,比如在浏览器中显示一些文本或响应一个按钮,那么就可以更轻松地启动项目。
当你学习 JavaScript 时,你可能会在不符合真实环境下进行练习。例如,你可能在网站的内置代码编辑器中输入内容,或者你可能依赖于教程中的粘贴文件。
这些方法对于学习来说可能是非常好的,但是你也可以尝试自己搭建环境。这意味着使用你自己的文本编辑器,并从头开始编写项目。
如果你不自己独立练习每一个概念,那你会依赖于训练环境。你最终会遇到这样的情况:你已经花了很多时间来学习,但你一个都无法掌握。
让自己更沮丧的最简单的方法之一就是和大神进行比较。因为你总是看他们在那里,而不是看他们如何到达那里。
举个例子,人们看到我的教程,并问我如何写这么干净的代码。他们说他们无法编写像这样的干净的代码,所以也许他们根本就不是 JavaScript 的那块料。
事实是我的过程是一团糟。我不断试验、犯错、查阅资料,写下丑陋的代码,最后把所有的内容都细化成一个可呈现的教程。人们看了优秀的版本,并且假设整个过程就是这样的。我也做过关于教程作者的这些假设,直到我开始写我自己的教程。
关键点是,认真学习你正在学习的东西,你会得到进步。继续重复这个过程,很快别人就会好奇你是如何达到那种高度的。
你会自然而然的花费大量的时间来观看视频和教程,但是除非你自己动手编写代码,否则你不能真的学会。
光看而不采取实际行动是很危险的,你会有一种你正在学习的错觉。六个月后,你会发现自己什么都没学会。
写 15 分钟的代码比上你光看一小时的教程有用多了。
阅读教程时,很容易陷入照葫芦画瓢的情况。这种教程并不会教你如何解决一个问题,例如需要进行怎样的测试,如何一步一步的探索可能出问题的方向。因此,只会跟着教程走的人往往学不到真正的知识。
那么解决方案是什么?
不要只知道跟着教程一步步走,而是要花点儿时间去自己实现。例如,如果您正在学习幻灯片教程,请尝试显示和隐藏 div,然后尝试计时,然后尝试另一个小部分。相对于跟着教程一步步地走,通过亲身尝试并拓展你将学到更多知识,并且有可能将它应用得更好。
在你读完这篇文章后,如果你问我最想让你记住什么,那就是通过采取最小的步骤来取得最大的进步。
无论你在学习什么,都要好好学习它本质上的东西。尝试你学到的东西,并乐在其中。
有时可能很困难,但这没关系。挑战意味着你正在提升个人能力,这将使你进步。如果一切总是太容易,这可能意味你需要进行些改变了。
我希望这篇文章对你有所帮助,如果有什么其他的帮助过你学习 JavaScript 的方法,欢迎你随时在评论中分享!
]]>
还记得 2008 年,自己选择了去吉林省延边第三中学读初中,独自一人远离我的小家,心里又些许的兴奋,但更多的是不舍。三年的半封闭式初中生活,让我成长很多,当然中间有犯过现在想起来很傻叉的错误,但想想谁的青春不烦错呢,也许正是这种不完美,才增添了生活的乐趣吧。
2011 年,我来到了我选择的吉林省延边第二高级中学读高中,这所学校是我现在想起来就满满自豪感的学校。哈哈哈,大家熟知的,锤子手机罗永浩,就是在这所学校读的高中,但是中途不念了。
2014 年阴差阳错,来到了现在就读的中国地质大学(武汉),有想过复读重考吗?根本没想过,高三那种生活我不想再经历第二次,不是身体累,而是心累。可以说,高三经历的那些让我成熟了不少。
2017 年 9 月,也就是此时此刻,回想大学四年的生活啊,我可以用六个字总结:不完美,不遗憾。
我曾经因为各种事交杂在一起而忙的要哭,路过没,哭过,被窝里自己哭啊,擦擦眼泪就成长了不是吗。我也曾经因为考试结束,感觉要挂科而心烦意乱,不对,我找不到词来形容那种感受了。我已经想好,如果挂了需要承受什么样的后果,做好了相应的打算,但庆幸最终的结果是好的,这也给我敲响了警钟。
当然,我也曾经因为获得一些奖励,一些荣誉而高兴,毕竟是自己努力获得的成果。我也摸清了学校一些比赛的套路,同时也对我总结的套路进行了实践,较为轻松的赢得了些比赛,套路是可以传授给身边的小师弟的,哈哈。
转眼间在北京实习已经一个月零五天了,学习到了一个公司是如何运作的,各个部门间大致是如何配合的等等。当然,还有很多需要学习的地方。希望我在公司的期间能够为公司创造些价值吧。
前段时间收获了第一笔做网站的几千块收入,不用和父母要钱换了个手机,还挺开心的。哈哈哈,很快就能经济独立了。
现在呢,我应该是选择保研了,对于这个决定,我不置可否。只希望自己能够不忘初心,努力朝着自己心中的方向努力前行。希望毕业能赚大钱,迎娶白富美,走向人生巅峰 『手动滑稽』。
明早就到武汉啦,再回去吃一碗热干面,喝一个豆浆,去游个泳。想想也是挺不错的呢。13 号晚上再坐车返京。
这时耳机里放着李健的『温暖』,哦对,他是我最喜欢的歌手之一呢。就这样睡吧,晚安。
记于 2017 年 9 月 11 日晚。
]]>后记,说好的回武汉吃热干面喝豆浆,我怎么就忘了呢!还好吃了周黑鸭。继续加油吧,小伙子。
push --force
具有破坏性,因为它无条件地覆盖远程存储库,无论你在本地拥有什么。使用这个命令,可能覆盖团队成员在此期间推送的所有更改。然而,有一个更好的办法,当你需要强制推送,但仍需确保不覆盖其他人的工作时,-force-with-lease
这条指令选项可以帮助到你。众所周知,git 的 push -force
指令是不推荐被使用的,因为它会破坏其他已经提交到共享库的内容。虽然这不总是完全致命的(如果那些修改的内容仍在某些同事的本地工作域中,那之后他们能被重新合并),但是这样的做法很欠考虑,最糟糕的情况会造成灾难性的损失。这是因为 --force
指令选项迫使分支的头指针指向你个人的修改记录,而忽略了那些其他和你同时进行地更改。
强制推动最常见的原因之一是当我们被迫 rebase
一个分支的时候。为了说明这一点,我们来看一个例子。我们有一个项目,其中有一个功能分支,Alice 和 Bob 要同时在这个分支上工作。他们都 git clone...
了这个仓库,并开始工作。
最初,Alice 完成了她负责的功能,并将其 push
到主仓库。这都没啥问题。
Bob 也完成了他的工作,但在 push
之前,他注意到一些变化已被合并到了 master 分支。想要保持一棵整洁的工作树,他会对主分支执行一个 rebase
。当然,当他 push
这个经过 rebase
的分支的时候将被拒绝。然而,Bob 没有意识到 Alice 已经 push
了她的工作。Bob 执行了 push --force
命令。不幸的是,这将清除 Alice 在远程主仓库的所有更改和记录。
这里的问题是,进行强制推送的 Bob 不知道为什么他的 push
会被拒绝,所以他认为这是 rebase
造成的,而不是由于 Alice 的变化。这就是为什么 --force
在同一个分支上协作的时候要杜绝的;并且通过远程主仓库的工作流程,任何分支都可以被共享。
但是 --force
有一个不为众人所知的亲戚,它在一定程度上能防止强制更新操作带来的结构性破坏;它就是 --force-with-lease
。
--force-with-lease
是用于拒绝更新一个分支,除非该分支达到我们期望的状态。即没有人在上游更新分支内容。 实际上,通过检查上游引用是我们所期望的,因为引用是散列,并将父系链隐含地编码成它们的值。
你可以告诉 --force-with-lease
究竟要检查什么,默认情况下会检查当前的远程引用。这在实践中意味着,当 Alice 更新她的分支并将其推送到远程仓库时,分支的引用指针将被更新。现在,除非 Bob从远程仓库 pull
一下,否则本地对远程仓库的引用将过期。当他使用 --force-with-lease
推送时,git 会检查本地与远程的引用是否对应,并拒绝 Bob 的强制推送。--force-with-lease
有效地只在没有人在上游更新分支内容的时候允许你强制推送。就像是一个带有安全带的 --force
。它的一个快速演示可能有助于说明这一点:
Alice 已经对该分支进行了一些更改,并已推送到了远程主仓库。Bob 现在又对远程仓库的 master
分支进行了 rebases
操作:
ssmith$ git rebase master |
rebase
之后,他试图将自己的更改 push
上去,但服务器拒绝了,因为这会覆盖 Alice 的工作:
ssmith$ git push |
但 Bob 认为这是 rebase
操作造成的,并决定强制 push
:
ssmith$ git push --force |
然而,如果他使用了 --force-with-lease
,则会得到不同的结果,因为 git 会检查远程分支,发现 从上一次 Bob 使用 fetch
到现在,实际上并没有被更新:
ssmith$ git push -n --force-with-lease |
当然,在这有一些关于 git 的注意事项。上面展示的,只有当 Alice 已经将其更改推送到远程存储库时,它才有效。这不是一个严重的问题,但是如果她想修改她提交的东西,那她去 pull
分支时,会被提示合并被更改。
一个更微妙的问题是,我们有方法去骗 git,让 git 认为这个分支没有被修改。在正常使用情况下,最常发生这种现象的情况是,Bob 使用 git fetch
而不是
git pull来更新他的本地副本。
fetch
将从远程仓库拉出对象和引用,但没有匹配的 merge
则不会更新工作树。这将使本地仓库看起来已经与远程仓库进行了同步更新,但实际上本地仓库并没有进行更新,并欺骗 --force-with-lease
命令,成功覆盖远程分支,就像下面这个例子:
ssmith$ git push --force-with-lease |
这个问题的最简单的答案就是,简单的说“不要在没有合并的情况下 fetch
远程该分支”(或者更常用的方法是 pull
,这个操作包含了前面的两个),但是如果由于某种原因你希望在用 --force-with-lease
进行代码上传之前进行 fetch
,那么这有一种比较安全的方法。像 git 那么多的属性一样,引用只是对象的指针,所以我们可以创建我们自己的引用。在这种情况下,我们可以在进行 fetch
之前,为远程仓库引用创建“保存点”的副本。然后,我们可以告诉 --force-with-lease
将此作为引用值,而不是已经更新的远程引用。
为了做到这一点,我们使用 git 的 update-ref
功能来创建一个新的引用,以保存远程仓库在任何 rebase
或 fetch
操作前的状态。这有效地标记了我们开始强制 push
到远程的工作节点。在这里,我们将远程分支 dev
的状态保存到一个名为 dev-pre-rebase
的新引用中:
ssmith$ git update-ref refs/dev-pre-rebase refs/remotes/origin/dev |
这时呢,我们就可以进行 rebase
和 fetch
操作,然后使用保存的 ref
来保护远程仓库,以防有人在工作时做了更改:
ssmith$ git rebase master |
我们可以看到 --force-with-lease
对于有时需要进行强制推送的 git 用户来说,是一个很有用的工具。但是,对于 --force
操作的所有风险来说,这并不是万能的,如果不了解它内部的工作及其注意事项,就不应该使用它。
但是,在最常见的用例中,开发人员只要按照正常的方式进行 pull
和 push
操作即可。偶尔使用下 rebase
,这个命令提供了一些我们非常需要的,防止强制推送带来破坏的保护功能。因此,我希望在未来版本的 git(但可能 3.0 以前都不会实现),它将成为 --force
的默认行为,并且当前的行为将被降级到显示其实际行为的选项中,例如:--force-replace-remote
。
]]>原文地址:-force considered harmful; understanding git’s –force-with-lease
原文作者:Steve Smith
译者:LeviDing
校对者:yifili09