当英文单词比较长时, 折断换行的时候想加一个"-"
word-break: break-all
这个只能折断换行, 该怎么加连接符呢, 求教
没有足够的数据
(゚∀゚ )
暂时没有任何数据
木纸鸢 提出了问题 · 2019-06-14
当英文单词比较长时, 折断换行的时候想加一个"-"
word-break: break-all
这个只能折断换行, 该怎么加连接符呢, 求教
当英文单词比较长时, 折断换行的时候想加一个"-" {代码...} 这个只能折断换行, 该怎么加连接符呢, 求教
关注 2 回答 1
木纸鸢 收藏了文章 · 2019-02-17
划重点,这是一道面试必考题,我靠这道题刷掉了多少面试者✧(≖ ◡ ≖✿)嘿嘿
首先这是一道非常棒的面试题,可以考察面试者的很多方面,比如基本功,代码能力,逻辑能力,而且进可攻,退可守,针对不同级别的人可以考察不同难度,比如漂亮妹子就出1☆题,要是个帅哥那就得上5☆了,(*^__^*) 嘻嘻……
无论面试者多么优秀,漂亮的回答出问题,我总能够潇洒的再抛出一个问题,看着面试者露出惊异的眼神,默默一转身,深藏功与名
本文我将给大家破解深拷贝的谜题,由浅入深,环环相扣,总共涉及4种深拷贝方式,每种方式都有自己的特点和个性
再开始之前需要先给同学科普下什么是深拷贝,和深拷贝有关系的另个一术语是浅拷贝又是什么意思呢?如果对这部分部分内容了解的同学可以跳过
其实深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据
// 基本类型
var a = 1;
var b = a;
a = 2;
console.log(a, b); // 2, 1 ,a b指向不同的数据
// 引用类型指向同一份数据
var a = {c: 1};
var b = a;
a.c = 2;
console.log(a.c, b.c); // 2, 2 全是2,a b指向同一份数据
对于引用类型,会导致a b指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个,有时候这可能不是我们想要的结果,如果对这种现象不清楚的话,还可能造成不必要的bug
那么如何切断a和b之间的关系呢,可以拷贝一份a的数据,根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝
var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // 浅拷贝
a2.b.c === a1.b.c // true
var a3 = clone(a3); // 深拷贝
a3.b.c === a1.b.c // false
浅拷贝的实现非常简单,而且还有多种方法,其实就是遍历对象属性的问题,这里只给出一种,如果看不懂下面的方法,或对其他方法感兴趣,可以看我的这篇文章
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
深拷贝的问题其实可以分解成两个问题,浅拷贝+递归,什么意思呢?假设我们有如下数据
var a1 = {b: {c: {d: 1}};
只需稍加改动上面浅拷贝的代码即可,注意区别
function clone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
if (typeof source[i] === 'object') {
target[i] = clone(source[i]); // 注意这里
} else {
target[i] = source[i];
}
}
}
return target;
}
大部分人都能写出上面的代码,但当我问上面的代码有什么问题吗?就很少有人答得上来了,聪明的你能找到问题吗?
其实上面的代码问题太多了,先来举几个例子吧
(⊙o⊙),下面我们来看看各个问题的解决办法,首先我们需要抽象一个判断对象的方法,其实比较常用的判断对象的方法如下,其实下面的方法也有问题,但如果能够回答上来那就非常不错了,如果完美的解决办法感兴趣,不妨看看这里吧
function isObject(x) {
return Object.prototype.toString.call(x) === '[object Object]';
}
函数需要校验参数,如果不是对象的话直接返回
function clone(source) {
if (!isObject(source)) return source;
// xxx
}
关于第三个问题,嗯,就留给大家自己思考吧,本文为了减轻大家的负担,就不考虑数组的情况了,其实ES6之后还要考虑set, map, weakset, weakmap,/(ㄒoㄒ)/~~
其实吧这三个都是小问题,其实递归方法最大的问题在于爆栈,当数据的层次很深是就会栈溢出
下面的代码可以生成指定深度和每层广度的代码,这段代码我们后面还会再次用到
function createData(deep, breadth) {
var data = {};
var temp = data;
for (var i = 0; i < deep; i++) {
temp = temp['data'] = {};
for (var j = 0; j < breadth; j++) {
temp[j] = j;
}
}
return data;
}
createData(1, 3); // 1层深度,每层有3个数据 {data: {0: 0, 1: 1, 2: 2}}
createData(3, 0); // 3层深度,每层有0个数据 {data: {data: {data: {}}}}
当clone层级很深的话就会栈溢出,但数据的广度不会造成溢出
clone(createData(1000)); // ok
clone(createData(10000)); // Maximum call stack size exceeded
clone(createData(10, 100000)); // ok 广度不会溢出
其实大部分情况下不会出现这么深层级的数据,但这种方式还有一个致命的问题,就是循环引用,举个例子
var a = {};
a.a = a;
clone(a) // Maximum call stack size exceeded 直接死循环了有没有,/(ㄒoㄒ)/~~
关于循环引用的问题解决思路有两种,一直是循环检测,一种是暴力破解,关于循环检测大家可以自己思考下;关于暴力破解我们会在下面的内容中详细讲解
有些同学可能见过用系统自带的JSON来做深拷贝的例子,下面来看下代码实现
function cloneJSON(source) {
return JSON.parse(JSON.stringify(source));
}
其实我第一次简单这个方法的时候,由衷的表示佩服,其实利用工具,达到目的,是非常聪明的做法
下面来测试下cloneJSON有没有溢出的问题,看起来cloneJSON内部也是使用递归的方式
cloneJSON(createData(10000)); // Maximum call stack size exceeded
既然是用了递归,那循环引用呢?并没有因为死循环而导致栈溢出啊,原来是JSON.stringify内部做了循环引用的检测,正是我们上面提到破解循环引用的第一种方法:循环检测
var a = {};
a.a = a;
cloneJSON(a) // Uncaught TypeError: Converting circular structure to JSON
其实破解递归爆栈的方法有两条路,第一种是消除尾递归,但在这个例子中貌似行不通,第二种方法就是干脆不用递归,改用循环,当我提出用循环来实现时,基本上90%的前端都是写不出来的代码的,这其实让我很震惊
举个例子,假设有如下的数据结构
var a = {
a1: 1,
a2: {
b1: 1,
b2: {
c1: 1
}
}
}
这不就是一个树吗,其实只要把数据横过来看就非常明显了
a
/ \
a1 a2
| / \
1 b1 b2
| |
1 c1
|
1
用循环遍历一棵树,需要借助一个栈,当栈为空时就遍历完了,栈里面存储下一个需要拷贝的节点
首先我们往栈里放入种子数据,key
用来存储放哪一个父元素的那一个子元素拷贝对象
然后遍历当前节点下的子元素,如果是对象就放到栈里,否则直接拷贝
function cloneLoop(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
改用循环后,再也不会出现爆栈的问题了,但是对于循环引用依然无力应对
有没有一种办法可以破解循环应用呢?别着急,我们先来看另一个问题,上面的三种方法都存在的一个问题就是引用丢失,这在某些情况下也许是不能接受的
举个例子,假如一个对象a,a下面的两个键值都引用同一个对象b,经过深拷贝后,a的两个键值会丢失引用关系,从而变成两个不同的对象,o(╯□╰)o
var b = 1;
var a = {a1: b, a2: b};
a.a1 === a.a2 // true
var c = clone(a);
c.a1 === c.a2 // false
如果我们发现个新对象就把这个对象和他的拷贝存下来,每次拷贝对象前,都先看一下这个对象是不是已经拷贝过了,如果拷贝过了,就不需要拷贝了,直接用原来的,这样我们就能够保留引用关系了,✧(≖ ◡ ≖✿)嘿嘿
但是代码怎么写呢,o(╯□╰)o,别急往下看,其实和循环的代码大体一样,不一样的地方我用// ==========
标注出来了
引入一个数组uniqueList
用来存储已经拷贝的数组,每次循环遍历时,先判断对象是否在uniqueList
中了,如果在的话就不执行拷贝逻辑了
find
是抽象的一个函数,其实就是遍历uniqueList
// 保持引用关系
function cloneForce(x) {
// =============
const uniqueList = []; // 用来去重
// =============
let root = {};
// 循环数组
const loopList = [
{
parent: root,
key: undefined,
data: x,
}
];
while(loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
// =============
// 数据已经存在
let uniqueData = find(uniqueList, data);
if (uniqueData) {
parent[key] = uniqueData.target;
break; // 中断本次循环
}
// 数据不存在
// 保存源数据,在拷贝数据中对应的引用
uniqueList.push({
source: data,
target: res,
});
// =============
for(let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
});
} else {
res[k] = data[k];
}
}
}
}
return root;
}
function find(arr, item) {
for(let i = 0; i < arr.length; i++) {
if (arr[i].source === item) {
return arr[i];
}
}
return null;
}
下面来验证一下效果,amazing
var b = 1;
var a = {a1: b, a2: b};
a.a1 === a.a2 // true
var c = cloneForce(a);
c.a1 === c.a2 // true
接下来再说一下如何破解循环引用,等一下,上面的代码好像可以破解循环引用啊,赶紧验证一下
惊不惊喜,(*^__^*) 嘻嘻……
var a = {};
a.a = a;
cloneForce(a)
看起来完美的cloneForce
是不是就没问题呢?cloneForce
有两个问题
第一个问题,所谓成也萧何,败也萧何,如果保持引用不是你想要的,那就不能用cloneForce
了;
第二个问题,cloneForce
在对象数量很多时会出现很大的问题,如果数据量很大不适合使用cloneForce
上边的内容还是有点难度,下面我们来点更有难度的,对比一下不同方法的性能
我们先来做实验,看数据,影响性能的原因有两个,一个是深度,一个是每层的广度,我们采用固定一个变量,只让一个变量变化的方式来测试性能
测试的方法是在指定的时间内,深拷贝执行的次数,次数越多,证明性能越好
下面的runTime
是测试代码的核心片段,下面的例子中,我们可以测试在2秒内运行clone(createData(500, 1)
的次数
function runTime(fn, time) {
var stime = Date.now();
var count = 0;
while(Date.now() - stime < time) {
fn();
count++;
}
return count;
}
runTime(function () { clone(createData(500, 1)) }, 2000);
下面来做第一个测试,将广度固定在100,深度由小到大变化,记录1秒内执行的次数
深度 | clone | cloneJSON | cloneLoop | cloneForce |
---|---|---|---|---|
500 | 351 | 212 | 338 | 372 |
1000 | 174 | 104 | 175 | 143 |
1500 | 116 | 67 | 112 | 82 |
2000 | 92 | 50 | 88 | 69 |
将上面的数据做成表格可以发现,一些规律
我们先来分析下各个方法的时间复杂度问题,各个方法要做的相同事情,这里就不计算,比如循环对象,判断是否为对象
cloneJSON的速度只有clone的50%,很容易理解,因为其会多进行一次递归时间
cloneForce由于要判断对象是否在缓存中,而导致速度变慢,我们来计算下判断逻辑的时间复杂度,假设对象的个数是n,则其时间复杂度为O(n2),对象的个数越多,cloneForce的速度会越慢
1 + 2 + 3 ... + n = n^2/2 - 1
关于clone和cloneLoop这里有一点问题,看起来实验结果和推理结果不一致,其中必有蹊跷
接下来做第二个测试,将深度固定在10000,广度固定为0,记录2秒内执行的次数
宽度 | clone | cloneJSON | cloneLoop | cloneForce |
---|---|---|---|---|
0 | 13400 | 3272 | 14292 | 989 |
排除宽度的干扰,来看看深度对各个方法的影响
下面我们来测试一下cloneForce的性能极限,这次我们测试运行指定次数需要的时间
var data1 = createData(2000, 0);
var data2 = createData(4000, 0);
var data3 = createData(6000, 0);
var data4 = createData(8000, 0);
var data5 = createData(10000, 0);
cloneForce(data1)
cloneForce(data2)
cloneForce(data3)
cloneForce(data4)
cloneForce(data5)
通过测试发现,其时间成指数级增长,当对象个数大于万级别,就会有300ms以上的延迟
尺有所短寸有所长,无关乎好坏优劣,其实每种方法都有自己的优缺点,和适用场景,人尽其才,物尽其用,方是真理
下面对各种方法进行对比,希望给大家提供一些帮助
clone | cloneJSON | cloneLoop | cloneForce | |
---|---|---|---|---|
难度 | ☆☆ | ☆ | ☆☆☆ | ☆☆☆☆ |
兼容性 | ie6 | ie8 | ie6 | ie6 |
循环引用 | 一层 | 不支持 | 一层 | 支持 |
栈溢出 | 会 | 会 | 不会 | 不会 |
保持引用 | 否 | 否 | 否 | 是 |
适合场景 | 一般数据拷贝 | 一般数据拷贝 | 层级很多 | 保持引用关系 |
本文的灵感都来自于@jsmini/clone,如果大家想使用文中的4种深拷贝方式,可以直接使用@jsmini/clone这个库
// npm install --save @jsmini/clone
import { clone, cloneJSON, cloneLoop, cloneForce } from '@jsmini/clone';
本文为了简单和易读,示例代码中忽略了一些边界情况,如果想学习生产中的代码,请阅读@jsmini/clone的源码
@jsmini/clone孵化于jsmini,jsmini致力于为大家提供一组小而美,无依赖的高质量库
jsmini的诞生离不开jslib-base,感谢jslib-base为jsmini提供了底层技术
感谢你阅读了本文,相信现在你能够驾驭任何深拷贝的问题了,如果有什么疑问,欢迎和我讨论
最后推荐下我的新书《React状态管理与同构实战》,深入解读前沿同构技术,感谢大家支持
京东:https://item.jd.com/12403508.html
当当:http://product.dangdang.com/25308679.html
最后最后招聘前端,后端,客户端啦!地点:北京+上海+成都,感兴趣的同学,可以把简历发到我的邮箱: yanhaijing@yeah.net
原文网址:http://yanhaijing.com/javascr...
查看原文首先这是一道非常棒的面试题,可以考察面试者的很多方面,比如基本功,代码能力,逻辑能力,而且进可攻,退可守,针对不同级别的人可以考察不同难度,比如漂亮妹子就出1☆题,要是个帅哥那就得上5☆了,(*^__^*) 嘻嘻……
木纸鸢 收藏了文章 · 2018-06-25
本章的目标是提供一些Flow工具的介绍与使用建议。Flow本质上也只是个检查工具,它并不会自动修正代码中的错误,也不会强制说你没按照它的警告消息修正,就不会让你运行程序。当然,并没有要求什么时候一定要用这类的工具,只是这种作法可以让你的代码更具强健性与提高阅读性,也可以直接避去很多不必要的数据类型使用上的问题,这种开发方式目前在许多框架与函数库项目,或是以JavaScript应用为主的开发团队中都已经都是必用工具。
注: 本文内容大部份参考自Flow官网,是之前我个人博客文章 - "Flow静态数据类型的检查工具,10分钟快捷入门"的增修版本。
注: 本文内容字数过万,去除代码也有数千字,笔误在所难免,有错再回馈留言吧。
"奇异博士"说过「使用警语应该要加注在书的最前面」。所以我把注意项目先加在这里。
由于Flow还是个年轻的项目,问题仍然很多,功能也没你想像中完整,用起来有时候会卡顿是正常的,效能仍须改善。以后用户愈来愈多就会愈作愈好。
Windows平台的支持也是几个月前(2016.8)时的事,Flow只支持64位元的作业系统,32位元就不能用了。
如果你是要学或用React或Vue.js等等,Flow是必学的。不管你要用不用,库源码里面都用了。
Flow是个JavaScript的静态类型检查工具,由Facebook出品的开源码项目,问世只有一年多,是个相当年轻的项目。简单来说,它是对比TypeScript语言的解决方式。
会有这类解决方案,起因是JavaScript是一种弱(动态)数据类型的语言,弱(动态)数据类型代表在代码中,变量或常量会自动依照赋值变更数据类型,而且类型种类也很少,这是直译式脚本语言的常见特性,但有可能是优点也是很大的缺点。优点是容易学习与使用,缺点是像开发者经常会因为赋值或传值的类型错误,造成不如预期的结果。有些时候在使用框架或函数库时,如果没有仔细看文件,亦或是文件写得不清不楚,也容易造成误用的情况。
这个缺点在应用规模化时,会显得更加严重。我们在开发团队的协同时,一般都是用详尽的文字说明,来降低这个问题的发生,但JS语言本身无法有效阻止这些问题。而且说明文件也需要花时间额外编写,其他的开发者阅读也需要花时间。在现今预先编译器流行的年代,像TypeScript这样的强(静态)类的JavaScript超集语言就开始流行,用严格的角度,以JavaScript语言为基底,来重新打造另一套具有强(静态)类型特性的语言,就如同Java或C#这些语言一样,这也是为什么TypeScript称自己是企业级的开发JavaScript解决方案。
注: 强(静态)类型语言,意思是可以让变量或常量在声明(定义)时,就限制好只能使用哪种类型,之后在使用时如果发生类型不相符时,就会发出错误警告而不能编译。但不只这些,语言本身也会拓展了更多的类型与语法。
TypeScript自然有它的市场,但它有一些明显的问题,首先是JavaScript开发者需要再进一步学习,内容不少,也有一定陡峭的学习曲线,不过这还算小事情。重大的事情是需要把已经在使用的应用代码,都要整个改用TypeScript代码语法,才能发挥完整的功用。这对很多已经有内部代码库的大型应用开发团队而言,将会是个重大的决定,因为如果不往全面重构的路走,将无法发挥强(静态)类型语言的最大效用。
所以许多现行的开源码函数库或框架,并不会直接使用TypeScript作为代码的语言,另一方面当然因为是TypeScript并非普及到一定程度的语言,社群上有热爱的粉丝也有不是那么支持的反对者。当然,TypeScript也有它的优势,自从TypeScript提出了DefinitelyTyped的解决方式之后,让现有的函数库能额外再定义出里面使用的类型,这也是另一个可以与现有框架与库相整合的方案,这让许多函数库与框架都提交定义档案,提供了另一种选择。另一个优势是,TypeScript也是个活跃的开源码项目,发展到现在也有一段时间,算是逐渐成熟的项目。它的背后有微软公司的支持,在最近发布的知名的、全新打造过的Angular2框架中(由Google主导),也采用了TypeScript作为基础的开发语言。
现在,Flow提供了另一个新的选项,它是一种强(静态)类型的辅助检查工具。Flow的功能是让现有的JavaScript语法可以事先作类型的声明(定义),在开发过程中进行自动检查,当然在最后编译时,一样可以用babel工具来移除这些标记。
相较于TypeScript是另外重新制定一套语言,最后再经过编译为JavaScript代码来运行。Flow走的则是非强制与非侵入性的路线。Flow的优点是易学易用,它的学习曲线没有TypeScript来得高,虽然内容也很多,但大概一天之内学个大概,就可以渐进式地开始使用。而且因为Flow从头到尾只是个检查工具,并不是新的程序语言或超集语言,所以它可以与各种现有的JavaScript代码兼容,如果你哪天不想用了,就去除掉标记就是回到原来的代码,没什么负担。当然,Flow的功用可能无法像TypeScript这么全面性,也不可能改变要作某些事情的语法结构。
总结来说,这两种方式的目的是有些相似的,各自有优点也有不足之处,青菜萝卜各有所爱,要选择哪一种方式就看你的选择。
这种类型不符的情况在代码中非常容易发生,例如以下的例子:
function foo(x) {
return x + 10
}
foo('Hello!')
x
这个传参,我们在函数声明时希望它是个数字类型,但最后使用调用函数时则用了字符串类型。最后的结果会是什么吗? "Hello!10",这是因为加号(+)在JavaScript语言中,除了作为数字的加运算外,也可以当作字符串的连接运算。想当然这并不是我们想要的结果。
聪明如你应该会想要用类型来当传参的识别名,容易一眼看出传参要的是什么类型,像下面这样:
function foo(number) {
return number + 10
}
但如果在复合类型的情况,例如这个传参的类型可以是数字类型也可以是布尔类型,你又要如何写得清楚?更不用说如果是个复杂的对象类型时,结构又该如何先确定好?另外还有函数的返回类型又该如何来写?
利用Flow类型的定义方式,来解决这个小案例的问题,可以改写为像下面的代码:
// @flow
function foo(x: number): number {
return x + 10
}
foo('hi')
你有看到在函数的传参,以及函数的圆括号(())后面的两个地方,加了: number
标记,这代表这个传参会限定为数字类型,而返回值也只允许是数字类型。
当使用非数字类型的值作为传入值时,就会出现由Flow工具发出的警告消息,像下面这样:
message: '[flow] string (This type is incompatible with number See also: function call)'
这消息是说,你这函数的传参是string(字符串)类型,与你声明的number(数字)不相符合。
如果是要允许多种类型也是很容易可以加标记的,假使这个函数可以使用布尔与数字类型,但返回可以是数字或字符串,就像下面这样修改过:
// @flow
function foo(x: number | boolean): number | string {
if (typeof x === 'number') {
return x + 10
}
return 'x is boolean'
}
foo(1)
foo(true)
foo(null) // 这一行有类型错误消息
由上面这个小例子你可以想见,如果在多人协同开发某个有规模的JavaScript应用时,这种类型的输出输入问题就会很常遇见。如果利用Flow工具的检查,可以避免掉许多不必要的类型问题。
可能你会认为Flow工具只能运用在小型代码中,但实际上Facebook会创造出Flow工具,有很大的原因是为了React与React Native。
举一个我最近正在研究的的函数库代码中NavigationExperimental(这网址位置有可能会变,因为是直接连到源码里),这里面就预先声明了所有的对象结构,像下面这样的代码:
export type NavigationGestureDirection = 'horizontal' | 'vertical';
export type NavigationRoute = {
key: string,
title?: string
};
export type NavigationState = {
index: number,
routes: Array<NavigationRoute>,
};
// ...
Flow具备有像TypeScript语言中,预先定义对象类型的作用。上面代码的都是这个组件中预先定义的类型,这些类型可以再套用到不同的代码文档之中。
export type NavigationGestureDirection = 'horizontal' | 'vertical';
上面这行类似于列举(enum)的类型,意思是说要不就是'horizontal'(水平的),要不然就'vertical'(垂直的),就这两种字符串值可使用。
export type NavigationRoute = {
key: string,
title?: string
};
这行里面用了一个问号(?)定义在title
属性的后面,这代表这属性是可选的(Optional),不过你可能会有点搞混,因为问号(?)可以放在两个位置,见下面的例子:
export type Test = {
titleOne?: string,
titleTwo: ?string
}
titleOne
代表的是属性为可自定义的(可有可无),但一定是字符串类型。titleTwo
代表的是类型可自定义,也就是值的部份除了定义的类型,也可以是null或undefined,不过这属性是需要的,而且你一定要给它一个值。好的,这有些太细部了,如果有用到再查手册文档就可以。
export type NavigationState = {
index: number,
routes: Array<NavigationRoute>,
};
上面的代码可以看到,只要是声明过的类型(type),同样可以拿来拿在其他类型中套用,像这里的Array<NavigationRoute>
,就是使用了上面已声明的NavigationRoute
类型。它是一个数组,里面放的成员是NavigationRoute
类型,是个对象的结构。
刚已经有说过Flow工具有很大的原因是为了React与React Native所设计,因为Flow本身就内建对PropTypes的检查功能,也可以正确检查JSX语法,在这篇官方文档中有说明,而这在之后介绍React的文档的例子中就可以看到。
Flow目前可以支持macOS、Linux(64位元)、Windows(64位元),你可以从以下的四种安装方式选择其中一种:
直接从Flow的发布页面下载可运行档案,加到计算机中的PATH(路径),让flow
指令可以在命令列窗口访问即可。
透过npm安装即可,可以安装在全局(global)或是各别项目中。下面为安装在项目中的指令:
npm install --save-dev flow-bin
macOS中可以使用homebrew安装:
brew update
brew install flow
透过OCaml OPAM套装管理程序打包与安装,请见Flow的Github页面。
在你的项目根目录的用命令列工具输入下面的指令,这将会创建一个.flowconfig
文档,如果这文档已经存在就不需要再进行初始化,这个设置档一样是可以加入自定义的设置值,请参考Advanced Configuration这里的说明,目前有很多项目里面都已经内附这个设置档,例如一些React的项目:
flow init
一般都在代码档案的最上面一行加入,没加Flow工具是不会进行检查的,有两种格式都可以:
// @flow
或
/* @flow */
目前支持Flow工具插件的代码编辑工具很多,常见的Atom, Visual Studio Code(VSC), Sublime与WebStorm都有,当有安装搭配代码编辑工具的插件时,编辑工具会辅助显示检查的讯息。不过有时候会有点卡顿的要等一下,因为检查速度还不是那么快。
或是直接用下面的命令列指令来进行检查:
flow check
在Visual Studio Code中因为它内建TypeScript与JavaScript的检查功能,如果要使用Flow工具来作类型检查,需要在用户设置中,加上下面这行设置值以免冲突:
"javascript.validate.enable": false
注: 有些脚手架就已经装好与设置好这个babel拓展插件,你不用再多安装了。
在开发的最后阶段要将原本有使用Flow标记,或是有类型注释的代码,进行清除或转换。转换的工作要使用babel编译器,这也是目前较推荐的方式。
使用babel编译器如果以命令列工具为主,可以使用下面的指令来安装在全局中:
npm install -g babel-cli
再来加装额外移除Flow标记的npm套件babel-plugin-transform-flow-strip-types在你的项目中:
npm install --save-dev babel-plugin-transform-flow-strip-types
然后创建一个.babelrc
设置档案,档案内容如下:
{
"plugins": [
"transform-flow-strip-types"
]
}
完成设置后,之后babel在编译时就会一并转换Flow标记。
下面的指令则是直接把src
目录的档案编译到dist
目录中:
babel src -d dist
当然,babel的使用方式不是只有上面说的这种命令列指令,你可以视项目的使用情况来进行设置。
Flow用起来是的确是简单,但里面的内容很多,主要原因是是要看实际不同的使用情况作搭配。JavaScript里面的原始数据类型都有支持,而在函数、对象与一些新的ES6中的类,在搭配使用时就会比较复杂,详细的情况就请到官网文档中观看,以下只能提供一些简单的介绍说明。
Flow支持原始数据类型,如下面的列表:
boolean
number
string
null
void
其中的void
类型,它就是JS中的undefined
类型。
这里可能要注意的是,在JS中undefined
与null
的值会相等但类型不同,意思是作值相等比较时,像(undefined == null)
时会为true
,有时候在一些运行期间的检查时,可能会用值相等比较而不是严格的相等比较,来检查这两个类型的值。
所有的类型都可以使用垂直线符号(|)作为联合使用(也就是 OR 的意思),例如string | number
指的是两种类型其中一种都可使用,这是一种联合的类型,称为"联合(Union)类型"。
最特别的是可选的(Optional)类型的设计,可选类型代表这个变量或常量的值有可能不存在,也就是允许它除了是某个类型的值外,也可以是null
或undefined
值。要使用可选类型,就是在类型名称定义前加上问号(?),例如?string
这样,下面是一个简单的例子:
let bar: ?string = null
字面文字类型指的是以真实值作为数据类型,可用的值有三种,即数字、字符串或布尔值。字面文字类型搭配联合的类型可以作为列举(enums)来使用,例如以下的一个扑克牌的类型例子:
type Suit =
| "Diamonds"
| "Clubs"
| "Hearts"
| "Spades";
type Rank =
| 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
| "Jack"
| "Queen"
| "King"
| "Ace";
type Card = {
suit: Suit,
rank: Rank,
}
注: type是Flow中定义类型别名(Type Alias)的关键字,是一种预先声明的类型,这些声明的标记一样只会在开发阶段中使用,最后编译去除。
类型别名(Type Alias)提供了可以预先定义与集中代码中所需要的类型,一个简单的例子如下:
type T = Array<string>
var x: T = []
x["Hi"] = 2 //有Flow警告
类型别名(Type Alias)也可以用于复杂的应用情况,详见Flow官网提供的Type Aliases内容。
在某一些情况可能不需要定义的太过于严格,或是还在开发中正在调试时,有一种作为渐进的改善代码的类型。
Flow提供了两种特殊的类型可以作为松散的数据类型定义:
any: 相当于不检查。既是所有类型的超集(supertype),也是所有类型的子集(subtype)
mixed: 类似于any是所有类型的超集(supertype),但不同于any的是,它不是所有类型的子集(subtype)
mixed
是一个特别的类型,中文是混合
的意思,mixed
算是any
的"啰嗦"进化类型。mixed
用在函数的输入(传参)与输出(返回)时,会有不一样的状态,例如以下的例子会出现警告:
function foo(x: mixed): string {
return x + '10'
}
foo('Hello!')
foo(1)
会出现警告消息如下:
[flow] mixed (Cannot be added to string)
这原因是虽然输入时可以用mixed
,但Flow会认为函数中x
的值不见得可以与string
类型作相加,所以会请求你要在函数中的代码,要加入检查对传入类型在运行期间的类型检查代码,例如像下面修改过才能过关:
function foo(x: mixed): string {
if (typeof x === 'number' || typeof x === 'string') {
return x + '10'
}
throw new Error('Invalid x type')
}
foo('Hello!')
foo(1)
mixed
虽然"啰嗦",但它是用来渐进替换any
使用的,有时候往往开发者健忘或偷懒没作传入值在运行期间的类型检查,结果后面要花更多的时间才能找出错误点,这个类型的设计大概是为了提早预防这样的情况。
注: 从上面的例子可以看到Flow除了对类型会作检查外,它也会请求对某些类型需要有动态的检查。在官方的文件可以参考Dynamic Type Tests这个章节。
数组类型使用的是Array<T>
,例如Array<number>
,会限定数组中的值只能使用数字的数据类型。当然你也可以加入埀直线(|)来定义允许多种类型,例如Array<number|string>
。
对象类型会比较麻烦,主要原因是在JavaScript中所有的数据类型大概都可以算是对象,就算是基础数据类型也有对应的包装对象,再加上有个异常的null
类型的typeof
返回值也是对象。
对象类型在Flow中的使用,基本上要分作两大部份来说明。
第一种是单指Object
这个类型,Flow会判断所有的基础数据类不是属于这个类型的,以下的例子全部都会有警告:
// 以下都有Flow警告
(0: Object);
("": Object);
(true: Object);
(null: Object);
(undefined: Object);
其他的复合式数据类型,除了数组之外,都会认为是对象类型。如下面的例子:
({foo: "foo"}: Object);
(function() {}: Object);
(class {}: Object);
([]: Object); // Flow不认为数组是属于对象
注意: 上面有两个特例,
typeof null
与typeof []
都是返回'object'。也就是说在JS的标准定义中,null
与数组
用typeof检测都会返回对象类型。所以,Flow工具的检查会与JS预设并不相同,这一点要注意。注:
typeof
在Flow中有一些另外的用途,详见Typeof的说明。
第二种方式是要定义出完整的对象的字面文字结构,像{ x1: T1; x2: T2; x3: T3;}
的语法,用这个结构来检查,以下为例子:
let object: {foo: string, bar: number} = {foo: "foo", bar: 0};
object.foo = 111; //Flow警告
object.bar = '111'; //Flow警告
上面已经有看到,函数也属于对象(Object)类型,当然也有自己的Function
类型,函数的类型也可以从两大部份来看。
第一是单指Function
这个类型,可以用来定义变量或常量的类型。如下面的代码例子:
var anyFunction: Function = () => {};
第二指的是函数中的用法,上面已经有看到函数的输出(返回值)与输入(传参)的用法例子。例如以下的例子:
function foo(x: number): number {
return x + 10;
}
因为函数有很多种不同的使用情况,实际上可能会复杂很多,Flow工具可以支持目前最新的arrow functions、async functions与generator functions,详见官方的这篇Functions的说明。
类是ES6(ES2015)中新式的特性,类目前仍然只是原型的语法糖,类本身也属于一种对象(Object)类型。类的使用情况也可能会复杂,尤其是涉及多型与实例的情况,详见Flow网站提供的Classes内容。
Flow在最近的博客中说明引入了flow-typed的函数库定义档("libdefs"),在这个Github存储库中将统一存放所有来自社群提供的函数库定义档案。这是一种可以让现有的函数库与框架,预先写出里面使用的类型定义。让项目里面有使用Flow工具与这些函数库,就可以直接使用这些定义档,以此结合现有的函数库与框架来使用。这个作法是参考TypeScript的DefinitelyTyped方式。因为这还是很新的消息(2016.10),目前加入的函数库还没有太多,不过React周边的一些函数库或组件都已经开始加入,其他常用的像underscore、backbone或lodash也已经有人在提交或维护。
Flow另一个发展会是在开发工具的自动完成功能的改进,因为如果已经能在撰写代码时,就知道变量或常量的类型(静态类型),那么在自动完成功能中就可以更准确地给出可用的属性或方法。这一个功能在Facebook自家的Nuclide开发工具的Flow说明页中就有看到。Nuclide是基于Atom开发工具之上的工具,计算机硬件如果不够力是跑不动的,而且它稳定性与运行速度都还需要再努力。这大概是未来可见到的一些新趋向。
本文简单的说明了Flow工具的功能介绍,以及其中的一些简要的内容等等。相信看过后你已经对这个Flow工具有一些认识,以我个人学过TypeScript的经验,相较于TypeScript的学习曲线,Flow大概是等于不用学。Flow虽然是一个很新的工具,但相当的有用,建议每个JavaScript开发者都可以试试,一开始不用学太多,大概这篇文档看完就可以开始用了。复杂的地方就再查找官方的文件即可。
对于每个正在使用JS开发稍具规模化的应用,或是开发开源码的函数库或框架的团队来说,让JS具有静态类型特性,是一个很重要而且必要的决定。以我的观察,在网络上一直有很多的超集语言(例如TypeScript)的爱好者,会提出要全面改用TypeScript(或其他超集语言)的声音,例如Vue.js在很早之前就有讨论是不是要全面采用TypeScript的声音。后来Vue.js只有提交TypeScript的DefinitelyTyped文档,但在2.0中则采行了Flow工具。在这篇Vue作者于知乎上发表的: Vue 2.0 为什么选用 Flow 进行静态代码检查而不是直接使用 TypeScript?的内容中,你可以看到为何选择Flow的理由,这可能也是整个开发团队所认同的最后结果。作者回答的文中可以总结下面这句话:
全部换 TS(TypeScript) 成本过高,短期内并不现实。 相比之下 Flow 对于已有的 ES2015 代码的迁入/迁出成本都非常低 … 万一哪天不想用 Flow 了,转一下,就得到符合规范的 ES。
总之,Flow提供了另一个选择,要用什么工具就看聪明的你如何选择了。
查看原文本章的目标是提供一些Flow工具的介绍与使用建议。Flow本质上也只是个检查工具,它并不会自动修正代码中的错误,也不会强制说你没按照它的警告消息修正,就不会让你运行程序。当然,并没有要求什么时候一定要用这类的工具,只是这种作法可以让你的代码更具强健性与提...
木纸鸢 关注了标签 · 2018-04-08
Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。
七牛云存储CEO许式伟出版《Go语言编程》
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...
Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。 Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复...
关注 26113
木纸鸢 提出了问题 · 2018-03-24
go发http请求调其它服务,新手求教
go发http请求调其它服务,新手求教
关注 3 回答 2
木纸鸢 回答了问题 · 2018-02-11
h5展示页引入编辑后富文本所需的css class样式, 编辑后的富文本html标签里有class不用style,
h5展示页引入编辑后富文本所需的css class样式, 编辑后的富文本html标签里有class不用style,
关注 4 回答 4
查看全部 个人动态 →
(゚∀゚ )
暂时没有
(゚∀゚ )
暂时没有
注册于 2017-11-15
个人主页被 267 人浏览
推荐关注