HYPERS 前端团队博客


  • 首页

  • 分类

  • 归档

  • 标签
loop

🎉 React Suite 4.0 版本发布

发表于 2019-09-09 | 分类于 前端
| 字数统计 2,890

开学了,又是一个新的起点。伴着丝丝凉爽的秋风,React Suite 迎来了 4.0 版本的发布。从 2019 年 3 月份开始设计 V4 版本,经历了 6 个多月的开发与测试,讨论与争执,终于完成了所有的计划。

春种一粒粟,秋收万颗子,在这个收获的季节,我们为大家准备了一系列的更新,准备好了吗? 嘟嘟嘟嘟…

一、从 Flow 迁移到 TypeScript

首先,感谢 Flow 任教了整个 V3 版本,让组件库能够方便的拥有静态类型检查。随着我们在 TypeScript 的应用上更加广泛,以及 Flow 陆续暴露的一些问题后,在本次版本中 Flow 离开了我们的课堂,而采用 TypeScript 重构了所有代码。每一次破碎的重构都是一次重生,就是要让代码更具可读性和可维护性。

二、可访问性改进

为支持新的浏览器特性,我们在上一个版本 V3 就放弃了 IE9。但我们任然希望用 React Suite 开发的 Web 应用尽可能有更多的人使用,更好的使用。我们在可访问性上尽量去覆盖更多的人群。

2.1 颜色对比度改进

在全世界存在很多弱视人群,而这部分用户使用的显示器往往又是参差不齐,文字与背景的对比度就成了用户最基础的功能问题。作为一个贴心的 UI 组件库,怎么能不照顾用户的眼睛呢?

按照 《Web Content Accessibility Guidelines (WCAG) 》的要求,文字的颜色,字体的粗细,我们在对比度上都做了改进,调整了色板的算法,目的就是让您的产品更具有可访问性。

2.2 新增 Dark Mode(夜间模式)主题

在我们周围的电子产品中,从操作系统到浏览器、编辑器、以及各种阅读器都开始支持夜间模式。它是一种高对比度,或者反色模式的显示模式,如果您的用户需要长时间使用您的产品,拥有夜间模式能够有效的缓解眼疲劳,更易于阅读。

我们在您的夜里制作了一个美梦,在默认的主题基础上新增了 Dark Mode 主题,并提供了充分可以定制的选项,开发时候,只需要引入对应的样式文件:

1
import 'rsuite/lib/styles/themes/dark/index.less';

更多关于主题相关的设置可以参考:自定义主题

三、新增组件

组件是 React Suite 提供的最小单元。随着 Web 应用原来越丰富,越来越多样性,我们会陆续提供更丰富的组件。

3.1 支持 List 组件

List 组件在移动端使用的非常多,但在中后台产品中,一直是一个不太好标准化的组件,在不同的业务场景下要求的表现形式都会存在差异,以至于我们在这个版本才实现它。List 除了可以自定义每一项的内容以外, 我们默认提供了拖拽排序功能。

3.2 支持 Placeholder 组件

在前端业内大家都知道“骨架屏”这个词,它的作用其实和 Loader 类似,都是在应用未加载完成前,显示给用户的一个状态,告诉用户当前数据正在加载中。而 “骨架屏” 的优点,是在数据尚未加载前提前先给用户看到页面的大致结构,提升感官上的体验。

Placeholder 就是这么一个提供数据大致结构的组件。可以通过线条、矩形、圆形概要的绘制出内容区域的大致结构。

3.3 支持 Calendar 组件

Calendar 是一个简单的日历面板,可以针对日程显示数据。提供了两种使用场景,一种就是默认的显示一个撑满容器的大日历面板,可以显示一个月内的数据。另外一种是提供了一个小的紧凑的小日历面板,在一些系统的侧边栏我们经常遇到,用于数据的筛选。

3.4 支持 Avatar 与 Badge 组件

支持 Avatar 组件,用于展示一个头像或者商标。

支持 Badge 组件,用于按钮、图标旁的数字或状态标记。



四、破坏性的变更

每一次的更新我们都希望对历史版本做最大程度的兼容,但还是会存在一些破坏性的变更,比如使用了 React 新的特性,或者对之前不合理设计的改进。

4.1 不支持小于 React 16.6 版本

在本次版本中使用了 React 新的一些特性。例如:新的 context API,在 React 16.6.0 版本中开始支持 Class 组件静态 contextType 属性 #13728,我们使用了这一特性。 所以要使用 React Suite 4.0 版本必须升级 react 及 react-dom 至 >=16.6 的版本。

4.2 Less 兼容性变更

在本次版本中开始支持 Dark 主题,对 Less 文件的引入地址进行了调整。

3.* 版本

1
import 'rsuite/styles/less/index.less';

4.* 版本

1
2
3
4
import 'rsuite/lib/styles/themes/default/index.less'

// 或者
// import 'rsuite/lib/styles/index.less';

同时 Less 的版本需要升级至 >=3.0 版本。

4.3 TreePicker 与 CheckTreePicker 废弃 expandAll 属性

TreePicker 组件与 CheckTreePicker 组件废弃了 expandAll 属性,同时添加了 expandItemValues 属性,用于展开指定节点。

4.4 调整了 Dropdown、Whisper 以及所有 Picker 组件 placement 属性的值

placement 属性是配置选择器在打开后显示的位置,为了让参数值得可读性更好,对值做了如下调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Placement4 = 'top' | 'bottom' | 'right' | 'left';
type Placement8 =
| 'bottomStart'
| 'bottomEnd'
| 'topStart'
| 'topEnd'
| 'leftStart'
| 'rightStart'
| 'leftEnd'
| 'rightEnd';
type PlacementAuto =
| 'auto'
| 'autoVerticalStart'
| 'autoVerticalEnd'
| 'autoHorizontalStart'
| 'autoHorizontalEnd';

兼容 3.* 版本

五、修复及改进

5.1 所有的 Picker 组件支持设置大小

我们在数据录入组件中有非常完善的 Picker 系列组件,除在可以在表单中使用以外,在一些数据过滤栏中也经常用到。考虑到 Input 和 Button 组件有 size 属性可以调整大小,所以对所有 Picker 也添加了 size 属性,满足更多场景的组合使用。

5.2 Whisper、Picker 组件的防溢出处理

所有的 Picker 组件与 Whisper 组件都是弹出一个指定位置的浮层,但有时候会因为浮层的大小超出容器的范围导致一部分浮层别挡住看不到,这个时候您可以设置一个 preventOverfow 属性,会根据容器的空闲空间调整浮层显示的相对位置,尽量让浮层完全展示在页面中。

5.3 FormControl 组件设置表单只读

