SegmentFault QETHAN最新的文章
2020-03-26T22:32:07+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
Vue.js render函数那些事儿
https://segmentfault.com/a/1190000022162951
2020-03-26T22:32:07+08:00
2020-03-26T22:32:07+08:00
前端知否
https://segmentfault.com/u/qethan
13
<p>大多时候,我会使用template, vue单文件去渲染组件。虽然知道Vue中有个render函数,但却很少在项目中去主动使用它。使用最多的地方是在使用一些UI框架的时候,比如iview table中的按钮操作,会使用到render函数。另外平时在阅读一些Vue UI框架源码的时候,也时常能遇到使用render函数的地方,这也激发了自己研究学习的欲望。如果你也感兴趣,那就继续阅读吧。</p>
<p>在本文中,会有如下内容:</p>
<ul>
<li>什么是Vue render函数</li>
<li>Vue编译器如何处理render函数</li>
<li>创建一个组件</li>
<li>在render函数中使用指令</li>
<li>Vue渲染函数中的事件绑定</li>
<li>模板覆盖的实际用例</li>
</ul>
<p>让我们开始吧!</p>
<h2>什么是Vue render函数</h2>
<p>Vue.js模板功能强大,几乎可以满足我们在应用程序中所需的一切。但是,有一些场景下,比如基于输入或插槽值创建动态组件,render函数可以更好地满足这些用例。</p>
<p>那些来自React世界的开发者可能对render函数非常熟悉。通常在<code>JSX</code>中使用它们来构建React组件。虽然Vue渲染函数也可以用JSX编写,但我们将继续使用原始JS,有助于我们可以更轻松地了解Vue组件系统的基础。。</p>
<p>每个Vue组件都实现了一个render函数。大多数时候,该函数将由<strong>Vue编译器</strong>创建。当我们在组件上指定模板时,该模板的内容将由Vue编译器处理,编译器最终将返回render函数。渲染函数本质上返回一个虚拟DOM节点,该节点将被Vue在浏览器DOM中渲染。</p>
<p>现在又引出了虚拟DOM的概念,"虚拟DOM到底是什么?"</p>
<p>虚拟文档对象模型(或"DOM")允许Vue在更新浏览器之前在其内存中渲染组件。<strong>这使一切变得更快,同时也避免了DOM重新渲染的高昂成本。因为每个DOM节点对象包含很多属性和方法,因此使用虚拟DOM预先在内存进行操作,可以省去很多浏览器直接创建DOM节点对象的开销。</strong></p>
<p>Vue更新浏览器DOM时,会将更新的虚拟DOM与上一个虚拟DOM进行比较,并仅使用已修改的部分更新实际DOM。这意味着更少的元素更改,从而提高了性能。Render函数返回虚拟DOM节点,在Vue生态系统中通常称为<strong>VNode</strong>,该接口是允许Vue在浏览器DOM中写入这些对象的接口。它们包含使用Vue所需的所有信息。 <strong>Vue的下一版本将包含一个全新的虚拟DOM实现,该实现将比目前更快。</strong> React,Riot,Inferno等许多其他框架也使用虚拟DOM的概念。</p>
<p><img src="/img/remote/1460000022162954" alt="" title=""></p>
<p>我们可以在任何Vue组件中实现Vue render函数。同样,由于Vue的数据响应性,每当组件的数据得到更新时,都会再次调用render函数。</p>
<p>这是一个简单的示例,说明如何直接使用组件中的render函数去渲染h1标签:</p>
<pre><code>new Vue({
el: '#app',
render(createElement) {
return createElement('h1', 'Hello world');
}
});</code></pre>
<p>有一些内置组件可以利用渲染函数的功能,例如<strong>transition</strong>和<strong>keep-alive</strong>。这些组件直接在渲染函数中操纵VNode。如果Vue没有提供这个函数特性,这些功能将无法实现。</p>
<h2>Vue编译器如何处理render函数?</h2>
<p>大多数时候,Vue渲染函数将在项目构建期间由Vue编译器进行编译(例如,使用Webpack)。因此,编译器不会最终出现在您的生产代码中,从而减小了包的体积。这就是为什么当您使用"单个文件组件"时,除非我们确实需要/想要,否则您实际上不需要使用render函数。</p>
<p>但是,如果我们想在代码中使用编译器,则可以使用带有编译器的Vue版本。简而言之,我们正在使用Vue编译器来编译自定义模板。假设我们在做一个电商项目,那么可以将其注入购物车,从而可以拥有更多的控制。</p>
<p>我们编写了一个实现自定义渲染功能的组件,该功能可获取用户创建的模板并替换我们的默认模板。</p>
<p>这是一个如何使用编译器将模板字符串编译为渲染函数的快速示例:</p>
<pre><code>const template = `
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>`;
const compiledTemplate = Vue.compile(template);
new Vue({
el: '#app',
data() {
return {
items: ['Item1', 'Item2']
}
},
render(createElement) {
return compiledTemplate.render.call(this, createElement);
}
});</code></pre>
<p>如您所见,编译器将返回一个<strong>对象(compiledTemplate)</strong>,其中包含准备使用的render函数。</p>
<h2>创建一个组件</h2>
<p>具有渲染功能的组件没有模板标记或属性。相反,他们定义了一个称为render的函数,该函数接收一个<code>createElement(renderElement:String | Component,define:Object,children:String | Array)</code>参数(由于某种原因,通常别名为h,归咎于JSX)并返回使用该函数创建的元素。其他一切保持不变。</p>
<pre><code>export default {
data() {
return {
isRed: true
}
},
/*
* 和下边使用template相同
* <template>
* <div :class="{'is-red': isRed}">
* <p>Example Text</p>
* </div>
* </template>
*/
render(h) {
return h('div', {
'class': {
'is-red': this.isRed
}
}, [
h('p', 'Example Text')
])
}
}</code></pre>
<h2>在render函数中使用指令</h2>
<p>Vue模板具有各种便捷功能,以便向模板添加基本逻辑和绑定功能。渲染函数无法访问它们。取而代之的是,它们必须以纯Javascript实现,对于大多数指令而言,这是相当简单的。</p>
<h3>v-if</h3>
<p>这个很简单。除了使用v-if之外,还可以在<code>createElement</code>中调用普通的<code>if(expr)</code>语句。</p>
<pre><code><ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p></code></pre>
<p>这些都可以在渲染函数中用JavaScript的<code>if/else 和 map</code>来重写:</p>
<pre><code>props: ['items'],
render: function (h) {
if (this.items.length) {
return h('ul', this.items.map(function (item) {
return h('li', item.name)
}))
} else {
return h('p', 'No items found.')
}
}</code></pre>
<h3>v-for</h3>
<p>v-for可以使用for-of,Array.map,Array.filter等多种迭代方法中的任何一种来实现。我们可以将它们以非常有趣的方式组合在一起,以实现过滤或分割数据。</p>
<p>例如,我们可以替换</p>
<pre><code><template>
<ul>
<li v-for="item of list">
</li>
</ul>
</template></code></pre>
<p><strong>为</strong></p>
<pre><code>render(h) {
return h('ul', this.list.map(item => h('li', item.name)));
}</code></pre>
<h3>v-model</h3>
<p>要记住的一件事是,<code>v-model</code>就是绑定属性为value(还可以是其他属性),只要触发input事件并设置data属性的简写形式。不幸的是,渲染函数没有这么简写。我们必须自己实现它,如下所示。</p>
<pre><code>render(h) {
return h('input', {
domProps: {
value: this.myBoundProperty
},
on: {
input: e => {
this.myBoundProperty = e.target.value
}
}
})
}</code></pre>
<p><strong>等效于:</strong></p>
<pre><code><template>
<input :value="myBoundProperty" @input="myBoundProperty = $event.target.value"/>
</template></code></pre>
<p><strong>或</strong></p>
<pre><code><template>
<input v-model="myBoundProperty"/>
</template></code></pre>
<h3>v-bind</h3>
<p>属性和属性绑定以<code>attrs,props和domProps</code>(类似于value和innerHTML之类)的形式放置在元素定义中。</p>
<pre><code>render(h) {
return h('div', {
attrs: {
// <div :id="myCustomId">
id: this.myCustomId
},
props: {
// <div :someProp="someValue">
someProp: this.someValue
},
domProps: {
// <div :value="someValue">
value: this.someValue
}
});
}</code></pre>
<p>附带说明一下,类和样式绑定直接在定义的根进行处理,而不是作为attrs,props或domProps处理。</p>
<pre><code>render(h) {
return h('div', {
// "class" 是JS中的保留字, 所以需要引号包起来.
'class': {
myClass: true,
theirClass: false
},
style: {
backgroundColor: 'green'
}
});
}</code></pre>
<h3>v-on</h3>
<p>事件处理程序也可以直接在<code>on</code>(或<code>nativeOn</code>,与组件的<code>v-on.native</code>效果相同)中直接添加到元素定义中。</p>
<pre><code>render(h) {
return h('div', {
on: {
click(e) {
console.log('我点击了!')
}
}
});
}</code></pre>
<ol><li>修饰符可以在处理程序内部实现:</li></ol>
<ul>
<li>.stop -> e.stopPropagation()</li>
<li>.prevent -> e.preventDefault()</li>
<li>.self -> if (e.target !== e.currentTarget) return</li>
</ul>
<ol><li>键盘修饰符</li></ol>
<ul>
<li>.[TARGET_KEY_CODE] -> if (event.keyCode !== TARGET_KEY_CODE) return</li>
<li>.[MODIFIER] -> if (!event.MODIFIERKey) return</li>
</ul>
<h3>插槽</h3>
<p>可以通过<code>this.$slots</code>作为createElement()节点的数组来访问插槽。</p>
<pre><code><div id="app">
<myslot>
<div slot="slot1">this is slot1</div>
<div slot="slot2">This is slot2 </div>
</myslot>
<script>
var app = new Vue({
el: '#app'
})
</script></code></pre>
<pre><code>Vue.component('my-slot', {
render: function(h) {
const child = h('div', {
domProps: {innerHTML: 'this is child'}
});
return h(
'div',
[
child,
this.$slots.slot1,
this.$slots.slot2
]
)
}
})</code></pre>
<p>作用域插槽存储在<code>this.$scopedSlots[scope](props:object)</code>中,作为返回createElement()节点数组的函数。</p>
<pre><code><div id="app">
<myslot>
<template scope="props">
<div>{{props.text}}</div>
</template>
</myslot>
<script>
var app = new Vue({
el: '#app'
})
</script></code></pre>
<pre><code>Vue.component('my-slot', {
render: function(h) {
const child = h('div', {
domProps: {innerHTML: 'this is child'}
});
return h(
'div',
[
child,
this.$scopedSlots.default({
text: 'hello scope'
})
]
)
}
})</code></pre>
<h2>Vue渲染函数中的事件绑定</h2>
<p><code>createElement</code>函数可以接收称为<code>数据对象</code>的参数。该对象可以具有多个属性,这些属性与我们在标准模板中使用的<code>v-bind:on</code>等指令等效。这是带有按钮的简单计数器组件的示例,该按钮可以增加点击次数。</p>
<pre><code>new Vue({
el: '#app',
data() {
return {
clickCount: 0,
}
},
methods: {
onClick() {
this.clickCount += 1;
}
},
render(h) {
const button = h('button', {
on: {
click: this.onClick
}
}, 'Click me');
const counter = h('span', [
'Number of clicks:',
this.clickCount
]);
return h('div', [
button, counter
])
}
});</code></pre>
<p>但是数据对象不限于事件绑定!我们也可以像使用<code>v-bind:class</code>指令一样将CSS类应用于元素。</p>
<pre><code>new Vue({
el: '#app',
data() {
return {
clickCount: 0,
}
},
computed: {
backgroundColor() {
return {
'pink': this.clickCount%2 === 0,
'green': this.clickCount%2 !== 0,
};
}
},
methods: {
onClick() {
this.clickCount += 1;
}
},
render(h) {
const button = h('button', {
on: {
click: this.onClick
}
}, 'Click me');
const counter = h('span', {
'class': this.backgroundColor,
}, [
'Number of clicks:',
this.clickCount
]);
return h('div', [
button, counter
])
}
});</code></pre>
<h2>模板覆盖的实际用例</h2>
<p>我认为了解Vue的幕后工作非常有趣。要知道是否能够最有效地使用工具,唯一的方法是确切地了解它的工作方式。</p>
<p>这并不是说我们应该开始将所有模板都转换为render函数,但是有时它们可以派上用场,所以我们至少应该知道如何使用它们。</p>
<p>在上面的示例中,我展示了如何在组件中使用自定义render函数,该函数允许我们的某些组件可重写。</p>
<p>首先,让我们创建初始模板。</p>
<pre><code><div id="app">
<heading>
<span>Heading title is: {{ title }}</span>
</heading>
</div></code></pre>
<p>在将要安装Vue应用程序的div内,我们编写一个自定义模板。接下来,我们希望模板覆盖我们将要创建的标题组件的默认版本。</p>
<p>我们要做的第一件事是<strong>遍历自定义模板,并使用Vue编译器对其进行预编译</strong>:</p>
<pre><code>const templates = [];
const templatesContainer = document.getElementById('app');
// 我们遍历每个自定义模板并对其进行预编译
for (var i = 0; i < templatesContainer.children.length; i++) {
const template = templatesContainer.children.item(i);
templates.push({
name: template.nodeName.toLowerCase(),
renderFunction: Vue.compile(template.innerHTML),
});
}</code></pre>
<p>然后,让我们创建标题组件:</p>
<pre><code>Vue.component('heading', {
props: ['title'],
template: `
<overridable name="heading">
<h1>
{{ title }}
</h1>
</overridable>`
});</code></pre>
<p>现在,它只是一个简单的组件,具有一个名为title的属性。默认模板将渲染带有标题的<strong>h1</strong>。我们将用随后创建的<code>overridable</code>组件包装该组件。</p>
<p>这是我们将使用自定义渲染功能的地方。</p>
<pre><code>Vue.component('overridable', {
props: ['name'],
render(createElement) {
// 我们使用在初始化应用程序时创建的模板数组
const template = templates.find(x => x.name === this.name);
// 当没有自定义模板时,我们将使用默认插槽返回默认内容。
if (!template) {
return this.$slots.default[0];
}
// 使用预编译的render函数
return template.renderFunction.render.call(this.$parent, createElement);
}
});</code></pre>
<p>然后,让我们挂载Vue应用程序:</p>
<pre><code>new Vue({
el: '#app',
template: `<heading title="Hello world"></heading>`
});</code></pre>
<p>在此示例中,我们可以看到使用了默认模板,因此它是一个标准的<code>h1</code>标签。</p>
<p><img src="/img/remote/1460000022162956" alt="" title=""></p>
<p>如果将自定义模板添加到<strong>div#app</strong>内,则会看到标题组件会被渲染成我们指定的自定义模板。</p>
<p><img src="/img/remote/1460000022162955" alt="" title=""></p>
<h2>最后</h2>
<p>如果使用render函数创建组件,让你感觉非常繁琐。那么还可以尝试<code>babel-plugin-transform-vue-jsx</code>插件,就可以像在React中那样轻便(属性细节略有不同,具体参考插件文档)。</p>
<p>总体而言,使用render函数非常有趣,并且在v3.0中也派上了用场。 Vue渲染函数是Vue本身的基本组成部分,因此,我真的认为花一些时间并彻底理解该概念(特别是长期使用该框架)很有价值。随着Vue.js的发展和效率的提高,我们平时积累的这些底层基础知识也有助于我们的发展。</p>
<p>换句话说,了解Vue render函数只是你技术进步中的一小步,但很重要。 :)<br><img src="/img/bVbxovn" alt="扫码_搜索联合传播样式-标准色版.png" title="扫码_搜索联合传播样式-标准色版.png"></p>
ES10的13个新特性示例
https://segmentfault.com/a/1190000020460927
2019-09-22T21:52:34+08:00
2019-09-22T21:52:34+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p><img src="/img/remote/1460000020460930" alt="" title=""></p>
<h2>介绍</h2>
<p>ES10是与2019年相对应的ECMAScript版本。这个版本中的新功能没有ES6(2015)中的那么多。但是,也不乏一些有用的功能。</p>
<p>本文在简单的代码示例中介绍了ES10提供的功能。这样,您无需复杂的解释即可快速了解新功能。</p>
<p>当然,需要具备JavaScript的基础知识才能完全理解所介绍的新功能。</p>
<p>ES2019中的JavaScript新功能包括:</p>
<ul>
<li>Array#{flat,flatMap}</li>
<li>Object.fromEntries</li>
<li>String#{trimStart,trimEnd}</li>
<li>Symbol#description</li>
<li>try { } catch {} // 可选的错误参数绑定</li>
<li>JSON ⊂ ECMAScript</li>
<li>格式良好的 JSON.stringify</li>
<li>稳定的排序 Array#sort</li>
<li>新版 Function#toString</li>
<li>新增 BigInt 原始类型 (stage 3).</li>
<li>动态引入模块(stage 3).</li>
<li>标准的 globalThis 对象 (stage 3).</li>
<li>ES10 Class: private, static & public (stage 3).</li>
</ul>
<hr>
<h2>Array.flat() & Array.flatMap()</h2>
<p>两个新的数组方法:</p>
<p>Array.flat() 方法创建一个新数组,所有子数组元素都以递归方式合并到该数组中,直至达到指定深度。</p>
<p>Array.flatMap() 方法首先使用map函数转换每个元素,然后将结果展平为新数组。它与map()后再调用深度为1的flat() 效果相同,但是flatMap()将两者合并为一种方法,效率更高。</p>
<p><img src="/img/remote/1460000020460931" alt="" title=""></p>
<h2>Object.fromEntries()</h2>
<p>把键值对数组为元素的二维数组转换为一个对象。</p>
<p><img src="/img/remote/1460000020460932" alt="" title=""></p>
<h2>String.protype.matchAll()</h2>
<p>matchAll() 方法返回所有与正则表达式匹配字符串的结果的迭代器,包括捕获组。</p>
<p><img src="/img/remote/1460000020460933" alt="" title=""></p>
<h2>String.trimStart() & String.trimEnd()</h2>
<p>有两种新的String方法可从字符串中删除空格:</p>
<p>trimStart() 方法从字符串的开头删除空格。<br>trimEnd() 方法从字符串末尾删除空格。</p>
<p><img src="/img/remote/1460000020460934" alt="" title=""></p>
<h2>Symbol.Description</h2>
<p>当创建符号时,可以提供一个字符串作为描述。在ES10中,有一个获取描述的访问器。</p>
<p><img src="/img/remote/1460000020460935" alt="" title=""></p>
<h2>可选的 Catch 参数变量</h2>
<p>过去,try / catch语句中的catch子句需要一个变量。现在,它允许开发人员使用try / catch而不创建未使用的error变量绑定。</p>
<p><img src="/img/remote/1460000020460936" alt="" title=""></p>
<h2>JSON⊂ECMAScript</h2>
<p>在ES10之前的版本中,不接受非转义的行分隔符U+2028和段落分隔符U+2029。</p>
<p>U+2028是段落分隔符。<br>U+2029是行分隔符。</p>
<p><img src="/img/remote/1460000020460937" alt="" title=""></p>
<h2>格式良好的 JSON.stringify()</h2>
<p>JSON.stringify() 可能返回U+D800和U+DFFF之间的字符,来作为没有等效UTF-8字符的值。但是,JSON格式需要UTF-8编码。解决方案是,将未配对的替代代码点表示为JSON转义序列,而不是将其作为单个UTF-16代码单元返回。</p>
<p><img src="/img/remote/1460000020460938" alt="" title=""></p>
<h2>Array.prototype.sort()</h2>
<p>V8的先前实现,对包含10个以上项的数组使用了不稳定的快速排序算法。</p>
<blockquote>一种稳定的排序算法是,当两个具有相同键的对象在排序输出中出现的顺序,与未排序输入中出现的顺序相同。</blockquote>
<p><img src="/img/remote/1460000020460939" alt="" title=""></p>
<h2>新版 Function.toString()</h2>
<p>toString() 方法返回一个表示函数源代码的字符串。在ES6中,当在函数上调用toString时,它将根据ECMAScript引擎返回该函数的字符串表示形式。如果可能,它将返回源代码,否则-一个标准化的占位符。</p>
<p><img src="/img/remote/1460000020460940" alt="" title=""></p>
<h2>BigInt — 任意精度的整数</h2>
<p>BigInt是第7个原始类型,它是一个任意精度的整数。而不仅仅是在9007199254740992处的最大值。</p>
<p><img src="/img/remote/1460000020460941" alt="" title=""></p>
<h2>动态引入</h2>
<p>动态import()返回所请求模块的Promise。因此,可以使用async/await 将导入的模块分配给变量。</p>
<p><img src="/img/remote/1460000020460942" alt="" title=""></p>
<p><img src="/img/remote/1460000020460943" alt="" title=""></p>
<h2>标准 globalThis 对象</h2>
<p>全局 this 在ES10之前尚未标准化。在生产代码中,您可以通过编写下边代码来“标准化”它:</p>
<p><img src="/img/remote/1460000020460944" alt="" title=""></p>
<h2>ES10 Class: private, static & public 成员变量,函数</h2>
<p>现在,新的语法字符#(哈希标签)用于直接在类中定义变量,函数,getter和setter,以及构造函数和类方法。</p>
<p><img src="/img/remote/1460000020460945" alt="" title=""></p>
<h2>总结</h2>
<p>自2015年ES6出现以来,这个语言就一直处于高速发展的状态。在这篇文章中,我们回顾了ES10(2019)中出现的功能,并介绍了一些在ES11(2020)中将保持稳定的功能,因为它们处于状态3,并且可能最终会在下一版中实现标准化。</p>
<p>尽管这些功能中的许多功能对于Web应用程序的开发可能不是必需的,但是它们提供了通过技巧或大量冗长代码才能实现的可能性。</p>
<p><img src="/img/remote/1460000020460946" alt="" title=""></p>
<p><img src="/img/remote/1460000020460947" alt="" title=""></p>
Flutter必备语言Dart教程04 - 异步,库
https://segmentfault.com/a/1190000020442316
2019-09-20T11:30:48+08:00
2019-09-20T11:30:48+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p><img src="/img/remote/1460000020442319" alt="" title=""><br>现在我们来看看如何在Dart中处理异步代码。使用Flutter时,会执行各种操作,例如网络调用和数据库访问,这些操作都应该异步执行。</p>
<h2>在Dart中导入库</h2>
<p>在Dart中使用异步,需要先导入异步库。</p>
<p><img src="/img/remote/1460000020442320" alt="" title=""></p>
<h2>Future</h2>
<p>异步库包含一个名为Future的类,Future是基于观察者模式的。如果您熟悉Javascript中的Rxjs或Promises,那么理解起来会很容易。</p>
<p>简单来说,Future定义的是“未来”发生的事情,也会在未来某个时刻返回一个值给我们。让我们看看如何使用Future。</p>
<p>Future是一个泛型类型,即 Future <T>,你必须指定返回值的类型。</p>
<p><img src="/img/remote/1460000020442321" alt="" title=""><br>我们定义了一个名为getAJoke的函数,它返回一个Future <String>。使用new关键字创建Future,Future构造函数接收一个返回值类型为T的函数参数。无论您在匿名函数中返回什么,都会被转化为Future。</p>
<p>在main中,我们调用getAJoke函数,该函数返回 Future<String>。我们通过调用then函数来订阅Future,这些函数注册了一个回调,当Future发出值时调用它。我们还注册了一个catchError来处理在执行Future期间发生的任何异常。在我们的示例中,我们没有发生任何异常。</p>
<p>以下是发生异常的示例。</p>
<p><img src="/img/remote/1460000020442322" alt="" title=""><br>在这个例子中,结果会立即返回。但在实际业务中,会使用Future来执行一些需要时间的代码,例如网络调用。我们可以使用 Future.delayed() 来模拟该行为。</p>
<p><img src="/img/remote/1460000020442323" alt="" title=""><br>现在,如果运行该程序,等待2秒钟后才出结果。让我们看另一个例子。</p>
<p><img src="/img/remote/1460000020442324" alt="" title=""><br>如您所见,我在调用函数后添加了一个print语句。在这种情况下,首先执行print语句,然后打印从Future返回的值。</p>
<p>但是,如果我们有一个Future,我们想先执行它,然后再执行print语句。这就需要使用 async/await 了。</p>
<h2>Async/Await</h2>
<p><img src="/img/remote/1460000020442325" alt="" title=""></p>
<p>首先在第3行的main函数的大括号之前添加async关键字。</p>
<p>然后我们在调用getAJoke函数之前添加await关键字,它的作用是等待从Future返回结果。后边的代码也会一直等待着被执行。</p>
<p>我们将代码包装在 try/catch 块中,来捕获任何异常(之前使用catchError回调来捕获)。要使用关键字await,就必须使用async关键字标记该函数,否则它将无法工作。</p>
<h2>总结</h2>
<p>这就是本教程系列的内容,更多语法细节和功能特性,强烈推荐阅读官方语言文档。接下来让我们一起探索Flutter开发之旅。</p>
<p><img src="/img/remote/1460000020442326" alt="" title=""><br><img src="/img/remote/1460000020442327" alt="" title=""></p>
Flutter必备语言Dart教程03 - 类,泛型
https://segmentfault.com/a/1190000020414874
2019-09-18T09:08:25+08:00
2019-09-18T09:08:25+08:00
前端知否
https://segmentfault.com/u/qethan
1
<p><img src="/img/remote/1460000020414877?w=640&h=360" alt="" title=""><br>上篇中我们学习了<a href="https://link.segmentfault.com/?enc=NkjgIRZD7oVgrsUu4ItXew%3D%3D.HulHU97s%2BOvAwZ0JJUDG7os%2FSjcD7%2FMn80gQevloOBZscVSYHVfN03PUk%2F3uFhQHY4Pm%2FrMzzXMRPTBPh8lxBNkaPmm1E7FDw16rRdBQCzI%3D" rel="nofollow">Flutter必备语言Dart教程02 - 控制流,异常</a>,现在我们继续学习Dart中的类和泛型。</p>
<h2>Class</h2>
<p>以下是在Dart中声明一个简单类,并创建它的实例的方法。</p>
<p><img src="/img/remote/1460000020414878?w=640&h=500" alt="" title=""></p>
<p>向类中添加实例变量,以及构造函数</p>
<p><img src="/img/remote/1460000020414880?w=640&h=624" alt="" title=""></p>
<p>Dart提供了一种构造函数初始化的简洁语法。如下所示:</p>
<p><img src="/img/remote/1460000020414881?w=640&h=584" alt="" title=""></p>
<p>如您所见,我们编写了一行构造函数,第一个参数值将设置为name,第二个参数值将设置为age。现在我们就不用写 this.name = name 这样的无聊语句了。</p>
<h2>命名的构造函数</h2>
<p>Dart提供了另一种定义构造函数的方法,称为命名构造函数。</p>
<p><img src="/img/remote/1460000020414882?w=640&h=740" alt="" title=""></p>
<p>如您所见,我们为构造函数提供了一个名称。这样我们在调用不同构造函数时,语义会更加清晰明了,不用根据参数去判断了。</p>
<h2>继承</h2>
<p>您可以使用extend关键字在Dart中继承其他类。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e21290996aa?w=640&h=616&f=png&s=182781" alt="" title=""></p>
<p>这里我们的Pug类继承自Dog类,并使用super关键字,传入适当的参数,调用Dog类的构造函数。</p>
<p>您还可以在冒号(:) 之后使用关键字this来调用同一类中的其他构造函数。</p>
<p>冒号(:)后边可以做一些初始化操作,比如调用构造函数、实例变量赋值等。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e2443fc6178?w=640&h=703&f=png&s=232531" alt="" title=""></p>
<p>这里我们创建两个命名构造函数,它们只有name参数,并调用默认的Pug构造函数。</p>
<h2>方法</h2>
<p>类中的方法与Dart中定义普通方法类似。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e26b3aed67a?w=640&h=810&f=png&s=225057" alt="" title=""></p>
<p>覆盖方法也很简单。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e2942a76591?w=640&h=899&f=png&s=279732" alt="" title=""></p>
<p>Setter<br>默认情况下,您在类中定义的任何变量,只需引用对象上的变量名称即可访问,例如dog.name,对象变量也可以直接赋值。但有时你想自定义属性的getter和setter,在Dart中你可以使用 get 和 set 关键字来自定义getter和setter。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e2bbf4ab9db?w=640&h=948&f=png&s=300495" alt="" title=""></p>
<p>现在类属性仍然可见并且可以随意更改,接下来我们把类属性设为私有。</p>
<h2>控制可访问性</h2>
<p>默认情况下,您在类中定义的每个属性和方法都是公共的,可以直接访问。在Dart中,您可以通过在其名称前添加“_”来使任何变量或方法变为私有。让我们将name属性设为私有。</p>
<p><img src="/img/remote/1460000020414888?w=640&h=1024" alt="" title=""></p>
<h2>抽象类和方法</h2>
<p>您可以使用abstract关键字,在Dart中创建一个抽象类。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e3095fd012a?w=640&h=406&f=png&s=98636" alt="" title=""></p>
<p>您只需要在类声明之前提供abstract关键字。对于方法,只需提供签名并省略实现。</p>
<h2>静态方法</h2>
<p>要使字段/方法静态,只需在声明之前,添加关键字static。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e337b2e95d1?w=640&h=697&f=png&s=182269" alt="" title=""></p>
<h2>枚举</h2>
<p>Dart支持枚举,并像其他语言一样使用。如果你来自Java语言,会很熟悉它们。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e359fda1cd6?w=640&h=574&f=png&s=176222" alt="" title=""></p>
<h2>泛型</h2>
<p>Dart全面支持泛型。假设您正在编写一个只保存数据的类,并且您希望它能够保存任何类型的数据。以下是使用泛型编写该类的方法。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e37b62f5b41?w=640&h=508&f=png&s=145694" alt="" title=""></p>
<hr>
<p>相关文章:</p>
<p><a href="https://link.segmentfault.com/?enc=7TxaSYmBbobJDS2BQrnxgQ%3D%3D.9AZ8mwUKliomH8AO05UDBwrbgMt72cdyNsZxsBD%2FdxjilbrjppZ28%2BJj11PBhL3X3DJyNHEdlerW7YVnqQBcWXwcBOJk9UqnQVjCj0Oiggk%3D" rel="nofollow">Flutter必备语言Dart教程01 - 变量,类型,函数</a></p>
<p><a href="https://link.segmentfault.com/?enc=RmMjxe31vHm08eadhanB5g%3D%3D.4Gnsrh5sWJdDSY%2FQtUG1gBrY9clg6pUwmvYviQWEnp9SpS3adT9z6FVJ%2FEM0338CqDszoo%2FzG2SKN62tipuydrZxosAniKD6P%2Foyi9p206g%3D" rel="nofollow">Flutter必备语言Dart教程02 - 控制流,异常</a></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e6768687317?w=2800&h=800&f=jpeg&s=153650" alt="" title=""></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/18/16d41e69a8b86e21?w=150&h=150&f=jpeg&s=11529" alt="" title=""></p>
Yarn包管理器使用入门
https://segmentfault.com/a/1190000020408756
2019-09-17T16:16:50+08:00
2019-09-17T16:16:50+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p><img src="/img/remote/1460000020408759" alt="" title=""></p>
<h2>简介</h2>
<p>Yarn是一个JavaScript包管理器,是npm的直接竞争对手,它是Facebook开源项目之一。</p>
<p>它与npm软件包兼容,因此它具有作为npm的直接替代品的巨大优势。</p>
<p>因为并行下载和缓存,它在刚推出时候,启动速度比npm快很多。虽然现在npm也追赶上了它的许多功能,但是Yarn仍是我推荐的解决方案。</p>
<p><strong>Yarn和npm的关系,就好比不同的下载软件。去下载同一个资源,使用不同的下载工具而已。</strong></p>
<p>工具发展到一定程度,彼此间在功能和使用体验上就会逐渐趋同。因此竞争对我们的用户来说是好事,npm也会借鉴和实现其他好的功能特性。</p>
<h2>安装Yarn</h2>
<p>虽然您可以使用npm(npm install -g yarn)安装Yarn,但Yarn团队不推荐使用它。</p>
<p>系统特定的安装方法在<a href="https://link.segmentfault.com/?enc=ixntcNb%2FChVmf5W23diBOA%3D%3D.PBWRB%2F0fhFchL677f0qFZYnXdeoNhIS1%2FTgyDLBWOZWCMm%2FbUuejHa0VqY8O2iLTVsqkp5ktxREyMQvCDVfhDzqCIA2PRyRf6VyecKROCOs%3D" rel="nofollow">https://yarnpkg.com/zh-Hans/d...</a>。例如,在macOS上,您可以使用Homebrew,并运行:</p>
<p><img src="/img/remote/1460000020408760" alt="" title=""></p>
<p>每个操作系统都有自己的包管理器,可以使安装流程非常简单顺利。</p>
<p>最后,您会得到shell中可用的yarn命令:</p>
<p><img src="/img/remote/1460000020408761" alt="" title=""></p>
<h2>管理包</h2>
<p>Yarn将其依赖项写入名为package.json的文件,该文件位于项目的根文件夹中,并将依赖项文件存储到node_modules文件夹中,就像npm(如果您以前使用过它)一样。</p>
<h2>初始化一个新项目</h2>
<p><img src="/img/remote/1460000020408762" alt="" title=""></p>
<p>安装现有项目的依赖项<br>如果您已经有一个包含依赖项列表的package.json文件但尚未安装软件包,请运行</p>
<pre><code>yarn</code></pre>
<p>或者</p>
<pre><code>yarn install</code></pre>
<p>来开始安装过程。</p>
<h2>局部本地安装软件包</h2>
<p>把包安装在本地项目中,使用:</p>
<pre><code>yarn add package-name</code></pre>
<h2>全局安装软件包</h2>
<pre><code>yarn global add package-name</code></pre>
<h2>在本地安装软件包作为开发依赖项</h2>
<pre><code>yarn add --dev package-name</code></pre>
<blockquote>等价于npm中的 --save-dev 或 -D</blockquote>
<h2>删除软件包</h2>
<pre><code>yarn remove package-name</code></pre>
<h2>检查许可证</h2>
<p>安装许多依赖项时,可能会有很多其他依赖项。您需要安装许多软件包,而您对这些软件包使用的许可证一无所知。</p>
<p>Yarn提供了一个方便的工具,可以打印您拥有的任何依赖项的许可证</p>
<pre><code>yarn licenses list</code></pre>
<p><img src="/img/remote/1460000020408763" alt="" title=""></p>
<p>它还可以自动生成免责声明,包含您项目中的所有许可证:</p>
<pre><code>yarn licenses generate-disclaimer</code></pre>
<p><img src="/img/remote/1460000020408764" alt="" title=""></p>
<h2>检查依赖项</h2>
<p>您是否检查过node_modules文件夹,并想知道为什么安装了特定的软件包?<strong>yarn why</strong> 命令会告诉你:</p>
<pre><code>yarn why package-name</code></pre>
<p><img src="/img/remote/1460000020408765" alt="" title=""></p>
<h2>升级依赖包</h2>
<p>如果要升级单个程序包,请运行</p>
<pre><code>yarn upgrade package-name</code></pre>
<p>要升级所有包,请运行</p>
<pre><code>yarn upgrade</code></pre>
<p>但是这个命令有时会导致问题,因为盲目升级而没有考虑到主版本的变化,可能会导致项目编译和运行失败。某个软件包大版本的更新,很可能会影响现有代码的使用。</p>
<p>Yarn有一个很好的工具,可以让你有选择地更新项目中的包,这对上个问题有很大的帮助:</p>
<pre><code>yarn upgrade-interactive --latest</code></pre>
<p><img src="/img/remote/1460000020408766" alt="" title=""></p>
<blockquote>--latest:该标志告诉Yarn忽略package.json指定的版本范围,并使用latest注册表中标记的版本</blockquote>
<h2>如何升级Yarn</h2>
<p>如果您使用brew安装它,如上所述,请使用:</p>
<pre><code>brew upgrade yarn</code></pre>
<p>或者使用npm:</p>
<pre><code>npm uninstall yarn -g
npm install yarn -g</code></pre>
<p>Windows用户,安装和升级部分,请自行翻阅文档:<a href="https://link.segmentfault.com/?enc=gBwzE3kK%2BupQ23dcewKiFw%3D%3D.S%2Fvb17ufSumD7gmqCpjgvOqTOyHWZ2aNHquZFGa9yL88FLOs7SkzgHy6ZgF%2B5BHSc8t%2F%2FEnn3oBONGp90jisDg%3D%3D" rel="nofollow">https://yarnpkg.com/zh-Hans/d...</a></p>
<p><img src="/img/bVbxovn?w=2800&h=800" alt="图片描述" title="图片描述"></p>
<p><img src="/img/bVbxFjC?w=150&h=150" alt="图片描述" title="图片描述"></p>
Node.js历史简介
https://segmentfault.com/a/1190000020394382
2019-09-16T14:12:34+08:00
2019-09-16T14:12:34+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p><img src="/img/remote/1460000020394385?w=640&h=360" alt="" title=""><br>你可能不相信,Node.js才10岁。</p>
<p>相比之下,JavaScript已有24年的历史,而我们一直使用的web,也有26年历史了。</p>
<p>10年不是一段很长的时间,但是Node.js让人感觉已经存在了很久。</p>
<p>在Node.js发布第二年的时候,我已经开始使用它了。虽然当时资料信息有限,但是仍然感受到了它的不凡潜力和应用前景。</p>
<p>在这篇文章中,我会简单介绍一些Node.js发展历程中的一些重要事件,让我们从时间发展的角度去审视它。</p>
<h2>历史背景</h2>
<p>JavaScript(通常缩写为JS)是一种高级的、解释型的编程语言。JavaScript是一门基于原型、函数先行的语言,是一门多范式的语言,它支持面向对象编程,命令式编程,以及函数式编程。</p>
<p>1995年,网景(Netscape)招募了布兰登·艾克,目标是把Scheme语言嵌入到Netscape Navigator浏览器当中。艾克在1995年5月仅花了十天时间就把原型设计出来了。</p>
<p>最初命名为Mocha,1995年9月在Netscape Navigator 2.0的Beta版中改名为LiveScript,同年12月,Netscape Navigator 2.0 Beta 3中部署时被重命名为JavaScript。</p>
<p>网景的部分业务是销售Web服务器,服务器中包含一个名为Netscape LiveWire的环境,可以使用服务器端JavaScript创建动态页面。不幸的是,Netscape LiveWire并不是很成功,直到后来,通过引入Node.js,服务器端Javascript才得以普及。</p>
<p>导致Node.js兴起的一个关键因素是时机。由于“Web 2.0”应用程序(如Flickr,Gmail等)向全世界展示了网络上的现代体验,JavaScript开始被认为是一种更为正式的语言。</p>
<p>随着许多浏览器之间的竞争,为了给用户提供最佳性能,JavaScript引擎也变得相当好。主要浏览器背后的开发团队努力为JavaScript提供更好的支持,并找到使JavaScript运行更快的方法。 Node.js引擎使用的引擎V8(也称为Chrome V8作为The Chromium Project的开源JavaScript引擎)由于这些竞争而得到显着改善。</p>
<p>Node.js占据了天时地利,但运气并不是今天流行的唯一原因。它为JavaScript服务器端开发引入了许多创新思维和方法,帮助了许多开发人员。</p>
<h2>2009</h2>
<p>Node.js诞生了</p>
<p>创建了第一版npm(Node.js包管理器和生态)</p>
<h2>2010</h2>
<p>Express.js web框架诞生了</p>
<p>Socket.io诞生了</p>
<h2>2011</h2>
<p>npm 1.0版,大公司开始采用Node.js:LinkedIn,Uber等。</p>
<p>Hapi.js诞生了</p>
<h2>2012</h2>
<p>使用率继续保持高速增长</p>
<h2>2013</h2>
<p>使用Node.js的第一个大博客平台:Ghost</p>
<p>Koa出生了</p>
<h2>2014</h2>
<p>分歧:io.js是Node.js的主要分支,其目标是引入ES6支持,并加快Node.js发展</p>
<h2>2015</h2>
<p>Node.js基金会诞生了</p>
<p>io.js合并回Node.js,npm引入私有模块</p>
<p>Node.js 4(直接跳过1, 2, 3版本)</p>
<h2>2016</h2>
<p>LeftPad事件</p>
<p>Yarn 诞生了</p>
<p>Node.js 6</p>
<h2>2017</h2>
<p>npm更注重安全性</p>
<p>Node.js 8</p>
<p>HTTP / 2</p>
<p>V8在其测试套件中引入了Node.js,除了Chrome之外,正式使Node.js成为V8引擎的目标平台</p>
<p>每周下载30亿npm</p>
<h2>2018</h2>
<p>Node.js 10</p>
<p>ES模块.mjs实验支持</p>
<h2>2019</h2>
<p>Node.js 12</p>
<p>V8 更新带来好多不错的特性。</p>
<p>HTTP 解析速度提升。</p>
<p>启动速度大幅提升。</p>
<p>更好的诊断报告和堆分析工具。</p>
<p>ES模块更新。</p>
<h3>期待Node.js在下一个十年,继续保持良好发展,成为一个更加了不起的平台!</h3>
<hr>
<p>相关文章:</p>
<p><a href="https://link.segmentfault.com/?enc=K21Jtf71GSJfCBStGTAhoQ%3D%3D.fmD2gbvsXEqqJ1iREuOVhZjoQmtWLNlsytuubYKVrxNQibCMOfb5cFtAo5p3rVXNPCMLR05qAfHNCufwjy2xdWcOUZ%2B4mAGYZ3Ok1qEBx4g%3D" rel="nofollow">Node.js错误处理模式</a></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/16/16d38b200f7d338e?w=2800&h=800&f=jpeg&s=153650" alt="" title=""></p>
<p><img src="/img/remote/1460000020394387?w=150&h=150" alt="" title=""></p>
Flutter必备语言Dart教程02 - 控制流,异常
https://segmentfault.com/a/1190000020384805
2019-09-15T07:08:09+08:00
2019-09-15T07:08:09+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p><img src="/img/remote/1460000020384808?w=640&h=272" alt="" title=""></p>
<p>我们已经完成了Dart中的变量,类型和函数的学习(如果你还没有读过它,请阅读<a href="https://link.segmentfault.com/?enc=neV0tlhT69xEueF%2Bj8MtoA%3D%3D.5pyp%2BBtDhruqGQZYVnNUK7j8YFoO1OPH0wiH%2FQD1iI%2FwPaV0ip%2BRzZVEMvxkVFHy" rel="nofollow">Flutter必备语言Dart教程01 - 变量,类型,函数</a>),现在我们来看看Dart中的控制流和异常处理。</p>
<h2>控制流</h2>
<p><strong>If – else</strong></p>
<p>Dart中的 if-else 非常简单,与其他语言非常相似。</p>
<p><img src="/img/remote/1460000020384809?w=640&h=600" alt="" title=""></p>
<p>您还可以使用三元运算符编写 if-else 条件的简短形式。</p>
<p><img src="/img/bVbxIEQ" alt="clipboard.png" title="clipboard.png"></p>
<h2>循环</h2>
<p>Dart支持各种循环,您会很快熟悉上手这些语法,因为它与许多其他语言一样。</p>
<p>For loop</p>
<p><img src="/img/remote/1460000020384810?w=640&h=399" alt="" title=""></p>
<p>While loop</p>
<p><img src="/img/remote/1460000020384811?w=640&h=520" alt="" title=""></p>
<p>Do-while loop</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/15/16d3204b84119a05?w=640&h=549&f=png&s=114411" alt="" title=""></p>
<p><strong>Switch语句</strong></p>
<p><img src="/img/remote/1460000020384814?w=640&h=823" alt="" title=""></p>
<h2>异常处理</h2>
<p>Dart使用典型的try-catch块来处理异常,并使用throw关键字来引发异常。</p>
<p><strong>抛出异常</strong></p>
<p>首先让我们看看我们如何在Dart中抛出异常。</p>
<p><img src="/img/remote/1460000020384815?w=640&h=411" alt="" title=""></p>
<p>当整数b的值为0时,我们抛出一个名为<strong>IntegerDivisionByZeroException</strong>的内置异常。</p>
<p>您也可以使用消息字符串抛出<strong>Exception</strong>对象本身。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/15/16d3205c0a7a2c80?w=640&h=445&f=png&s=120242" alt="" title=""></p>
<p><strong>捕捉和处理异常</strong></p>
<p>现在是捕获和处理异常的主要部分。</p>
<p>可以使用on关键字捕获特定类型的异常,如下所示。</p>
<p><img src="/img/remote/1460000020384817?w=640&h=492" alt="" title=""></p>
<p>如果您不知道将抛出的异常类型,或者不确定,那么使用catch块来处理任何类型的异常。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/15/16d320633d981dfe?w=640&h=517&f=png&s=157430" alt="" title=""></p>
<p><strong>Finally</strong></p>
<p>Dart还提供了一个finally块,无论是否抛出异常,都将始终执行。</p>
<p><img src="/img/remote/1460000020384819?w=640&h=556" alt="" title=""></p>
<hr>
<p><strong>系列文章:</strong></p>
<p><a href="https://link.segmentfault.com/?enc=BjxpS4PaxJRDVPCBl4koLA%3D%3D.71Qz5LzwBIrJoM%2BQAnHzxZ2ENeTrZ2FBQphz6iG0nlfwZps4gDOrFDCTjpEnKOfQ" rel="nofollow">Flutter必备语言Dart教程01 - 变量,类型,函数</a></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/15/16d320713f56ca6d?w=2800&h=800&f=jpeg&s=153650" alt="" title=""></p>
<p><img src="/img/remote/1460000020384821?w=150&h=150" alt="" title=""></p>
Flutter必备语言Dart教程01 - 变量,类型,函数
https://segmentfault.com/a/1190000020384078
2019-09-14T22:50:08+08:00
2019-09-14T22:50:08+08:00
前端知否
https://segmentfault.com/u/qethan
3
<p><img src="/img/remote/1460000020384081?w=640&h=360" alt="" title=""></p>
<h2>Hello World</h2>
<p>致敬经典 'Hello World' 程序。</p>
<p><img src="/img/remote/1460000020384082?w=640&h=395" alt="" title=""></p>
<p>与Java类似,每个Dart程序都必须有一个main作为其入口点。</p>
<p>要运行程序,请将其保存在名为“hello_world.dart”的文件中,并在终端中执行以下命令。(如果没有dart环境,请先完成安装。)</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/14/16d3039753c0d9c5?w=640&h=369&f=png&s=68680" alt="" title=""></p>
<h2>定义变量</h2>
<p>就像在JavaScript中一样,您可以使用var关键字来定义变量。</p>
<p><img src="/img/remote/1460000020384084?w=640&h=484" alt="" title=""></p>
<p>但是!与JavaScript不同,在Dart 2中,一旦分配了类型,就无法将具有新类型的值重新分配给变量。 Dart会自动从右侧推断数据类型。</p>
<p>您还可以通过显式提供数据类型来定义变量。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/14/16d303a3eb105f36?w=640&h=477&f=png&s=142733" alt="" title=""></p>
<p>如果您不打算更改变量所持有的值,则使用final或const声明它。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/14/16d303ab919b9bf0?w=640&h=475&f=png&s=155650" alt="" title=""></p>
<p>final 和 const之间的区别在于:</p>
<p>const变量是编译时常量,即const变量在编译期间必须有一个值,例如const PI = 3.14;</p>
<p>虽然final的变量只能分配一次,但它们不需要在编译期间分配,并且可以在运行时分配。也就是说,final的变量可以先不赋值,稍后在其他地方赋值。</p>
<h2>内置数据类型</h2>
<p>Dart提供了您可以从现代语言中获得的所有基本数据类型。</p>
<ul>
<li>Numbers</li>
<li>Strings</li>
<li>Booleans</li>
<li>Lists</li>
<li>Maps</li>
</ul>
<p><img src="/img/remote/1460000020384087?w=640&h=328" alt="" title=""></p>
<h2>Lists</h2>
<p>声明一个列表非常简单,使用方括号[]可以简单地定义一个列表。以下是列表中的一些常见操作。</p>
<p><img src="/img/remote/1460000020384088?w=640&h=810" alt="" title=""></p>
<p>如果要定义一个编译时常量的List,即列表内容不可更改,则使用const关键字。</p>
<p><img src="/img/remote/1460000020384090?w=640&h=237" alt="" title=""></p>
<h2>Maps</h2>
<p>定义Map同样直截了当。使用花括号{}来定义Map。</p>
<p><img src="/img/remote/1460000020384091?w=640&h=850" alt="" title=""></p>
<p>您还可以使用Map构造函数定义map。</p>
<p><img src="/img/remote/1460000020384092?w=640&h=416" alt="" title=""></p>
<p>如果要定义编译时常量的Map,即map的内容不可更改,则使用const关键字。</p>
<p><img src="/img/remote/1460000020384093?w=640&h=401" alt="" title=""></p>
<h2>函数</h2>
<p>Dart中的函数尽可能简单,有点类似于javascript。您需要做的就是提供名称,返回类型和参数。</p>
<p><img src="/img/remote/1460000020384094?w=640&h=343" alt="" title=""></p>
<p>您甚至可以省略返回类型,程序仍然有效。</p>
<p><img src="/img/remote/1460000020384095?w=640&h=380" alt="" title=""></p>
<p>这是编写单行函数的简便方法。</p>
<p><img src="/img/remote/1460000020384096?w=640&h=245" alt="" title=""></p>
<h2>命名参数</h2>
<p>Dart有一个叫做命名参数的东西,当使用命名参数时,你必须在调用函数时指定参数的名称。要启用命名参数,只需使用大括号{}将参数包装在函数中。</p>
<p><img src="/img/remote/1460000020384097?w=640&h=319" alt="" title=""></p>
<p>如果在使用命名参数调用函数时未提供名称,则程序将崩溃。</p>
<h2>默认参数值</h2>
<p>您可以为命名参数指定默认值,从而在调用函数时使它们成为可选参数。在下面的示例中,我们为lastName指定了一个默认值。</p>
<p><img src="/img/remote/1460000020384098?w=640&h=328" alt="" title=""></p>
<h2>函数是一等公民</h2>
<p>在Dart中,函数非常灵活,例如,你可以在另一个函数中传递一个函数。对于这一点,JavaScript开发者应该很熟悉了。</p>
<p><img src="/img/remote/1460000020384099?w=640&h=485" alt="" title=""></p>
<p>在这里,我定义了一个名为out的函数,它接受一个参数:一个函数(具有参数message)。然后我定义了一个名为printOutLoud的函数,它所做的就是以大写形式打印一个字符串。</p>
<p>Dart也有匿名函数,所以在上面的例子中,我们可以传递一个匿名函数而不是预定义函数(printOutLoud)。</p>
<p><img src="/img/remote/1460000020384100?w=640&h=440" alt="" title=""></p>
<p>另一个匿名函数的例子。</p>
<p><img src="/img/remote/1460000020384101?w=640&h=503" alt="" title=""></p>
<hr>
<p><strong>系列文章:</strong></p>
<p><a href="https://link.segmentfault.com/?enc=F47uLXSAW79za7aDmoURvw%3D%3D.NzB3MZbOhx1tj5IiBqJOS7QNZenalGEAKjN4XguOXTIiDN9a%2B%2BH9AEMU35lg5Ex3" rel="nofollow">Flutter必备语言Dart教程02 - 控制流,异常</a></p>
<p><img src="/img/remote/1460000020384102?w=2800&h=800" alt="" title=""></p>
<p><img src="/img/remote/1460000020384103?w=150&h=150" alt="" title=""></p>
Node.js错误处理模式
https://segmentfault.com/a/1190000020382697
2019-09-14T17:11:23+08:00
2019-09-14T17:11:23+08:00
前端知否
https://segmentfault.com/u/qethan
3
<p><img src="/img/remote/1460000020382700?w=640&h=360" alt="" title=""></p>
<p>异步语言中的错误处理,需要使用一些独特的,非常规的方式。 下边是Node.js中几种主要的错误处理模式。让我们来看一下。</p>
<p><strong>先来熟悉一下Error对象。</strong></p>
<p>Error对象可以是Error类的一个实例,或者扩展继承Error类的自定义Error类:</p>
<p><img src="/img/remote/1460000020388788" alt="" title=""></p>
<p><img src="/img/remote/1460000020388789" alt="" title=""></p>
<h2>捕获未捕获的异常</h2>
<p>如果在程序执行期间抛出未捕获的异常,程序将崩溃。</p>
<p>要解决此问题,需要在 process 对象上侦听 uncaughtException 事件:</p>
<p><img src="/img/remote/1460000020388790" alt="" title=""></p>
<h2>错误返回值</h2>
<p>最简单的模式,不能处理异步情况。如下:</p>
<p><img src="/img/remote/1460000020382701?w=640&h=300" alt="" title=""></p>
<h2>抛出错误</h2>
<p>这是一个常用的模式,函数执行时,如果出现错误情况,它会直接抛出一个错误。错误异常需要在catch中捕捉并处理。在try / catch中无法处理异步方法调用抛出的错误。要解决这个问题,我们需要使用domains。在node v0.8+版本的时候,发布了一个模块domain。这个模块做的就是try...catch所无法做到的:捕捉异步回调中出现的异常。</p>
<p><img src="/img/remote/1460000020382702?w=640&h=489" alt="" title=""></p>
<p>Domain示例:</p>
<p><img src="/img/remote/1460000020382703?w=640&h=430" alt="" title=""></p>
<p>其中 <strong>run()</strong> 相当于 try, <strong>on('error')</strong> 相当于 catch</p>
<h2>错误回调</h2>
<p>通过回调返回错误是Node.js中最常见的错误处理模式。处理错误回调可能变得一团糟(回调地狱金字塔)。</p>
<p><img src="/img/remote/1460000020382704?w=640&h=360" alt="" title=""></p>
<h2>订阅,监听错误</h2>
<p>当发出错误时,错误被广播给所有相关的订阅者,按照订阅顺序,间隔执行。</p>
<p><img src="/img/remote/1460000020382705?w=640&h=408" alt="" title=""></p>
<h2>Promise</h2>
<p>Promise用于异步错误处理。如下:</p>
<p><img src="/img/remote/1460000020382706?w=504&h=454" alt="" title=""></p>
<h2>Try...catch 和 async/await</h2>
<p>ES7 Async / await能够让我们编写看起来是同步的,异步JS代码。</p>
<p><img src="/img/remote/1460000020382707?w=640&h=361" alt="" title=""></p>
<h2>使用Await-to-js 第三方类库</h2>
<p>不使用try-catch块的async / await。如下:</p>
<p><img src="/img/remote/1460000020382708?w=640&h=288" alt="" title=""></p>
<h2>总结</h2>
<p>以上几种方式,基本包含了从早期到现在的所有错误处理方案。Aysnc/await语法更加直观,简洁,以及编写同步代码的体验。不同环境下,使用最合适的方案,就是最好的。</p>
<p><img src="/img/remote/1460000020382709?w=2800&h=800" alt="" title=""></p>
<p><img src="/img/remote/1460000020382710?w=150&h=150" alt="" title=""></p>
如何在Vue Router中应用中间件
https://segmentfault.com/a/1190000020379162
2019-09-13T16:24:46+08:00
2019-09-13T16:24:46+08:00
前端知否
https://segmentfault.com/u/qethan
7
<p><img src="/img/remote/1460000020379165?w=800&h=480" alt="" title=""></p>
<p>中间件是我们在软件开发中的一个古老而强大的概念,当我们在应用程序中使用路由相关模式时,它非常有用。</p>
<p>如果您不太了解中间件的含义,Nodejs框架Express里的中间件可以帮助您了解它们的工作原理。</p>
<p>但是,中间件仅适用于后端吗?</p>
<p>不,当应用程序中有路由时,中间件在前端或后端中就会非常常见。比如现在流行的单页应用程序。</p>
<p>有一些示例可以说明,何时可以使用中间件:</p>
<p>不允许未登录用户访问您的网页。<br>仅允许某些类型的用户查看页面(角色:管理员,作者等)<br>数据采集。<br>重置设置或清理存储空间。<br>限制访问用户的年龄。<br>还有一些......</p>
<p>那么如何在Vue中使用中间件?</p>
<p>感谢Vue Router,这将非常简单!因为这个插件实现了一个类似的概念,称为“导航守卫”。</p>
<p><img src="/img/remote/1460000020379166?w=1550&h=682" alt="" title=""></p>
<p>导航守卫真的很棒,让我们在进入路由之前,更新之前和离开之前,可以执行一些代码逻辑。</p>
<p><img src="/img/remote/1460000020379167?w=860&h=994" alt="" title=""></p>
<p>还可以使用全局守卫。</p>
<p><img src="/img/remote/1460000020379168?w=860&h=850" alt="" title=""></p>
<p>但有时我们需要多个中间件用于同一路由,我们可以用Vue Router Multiguard包解决问题。这允许我们设置一系列守卫,如下所示:</p>
<p><img src="/img/remote/1460000020379169?w=1248&h=1102" alt="" title=""></p>
<p>在上边示例中可以看到,通过Vue Router Multiguard,在路由配置中应用中间件很容易。让我们再看一个简化的例子:</p>
<p>首先,我们定义一个模拟用户。然后假设您有一个服务,可以从全局state或其他地方获得当前用户的数据。</p>
<p><img src="/img/remote/1460000020379170?w=472&h=418" alt="" title=""></p>
<p>现在,我们可以用中间件创建我们的“真实”示例:</p>
<p><img src="/img/remote/1460000020379171?w=1484&h=1534" alt="" title=""></p>
<hr>
<p><strong>PS:</strong></p>
<p>1.Vue Router还有组件内的守卫</p>
<pre><code>* beforeRouteEnter
* beforeRouteUpdate (2.2 新增)
* beforeRouteLeave
其中beforeRouteEnter,很适合在进入页面之前去获取数据。
</code></pre>
<p>2.如果你阅读了文档,你会发现你可以将下一个路由传递给 next() 函数,例如重定向到 login - next('/login')</p>
<p><img src="/img/remote/1460000020379172?w=2800&h=800" alt="" title=""></p>
<p><img src="/img/remote/1460000020379173?w=150&h=150" alt="" title=""></p>
JS对象那些事儿
https://segmentfault.com/a/1190000020377597
2019-09-13T09:09:00+08:00
2019-09-13T09:09:00+08:00
前端知否
https://segmentfault.com/u/qethan
14
<p><img src="/img/remote/1460000020377600?w=640&h=384" alt="" title=""></p>
<p>JavaScript中几乎所有东西都是一个对象,除了六种基本类型数据 - null,undefined,strings,numbers,boolean和symbols。</p>
<p>任何不是原始值的东西都是Object。这包括数组,函数,构造函数和对象本身。</p>
<h2>对象</h2>
<p>从概念上讲,对象在所有编程语言中都是相同的。它们使用具有属性和方法的代码来表示真实世界。</p>
<p>例如,如果您的对象是学生,则它将具有名称,年龄,地址,ID等属性以及updateAddress,updateName等方法。</p>
<p>在JavaScript中,将对象视为包含元素项的列表,并且列表中的每个项(属性或方法)都由内存中的键值对存储。</p>
<p>让我们看一个对象的例子。</p>
<p><img src="/img/remote/1460000020377601?w=472&h=382" alt="" title=""></p>
<p>firstObj 是一个对象,有2个属性:1,age;value 为 foo 和 28。</p>
<p>JavaScript对象在创建方式上有所不同。不需要非得用class创建,并且可以使用字面量表示法声明。</p>
<h2>对象创建</h2>
<p>我们可以在JavaScript中以多种方式创建对象,让我们来看看都有哪些。</p>
<p><strong>1. 对象字面量(最直接的方式)</strong>。对象字面量是用大括号括起来的以逗号分隔的键值对列表。对象字面量属性值可以是任何数据类型,包括数组文字,函数,嵌套对象字面量或基本数据类型。</p>
<p>`<br>注意:上面的学生对象键可以通过点表示法访问,即student.id,student.name或通过方括号表示法,即学生['id'],学生['姓名']等<br>`</p>
<p><strong>2. Object.create()</strong>。该方法使用指定的原型和旧对象的属性创建一个新对象。</p>
<p>`<br>注意:默认情况下,每个JavaScript函数都有一个原型对象属性(默认情况下它是空的)。方法或属性可以附加到此属性。<br>`</p>
<p><img src="/img/remote/1460000020377602?w=1068&h=346" alt="" title=""></p>
<p>下面是对象__proto__的输出:</p>
<p><img src="/img/remote/1460000020377603?w=640&h=316" alt="" title=""></p>
<p>我们现在可以使用Object.create()方法向newStudent对象添加新属性和数据。</p>
<p>`<br>注意:newStudent能够访问student对象的键和值,因为它已被添加到newStudent的原型链中,这是我们在javascript中继承的一种方式。也就是说,newStudent将存储一个指向student对象的链接。读取属性时也会查询此父对象。<br>`</p>
<p>父母可以有父母,依此类推。重复这一过程,直到我们到达一个没有任何父项的对象,即父项为空。</p>
<p><strong>3. 对象实例</strong>。将Object constructor与“new”关键字结合使用可以让我们初始化新对象。</p>
<p>我们来看一个例子吧。</p>
<p><img src="/img/remote/1460000020377604?w=640&h=375" alt="" title=""></p>
<p>但是,new Object() 不适合需要创建同一类型的多个对象的情况,因为它需要为每个这样的对象重复编写上面的代码。</p>
<p>为了解决这个问题,我们可以使用下一个方法。</p>
<p><strong>4. 对象构造器</strong>。当我们需要一种可以多次创建对象“类型”的方法时,构造函数非常有用,而无需每次都重新定义对象,这可以使用Object Constructor函数来实现。</p>
<p>我们来看一个例子吧。</p>
<p><img src="/img/remote/1460000020377605?w=640&h=380" alt="" title=""></p>
<p>我们创建了两个具有相同属性但具有不同值的对象。</p>
<p><strong>5. Object.assign()</strong>。这是从其他对象创建新对象的另一种方法。</p>
<p>它将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。让我们通过一个例子来理解:</p>
<p><img src="/img/remote/1460000020377607?w=640&h=330" alt="" title=""></p>
<p>Object.assign() 有很多用例,比如对象克隆,合并对象等。</p>
<p><strong>6. Object.fromEntries()</strong>。方法将键值对列表转换为对象。我们来看一个例子吧</p>
<p><img src="/img/remote/1460000020377608?w=640&h=426" alt="" title=""></p>
<p>`<br>注意:创建对象的最佳方法是通过字面量表示法,因为它在源代码中占用的空间更少。它可以清楚地识别出发生了什么,所以使用new Object(),你实际上只是输入更多(理论上,如果没有被JavaScript引擎优化)和进行不必要的函数调用。此外,字面量表示法创建对象,并在同一行代码中分配属性,而其他代码则不然。<br>`</p>
<h2>如何添加/更新和删除对象的属性</h2>
<p>如前所述,可以通过点 或 括号表示法添加对象的属性。让我们看一个例子</p>
<p><img src="/img/remote/1460000020377609?w=572&h=490" alt="" title=""></p>
<p>这里,name 和 city 是对象属性。</p>
<p>对象只能包含一个且具有一个值的键,也就是说同一个键只能有一个值。</p>
<p>属性名称可以是字符串,数字或特殊字符,也可以是动态属性,但如果属性名称不是字符串,则必须使用括号表示法访问它。因此,如果我们需要访问上面示例中的属性1,我们可以执行a[1],但是a.1将返回语法错误。而a.name或[“name”]则都可以。</p>
<p><img src="/img/remote/1460000020377610?w=640&h=324" alt="" title=""></p>
<p>要更新属性,我们可以再次使用上述两种表示法。如果我们为已创建的属性添加值,则会更新这个属性的值。</p>
<p>我们还可以通过Object函数方法( 如Object.defineProperties() 或 Object.defineProperty() )创建和更新对象的属性。</p>
<p><img src="/img/remote/1460000020377611?w=640&h=508" alt="" title=""></p>
<p>要删除对象的属性,我们可以使用delete关键字,来执行此操作。</p>
<p><img src="/img/remote/1460000020377612?w=456&h=310" alt="" title=""></p>
<p>如果成功删除属性,则返回值delete为true。否则,它将是错误的。</p>
<p><strong>如何迭代对象属性?</strong></p>
<p>如果我们想要访问所有对象键值对的情况下,会出现这种需求。</p>
<p><strong>使用循环</strong> - for in 和 for of</p>
<p>在 for in 的情况下,它迭代一个对象并逐个返回属性。</p>
<p><img src="/img/remote/1460000020377613?w=640&h=346" alt="" title=""></p>
<p>Key将逐个对应对象的属性,[key]返回该值。<strong>对于for in循环也迭代原型链并返回父键</strong>,所以如果你看到更多的键,不要感到惊讶。<strong>为了避免看到更多的键,我们可以执行hasOwnProperty 检查以仅获取当前对象键。</strong></p>
<p>在 for of 情况下,它迭代遍历可迭代对象,仅获取当前对象的key。这点也是和 for in 的区别。更多详细解释,可以参考MDN for...of。</p>
<p>Object函数中有各种方法,它们只会访问当前对象的属性和值,而不是其原型链。</p>
<p><strong>1. Object.keys()</strong> 或 <strong>Object.getOwnPropertyNames()</strong>。 返回字符串键数组。</p>
<p><img src="/img/remote/1460000020377614?w=640&h=357" alt="" title=""></p>
<p><strong>2. Object.values(). 返回一个值数组。</strong></p>
<p><img src="/img/remote/1460000020377615?w=640&h=222" alt="" title=""></p>
<p><strong>3. Object.entries()</strong>. 返回 [key, value] 为元素的二维数组</p>
<p><img src="/img/remote/1460000020377616?w=1484&h=382" alt="" title=""></p>
<p>从输出结果看,上面的属性顺序是不固定的。</p>
<h2>如何检查对象中的属性是否存在</h2>
<p>有三种方法可以检查对象中是否存在属性。</p>
<p><strong>1. 使用hasOwnProperty</strong>。此方法返回一个布尔值,表示对象本身是否具有指定的属性,而不是父/继承属性。</p>
<p><img src="/img/remote/1460000020377617?w=640&h=300" alt="" title=""></p>
<p>注意:即使属性的值为 null 或 undefined,hasOwnProperty 也会返回true。</p>
<p>如果我们将hasOwnProperty作为对象中的属性名称怎么办?这个值得思考。</p>
<p><strong>2. 使用in运算符</strong> - 如果指定的属性位于指定的对象 或 其原型链中(即在其父级内),则 in 运算符返回true。</p>
<p><img src="/img/remote/1460000020377618?w=640&h=321" alt="" title=""></p>
<p>注意:hasOwnProperty仅检查当前对象属性,而 in 运算符中检查当前+父属性</p>
<p><strong>3. 使用自定义功能</strong></p>
<p>有多种方式可以通过自定义方法检查属性是否存在。其中一个是通过 Object.keys。</p>
<p><img src="/img/remote/1460000020377619?w=960&h=274" alt="" title=""></p>
<p><strong>什么是按引用/共享复制和按值复制,它如何应用于对象?</strong></p>
<p>不同之处在于,通过值,我们的意思是每次创建内容时都会执行新的内存分配,而在引用的情况下,我们指向已经创建的内存空间。</p>
<p>在javascript的上下文中,所有原始数据类型都是通过值方法分配的内存,对于一个对象,可以进行值或引用传递,根据具体操作情况。</p>
<p><img src="/img/remote/1460000020377620?w=640&h=499" alt="" title=""></p>
<h2>什么是浅层和深层复制/克隆对象?</h2>
<p>浅层和深层副本之间的核心区别在于如何将属性复制到新对象。</p>
<p>在浅拷贝中,新对象与旧对象共享数据,即在上述示例的情况下使用 = 创建对象的浅拷贝b。因此,在大多数情况下,通过引用传递是浅层复制。</p>
<p>此外,浅拷贝将复制<strong>顶级属性</strong>,但<strong>嵌套对象</strong>在原始(源)和副本(目标)之间共享。</p>
<p>浅拷贝的另一种方法是使用Object.assign()。我们来看看这个例子</p>
<p><img src="/img/remote/1460000020377621?w=640&h=601" alt="" title=""></p>
<p>正如我们在上面看到的 obj.b.c = 30,这是 Object.assign() 的一个陷阱。 Object.assign 只生成浅拷贝。 newObj.b 和 obj.b共享对象的相同引用,没有制作单独的副本,而是复制了对象的引用。</p>
<p>在Deep copy中,新对象将拥有自己的一组键值对(与原始对象具有相同的值)而不是共享。</p>
<p>让我们看看做一些深层复制的方法</p>
<p><strong>1. JSON.parse(JSON.stringify(object))</strong></p>
<p><img src="/img/remote/1460000020377622?w=640&h=368" alt="" title=""></p>
<p>我们无法复制自定义的对象函数,以及键对应的值是undefined 或 Symbol的情况,如下:</p>
<p><img src="/img/remote/1460000020377623?w=640&h=344" alt="" title=""></p>
<p>此外,此方法不适用于循环对象。</p>
<p>`<br>注意:循环对象是具有引用自身属性的对象。<br>`</p>
<p><img src="/img/remote/1460000020377624?w=640&h=586" alt="" title=""></p>
<p>上面将抛出一个错误,<code>converting circular structure to JSON.</code></p>
<p><strong>2. 使用ES6展开运算符</strong></p>
<p><img src="/img/remote/1460000020377625?w=640&h=290" alt="" title=""><br>但是,nested对象仍然是浅层复制的。</p>
<h2>如何比较两个对象?</h2>
<p>对象的等式 == 和 严格相等 === 运算符完全相同,即只有两个对象的内存引用相同时才相等。</p>
<p>例如,如果两个变量引用同一个对象,它们是相等的:</p>
<p><img src="/img/remote/1460000020377626?w=640&h=383" alt="" title=""></p>
<p>未完待续</p>
<hr>
<p>相关文章:</p>
<p><a href="https://link.segmentfault.com/?enc=de%2FBZlKNjEGOdvDgvRGlDQ%3D%3D.vt8BzB22iVud3m9%2FtOXSJ8oDWgQc7vg4ZSBVDCDQBqPuLMRuZK1aTAOGBraa8tURUqzgKqOyQpJDTSeGlJS%2FjBdc9iBbrE5s9C2r3ySQue0%3D" rel="nofollow">使用Array.isArray更好地检查数组</a></p>
<p><a href="https://link.segmentfault.com/?enc=lrSp15E9n2YWVw5to15fqQ%3D%3D.SoYhln%2B1k05%2BUdH4vyFy6RmiAKBwXJEvmKUkoU6iwGH6cKb%2B0Tu7oXCs4K%2BxmLbCtXYyIX9HMqLDj4vuOuGT%2FuiajY94GEkFoOAJi%2FB70JM%3D" rel="nofollow">JS扩展运算符(Spread Operator)的5种用法</a></p>
<p><a href="https://link.segmentfault.com/?enc=6Z4DiU9bBh9%2BBTRCwIoBfg%3D%3D.lG7fvKil%2F9wFDEw2hI8JcEwIX8vAc4ik0ISjM5a9i3%2B%2F%2BalCFgpekp5z52PvZ0FjGag9L%2FDwxs%2FRRT7x9%2BF3teSXe9FntvAW6EFBOMh0tHQ%3D" rel="nofollow">JavaScript中如何反转数组</a></p>
<p><a href="https://link.segmentfault.com/?enc=WCsHGjQH2Fx1pRMonx6E8w%3D%3D.vU0wa8ip9929dt%2F%2BJ9LMcPolK1khbCbziDeirWroani7%2FE62UcwuXzgkQkDMtO%2F%2B" rel="nofollow">如何使用ES6语法给数组去重</a></p>
<hr>
<p><img src="/img/remote/1460000020377627?w=2800&h=800" alt="" title=""></p>
<p>还可以关注头条号:「前端知否」</p>
<p><img src="/img/remote/1460000020377628?w=150&h=150" alt="" title=""></p>
25个JavaScript代码简写技巧(下篇)
https://segmentfault.com/a/1190000020366414
2019-09-12T07:32:03+08:00
2019-09-12T07:32:03+08:00
前端知否
https://segmentfault.com/u/qethan
59
<p><img src="/img/remote/1460000020366417?w=640&h=360" alt="" title=""></p>
<h2>14. 多行字符串</h2>
<p>如果您发现自己需要在代码中编写多行字符串,那么您可以编写它:</p>
<p>常规:</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/12/16d22a4187d111a4?w=640&h=286&f=png&s=167914" alt="" title=""><br>但是有一种更简单的方法。只需使用反引号。</p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366420?w=640&h=314" alt="" title=""></p>
<h2>15. 展开(spread)操作符</h2>
<p>ES6中引入的展开运算符有几个用例,可以使JavaScript代码更高效,更有趣。它可以用来替换某些数组函数。展开操作符只是连续的三个点。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366421?w=640&h=483" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366422?w=640&h=481" alt="" title=""></p>
<p>与 concat() 函数不同,您可以使用spread运算符在另一个数组内的任何位置插入数组。</p>
<p><img src="/img/remote/1460000020366423?w=640&h=405" alt="" title=""></p>
<p>您还可以将spread运算符与ES6解构表示法结合使用:</p>
<p><img src="/img/remote/1460000020366424?w=640&h=332" alt="" title=""></p>
<h2>16. 强制参数</h2>
<p>默认情况下,如果未传递值,JavaScript会将函数参数设置为undefined。其他一些语言会引发警告或错误。要强制执行参数赋值,可以使用 if 语句在未定义时抛出错误。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366425?w=640&h=406" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366426?w=640&h=446" alt="" title=""></p>
<h2>17. Array.find</h2>
<p>如果您曾经在纯JavaScript中编写过 find 函数,那么您可能已经使用了for循环。在ES6中,引入了一个名为 find() 的新数组函数</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366427?w=640&h=456" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366428?w=640&h=230" alt="" title=""></p>
<h2>18. Object[key]</h2>
<p>你知道Foo.bar也可以写成Foo ['bar']吗?起初,似乎没有理由应该这样写。但是,这种表示法可以为编写可重用代码块提供方便。</p>
<p>思考一下,验证函数的这个简化示例:</p>
<p><img src="/img/remote/1460000020366429?w=640&h=362" alt="" title=""></p>
<p>这个功能完美地完成了它的工作,但是请考虑这样一种情况,即您需要验证的表单有很多,表单都具有不同字段和规则。构建可在运行时配置的通用验证函数不是更好吗?</p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366430?w=640&h=613" alt="" title=""><br>现在我们有了一个通用验证函数,可以在所有表单中重用,而无需为每个表单编写自定义验证函数。</p>
<h2>19. 双取反运算</h2>
<p>双取反运算符有一个非常实用的场景。您可以将它用作 Math.floor() 的替代品。 双取反运算符的优点是它可以更快地执行相同的操作。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366431?w=640&h=369" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366432?w=536&h=406" alt="" title=""></p>
<h2>20. 数学指数幂函数</h2>
<p>常规:</p>
<p><img src="/img/remote/1460000020366433?w=552&h=478" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/bVbxDld" alt="图片描述" title="图片描述"></p>
<h2>21. 将字符串转换为数字</h2>
<p>有时,您的代码会接收String类型的参数,但需要以数字类型处理。这不是什么大问题,我们可以进行快速转换。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366435?w=640&h=351" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366436?w=640&h=247" alt="" title=""></p>
<h2>22. 对象属性赋值</h2>
<p>考虑以下代码:</p>
<p><img src="/img/remote/1460000020366437?w=640&h=343" alt="" title=""></p>
<p>你会如何把它们合并为一个对象?</p>
<p>一种方法是编写一个将第二个对象的数据复制到第一个对象的函数。</p>
<p>最简单的方法是,使用ES6中引入的 Object.assign 函数:</p>
<p><img src="/img/remote/1460000020366438?w=640&h=266" alt="" title=""></p>
<p>您还可以使用ES8中引入的对象销毁表示法:</p>
<p><img src="/img/remote/1460000020366439?w=640&h=304" alt="" title=""></p>
<p>您可以合并的对象属性数量没有限制。如果确实具有相同属性名称的对象,则将按合并的顺序覆盖值。</p>
<h2>23. 取反运算 和 IndexOf</h2>
<p>使用数组执行查找时,indexOf() 函数用于检索您要查找的项目的位置。如果未找到该项,则返回值-1。在JavaScript中,0被认为是'falsy',而大于或小于0的数字被认为是'truthy'。因此,必须像这样编写正确的代码。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020366440?w=640&h=423" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020366441?w=640&h=465" alt="" title=""></p>
<p>取反(〜)运算符对 -1 以外的任何值,都返回 truthy 值。对它进行非运算,直接 !〜。</p>
<p>或者,我们也可以使用 includes() 函数:</p>
<p><img src="/img/remote/1460000020366442?w=640&h=228" alt="" title=""></p>
<h2>24. Object.entries()</h2>
<p>这是ES8中引入的一项功能,允许您将对象转换为键/值对数组。请参阅以下示例:</p>
<p><img src="/img/remote/1460000020366443?w=640&h=331" alt="" title=""></p>
<h2>25. Object.values()</h2>
<p>这也是ES8中引入的一个新功能,它执行与 Object.entries() 类似的功能,但没有key部分:</p>
<p><img src="/img/bVbxCAh" alt="图片描述" title="图片描述"></p>
<p>看到这里,都已经介绍完了。才疏学浅,如果您有其他好的技巧,欢迎留言评论。</p>
<hr>
<p>相关文章:</p>
<p><a href="https://segmentfault.com/a/1190000020354772">25个JavaScript代码简写技巧(上篇)</a></p>
<p><a href="https://link.segmentfault.com/?enc=%2Bh7153pqvtN2jCDXdFX7LQ%3D%3D.3gxQLYZQqyPz3%2FGeWMUjpurq%2F0NtoWOsA%2ByiSZcOlU%2FsnMFH%2FKyMBumWyUOmA5TX" rel="nofollow">使用Array.isArray更好地检查数组</a></p>
<p><a href="https://link.segmentfault.com/?enc=Wz2cxlGCNit05TUYp5vZxg%3D%3D.A9cZmf1M8mp2PA%2FaoEm4XXZczIJlR4Po4%2BoM9Dygzz6YpJnaZngkiNWuhLWSyZfsMYZriVNJ5KE95ylYY6yNXul2NpQgoW2WdqZduY%2BIu2M%3D" rel="nofollow">Vue.js应用性能优化一</a></p>
<p><a href="https://link.segmentfault.com/?enc=aeI1I8fcjU1fE0G%2FrN1xUg%3D%3D.xl4PRyrdShQlpsUQ10CDdFDl9iLrZjJafOouv9Ku%2BpHOQ4IkFw%2BxNIOy3leZJsP6OldPcfDiqXo8ClnTmwh66%2FSEiMMGu8OMokgkda3vWKE%3D" rel="nofollow">Vue.js应用性能优化二</a></p>
<p><a href="https://link.segmentfault.com/?enc=Jf13hOO7k9ZAcilzTcZhjw%3D%3D.G2ZqFkCgiexpnSI7JP%2BpF%2BtwN6g7W7O5cyHq5RD7GUH2Z1a%2FVzU3IedU4fiuUess00w2BgDTGgTnKW%2Fhp99caUhHLHi51qhWlX0aUGKikw8%3D" rel="nofollow">Vue.js应用性能优化三</a></p>
<p><a href="https://link.segmentfault.com/?enc=TLk0PnqimcR7d9DUMoN7Uw%3D%3D.LDNKDEaojIgHnbvU1SLpThuOOVgRvFQolAYh26iv418SYLSGs7Qpih7Y4j3N0uUTMpErwDTUd6UZrtl8jaBLvL5o%2BZFkX9ZQZ6exUET5Z8c%3D" rel="nofollow">如何使用Vue.js渲染JSON中定义的动态组件</a></p>
<p><a href="https://link.segmentfault.com/?enc=g2OIknR8QgoAQFG2PxMT3Q%3D%3D.yc1KsejRiyHVp1lZYwh370U9n1XTNXAR8dr9Th%2BbVpomxt8vmhLue8L%2FrHgHazzc" rel="nofollow">构建Vue.js组件的10个技巧</a></p>
<p><img src="/img/remote/1460000020366445?w=2800&h=800" alt="" title=""></p>
构建Vue.js组件的10个技巧
https://segmentfault.com/a/1190000020366326
2019-09-12T07:22:25+08:00
2019-09-12T07:22:25+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p><img src="/img/remote/1460000020366329" alt="" title=""></p>
<h2>1. 组件可以在全局或本地加载</h2>
<p>Vue.js提供了两种加载组件的方法:一种在Vue实例全局,另一种在组件级别。两种方法都有其自身的优点。</p>
<p>全局加载组件使其可以从应用程序中的任何模板(包括子组件)访问。它减少了将全局组件导入子组件的次数。</p>
<p>此外,如果全局加载组件,将无法获得Vue注册组件错误--“did you register the component correctly?”。注意,谨慎加载全局组件。它会使您的应用程序膨胀,即使它未被使用,它仍将包含在Webpack构建的初始bundle中。</p>
<p><img src="/img/remote/1460000020366330" alt="" title=""></p>
<p>在本地加载组件使您能够隔离组件并仅在必要时加载它们。与Webpack结合使用时,只有在使用组件时才去延迟加载组件。这使您的初始应用程序文件大小更小,并减少了初始加载时间。</p>
<p><img src="/img/remote/1460000020366331" alt="" title=""></p>
<h2>2. 延迟加载/异步组件</h2>
<p>使用Webpack的动态导入延迟加载组件。 Vue支持在渲染时和代码拆分时延迟加载组件。这些优化允许您的组件代码仅在需要时加载,从而减少您的HTTP请求,文件大小,并自动为您提供性能提升。关于此功能的重要部分是它适用于全局加载和本地加载的组件。</p>
<p>全局加载异步组件:</p>
<p><img src="/img/remote/1460000020366332" alt="" title=""><br>本地加载异步组件:</p>
<p><img src="/img/remote/1460000020366333" alt="" title=""></p>
<h2>3. 必须的属性</h2>
<p>有很多方法可以为组件创建props。您可以传递表示prop名称的字符串数组,也可以传入一个带有键作为prop名称和配置对象的对象。</p>
<p>使用基于对象的方法允许您为单个 prop 修改一些配置,比如设置是否 required。required 的值是true 或 false。如果在使用组件时未设置prop,true将抛出错误,false(默认值)表示不是必须的,不抛出错误。</p>
<p>在共享组件给他人或自己使用时,准确使用 required 配置是很好的,表明这个prop很重要。</p>
<p><img src="/img/remote/1460000020366334" alt="" title=""></p>
<h2>4. 使用$emit触发自定义事件</h2>
<p>子组件和父组件之间的通信可以通过使用组件内置函数 $emit 发出自定义事件来完成。</p>
<p>$ emit函数接收 事件名称的字符串 和 可选的值两个参数。要监听事件,只需将“@eventName”添加到发出事件的组件中(即子组件使用的地方),然后传入事件处理方法。这是保持单一数据流,并使数据从子组件流向父组件的好方法。</p>
<p><img src="/img/remote/1460000020366335" alt="" title=""></p>
<p><img src="/img/remote/1460000020366336" alt="" title=""></p>
<h2>5. 从逻辑上分解组件</h2>
<p>说起来容易做起来难,如何根据一个逻辑来划分一个组件?</p>
<p>分解组件的第一种方法是基于数据变化。如果数据在组件的一个部分中不断变化,而在其他部分中不变,那么变化的组件那部分应该单独抽取出来作为独立组件。</p>
<p>原因是如果您的数据/HTML在模板的一个部分中不断变化,则需要检查和更新整个组件。但是,如果将变化的HTML放入其自己的组件中,并使用props传入数据,则只有该组件在其props更改时才会更新。</p>
<p>从逻辑上分解组件的另一种方法是可重用性。如果您拥有在整个应用程序中重复使用的HTML,图形或功能,如按钮,复选框,徽标动画,号召性用语或具有简单更改文本的图形 - 这将是一个很好的候选,抽取到一个新的组件,可以被重用。可重用组件具有易于维护的隐藏优势,因为您只需要更改一个组件,而不必在代码库中找到替换和更改多个地方。</p>
<h2>6. 验证您的props</h2>
<p>不使用字符串数组来定义props,而是使用允许配置每个prop的对象。两种非常有用的配置项目是“类型”和验证器。</p>
<p>使用类型参数,Vue将自动键入检查您的prop值。例如,如果我们期望一个Number prop但收到一个String,你会在控制台中收到类似这样的警告:</p>
<p><code>[Vue warn]: Invalid prop: type check failed for prop “count”. Expected Number</code></p>
<p>对于更复杂的验证,我们可以将函数传递给validator属性,该属性接收 prop值 作为参数并返回true或false。这非常强大,因为它允许我们针对传递给该特定属性的值编写自定义验证。</p>
<p><img src="/img/remote/1460000020366337" alt="" title=""></p>
<h2>7. 多个props绑定和覆盖</h2>
<p>如果你的组件有多个props,比如说5,6,7或更多,那么连续设置每个prop的绑定可能会变得很繁琐。幸运的是,有一种快速方法可以为组件上的所有属性设置绑定,这就是通过使用v-bind绑定对象而不是单个属性。</p>
<p>使用对象绑定的另一个好处是可以覆盖对象的任何绑定。</p>
<p>在我们的例子中,如果我们在 person 对象中将 isActive 设置为false,那么我们可以对实际person 组件执行另一个绑定,并将 isActive 设置为true而不覆盖原始对象。</p>
<p><img src="/img/remote/1460000020366338" alt="" title=""></p>
<h2>8. 修改组件中的props</h2>
<p>在某些情况下,您可能希望修改从prop传入的值。但是,这样做会给你一个警告“Avoid mutating a prop directly”,不让直接修改属性。而是使用prop值作为本地数据属性的默认值。这样做将使您能够查看原始值,但修改本地数据不会更改prop值。</p>
<p>有一个好处。使用此方法,您的本地数据属性不会对prop值产生影响,因此对父组件的prop值的任何更改都不会更新您的本地值。但是,如果您确实需要这些更新,则可以使用计算属性组合值。</p>
<p><img src="/img/remote/1460000020366339" alt="" title=""></p>
<h2>9. 测试工具中 Mount vs Shallow Mount</h2>
<p>在Vue测试工具中有两种方法可以创建和启动组件。一个是mount,另一个是shallow mount。两者都有自己的优点和缺点。</p>
<p>当您想要在组件及其子组件之间进行相互依赖的测试时,mout技术非常有效。允许您测试父组件是否按预期正确地与其子组件交互。相反,正如其名称所暗示的那样,shallow mount技术实例化并仅渲染父组件,而完全隔离而忽略其任何子组件。也就是说,mount会渲染所有父子组件,shallow mount仅仅渲染父组件。因为有时候只需要测试父组件的一些特性。</p>
<p>那么哪种方法更好?随你(由你决定。您选择的策略应取决于您可衡量的目标。试图通过完全隔离来自行测试组件,shallow mount方法效果很好。需要处理具有要确保通信的子组件的组件,那就使用mount。一个很好的选择是同时使用它们。不局限于一个混合搭配,以满足您的测试需求。</p>
<h2>10. Vue CLI的力量</h2>
<p>Vue CLI 是一个功能强大的命令行界面,允许开发人员快速利用大量可以加快工作流程的功能。</p>
<p>一个我使用很多的功能是,运行 vue serve,后边跟上一个Vue组件的路径。这样做的好处在于,您可以完全开发一个独立的组件,同时也可以对组件进行热重新加载和迭代,无需临时将新组件导入页面进行开发。</p>
<p><img src="/img/remote/1460000020366340" alt="" title=""><br>在团队工作时,您可能需要提取一个特定组件并与其他组人共享。这就引出了 Vue CLI 的下一个功能:将Vue组件导出为库的能力。调用时,Vue将自动构建单个文件组件,将CSS移动到外部CSS文件(可选,您也可以内联),以及创建 UMD 和 Common .js文件以导入到其他JS项目中。</p>
<p><img src="/img/remote/1460000020366341" alt="" title=""></p>
<hr>
<p>相关文章:</p>
<p><a href="https://link.segmentfault.com/?enc=xjkPOjqo5JAHf9WwNLvXjA%3D%3D.wEl%2FPqQvkITK1atkV6NE7PxH5YHCdVvIrVsDkqLybAlUpdR1bKhEtH7%2FBdWrEy%2F2wNAnnqykftVBqBmaxBBaWSwlzlZArRi2lVo5xQQxVq8%3D" rel="nofollow">Vue.js应用性能优化一</a></p>
<p><a href="https://link.segmentfault.com/?enc=fY60mDMLFP%2BFi8IeSDgiAQ%3D%3D.iWqXQKTKh%2BhX9J7sbn2H0N3GAcMf89HxAZQB77HsCIHN1InH2AN2JD4SkDiHmNpneCBH7v%2FpXu8P3w6I6j6g5zMDhFggnoS3d6Y6Vcgj%2F3c%3D" rel="nofollow">Vue.js应用性能优化二</a></p>
<p><a href="https://link.segmentfault.com/?enc=IPXsZrr1PWDYOl%2FIdBWwAw%3D%3D.JnobZxm1zQebA0%2Fg1%2FJ4ct1NCi05auDERdzsZyhpc0yC528SgVo85Q0f1NUg6tWYObnNAiB%2Fts2drrsKHcJCWUTCakPN%2B7ku4YHkqY1tTHc%3D" rel="nofollow">Vue.js应用性能优化三</a></p>
<p><a href="https://link.segmentfault.com/?enc=DArvS5wjc0CF%2Fg1RR1QMIw%3D%3D.q6rKnarZloiejahQFfzT3TCBVDxiw9DfVYTvBZdwixKAaYO90IdUKSvNil1Uueal89a%2FIkVS9PNf0fzzK1mq6rIbOVWndXCcRNI%2FRUxpuHs%3D" rel="nofollow">如何使用Vue.js渲染JSON中定义的动态组件</a></p>
<p><img src="/img/bVbxovn?w=2800&h=800" alt="图片描述" title="图片描述"></p>
25个JavaScript代码简写技巧(上篇)
https://segmentfault.com/a/1190000020354772
2019-09-11T08:58:35+08:00
2019-09-11T08:58:35+08:00
前端知否
https://segmentfault.com/u/qethan
44
<p><img src="/img/remote/1460000020354775?w=640&h=360" alt="" title=""><br>对于任何JavaScript开发人员来说,这篇文章很值得一读。这里记录了我多年来学习的JavaScript代码简洁写法,也给大家提供一些编码上的思考和取舍。</p>
<h2>1. 三元(三目)运算符</h2>
<p>如果只想在一行中编写if..else语句时,这是一个很好的节省代码的方式。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354776?w=640&h=657" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354777?w=640&h=280" alt="" title=""></p>
<p>嵌套版三元运算:</p>
<p><img src="/img/remote/1460000020354778?w=1364&h=438" alt="" title=""></p>
<h2>2. 短路判断简写</h2>
<p>将变量值分配给另一个变量时,您可能希望确保源变量不为null,undefined或为空。您可以编写带有多个条件的长 if 语句,也可以使用短路判断。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354779?w=640&h=218" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354780?w=640&h=305" alt="" title=""></p>
<p>再来点示例,尝试一下:</p>
<p><img src="/img/remote/1460000020354782?w=640&h=431" alt="" title=""></p>
<p>请注意,如果将variable1设置为false或0,则赋值为bar。</p>
<h2>3. 声明变量简写</h2>
<p>常规:</p>
<p><img src="/img/remote/1460000020354783?w=448&h=510" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354784?w=516&h=438" alt="" title=""></p>
<h2>4. If真值判断简写</h2>
<p>这可能是微不足道的,但值得一提。在执行“if 检查”时,有时可以省略全等运算符。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354785?w=640&h=373" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354786?w=600&h=438" alt="" title=""></p>
<p>注意:这两个例子并不完全相同,因为只要likeJavaScript是一个真值,检查就会通过。</p>
<p>这是另一个例子。如果a不等于true,那就做点什么吧。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354787?w=600&h=546" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354788?w=584&h=546" alt="" title=""></p>
<h2>5. For循环简写</h2>
<p>如果您想要纯JavaScript并且不想依赖外部库(如jQuery或lodash),这个小技巧非常有用。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354789?w=640&h=297" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354790?w=640&h=399" alt="" title=""></p>
<p>如果您只想访问索引,请执行以下操作:</p>
<p><img src="/img/remote/1460000020354791?w=640&h=399" alt="" title=""></p>
<p>如果要访问文字对象中的键,这也适用:</p>
<p><img src="/img/remote/1460000020354792?w=640&h=253" alt="" title=""></p>
<p><strong>Array.forEach</strong>简写:</p>
<p><img src="/img/remote/1460000020354793?w=640&h=372" alt="" title=""></p>
<h2>6. 短路判断赋值</h2>
<p>如果预期参数为null或undefined,我们可以简单地使用短路逻辑运算符,只需一行代码即可完成相同的操作,而不是编写六行代码来分配默认值。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354794?w=640&h=492" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354795?w=640&h=249" alt="" title=""></p>
<h2>7. 十进制基本指数</h2>
<p>你可能已经看过这个了。它本质上是一种编写没有尾随零的数字的奇特方式。例如,1e7实质上意味着1后跟7个零。它表示一个十进制基数(JavaScript解释为浮点类型)等于10,000,000。</p>
<p>常规:<br><img src="/img/remote/1460000020354796?w=640&h=328" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354797?w=640&h=567" alt="" title=""></p>
<h2>8. 对象属性简写</h2>
<p>在JavaScript中定义对象很简单。 ES6提供了一种更简单的方法来为对象分配属性。如果变量名称与对象键相同,则可以使用简写表示法。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354798?w=640&h=412" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354799?w=634&h=438" alt="" title=""></p>
<h2>9. 箭头函数简写</h2>
<p>经典函数以简单的形式易于读写,但是一旦你开始将它们嵌套在其他函数调用中,它们往往会变得有点冗长和混乱。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354800?w=640&h=665" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354801?w=640&h=353" alt="" title=""></p>
<p><strong>重要的是要注意</strong>,箭头函数内部的 this 与常规函数的不同,因此这两个示例并不严格等效。有关详细信息,请参阅有关箭头函数语法的文章。</p>
<h2>10. 隐式返回简写</h2>
<p>Return 是我们经常使用的关键字,用于返回函数的最终结果。具有单个语句的箭头函数将隐式返回其执行结果(函数必须省略大括号({})以省略return关键字)。</p>
<p>要返回多行语句(例如对象),必须使用 () 而不是 {} 来包装函数体。这可确保将代码执行为单个语句。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354802?w=640&h=355" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354803?w=640&h=390" alt="" title=""></p>
<h2>11. 默认参数值</h2>
<p>您可以使用if语句定义函数参数的默认值。在ES6中,您可以在函数声明本身中定义默认值。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354804?w=640&h=581" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354805?w=640&h=325" alt="" title=""></p>
<h2>12. 模板字符串</h2>
<p>您是否厌倦了使用 '+' 将多个变量连接成一个字符串?有没有更简单的方法?如果你能够使用ES6,那么你很幸运。您需要做的就是使用反引号,并使用 ${} 来包含变量。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354806?w=640&h=259" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354807?w=640&h=300" alt="" title=""></p>
<h2>13. 解构赋值简写</h2>
<p>如果您正在使用任何流行的Web框架,那么很有可能您将使用对象形式的数组或数据,在组件和API之间传递信息。数据对象到达组件后,您需要将其解构。</p>
<p>常规:</p>
<p><img src="/img/remote/1460000020354808?w=640&h=427" alt="" title=""></p>
<p>简写:</p>
<p><img src="/img/remote/1460000020354809?w=640&h=253" alt="" title=""></p>
<p>您甚至可以分配自己的变量名称,比如entity替换原来对象中的contact:</p>
<p><img src="/img/remote/1460000020354810?w=1428&h=438" alt="" title=""></p>
<hr>
<p><strong>相关文章:</strong></p>
<p><a href="https://segmentfault.com/a/1190000020366414">25个JavaScript代码简写技巧(下篇)</a></p>
<p><a href="https://link.segmentfault.com/?enc=uD8T72WSn2Vf9M6lzNKDrg%3D%3D.Vr4z7sdCzsmSlx9CSCwpHAkaTQsvj4%2FUqeI9EQRUGLNmezJ2YewrU5Hj928jeZKL" rel="nofollow">使用Array.isArray更好地检查数组</a></p>
<p><a href="https://link.segmentfault.com/?enc=plceEiddSz6HM%2BrPkV9Xvg%3D%3D.b0VFfjdo0k6dg6ROjgNKYi1jWiFjx0loqmA%2BZ0bgOSwuJfYfNNUqnOzMkmD4Y%2BtnPidnPhICq50hHtKwZj%2FgAna67JUnejldOB%2BcNTB%2BsLA%3D" rel="nofollow">Vue.js应用性能优化一</a></p>
<p><a href="https://link.segmentfault.com/?enc=1FgVLCq2BX4McY%2FRjJwOnQ%3D%3D.nr4TLE%2FfZtG6qTuu8cfDW3EY5qsabOq7FJjgUk15hd8PJyRg0u7mrlMuHawI7iQtfpTs32fAwPvGkHddEPk3xXf4HyQ4vOddThiFSHEttfs%3D" rel="nofollow">Vue.js应用性能优化二</a></p>
<p><a href="https://link.segmentfault.com/?enc=yCfoob1HwY1gl7P782WPFg%3D%3D.p%2Fz%2BXcuDERZPyPjddWyRmGHUae%2FxSLhafao0QbotblMM91lM5UBT7oqRmw7RfyD7POQfCmKI2Hz%2F%2Fz1gi%2B33RdL357e2%2BcLYPiNet0aV6%2Fk%3D" rel="nofollow">Vue.js应用性能优化三</a></p>
<p><a href="https://link.segmentfault.com/?enc=nehvpx884QHwlCzS1YF69g%3D%3D.JBPRi4RoHVjE5mS5wcl3L1ydMJUeIjhr6Vjp9vGWQMBUj0bFI4kbfKSLI4hOipStnd12t8tBTNe3I5QtZVQOsy1zfQDv5o2LjlqX%2FUducoo%3D" rel="nofollow">如何使用Vue.js渲染JSON中定义的动态组件</a></p>
<p><a href="https://link.segmentfault.com/?enc=M010zYmiakv5sjiCzEijfg%3D%3D.jW5sPcvmhpRBDDlEcFt8e%2BN7BDxyi1i9FdWDel9J5Ioe96BOlkd8o7%2Blgc9uU0mA" rel="nofollow">构建Vue.js组件的10个技巧</a></p>
<p><img src="/img/remote/1460000020354811?w=2800&h=800" alt="" title=""></p>
使用React Hooks进行状态管理 - 无Redux和Context API
https://segmentfault.com/a/1190000020354594
2019-09-11T08:43:13+08:00
2019-09-11T08:43:13+08:00
前端知否
https://segmentfault.com/u/qethan
3
<p>React Hooks比你想象的更强大。</p>
<p>现在,我们将探索和开发一个自定义Hook来管理全局状态 - 比Redux更容易使用的方法,并且比Context API更高效。</p>
<h2>Hooks基础</h2>
<p>如果你已经很熟悉React Hooks,那么可以直接跳过这部分。</p>
<p>useState()</p>
<p>在Hooks之前,功能组件没有状态。现在,使用useState(),我们可以让功能组件拥有状态。</p>
<p><img src="/img/remote/1460000020354597" alt="" title=""><br><strong>useState()会返回一个数组</strong>。上面数组的<strong>第一项</strong>是一个可以访问状态值的变量。<strong>第二项</strong>是一个能够更新组件状态,而且影响dom变化的函数。</p>
<p><img src="/img/remote/1460000020354598" alt="" title=""></p>
<p><strong>useEffect()</strong></p>
<p>类似Component组件,使用生命周期方法来管理副作用,例如componentDidMount()。useEffect() 函数允许您在<strong>函数组件</strong>中执行副作用。</p>
<p><strong>默认情况下,useEffect在每次完成渲染后运行</strong>。但是,您可以选择仅在某些值发生更改时触发它,并将一个数组作为第二个可选参数传递。</p>
<p><img src="/img/remote/1460000020354599" alt="" title=""></p>
<p>要获得与 componentDidMount() 相同的结果,我们可以发送一个空数组。空数组不会改变,useEffect只会运行一次。</p>
<p><strong>共享states</strong></p>
<p>我们可以看到Hooks状态与类组件状态完全相同。组件的每个实例都有自己的状态。</p>
<p>为了组件之间共享状态,我们将创建一个自定义Hook。</p>
<p><img src="/img/remote/1460000020354600" alt="" title=""><br>这个想法是创建一个监听器数组,只有一个状态对象。每当一个组件更改状态时,所有订阅的组件都会触发其 setState() 函数并进行更新。</p>
<p>我们可以通过调用自定义<strong>Hook中的 useState()</strong> 来实现。我们将 setState() 函数添加到一个监听器数组,并返回一个函数用来更新state 和 运行所有监听器函数。</p>
<p>现在已经有了 <strong>use-global-hook 这个npm包</strong>,您可以通过包文档中的示例了解如何使用它。但是,从现在开始,我们将专注于它是怎么实现的。</p>
<h2>第一个版本</h2>
<p><img src="/img/remote/1460000020354601" alt="" title=""><br><strong>在组件中使用它:</strong></p>
<p><img src="/img/remote/1460000020354602" alt="" title=""><br>第一个版本已经可以共享状态。您可以在应用程序中添加任意数量的Counter组件,它们都具有相同的全局状态。</p>
<h2>但我们可以做得更好</h2>
<p>我想在第一个版本中改进的内容:</p>
<ul>
<li>我想在卸载组件时从数组中删除监听器。</li>
<li>我想让它更通用,可以在其他项目中使用。</li>
<li>我想通过参数设置 initialState。</li>
<li>我想使用更多函数式编程。</li>
</ul>
<p><strong>在组件卸载之前调用一个函数</strong></p>
<p>我们了解到,使用空数组调用 useEffect(function,[])与componentDidMount() 具有相同的用途。但是,如果第一个参数中使用的函数返回另一个函数,则第二个函数将在卸载组件之前触发。完全像 componentWillUnmount()。</p>
<p>这是从监听器数组中删除组件的理想位置。</p>
<p><img src="/img/remote/1460000020354603" alt="" title=""></p>
<h2>第二个版本</h2>
<p>除了最后的修改,我们还将:</p>
<ul>
<li>将React设置为参数,不再导入它。</li>
<li>不导出 customHook,而是导出根据 initialState 参数返回新 customHook()。</li>
<li>创建一个包含state和 setState() 函数的store对象。</li>
<li>替换 setState() 和 useCustom() 的上下文为store。</li>
</ul>
<p><img src="/img/remote/1460000020354604" alt="" title=""></p>
<p>因为我们现在有一个更通用的Hook,我们必须在store文件中设置它。</p>
<p><img src="/img/remote/1460000020354605" alt="" title=""></p>
<h2>将actions与组件分开</h2>
<p>如果您曾经使用过复杂的状态管理库,那么您就知道直接在组件中操作全局状态并不是最好的做法。</p>
<p>最好的方法是,通过创建操作状态的action来分离业务逻辑。出于这个原因,我希望我们的解决方案的最后一个版本中,组件不能访问setState()去操作状态,而是通过actions。</p>
<p>为了解决这个问题,我们的 useGlobalHook(React,initialState,actions) 函数将接收一个action对象作为第三个参数。关于这一点,我想补充一些东西:</p>
<p>Actions将有权访问store对象。因此,action可以使用 store.state 读取状态,通过store.setState() 写入状态,甚至使用 state.actions 调用其他操作。</p>
<p>对于组织,actions对象可以包含其他actions的子对象。因此,您可能调用 actions.addToCounter(amount) ,<strong>或者一个action子对象, 调用actions.counter.add(amount) </strong>。</p>
<h2>最终版本</h2>
<p>以下是NPM包use-global-hook中的内容。</p>
<p><img src="/img/remote/1460000020354606" alt="" title=""></p>
<h2>最后,一个实战案例</h2>
<p>src/styles.css<br><img src="/img/remote/1460000020354607" alt="" title=""></p>
<p>src/index.js</p>
<p><img src="/img/remote/1460000020354608" alt="" title=""></p>
<p>src/store/index.js</p>
<p><img src="/img/remote/1460000020354609" alt="" title=""></p>
<p>src/components/Counters.js</p>
<p><img src="/img/remote/1460000020354610" alt="" title=""></p>
<p>src/components/Repos.js</p>
<p><img src="/img/remote/1460000020354611" alt="" title=""></p>
<p>src/components/SearchForm.js</p>
<p><img src="/img/remote/1460000020354612" alt="" title=""></p>
<p>src/actions/counter.js</p>
<p><img src="/img/remote/1460000020354613" alt="" title=""></p>
<p>src/actions/github.js</p>
<p><img src="/img/remote/1460000020354614" alt="" title=""></p>
<p>src/actions/index.js</p>
<p><img src="/img/remote/1460000020354616" alt="" title=""></p>
<p>package.json</p>
<p><img src="/img/remote/1460000020354617" alt="" title=""></p>
JavaScript中如何反转数组
https://segmentfault.com/a/1190000020338983
2019-09-09T21:58:39+08:00
2019-09-09T21:58:39+08:00
前端知否
https://segmentfault.com/u/qethan
5
<p><img src="/img/remote/1460000020338986?w=1200&h=600" alt="" title=""><br>如果您需要反转数组元素的顺序,可以使用数组方法reverse()⏪,如下所示:</p>
<p><img src="/img/remote/1460000020338987?w=996&h=658" alt="" title=""></p>
<h2>修改原始数组</h2>
<p>需要注意的一点是它会改变原始数组。</p>
<p><img src="/img/remote/1460000020338988?w=1092&h=626" alt="" title=""></p>
<h2>如何在不改变原始数组的情况下反转数组</h2>
<p>以下是一些不会改变原始数组的做法。我们来看看</p>
<p>1.使用 slice 和 reverse</p>
<p><img src="/img/remote/1460000020338989?w=1108&h=626" alt="" title=""><br>2.使用 ...扩展运算符 和 reverse</p>
<p><img src="/img/remote/1460000020338990?w=1060&h=626" alt="" title=""><br>3.使用 reduce 和 ...扩展运算符</p>
<p><img src="/img/remote/1460000020338991?w=1362&h=698" alt="" title=""><br>4.使用 reduceRight 和 ...扩展运算符</p>
<p><img src="/img/remote/1460000020338992?w=1448&h=734" alt="" title=""><br>5.或者使用push<br><img src="/img/remote/1460000020338993?w=1448&h=734" alt="" title=""></p>
<hr>
<p>参考资源:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=MiFH%2FsaTJhasBxKjb8ovqw%3D%3D.olscXuEyMrvauZ%2BMIHQtZfIk1PDibrkPWqdYmWlp8b%2BHhjb6oDMV46Z81ZSD2%2BREnAL3SxN8sR8IvS0CL%2BR6Nj%2FF6rvJ0uBJcBwk44IJN1wocgN09PUgf8EcNXkKj8C8" rel="nofollow">MDN Web Docs: reverse</a></li>
<li><a href="https://link.segmentfault.com/?enc=pnOtBksKUD8IdQC8UPDIlA%3D%3D.2oZXKrZDky2YWjrC65qzPDACJ52aqA6gTcFOle4vLUKRINvk0HlS68PpBY2sFiAldYVMTmnpTLa%2Fbiaqskj%2F%2BA%3D%3D" rel="nofollow">w3schools: reverse</a></li>
<li><a href="https://link.segmentfault.com/?enc=t2QDuld23HBRgYWgUAZlDQ%3D%3D.rVJgzAHz%2BtAPLCubZgM3Vdd0xXwKpYYFpPSJz%2BmF68YqowUYhkHJSKvYvEmooBHAzxtdsTHKbshVLWIyJP1vDAfIQFPiMb0espOnw6dj46wTOJK%2BeXX2X3WrnJFPu0n8kjcRQ9vv9Bb5JVjfds7SDw%3D%3D" rel="nofollow">Stack Overflow: Reverse array in Javascript without mutating original array</a></li>
</ul>
<p><img src="/img/remote/1460000020338994?w=2800&h=800" alt="" title=""><br>还可以关注头条号:「前端知否」</p>
使用React hooks处理复杂表单状态数据
https://segmentfault.com/a/1190000020328291
2019-09-09T07:44:41+08:00
2019-09-09T07:44:41+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p><img src="/img/remote/1460000020328294" alt="使用hooks替换this.setState()" title="使用hooks替换this.setState()"><br><code>使用hooks替换this.setState()</code></p>
<p>自从<strong>React hooks</strong>*发布以来已经有一段时间了,我很喜欢这个特性。这个hooks把我勾上了!</p>
<p><strong>Hooks</strong>允许我们创建更小,可组合,可重用,更易管理的React组件。</p>
<p>您可能正在使用Hooks的一个用例是:使用useState或useReducer管理表单状态。</p>
<p>让我们考虑一个场景,您必须管理具有多个输入的复杂表单状态,这些表单输入可以是几种不同的类型,如文本,数字,日期输入。<strong>表单状态甚至可以具有嵌套信息</strong>,例如用户的地址信息,它具有子字段,例如address.addressLine1,address.addressLine2等。</p>
<p>也许您还必须根据当前状态更新表单状态,例如<strong>toggle切换按钮</strong>。</p>
<p>现在,如果您对每个单独的表单字段使用<strong>useState</strong>,那么您可以<strong>根据当前状态计算新状态</strong>。</p>
<p><img src="/img/remote/1460000020328295" alt="" title=""><br>但是,如果你有太多单独的表单字段,比如100+,那么这种方法并不友好。</p>
<p>脑补一下...</p>
<p><img src="/img/remote/1460000020328296" alt="" title=""><br><strong>编写单独的useStates,然后为每个字段使用单独的更新函数是不切实际的</strong>。我们的另一个选择是hook,<strong>useReducer</strong>。</p>
<p>我们来看一个例子。</p>
<p><img src="/img/remote/1460000020328297" alt="" title=""><br>呃,不好。<strong>您不可能为reducer中的n个表单字段编写每个用例</strong>。</p>
<p>但是,<strong>useReducer中使用的reducer函数只是一个返回更新状态对象的普通函数</strong>。所以,我们可以做得更好。</p>
<p><img src="/img/remote/1460000020328298" alt="" title=""><br>这样看起来,reducer简洁干净多了。</p>
<p>但是,现在reducer<strong>更新参数中如果有回调函数</strong>,则不能基于当前状态计算新状态,因为当前state没有传递给回调函数作为参数。就像我们在useState一样:<br><img src="/img/remote/1460000020328299" alt="" title=""></p>
<p><code>useState中的更新函数可以基于prev参数计算新状态</code></p>
<p>另外,如何更新嵌套状态如address.addressLine1,address.pinCode。</p>
<p>我们通过使用不那么理想的方法进行了很多关于管理复杂表单状态的讨论。让我告诉你解决方案。</p>
<p><img src="/img/remote/1460000020328300" alt="" title=""><br>因此,这是处理复杂表单场景的完整源代码。</p>
<p>我将稍微解释一下reducer(<strong>enhancedReducer</strong>)函数。</p>
<p>reducer函数接收两个参数,<strong>第一个参数是更新前的当前状态</strong>。当您调用updateState / dispatch函数来更新reducer状态时,<strong>将自动提供此参数。 reducer函数的第二个参数是用于更新state。它不一定是采用{type:'something',payload:'something'}形式的典型redux动作对象。它甚至可以是任何东西,数字,字符串,对象或函数</strong>。</p>
<p>这就是我们的做法。<strong>如果updateArg是一个函数,我们用当前状态调用它来计算新函数。无论我们从这个函数返回什么对象都成为我们的新状态</strong>。</p>
<p>如果updateArg是一个普通的旧Javascript对象,那么有两种情况。</p>
<p>1:<strong>该对象没有_path和_value属性,因此是一个普通的更新对象</strong>,就可以像使用this.setState一样。因此,您可以使用包含要更新的状态片段的新对象调用updateState,并将其与旧状态合并并返回新状态。</p>
<p>2:<strong>对象具有_path和_value属性</strong>- 当使用具有这两个属性的对象作为参数,调用更新回调函数时。我们将此视为一种特殊情况,其中_path表示嵌套的字段路径。在字符串形式中,例如:<strong>'address.pinCode'或表示路径['address','pinCode']的数组</strong>。</p>
<p>我们如何使用此类路径表示来更新对象中的嵌套字段?我们将使用lodash的set方法。它接受路径表单作为更新和对象的有效输入。</p>
<p><img src="/img/remote/1460000020328301" alt="" title=""><br>但是,set方法就地改变对象并且不返回新副本,但在React世界中,更改检测取决于Immutability(不可变)。<strong>需要一个全新的数据副本,在内存中有一个新位置来触发渲染。</strong></p>
<p>为了绕过这个,我们使用immer,来轻松地处理Javascript对象的不变性。</p>
<p><img src="/img/remote/1460000020328302" alt="" title=""><br><strong>immer中的produce函数</strong>将对象作为其<strong>第一个参数</strong>进行处理,在我们的例子中是<strong>当前状态</strong>,它的第二个参数是一个函数,<br><strong>它接收对象的草稿副本以进行mutate,无论你在这个函数内修改了什么草稿状态,是在副本上完成的,而不是实际的输入对象状态。</strong> 然后,它会自动返回包含更新数据的新对象。</p>
<p>这就是我们的增强版reducer。</p>
<p>安装一下依赖,就可以跑起来了。</p>
<p><img src="/img/remote/1460000020328303" alt="" title=""></p>
<p><strong>PS</strong>:在enhancedReducer中可以处理更多边缘情况,动态字段映射也可以缩短一些代码,减少代码重复和其他一些事情。</p>
<hr>
<p>参考资源:</p>
<p>Lodash文档: <a href="https://link.segmentfault.com/?enc=ebBkd6SreghMDdbaDhTk7w%3D%3D.wZKAWM1GZbhf4fUAITvm7myG5Sr7Ab01prtPn6bU8jjTBQBr8tnRAO1PpyMy4Of6mEzlTqlikavJ6AZwiSk97X0cf%2FP%2Fy2ZjrFqArHd%2Bdtge0qc6xsWUuRUfCnl%2FTCuE" rel="nofollow">https://lodash.com/docs/4.17....</a></p>
<p>immerjs/immer: <a href="https://link.segmentfault.com/?enc=Z80nTg%2FF6j7SQvWEOLdfCQ%3D%3D.vKJCMCwzIg%2FFS%2BlQU3NHsCHmabWl0Qv2DFSD7zQ%2FfGpXypTUbD8UoWSZ1Mcnb78Coa78xtBO4QQyK6Qeoth3yuExoq1zWuuUB9Z2gteoRZp5evLCxJ5i6eh71K7bamdb" rel="nofollow">https://github.com/immerjs/im...</a></p>
<p><img src="/img/remote/1460000020328304" alt="" title=""></p>
<p>还可以关注头条号:「前端知否」</p>
Vue.js应用性能优化三
https://segmentfault.com/a/1190000020319908
2019-09-07T16:20:44+08:00
2019-09-07T16:20:44+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac0497093377?w=2004&h=1220&f=png&s=148065" alt="" title=""></p>
<p>在上一篇<a href="https://segmentfault.com/a/1190000020316667">Vue.js应用性能优化二</a>中,我们学习了足够强大的模式,可以显着提高应用程序的性能 - 按照路由分割代码。虽然按照路由拆分代码非常有用,但在用户访问我们的站点后,仍然有很多内部代码不需要。在本系列的这一部分中,我们将重点关注代码拆分我们的状态管理 - Vuex模块。</p>
<h2>两种类型的Vuex模块</h2>
<p>在我们进一步了解如何懒加载Vuex模块之前,您需要注意一件重要的事情。您需要了解注册Vuex模块的方法有哪些,以及它们的优缺点。</p>
<p><strong>静态Vuex模块</strong>在Store初始化期间声明。以下是显式创建的静态模块的示例:</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac198e45ea50?w=1260&h=662&f=png&s=124653" alt="" title=""></p>
<p>上面的代码将创建一个带有静态模块<strong>userAccountModule</strong>的新Vuex Store。静态模块不能取消注册(也不能延迟注册),并且在Store初始化后不能更改它们的结构(不是状态!)。</p>
<p>虽然这种限制对于大多数模块来说都不是问题,并且在一个地方声明,那么所有与数据相关的东西都可以保存在一个地方。但这种方法存在一些缺点。</p>
<p>假设我们的应用程序中有一个Admin Dashboard,它关联一个专用Vuex模块<strong>adminModule</strong>。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac368c052e65?w=1260&h=734&f=png&s=147507" alt="" title=""><br>你可以想象这样的模块可能非常庞大。尽管仪表板将仅由一小部分用户和应用程序的受限区域(假设在/admin路径下)使用,由于静态Vuex模块的集中注册,它的所有代码都将在主程序包中。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac3ad3797523?w=1418&h=882&f=png&s=26293" alt="" title=""><br>这当然不是我们想要的结果。我们需要一种方法只在/admin路由中加载这个模块。您可能已经猜到静态模块无法满足我们的需求。所有静态模块都需要在创建Vuex Store时注册,因此以后无法注册。</p>
<p>这是动态模块可以帮助我们的地方!</p>
<p>在创建Vuex Store后,可以注册与静态模块相反的<strong>动态模块</strong>。这个简洁的功能意味着我们不需要在应用程序初始化时下载动态模块,并且可以将其打包在不同的代码块中,或者在需要时懒加载。</p>
<p>首先让我们看一下以前的代码使用<strong>动态注册管理模块</strong>之后的样子。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac45068169de?w=1260&h=770&f=png&s=156216" alt="" title=""><br>不是将adminModule对象直接传递到我们store的modules属性,而是在创建Store之后,使用<strong>registerModule</strong>方法注册它。</p>
<p>动态注册不需要在模块内部进行任何更改,因此可以静态或动态地注册任何Vuex模块。</p>
<p>当然,在目前的形式下,这个动态注册的模块并没有给我们任何好处。</p>
<h2>适当的代码分割Vuex模块</h2>
<p>现在我们知道如何动态注册管理模块,我们当然可以尝试将它的代码放入/admin route bundle。</p>
<p>让我们暂时停下来,简要了解我们正在使用的应用程序。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac51e0280e00?w=928&h=575&f=png&s=29429" alt="" title=""></p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac54742e6ed7?w=1108&h=806&f=png&s=161861" alt="" title=""><br>在router.js中,我们有两个懒加载的代码分割路由。使用我们上面看到的代码,我们的admin Vuex模块仍然在主app.js包中,因为它是store.js中的静态导入。</p>
<p>让我们解决这个问题,并将此模块仅交付给输入/admin路由的用户,以便其他人不会下载冗余代码。</p>
<p>为此,我们将在/admin路由组件中加载管理模块,而不是导入并注册它在store.js。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac604a606cb7?w=1260&h=1166&f=png&s=212869" alt="" title=""><br>我们来看看发生了什么!</p>
<p>我们在Admin.vue(/admin route)mounted后,导入和注册admin Store。稍后在代码中,<strong>一旦用户退出管理面板,我们就会取消注册该模块,以防止同一模块的多次注册。</strong></p>
<p>现在因为admin模块是在Admin.vue而不是store.js中导入的,所以它将与代码分割的Admin.vue打包在一起!</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac6ac1932716?w=911&h=577&f=png&s=29262" alt="" title=""></p>
<p><strong>重要说明:如果您正在使用SSR模式,请确保在mounted钩子中注册模块。否则它可能导致内存泄漏,因为在服务器端没有beforeDestroy钩子。</strong></p>
<p>现在我们知道如何动态注册Vuex模块,并将路由模块分发到适当的包中。下边让我们来看看稍微复杂一些的用例。</p>
<h2>延迟加载Vuex模块</h2>
<p>假设我们在Home.vue上有推荐部分,我们希望展示一些用户推荐评语。但是我们不想在用户进入我们的网站后立即显示它们。只有在用户需要时才能显示它们。我们可以添加“显示推荐”按钮,点击后会加载并显示其下方的推荐。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac7a92683574?w=1183&h=88&f=png&s=3629" alt="" title=""><br>要存储推荐数据,我们还需要一个Vuex模块。我们称之为推荐模块。该模块将负责显示以前添加的推荐和添加新推荐。我们不需要了解实现细节。</p>
<p>我们希望只有用户点击按钮才去请求下载推荐模块代码,因为之前不需要它。让我们看看如何利用动态模块注册和动态导入来实现此功能。 Testimonials.vue是Home.vue中的一个组件。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac7e45febed8?w=1502&h=1314&f=png&s=202041" alt="" title=""><br>当用户单击Show Testimonials按钮时,将调用getTestimonials()方法。它负责调用getTestimonialsModule()来获取testimonials.js。一旦promise resovled(这意味着加载了模块),我们就会动态注册它并调度负责获取推荐的动作action。</p>
<p>由于动态导入,testimonials.js内容被打包到一个单独的文件中,只有在调用getTestimonialsModule方法时才会下载该文件。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0ac824d94f6ca?w=1698&h=868&f=png&s=53558" alt="" title=""></p>
<p>当我们需要退出管理面板时,在beforeDestroy生命周期钩子中取消注册模块,这样如果我们再次进入,就不会重复注册模块。</p>
<h2>总结</h2>
<p>即使静态Vuex模块注册对于大多数用例来说已足够,但在某些情况下我们可能希望使用动态注册。</p>
<p>如果只在特定路由上需要模块,那么我们可以在适当的路由组件中动态注册它,这样它就不会在主bundle中存在。</p>
<p>如果只是在一些交互之后才需要模块,那么我们需要以适当的方法,将<strong>动态注册模块</strong>与<strong>动态导入和懒加载模块</strong>结合起来使用。</p>
<p><strong>能够对Vuex模块进行代码分割,动态注册是一种强大的能力。</strong> 我们在应用程序中处理的与数据相关的操作越多,就可以在bundle大小方面节省更多成本。</p>
<p>在本系列的下一部分中,我们将学习如何懒加载单个组件,更重要的是,应该懒加载哪些组件。</p>
<p><img src="https://user-gold-cdn.xitu.io/2019/9/7/16d0aca17016fd39?w=2800&h=800&f=jpeg&s=153650" alt="" title=""></p>
Vue.js应用性能优化二
https://segmentfault.com/a/1190000020316667
2019-09-07T10:15:05+08:00
2019-09-07T10:15:05+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p><img src="/img/remote/1460000020316670" alt="" title=""></p>
<p>在<a href="https://link.segmentfault.com/?enc=96cfNTvtyzAvPiRFxAXuuA%3D%3D.eugPYiFjAWpGlVyukrk7uVpKzupkqbA1587IYSwdlJ%2F%2Fh1Z%2F%2F6z8ARc783R9aMP%2B" rel="nofollow">Vue.js应用性能优化一</a>文章中,我们了解了代码拆分是什么,它如何与Webpack一起工作以及如何在Vue应用程序中使用延迟加载来使用它。现在我们将深入研究代码,并学习最有用的Vue.js应用程序代码分割模式。</p>
<p>通过使用以下技术,我们能够将初始bundle的大小减少70%并使其在眨眼间加载。</p>
<h2>应用规模增长带来的问题</h2>
<p>Vue-router是一个库,允许自然地将我们的Web应用程序拆分为单独的页面。每个页面都是与某个特定URL路径关联的路由。</p>
<p>知道这一点,我们有一个简单的应用程序,具有以下结构:</p>
<p><img src="/img/remote/1460000020316671" alt="" title=""></p>
<p><img src="/img/remote/1460000020316672" alt="" title=""><br><code>图片说明:所有js代码都被打包到一个文件 — app.js</code></p>
<p>您可能已经注意到,根据我们访问的路由,我们可能不需要Home.vue或About.vue(依赖lodash)但它们都在相同的app.js包中,无论路由用户是什么,都会被下载访问。浪费下载和解析时间!</p>
<p>如果我们只是多下载了一个路由,那这并不是什么大问题。但你可以想象,随着这个应用程序越来越大,任何新的添加都意味着在首次访问时下载更大的bundle。</p>
<p><strong>当1秒的时间足以让用户心里犯嘀咕,并且(可能)离开我们的网站时,这是不可接受的!</strong></p>
<h2>不同延迟,用户的心理反应:</h2>
<ul>
<li>0 - 100ms,感觉很快</li>
<li>100 - 300ms 可以接受的延迟等待</li>
<li>300 - 1000ms 盯着网页,明显感觉到延迟</li>
<li>1000+ms 心里开始嘀咕,要不要离开</li>
<li>10,000+ms 先去别的地方逛逛吧,稍后见</li>
</ul>
<p>使用vue-router进行基于路由的代码分割<br>为了避免弄巧成拙,我们只需要使用我们在前一篇文章中学习的动态导入语法,为每个路由创建单独的bundle。</p>
<p>像Vue.js中的其他所有东西一样 - 它非常简单。我们只需要在那里动态导入组件,而不是将组件直接导入到路径对象中。仅当解析给定路线时才会下载路线组件。</p>
<p><strong>所以不要像这样静态导入路径组件:</strong></p>
<p><img src="/img/remote/1460000020316673" alt="" title=""></p>
<p>我们需要<strong>动态导入</strong>它,这将创建一个包含此路由的新bundle作为入口点:</p>
<p><img src="/img/remote/1460000020316674" alt="" title=""></p>
<p>知道了这一点,让我们看看我们的捆绑和路由如何与动态导入一样:</p>
<p><img src="/img/remote/1460000020316675" alt="" title=""><br><code>图片说明:home.js,about.js 都被拆分成单独的bundle</code></p>
<p><img src="/img/remote/1460000020316676" alt="" title=""></p>
<p>通过此设置,webpack将创建三个包:</p>
<ul>
<li>app.js - 我们的主要包含应用程序入口点(main.js)和每个路由所需的库/组件</li>
<li>home.js - home页面bundle,只有在输入/路径时才会下载</li>
<li>about.js - about页面bundle(依赖 lodash),只有在输入路径为/about时才会下载</li>
</ul>
<p><strong>bundle名称不是webpack生成的真实名称,以便于理解。 Webapck实际上正在生成类似0.js 1.js等,具体取决于您的webpack配置。</strong></p>
<p>这种技术几乎适用于所有应用,并且可以提供非常好的效果。</p>
<p>在许多情况下,基于路由的代码拆分将解决您的所有性能问题,并且可以在几分钟内应用于几乎任何应用程序!</p>
<p>Vue生态系统中的代码拆分<br>您可能正在使用Nuxt或vue-cli来创建您的应用程序。如果是这样,重要的是要知道它们都有关于代码拆分的一些自定义行为:</p>
<p>在<strong>vue-cli 3</strong>中,默认情况下<strong>将预取所有延迟加载的块</strong>。我们将在稍后学习如何使用预取 <strong>(prefetching)</strong>。</p>
<p>在<strong>Nuxt</strong>中,如果我们使用Nuxt路由系统,所有页面路由都是开箱即用的</p>
<p>现在让我们来看看非常流行且常用的反模式,它会减弱基于路由的代码拆分效果。</p>
<h2>Vendor bundle 反模式</h2>
<p><strong>vendor包(第三方类库)</strong> 通常用于包含node_modules中所有模块的单独js文件的上下文中。</p>
<p>虽然可以将所有内容放在这里,将所有依赖项保存在一个地方并缓存它们,感觉上可能很好,但这种方法带来了将所有路由打包在一起时遇到的相同问题:</p>
<p><img src="/img/remote/1460000020316677" alt="" title=""><br><code>图片说明:黄色模块,都是vendor</code></p>
<p>你看到了问题吗?即使我们只需要在一个路由中使用lodash(它是其中一个依赖项),但是现在它被捆绑在vendor.js中以及所有其他依赖项中,因此它将始终下载。</p>
<p>将所有依赖项打包在一个文件中听起来很好,但会使您的应用加载时间更长。我们可以做得更好!</p>
<p>如果按照基于路由的代码分割方式,会确保所有依赖的代码被下载。但同时也会重复下载一些相同的依赖。比如两个路由页面中都依赖lodash的情况。</p>
<p>让我们假设Home.vue也需要lodash。</p>
<p><img src="/img/remote/1460000020316678" alt="" title=""></p>
<p><strong>在这种情况下,从/about(About.vue)导航到/(Home.vue)将最终导致两次下载lodash。</strong></p>
<p>它仍然比下载大量的冗余代码更好,但是如果我们已经有了这种依赖,那么重用它就没有意义了,对吧?</p>
<p>这是webpack splitChunksPlugin可以帮助我们的地方。只需将这几行添加到webpack配置中,我们就会将公共依赖项分组到一个单独的包中,以便共享它们。再说的清楚一些,<strong>几个路由页面共享的依赖,会单独抽取出来打包,而其他页面不会下载这个共享包。整个应用有一个全局共享的vendor bundle。</strong></p>
<p><img src="/img/remote/1460000020316679" alt="" title=""></p>
<p>在<strong>chunks</strong>属性中,我们只是告诉webpack应该优化哪些代码块。您可能已经猜到了,将此属性设置为all,这意味着它应该优化所有代码块。</p>
<p>您可以在webpack文档中阅读有关此过程的更多信息</p>
<h2>总结</h2>
<p><strong>按路由拆分代码</strong>是降低初始bundle大小的最佳(也是最简单)方法之一。在下一部分中,我们将了解所有其他小部件(Vuex存储和单个组件),这些部件也可以从主bundle中减掉并且懒加载。</p>
<p><img src="/img/remote/1460000020316680" alt="" title=""></p>
Vue.js应用性能优化一
https://segmentfault.com/a/1190000020312980
2019-09-06T17:37:09+08:00
2019-09-06T17:37:09+08:00
前端知否
https://segmentfault.com/u/qethan
3
<p><img src="/img/bVbxors?w=751&h=512" alt="图片描述" title="图片描述"></p>
<p>虽然现在网络环境和电子设备变得越来越好,但是保持应用程序快速加载变得越来越困难。在本系列中,我将深入研究我们在实践中使用的Vue性能优化技术,并且您可以在Vue.js应用程序中使用它们,使应用程序快速加载并顺利执行。我的目标是让这个系列成为关于Vue应用程序性能的全面而完整的指南。</p>
<blockquote><strong>Webpack bundling 打包机制</strong></blockquote>
<p>本系列中的大多数技巧都将集中在如何使我们的JS包更小。要了解它,首先我们需要了解Webpack如何打包所有文件。</p>
<p>打包我们的资源(assets)时,Webpack会创建一个依赖图。它是一个基于导入链接所有文件的图表。假设我们在webpack配置中指定了一个名为main.js的文件作为入口点,它将成为我们依赖图的根。现在,我们将在此文件中导入的每个js模块将成为图中的节点,并且在这些节点中导入的每个模块都将成为其节点。</p>
<p><img src="/img/bVbxorI?w=454&h=452" alt="图片描述" title="图片描述"></p>
<p>Webpack使用此依赖关系图来检测它应该包含在输出包中的文件。输出包只是一个(或我们将在后面的部分中看到的多个)javascript文件,其中包含依赖图中的所有模块。</p>
<p>这个bundle包本质上是我们整个应用程序的JavaScript。</p>
<p>我们可以用下图来说明这个过程:</p>
<p><img src="/img/bVbsqZK?w=1312&h=574" alt="图片描述" title="图片描述"></p>
<p>现在我们知道webpack是如何打包的,很明显我们的项目越大,初始JavaScript包就越大。</p>
<p>越大的初始bundle,下载和解析,我们的用户所需的时间就越长。用户必须等待的时间越长,他离开我们网站的可能性就越大。事实上,据搜索引擎统计,53%的移动用户留下的页面加载时间超过3秒。</p>
<p>总而言之,更大的bundle=更少的用户,这可以直接转化为潜在收入的损失。<strong>有关案例统计,延迟2秒导致每位访客的收入损失4.3%。</strong></p>
<blockquote><strong>延迟加载</strong></blockquote>
<p>那么当我们仍然需要添加新功能并改进我们的应用程序时,我们如何削减budle包大小?答案很简单 - <strong>延迟加载和代码分割。</strong></p>
<p>顾名思义,<strong>延迟加载是一个懒惰地加载应用程序的部分(块)的过程。</strong>换句话说 - 只有在我们真正需要它们时加载它们。代码拆分只是将应用程序拆分为多个延迟加载的代码块的一种处理方式。</p>
<p><img src="/img/bVbsqZT?w=1036&h=302" alt="图片描述" title="图片描述"></p>
<p>在大多数情况下,当用户访问您的网站时,您不需要立即使用Javascript包中的所有代码。</p>
<p>例如,我们不需要花费宝贵的资源来为首次访问我们网站的访客加载“我的页面”区域。或者可能存在每个页面上不需要的模态,工具提示和其他零件和组件。</p>
<p>当只需要几个部分时,在每个页面加载时下载,解析和执行整个包的所有内容都是浪费。</p>
<p>延迟加载允许我们拆分捆绑包并仅提供所需的部分,这样用户就不会浪费时间下载和解析不会使用的代码。</p>
<p><strong>要查看我们网站中实际使用了多少JavaScript代码</strong>,我们可以转到devtools - > cmd(ctrl) + shift + p - >输入coverage - >点击Performance instrument coverage 。现在我们应该能够看到实际使用了多少下载的代码。</p>
<p><img src="/img/bVbxosy?w=2400&h=1011" alt="图片描述" title="图片描述"></p>
<p><strong>标记为红色的所有内容都是当前路由上不需要的东西,可以延迟加载。</strong>如果您正在使用source maps,则可以单击此列表中的任何文件,并查看那些未调用部分。正如我们所看到的,甚至vuejs.org还有很大的改进空间)。</p>
<p>通过延迟加载适当的组件和库,我们设法将Vue Storefront的捆绑大小减少了60%!这可能是获得性能提升的最简单方法。</p>
<p>现在我们知道延迟加载是什么,它非常有用。现在是时候看看我们如何在我们自己的Vue.js应用程序中使用延迟加载。</p>
<blockquote><strong>动态导入</strong></blockquote>
<p>我们可以使用webpack的动态导入,轻松地加载我们应用程序的某些部分。让我们看看它们的工作原理,以及它们与常规导出模块的区别。</p>
<p>如果我们以这样的标准方式导入JavaScript模块:</p>
<p><img src="/img/bVbxosH?w=772&h=878" alt="图片描述" title="图片描述"></p>
<p>它将作为main.js的节点添加到依赖关系图中并与之捆绑在一起。</p>
<p>但是,<strong>如果我们仅在某些情况下需要我们的Cat模块</strong>,例如对用户交互的响应,该怎么办?将此模块与我们的初始bundle包捆绑在一起是一个坏主意,因为它不是一直需要的。我们需要一种方法告诉我们的应用程序什么时候应该下载这段代码。</p>
<p>这是动态导入可以帮助我们的地方!现在看一下这个例子:</p>
<p><img src="/img/bVbxosX?w=1750&h=590" alt="图片描述" title="图片描述"></p>
<p>我们来看看这里发生的事情:</p>
<p>我们创建了一个返回<strong>import()</strong>函数的函数,而不是直接导入Cat模块。<strong>现在,webpack会将动态导入的模块的内容捆绑到一个单独的文件中</strong>。表示动态导入模块的函数返回一个Promise,它将使我们在Promise resolve后,可以访问导出的模块成员。</p>
<p>然后,我们可以在需要时下载此可选块。例如,作为对某个用户交互的响应(如路由更改或单击)。</p>
<p>通过动态导入,我们基本上将给定节点(在这种情况下为Cat)隔离,当我们决定需要时,它将被添加到依赖图并下载此部分(<strong>这意味着我们也砍掉了一些Cat.js 中导入的模块</strong>)。</p>
<p>让我们看另一个更好地说明这种机制的例子。</p>
<p>假设我们有一个非常小的网上商店,有4个文件:</p>
<p>main.js 作为我们的主要bundle包<br>product.js 用于产品页面中的脚本<br>productGallery.js 用于产品页面中的产品库<br>category.js 用于类别页面中的脚本</p>
<p><img src="/img/bVbxotq?w=1128&h=1346" alt="图片描述" title="图片描述"></p>
<p>在上面的代码中,根据当前路由,我们动态导入产品或类别模块,然后运行由它们两者导出的init函数。</p>
<p>了解动态导入的工作方式之后,我们知道<strong>产品</strong>和<strong>类别</strong>最终会以单独的bundle包形式出现,但是未动态导入的productGallery模块会发生什么?正如我们所知,<strong>通过动态导入模块,我们削减了依赖图中的一部分</strong>。此部件中导入的所有内容都将捆绑在一起,因此productGallery将与产品模块位于同一个bundle包中。</p>
<p>换句话说,我们只是为依赖图创建某种新的入口点。</p>
<p><img src="/img/bVbxotB?w=976&h=522" alt="图片描述" title="图片描述"></p>
<blockquote><strong>延迟加载Vue components</strong></blockquote>
<p>现在我们知道延迟加载是什么,以及为什么需要它。现在是时候看看我们如何在Vue应用程序中使用它了。</p>
<p>好消息是它非常简单,我们可以懒加载整个vue单一文件组件(SFC),vue文件语法和HTML, CSS一样。不熟悉的话,去看看官方文档。</p>
<p><img src="/img/bVbxotI?w=1160&h=410" alt="图片描述" title="图片描述"></p>
<p>现在只有在请求时才会下载组件。以下是调用Vue组件动态加载的最常用方法:</p>
<ul><li>调用包含导入的函数</li></ul>
<p><img src="/img/bVbxotK?w=1160&h=482" alt="图片描述" title="图片描述"></p>
<ul><li>渲染组件</li></ul>
<p><img src="/img/bVbxotS?w=1160&h=1094" alt="图片描述" title="图片描述"></p>
<p>请注意,仅当请求的组件在模板中渲染时,才会调用<strong>lazyComponent</strong>函数。例如这段代码:</p>
<p><img src="/img/bVbxot1?w=840&h=410" alt="图片描述" title="图片描述"></p>
<p>在DOM中需要渲染组件之前,组件将不会加载。想要加载,只要v-if值更改为true即可。</p>
<blockquote><strong>总结</strong></blockquote>
<p>延迟加载,是使您的Web应用程序更高效并减少js bundle大小的最佳方法之一。我们已经学习了如何使用Vue组件进行延迟加载。</p>
<p>在本系列的下一部分中,我将向您展示在任何Vue.js应用程序上获得显着性能提升的最有用(也是最快)的方法。</p>
<p>您将学习如何使用异步路由拆分Vue代码,以及此过程中推荐的最佳实践。</p>
<p><img src="/img/bVbxovn?w=2800&h=800" alt="图片描述" title="图片描述"></p>
创建自己的Code Snippets在VSCode中
https://segmentfault.com/a/1190000019506459
2019-06-17T22:52:31+08:00
2019-06-17T22:52:31+08:00
前端知否
https://segmentfault.com/u/qethan
2
<h2>创建Vuejs文件模板代码片段</h2>
<p>1.Go to Code → Preferences → User Snippets</p>
<p><img src="/img/remote/1460000019506462" alt="" title=""></p>
<p>2.弹出提示框,选择一个vue代码高亮插件,比如vue.js</p>
<p><img src="/img/remote/1460000019506463" alt="" title=""></p>
<p>3.VSCode会创建一个vue.json,开始自定义</p>
<pre><code>* vue.json *
{
"New File": {
"prefix": "template",
"body": [
"<template>",
"\t<div class='${name}'></div>",
"</template>",
"",
"<script>",
"\texport default {",
"\t\tname: '${name}'",
"\t}",
"</script>",
"",
"<style lang='sass'>",
"\t.${name} {",
"",
"\t}",
"</style>"
]
}
}
</code></pre>
<p>使用效果:<br><img src="/img/remote/1460000019506464" alt="" title=""></p>
<h2>创建px2rem sass转换函数snippets</h2>
<p>1.Go to Code → Preferences → User Snippets</p>
<p>2.选择新建全局snippets file</p>
<p><img src="/img/remote/1460000019506465" alt="" title=""></p>
<p>3.VSCode会生成./vscode/px2rem.code-snippets,开始自定义</p>
<pre><code>{
// Place your giftmall_app workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and
// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope
// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is
// used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders.
// Placeholders with the same ids are connected.
// Example:
// "Print to console": {
// "scope": "javascript,typescript",
// "prefix": "log",
// "body": [
// "console.log('$1');",
// "$2"
// ],
// "description": "Log output to console"
// }
"px2rem": {
"scope": "javascript,typescript,scss",
"prefix": "prm",
"body": [
"pxToRem($1)"
],
"description": "px to rem"
}
}
</code></pre>
<p>4.在<code><style lang=“scss scoped></code>中输入prm,就可以看到补全提示<code>prm -> px2rem(参数值)</code></p>
<p>这里只是一个简单介绍,可以在平时工作中,去多多实践,减少一些无意义的体力活。</p>
<p><img src="/img/remote/1460000019506466" alt="" title=""></p>
ElementUI Table组件,如何在多页数据下勾选多行
https://segmentfault.com/a/1190000019214423
2019-05-17T15:56:12+08:00
2019-05-17T15:56:12+08:00
前端知否
https://segmentfault.com/u/qethan
3
<p>ElementUI Table组件,选择多行数据时使用 Checkbox。如下图:</p>
<p><img src="/img/remote/1460000019214426?w=895&h=574" alt="" title=""></p>
<p>但是业务中,表格数据往往不只一页。多页数据情况下,表格勾选某些行,就会遇到返回上一页,勾选消失的情况。这种情况,需要一些技巧和处理。具体代码如下:</p>
<pre><code><template>
<div class="demo-example">
<el-table
ref="table"
v-loading="loading"
:data="list"
height="650"
border
@select="onSelect"
@select-all="onSelectAll"
@selection-change="onSelectionChange"
>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column prop="u_createTime" label="注册时间"></el-table-column>
<el-table-column prop="u_id" label="用户ID"></el-table-column>
<el-table-column prop="u_nickname" label="用户名称"></el-table-column>
<el-table-column prop="u_phone" label="用户账户"></el-table-column>
<el-table-column prop="u_gender" label="性别"></el-table-column>
<el-table-column label="角色">
<template slot-scope="scope">
<span>{{scope.row.u_role === 1 ? '团队长' : '保险人'}}</span>
</template>
</el-table-column>
<el-table-column prop="u_companyNum" label="所属企业数量"></el-table-column>
<el-table-column prop="ha_addPeople" label="所属群组">
<template slot-scope="scope">
<el-button @click="onChakan(scope.row)" type="text" style="color:#67c23a" size="small">查看</el-button>
</template>
</el-table-column>
<el-table-column prop="u_lastLoginTime" label="最近登录时间"></el-table-column>
</el-table>
<div class="block pag" v-if="total">
<el-pagination
@current-change="onChangePage"
:current-page="currentPage"
:page-size="10"
layout="total, prev, pager, next, jumper"
:total="total"
></el-pagination>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selections: {}, // 保存已选择过的row
list: [],
total: 0,
curPage: 1,
};
},
//方法事件
methods: {
// 勾选时候,记录[{u_id: row}, ...]
onSelect(selection, row) {
if (this.selections[row.u_id]) {
delete this.selections[row.u_id];
} else {
this.selections[row.u_id] = row;
}
},
// 全选情况
onSelectAll(selection) {
// 全选
if (selection.length) {
selection.forEach(row => {
this.selections[row.u_id] = row;
})
} else {
// 取消全选
this.list.forEach(row => {
delete this.selections[row.u_id];
})
}
},
// 对已选择过的row勾选,返回到上一页时候
checkRows() {
const keys = Object.keys(this.selections);
const rows = this.list.filter(row => {
return keys.includes(String(row.u_id));
});
rows.map(row => {
this.$refs.table.toggleRowSelection(row);
});
},
// 省略...
// 获取数据列表
getData() {
// ...
},
},
created() {
this.getData();
}
};
</script></code></pre>
<p>现在分页切换,勾选依然会显示,对应每页勾选过的行数据。需要提交的勾选数据,也都在<code>this.selections</code>对象中。</p>
<p><img src="/img/remote/1460000019214427" alt="" title=""></p>
在Sequelize中使用迁移
https://segmentfault.com/a/1190000019140663
2019-05-10T17:43:07+08:00
2019-05-10T17:43:07+08:00
前端知否
https://segmentfault.com/u/qethan
2
<blockquote>Sequelize是Nodejs生态中一个比较出名的ORM框架。通过ORM框架,可以使用对象来操作数据库表数据,提高了开发效率和代码可读性,也方便后期维护。</blockquote>
<p>今天主要介绍通过<code>迁移[Migration]</code>来创建数据库,表。</p>
<p>迁移的好处,可以类比git。通过每次创建迁移文件,来支持更新,回滚数据库表结构,也方便协同开发,也避免人工手动去直接修改数据库,用代码自动管理。换个电脑,也不用去拷贝数据库,直接运行迁移就可以完全恢复开发环境,极大减轻了心智负担。</p>
<h2>1. 创建项目, 安装node package依赖</h2>
<pre><code>mkdir node_work
cd node_work
mkdir app
npm init -y
npm i sequelize-cli sequelize mysql2 koa</code></pre>
<h2>2. 初始化Sequelize</h2>
<pre><code>npx sequelize init</code></pre>
<p>运行之后,会产生四个目录:</p>
<p>config, migrations, models, seeders</p>
<p><br></p>
<p><strong>config:</strong></p>
<pre><code>{
"development": {
"username": "root",
"password": "root",
"database": "app_development",
"host": "127.0.0.1",
"port": 8889,
"dialect": "mysql",
"timezone": "+08:00"
},
"test": {
"username": "root",
"password": null,
"database": "app_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "app_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}</code></pre>
<blockquote>环境env => {配置}</blockquote>
<p>不同环境,对应不同的配置,也可以自定义环境,比如home</p>
<p>env指的是<code>process.env.NODE_ENV</code>,</p>
<p>可以通过设置环境变量来改变,比如<code>export NODE_ENV=production</code>;</p>
<p>迁移时候,也可以指定环境:<code>npx sequelize db:migrate --env production</code>,来连接production对应配置的数据库</p>
<p><strong>创建数据库:</strong></p>
<pre><code>npx sequelize db:create</code></pre>
<blockquote>说明<code>npx</code>是npm5.2之后,自带的一个命令。可以不用全局安装sequelize,使用时候,如果本地没有,就去npm仓库下载;下载完后或者本地已经下载过,就运行脚本命令。这样可以避免本地全局包过期,环境问题,每次都使用最新版本</blockquote>
<p><br></p>
<p><strong>migrations: 迁移文件</strong></p>
<pre><code>npx sequelize model:generate --name User --attributes username:string</code></pre>
<p>执行后,会生成<code>20180918055558-create-user.js</code>迁移文件,和<code>models/user.js</code>模型文件</p>
<p>其他字段可以在迁移文件中补全,最后再运行<code>npx sequelize db:migrate</code>,就可以在数据库中看到生成了users表</p>
<pre><code>'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
type: Sequelize.STRING(20),
allowNull: false
},
password: {
type: Sequelize.CHAR(32),
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}, {
tableName: 'users',
charset: 'utf8mb4',
collate: 'utf8mb4_bin',
define: {
timestamps: true
}
}).then(() => {
// 添加索引
return queryInterface.addIndex('users', {
name: 'username',
unique: true,
fields: ['username']
});
});
},
// 回退时执行,删除表
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};</code></pre>
<p>执行迁移:</p>
<pre><code>npx sequelize db:migrate
npx sequelize db:migrate:all</code></pre>
<p>撤销迁移:</p>
<pre><code>npx sequelize db:migrate:undo 最近一次的
npx sequelize db:migrate:undo:all
npx sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js</code></pre>
<p><code>--from, --to</code> 参数,可以指定迁移文件</p>
<p><br></p>
<p><strong>models: 模型文件</strong></p>
<p><code>model:generate</code>生成的model都在这个目录中</p>
<pre><code>'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Users', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
username: {
type: Sequelize.STRING(20),
allowNull: false
},
password: {
type: Sequelize.CHAR(32),
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
},
{
tableName: 'users',
charset: 'utf8mb4',
collate: 'utf8mb4_bin',
}).then(() => {
return queryInterface.addIndex('users', {
name: 'username',
unique: true,
fields: ['username']
});
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Users');
}
};</code></pre>
<p><br></p>
<blockquote>模型对象创建,默认会自动赋值,更新createdAt, updatedAt两个timestamps字段。下边会给出完整示例。</blockquote>
<p><br></p>
<p><strong>seeders: 填充数据文件</strong></p>
<p>创建seed文件:</p>
<pre><code>npx sequelize seed:generate --name demo-user</code></pre>
<p>执行之后,会得到<code>20180918090545-demo-user.js</code></p>
<pre><code>'use strict';
const md5 = require('md5')
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.bulkInsert('Users', [
{
username: 'Kimoo',
password: md5('123456'),
createdAt: new Date(),
updatedAt: new Date(),
},
{
username: 'Reci',
password: md5('123321'),
createdAt: new Date(),
updatedAt: new Date(),
}
], {});
},
down: (queryInterface, Sequelize) => {
/*
Add reverting commands here.
Return a promise to correctly handle asynchronicity.
Example:
return queryInterface.bulkDelete('Person', null, {});
*/
return queryInterface.bulkDelete('Users', null, {});
}
};
</code></pre>
<p>填充数据:</p>
<pre><code>npx sequelize db:seed:all
</code></pre>
<p>撤销数据:</p>
<pre><code>npx sequelize db:seed:undo 最近一次的
npx sequelize db:seed:undo --seed name-of-seed-as-in-data 具体某个
npx sequelize db:seed:undo:all
</code></pre>
<p><br></p>
<h2>3. 具体实践</h2>
<p><strong>app.js</strong></p>
<pre><code>(async function() {
const Koa = require('koa');
const KoaStaticCache = require('koa-static-cache');
const KoaBodyParser = require('koa-bodyparser');
const router = require('./routers/main');
const Session = require('koa-session');
const app = new Koa();
// app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha256');
app.keys = ['app'];
app.use( Session({
key: 'koa:sess',
maxAge: 86400000,
autoCommit: true,
overwrite: true,
httpOnly: true,
signed: true,
rolling: false,
renew: false
}, app) );
// app.use( async (ctx, next) => {
// ctx.set('Access-Control-Allow-Origin','*');
// await next();
// } );
app.use( KoaStaticCache('./public', {
prefix: 'public',
gzip: true
}) );
app.use( KoaBodyParser() );
app.use( router.routes() );
app.listen(8088);
})();</code></pre>
<p><br></p>
<p><strong>models/index.js</strong></p>
<pre><code>'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
// 自动导入 models 文件夹下的所有文件,比如user.js这个模型文件
// 自动加载模型并执行
// let users = require('./users');
// let UsersModel = users(sequelize, Sequelize);
// db[UsersModel.name] = UsersModel; // db['Users'] = UsersModel;
// 下面通过fs自动加载所有的文件,并执行,同时生成的模型对象挂载到db对象下面,最后返回出去
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;</code></pre>
<p><br></p>
<p><strong>routers/main.js</strong></p>
<pre><code>const KoaRouter = require('koa-router');
const md5 = require('md5');
const Models = require('../models');
const Sequelize = require('sequelize');
const router = new KoaRouter();
router.post('/register', async ctx => {
// console.log(ctx.request.body);
let username = ctx.request.body.username.trim();
let password = ctx.request.body.password.trim();
let repassword = ctx.request.body.repassword.trim();
if (username=='' || password == '' || repassword == '') {
return ctx.body = {
code: 1,
data: '用户名或密码不能为空'
}
}
if (password != repassword) {
return ctx.body = {
code: 2,
data: '两次输入的密码不一致'
}
}
let user = await Models.Users.findOne({
where: {
username
}
});
if (user !== null) {
return ctx.body = {
code: 3,
data: '当前用户已经被注册了'
}
}
let newUser = await Models.Users.build({
username,
password: md5(password)
}).save();
ctx.body = {
code: 0,
data: {
id: newUser.get('id'),
username: newUser.get('username')
}
}
});
router.post('/login', async ctx => {
let username = ctx.request.body.username;
let password = ctx.request.body.password;
let user = await Models.Users.findOne({
where: {
username
}
});
if (user === null) {
return ctx.body = {
code: 1,
data: '不存在该用户'
}
}
if (user.get('password') !== md5(password)) {
return ctx.body = {
code: 1,
data: '密码错误'
}
}
// ctx.cookies.set('uid', user.get('id'), {
// httpOnly: false
// });
// 服务端发送一个约定好的cookie,来表示当前是登录
// ctx.cookies.set('uid', user.get('id'), {
// // httpOnly,表示当前的cookie是否允许客户端进行操作(js),如果为true,那么就表示这个cookie是能用户http协议的数据传输
// httpOnly: true,
// signed: true
// });
ctx.cookies.set('username', user.get('username'), {
httpOnly: false
});
ctx.session.uid = 1;
ctx.body = {
code: 0,
data: {
id: user.get('id'),
username: user.get('username')
}
}
});
})
module.exports = router;</code></pre>
<p><br></p>
<h2>4. 测试接口,注册用户,添加数据</h2>
<p>可以在<code>postman</code>中测试接口,地址<code>http://localhost:8088/register</code>,注册用户</p>
<pre><code>node app.js</code></pre>
React Hooks系列之useState
https://segmentfault.com/a/1190000018860673
2019-04-14T08:04:36+08:00
2019-04-14T08:04:36+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p>在React Hooks出现之前,组件添加state, 只能在class中完成。</p>
<h2>class方式</h2>
<p><img src="/img/remote/1460000018860676" alt="" title=""></p>
<p>React 16.7 alpha之后,可以在function组件中创建state了,不用再每次都需要创建一个class component,更加函数式了。</p>
<h2>useState方式</h2>
<p><img src="/img/remote/1460000018860677" alt="" title=""></p>
<blockquote><h3>不同场景下,应该如何使用<code>useState</code>
</h3></blockquote>
<ul><li>场景1:隐藏/显示一个组件</li></ul>
<p><img src="/img/remote/1460000018860678?w=1172&h=1662" alt="" title=""></p>
<ul><li>场景2:根据上一个state更新state<p>setSteps方法中第一个参数是prevState</p>
</li></ul>
<p><img src="/img/remote/1460000018860679" alt="" title=""></p>
<ul><li>场景3:state是一个数组值</li></ul>
<p><img src="/img/remote/1460000018860680" alt="" title=""></p>
<ul><li>场景4:state是一个对象<p>下边是一个表单,表单中有username, password两个字段。展示了如何初始化表单数据,和更新对应的字段</p>
</li></ul>
<p><img src="/img/remote/1460000018860681?w=852&h=1698" alt="" title=""></p>
<p><img src="/img/remote/1460000018860682?w=800&h=229" alt="" title=""></p>
Vue.js 2.6尝鲜
https://segmentfault.com/a/1190000018105236
2019-02-07T10:03:44+08:00
2019-02-07T10:03:44+08:00
前端知否
https://segmentfault.com/u/qethan
7
<p><img src="/img/remote/1460000018105239?w=1231&h=669" alt="" title=""></p>
<p>Vue 2.6 "Macross" 发布了,同时也是Vuejs五周年~</p>
<p>在这篇文章中,将会介绍新版本的新特性, 比如<code>slots</code>的新语法,<code>Vue.observable()</code>等等</p>
<h3>1. Scoped slots(作用域插槽)的新语法</h3>
<p>这是一个比较重大的改变,包含的有:</p>
<ul>
<li>v-slot新指令,结合了<code>slot</code> 和 <code>slot-scope</code>的功能</li>
<li>
<code>scoped slots</code>的简写</li>
</ul>
<p><strong>之前</strong>在<strong>Vue@2.5.22</strong>中是这样使用<code>scoped-slots</code>的:</p>
<pre><code class="vue"><template>
<TestComponent>
<! - 默认 scoped slot ->
<div slot-scope="{message}">
{{`Default slot with message: ${message}`}}
</ div>
<! - 具名 scoped slot ->
<div slot="text" slot-scope="{text}">
{{`Named slot with text: ${text}`}}
</ div>
</ TestComponent>
</ template></code></pre>
<p><strong>现在</strong>是这样的:</p>
<pre><code class="vue"><template>
<TestComponent>
<! - 默认 scoped slot ->
<template v-slot="{message}">
<div>
{{`Default slot with message: ${message}`}}
</ div>
</ template>
<! - 具名 scoped slot ->
<template v-slot:text="{text}">
<div>
{{`Named slot with text: ${text}`}}
</ div>
</ template>
</ TestComponent>
</ template></code></pre>
<p>默认插槽:</p>
<pre><code class="vue"><template>
<! - v-slot is used directly on the parent ->
<TestComponent v-slot="{message}">
<div>
{{`Default slot with message: ${message}`}}
</ div>
</ TestComponent>
</ template></code></pre>
<p>具名插槽:</p>
<pre><code class="vue"><template>
<TestComponent>
<! - # 简写: ->
<template #text="{text}">
<div>
{{`Named slot with text: ${text}`}}
</ div>
</ template>
</ TestComponent>
</ template></code></pre>
<p>新版中,可以不使用任何作用域插槽变量,但是仍然可以通过父组件的<code>$scopedSlots</code>去引用到</p>
<h3>2. 动态参数指令</h3>
<p>如果我们想在<code>v-bind</code> or <code>v-on</code>中使用动态变量,在<strong>Vue@2.5.22</strong>中:</p>
<pre><code class="vue"><div v-bind="{ [key]: value }"></div>
<div v-on="{ [event]: handler }"></div></code></pre>
<p>但是这个例子有几个缺点:</p>
<ul>
<li>不是所有人都知道在<code>v-bind / v-on</code>中可以使用动态变量名</li>
<li>
<code>vue-template-compier</code> 生成了低效的代码</li>
<li>
<code>v-slot</code>没有类似的使用对象的语法</li>
</ul>
<p>为了解决这些问题,<code>Vue@2.6.0 </code>引入了一个新语法:</p>
<pre><code class="VUE"><div v-bind:[key]="value"></div>
<div v-on:[event]="handler"></div></code></pre>
<p>举个例子:</p>
<pre><code class="vue"><template>
<div>
<! - v-bind 动态key ->
<div v-bind:[key]="value"> </ div>
<! - 简写 ->
<div :[key]="value"> </ div>
<! - v-on 动态事件,event变量 ->
<div v-on:[event]="handler"> </ div>
<! - 简写 ->
<div @[event]="handler"> </ div>
<! - v-slot 动态名字 ->
<TestComponent>
<template v-slot:[name]>
Hello
</ template>
</ TestComponent>
<! - 简写 ->
<TestComponent>
<template #[name]>
Cool slot
</ template>
</ TestComponent>
</ div>
</ template></code></pre>
<h3>3. 使用Vue.observable()创建一个响应对象</h3>
<p>之前,创建一个响应对象,必须在一个Vue实例中配置。现在我们可以在Vue实例外部,通过使用<code>Vue.observable(data)</code>创建,如下:</p>
<pre><code class="vue">import vue from vue;
const state = Vue.observable ({
counter: 0,
});
export default {
render () {
return (
<div>
{state.counter}
<button v-on:click={() => {state.counter ++; }}>
Increment counter
</ button>
</ div>
);
},
};</code></pre>
<h3>4. server端获取数据</h3>
<p>在新的升级版本中,<code>vue-server-renderer</code>改变了SSR的数据加载策略。</p>
<p>之前,我们推荐使用<code>asyncData()</code>在<code>router.getMatchedComponents()</code>方法中获取的组件中,获取数据。</p>
<p>新版本中有一个特别的组件方法:<code>serverPrefetch()</code> 。vue-server-renderer会在每个组件中调用它,它会返回一个promise。</p>
<pre><code class="vue"><template>
<div v-if="item">
{{item.name}}
</ div>
</ template>
<script>
export default {
// Call on the server
async serverPrefetch () {
await this.fetchItem();
},
computed: {
item () {
return this.$store.state.item;
},
},
// Call on client
mounted () {
if (!this.item) {
this.fetchItem();
}
},
methods: {
async fetchItem () {
await this.$store.dispatch('fetchItem');
},
},
};
</ script></code></pre>
<p>在<code>serverPrefetch()</code>执行之后,我们需要知道应用在什么时候渲染完成,在server render 上下文中,我们可以使用<code>rendered()</code>钩子方法。</p>
<pre><code class="js">/ * Simplified entry-server.js * /
import {createApp} from './app';
export default context => new Promise ((resolve, reject) => {
const {app, router, store} = createApp();
const {url} = context;
router.push(url);
router.onReady(() => {
context.rendered = () => {
context.state = store.state;
};
resolve (app);
}, reject);
});</code></pre>
<h3>5. 改进的错误输出</h3>
<p>在<code>render</code>方法中编译html,<code>vue-template-compiler</code>可能会产生错误。在之前,Vue会产生一个没有位置的错误描述。新版本中会展示这个错误出现在哪里,比如:</p>
<pre><code class="vue"><template>
<div>
<template key="test-key">
{{ message }}
</template>
</div>
</template>
</code></pre>
<p>在<strong>vue-template-compiler@2.5.22</strong>中:</p>
<pre><code class="vue">Error compiling template:
<div>
<template key="test-key">
{{ message }}
</template>
</div>
- <template> cannot be keyed. Place the key on real elements instead.</code></pre>
<p>在<strong>vue-template-compiler@2.6.0</strong>中:</p>
<pre><code class="vue">Errors compiling template:
<template> cannot be keyed. Place the key on real elements instead.
1 |
2 | <div>
3 | <template key="test-key">
| ^^^^^^^^^^^^^^
4 | {{ message }}
5 | </template></code></pre>
<h3>6. 捕捉异步错误</h3>
<p>现在<strong>Vue</strong>可以在生命周期方法钩子和事件方法中捕捉到异步错误异常。比如:</p>
<pre><code class="vue">/ * TestComponent.vue * /
<template>
<div @click="doSomething()">
Some message
</ div>
</ template>
<script>
export default {
methods: {
async doSomething () {
await this.$nextTick ();
throw new Error ('Another Error');
},
},
async mounted () {
await this.$nextTick ();
throw new Error ('Some Error');
},
};
</ script></code></pre>
<p>mount后错误:</p>
<pre><code class="vue">[Vue warn]: Error in mounted hook (Promise/async): "Error: Some Error"</code></pre>
<p>点击事件后错误:</p>
<pre><code class="vue">[Vue warn]: Error in v-on handler (Promise/async): "Error: Another Error"</code></pre>
<h3>7. ESM 浏览器中的新版构建</h3>
<p>新版本中,增加了一个<strong>vue.esm.browser.js</strong>。它是为了支持<strong>ES6 Modules</strong>的浏览器设计的。</p>
<p>特性:</p>
<ul>
<li>在render函数中,包含HTML编译器</li>
<li>使用ES6模块语法</li>
<li>包含非副本代码(non-transcript)</li>
</ul>
<p>举例:</p>
<pre><code class="html"><html lang="en">
<head>
<title> Document </ title>
</ head>
<body>
<div id="app">
{{message}}
</ div>
<script type="module">
import Vue from 'path/to/vue.esm.browser.js';
new Vue {{
el: '#app',
data () {
return {
message: 'Hello World!',
};
},
});
</ script>
</ body>
</ html></code></pre>
<h3>8. v-bind.prop简写</h3>
<p><code>v-bind</code>指令有一个特殊的修饰符---<code>.prop</code>。你可以在<a href="https://link.segmentfault.com/?enc=iUbXea%2FYxPVIkv2LQIpYzA%3D%3D.QaYvboucw1hDClgG09OH49CUhhvSi2pC%2F1W8KUnUAham22ss95HOvVoj2ASbLiwg" rel="nofollow">文档</a>中查看具体用法。我自己从没使用过,暂时也想不到在什么时候使用。</p>
<p>现在有一个简写方式,对于<code>v-bind:someProperty.prop="foo"</code>, 可以写成<code>.someProperty="foo"</code></p>
<p>在<strong>Vue@2.5.22</strong>中:</p>
<pre><code class="vue"><template>
<div>
<div v-bind:textContent.prop="'Important text content'" />
<! - 简写版本 ->
<div: textContent.prop="'Important text content'" />
</ div>
</ template></code></pre>
<p><strong>Vue@2.6.0</strong>:</p>
<pre><code class="vue"><template>
<div .textContent="'Important text content'" />
</template></code></pre>
<h3>9. 支持自定义toString()</h3>
<p>规则很简单:如果重写了对象的<code>toString()</code>方法,显示的时候,Vue将使用它,而不是<code>JSON.stringify()</code></p>
<p>举例:</p>
<pre><code class="vue">/ * TestComponent.vue * /
<template>
<div>
{{message}}
</ div>
</ template>
<script>
export default {
data () {
return {
message: {
value: 'qwerty',
toString () {
return 'Hello Habr!';
},
},
};
},
};
</ script></code></pre>
<p><strong>Vue@2.5.22</strong>中显示:</p>
<pre><code class="js">{ "value": "qwerty" }</code></pre>
<p><strong>Vue@2.6.0</strong>:</p>
<pre><code class="js">Hello Habr!</code></pre>
<h3>10. v-for和可迭代对象</h3>
<p>在新版本中,<code>v-for</code>可以遍历任何实现了<a href="https://link.segmentfault.com/?enc=Zf53SS1b%2BexKxOzlJjBorw%3D%3D.cBsj6PAqnZDoN3fgVVvfVNU9YMEW0ZHQKTwKugqJsi2M4Jm%2FRAPyc9syAX5JOrBC%2F5DC%2FHAenacySipl%2FeSMKSmyRP60%2BAsvn7EecBdN4hZCoouCfJZPvEZ10wINwzjDJiNOnHVj%2BtOZvxjNttvSBQ%3D%3D" rel="nofollow"><strong>iterable协议</strong></a>的对象,比如<strong>Map</strong>, <strong>Set</strong>。</p>
<blockquote>在<strong>2.X</strong>版本中,Map和Set, 不支持数据响应。</blockquote>
<p>举例:</p>
<pre><code class="vue">/ * TestComponent.vue * /
<template>
<div>
<div
v-for="item in items"
:key="item"
>
{{item}}
</ div>
</ div>
</ template>
<script>
export default {
data () {
return {
items: new Set([4, 2, 6]),
};
},
};
</ script></code></pre>
<p><img src="/img/remote/1460000018105240?w=800&h=229" alt="" title=""></p>
ES6+好用的小技巧,让你的代码更干净,短巧,易读
https://segmentfault.com/a/1190000018095136
2019-02-03T00:16:55+08:00
2019-02-03T00:16:55+08:00
前端知否
https://segmentfault.com/u/qethan
4
<h3>模板字符串</h3>
<pre><code class="js">let name = 'siri', age = 18, job = 'front-end engineer'
let oldStr = 'Hi, ' + name + ', I\'m ' + age + ' and work as a ' + job + '.';
let newStr = `Hi, ${ name }, I'm ${ age } and work as a ${ job }.`;
</code></pre>
<h3>扩展操作符</h3>
<blockquote>
<p>… 操作符,有两个主要用处:</p>
<ol>
<li>复制一个新的数组或对象</li>
<li>把多个参数赋值给一个数组变量</li>
<li>把一个数组变量赋值给多个参数</li>
</ol>
</blockquote>
<pre><code class="js">let a = [1, 2, 3]
let b = [...a] // b是一个新的数组,内容和a一样
let c = [...a, 4, 5, 6]
let car = { type: 'vehicle ', wheels: 4};
let newCar = {...car}
console.log(newCar); // { type: 'vehicle ', wheels: 4}
// 合并对象属性,后边的属性会覆盖前边的,可用于修改对象的某个属性值
let car2 = {...car, type: 'vehicle2', wheels: 2} // {type: "vehicle2", wheels: 2}</code></pre>
<pre><code class="js">function foo(...args) {
console.log(args);
}
foo( 'car', 54, 'tree'); // console.log 输出 [ 'car', 54, 'tree' ] </code></pre>
<h3>默认参数</h3>
<pre><code class="js">// 给方法添加默认参数值
function foo( a = 5, b = 10) {
console.log( a + b);
}
foo(); // 15
foo( 7, 12 ); // 19
foo( undefined, 8 ); // 13
foo( 8 ); // 18
foo( null ); // 10 as null is coerced to 0</code></pre>
<pre><code class="js">// 默认参数值也可以是表达式或者函数
function foo( a ) { return a * 4; }
// y = x + 4, z = foo(x)
function bar( x = 2, y = x + 4, z = foo(x)) {
console.log([ x, y, z ]);
}
bar(); // [ 2, 6, 8 ]
bar( 1, 2, 3 ); //[ 1, 2, 3 ]
bar( 10, undefined, 3 ); // [ 10, 14, 3 ]</code></pre>
<pre><code class="js">// 对象参数默认值,如果参数为空,则会抛出异常
function show({ title = "title", width = 100, height = 200 }) {
console.log( `${title} ${width} ${height}` );
}
show() // Cannot destructure property `title` of 'undefined' or 'null'.
show({}) // title 100 200
// 解决办法:
function show({ title = "title", width = 100, height = 200 } = {}) {
console.log( `${title} ${width} ${height}` );
}
show(); // title 100 200
show({width: 200}) // title 200 200</code></pre>
<h3>解析赋值</h3>
<pre><code class="js">// key变量重命名, first --> firstName
const person = {
first: 'foo',
last: 'tom',
};
const { first: firstName } = person;
console.log(firstName); // foo</code></pre>
<pre><code class="js">// 默认值
const settings = {
speed: 150
}
const { speed = 750, width = 500 } = settings;
console.log(speed); // 150
console.log(width); // 500
// 可能不存在的key
const { middle: middleName = 'midname' } = person;
console.log(middleName); // 'midname'</code></pre>
<pre><code class="js">// 嵌套赋值
const user = {
id: 339,
name: 'Fred',
age: 42,
education: {
degree: 'Masters'
}
};
const {education: {degree}} = user;
console.log(degree); //prints: Masters</code></pre>
<pre><code class="js">// 如果嵌套的属性不存在
const user = {
id: 339,
name: 'Fred',
age: 42
};
const {education: {degree}} = user; // TypeError: Cannot match against 'undefined' or 'null'.
// 解决办法:
const user = {
id: 339,
name: 'Fred',
age: 42
};
const {education: {degree} = {}} = user;
console.log(degree); //prints: undefined</code></pre>
<h3>利用数组生成一个数字序列</h3>
<pre><code class="js">const numRange = (start, end) => {
return Array(end - start + 1).fill().map((item, index) => start + index);
};
const numbers = numRange(0, 5); // [0, 1, 2, 3, 4, 5]
const numbers2 = numRange(1, 5); // [1, 2, 3, 4, 5]
</code></pre>
<h3>利用Set给数组去重</h3>
<pre><code class="js">const years = [2016, 2017, 2017, 2018, 2018, 2019]
// set构造函数的参数是一个array
const distinctYears = [...new Set(years)] // [2016, 2017, 2018, 2019]</code></pre>
<h3>生成唯一随机字符串,可以指定长度</h3>
<pre><code class="js">function generateRandom(length) {
let radom13chars = function () {
return Math.random().toString(16).substring(2, 15)
}
let loops = Math.ceil(length / 13)
return new Array(loops).fill(radom13chars).reduce((string, func) => {
return string + func()
}, '').substring(0, length)
}
generateRandom(8) // "03836a49"</code></pre>
<p><img src="/img/bVbn5xb?w=2800&h=800" alt="图片描述" title="图片描述"></p>
移动端网页布局适配rem方案小结
https://segmentfault.com/a/1190000018041164
2019-01-28T10:24:12+08:00
2019-01-28T10:24:12+08:00
前端知否
https://segmentfault.com/u/qethan
4
<h2>前言</h2>
<p>根据 W3C 规范中对 1rem 的定义:</p>
<blockquote>1rem 与等于根元素 font-size 的计算值。当明确规定根元素的 font-size 时,rem 单位以该属性的初始值作参照。<p>这就意味着 1rem 等于 html 元素的字体大小(大部分浏览器根元素的字体大小为16px)</p>
</blockquote>
<h2>兼容性</h2>
<p>ios:6.1系统以上都支持</p>
<p>android:2.1系统以上都支持</p>
<p>大部分主流浏览器都支持,可以安心的往下看了。</p>
<hr>
<p><strong>rem:(font size of the root element)</strong></p>
<p>意思就是根据网页的根元素来设置字体大小,和em(font size of the element)的区别是,em是根据其父元素的字体大小来设置,而rem是根据网页的跟元素(html)来设置字体大小的,举一个简单的例子,</p>
<p>现在大部分浏览器IE9+,Firefox、Chrome、Safari、Opera ,如果我们不修改相关的字体配置,都是默认显示font-size是16px,即</p>
<pre><code>html {
font-size:16px;
}
</code></pre>
<p>那么如果我们想给一个P标签设置12px的字体大小,那么用rem来写就是</p>
<pre><code>p {
font-size: 0.75rem; //12÷16=0.75(rem)
}
</code></pre>
<p>使用rem这个字体单位进行适配,就是利用它作为一个全局字体固定参照单位的特性。如果改变html元素的字体大小,rem的值也就跟着改变,对应的其他使用rem的布局尺寸,也会跟着改变,从而达到适配的目的,保证比例一致。 <strong>所以rem不仅可以适用于字体,同样可以用于width height margin这些样式的单位。</strong></p>
<h2>rem适配具体实现方案:</h2>
<blockquote>设计稿尺寸宽度为750px,如果设计稿是640px,下边js会自动计算rem的值(比如rem:75px -> rem: 64px),具体的尺寸rem不用调整(例如 padding: 1.5rem,不用调整,这是一个比例大小),对应的元素大小px值会根据新的rem(比如rem: 64px, padding等于 1.5 * 64)改变,从而按照比例适配。</blockquote>
<p><strong>index.html</strong></p>
<pre><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>rem适配</title>
<script>;(function(win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
console.warn('将根据已有的meta标签来设置缩放比例');
var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
var content = flexibleEl.getAttribute('content');
if (content) {
var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute('data-dpr', dpr);
if (!metaEl) {
metaEl = doc.createElement('meta');
metaEl.setAttribute('name', 'viewport');
metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem(){
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
flexible.rem = win.rem = rem;
}
win.addEventListener('resize', function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener('pageshow', function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === 'complete') {
doc.body.style.fontSize = 12 * dpr + 'px';
} else {
doc.addEventListener('DOMContentLoaded', function(e) {
doc.body.style.fontSize = 12 * dpr + 'px';
}, false);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function(d) {
var val = parseFloat(d) * this.rem;
if (typeof d === 'string' && d.match(/rem$/)) {
val += 'px';
}
return val;
}
flexible.px2rem = function(d) {
var val = parseFloat(d) / this.rem;
if (typeof d === 'string' && d.match(/px$/)) {
val += 'rem';
}
return val;
}
})(window, window['lib'] || (window['lib'] = {}));</script>
</head>
<body>
<noscript>
<strong>We're sorry but rem适配 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
</code></pre>
<p><strong>helper.scss</strong></p>
<pre><code>$remBase: 75;
$primaryColor: #ffd633;
@function px2rem($px) {
@return ($px / $remBase) * 1rem;
}
%textOverflow {
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
// @include borderLineTop('top', color)
@mixin borderLine($mode: 'top', $color: #e5e5e5) {
position: relative;
@if $mode == 'top' {
&::before {
// 实现1物理像素的下边框线
content: '';
position: absolute;
z-index: 1;
pointer-events: none;
background-color: $color;
height: 1px;
left: 0;
right: 0;
top: 0;
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
-webkit-transform: scaleY(0.5);
-webkit-transform-origin: 50% 0%;
}
}
}
@if $mode == 'bottom' {
&::after {
// 实现1物理像素的下边框线
content: '';
position: absolute;
z-index: 1;
pointer-events: none;
background-color: $color;
height: 1px;
left: 0;
right: 0;
bottom: 0;
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
-webkit-transform: scaleY(0.5);
-webkit-transform-origin: 50% 0%;
}
}
}
}
@mixin borderRadius($radius) {
border-top-left-radius: px2rem($radius);
border-top-right-radius: px2rem($radius);
border-bottom-left-radius: px2rem($radius);
border-bottom-right-radius: px2rem($radius);
}
// @include banner(100)
@mixin banner($height) {
position: relative;
padding-top: percentage($height/750); // 使用padding-top
height: 0;
overflow: hidden;
img {
width: 100%;
height: auto;
position: absolute;
left: 0;
top: 0;
}
}
$spaceamounts: (5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 100);
$sides: (top, bottom, left, right);
@each $space in $spaceamounts {
@each $side in $sides {
.m-#{str-slice($side, 0, 1)}-#{$space} {
margin-#{$side}: #{px2rem($space)} !important;
}
.p-#{str-slice($side, 0, 1)}-#{$space} {
padding-#{$side}: #{px2rem($space)} !important;
}
}
}
.flex-center {
display: flex;
align-items: center;
}
@mixin font-dpr($font-size){
font-size: $font-size;
[data-dpr="2"] & {
font-size: $font-size * 2;
}
[data-dpr="3"] & {
font-size: $font-size * 3;
}
}</code></pre>
<p><strong>App.vue, 使用px2rem进行转换</strong></p>
<pre><code><style lang="scss">
@import "@/assets/style/helper.scss";
#nav {
padding: px2rem(24);
a {
font-size: px2rem(24);
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style></code></pre>
<p><img src="/img/bVbnLDv?w=2800&h=800" alt="图片描述" title="图片描述"></p>
iOS实现html链接a标签正则匹配,高亮
https://segmentfault.com/a/1190000007835341
2016-12-17T16:37:43+08:00
2016-12-17T16:37:43+08:00
前端知否
https://segmentfault.com/u/qethan
0
<blockquote>
<p>需求:匹配文本内容中的标签,然后高亮显示出来。</p>
<p>运行环境:XCode8.1, iPhone7-iOS10.1<br>第三方类库框架:</p>
<ul>
<li><p>YYText: 富文本渲染类库框架 <a href="https://link.segmentfault.com/?enc=FTG4AGrQoWybINLG1P2nUg%3D%3D.i04hdL3oEX06rnyrUVbwU8sAmPUaoXEhg17LM%2BbDE73Y6JglsiqxbVqbWFz%2BcUxl" rel="nofollow">Github地址</a></p></li>
<li><p>RegexKitLite: 封装了正则匹配操作的方法 <a href="https://link.segmentfault.com/?enc=%2FO80dmBFwidb1HFYedqZCQ%3D%3D.HYJmXMt7sLtXGZng2tEaMN6jnrzvKz5S2I0n8upVA4%2FCkBCmOmpqbwTBKVjF%2ByIA" rel="nofollow">Github地址</a></p></li>
</ul>
</blockquote>
<h3>加入RegexKitLite类库遇到问题:</h3>
<pre><code>Undefined symbols for architecture x86_64:
"_u_errorName", referenced from:
_rkl_NSExceptionForRegex in RegexKitLite.o
_rkl_makeNSError in RegexKitLite.o
_rkl_userInfoDictionary in RegexKitLite.o
_rkl_NSExceptionForRegex in MOBFoundation
_rkl_userInfoDictionary in MOBFoundation
"_u_strlen", referenced from:
_rkl_userInfoDictionary in RegexKitLite.o
_rkl_userInfoDictionary in MOBFoundation
"_uregex_appendReplacement", referenced from:
_rkl_replaceAll in RegexKitLite.o
_rkl_replaceAll in MOBFoundation
"_uregex_appendTail", referenced from:
_rkl_replaceAll in RegexKitLite.o
_rkl_replaceAll in MOBFoundation
省略...</code></pre>
<h3>解决办法:加入一个flag: -licucore</h3>
<p>感谢 <a href="https://link.segmentfault.com/?enc=wO6kTSm0zBYX4HzDbZU4yw%3D%3D.IkQRZZKdh74Sl9ZfjfjzuWKw9HzzpBKwaMZJTe76eNPE39oDg6pPV02qOUQtMYjf" rel="nofollow">使用Xcode开发,错误总结<持续更新ing></a></p>
<p><img src="/img/bVG2tM?w=854&h=592" alt="clipboard.png" title="clipboard.png"></p>
<pre><code>#import "ViewController.h"
#import "YYText.h"
#import "RegexKitLite.h"
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
YYLabel *label = [[YYLabel alloc] initWithFrame:CGRectMake(0, 100, kScreenWidth, 16)];
label.text = @"占位符占位符<a href='https://www.xxx.com/223?232323&2323'>#我是链接1#</a>占位符<a href='https://www.xxx.com/223?232323&2323'>#我是链接2#</a>占占位符占位符占位符占位符占位符占位符占位符占位符占位符占位符";
// url正则有很多种,不过这个已经够满足我的需求
NSString *regex_http = @"<a href=(?:.*?)>(.*?)<\\/a>";
// 文本内容
NSString *labelText = [label.text copy];
//[label.text captureComponentsMatchedByRegex:@""]; // 只会匹配第一个满足条件的
// 这个方法可以匹配多个满足条件的,得到一个二维数组。内容中可能会有多个链接,所以要用这个
NSArray *array_http = [labelText arrayOfCaptureComponentsMatchedByRegex:regex_http];
if ([array_http count]) {
// 先把html a标签都给去掉
labelText = [labelText stringByReplacingOccurrencesOfString:@"<a href=(.*?)>"
withString:@""
options:NSRegularExpressionSearch
range:NSMakeRange (0, labelText.length)];
labelText = [labelText stringByReplacingOccurrencesOfString:@"<\\/a>"
withString:@""
options:NSRegularExpressionSearch
range:NSMakeRange (0, labelText.length)];
// 样式文本
NSMutableAttributedString *one = [[NSMutableAttributedString alloc] initWithString: labelText];
// 处理掉a标签后的内容,用来让UILabel去显示
label.text = labelText;
for (NSArray *array in array_http) {
// 获得链接显示文字的range,用来设置下划线
NSRange range = [labelText rangeOfString:array[1]];
// 设置下划线样式
[one yy_setTextUnderline:[YYTextDecoration decorationWithStyle:YYTextLineStyleSingle] range:range];
// 设置链接文本字体颜色
UIColor *textColor = [UIColor redColor];
[one yy_setColor:textColor range:range];
}
// 设置UILabel样式
label.attributedText = one;
}
[self.view addSubview:label];
}
@end</code></pre>
<h3>正则匹配结果图片:</h3>
<p><img src="/img/bVG2r7?w=890&h=374" alt="clipboard.png" title="clipboard.png"></p>
<h3>最终效果图片:</h3>
<p><img src="/img/bVG2r4?w=459&h=773" alt="clipboard.png" title="clipboard.png"></p>
<h3>注意</h3>
<blockquote>
<p><code><a href=(?:.*?)></code>, 这个正则这里要主要贪婪匹配的问题。如果是<code><a href=(?:.*)></code>那个会匹配到:</p>
<p><img src="/img/bVG2sL?w=437&h=140" alt="clipboard.png" title="clipboard.png"></p>
<p>一直匹配到最后一个a标签,就得不到每个链接了。</p>
</blockquote>
<h3>杂项</h3>
<blockquote><p>YYText也可以实现高亮a链接文本的点击事件。具体可以参考下边的第二篇博客</p></blockquote>
<h3>最后</h3>
<p>感谢:1. <a href="https://link.segmentfault.com/?enc=WdSg9BYaTJcEPU16PcEWhQ%3D%3D.fK2XS2gIEe%2FfmArhuUrN%2FxwRXG0nPx22lMBcvWfksH3ulmBhFSDPki0rS53cv4NBSpmgLt5IP%2FnR2o%2BrFeMmUU0U69zg2r32UxOl%2Bq2zSEjfqf1OvZl3AeDCb5lylalCQciBHoiIf9A0BKa4QseWJg%3D%3D" rel="nofollow">iOS中使用RegexKitLite来试用正则表达式</a> 2. <a href="https://link.segmentfault.com/?enc=MfZvJ4FcXAhkK2LQowJI5g%3D%3D.2C7VK7fSeZGPyr%2B3pwR6sZjhgXEnL7Ad2ojJ%2F3QHtgQ5AKpI7LHDzHMZLkS3J6dTixwG9ENCL5YmbLdsnTkdRQ%3D%3D" rel="nofollow">使用YYText-文本蓝色文字点击实现超链接跳转</a></p>
UILabel文本高度计算的那些事儿
https://segmentfault.com/a/1190000007817569
2016-12-15T19:00:50+08:00
2016-12-15T19:00:50+08:00
前端知否
https://segmentfault.com/u/qethan
0
<h2>1. 计算文本在一行高度内的宽度</h2>
<pre><code>// 段落样式
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 6.0;
// 清除掉换行符(或者还要清除空白符,这个看自己需求)
// 要处理掉换行符,否则得不到正确的高度
NSString *intro = [self.columnDict.head.intro copy]; // 这个数据就是文本
intro = [intro stringByReplacingOccurrencesOfString:@"\r" withString:@""];
intro = [intro stringByReplacingOccurrencesOfString:@"\n" withString:@""];
CGRect introRect = [intro boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 20)
options:0 attributes:@{
NSFontAttributeName:font(14),
NSParagraphStyleAttributeName:paragraphStyle
} context:nil];
CGFloat width = introRect.size.width;
// 如果width是小数,小数部分的宽度不会被渲染,不够一个像素,所以最好向上取整
width = ceil(width);
</code></pre>
<p><code>boundingRectWithSize</code>的第一个参数是CGSize. 一般会设置宽度固定,高度CGFLOAT_MAX来获取在这种显示宽度内的文本高度;或者设置宽度CGFLOAT_MAX,高度固定,来获取某个高度内的文本宽度。比如上边的一行高度内,文本有多长</p>
<h2>2. 固定宽度,行数的文本高度(没有设置行间距的情况下)</h2>
<pre><code>UILabel *label = [[UILabel alloc] init];
label.numberOfLines = 2;
label.text = self.columnDict.head.intro;
[label sizeToFit];
CGFloat height = label.bounds.size.height;</code></pre>
<p>利用一个UILabel对象,来获取渲染后的文本size,高度。</p>
<blockquote><p>假设固定行数为2行,如果文本内容比较多,就可以直接用下边的<code>fontLineHeight * 2</code>来设置高度(欢迎高手指正)</p></blockquote>
<h2>3. 固定宽度,行数的文本高度(有设置行间距的情况下)</h2>
<pre><code>CGFloat fontLineHeight = _descLabel.font.lineHeight; // 不同系统,字体下高度会和字号大小不同
// 2行,行间距6
CGFloat height = fontLineHeight * 2 + 6;
// 1行,行间距6
CGFloat height = fontLineHeight + 6;</code></pre>
<blockquote><p>以上两种情况,假设一开始设定高度为2行,但是文本只有一行的时候,那么就直接使用<code>fontLineHeight</code>这个高度来设置Label的高度</p></blockquote>
<h2>4. 固定宽度,纯粹获取文本高度</h2>
<pre><code>CGRect introRect = [intro boundingRectWithSize:CGSizeMake(kScreenWidth, CGFLOAT_MAX)
options:0 attributes:@{
NSFontAttributeName:font(14),
NSParagraphStyleAttributeName:paragraphStyle
} context:nil];
CGFloat height = introRect.size.height;</code></pre>
<blockquote><p><code>kScreenWidth</code>是一个获取屏幕宽度的宏定义</p></blockquote>
<blockquote><p><code>boundingRectWithSize</code>方法介绍:</p></blockquote>
<ul>
<li>
<h2>size</h2>
<pre><code>限制最大宽高, 虽然是自适应, 但是需要限制最大的宽度和高度</code></pre>
</li>
<li>
<h2>options</h2>
<pre><code>一个枚举, 绘制自定义方式字符串的选项,可以互相组合。例如:
NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading</code></pre>
<pre><code>enum {
NSStringDrawingTruncatesLastVisibleLine = 1 << 5,
NSStringDrawingUsesLineFragmentOrigin = 1 << 0,
NSStringDrawingUsesFontLeading = 1 << 1,
NSStringDrawingUsesDeviceMetrics = 1 << 3,
};typedef NSInteger NSStringDrawingOptions;</code></pre>
<ul>
<li>
<p>NSStringDrawingTruncatesLastVisibleLine :</p>
<pre><code>如果文本内容超出指定的矩形限制,文本将被截去并在最后一个字符后加上省略号 . 如果三选项没有选择, 忽略此选项</code></pre>
</li>
<li>
<p>NSStringDrawingUsesLineFragmentOrigin :</p>
<pre><code>整个文本将以每行组成的矩形为单位计算整个文本的尺寸
</code></pre>
</li>
<li>
<p>NSStringDrawingUsesFontLeading :</p>
<pre><code>以字体间的行距(leading,行距:从一行文字的底部到另一行文字底部的间距。)来计算高度
</code></pre>
</li>
<li>
<p>NSStringDrawingUsesDeviceMetrics :</p>
<pre><code>计算布局时使用图像符号边界, 而不是排版的边界
</code></pre>
</li>
</ul>
</li>
<li>
<h2>attributes</h2>
<pre><code> 应用于字符串的文本样式字典属性</code></pre>
</li>
<li>
<h2>context</h2>
<pre><code>控制如何调整字间距和缩放。对象包含的信息将用于文本绘制。该参数可为 nil</code></pre>
</li>
<li>
<h2>返回值</h2>
<pre><code>返回一个矩形CGRect, 这个矩形为文字所占的矩形</code></pre>
</li>
</ul>
xcode6.1 SourceKitService crashed or terminated 解决办法
https://segmentfault.com/a/1190000002397021
2014-11-30T11:18:42+08:00
2014-11-30T11:18:42+08:00
前端知否
https://segmentfault.com/u/qethan
1
<blockquote>
<p>我的情况:<br>
环境:xcode6.1<br>
语言:swift<br>
问题:代码不提示;点击类名后,无法进入源代码查看</p>
</blockquote>
<pre><code>我的解决办法:
</code></pre>
<ol>
<li><p>进入 ~/Library/Developer/Xcode/DerivedData/,然后删除里边的所有文件夹和文件。</p></li>
<li><p>重启xcode</p></li>
</ol>
在你的项目中使用Sass Mixins
https://segmentfault.com/a/1190000000518246
2014-05-24T02:15:30+08:00
2014-05-24T02:15:30+08:00
前端知否
https://segmentfault.com/u/qethan
0
<p>原文:<a rel="nofollow" href="https://www.sitepoint.com/sass-mixins-kickstart-project/"></a><a rel="nofollow" href="https://www.sitepoint.com/sass-mixins-kickstart-project/">https://www.sitepoint.com/sass-mixins-kickstart-project/</a></p>
<p>Mixins很好用,就像函数一样,可以输出CSS。Sass的官方文档是这样描述的:</p>
<blockquote>
<p>Mixins 允许自己定义样式,这些样式可以在全局样式表里重用,而不用去借助一些无语义的类,比如.float-left。Mixins可以包含完整的CSS样式规则和其他Sass中的特性规则等。mixin还可以接收参数,不同的参数值将产生不同的样式规则。</p>
</blockquote>
<p>Mixins使用起来非常方便,特别是在大型项目中。在你最近的项目中,你需要定义多少次宽度和高度这两个属性值?为了实现一个CSS三角形,你需要搜索谷歌多少次?或者当设置CSS定位时,你是不是希望有一个快捷方式,可以同时一起设置top,right,bottom,left这些属性?</p>
<p>你可以使用mixins解决这些问题。更爽的是:我已经替你写好这些常用的mixins了。那让我们开始吧。</p>
<h2>Sizing Mixin</h2>
<p>一个 <code>size()</code> mixin 同时可以定义宽度和高度。第一个参数是宽度,第二个是高度。如果高度参数没有设置,那么高度默认和宽度一样。</p>
<pre><code>@mixin size($width, $height: $width) {
width: $width;
height: $height;
}
</code></pre>
<p>非常简单。</p>
<h2>用法示例</h2>
<p>Sass</p>
<pre><code>.element {
@include size(100%);
}
.other-element {
@include size(100%, 1px);
}
</code></pre>
<p>CSS 输出:</p>
<pre><code>.element {
width: 100%;
height: 100%;
}
.other-element {
width: 100%;
height: 1px;
}
</code></pre>
<h2>Positioning Mixin</h2>
<p>我认为CSS最缺少一个简写,比如top,right,bottom,left这些偏移量属性的简写方式。CSS自己已经有了一些可以简写的属性:<code>padding</code>,<code>margin</code>,<code>background</code>,甚至<code>text-decoration</code>!但是没有偏移量属性的简写,所以我开始自己写。</p>
<p>受到Stylus语法的启发:</p>
<pre><code>absolute: top 0 left 1em
</code></pre>
<p>不幸的是,Sass没有提供一个<a rel="nofollow" href="http://learnboost.github.io/stylus/docs/mixins.html">transparent mixin</a>这样的特性。但是我们可以这样做:</p>
<pre><code>@include absolute(top 0 left 1em);
</code></pre>
<p>这种方式看起来还不错。很明显,<code>relative()</code>和<code>fixed()</code>和上边是一样的。</p>
<p>我之前写过一篇<a rel="nofollow" href="http://hugogiraudel.com/2013/08/05/offsets-sass-mixin/">博客</a>,里面详细介绍了如何实现这个mixin。所以这里我就不再详细去说了。不过主要思想是为这个mixin提供一个列表值。如果在这个列表中存在top,right,bottom,left这些属性,那么这些属性的值就跟在它们后边。不过要保证这些值是有效的。</p>
<pre><code>@mixin position($position, $args) {
@each $o in top right bottom left {
$i: index($args, $o);
@if $i and $i + 1< = length($args) and type-of(nth($args, $i + 1)) == number {
#{$o}: nth($args, $i + 1);
}
}
position: $position;
}
@mixin absolute($args) {
@include position("absolute", $args);
}
@mixin fixed($args) {
@include position("fixed", $args);
}
@mixin relative($args) {
@include position("relative", $args);
}
</code></pre>
<h2>用法示例</h2>
<p>Sass:</p>
<pre><code>.element {
@include absolute(top 0 left 1em);
}
.other-element {
@include fixed(top 1em left "WAT? A STRING?!" right 10% bottom)
}
</code></pre>
<p>CSS 输出:</p>
<pre><code>.element {
position: absolute;
top: 0;
left: 1em;
}
.other-element {
position: fixed;
top: 1em;
right: 10%;
}
</code></pre>
<h2>Vendor Prefix Mixin</h2>
<p>当使用CSS3的一些属性时,需要加上各种浏览器厂商提供的属性前缀。比如<code>-webkit-</code>,<code>-moz-</code>等。自己手动加前缀,太繁琐。不过<a rel="nofollow" href="http://compass-style.org/reference/compass/helpers/cross-browser/">Compass</a>和<a rel="nofollow" href="http://bourbon.io/docs/#prefixer-settings">Bourbon</a>这些框架已经为我们提供了这些前缀集合。比如<code>@include box-sizing()</code>。</p>
<p>我一直在使用Compass,但是现在我决定放弃它。主要原因是,它提供的东西,我在项目中只能使用一小部分。放弃使用之后,编译时间也缩短了。当我需要前缀mixin时,我可以自己编写(更新到Sass 3.3)。</p>
<h2>Sass 3.2 版本</h2>
<p>做法很简单。第一个参数是属性。第二个参数是属性值。第三个参数是可选的,表示可以使用的前缀列表。默认值是全部前缀。</p>
<pre><code>@mixin prefix($property, $value, $vendors: webkit moz ms o) {
@if $vendors {
@each $vendor in $vendors {
#{"-" + $vendor + "-" + $property}: #{$value};
}
}
#{$property}: #{$value};
}
</code></pre>
<h2>用法示例</h2>
<p>Sass:</p>
<pre><code>.element {
@include prefix(transform, rotate(42deg), webkit ms);
}
</code></pre>
<p>输出:</p>
<pre><code>.element {
-webkit-transform: rotate(42deg);
-ms-transform: rotate(42deg);
transform: rotate(42deg);
}
</code></pre>
<h2>Sass 3.3 版本</h2>
<p><code>Sass 3.3</code>版本更好,因为它可以做到一次设置多个属性的前缀,使用<code>map</code>这个数据类型。现在你只需要一个map参数,它的键是属性名,值是属性值。</p>
<pre><code>@mixin prefix($map, $vendors: webkit moz ms o) {
@each $prop, $value in $map {
@if $vendors {
@each $vendor in $vendors {
#{"-" + $vendor + "-" + $prop}: #{$value};
}
}
// Dump regular property anyway
#{$prop}: #{$value};
}
}
</code></pre>
<h2>用法示例</h2>
<p>Sass:</p>
<pre><code>.element {
@include prefix((transform: translate(-50%, -50%)), webkit ms);
}
.other-element {
@include prefix((
column-count: 3,
column-gap: 1em,
column-rule: 1px solid silver,
column-width: 20em
)), webkit moz);
}
</code></pre>
<p>CSS 输出:</p>
<pre><code>.element {
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
.other-element {
-webkit-column-count: 3;
-moz-column-count: 3;
column-count: 3;
-webkit-column-gap: 1em;
-moz-column-gap: 1em;
column-gap: 1em;
-webkit-column-rule: 1px solid silver;
-moz-column-rule: 1px solid silver;
column-rule: 1px solid silver;
-webkit-column-width: 20em;
-moz-column-width: 20em;
column-width: 20em;
}
</code></pre>
<h2>进一步优化</h2>
<p>可以在其他mixins里边使用<code>prefix()</code>,像下边这样:</p>
<pre><code>@mixin transform($value) {
@include prefix(transform, $value, webkit ms);
}
@mixin column-count($value) {
@include prefix(column-count, $value, webkit moz);
}
</code></pre>
<h2>用法示例</h2>
<pre><code>.element {
@include transform(rotate(42deg));
}
</code></pre>
<p>CSS 输出:</p>
<pre><code>.element {
-webkit-transform: rotate(42deg);
-ms-transform: rotate(42deg);
transform: rotate(42deg);
}
</code></pre>
<h2>Opposite Direction Mixin</h2>
<p>如果你是Compass的用户,你一定对<code>opposite-direction</code>这个函数很熟悉。这个函数不是mixin。因为Compass是由Ruby写的。不过很容易用Sass实现这个同样功能的mixin。下边的示例,我使用了Sass 3.3版本,不过也很容易调整改写成Sass 3.2版本。</p>
<p>在我写的函数中,你可以传递一个方向列表。所以你可以从反方向(<code>top right</code>)得到<code>bottom left</code>。当你在处理<code>background-position</code>时,这个会非常有用。</p>
<pre><code>@function opposite-direction($directions) {
$opposite-directions: ();
$direction-map: (
'top': 'bottom',
'right': 'left',
'bottom': 'top',
'left': 'right',
'ltr': 'rtl',
'rtl': 'ltr'
);
@each $direction in $directions {
$opposite-direction: map-get($direction-map, $direction);
@if $opposite-direction != null {
$opposite-directions: append($opposite-directions, #{$opposite-direction});
}
@else {
@warn "No opposite direction can be found for `#{$direction}`.";
}
}
@return $opposite-directions;
}
</code></pre>
<h2>用法示例</h2>
<pre><code>$direction: opposite-direction(top);
// bottom
$other-direction: opposite-direction(bottom left);
// top right
</code></pre>
<h2>Breakpoint Handler Mixin</h2>
<p>如果你之前在做响应式设计的话,那么你会知道处理几种不同的媒体查询会很麻烦。现在有一个好的做法:用变量把不同的断点值存储起来。每次使用的时候只需要只用对应的变量就行了。</p>
<p>如果想更进一步的话,你可以去命名每个断点。这样的话,断点处理mixin就只根据这个断点名字就可以输出对应的断点查询。</p>
<p>还可以再进行优化,那就是把<code>这些命名和它对应的断点</code>存储在一个map中。代码如下:</p>
<pre><code>$breakpoints: (
'tiny': ( max-width: 767px ),
'small': ( min-width: 768px ),
'medium': ( min-width: 992px ),
'large': ( min-width: 1200px ),
'custom': ( min-height: 40em )
);
@mixin breakpoint($name) {
@if map-has-key($breakpoints, $name) {
@media #{inspect(map-get($breakpoints, $name))} {
@content;
}
}
@else {
@warn "Couldn't find a breakpoint named `#{$name}`.";
}
}
</code></pre>
<p>如果<code>breakpoint</code>mixin中传入的字符串值和<code>$breakpoints</code>这个map中的某个键值匹配,那么这个mixin会打开一个@media指令,然后使用<code>inspect</code>函数(来自Sass 3.3)输出这个map。当使用<code>inspect((key:value))</code>时,'(key:value)`这个字符串会被输出到样式表中,相当于字符串化操作。</p>
<p>如果传入的字符串参数没有匹配任何一个断点map的键值,那么<code>@warn</code>这个指令会输出警告信息,在控制台里可以看到。</p>
<h2>用法示例</h2>
<p>Sass:</p>
<pre><code>.element {
color: red;
@include breakpoint(medium) {
color: blue;
}
}
</code></pre>
<p>CSS 输出:</p>
<pre><code>.element {
color: red;
}
@media (min-width: 992px) {
.element {
color: blue;
}
}
</code></pre>
<h2>最后</h2>
<p>我相信这是一个好的开始。这些工具可以让你在下个项目中,减少编写CSS花费的时间。</p>
Sass->十分钟写一个Sass组件
https://segmentfault.com/a/1190000000515630
2014-05-21T16:59:16+08:00
2014-05-21T16:59:16+08:00
前端知否
https://segmentfault.com/u/qethan
2
<p>原文:<a rel="nofollow" href="https://www.sitepoint.com/sass-component-10-minutes/">https://www.sitepoint.com/sass-component-10-minutes/</a></p>
<p>现在一些开发者认为,把具有特定功能的代码片段独立出来,互相组合,去开发网站或应用,这是一种很好的方式。也就是组件化,但是实现起来却没有那么容易。</p>
<p>一个好的示例让人豁然开朗, 我选取的示例是几乎每个网站或应用都具有的交互组件:信息提示。</p>
<p>通过写一个具有不同信息类型的提示组件,会有助于提升你的Sass。那就让我们开始动手吧。</p>
<h2>定义提示颜色</h2>
<p>我们需要什么,信息。什么样式的信息?让我们参照一下具有这个组件的框架:<strong>Bootstrap</strong>。Bootstrap定义了<code>四种类型</code>的提示信息:</p>
<ul>
<li>验证</li>
<li>错误</li>
<li>警告</li>
<li>信息</li>
</ul>
<p>看起来挺适合我们用来作为参考。在生活中我们会用不同的语调去表达不同的情感,同理,我们可以在网站上用不同的颜色去显示不同类型的信息。四种类型的提示,每个都有自己的颜色。让我们再一次借用一下<code>Bootstrap</code>的做法,定义我们提示的颜色:</p>
<ul>
<li>绿色表示成功</li>
<li>红色表示错误</li>
<li>黄色或者橘黄色表示警告</li>
<li>浅蓝色表示信息,说明</li>
</ul>
<h2>基本样式</h2>
<p>所有的信息都有一些公共的样式,如 padding, margins, 也可能会有排版样式。最后,唯一不同的样式是<strong>表示不同提示的颜色</strong>。</p>
<p>让我们先写一个<strong>placeholder</strong>:</p>
<pre><code>%message {
padding: .5em;
margin-bottom: .5em;
border-radius: .15em;
border: 1px solid;
}
</code></pre>
<p>第一个要注意的事情是:我们没有设置任何字体样式,如大小,字体,或者行高。因为<code>使用这个模块的项目,一般都已经设置了这些通用的样式</code>。现在这样,会让我们的组件更加<code>独立化</code>。</p>
<p>我们也没有设置边框的颜色。<code>border-color</code>这个属性的默认值是<code>currentcolor</code>。在大多浏览器中,是黑色。</p>
<p>现在开始写一个mixin:</p>
<pre><code>@mixin message($color) {
@extend %message;
color: $color;
border-color: lighten($color, 20%);
background: lighten($color, 40%);
}
</code></pre>
<p>就像你所看到的,mixin做了两件事情:</p>
<ol>
<li><p>它设置了一个关于<code>color</code>的属性,通过参数把一个颜色值传递进来。然后设置了<code>color</code>,<code>border-color</code>这两个属性,很感谢Sass的<code>lighten</code>颜色函数。使用Sass的颜色操作函数有助于减少mixin需要输入的参数数量。</p></li>
<li><p>继承<code>%message</code>这个placeholder,所以你不用在每个信息类型中去重复书写这些样式了。这样一来,代码非常符合<code>DRY</code>原则。</p></li>
</ol>
<h2>调用mixin</h2>
<p>到现在为止,已经完成的差不多了。还剩下的事情是调用mixin,为不同类型的提示信息传入其对应的颜色参数值:</p>
<pre><code>.message-error {
@include message(#b94a48);
}
.message-valid {
@include message(#468847);
}
.message-warning {
@include message(#c09853);
}
.message-info {
@include message(#3a87ad);
}
</code></pre>
<h2>让组件可用性更强</h2>
<p>我们现在做的已经相当整洁了。如果想增加一个新的提示类型,那需要做的只是<code>@include message(你选择的颜色值)</code>,在那个新的提示类型对应的class里边。但是我们能使组件代码更轻便适用吗?</p>
<p>把每个类型的提示和一个颜色映射对应起来,有一些好的方式吗?有:嵌套列表(Nested Lists)。通过创建一个二维列表,然后循环这些值,我们可以很容易做到。</p>
<pre><code>$message-types: (
(error #b94a48)
(valid #468847)
(warning #c09853)
(info #3a87ad)
) !default;
@each $message-type in $message-types {
$type: nth($message-type, 1);
$color: nth($message-type, 2);
.message-#{$type} {
@include message($color);
}
}
</code></pre>
<p>现在Sass生成的代码和我们上边写的效果一样。你可以把变量列表放到样式表的顶部或者一个配置文件中去,这样的话,增删改一个信息类型将会很方便容易。</p>
<blockquote>
<p>提醒:如果你打算将这个组件添加到某个库或者框架中去,那么<code>!default</code>标记($message-types这个变量中使用的)将会让你很容易去覆盖这个变量,如果你有这个需求的话。</p>
</blockquote>
<h2>使用map(Sass 3.3)</h2>
<p>我们可以使用Sass 3.3中新增的数据类型:<code>Maps</code>。Maps就像PHP中的关联数组,或者javascript中的对象。他们都是键值对映射。</p>
<pre><code><br>$message-types: (
error : #b94a48,
valid : #468847,
warning : #c09853,
info : #3a87ad
) !default;
@each $type, $color in $message-types {
.message-#{$type} {
@include message($color);
}
}
</code></pre>
<p>很爽吧。</p>
<h2>优雅的错误处理</h2>
<p>经常被Sass开发者们忽略的一件事情是:优雅地处理错误的能力。你应该保证<strong>自定义的函数和mixins中</strong>传入的参数值是正确的,如果不正确,那么要提供警告信息。比起让Sass自己去处理这些错误好多了。</p>
<p>在这个示例里边,我们需要保证<code>$color</code>这个参数的值必须是一个颜色值。</p>
<pre><code><br>@mixin message($color) {
@if type-of($color) == color {
@extend %message;
color: $color;
border-color: lighten($color, 20%);
background: lighten($color, 40%);
}
@else {
@warn "#{$color} is not a color for `message`.";
}
}
</code></pre>
<p>现在这样的话,就能够保证当mixin接受到一个非颜色的参数值时,Sass不会崩溃掉。另外,它也能帮助开发者知道这是由无效参数引发的错误。</p>
<h2>最后的话</h2>
<p>在30行的SCSS里边,我们写出了这样的组件:</p>
<ul>
<li>干净,好懂</li>
<li>DRY,轻量</li>
<li>可随处使用,可配置</li>
<li>容易扩展</li>
</ul>
<p>这些是适用于所有组件的规则。</p>
Sass->什么时候使用Mixins 和 Placeholders
https://segmentfault.com/a/1190000000512082
2014-05-19T08:52:26+08:00
2014-05-19T08:52:26+08:00
前端知否
https://segmentfault.com/u/qethan
4
<p>原文:<a href="https://link.segmentfault.com/?enc=Z6LQmnolE7dp%2BH56h1PI%2Bw%3D%3D.8NHu0TDyWvkpoCHbFDgjY9t4eMd6iiy3%2FoQ7s8KXXXFuXhzgMzlj4BXuwnJRWa5aCML73YmrnxbuD7HjxEh9oQ%3D%3D" rel="nofollow">https://www.sitepoint.com/sass-mixin-placeholder/</a></p>
<p>一年半之前,我开始使用Sass的时候,对于<code>include a mixin</code>和<code>extending a placeholder</code>的区别,我花费了一些时间去理解它们。<br> 在那个时候,单单placeholder的概念,就已经相当于一种黑魔法巫术一样让我不知其解。</p>
<p>如果你有同样的疑问,不要担心,我接下来会说解释指明它们之间的区别。今天我们会学到minxin是什么东西,和什么时候去使用Sass的placeholder。你会明白他们有不同的用处,不能混淆使用。</p>
<blockquote>提醒:我接下来要谈论的关于Sass的观点,同样适用于其他CSS预处理器,不管是Stylus,Less,还是其他的。技术大多做的是同样的事情,所以完全不用担心这篇文章的内容,是否适合你已经选择使用的其他CSS预编译工具。</blockquote>
<p>首先我们先去熟悉和认识 <strong>Sass placeholders and mixins</strong></p>
<h2>Mixin it up, 混合体,封装体</h2>
<p>一个mixin指令可以让你去定义很多CSS规则。把它看做是一个function函数,方法,这个function可以有自己的参数。它会输出这些css规则内容,而不是返回一个值。下边是来自Sass官方参考里定义:</p>
<blockquote>
<code>Mixins</code> 允许自己定义样式,这些样式可以在全局样式表里重用,而不用去借助一些无语义的类,比如<code>.float-left</code>。Mixins可以包含完整的CSS样式规则和其他Sass中的特性规则等。mixin还可以接收参数,不同的参数值将产生不同的样式规则。</blockquote>
<p>在样式表中,你会见到一些CSS规则声明被重复出现了好多次。你明白这样的代码不好,而且还知道DRY(Don't Repeat yourself)这个概念原则。现在使用mixin去改善这样的代码:</p>
<pre><code>@mixin center() {
display: block;
margin-left: auto;
margin-right: auto;
}
.container {
@include center();
/* Other styles here... */
}
/* Other styles... */
.image-cover {
@include center;
}</code></pre>
<blockquote>提醒:include使用的时候,如果你不传递参数给mixin,那么可以去掉mixin名字后边的括号。比如 <code>@include center</code>; 其实你也可以在mixin定义的时候就把括号去掉。比如 <code>@mixin center {}</code>
</blockquote>
<p>定义了这个mixin之后,你就不用每次去重复那三行元素居中的规则。使用的时候,就去包含这个mixin。</p>
<p>在某些情况下,可以使用一个mixin去创建一些属性组合的“缩写”。例如 <code>width</code> 和 <code>height</code>。你应该已经厌倦了重复书写这两行属性。特别是当这两个属性值一样的时候。现在我们就使用一个mixin来解决这些问题!</p>
<pre><code>@mixin size($width, $height: $width) {
width: $width;
height: $height;
}</code></pre>
<p>很简单吧。这里我们设置hight属性的<code>默认值</code>和width参数的值一样。当你想定义一个元素的面积大小时,你可以这样做:</p>
<pre><code>.icon {
@include size(32px);
}
.cover {
@include size(100%, 10em);
}
</code></pre>
<blockquote>提醒:当我想去设置一个元素的position属性时,为了避免逐个书写top, right, bottom, left这些属性。也可以去使用mixin这种很好的语法糖。</blockquote>
<h2>认识 Placeholder(占位符)</h2>
<p>Placeholders 是一种奇怪的东西。它们是class,但是在Sass编译过后,并不会被输出,出现在样式表文件里。然后你会问它有什么意义。事实上,如果不是为了<code>@extend</code>这个指令,它都没什么意义。你可以这样去写一个placeholder:</p>
<pre><code>%center {
display: block;
margin-left: auto;
margin-right: auto;
}
</code></pre>
<blockquote>提醒:Like a placholder, a mixin is likewise useless unless its referenced, so this section is not necessarily saying they are different in that respect, but this is only clarifying that even though it looks similar to a CSS declaration block, it won’t get compiled on its own.</blockquote>
<p>placeholder的写法使用%,而不是.(点),但是遵守class的命名规则。</p>
<p>如果编译Sass文件,placeholder的代码不会出现在生成的css的文件里。正如我说过的,placeholder的代码不会被编译出现在css源文件里。</p>
<p>placeholder 要通过 <code>@extend</code> 去使用。<code>@extend</code>指令的作用是<code>继承</code>一个<code>CSS选择器</code>的属性或者一个<code>Sass的placeholder</code>代码。例如:</p>
<pre><code>.container {
@extend %center;
}
</code></pre>
<p>这样之后,Sass会获得<code>%center</code>这个placeholder的内容给<code>.container</code> 这个类。</p>
<p>另外,你还可以extend一个CSS class,就像这样:</p>
<pre><code>.table-zebra {
@extend .table;
tr:nth-of-type(even) {
background: rgba(0,0,0,.5);
}
}
</code></pre>
<p>这是<code>@extend</code>的常用法。当你使用模块组件化开发一个网站或者应用,继承选择器是便利的。</p>
<h2>使用哪一个</h2>
<p>我们应该使用哪一个,mixin还是placeholder。要看具体使用场景。<br>最好的建议是:如果你需要参数变量,使用mixin。否则,继承一个placehodler。这样做两个原因:</p>
<p>第一,在placeholder里面,不能像mixin那样传递使用参数变量。但是可以使用全局变量。</p>
<p>第二,当你使用mixin时,Sass会重复输出这个mixin的属性规则内容,不会让CSS选择器公用这个mixin。这样的话,样式表将会变得很大。</p>
<pre><code>@mixin center {
display: block;
margin-left: auto;
margin-right: auto;
}
.container {
@include center;
}
.image-cover {
@include center;
}
</code></pre>
<p>输出如下:</p>
<pre><code>.container {
display: block;
margin-left: auto;
margin-right: auto;
}
.image-cover {
display: block;
margin-left: auto;
margin-right: auto;
}
</code></pre>
<p>看到重复的CSS了吧。如果只有三行代码重复的话,感觉好像问题还不是很糟糕。但是如果这个mixin有300行呢。重复的代码就太多了。那让我们使用placeholder改造一下这个示例:</p>
<pre><code>%center {
display: block;
margin-left: auto;
margin-right: auto;
}
.container {
@extend %center;
}
.image-cover {
@extend %center;
}
</code></pre>
<p>下边是生成的CSS:</p>
<pre><code>.container,
.image-cover {
display: block;
margin-left: auto;
margin-right: auto;
}
</code></pre>
<p>看起来好多了吧。这样就避免了总是重复相同的属性规则,使用placeholder,会让整个样式表文件很瘦。</p>
<p>另外,如果你在不同的地方都要使用一些属性,但是这些属性的值是变量决定的,那么mixin是一个好的选择。如果你的CSS属性同时有固定的和变动的值,那么你可以组合使用mixin和placeholder。例如:</p>
<pre><code>%center {
margin-left: auto;
margin-right: auto;
display: block;
}
@mixin skin($color, $size) {
@extend %center;
background: $color;
height: $size;
}
a { @include skin(pink, 10em) }
b { @include skin(blue, 90px) }
</code></pre>
<p>在这个示例里边,mixin继承了placeholder, 生成了干净不重复的CSS:</p>
<pre><code>a, b {
margin-left: auto;
margin-right: auto;
display: block;
}
a {
background: pink;
height: 10em;
}
b {
background: blue;
height: 90px;
}
</code></pre>
<h2>总结</h2>
<p>希望你已经清楚了什么是mixins和placeholders,而且知道什么时候去使用它们和它们编译之后的效果。</p>
<p><img src="/img/bVbnLDv?w=2800&h=800" alt="图片描述" title="图片描述"></p>
强迫症->js注释规范
https://segmentfault.com/a/1190000000502593
2014-05-12T14:05:07+08:00
2014-05-12T14:05:07+08:00
前端知否
https://segmentfault.com/u/qethan
8
<blockquote>
<p>之前自己写代码,就像一盘散沙,完全没有一种规范。这种自由,会让自己写的东西时常变化。也很不利于团队协作开发。经过最近一段时间的开发,和对一些注释风格的参考,形成了自己想去使用的注释规范。</p>
</blockquote>
<p>js的组织是<code>模块化</code>,一个模块对应一个js文件。</p>
<p>模块功能描述说明:</p>
<pre><code>/**
* ------------------------------------------------------------------
* 模块描述说明
* ------------------------------------------------------------------
*/
</code></pre>
<p>我喜欢<code>开始和结束</code>各空一行,中间是<code>描述内容</code>。</p>
<p>模块内的小函数方法归类:</p>
<pre><code>/**
* 小函数方法归类说明,这些零散的小函数方法放在一起 对应 一个业务方法逻辑
* ------------------------------------------------------------------
*/
</code></pre>
<p>把<code>一个业务方法</code>中抽取出来的小函数放在一起,便于查找。</p>
<p>单个函数方法:</p>
<pre><code>/**
* 函数功能简述
*
* 具体描述一些细节
*
* @param {string} address 地址
* @param {array} com 商品数组
* @param {string} pay_status 支付方式
* @returns void
*
* @date 2014-04-12
* @author QETHAN<qinbinyang@zuijiao.net>
*/
</code></pre>
<p>开发中使用的是PhpStorm IDE, 每次创建一个js新文件,文件内容头部会根据配置文件模板去自动加上一些注释信息。我配置的是 日期 和 作者。现在是一个人开发,所以上边注释中的日期和作者 我一般不会在函数中去加上。<code>但是,如果其他人参与进来了,自己修改的是别人的代码,就要更新添加这些注释信息。</code></p>
<p>单行注释:</p>
<pre><code>//这是一条单行注释
</code></pre>
<p>有些人喜欢这样 <code>// 这是一条单行注释</code> 双斜杠后边会加一个空格。我不认同。<code>喜欢干练清晰简洁,在适合的时候,就一定会这样做。</code></p>
<p>单个函数方法中变量注释:</p>
<pre><code>//商品属性变量(一组变量描述)
//商品名字(单个变量注释)
var name = $(item).find('.js-name').val(),
//商品数量
count = $(item).find('.js-count').text(),
//商品单价
price = $(item).find('.js-price').val();
</code></pre>
<p>有些喜欢注释放在单个变量后边。如果变量注释有点长,就不太好了。放在上边,比较省心,清晰。</p>
<p>单个函数方法中代码片段注释:</p>
<pre><code>/*
| 代码片段的描述说明
*/
</code></pre>
<p>if, foreach, addEventListener ... 这些代码片段的时候</p>
<blockquote>
<p>注释中缩进 必须使用空格。保证各种环境下排版的一致性。</p>
<p><a rel="nofollow" href="http://usejsdoc.org/"><code>@use JSDoc</code></a></p>
</blockquote>
<p><code><持续维护更新...></code></p>
Laravel 初次体验
https://segmentfault.com/a/1190000000501874
2014-05-11T23:13:11+08:00
2014-05-11T23:13:11+08:00
前端知否
https://segmentfault.com/u/qethan
1
<p>官方文档地址:<a href="https://link.segmentfault.com/?enc=YtXgIYlu5CNWJmwR4ApgZw%3D%3D.%2FpZ2dL72qlu3jokiwe7OwlPTkyMK0J1eMFV8zR3Wd0In7NAz5qo1bBeCwKaUQZnZ" rel="nofollow">http://www.golaravel.com/docs/4.1/introduction/</a></p>
<p>系统环境 <code>Mac OSX 10.9.2</code></p>
<h2>升级php</h2>
<pre><code>brew update
brew upgrade
brew tap homebrew/dupes
brew tap josegonzalez/homebrew-php
brew install php55
</code></pre>
<h2>安装composer</h2>
<pre><code>https://getcomposer.org/doc/00-intro.md#globally-on-osx-via-homebrew-
</code></pre>
<h2>安装php55-mcrypt</h2>
<p>(启动后访问路由地址,如果提示 Mcrypt PHP extension required)</p>
<pre><code>1.Mac brew install php55-mcrypt
2.XAMPP 我没遇到那个提示,一切正常。php -i | grep mcrypt 显示为enabled
</code></pre>
<h2>创建一个新项目</h2>
<pre><code>composer create-project laravel/laravel demo
</code></pre>
<h2>配置 /etc/hosts:(个人喜好)</h2>
<pre><code>127.0.0.1 localhost
127.0.0.1 laravel.dev
</code></pre>
<h2>配置 /Applications/XAMPP/xamppfiles/etc/extra/httpd-vhosts.conf</h2>
<pre><code><VirtualHost *:80>
ServerAdmin qbylucky@gmail.com
DocumentRoot "/Applications/XAMPP/xamppfiles/htdocs/demo/public"
ServerName laravel.dev
ServerAlias laravel.dev
ErrorLog "logs/qbylucky@gmail.com-error_log"
CustomLog "logs/qbylucky@gmail.com-access_log" common
</VirtualHost>
</code></pre>
<h2>启动项目</h2>
<p>开启 XAMPP apache 服务器<br><a href="https://link.segmentfault.com/?enc=8bu%2FTKMIoazOjlsPVTByxw%3D%3D.DjN332TsLNStJlh1Vw1p1CeKO5VvGpS4L%2F8TBxQo%2BXw%3D" rel="nofollow">http://laravel.dev/</a></p>
<h2>知识点</h2>
<pre><code>php的位置:
1.Mac /usr/bin/php
2.XAMPP /Applications/XAMPP/xamppfiles/bin/php
php.ini的位置:
1.Mac /etc/php.ini
2.XAMPP /Applications/XAMPP/xamppfiles/etc/php.ini
</code></pre>
Javascript 正则使用第一篇
https://segmentfault.com/a/1190000000500300
2014-05-10T00:58:55+08:00
2014-05-10T00:58:55+08:00
前端知否
https://segmentfault.com/u/qethan
0
<h3>replace()</h3>
<h3>参数说明</h3>
<ul>
<li><p><code>@param match</code><br>
The matched substring. (Corresponds to $&.)</p></li>
<li><p><code>@param p1</code></p></li>
<li><code>@param p2</code></li>
<li><p><code>@param p3</code><br>
The nth parenthesized submatch string,<br>
provided the first argument to replace was a RegExp object.<br>
(Corresponds to $1, $2, etc. above.) For example,<br>
if /(\a+)(\b+)/, was given, p1 is the match for \a+, and p2 for \b+.</p></li>
<li><p><code>@param offset</code><br>
The offset of the matched substring within the total string being examined. (For example,<br>
if the total string was "abcd",<br>
and the matched substring was "bc",<br>
then this argument will be 1.)</p></li>
<li><p><code>@param string</code><br>
The total string being examined.</p></li>
<li><p><code>@returns {*|string}</code></p></li>
</ul>
<pre><code>function replacer(match, p1, p2, p3, offset, string) {
console.log('match: ' + match);
console.log('string: ' + string);
//p1 is nondigits, p2 digits, and p3 non-alphanumerics
console.log('p1: ' + p1);
console.log('p2: ' + p2);
console.log('p3: ' + p3);
//此处赋值,为了更好的观察p1,p3的值,位置
p1 = 1;
p3 = 3;
return [p1, p2, p3].join(' - ');
}
newString = "111abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
console.log(newString);
</code></pre>
<h3>运行结果:</h3>
<pre><code> * match: 111
* string: 111abc12345#$*%
* p1:
* p2: 111
* p3:
* 1 - 111 - 3abc12345#$*%
</code></pre>
<pre><code>function replacer2(match, p1, p2, p3, offset, string) {
console.log('match: ' + match);
console.log('string: ' + string);
// p1 is nondigits, p2 digits, and p3 non-alphanumerics
console.log('p1: ' + p1);
console.log('p2: ' + p2);
console.log('p3: ' + p3);
return [p1, p2, p3].join(' - ');
}
newString2 = "abc12345xxx#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer2);
console.log(newString2);
</code></pre>
<h3>运行结果:</h3>
<pre><code> * match: abc12345
* string: abc12345xxx#$*%
* p1: abc
* p2: 12345
* p3:
* abc - 12345 - xxx#$*%
</code></pre>
<pre><code>function replacer3(match, p1, p2, p3, offset, string) {
console.log('match: ' + match);
console.log('string: ' + string);
// p1 is nondigits, p2 digits, and p3 non-alphanumerics
console.log('p1: ' + p1);
console.log('p2: ' + p2);
console.log('p3: ' + p3);
return [p1, p2, p3].join(' - ');
}
newString3 = "1111abc12345xxx#$*%2392039abc12345xxx#$*%".replace(/([^\d]*)(\d*)([^\w]*)/g, replacer3);
console.log(newString3);
</code></pre>
<h3>运行结果:</h3>
<pre><code> * 第一次匹配
* match: 1111
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1:
* p2: 1111
* p3:
* 第二次匹配
* match: abc12345
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1: abc
* p2: 12345
* p3:
* 第三次匹配
* match: xxx#$*%2392039
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1: xxx#$*%
* p2: 2392039
* p3:
* 第四次匹配
* match: abc12345
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1: abc
* p2: 12345
* p3:
* 第五次匹配
* match: xxx#$*%
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1: xxx#$*%
* p2:
* p3:
* 第六次匹配
* match:
* string: 1111abc12345xxx#$*%2392039abc12345xxx#$*%
* p1:
* p2:
* p3:
* - 1111 - abc - 12345 - xxx#$*% - 2392039 - abc - 12345 - xxx#$*% - - - -
</code></pre>
<h2>match()</h2>
<pre><code>function match1() {
var str = "For more information, see Chapter 3.4.5.1",
re = /(chapter \d+(\.\d)*)/i,
found = str.match(re);
console.dir(found);
}
match1();
</code></pre>
<h3>运行结果:</h3>
<pre><code> * Array[3]
* 0: "Chapter 3.4.5.1"
* 1: "Chapter 3.4.5.1"
* 2: ".1"
* index: 26
* input: "For more information, see Chapter 3.4.5.1"
* length: 3
*
* [input]:
* which contains the original string that was parsed
* [0]:
* the first match
* [1]:
* the first value remembered from (Chapter \d+(\.\d)*).
* [2]:
* ".1" is the last value remembered from (\.\d).
* ---------------------------------------------------
*/
</code></pre>
<pre><code>function match2() {
var str = "For more information, see Chapter 3.4.5.1",
re = /(chapter \d+(\.\d)*)/gi,
found = str.match(re);
console.dir(found);
}
match2();
</code></pre>
<h3>运行结果:</h3>
<pre><code> * Array[1]
* 0: "Chapter 3.4.5.1"
* length: 1
*
* 如果使用了全局匹配g, 则只显示匹配到的全部字符串。
* 也没有input属性
</code></pre>
<pre><code>function match3() {
var str = "qbylucky@gmail.com",
re = /(.*)@(.*)\.(.*)/,
found = str.match(re);
console.dir(found);
}
match3();
</code></pre>
<h3>运行结果:</h3>
<pre><code> * Array[4]
* 0: "qbylucky@gmail.com"
* 1: "qbylucky"
* 2: "gmail"
* 3: "com"
* index: 0
* input: "qbylucky@gmail.com"
* length: 4
</code></pre>
<pre><code>function match4() {
var str = "qbylucky@gmail.com",
re = /(.*)@(.*)\.(.*)/g,
found = str.match(re);
console.dir(found);
}
match4();
</code></pre>
<h3>运行结果:</h3>
<pre><code> * Array[1]
* 0: "qbylucky@gmail.com"
* length: 1
</code></pre>