leowang721 / blog Goto Github PK
View Code? Open in Web Editor NEWuse issue to write note ...
use issue to write note ...
JavaScript引擎都在尽力的优化以接近C类语言的性能。
对于数组而言,可以直接使用,但最好是触发Fast Elements
,具体可查看:《高性能的JavaScript开发》的数组部分
使用数组就可以轻易的模拟了,最多封装个类方便使用。
栈: push pop
队列:unshift pop
es6已经有了,直接使用吧!
我一直认为 链表
是数组长度固定化的产物,js的数组是动态的,不觉得它还有什么存在的必要, arr.splice(10, 1, newNode)
这种代码不是比链表插入要优美得多?而且还有一堆方法可以直接用:shift, unshift, pop, push, forEach, map ...
遍历也可以直接用 for...of
使用Iterator
的方式
当然如果有不同的意见,我也可以改正。
如果是为了练习代码能力,那倒是不妨自己实现一个。
js中的对象起到的作用已经超过了基础的数据结构 Dictionary
和 HashTable
,也不必费心去实现了,而且es6还有Map
。
hash算法倒是个有意思的东西,不过不在当前范畴。
这里还是数学概念:
自由树
是一个连通的、无回路的无向图。有根树
就是多了个根节点,由此就有了“父子层级”的概念。有序树
指的是子级节点有序的树。二叉树
是指最多有两个子节点的树。完全二叉树
是指子节点是满的,只要有就一定是两个,并且叶节点的深度一定相同(就是说左右子树的层数相等)。树的最大特点是:任意两个顶点由唯一一条简单路径相连
二叉树 => 二叉查找树 => 红黑树 => B树 => B+树 .... ????
节点存值,左子树都比它小,右子树都比它大。
执行基本操作的时间与树的高度成正比。n个节点的完全二叉树,操作最坏运行时间为O(lgn);如果是线性链(子级节点都跑一边去了),最坏是O(n)。
一颗随机构造的二叉树期望高度为O(lgn),基本操作的平均时间为O(lgn)。
经典的图定义是 G = (V, E)
:
// 至少我曾经会这么写……
class Graph {
nodes = [];
edges = [];
}
但实际上有两种表示方法:邻接表
or 邻接矩阵
,上面那个一般是邻接矩阵,当然要写成边集的话那也没办法~~
来看图示:
邻接矩阵
,网上找的图:
邻接表适合于稀疏图(|E|远小于|V|^2),因为表示出来比较紧凑
邻接矩阵则适合于稠密图,或必须很快判别两个给定顶点是否存在连接边(顶点间最短路径算法
)
待续...
引擎暂时只是v8, JavaScriptCore、SpiderMonkey、Chakra、Carakan等等这些回头再看看做做对比啥的吧……
其他语言是以C/C++为例。
测试环境为Mac,并列出浏览器 or 环境的版本
持续更新中... 只是个持续更新的笔记而已,只是希望能说明白罢了!!
C的访问机制,基本是编译确定了位置,偏移信息共享,使用时直接使用偏移量,所以非常高效。而js则不然,它在执行阶段才能确定结构,而且还能够增减对象的属性,在查找值时需要匹配属性名才能找到正确的值,这是很浪费时间的。
不过引擎们做了很多努力,已经在逐步接近其他语言的性能了,例如隐藏类。
这里说到的东西目的主要是为了避免让引擎的优化被浪费,甚至是倒退。
其实呢,对于WebGL这类的貌似更有用一些,对于一般开发倒是意义不大。
在v8中,除了基础类型 Boolean
、Number
、String
、Null
和Undefined
以外,其他都是对象。
在v8中,数据以两种形式来表示:
v8实际上将所有的数据交由GC管理的,被定义在内部,我们不能直接操作,只能通过Handle
(句柄类)来进行操作,所以访问的时候都是先找到Handle
,再通过指针去访问实际的值,修改的时候也是修改Handle
中的指针。
除了极少数的数据例如整型,其他的内容都是从堆里申请内存来存储的。
Handle
本身能够存放特定数据,这也就使得这些数据不需要再从堆中分配,这也就减少了内存的使用并增加了访问速度。
在v8中,Handle
对象的大小是4/8字节(32/64位机器),在JavaScriptCore中是8字节。JavaScript的数字尽管按照标准都应是64位浮点格式来表示,但是对于整数实际的操作是基于32位整数,这也就意味着可以在Handle
中直接存储整数以达到快速访问的目的。
但针对着32位的Handle
对象而言,它至少要区分存储的数据是整数还是指针,而指针本身的最后两位都是00,其实是不需要的,所以,就用这两位来表示句柄中包含的数据的类型,最后一位如果为0表示是整数,如果是1则表示是其他类型。
所以v8中实际能够直接快速使用的整数是小整数
,31位的有符号整数。
超过31位的数字,则被转换为double,放到一个Object中,再用指针指过去。
我们都知道数组
这种数据结构,它的经典实现就是:内存中一段连续的位置分配来存储数据的线性集合。在其他语言中,它是需要在声明时固定类型、指定长度,以便在栈区
分配内存,并且长度是不可变的。
数组能够高效的访问,其实就在于内存的“固定”分配机制,元素类型固定,每次偏移的长度也是固定的,那么对于访问而言,仅需要计算地址位移信息而已,仅需要几个机器语言指令就可以存、取及使用。
js的数组本质是个对象,但在v8中,以非负整数为key的元素被称为Element,每个对象都有一个指向Element数组的指针,其存放和其他属性是分开的,这其实也是针对数组的优化。
那么Element
,其实也就是数组元素,它有两种类型:
毫无疑问,Dictionary Elements
要比Fast Elements
慢很多,所以从性能角度而言,我们要做的事情是避免Dictionary Elements
的出现。
什么状况下会导致Dictionary Elements
的出现呢?
至于过多的“洞”出现在数组中会不会导致转换为Dictionary Elements
暂时还没有明确的结论,不过不建议这样的用法。
示例:
// for 1
let arr = new Array(65000);
// for 2
let arr = [];
arr[1024] = 1024;
再来看Fast Elements
,它其实也有三种类型:
这里其实是由元素类型导致的不同类型,所以可以再看一下上面的数据类型
这部分
首先是整型,v8为了提高效率,31位有符号整数是由Handle
保存的,所以处理起来是极快的。
再来是 doubles,常见的触发条件就是在整数数组中增加了一个浮点数,这会导致整个数组都被展开转换为double来存储,通过增加一个隐藏类实现的
最后是 values,例如再加入了一个true|undefined|null
,这时候就要再次增加隐藏类,指明要存储的是Object
如果涉及到了这些转化,都会是性能的消耗,所以我们要尽可能的避免这种情况的出现。
然而如果一开始就初始化如:let arr = [1, 2, 1.5, true]
,并没有这样的转换问题,所以复杂类型的字面直接初始化是一个好主意,但是更好的主意是:尽量的在数组中存储相同类型的元素。
在Google IO 2012年的v8讲解中有这方面的讲解,清晰易懂,建议查看一下:视频 PPT
上面的说到的东西谈及了一个概念:隐藏类
,这个会在后面的部分再细说,在这里只需要知道这个就行了:数组的Fast Elements
模式默认是小整数
,随着不同类型值的进入,会导致数组类型发生转换,都是通过隐藏类
来实现的,而这个转换往往是一种不必要的消耗。
下面的代码注释部分就是在说明隐藏类
var a = new Array();
a[0] = 77; // integer 无洞
a[1] = 88;
a[2] = 0.5; // double 无洞
a[3] = true; // Object 无洞
可以看出,有洞、无洞也会是隐藏类
的一个标记,所以洞的出现不可避免的会造成性能的下降。
var length = 10000000;
function a(){
var arr = [];
var i = 0;
for (i = 0; i < length; i++) {
arr[i] = i;
}
}
function b(){
var arr = new Array(length);
var i = 0;
for (i = 0; i < length; i++) {
arr[i] = i;
}
}
计算10次运行的平均值,结果依次为 a 和 b,Safari不支持performance.mark,所以都用了new Date()计算:
1000W次运算的结果
100W:
10W及以下几可无视差别。
其实还尝试了小额内存预分配(<64K)且试探性的内存延展(1024),不过从实验结果上来看由于引入了额外的计算,反而时间较内存自动分配有所增加。
var length = 10000000;
function a(){
var arr = new Array(length);
var i = 0;
for (; i < length; i++) {
arr[i] = i;
}
}
function b(){
var arr = new Array(length);
var i = length - 1;
for (; i >= 0; i--) {
arr[i] = i;
}
}
测试结果几近相同,b的表现偶尔会变坏一点点,但是不知是不是触发什么bug了,Firefox直接在逆向赋值崩掉了,刷出了61.3, 5782
这样令人崩溃的结果。
anyway,不要逆向赋值了!!
Dictionary Elements
,据说洞变少可能会被v8优化回紧凑结构,但是这是不可依赖的行为arr[100] == null
或者隐式转换的判断性访问。之前说过,js中除了那五种基础类型,其他的都是对象。而js又是个弱类型的语言,为了能够达到优化的目的(还记得与C类语言的特点么?就在前言
部分。),即提升读取性能,v8利用动态创建隐藏内部类的方式动态地将属性的内存地址记录在对象内,从而提升整体的属性访问速度。
避免了通过字符串匹配的方式来查找属性值。
隐藏类
是为Object服务的,相同结构的Object会共享隐藏类
,当结构发生了改变,对应的隐藏类
也会发生改变,要么复用,要么新增。
而且会将使用过的隐藏类
结构通过内嵌缓存(inline cache)缓存起来,以便复用时可以快速的访问偏移值。
复用的一个最佳例子就是类的使用了:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
var p2 = new Point(3, 4); // 复用隐藏类
p2.z = 5; // p2使用了不同的隐藏类!!很可能触发了一个新增处理
需要注意的是,隐藏类
将属性这些东西解析为树,所以顺序不同的初始化会导致隐藏类
的解析结果也不同。
所有的Object类型都是这样。
delete
会触发隐藏类的改变,如果是为了内存回收,设置为null是更好的选择v8有一个优化行为,针对于热点函数,会使用Crankshaft
编译器去乐观且猜测性的生成高效的本地代码,这通常是建立在变量类型不改变的前提下,如果发现类型变化了,那么v8就会使用优化回滚(Deoptimization)机制来回滚到之前的一个没有经过特别优化的代码。
例如:
var counter = 0;
function waitThenGo() {
counter++;
if (counter < 10000000) {
return counter;
}
var now = new Date();
console.log(now);
}
或许目的只是为了等一段时间之后打印当前时间,但是对于v8来说,waitThenGo执行了很多次之后,就可能会触发Crankshaft
编译器来生成优化的代码了,它认为已经知道了所有的信息了,例如类型。
但是实际上,没运行到 now 这一行之前,我们都不知道 now 的类型,所以当运行到这一行时,v8就别无选择的只能回滚到一个通用的状态了。
权当一个笔记,再写写或许更明白点
原本搞了个模拟的,就使用元素来模拟绘图,例如直线……
不过确实是很麻烦…… 不过再去扫了扫 Issues,果然发现有人问过,且 facebook 也有相应的方案:
绘图可使用:ReactART,位置:Libraries/ART/ReactNativeART.js
var ReactART = {
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
Pattern: Pattern,
Transform: Transform,
Path: Path,
Surface: Surface,
Group: Group,
ClippingRectangle: ClippingRectangle,
Shape: Shape,
Text: Text,
};
例子可暂时扫扫 https://github.com/reactjs/react-page/blob/art/
继续记笔记:
使用这个之前,安装 art 库:
npm i art --save
并且需要在项目中添加依赖:
ProjectName
-> 选择 node_modules/react-native/React/Libraries/ART/ART.xcodeproj'libART.a
添加到 Linked Frameworks and Libraries
即一个图形,必须是这样的:最外层是一个 Surface,去定义容器的大小,就像是 Canvas 元素一样,然后其子元素由 Group 进行组装,使用 Shape 和 Text 确定内容
说明:
x, y 每个都可有,它指定了元素的位置。
originX, originY则是一个相对位置,它主要是给 scale 和 rotate 使用的,默认是0, 0,这样就意味着,默认是以起点为中心旋转,或者从起点开始缩放!
容器,接受的 props:
定义分组,它是一个 Component
,实际上使用了 NativeGroup
,可接受的 props:
形状,与 ART 或者 ReactART 不同的是,当前不支持 width 和 height,可接受的 props:
文字
其余几个跟 Shape 一致的
变形相关
var t = new Transform();
t.transformTo(1, 0, 0, 1, 0, 0)
.move(props.x || 0, props.y || 0)
.rotate(props.rotation || 0, props.originX, props.originY)
.scale(scaleX, scaleY, props.originX, props.originY);
参数为(xx, yx, xy, yy[, x, y]),即 arguments
result current arguments
xx xy x xx xy x xx xy x
yx yy y = yx yy y * yx yy y
0 0 1 0 0 1 0 0 1
xx - (number) 沿 X 轴的缩放 (default: 1)
yx - (number) 旋转? multiplier to skew the x axis (default: 0)
xy - (number) 旋转? multiplier to skew the y axis (default: 0)
yy - (number) 沿 Y 轴的缩放 (default: 1)
x - (number) 水平移动 (default: 0)
y - (number) 竖直移动 (default: 0)
重置矩阵为某个值,其实就是重置 current,跟构造函数参数一致
translate(x, y)
move(x, y) 水平位移和竖直位移
moveTo(x, y) 移动到
(deg, x, y)
(x, y)
缩放
resizeTo(width, height)
好像是点,不过没确认作用
是在 art/core/path的基础上扩展了一小下的 path,做了一点处理,使用跟 path 一样:
所有的起点都是当前位置,往往是所在 Group 的x, y
move
moveTo
line
lineTo
reset
close
toJSON
主要是笔刷
其实还是个 Group,不过似乎是有路径规划了还是神马,没确定完呢
props
似乎是笔刷定义,暂没确认
to be continued
权当一个笔记,再写写或许更明白点
习惯了前端世界的交互模式之后(其实就是 DOM 事件),在这个入门的过程中感觉 React Native 的交互处理就是个不适应。
如果要做 React Native 的交互,首先至少要知道这样几个东西:
不过干读这几个文档的话,基本就是一头雾水……
还是一点一点来看罢:
TouchableHighlight、TouchableOpacity、TouchableWithoutFeedback 这几个很好弄,官方贴心的直接封装了最基础的 Touch 行为,在任何需要点击的 View 外面直接包上这样的标签就行了。
TouchableHighlight 在点击时表现为高亮
TouchableOpacity 在点击时表现为透明
TouchableWithoutFeedback 在点击时无反馈
这几个效果都是封装好了的,无需开发者操心。
Sample Code
<TouchableOpacity
onPressIn={this._onPressInCircle.bind(this)}
onPressOut={this._onPressOutCircle.bind(this)}>
<View style={styles.gridItem}></View>
</TouchableOpacity>
然而这些只适用于按钮系……
支持事件:
支持参数:
再特殊点的行为,例如划过,就不用想用这几个货直接实现了。
中文翻译叫:手势应答系统。
主要就是搞手势识别处理的,那其实也就是复杂点的触摸:例如一边摸一遍动啊,摸着还动出花样画个 L 啥的的那种。
冒泡的:
touchStart
/mouseDown
行为发生,是否当前的元素成为处理器touchMove
/mouseMove
行为发生,是否当前行为成为处理器不冒泡/未来不冒泡的:
是否接管成为处理器(因为冒泡是从最深处开始,可以在父级的元素使用此类方法接管):
touchStart
/mouseDown
行为发生,是否当前的元素代替最深层的子元素成为处理器touchStart
/mouseDown
行为发生,是否当前的元素代替最深层的子元素成为处理器touchUp
以上都是在ResponderEventPlugin.js里面实现的,我们直接使用视图 View 配置
这个图画的我头晕啊……
之前说到,有两个东西是冒泡的:
然则默认是触发最深的那个元素,也就是子级元素,如果父级要拦截作为处理器,则需要处理:
这两个事件的触发顺序是从父级开始的,所以如果父级设置了返回 true,则会执行父级的处理。
但是如果任一返回了 false,则依然使用子级元素作为处理器。
不过如果父级的 onStartShouldSetResponder
如果返回 false,干脆不会触发父级的验证,onStartShouldSetResponderCapture
返回 true 也没用,Move 也是同理。
话说这个还没搞明白怎么用……
直接写属性,作为 prop:
<View onResponderStart={(evt) => true} />
也可以使用...运算符:
class GridItem extends Component {
get touchProps() {
return {
onStartShouldSetResponder: (evt) => true,
onResponderGrant: (evt) => {
console.log('child');
console.log(evt);
},
onResponderTerminationRequest: (evt) => true
};
}
render() {
return (
<View {...this.touchProps}></View>
)
}
}
也可以使用 PanResponder
(这个会在实际处理的事件前加个 Pan,输出时又会去掉,而且会增加一个参数 gestureState
):
class GridItem extends Component {
componentWillMount() {
this._panGesture = PanResponder.create({
onStartShouldSetResponder: (evt, gestureState) => true,
onPanResponderGrant: (evt, gestureState) => {
console.log('child');
console.log(evt);
console.log(gestureState);
},
onPanResponderTerminationRequest: (evt, gestureState) => true
});
}
render() {
return (
<View {...this._panResponder.panHandlers}></View>
)
}
}
可以参考:http://www.terlici.com/2015/04/06/simle-slide-menu-react-native.html
就是使用 PanResponder + Animation做的。
这个回头我自己再搞个出来。
因为不想用 WebView 做,所以这里都是从纯 React Native 的角度去考虑的。
因为生命周期中,TouchIn 是起点,所以如果在外面按住了划过元素,元素是不会有反应的……
单纯的子级接管作为处理器然后释放也是没用的,如果同时设置 Capture,父级的优先级大……
那么是否可以这样呢?父级判断碰撞,然后释放处理权?但是拦截的判断只在最开始触发的时候能搞,所以似乎还是行不通的。而且都碰撞到了,如果能直接处理子元素不是更简便么?
没那么简单,需要看看这三个方法:
使用 refs:
<View style={styles.gridView} {...this._panResponder.panHandlers} >
<View style={styles.gridLine}>
<GridItem ref="item1" />
<GridItem ref="item2" />
<GridItem ref="item3" />
</View>
<View style={styles.gridLine}>
<GridItem ref="item4" />
<GridItem ref="item5" />
<GridItem ref="item6" />
</View>
<View style={styles.gridLine}>
<GridItem ref="item7" />
<GridItem ref="item8" />
<GridItem ref="item9" />
</View>
</View>
这样就可以直接通过 this.refs[name]
获取到子元素了。
这是从 React Native 的 Issue 1374 拿到的方法:
var RCTUIManager = require('NativeModules').UIManager;
var view = this.refs[name];
var handle = React.findNodeHandle(view);
RCTUIManager.measure(handle, (x, y, width, height, pageX, pageY) => {
// x,y 似乎是当前container的坐标
// width, height 是宽高
// pageX, pageY 是在屏幕中的坐标(起始坐标)
})
所以,元素在屏幕中的范围是:pageX ~ pageX + width, pageY ~ pageY + height
至少是个简单的正方形,如果是其他形状例如圆形,可能还需要计算圆心和半径的大小。
之前说过 PanResponder 会给事件方法增加一个参数 gestureState
:
一个 gestureState 对象有以下属性:
那么 touch 位置的坐标可以这么获得:[x0 + dx, y0 + dy]
当然也可以使用都有的 evt 参数:
changedTouches - Array of all touch events that have changed since the last event
identifier - The ID of the touch
locationX - The X position of the touch, relative to the element
locationY - The Y position of the touch, relative to the element
pageX - The X position of the touch, relative to the screen
pageY - The Y position of the touch, relative to the screen
target - The node id of the element receiving the touch event
timestamp - A time identifier for the touch, useful for velocity calculation
touches - Array of all current touches on the screen
直接用[pageX, pageY] 就行了。
这样就可以进行简单的碰撞计算了,计算位置是否在某个子元素的范围内就行了。
实际上至此手势解锁的几个关键问题已经解决,正在写一个手势解锁的组件:k-react-native-swipe-unlock 玩耍。
To Be Continued.
权当一个笔记,再写写或许更明白点
说到布局,首先作为一个前端同学,我想到的是:div、p、span、section ... 以及 article、header、footer...
这些非语义化的&语义化的标签,在前端的世界中,是用于按层级、按语义去实现一个可读的结构。
一个经典的 HTML 结构:
<header>
<nav>
<ul>
<li>首页</li>
<li>文章列表</li>
</ul>
</nav>
</header>
<section id="article-list">
<h1>文章列表</h1>
<article>
<header>
<hgroup>
<h1>title</h1>
<h2>作者</h2>
</hgroup>
</header>
<section>
<p>第一段</p>
<p>第二段</p>
</section>
<aside>
<ul>
<li>tag1</li>
<li>tag2</li>
</ul>
</aside>
</article>
...
</section>
<footer>
CopyRight
</footer>
写 HTML 的时候,我们往往是直接就设计完了这样的一个结构,一个 big picture,当然我们还是先按大块设计,再去设计细节!不过在设计结构的时候,基本一直都能看到完整的东西。
而在 React Native 中,讲究的是 Component 的复用,是不断的分层/块去编写 Component,但是只有在那个 Component 中才能看到再进一步的结构,因为往往是“你只需要使用我就好了,不需要关心我怎么显示的。”
React 也是一样,不信去看最终 React 展现到浏览器中的样子吧,还是那么一堆 div 标签,干读基本读晕。
话题转回来,我们要这么展现文章列表:
...
<ArticleList />
...
所以,我们需要设计个列表页:
class ArticleList extends React.Component {
...
render() {
return (
<Text>文章列表</Text>
<ListView
dataSource={this.state.datasource}
renderRow={this._renderArticle />
);
}
_renderArticle(rowData: string, sectionID: number, rowID: number) {
...
return (
<TouchableHighlight onPress={() => this._pressArticle(rowID)}>
<Article id="{articleId}" />
</TouchableHighlight>
)
}
}
在这里确定了:我要以 ListView 的方式展现列表,点击的时候,使用 Article 的视图展现内容。
所以还需要写个 Article(在写的时候直接按全屏搞的,导航什么的留给 Navigator 搞定,我才不管。)
class Article extends React.Component {
render() {
return (
<View style="{articleStyles.header}">
<View styles="{articleStyles.title}">
<Text>title</Text>
</View>
<ArticleAuthor name="Leo" />
</View>
<View style="{articleStyles.content}">
<Text>第一段</Text>
<Text>第二段</Text>
</View>
<View style="{articleStyles.aside}">
<ArticleTags />
</View>
);
}
}
这样的过程,导致我们不能对当前的视图有一个像 HTML 那样的总览,看到的永远是块,而不是结构。而且由于 App 的场景专注性是极强的,所以它实际上更多的是 Navigator 式的浏览,而不是像 Web 大多数时候都要展现一个完整的 Nav 放在那里。
所以在文章列表后,点击展现 Article 的时候,我们使用 navigator 提供了一个返回按钮(最多再提供上一篇、下一篇),不需要再提供 Nav 了,而且这些都跟 Article 没啥关系,Article 只关注自己要展现的东西,而且以全屏的方式展现。
事实上很多 Web 站点也开始使用这种方式了。
这样的好处是,独立性很强,复用性也很强。坏处是:如果你“查看元素”,只会吐槽什么鬼东西!
不过思路是 OK 的,不过说实话我更喜欢 webcomponents,这样的结构看起来不是更好么?基本也是一个玩路
缺了那么多的标签,让被惯坏了的前端怎么玩……
别的还好,View 和 Text 这两个东西,基本就要冒充很多标签的行为了:
在 React Native 的世界中,只有一种文本标记:Text。只要你要显示文字,就必须放在 Text 标签中,没的商量。
所以 Text 对于前端同学来说,就约等于 p、div、h1~h6、section、span 等等标签,先吐会儿……
标题基本就是:
<Text style="{某个styles.title}">标题内容</Text>
段落可能就是:
<Text style="{某个styles.paragraph}">标题内容</Text>
我们还是更细一些,在 Web 的世界中,文本可能为(不会出现在移动领域的就不提了,例如鼠标悬浮的 title):
不是简单的一一对应的关系,可能需要不断的组合。并且在 Web 中重新实现 UI 的方式,在这里依然存在,所以有可能对应的就是某个你自行实现的或者引用自外部库的 Component。
在 React Native 中,文本是信息,所以一个 Text 就够了,将文本的组合,或许引入交互,或许引入图片等等 视为 Component,转到组件的世界里去玩,而不是平铺的画板上直接画内容。
在 React Native 中,View 是最基本的视图:
View is a container that supports layout with flexbox, style, some touch handling, and accessibility controls, and is designed to be nested inside other views and to have 0 to many children of any type
所以其实所有的 视图Component 最终都是一个 View 而已。
所以从基础元素的角度来说:View 当前对于我们来说,约等于 div、section 等等所有跟布局相关的元素。
尼玛要用盒模型?要用 flexbox?用 View 吧,还支持无限嵌套的。同时作为容器,全局的行为神马的都往 View 身上扔吧,我就是新的 div,那个爆炸扩散嵌套的 div!
这时候,来说一下 UI 控件,就是在 Web 届很喜欢搞的东东,先看已经提供了的:
当然我们可以把基础的 View 和 Text 砍掉后剩下的东西也视为 ui 控件。
Web 届的 UI 种类如此之多,但我们可以庆幸的是,在 APP 中,解决了一个大问题:控件交互的各异性。APP 中是普适性的 UI 交互,就这一种,都这么用,不用去实现什么3、4、5、6种日历控件神马的了。
在 React Native 中,需要不断的根据需求进行可复用性评估,然后不断的开发出 ui Component、业务 Component、通用 Component 等等,因为我的 APP 就是用这些 Component 拼出来的视图!
而这么多自行定义的 Component,你说你不搞各异性体验的 UI??
2015-08-24 更新
绘图可使用:ReactART,位置:Libraries/ART/ReactNativeART.js
var ReactART = {
LinearGradient: LinearGradient,
RadialGradient: RadialGradient,
Pattern: Pattern,
Transform: Transform,
Path: Path,
Surface: Surface,
Group: Group,
ClippingRectangle: ClippingRectangle,
Shape: Shape,
Text: Text,
};
例子可暂时查看 https://github.com/reactjs/react-page/blob/art/
关于绘图,再单独记录一篇笔记罢:[React Native 浅入门 —— 绘图篇]
Form 没有了,不过还有 tcomb-form-native,不过这东西的使用模式需要适应下……
WebView 有多大的性能问题呢?
当前来说 OC <-> JS 的 Component 映射是不是还是主流模式?
JavaScript引擎是能够将JavaScript代码处理并执行的运行环境,不要把它跟浏览器内核搞混了!内核(如Webkit、Trident、Gecko等)主要是将页面转变成可视化的图像结果,即渲染出来,所以通常也被称为渲染引擎。
渲染引擎和JavaScript引擎是浏览器处理网页过程的两个核心部分,它们是相互合作的关系。
一般来说,渲染引擎根据JavaScript提供的桥接接口提供给JavaScript访问DOM的能力,很多HTML5也是通过桥接接口实现的。
桥接接口复杂而不高效往往是一个性能瓶颈,所以才说DOM的频繁使用会导致性能问题。
Google开源的使用C++编写的引擎,有其他语言(如Java)的封装,BSD
协议,自体还有其他协议。
Chrome
、Chromium
都是使用v8的,另外Android
上的浏览器默认也是使用v8作为js引擎,当然还有Node.js
。
还有个有趣的: TrifleJS,一个使用 .NET WebBrowser Class 及 v8引擎的IE浏览器。
是WebKit的默认JavaScript引擎,它其实是跟着 Webkit 一起发布的,源码在Source/JavaScript,东家是苹果,BSD
/LGPL
。
它有不少别名: SquirrelFish
,SquirrelFish Extreme
,在Safari
中还有Nitro
,Nitro Extreme
,但其实项目、库的名字始终都是JavaScriptCore。
Safari
使用JavaScriptCore,无论是PC还是M。PhantomJS
是基于Webkit
搞出来的,也是使用的JavaScriptCore。
iOS的开发能够直接用 JavaScriptCore 的,一家的。
微软系的引擎,微软以ChakraCore
为名在Github上开放了源代码,MIT
协议。
IE9+浏览器及Edge都是使用Chakra,Win10的应用也是使用了它。
Mozilla系的就是多,标题取了第一个和最新的一个……
SpiderMonkey,第一款JavaScript引擎,由Brendan Eich在Netscape Communications时编写,用于Mozilla Firefox 1.0~3.0版本。
Rhino,由Mozilla基金会管理,开放源代码,完全以Java编写。
TraceMonkey,基于实时编译的引擎,其中部份代码取自Tamarin引擎,用于Mozilla Firefox 3.5~3.6版本。
JaegerMonkey,德文Jäger原意为猎人,结合追踪和组合码技术大幅提高性能,部分技术借凿了V8、JavaScriptCore、WebKit,用于Mozilla Firefox 4.0以上版本。
IonMonkey,可以对JavaScript编译后的结果进行优化,用于Mozilla Firefox 18.0以上版本。
OdinMonkey,可以对asm.js进行优化,用于Mozilla Firefox 22.0以上版本。
自Opera10.50版本开始使用。
三星搞出的一个用于物联网的引擎,可以运行在受限制的设备上,例如微控制器:
只有几 KB RAM 能运行引擎的设备(<64 KB RAM)
只能为代码引擎提供有限 ROM 空间的设备(<200 KB ROM)
该引擎支持设备上编译,提供从 JavaScript 到外设的访问
权当一个笔记,再写写或许更明白点
本不想这么快就写这个的,不过用到了,顺便记录一下。
支持的东西在变化,请查看 /Libraries/StyleSheet/TransformPropTypes.js
确定当前支持的属性。
查看现有官方文档中 View 的 style 支持,会发现这么几个: rotation
scaleX
scaleY
transformMatrix
translateX
translateY
。
但是很不幸滴,如果去看 TransformPropTypes.js
就会发现,这几个已经被标成了:DEPRECATED
。
甭这么用了,就算现在能用也是一样。
不过官方还给了这么一段:
transform: ReactPropTypes.arrayOf(
ReactPropTypes.oneOfType([
ReactPropTypes.shape({perspective: ReactPropTypes.number}),
ReactPropTypes.shape({rotate: ReactPropTypes.string}),
ReactPropTypes.shape({rotateX: ReactPropTypes.string}),
ReactPropTypes.shape({rotateY: ReactPropTypes.string}),
ReactPropTypes.shape({rotateZ: ReactPropTypes.string}),
ReactPropTypes.shape({scale: ReactPropTypes.number}),
ReactPropTypes.shape({scaleX: ReactPropTypes.number}),
ReactPropTypes.shape({scaleY: ReactPropTypes.number}),
ReactPropTypes.shape({translateX: ReactPropTypes.number}),
ReactPropTypes.shape({translateY: ReactPropTypes.number})
])
)
所以实际上已经整体将变形、动画相关的切到了 transform
上。
来看一个简单的例子,看了再对比上面那段,就知道怎么用了:
var styles = StyleSheet.create({
line: {
transform: [
{rotate: '45deg'} // 旋转45度
]
}
});
这些属性都能在 css3 中找到,所以就分类看下好了。
这个说明来自于:W3school
perspective 属性定义 3D 元素距视图的距离,以像素计。该属性允许您改变 3D 元素查看 3D 元素的视图。
当为元素定义 perspective 属性时,其子元素会获得透视效果,而不是元素本身。
注意:perspective 属性只影响 3D 转换元素。
值类型为字符串,例如 '9deg',表示9度。
值类型为数字,表示倍数。
值行为数字:单位应该是坐标轴单位
上面的都是设置了 style,达到一个变形的效果,那么能不能让它们动起来呢?
简单的 View,再设置 state 的做法是不行的,就是一个跳跃的效果。
现存的翻译过的文档都没有详细些的内容,这也是因为动画
部分官方正在开发中,不过已经放出来了,而且E文的文档已经有更新,就翻译一些过来,顺便做点实验:
Animated 库被设计用来简便的实现风格多样的动画行为,同时又有较高的性能。
Animated 主要是通过声明初始/目标值,并且配置过渡的动画方式,然后通过简单的 start/stop 方法来控制动画的执行。
按官方说法要比设置 state 和 prerender 快很多滴。
如例子中的 Animated.Image,默认支持:Image、Text和 View。
不过可以通过 Animated.createAnimatedComponent 方法来创造你想要的 Component。
执行 start
方法开始动画效果,start 可以接受一个 callback
参数,在动画完成时触发执行。
如果动画正常执行结束,callback 会接收到一个参数 {finished: true}
,如果是非正常结束,例如被手势或其他动画打断了,finished 则为 false。
主要是使用 componentDidMount
class SampleAnimation extends Component {
constructor(props: any) {
super(props);
this.state = {
bounceValue: new Animated.Value(0)
};
}
render(): ReactElement {
return (
<Animated.View // 支持: Image, Text, View
style={
{
flex: 1,
transform: [
{scale: this.state.bounceValue}
]
}
}
/>
);
}
componentDidMount() {
this.state.bounceValue.setValue(1.5); // 目标值
Animated.spring( // 支持: spring, decay, timing,过渡的动画方式
this.state.bounceValue,
{
toValue: 0.8, // 目标值
friction: 1 // 动画方式的参数
}
).start(); // 开始
}
}
可以使用以下方法来进行组合:
这四个方法都接受数组参数,元素内容是动画效果。
如果任何一个动画被 stoped,则其他动画也会终止。
Parallel有一个参数:stopTogether,如果被设置为 false,则不会执行这种终止的效果。
Animated.sequence([ // spring to start and twirl after decay finishes
Animated.decay(position, { // coast to a stop
velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
deceleration: 0.997,
}),
Animated.parallel([ // after decay, in parallel:
Animated.spring(position, {
toValue: {x: 0, y: 0} // return to start
}),
Animated.timing(twirl, { // and twirl
toValue: 360,
}),
]),
]).start(); // start the sequence group
interpolate
例如:[0, 1]映射[0, 100]为:
value.interpolate({
inputRange: [0, 1],
outputRange: [0, 100],
});
同样支持多重值域:
value.interpolate({
inputRange: [-300, -100, 0, 100, 101],
outputRange: [300, 0, 1, 0, 0],
});
这意味着:
-300 => 300
-200 150
-100 0
-50 0.5
0 1
50 0.5
100 0
101 0
这几个很容易理解,主要是边界值:
最左侧的[-300, -100]对应着[300, 0],这意味着,这种映射会一直向负数方向延伸下去,因此 -400 => 450
最右侧是[100, 101]对应这[0, 0],这意味着此后的数目都对应0,即 200 => 0
interpolate
支持动画计算函数,很多已经在 Libraries/Animation/Animated/Easing.js
中定义了。
interpolate
可配置,默认是 extend
,不过 clamp
在防止输出超出值域上同样十分有用。
输入事件,可以让手势或其他事件来直接对应动画的值,这当然需要一个结构化的数据才能支持:
第一层是数组,元素是对象:
scrollX 对应着 event.nativeEvent.contentOffset.x
pan.x and pan.y 对应着 gestureState.dx 和 gestureState.dy
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}] // scrollX = e.nativeEvent.contentOffset.x
)}
onPanResponderMove={Animated.event([
null, // ignore the native event
{dx: pan.x, dy: pan.y} // extract dx and dy from gestureState
]);
允许全局的进行动画的创建和更新,这会在下次的渲染时执行:
var App = React.createClass({
componentWillMount() {
// Animate creation
LayoutAnimation.spring();
},
getInitialState() {
return { w: 100, h: 100 }
},
_onPress() {
// Animate the update
LayoutAnimation.spring();
this.setState({w: this.state.w + 15, h: this.state.h + 15})
},
render: function() {
return (
<View style={styles.container}>
<View style={[styles.box, {width: this.state.w, height: this.state.h}]} />
<TouchableOpacity onPress={this._onPress}>
<View style={styles.button}>
<Text style={styles.buttonText}>Press me!</Text>
</View>
</TouchableOpacity>
</View>
);
}
});
一般来说用不到这个 来自于 browser 的 API,Animations 基本封装好了。
不过,我还是来个简单的例子吧,一个超级简陋的时钟秒针的动画(这结合了绘图篇的知识):
class Timing extends Component {
constructor(props) {
super(props);
this.state = {
secondRotation: (new Date()).getSeconds() * 6;
};
}
animate() {
var secondRotation = (new Date()).getSeconds() * 6;
this.setState({secondRotation});
requestAnimationFrame(this.animate.bind(this));
}
componentDidMount() {
requestAnimationFrame(this.animate.bind(this));
}
render() {
return (
<View>
<Surface
width={300}
height={400}
style={
{backgroundColor: 'blue'}
}>
<Group x={150} y={150}>
<Shape stroke="#000000" strokeWidth={4} d={line} rotation={this.state.secondRotation} />
</Group>
</Surface>
</View>
);
}
}
to be continued.
个人还是很喜欢这种机制,也想搞一个这般的 Workflow 辅助类出来,不过不想用 Generator,所以折腾了一小下……
yield*
最大的作用,就是将Generator
嵌在另一个Generator
的内部执行。
function *a() {
console.log(1);
b();
console.log(3);
}
function *b() {
console.log(2);
}
执行结果是13
,是的,2不会被输出,但是如果换成:
yield *b();
就等同于:
function *a() {
console.log(1);
console.log(2);
console.log(3);
}
输出就是123
就好了。
而这也是koa2的中间件核心实现机制,笑话一下自己最开始搞出来的遍历行为,傻在next
上了,囧~~
如果还不明白,那么就看这个:
function *a() {
console.log(1);
yield* b();
console.log(5);
}
function *b() {
console.log(2);
yield* c();
console.log(4);
}
function *c() {
console.log(3);
}
循环内嵌之后,你只需要遍历a的Iterator就行了,会展开嵌入后面所有的Generator
!!
所以核心问题就变成了,如何定义出所需要的方法a而已。
koa的middlewares
是一个数组,为了能够得到要访问的方法,其实就是middlewares[0]的封装。我们知道每个async function
其实就是一个Generator function
,那么我只需要把参数next
指向下一个中间件函数,并且确保调用时使用yield*
去调用,并且参数绑定ctx和下一个next。
所以我需要的可能是这样的:
let nextc = function* () {
console.log(3);
}
let nextb = function* () {
console.log(2);
yield* nextc();
console.log(4);
}
let nexta = function* () {
console.log(1);
yield * nextb();
console.log(5);
}
nexta().next(); // 执行,输出 1 2 3 4 5
再换一下,用async/await:
// 注意这里我开始引入 next 了
async function a(next) {
console.log(1);
await next();
console.log(5);
}
async function b(next) {
console.log(2);
await next();
console.log(4);
}
async function c(next) {
console.log(3);
}
// 然后我们来手动定义next
let nextc = async function () {
c.call(this);
};
let nextb = async function () {
b.call(this, nextc);
}
let nexta = async function () {
a.call(this, nextb);
}
// 执行
nexta();
// 输出了 1 2 3 4 5
发现规律了么……
nexta调用的是函数a,将nextb作为next参数传入,而nextb调用b,将nextc作为参数传入,同理继续。最后一个指向空就行了,啥也不干。
那么:
function compose(middlewares) {
let i = middlewares.length;
let next = async function(){}; // noop
// 循环算出来next
for (let i = middlewares.length - 1; i >=0; i--) {
next = getCallback(middlewares[i], next);
}
return next;
}
function getCallback(method, next) {
return async function() {
method(next);
};
}
let callback = compose([a, b, c]);
callback(); // 输出1 2 3 4 5
貌似大功告成?还得继续……
中间件是需要传入数据的,简单理解为:一个数据要从第一个中间件进入,最终从第一个中间件的after出来,核心目的是改变这个数据,因此在当前的模式下,最好不要传值,我们来看:
app.use(async (ctx, next) => {
// do sth with ctx
await next();
// do sth with ctx after
});
根据上面的调用模式,如果一直是值引用,那么就要 return 处理后的值,与代码中的await next()
不符了,而且使用还会变得麻烦,所以koa选择使用context的概念,传入一个Object
的引用,然后一直去修改它。
那么我们接下来要做的事情,其实就是支持 ctx 参数了:
// 定义中间件,这看起来就跟 koa 一样了是吧
async function a(ctx, next) {
ctx.output.push(1);
await next();
ctx.output.push(5);
}
async function b(ctx, next) {
ctx.output.push(2);
await next();
ctx.output.push(4);
}
async function c(ctx, next) {
ctx.output.push(3);
}
// 定义compose,因为参数的位置变化了,所以下面的执行也要修改一下了
// 最简单的方式,加一个参数
function compose(middlewares, ctx) {
let i = middlewares.length;
let next = async function(){}; // noop
// 循环算出来next
for (let i = middlewares.length - 1; i >=0; i--) {
next = getCallback(middlewares[i], ctx, next);
}
return next;
}
function getCallback(method, ctx, next) {
return async function() {
method(ctx, next);
};
}
let ctx = {output: []};
let callback = compose([a, b, c], ctx);
await callback();
console.log(ctx.output); // 输出1 2 3 4 5
能不能更简便些呢?
好吧,反正我还是没看明白 koa 的 compose 结果,怎么被使用的,直接传一个 ctx 作为参数就达到效果了。
我其实怀疑是 koa-convert 搞的鬼,默认都是 Generator,都被转换成 async 了,然后 call 了ctx 和 next。
应该是这样吧……
Iterator
为不同的数据结构提供统一的访问机制,只要它部署Iterator接口,就可以完成遍历操作。
它的流程其实是跟链表的遍历很相似的:创建一个指针对象,指向当前数据结构的起始位置,然后不断的next()
,每次返回的是一个对象数据,包含value和done两个字段供访问。
for...of
循环实际上会去自动找Iterator
接口。
ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator
这个key上,一个数据结构只要具有Symbol.iterator
方法,就可以认为是“可遍历的”。
再次提醒,千万注意:Symbol.iterator
里面的i是小写!!!
跟其他语言一样,提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
原生的表述数据集的类型:Array
、Object
、Map
、Set
。
而实际上原生支持Iterator
的数据结构是:Array
、Map
、Set
。
Object
默认不支持,其实非要手写支持的话,还不如直接用Map
。
字符串也支持,因为它类似于Array
Symbol.iterator
是一个函数,返回的是一个遍历器,执行next(),不断的返回包含value和done两个字段的对象数据。
例如arguments
,对于这种,其实我们可以直接使用Array
的Iterator
接口:
let obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
如之前所述,能否支持遍历,仅是检查Symbol.iterator
这个key而已,所以普通的对象也能支持Iterator
,单还不如用Set
呢
普通对象部署Iterator
let obj = {
a: 'a',
b: 'b',
[Symbol.iterator]() {
const self = this;
const keys = Object.keys(self); // 举个例子而已
let i = 0;
return {
next() {
i++;
return {
done: i >= keys.length,
value: self[keys[i]]
};
}
}
}
};
需要注意的是,Iterator
的作用是使得数据结构的成员能够按某种次序排列,所以偶尔蛋疼的这么设定,可能仅仅是为了定义一个复杂的访问顺序,但是使用的时候仅仅for...of
就好了。
因此也不能强说这么做是不对的。
不过,对于这种,用类不是更好一点么?例如:
class ListIteratorByAge {
constructor(list) {
this.list = list;
}
[Symbol.iterator]() {
const self = this;
const arr = self.list.slice(0).sort((a, b) => a.age >= b.age);
let i = -1;
return {
next() {
i++;
return {
done: i >= arr.length,
value: arr[i]
};
}
}
}
}
注意这里的Symbol.iterator
的定义,能不能有更简便的方法呢:
class ListIteratorByAge {
constructor(list) {
this.list = list;
}
[Symbol.iterator]() {
const arr = self.list.slice(0).sort((a, b) => a.age >= b.age);
return arr[Symbol.iterator]();
}
}
再次强调,只是在定义某种次序的访问
,没规定要怎么访问!!只是在默认支持的数据结构中按顺序访问了而已。
一个经典的硬性使用Iterator的示例:
let arr = [1, 4, 2, 10];
let ite = arr[Symbol.iterator]();
let eachResult = ite.next(); // 调用一次访问到第一个元素
while(!eachResult.done) {
// do sth with eachResult.value
eachResult = ite.next();
}
其实直接用for...of
更简便:
let arr = [1, 4, 2, 10];
for (value of arr) {
// do sth with value
}
总结自 ECMAScript6 入门
let [x,y] = set
['a', ...arr, 'd']
yield* [2,3,4];
简而言之,结合着上面的Iterator
,可以理解Generator
实际上是生成了一个可遍历的结果数据集,数据集的元素有哪些,由yield
和return
定义,函数执行的结果就是数据集的Iterator
。
例如:
function* testGenerator() {
yield 1;
yield 2;
return 3;
}
let ite = testGenerator();
那么不断执行ite.next()
并以!ite.next().done
为判断条件的执行结果为:1,2,3
。
next
可以传入值,作为执行时上一个yield
的执行返回结果:
function* counterGen() {
let i = 0;
while(1) {
let signal = yield i++;
// console.log('now is ' + i);
if (signal) {
i = 0;
}
}
}
let counter = counterGen();
counter.next(); // {value: 0, done: false}
...
counter.next(true); // {value: 0, done: false} 重置了
需要注意的是,在执行next(true)时,counterGen 中那个注释掉的console语句中,i其实是6,后面置的0。
执行的最终结果中,value是0。
也就是说,yield的结果其实是可以被后面的语句给改掉的,或者可以将例子中while语句{}包起来的部分认为是一个函数,最后返回i是最终的结果。
也可以这么理解:执行到下一个yield
时,才返回了之前的结果,大概、或许是这样吧……
总结一小下:
*
yield
定义一个可被遍历到的元素(状态)return
也会被认为是一个可被遍历到的元素(状态),如果没有return
或没跟返回值,value会是undefined
。yield
之前都可以本来不想说这个,感觉要变成基础知识集了,不过研究中间件偏偏遇到了,不得不说……
yield*
最大的作用,就是将Generator
嵌在另一个Generator
的内部执行。
function *a() {
console.log(1);
b();
console.log(3);
}
function *b() {
console.log(2);
}
执行结果是13
,是的,2不会被输出,但是如果换成:
yield *b();
就等同于:
function *a() {
console.log(1);
console.log(2);
console.log(3);
}
输出就是123
就好了。
而这也是koa2的中间件核心实现机制,笑话一下自己搞出来的遍历行为,然后傻在next
的调用上了,囧~~
可以查看
建议查看 ECMAScript6 入门
语法糖,是对Generator
的改进。
返回的结果是一个Promise
。
只需注意几点就行了。
Promise
,所以按Promise
的错误处理就行了async
内部的await
指定的状态,是同步
的,全都成功整体状态才会成功,任何一个失败了,整体也就失败了(后面的不执行了),除非用try...catch包起来或者用.catch处理一下let body = '';
for await(const data of req) body += data;
const parsed = JSON.parse(body);
console.log('got', parsed);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.