FormControl 新增两个属性支持:

  • readOnly 让表单组件为只读状态,不可编辑。
  • plaintext 让表单组件以纯文本方式展示。

当这两个属性设置 Form 组件上,将对表单内所有的表单组件进行全局设置。在很多情况下我们需要给录入完成的表单添加一个数据详细页面,这个时候需要单独新增一个模块再把数据排版显示出来。为了提高代码的复用性,在这里您可以通过给 Form 组件设置一个 plaintext 属性,就把一个表单变成了数据详细面板。

5.4 DatePicker 与 DateRangePicker 支持显示周数
1
2
<DatePicker showWeekNumbers />
<DateRangePicker showWeekNumbers />

如果在您的业务中需要查看周数,可以通过设置 showWeekNumbers 属性,日历选择器的左侧就会显示当前行的周数。

5.5 Form 组合 Schema 支持异步校验

异步校验是一个基础的需求,在本次版本中 Schema 开始支持 Promise。以下是表单改进的几个内容:

  • 在需要异步校验的 <FormControl> 上设置 checkAsync 属性。
  • 异步校验的验证规则通过 schema 的 addRule 方法添加一个返回值为 Promise 的对象。
  • 通过调用 <Form> 的 checkAsync 与 checkForFieldAsync 的访问,可以手动触发校验。

Model

示例中我们需要异步验证一个邮箱地址是否在服务端已经存在,在给 Modal 添加规则的时候,通过 addRule 方法返回一个 Promise 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
function asyncCheckEmail(email) {
return new Promise(resolve => {
// 异步处理逻辑 resolve(true);
});
}

const model = SchemaModel({
email: StringType()
.isEmail('Please input the correct email address')
.addRule((value, data) => {
return asyncCheckEmail(value);
}, 'Email address already exists')
});

Form
把声明好的 model 设置到对应的 Form 上,同时需要给需要异步校验的组件设置一个 checkAsync 属性。

1
2
3
4
5
6
7
8
9
10

const formRef = React.createRef();

function render(){
return (
<Form model={model} ref={formRef}>
<FormControl checkAsync name="email"/>
</Form>
)
}

Form 默认提供了 check() 方法,如果是异步检查需要调用 checkAsync() 方法。

1
2
3
formRef.current.checkAsync().then(result => {
console.log(result);
});
5.6 Alert 与 Notification 支持手动关闭

Alert 与 Notification 都支持 close 与 closeAll 方法,分别关闭最后一个消息和关闭所有消息。在某些业务情况下,需要在进行某个操作后关闭掉页面上的警告信息,可以通过以下方法操作:

1
2
3
4
5
Alert.close();
Alert.closeAll();

Notification.close();
Notification.closeAll();
5.7 FlexboxGrid 支持响应式

Grid 布局中的 Col 组件是可以针对响应式布局进行配置,但是它不具备 Flex 布局得一些特性,为了能够让两种布局融合,我们可以让 FlexboxGrid.Item 可以和 Col 组合使用,组合后 FlexboxGrid 及拥有了 Flex 布局得特性,同时拥有响应式配置相关的属性。

1
2
3
<FlexboxGrid.Item componentClass={Col} md={6}>
content
</FlexboxGrid.Item>
5.8 所有的 Picker 新增打开和关闭方法

在某些情况下,需要通过执行某个操作后去打开或者关闭一个 Picker。例如:一个级联操作,在关闭一个 Picker 后希望快速选择,默认打个下一个 Picker。我们在 Picker 上提供了一个 open 与 close 方法:

1
2
3
4
5
6
7
8
9
10
11
const pickerRef = React.createRef();

function render() {
return <SelectPicker ref={pickerRef} />;
}

// 打开
pickerRef.current.open();

// 关闭
pickerRef.current.close();
5.9 其他修复
  • 修复了 Uploader 上传文件大于 1GB 显示问题。
  • 修复了 Input 在 IE 浏览器显示上的兼容性问题。
  • 修复了 InputPicker 在键盘 Delete 键会清除输入值得问题。
  • 修复了 Dropdown 设置 toggleComponentClass={Button} 背景样式错误的问题。
  • 修复了按需引入时候样式缺失的问题。
  • 修复了 DatePicker 禁用日与禁用月不一致的问题。
  • 修复了 Table 数据更新后滚动条位置不更新的问题。
  • 修复了 Table 属性 expandedRowKeys 更新值不受控。
  • 修复了 Table 属性 onRowClick 的回调参数缺少 event。
  • 修复了 Form 组件对 focus 事件的支持。
  • 修改了 Breadcrumb 的默认分隔符.
  • 修复了 Slider 在从隐藏到显示状态变化后,手柄的位置不更新的问题。

六、最后

希望我们的成长,能给更多的开发者带来更好的体验。如果您喜欢 React Suite,可以通过以下方式支持我们:

  • Star 这个项目。
  • 如果您在您的项目中使用了 React Suite,欢迎在这里留言!
  • 在 OpenCollective 上赞助我们。

这个项目的存在归功于所有贡献者。

国内交流群, 添加 React Suite 小助手,备注 rsuite, 邀请入群。

Dart 简单入门

发表于 2019-02-02
| 字数统计 4,467
  • 推荐图书:Dart编程语言 - 【美】Gilad Bracha
  • 推荐阅读官方文档:A Tour of the Dart Language
  • 建议切一个窗口打开DartPad边看边码,更能加深印象!

写在前面

Dart 其实并不算一门非常新的语言,最早版本发行与 2011 年 10 月,目标是取代 JavaScript 成为下一代 Web 开发语言(很显然他失败了)。截止发稿已经发布了 2.x 版本,与初始版本并无太多改动,保持了兼容性。他没有像同胞哥哥 Golang 在后端占有一席之地一样在 Web 端有所作为,而是不温不火好几年,直到 Flutter 的出现才有所改善。
语言本身来说,他是一门强类型且面向类编程的语言,前端朋友如果长期以 JavaScript 为主力语言的话可能会有些许不适应,但是相信在短暂的学习之后就会上手,并喊出“真香”。
笔者是实实在在的新手,本文更多也是作为读书笔记,如果有什么不足或纰漏,还望各位不吝赐教,定虚心接受。

语言设计理念

万物皆对象

不同于 JavaScript 和 Java 有对象和基本值,在 Dart 的世界里任何事物都是对象,无一例外。这一点可以让我们更好地去操作我们的代码而不用关心拆装箱等问题,在一定程度上简化了构建逻辑,简化了实现任务。

面向接口编程,而非面向实现

关注对象的行为而非内部实现时 OOP 的核心原则,在 Dart 中这一原则被更好地强调和实现,具体方式有以下几条:

  • 在 Dart 中并不存在声明的接口,因为所有的类都可以被当做接口,不管其他类是否采用了同样的底层实现(部分 core type 除外)
  • Dart 中没有 final 方法,允许几乎重写所有方法(部分内置操作符除外)。
  • Dart 把对象进行抽象封装,所有的操作都是通过存取来改变对象状态的,即在其他语言中习以为常的=赋值和.取值在 Dart 中也只是get()和set()的语法糖。

类型时为开发服务

这点我感觉与 TypeScript 的理念(或是说实现效果)比较相近,毕竟大家觉得强类型语言写起来舒服的原因就是清晰的接口和便捷的 IDE 提示,往编译层面来说就是为编译器提供更好的预判,从而优化性能。 Dart 在类型方面采用了可选类型(Sound type) ,算是在强弱类型之间找到了一个平衡:

  • 类型在语法层面时可选的
  • 类型对运行时的语义没有影响。

这意味着你也可以把 Dart 当做一门动态类型语言来编写,虽然不推荐,但是还是在一定程度上给了开发者很高的自由度,让其他语言的开发者更快迁移到 Dart 上来。

字面量

当然别忘了,他们也都还是对象

  • 整数 int: 1 2 3
  • 浮点数 double:(遵循 IEEE754 标准的 64-bit 浮点数)1.0

    在 Dart 2.1 之后 double value = 1; 将自动转为(等价于) double value = 1.0;

  • 数字 num: 作为int和double的父级
  • 布尔 bool: true
  • 字符串 String: (转义字符用法与 JavaScript 相似)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    void main(){
    var man = 'Mick';
    var s1 = 'It\'s hot today!';
    var s2 = "It's hot today!";
    var s3 = "'It\'s hot today!'$man said.";
    var s4 = '''
    It!
    is!
    hot!
    today!
    ''';

    //You can create a “raw” string by prefixing it with r:
    var s = r'In a raw string, not even \n gets special treatment.';
    }
  • 列表 List: [1,2,3]

  • 映射 Map: {'1':1,'2':2}

    注意:虽然和JavaScript中的对象很像但是不能通过.something的方式代替['sonething']

  • 函数 Function: ()=>{}
  • Symbol: (使用 # 开头,后面跟着一个或多个.分割的标识符或运算符,基本用不到) #MyClass #com.evil_empire.forTheWin

基本语法

和其他 C-style 语法相似,你之前学到的知识大概率是能用得上的。如果你写过 Java / Kotlin 的话应该会感觉更加亲切,因为真的很像。

变量声明

变量声明方面其实没有声明特别的地方,你可以凭自己的喜好给变量增加类型:

1
2
3
4
5
var a='1';//不带类型的变量声明
String b ='1';//带类型的变量声明
const c='1';//常量声明
final d='1';//在调用后被初始化
var e;//未初始化的变量将被初始化为null

来写一个类吧

  • 你可以像这样写一个类

    注意: Dart 是不支持函数重载的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Point{
    var x, y;
    Point(a,b){
    x = a;
    y = b;
    }
    scale(factor){
    return new Point(x * factor, y * factor);//在 Dart2 中你可以无条件省略 new 关键词
    }
    }
  • 你还可以像 Java / C++ 一样写个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Point{
    int x, y;
    Point(a,b){
    this.x = a;
    this.y = b;
    }
    scale(factor){
    return new Point(x * factor, y * factor);//在 Dart2 中你可以无条件省略 new 关键词
    }
    }
  • 当然, Dart 也有自己的语法糖

    1
    2
    3
    4
    5
    6
    class Point{
    var x, y;
    Point(this.x,this.y);//第一个参数将会自动赋给 x ,第二个参数会自动赋给y
    scale(factor) => new Point(x * factor, y * factor);//在 Dart2 中你可以无条件省略 new 关键词
    operator +(p) => new Poinit(x + p.x,y + p.y);//就像在 c++ 里一样,你又可以重写运算符了!
    }
  • 在 Dart 中还支持了 staic / final / const 关键词,还有 const构造方法

    还有抽象关键词abstract,与 Java 用法相似,在此就不做举例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Foo{
    static var onlyOne = 1;
    static const neverChange = 0;
    final lazyLoad;//推荐大部分变量都设置为 final ,可以在声明时初始化,也可以在构造函数初始化(必须二选一)。
    get onlyOne(){}
    set onlyOne(){}
    Foo(lazyOne):lazyLoad=lazyOne,super(key:lazyOne);// final 值也可以通过构造函数后冒号初始化,调用父类构造函数 super。
    const Foo()//所有 const 关键词赋值中调用的只能是常量或字面量,因为所有被 const 修饰的方法/变量将会在编译阶段被求值。
    }
  • Dart 的类支持了 extends / with / implements,按需取用。

    • 和其他现代语言一样, Dart 支持单继承多接口,在此基础上还增加了饱受争议的 mixin (一对多)。
    • mixin 关键词也可以替代 class 作为声明关键词来使用,以专门编写用于 mixin 的类,如果此 mixin 要求对象实现接口则使用 on 关键词在最后修饰。
    • 可参考Flutter Dart语法(1):extends 、 implements 、 with的用法与区别
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    class Bar {
    var a = 1;
    m(p, q) {}
    }

    mixin Bar2 on Bar {
    /*
    * on修饰符要求mixin对象的父类实现了Bar接口
    * */
    @override
    void m(q, p) {
    super.m(q, p);
    }
    //...
    }

    class Foo1 extends Bar {
    /*
    * 1. Dart中的继承是单继承
    * 2. 构造函数不能继承
    * 3. 子类重写超类的方法,要用@override
    * 4. 子类调用超类的方法,要用super
    * 注意:不可以重写父类中声明变量的set和get方法(隐式),同时重写的方法要与父类中方法参数个数相同。
    * */
    //...other codes
    }

    class Foo2 implements Bar {
    /*
    * 1. class 就是 interface
    * 2. 当class被当做interface用时,class中的方法就是接口的方法,需要在子类里重新实现,在子类实现的时候要加@override
    * 3. 当class被当做interface用时,class中的成员变量也需要在子类里重新实现。在成员变量前加@override
    * 4. 实现接口可以有多个
    * */
    @override
    var a = 1;
    @override
    m(p, q) {}
    //...other codes
    }

    class Foo3 with Bar {
    /*
    * 1. mixins类只能继承自object
    * 2. mixins类不能有构造函数
    * 3. 一个类可以mixins多个mixins类
    * 4. 可以mixins多个类,不破坏Dart的单继承
    * 注意:mixin有点像extends,但是我理解上更像是原型链的模型,with后即链上对象,采取的是覆盖原则,即靠后的生效。(甚至可以粗暴地理解为复制代码)
    * */
    //...other codes
    }

    class Foo4 extends Bar with Bar2 {
    //通过继承的方式实现。
    //...other codes
    }

    class Foo5 extends Foo2 with Bar2 {
    //通过继承的方式实现。
    //...other codes
    }

函数

  • 基本的函数可以参考下面,包含了三目运算符和错误抛出。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    maxElement(a){
    //你可以在maxElement前面加上返回类型 如 int maxElement(a)
    //你也可以在a前面标注上类型来声明a的类型 如 maxElement(List<int> a)
    var currentMax = a.isEmpty
    ? throw 'Maximal element undefined for empty array' : a[0];
    for(var i = 0; i < a.length; i++){
    currentMax = max(a[i],currentMax);
    }
    return currentMax;
    }
  • Dart还支持了可选参数/具名参数/参数默认值,当然你也可以使用@required来标注具名参数中必须传入的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void foo1(a, [ b = 1] ){//a为参数,b为可选参数并设置了默认值1
    print('${a},${b}');//当仅有变量时也可以简写为print('$a,$b')
    }
    void foo2(a,{ @required b, c = 0 }){//a为参数,b/c为具名参数,b为必传,c设置了默认值0
    print('${a},${b},${c}');
    }
    void main() {
    foo1(1, 2);//1,2
    foo2(1, b:2);//1,2,0
    }
  • 支持函数作为参数传递,当然也就支持箭头函数,不同于 JavaScript 中的箭头函数和普通函数有 this 的区别, Dart 中的箭头函数就是实实在在的“糖”

    1
    void main()=>{};
  • Dart同样支持了闭包,但是和JavaScript的又不大一样,这里有个不得不吐的槽:

    1
    2
    3
    4
    5
    for (var i = 0; i < 5; i++) {
    setTimeout(function () {
    console.log(i);//他会很不合预期地打出五个5
    }, 1000 * i);
    }

    这段代码应该是前端面试的常客了,但是与它类似的代码在 Dart 中就显得很“正常”

    1
    2
    3
    4
    5
    6
    7
    8
    import 'dart:async';
    void main(){
    for (var i = 0; i < 5; i++) {
    Timer(Duration(seconds: i), () {
    print(i);//0,1,2,3,4
    });
    }
    }

    因为在 Dart 中,每次执行 for 都会给代码新建一个 context ,所以在每次执行的 Timer 都是一个全新的 i

  • 在函数的调用上页支持..来级联操作,它不像.会返回调用后的结果,而是返回被调用者本身,这样的设计给链式调用多了一种选择。

    1
    2
    3
    4
    5
    6
    7
    void main(){
    /*1*/"Hello".length.toString();//5
    /*2*/"Hello"..length.toString();//Hello
    /*第2行等价于*/
    "Hello".length;
    "Hello".toString();
    }
  • 如果函数调用()也是”糖”?没错。他就是.call()方法的语法糖。所以你可以给对象加上call方法以使用()来调用方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class fakeFunc {
    call() {
    print('works!');
    }
    }

    void main() {
    var a = fakeFunc();
    a();//works!
    }
  • Dart同样支持迭代器语法,支持同步和异步

    1
    2
    3
    4
    5
    6
    7
    8
    naturalsTo(n) sync* {//同步迭代器
    var k=0;
    while(k<n) yield k++;
    }
    naturalsTo(n) async* {//异步迭代器
    var k=0;
    while(k<n) yield await k++;
    }
  • 还有一个不得不提的方法 noSuchMethod ,如果运行时对象没有对应的方法,将会调用这个方法,这使得我们可以由此做一些”Hack“(关于反射和动态代码)
    对于 noSuchMethod() 的参数只有一个 Invocation 。关于 Invocation 的布尔属性所辨认的方法调用的句法形式,如下表所示:

x.yx.y = ex.y(…)
isMethodfalsefalsetrue
isGettertruefalsefalse
isSetterfalsetruefalse
isAccessortruetruefalse
1
2
3
4
5
6
7
8
9
class A{
@override
noSuchMethod(Invocation inv)=>print('fallback!');
}
void main(){
var a = A();
//注:更多时候你将会被 IDE 拦下来
a.x();//'fallback!'
}

其他语句

  • 支持 c-style 的 if(){}else if(){}else{}
  • 支持以{}分界的块状作用域,不允许块之间互相递归声明。
  • 支持 c-style 的 for / while / do-while 循环以及相对应的 continue / break(还有对应的 label 也是支持的),还支持了 for-in 语法来快捷遍历集合类
  • 支持 switch-case 多条件选择

    注意:case 的值仅能为常量,且不允许falls-through,必须使用 break 跳出。

  • 支持 assert 语法,优化开发体验。
    1
    2
    3
    4
    5
    void main(){
    int x = -1;
    assert(x>0);//在开发模式下报错
    x++;
    }

异常处理

Dart 支持最舒适的 throw + try{}catch(){}finally{}语法,不多说!

库/模块

  • Dart 的模块化比较像 C / C++ / Java ,他是将 import 的文件直接注入到当前文件全局作用域下的。
  • Dart 引入关键词为 import ,支持使用 as 来重命名,还有 deferred as 来延迟加载,也支持使用 show / hide 组合器来限定引入范围。

    1
    2
    3
    4
    5
    6
    7
    8
    import 'stack.dart' show pop,push;
    import 'advancedStack.dart' as stack2 hide pop,push;
    import 'mayUseless.dart' deferred as rarelyUsed;//只有在使用时才会被加载

    void main(){
    onLoad(loadSuccess)=>loadSuceess?doStuff():makeExcusses();
    rarelyUsed.loadLibrary().then(onLoad);
    }
  • export 用于中转某个库需要在当前文件暴露的方法,但并不是和 import 配套使用的。同样支持使用 show / hide 组合器来限定引入范围。

    类似于 JavaScript(ES6) 里的 export from 语法

    1
    export 'stack.dart' hide pop;
  • 还有一个对我而言比较陌生的关键词: part ,有的时候一个库可能太大,不能方便的保存在一个文件当中。Dart 允许我们把一个库拆分成一个或者多个较小的 part 组件。或者我们想让某一些库共享它们的私有对象的时候,我们需要使用 part。

    1
    2
    3
    // A.dart
    import 'stack.dart' show pop;
    part 'A_1.dart';
    1
    2
    3
    4
    5
    //A_1.dart
    part of 'A.dart';
    void main(){
    pop(1);
    }

    这里我们可以看到,A_1.dart是part of 'A.dart'的文件,可以理解为,A_1是A的一部分。在part test2.dart中,我们并没有引入stack.dart包就直接使用了pop方法,是因为,在 part 中, import 进来的库是共享命名空间的。
    不是所有的库都有名称,但如果使用 part 来构建库,那么库必须要命名。

    1
    library xxx;

    每个子 part 都存放在各自的文件中。但是它们共享同一作用域,库的内部命名空间,以及所有的导入(import)。

可选类型/泛型

正如之前提到的,Dart 中的类型是可选的,它仅为开发提供便利,同时也提供了泛型来约束集合内的类型,用法与 C++ / Java 类似。

1
2
3
4
List<num> list;
Map<String,int> map;
Map<String,dynamic> map2;
Map<dynamic,dynamic> freeMap;//等价于 Map freeMap;

你可以使用 is 关键字来检测是否为某个类的实例

1
2
3
4
5
6
void main(){ 
var v=[1,2,3];
v is Map;//false
v is List;//true
v is Object;//always true
}

还可以使用 as 进行强制类型转换,这部分与 Java 的强制类型转换相似,只能是从子类转化为父类。

1
2
3
void main(){
1 as Object;//无意义的转换,仅作举例
}

注解(Annotation)

对于 Java 程序员来说注解一定不陌生,对于 JavaScript 程序员来说,可能听说过装饰器(decorator)的提案。

注解作为元数据(metadata),是为了给代码提供额外的信息,提升编码体验,大部分时候并不会对代码产生实质性影响。它以@打头,后跟一个const常量或调用一个const构造函数,内置对象有 @required @deprecated 等。

1
2
3
4
5
6
7
8
9
10
11
12
class Television {
/// _Deprecated: Use [turnOn] instead._
@deprecated
void activate() {
turnOn();
}

/// Turns the TV's power on.
void turnOn() {
//...
}
}

你也可以自定义一个注解:

1
2
3
4
5
6
7
8
9
10
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}

@Todo('seth', 'make this do something')//使用自定义注解
void doSomething() {
print('do something');
}

Async

让我很喜欢 JavaScript 的原因之一就是在疏不在堵的异步调用(在 Node 上更为明显),这也是很多 JavaScript 新手的末日。
很高兴在 Dart 上能找到几乎一样的实现方案: Future

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import 'dart:async';
//定义了返回结果值为String类型
Future<String> getData(String category) async {
var request = await _httpClient.getUrl(Uri.parse(url));
var response = await request.close();
return await response.transform(utf8.decoder).join();
}

void main() async{
var data = await getData('test');//调用 async 函数
//链式调用,捕获异常
var future = //调式调用
new Future
.then(funA(),onError: (e) { handleError(e); })
.then(funB(),onError: (e) { handleError(e); });
}

与 ES6 中的 Promise 用法基本一致,具名参数的加成使得代码可读性更佳,真香!

一些有趣的特性

  • 在 Dart 程序中一定存在一个入口函数,和 C 一样,我们把他命名为 main (),虽然是在面向类编程,但是我们依然可以在文件的最外层可以直接声明函数和变量,这点和 Java 倒是不大一样,但其实幕后工作编译器都帮我们做好了,我们只管写就好了!
  • Dart 中的整数长度取决于你的内存大小(编译成目标语言除外。说你呢 JavaScript !)
  • final 声明的值将是采用懒加载的方式,即只有当get()被调用之前,该变量才会被初始化。
  • 在 Dart 中只有 bool 类型的 true 是 true ,其他对象都是 false ,虽然不影响编译,但是在你写出来的时候 IDE 就会拼命给你报错了。
  • Dart 不存在内部类,类中不存在私有方法所有的方法都是可见并大部分都是可以重写的。私有变量以_开头,如 _privateThing;

一些高级特性

由于本文为入门教程,笔者对于 Dart 也是入门不久,以下高级编程部分用的较少,作为补充仅提及,后续可能会更新补充,有兴趣的读者可以自行搜索学习。

  • 代理 (Proxy)
  • 反射 (Mirror)

Webpack性能优化整理

发表于 2018-12-12
| 字数统计 1,300

加速构建

配置webpack中的Devtool选项

开发环境推荐:
cheap-module-eval-source-map
生产环境推荐:
cheap-module-source-map (这也是下版本 webpack 使用-d命令启动 debug 模式时的默认选项)

1
2
3
4
5
6
7
8
9
10
//webpack.config.js
module.exports = {
//...
entry: {
app: './src/index.js',
},
mode: 'development',
devtool: 'cheap-module-eval-source-map',
//...
}
模式解释
eval每个 module 会封装到 eval 里包裹起来执行,并且会在末尾追加注释 //@ sourceURL.
source-map生成一个 SourceMap 文件.
hidden-source-map和 source-map 一样,但不会在 bundle 末尾追加注释.
inline-source-map生成一个 DataUrl 形式的 SourceMap 文件.
eval-source-map每个 module 会通过 eval() 来执行,并且生成一个 DataUrl 形式的 SourceMap .
cheap-source-map生成一个没有列信息(column-mappings)的 SourceMaps 文件,不包含 loader 的 sourcemap(譬如 babel 的 sourcemap)
cheap-module-source-map生成一个没有列信息(column-mappings)的 SourceMaps 文件,同时 loader 的 sourcemap 也被简化为只包含对应行的。

注1:webpack 不仅支持这 7 种,而且它们还是可以任意组合上面的 eval、inline、hidden 关键字,就如文档所说,你可以设置 souremap 选项为 cheap-module-inline-source-map。
注2:如果你的 modules 里面已经包含了 SourceMaps ,你需要用 source-map-loader 来和合并生成一个新的 SourceMaps 。

  • 使用 cheap 模式可以大幅提高 souremap 生成的效率。大部分情况我们调试并不关心列信息,而且就算 sourcemap 没有列,有些浏览器引擎(例如 v8) 也会给出列信息。
  • 使用 eval 方式可大幅提高持续构建效率。参考官方文档提供的速度对比表格可以看到 eval 模式的编译速度很快。
  • 使用 module 可支持 babel 这种预编译工具(在 webpack 里做为 loader 使用)。
  • 使用 eval-source-map 模式可以减少网络请求。这种模式开启 DataUrl 本身包含完整 sourcemap 信息,并不需要像 sourceURL 那样,浏览器需要发送一个完整请求去获取 sourcemap 文件,这会略微提高点效率。而生产环境中则不宜用 eval,这样会让文件变得极大。

uglifyJS使用parallel参数

1
2
3
4
5
6
7
8
9
10
//webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
//...
plugins: [
new UglifyJsPlugin({
parallel: true
})
],
};

为babel-loader设置cacheDir

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory:true,//或者制定文件夹
}
}
}
]
}
};

cacheDirectory:默认值为false。当有设置时,指定的目录将用来缓存 loader 的执行结果。之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。如果设置了一个空值(loader: 'babel-loader?cacheDirectory')或者true (loader: babel-loader?cacheDirectory=true),loader 将使用默认的缓存目录node_modules/.cache/babel-loader,如果在任何根目录下都没有找到node_modules目录,将会降级回退到操作系统默认的临时文件目录。

使用happypack插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//webpack.config.js
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
module.exports = {
module: {
rules: [
{
test: /\.js$/,
//把对.js 的文件处理交给id为happyBabel 的HappyPack 的实例执行
loader: 'happypack/loader?id=happyBabel',
//排除node_modules 目录下的文件
exclude: /node_modules/
},
]
},
plugins: [
new HappyPack({
//用id来标识 happypack处理那里类文件
id: 'happyBabel',
//如何处理 用法和loader 的配置一样
loaders: [{
loader: 'babel-loader?cacheDirectory=true',
}],
//共享进程池
threadPool: happyThreadPool,
//允许 HappyPack 输出日志
verbose: true,
})
]
}
  • 在 Loader 配置中,所有文件的处理都交给了 happypack/loader 去处理,使用紧跟其后的querystring ?id=babel去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件。
  • 在 Plugin 配置中,新增了两个 HappyPack 实例分别用于告诉 happypack/loader 去如何处理.js和.css文件。选项中的 id 属性的值和上面querystring中的?id=babel相对应,选项中的loaders属性和Loader配置中一样。

使用parallel-webpack

npm install parallel-webpack –save-dev

可代替webpack使用

减小尺寸

使用WebpackDeepScopeAnalysisPlugin插件

1
2
3
4
5
6
7
8
//webpack.config.js
const WebpackDeepScopeAnalysisPlugin = require('webpack-deep-scope-plugin').default;
module.exports = {
//...
plugins:[
new WebpackDeepScopeAnalysisPlugin(),
]
};
  • 首先,要用到tree-shaking,必然要保证引用的模块都是ES6规范的。例如lodash要替换为lodash-es。
  • 在项目中,注意要把babel设置module: false,避免babel将模块转为CommonJS规范。
  • 在package.json中定义sideEffect: false,这也是为了避免出现import xxx导致模块内部的一些函数执行后影响全局环境,却被去除掉的情况。

使用webpack externals选项

index.html:

1
2
<script  src="https://code.jquery.com/jquery-3.1.0.js"</script>
<!--other scripts-->

webpack.config.js:

1
2
3
4
5
6
module.exports = {
//...
externals: {
jquery: 'jQuery'
}
};

可使用该选项将模块以cdn的形式引入,保持代码中的引入

参考文章

  • [webpack]devtool里的7种SourceMap模式是什么鬼?
  • Webpack - UglifyjsWebpackPlugin
  • Webpack - babel-loader
  • amireh/happypack: Happiness in the form of faster webpack build times.
  • trivago/parallel-webpack: Builds multi-config webpack projects in parallel
  • 体积减少80%!释放webpack tree-shaking的真正潜力 - 掘金
  • Webpack - 外部扩展(externals)

前端数据校验从建模开始

发表于 2018-08-30 | 分类于 前端
| 字数统计 1,386

前端开发过程中你们觉得处理什么业务功能最烦人?

做前端已经有很长一段时间了,不知道大家是否和我有同样的感受,在一些 Web 应用中表单处理起来比其他功能模块都麻烦,很多体力活。往往在数据的校验会很费时间。

为了能够把这部分代码更有条理,我们把数据校验部分通过 Schema 预先定义一个数据模型,把数据扔进去,返回校验结果。

接下来我介绍一下这个工具,schema-typed 是一个数据建模及数据验证工具, 它可以非常方便的设计的表单数据结构,当然它不限于在表单使用。如果你在产品中使用了 React , 那配合 React Suite 的表单组件简直就是如虎添翼。

安装

1
npm install schema-typed --save

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { SchemaModel, StringType, DateType, NumberType } from 'schema-typed';

const userModel = SchemaModel({
username: StringType().isRequired('用户名不能为空'),
email: StringType().isEmail('请输入正确的邮箱'),
age: NumberType('年龄应该是一个数字').range(18, 30, '年龄应该在 18 到 30 岁之间')
});

const checkResult = userModel.check({
username: 'foobar',
email: 'foo@bar.com',
age: 40
});

console.log(checkResult);

checkResult 返回结构是:

1
2
3
4
5
{
username: { hasError: false },
email: { hasError: false },
age: { hasError: true, errorMessage: '年龄应该在 18 到 30 岁之间' }
}

多重验证

1
2
3
4
StringType()
.minLength(6, '不能少于 6 个字符')
.maxLength(30, '不能大于 30 个字符')
.isRequired('该字段不能为空');

自定义验证

通过 addRule 函数自定义一个规则。

如果是对一个字符串类型的数据进行验证,可以通过 pattern 方法设置一个正则表达式进行自定义验证。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const myModel = SchemaModel({
field1: StringType().addRule((value, data) => {
return /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(value);
}, '请输入合法字符'),
field2: StringType().pattern(/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/, '请输入合法字符')
});

schema.check({ field1: '', field2: '' });

/**
{
field1: {
hasError: true,
errorMessage: '请输入合法字符'
},
field2: {
hasError: true,
errorMessage: '请输入合法字符'
}
};
**/

自定义验证 - 多字段交叉验证

例如,验证两次输入密码是否一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const schema = SchemaModel({
password1: StringType().isRequired('该字段不能为空'),
password2: StringType().addRule((value, data) => {
if (value !== data.password1) {
return false;
}
return true;
}, '两次密码不一致')
});

schema.check({ password1: '123456', password2: 'root' });

/**
{
password1: { hasError: false },
password2: {
hasError: true,
errorMessage: '两次密码不一致'
}
};
**/

API

  • StringType
  • NumberType
  • ArrayType
  • DateType
  • ObjectType
  • BooleanType

StringType

  • isRequired()
1
StringType().isRequired('该字段不能为空');
  • isEmail(String: errorMessage)
1
StringType().isEmail('请输入正确的邮箱地址');
  • isURL(String: errorMessage)
1
StringType().isURL('请输入正确的URL地址');
  • isOneOf(Array: items, String: errorMessage)
1
StringType().isOneOf(['Javascript', 'CSS'], '只能输入 `Javascript`和 `CSS`');
  • containsLetter(String: errorMessage)
1
StringType().containsLetter('必须包含英文字符');
  • containsUppercaseLetter(String: errorMessage)
1
StringType().containsUppercaseLetter('必须包含大写的英文字符');
  • containsLowercaseLetter(String: errorMessage)
1
StringType().containsLowercaseLetter('必须包含小写的英文字符');
  • containsLetterOnly(String: errorMessage)
1
StringType().containsLetterOnly('只能包含的英文字符');
  • containsNumber(String: errorMessage)
1
StringType().containsNumber('必须包含数字');
  • pattern(Object: regexp, String: errorMessage)
1
StringType().pattern(/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/, '请输入合法字符');
  • rangeLength(Number: minLength, Number: maxLength, String: errorMessage)
1
StringType().rangeLength(6, 30, '字符个数只能在 6 - 30 之间');
  • minLength(Number: minLength, String: errorMessage)
1
StringType().minLength(6, '最小需要6个字符');
  • maxLength(Number: maxLength, String: errorMessage)
1
StringType().minLength(30, '最大只能30个字符');
  • addRule(Function: onValid, String: errorMessage)
1
2
3
StringType().addRule((value, data) => {
return /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(value);
}, '请输入合法字符');

NumberType

  • isRequired()
1
NumberType().isRequired('该字段必填');
  • isInteger(String: errorMessage)
1
NumberType().isInteger('只能是整型');
  • isOneOf(Array: items, String: errorMessage)
1
NumberType().isOneOf([5, 10, 15], '只能是`5`,`10`,`15`');
  • pattern(Object: regexp, String: errorMessage)
1
NumberType().pattern(/^[1-9][0-9]{3}$/, '请输入合法字符');
  • range(Number: minLength, Number: maxLength, String: errorMessage)
1
NumberType().range(18, 40, '请输入 18 - 40 之间的数字');
  • min(Number: min, String: errorMessage)
1
NumberType().min(18, '最小值 18');
  • max(Number: min, String: errorMessage)
1
NumberType().max(40, '最大值 40');
  • addRule(Function: onValid, String: errorMessage)
1
2
3
NumberType().addRule((value, data) => {
return value % 5 === 0;
}, '请输入有效的数字');

ArrayType

  • isRequired()
1
ArrayType().isRequired('该字段必填');
  • rangeLength(Number: minLength, Number: maxLength, String: errorMessage)
1
ArrayType().rangeLength(1, 3, '至少选择1个,但不能超过3个');
  • minLength(Number: minLength, String: errorMessage)
1
ArrayType().minLength(1, '至少选择1个');
  • maxLength(Number: maxLength, String: errorMessage)
1
ArrayType().maxLength(3, '不能超过3个');
  • unrepeatable(String: errorMessage)
1
ArrayType().unrepeatable('不能出现重复选项');
  • of(Object: type, String: errorMessage)
1
ArrayType().of(StringType().isEmail(), '格式错误');
  • addRule(Function: onValid, String: errorMessage)
1
2
3
ArrayType().addRule((value, data) => {
return value.length % 2 === 0;
}, '好事成双');

DateType

  • isRequired()
1
DateType().isRequired('日期不能为空');
  • range(Date: min, Date: max, String: errorMessage)
1
2
3
4
5
DateType().range(
new Date('08/01/2017'),
new Date('08/30/2017'),
'时间应该在 08/01/2017 - 08/30/2017 之间'
);
  • min(Date: min, String: errorMessage)
1
DateType().min(new Date('08/01/2017'), '时间的最小值 08/01/2017');
  • max(Date: max, String: errorMessage)
1
DateType().max(new Date('08/30/2017'), '时间的最大值 08/30/2017');
  • addRule(Function: onValid, String: errorMessage)
1
2
3
DateType().addRule((value, data) => {
return value.getDay() === 2;
}, '只能选择周二');

ObjectType

  • isRequired()
1
ObjectType().isRequired('该对象不能为空');
  • shape(Object: types)
1
2
3
4
ObjectType().shape({
email: StringType().isEmail('应该是一个 email'),
age: NumberType().min(18, '年龄应该大于18岁')
});
  • addRule(Function: onValid, String: errorMessage)
1
2
3
4
5
6
ObjectType().addRule((value, data) => {
if (value.id || value.email) {
return true;
}
return false;
}, 'id 与 email 必须有一个不能为空');

BooleanType

  • isRequired()
1
BooleanType().isRequired('该字段不能为空');
  • addRule(Function: onValid, String: errorMessage)
1
2
3
4
5
6
ObjectType().addRule((value, data) => {
if (typeof value === 'undefined' && A === 10) {
return false;
}
return true;
}, '当 A 等于 10 的时候,该值必须为空');

让 Markdown 中的代码可以实时运行

发表于 2018-08-28 | 分类于 前端
| 字数统计 505

背景

让 Markdown 中的代码可以实时运行,为什么会有这样一个需求?

在我们前端团队中,技术相关的文档都采用 Markdown 编写, 文档中往往会伴随很多示例代码,我们希望大家在阅读文档的时候,可以运行示例代码,看到效果。

需求

  • 让 Markdown 中的代码可以运行,并预览效果。
  • 代码可以在线编辑。
  • 不影响整个文档流的布局。
  • 支持 React, 支持代码高亮。
  • 可以配置 babel。

明确需求以后,写了一个 React 组件来满足这些功能, react-code-view , 首先看一下效果:

preview

在线预览: https://simonguo.github.io/react-code-view/

原理

  • 就通过 markdown-loader 与 html-loader 解析 Markdown 文档。
  • 通过正则表达式取出 code ,交给 codemirror
  • 把 codemirror 中的代码通过 babel 编译,再通过 ReactDOM.render 渲染到指定的容器中。

安装

1
npm install react-code-view

配置 webpack

在 webpack 中需要添加对 markdown-loader 的支持。 需要安装:

1
2
3
npm install html-loader --save-dev
npm install markdown-loader --save-dev
npm install react-markdown-reader --save-dev

webpack.config.js 配置

>=webpack 2.x +

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

const markdownRenderer = require('react-markdown-reader').renderer;

{
test: /\.md$/,
use: [{
loader: 'html-loader'
}, {
loader: 'markdown-loader',
options: {
pedantic: true,
renderer: markdownRenderer(/**languages:Array<string>**/)
}
}]
}

languages 默认值:["javascript", "bash", "xml", "css", "markdown", "less"];

Github: https://github.com/simonguo/react-markdown-reader

添加 Babel

示例代码中通常是使用到 ES2015+ , React 等,需要对代码实时做编译,所以在 html 中引入 babel :

1
<script type="text/javascript" src="//cdn.staticfile.org/babel-standalone/6.24.0/babel.min.js"></script>

示例

1
2
3
4
5
6
7
8
9
10

import CodeView from 'react-code-view';
import '~react-code-view/lib/less/index.less';

import { Button } from 'rsuite';


<CodeView dependencies={{ Button }} >
{require('./example.md')}
</CodeView>

源代码都统一写在 markdown 中,参考:
example.md

这里需要注意的是把需要运行的代码一定要放在 <!--start-code--> 与 <!--end-code--> 之间。

API

NameTypeDefault valueDescription
dependenciesObject依赖的组件
showCodebooleantrue显示代码
babelTransformOptionsObject{ presets: [‘stage-0’, ‘react’, ‘es2015’] }babel 配置参数 options

谁在用?

  • React Suite

提交一个仓库到 CDNJS

发表于 2018-05-29
| 字数统计 2,370

概述

本篇文章主要介绍了:如何提交自己的库到 cdnjs,提交要求,详细的提交步骤及遇到的问题与解决办法。

阅读全文 »

React Suite v3.0 正式版发布

发表于 2018-04-25 | 分类于 前端
| 字数统计 477

相信很多人会好奇,React Suite 是什么?

React Suite 是 HYPERS 前端团队和 UX 团队开源的一套基于 React 的 UI 组件库,能够帮助您快速构建一个企业级应用。

官网访问地址: rsuitejs.com

阅读全文 »

webpack 换肤功能多主题/配色样式打包解决方案

发表于 2018-04-19
| 字数统计 1,800

本文主要详细介绍了,如何使用 webpack,打包多套不同主题的解决方案以及实践中所遇到的问题。

阅读全文 »

ECMAScript 2016, 2017, 和2018中新增功能的示例

发表于 2018-04-11
| 字数统计 6,651

以下是 ECMAScript 2016, 2017, 和 2018 中新增功能的示例

原文链接

要跟上 JavaScript(ECMAScript) 更新的脚步很难,更难的是找到有用的代码示例。

因此,在本文中,我将介绍在 ES2016,ES2017 和 ES2018(最终草案)中添加的TC39已完成提案中列出的所有 18 个新特性,并一一展示有用的示例。

这篇文章很长,不过应该很容易读,可以当成一篇“Netflix binge reading”。我保证你读完后将对这些新特性有很多的了解。

好,我们一个一个来看看。

阅读全文 »

浏览器渲染引擎

发表于 2018-04-04
| 字数统计 3,548

背景

浏览器的内核中主要分为渲染引擎和 javascript 引擎,本篇主要围绕渲染引擎介绍一下浏览器的工作原理。

首先,我们先看几个 user-agent 的字符串:

  • Mozilla/ 1.0 (Windows NT 6.1;rv:2.0.1) Gecko/2010010Firefox/4.0.1
  • Mozilla/ 4. 0 (compatible; MSIE 7. 0; Windows NT 6. 0)
  • Mozilla/ 5. 0 (Linux; Android4. 0. 4; Galaxy Nexus Build/ IMM76B) AppleWebKit/ 535. 19 (KHTML, like Gecko) Chrome/ 18. 0. 1025. 133 Mobile Safari/ 535.

这是 3 个不同浏览器的 user-agent,第 1 个是 Firefox 的,第 2 个是 IE7 的,第 3 个是 Chrome 的。有没有觉得很奇怪,为什么所有字符串的前面都会有一个 Mozilla 开头呢? 而且在 Chrome 中包含了很多其他浏览器的标识,Android,Gecko,Safari … , 这些浏览器厂商为什么要把这个 user-agent 字符串设计成这样?user-agent 按照正常的理解就是浏览器的标识,包含操作系统和浏览器信息带上版本号就行了,浏览器厂商为什么搞的这么复杂呢?

这需要了解一下 user-agent 字符串历史 ,大致的意思是, 早期的浏览器 Netscape 的 user-agent 是以 Mozilla/Version [Language] (Platform; Encryption) 的格式,大多数服务器在加载页面前都会检查 user-agent 是否为该款浏览器,然而在 1995 年, IE 发布首款浏览器,如果不兼容 Netscape user-agent 字串,用户就根本访问不了页面,于是 IE 就设计了这种格式: Mozilla/2.0 (compatible; MSIE Version; Operating System) 。 其他新的浏览器发布也是一样的,为了溶入主流而不被踢出局,在 user-agent 字串中放详尽的信息,以便骗取网站的信任使它与其它流行的浏览器兼容。

接下来,简单回顾一下浏览器的历史:

  • WorldWideWeb 1991 年
  • Mosaic 1993 年
  • Netscape 1994 年
  • Opera 1995 年
  • IE 1995 年 一战
  • Safari 2003 年
  • Firefox 2004 年 二战
  • Chrome 2008 年
  • Edge 2015 年

很多人都以为最早的浏览器是 Netscape, 其实在 Netscape 之前还有 WorldWideWeb 和 Mosaic 两个浏览器,WorldWideWeb 是世界上第一个浏览器,它同时也是一个编辑器,在 1991 年发布, 当时 http 的版本还是 0.9 ,只支持 get 请求。

随后在 1993 年 由美国伊利诺州的伊利诺大学的 NCSA 组织,发布第一个可以显示图片的浏览器,叫 Mosaic。随后 NCSA 将 Mosaic 的商业运营权转售给了 Spyglass 公司,该公司又向包括微软公司在内的多家公司技术授权,然后,微软的 IE 浏览器就是从这里开始了。
1994 年,在 Mosaic 浏览器开发团队的核心成员,重新成立 Netscape 公司,从此 Netscape 浏览器诞生。
1995 年,挪威的本土电信公司 Telenor 开发了一个新的浏览器 Opera,Opera 浏览器他特点就是速度快,但是兼容性不是很好,在浏览器上独创了很多功能,比如标签式浏览就是 Opera 发明的,Opera 在经历很多坎坷后,在 2016 年被奇虎 360 收购。
1995 同年,微软在取得 Spyglass Mosaic 的源代码和授权后,发布了首款浏览器 Internet Explorer ,从此展开浏览器第一次大战。
1998 年,Netscape 公司内部成立 Mozilla 组织。
2003 年,苹果公司发布了自己第一个浏览器 Safari,同时在两年后,也就是
2005 年开源了自己的浏览器内核 Webkit。
2004 年,Mozilla 组织发布了 Firefox 浏览器,然后第二次浏览器大战开始。
2008 年,是一个灾难很多的一年,四川大地震,过年回家又遇上雪灾,作为前端开发我的将要开始兼容一个由 Goolge 开发新的 Chrome 浏览器。 但是 Chrome 带来的体验是让人开心的 。
2015 年,微软随着 Windows 10 一起发布 Edge 浏览器,伤透大家心的 IE ,估计维护不下去。

当前市场浏览器市场占比最高的是 Chrome 浏览器,其次就是 IE,Firefox ,详细占比查看 Net Marketshare 的统计。

阅读全文 »
123
Hypers

Hypers

25 日志
2 分类
45 标签
© 2019 Hypers - 全站共58.9k字
由 Hexo 强力驱动
主题 - NexT.Muse