SegmentFault 冷星的前端杂货铺最新的文章
2023-09-08T09:40:00+08:00
https://segmentfault.com/feeds/blogs
https://creativecommons.org/licenses/by-nc-nd/4.0/
8月血泪史,助力金九银十
https://segmentfault.com/a/1190000044194258
2023-09-08T09:40:00+08:00
2023-09-08T09:40:00+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote><p>本文出现的问题都是在8月阶段实际面试过程中遇到的,大小公司都有涉及,希望能够助力大家金九银十,也希望自己能够顺利找到工作。现在外面大环境的确太难了……</p><p>文中部分题目提供了参考资料以及自己整理的相关答案,可能不一定是最准确的,大家酌情参考。</p><p>如涉及到其他文章内容引用没有标明出处的,可以私聊补充更新。</p></blockquote><h2>Javascript</h2><h3>1.JS基本数据类型有哪些</h3><p>基本数据类型:</p><ol><li>字符串(String):表示文本数据,使用引号(单引号或双引号)括起来。例如:"Hello World"。</li><li>数字(Number):表示数值数据,包括整数和浮点数。例如:10、3.14。</li><li>布尔值(Boolean):表示逻辑值,只有两个值:true(真)和false(假)。</li><li>null:表示空值,表示一个空对象指针。</li><li>undefined:表示未定义的值,表示变量未被赋值。</li><li>Symbol:表示唯一的标识符,用于对象属性的键。</li></ol><h4>【扩展】说一下undefined和null的区别</h4><p>当谈到 JavaScript 中的 undefined 和 null 时,它们是两个不同的值,但都表示缺少值。它们之间的区别如下:</p><ol><li>undefined 是表示变量声明但未赋值的默认值。当尝试访问未初始化的变量时,它的值为 undefined。全局属性 undefined 表示原始值undefined。它是一个 JavaScript 的 原始数据类型 。undefined是全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined的最初值就是原始数据类型undefined。</li><li>值 null 特指对象的值未设置。它是 JavaScript 基本类型 之一,在布尔运算中被认为是falsy。它是一个特殊的关键字,表示变量不指向任何对象、数组或函数。值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。</li></ol><h3>2.const、let和var的区别</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=hS5LPaMTh5ESJm%2Fy58MteA%3D%3D.RneeOxqShwJLlCISYoRILLOxbcNJv10ixRi9UsJf3b2zWZlp0rf0dRJBrN4KzW9tBdKz4N3ye4OIB8d8SBySRg%3D%3D" rel="nofollow">面试官:说说var、let、const之间的区别</a></p><h3>3.call、apply和bind的区别</h3><ul><li>三者都可以改变函数的this对象指向。</li><li>三者第一个参数都是this要指向的对象,如果如果没有这个参数或参数为undefined或null,则默认指向全局window。</li><li>三者都可以传参,但是apply是数组,而call是参数列表,且apply和call是一次性传入参数,而bind可以分为多次传入。</li><li>bind 是返回绑定this之后的函数,便于稍后调用;apply 、call 则是立即执行 。</li></ul><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=cWwGDgkGU5oJmfpbOsFkmQ%3D%3D.KXJWqkUnLl%2FlIl%2BODtqcYWdQpPehGHANTe8whYsuafGFWAgbNzxHUG3Yk7uooAXR" rel="nofollow">彻底弄懂bind,apply,call三者的区别</a></p><p><a href="https://link.segmentfault.com/?enc=pB%2FYhAi8by7DeaWR9c6o3Q%3D%3D.x3WI%2FI1nAVDipwwlt1kn0VextPV4fjnU1xCHcHtRUrM2nJOb%2F9oAv%2FWnhSvmUtfs" rel="nofollow">深入了解 call, apply, bind 和模拟实现</a></p><p><a href="https://link.segmentfault.com/?enc=GQHtL7pf6b0Shi9it6XlJQ%3D%3D.MKh8ZqJIdrkk0x0EiWGGu3bgUZ%2FHCfncbPIlYOOIOEBQI0YSik6dRGutXWrKCjFE" rel="nofollow">5分钟带你搞懂 Javascript 中的this(包含apply、call、bind)</a></p><h3>4.对闭包的理解</h3><h4>概念</h4><p>一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。</p><p>重点的一句:<strong>闭包让你可以在一个内层函数中访问到其外层函数的作用域。</strong></p><h4><strong>使用场景</strong></h4><ul><li>return一个内部函数,读取内部函数的变量;</li><li>函数作为参数</li><li>IIFE(自执行函数)</li><li>封装对象的私有对象和私有方法;</li></ul><h3>5.new 一个构造函数的过程</h3><p>用new操作符创建对象时发生的事情:</p><ul><li>创建新对象</li><li>新对象原型[[prototype]] = 构造函数prototype</li><li>this 指向新对象</li><li>执行构造函数</li><li>如果构造函数返回非空对象,就返回这个对象引用,不然返回创建的新对象</li></ul><p>函数调用时前面不加new就按普通函数来 执行。加new后对其进行了相应的变动, 按构造函数来执行。</p><p><strong>new的具体过程如下:</strong></p><pre><code>//例子:
function Person(name,age) {
this.userName = name;
this.userAge = age;
}
var personl = new Person('LULU',20)</code></pre><p><strong>1、创建一个新的空对象。(即实例对象)</strong></p><pre><code>obj = {}</code></pre><p><strong>2 、设 置 原 型 链</strong></p><p>将新对象obj的 __proto__属性指向构造函数的prototype对象。(即所有实例对象通过__proto__可以访问原型对象。构造函数的原型被其所有实例对象共享。)</p><pre><code>obj.__proto__= Person.prototype</code></pre><p><strong>3 、将构造函数的this改指向新对象obj并执行函数代码。</strong></p><p>(即给实例对象设置属性 userName, userAge并赋值。)</p><pre><code>var result = Person.apply(obj,['LULU',20])</code></pre><p><strong>4 、如果构造函数中没有人为返回一个对象类型的值,则返回这个新对象obj。否则直接返回那个对象类型值。(一般定义的构造函数中不写返回值。)</strong></p><pre><code>if (typeof(result) == 'object') {
return result;
}else{
return obj;
}</code></pre><h4><strong>【扩展】手动实现一个new操作,参数为构造函数 及其传参</strong></h4><pre><code>//构造函数
function Person(name,age) {
this.userName = name;
this.userAge = age;
}
Person.prototype.sayAge = function(){
console.Iog(this.userAge)
}
// new操作函数newFun
function newFun() {
var obj = {};
Constructor = [].shift.call(arguments);
obj.__proto__ = Constructor.prototype;
var ret = Constructor.apply(obj, arguments);
return typeof ret === 'object' ? ret: obj;
};
//调用 newFun
var s1 = newFun(Person, 'xiaoyun',20);
console.log(s1.userName) //xiaoyun
console.log(s1.sayAge()) //20</code></pre><p>备注:[].shift.call(arguments):删除并返回参数列表arguments中第一个参数,即获得构造函数。arguments剩余参数为构数传参。arguments是类数组,没有数组方法shift,可更改shift方法的this指向从而运用到 arguments 上。</p><h3>6.ES6高阶函数有哪些</h3><p>ES6中的高阶函数有以下几种:</p><ol><li>map:对数组中的每个元素进行操作,并返回一个新的数组。</li><li>filter:根据指定的条件过滤出数组中的元素,并返回一个新的数组。</li><li>reduce:对数组中的元素进行累加或累积操作,并返回一个结果。</li><li>forEach:对数组中的每个元素进行操作,没有返回值。</li><li>some:判断数组中是否存在满足指定条件的元素,返回布尔值。</li><li>every:判断数组中的所有元素是否都满足指定条件,返回布尔值。</li><li>find:查找数组中满足指定条件的第一个元素,并返回该元素。</li><li>findIndex:查找数组中满足指定条件的第一个元素的索引,并返回该索引。</li><li>sort:对数组中的元素进行排序。</li><li>flat:将多维数组转换为一维数组。</li><li>map、filter、reduce等方法的变种,如flatMap、find、findIndex等。</li></ol><p>这些高阶函数可以使代码更简洁、可读性更高,并且提供了一种函数式编程的方式来操作数组。</p><h2>Typescript</h2><h3>1.TS对比JS的优势有哪些</h3><h2>CSS</h2><h3>1.说下什么是BFC</h3><h4>BFC到底是什么东西</h4><p>BFC 全称:Block Formatting Context, 名为 "块级格式化上下文"。</p><p>W3C官方解释为:BFC它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,Block Formatting Context提供了一个环境,HTML在这个环境中按照一定的规则进行布局。</p><p>简单来说就是,BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。那么怎么使用BFC呢,BFC可以看做是一个CSS元素属性</p><h4>怎样触发BFC</h4><p>这里简单列举几个触发BFC使用的CSS属性</p><ul><li>overflow: hidden</li><li>display: inline-block</li><li>position: absolute</li><li>position: fixed</li><li>display: table-cell</li><li>display: flex</li></ul><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=KTQpSPWWm7EIH4ePPUK6WQ%3D%3D.P44%2BsqGPX4TbT0yfWLMHlUiRtnnQzSdFSAio%2BS75hpWtTGR7gaodoVvsdOKePCKO" rel="nofollow">面试官:请说说什么是BFC?大白话讲清楚</a></p><p><a href="https://link.segmentfault.com/?enc=jdMRLYEHOsvVGfv%2Bn0bzuw%3D%3D.bE%2BPRvMg9%2FMmJ7230jUMXRMhNLDSnMEf8uUv%2BQO7J%2Fev5su34%2Fdlhj6bdW3zYD7fz9hzCjC5pBH4z7ErUjcgnxIFzb297QoW7fc4SIeMWPk%3D" rel="nofollow">区块格式化上下文</a></p><h2>Vue</h2><h3>1.<strong>Vue2、3的模版编译渲染过程有哪些区别</strong></h3><h3>2.虚拟Dom的优势是什么?性能一定更好吗?</h3><p>虚拟 DOM (Virtual DOM )这个概念相信大家都不陌生,从 React 到 Vue ,虚拟 DOM 为这两个框架都带来了跨平台的能力(React-Native 和 Weex)</p><p>实际上它只是一层对真实DOM的抽象,以JavaScript 对象 (VNode 节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上</p><p>在Javascript对象中,虚拟DOM 表现为一个 Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性,不同框架对这三个属性的名命可能会有差别</p><p>创建虚拟DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应</p><p>优点:</p><p>保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限; 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;</p><p>跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。</p><p>缺点:</p><p>无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。</p><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=y%2F5EPDEr3z0kvESO3FuhNQ%3D%3D.FynwkH6GKy6iYDdsyRzvtOQpGv5x%2FLijoH5i8vatlv4%2FClZDtd4yay9gKf%2BIfRGy" rel="nofollow">面试官:什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路</a></p><h3>3.Vue2和Vue3的响应式区别</h3><ul><li>Vue2中响应式是通过defineProperty实现的</li><li>Vue3中响应式是通过ES6的Proxy实现的</li><li>Vue3中实现响应式数据的方法是ref和reactive</li></ul><h4>【扩展内容】 <a href="https://link.segmentfault.com/?enc=WB%2B8sbuHgd0jJMQg%2FkJIng%3D%3D.DJqU5MWajwNyOW%2BTzYp%2BHB8sBFBb8YtT5fQ4HUy2cUe2hC3n%2FrgKK6FGsAHABatn" rel="nofollow">盘点 Vue3 与 Vue2 的区别</a></h4><h3>4.ref和reactive的区别</h3><p><strong>reactive特点</strong></p><ul><li>reactive的参数一般是<strong>对象或者数组</strong>,他能够将复杂数据类型变为响应式数据。</li><li>reactive的响应式是深层次的,底层本质是将传入的数据转换为Proxy对象</li></ul><p><strong>ref特点</strong></p><ul><li>ref的参数一般是基本数据类型,也可以是对象类型</li><li>如果参数是对象类型,其实底层的本质还是reactive,系统会自动将ref转换为reactive,例如 ref(1) ===> reactive({value:1})</li><li>在模板中访问ref中的数据,系统会自动帮我们添加.value,在JS中访问ref中的数据,需要手动添加.value</li><li>ref的底层原理同reactive一样,都是Proxy</li></ul><h3>5.Vue的双向数据绑定/响应式原理</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=EoKhuXbdI24%2Flx24BfOX%2Bg%3D%3D.vdIm6XjAEXFLmkki9vTvB5sQmKg1udK79MYKAwAKe50DPEpFfdo2ykHHG5TuMRHR" rel="nofollow">面试官:双向数据绑定是什么</a></p><h3>6.Vue2为什么不能监控到数组/对象的属性变更</h3><p>首先Object.defineProperty是可以监听部分数组的更新动作的,但是由于它是通过遍历的方式来实现,当数组数据量小的时候没有问题,但是当数据量大的时候,处理上会出现明显的性能问题。也是出于“性能代价和获得的用户体验收益不成正比”的原因,所以Vue2没有去实现监听。Vue3的Proxy的劫持方式完美解决了这个问题。</p><h3>7.说下Vue中的Slot,使用场景</h3><h4>使用场景</h4><p>通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理</p><p>如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情</p><p>通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用</p><p>比如布局组件、表格列、下拉选、弹框显示内容等</p><h4>分类</h4><p>slot可以分来以下三种:</p><ul><li>默认插槽</li><li><p>具名插槽</p><ul><li>子组件用name属性来表示插槽的名字,不传为默认插槽</li></ul></li><li><p>作用域插槽</p><ul><li><p>子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot接受的对象上。父组件中在使用时通过v-slot:(简写:#)获取子组件的信息,在内容中使用</p><pre><code class="vue"><!--子组件-->
<template>
<slot name="footer" testProps="子组件的值">
<h3>没传footer插槽</h3>
</slot>
</template>
<!--父组件-->
<child>
<!-- 把v-slot的值指定为作⽤域上下⽂对象 -->
<template v-slot:default="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
<template #default="slotProps">
来⾃⼦组件数据:{{slotProps.testProps}}
</template>
</child></code></pre></li></ul></li><li><p>小结:</p><ul><li>v-slot属性只能在<code><template></code>上使用,但在只有默认插槽时可以在组件标签上使用</li><li><p>默认插槽名为default,可以省略default直接写v-slot</p><ul><li>缩写为#时不能不写参数,写成#default</li><li>可以通过解构获取v-slot={user},还可以重命名v-slot="{user: newName}"和定义默认值v-slot="{user = '默认值'}"</li></ul></li></ul></li></ul><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=B9MqNgkJGPymNmuouU1YXA%3D%3D.LWvIWSmRohZ%2BS6eI1q2wL4uUfXUtEYsaNpQBnhIUdaqtLn5CaIRCwcmfkc8CCS41" rel="nofollow">面试官:说说你对slot的理解?slot使用场景有哪些?</a></p><h3>8.为什么data 返回一个函数</h3><ul><li>vue中组件是用来复用的,为了防止data复用,将其定义为函数。</li><li>vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。</li><li>当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。</li><li>当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。</li></ul><h3>9.JSX有了解吗,和template的区别;</h3><h3>10.vue里面针对错误的生命周期函数使用</h3><h3>11.说下Vuex or Pinia的使用</h3><h3>12.Vue.set的原理</h3><p>Vue.set是Vue.js提供的一个全局方法,用于在响应式对象上设置响应式属性。它的原理是通过调用Vue实例的内部方法defineReactive来为对象设置新属性,并使其成为响应式属性。</p><p>具体原理如下:</p><ol><li>Vue.set方法接受三个参数:对象、属性名和属性值。</li><li>首先,它会判断对象是否是响应式的,如果不是,则直接返回。</li><li>然后,它会判断属性是否已经存在于对象中,如果存在,则直接更新属性的值。</li><li>如果属性不存在于对象中,它会通过defineReactive方法将属性设置为响应式属性。</li><li>defineReactive方法会为属性创建一个Dep实例对象,用于收集依赖和触发更新。</li><li>然后,它会使用Object.defineProperty方法将属性设置为响应式,并在getter和setter中触发依赖收集和更新。</li><li>最后,它会触发属性的更新,通知相关的Watcher进行视图的更新。</li></ol><h2>React</h2><h3>1.React中setState什么时候是同步,什么时候是异步</h3><p>在React中,setState有时是同步的,有时是异步的。</p><p>当在React事件处理函数(如onClick)或生命周期方法(如componentDidMount)中调用setState时,setState是异步的。这是因为React会对连续的setState调用进行批处理,以提高性能。</p><p>例如,在以下代码中,setState是异步的:</p><pre><code>class MyComponent extends React.Component {
handleClick() {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 输出的是更新前的值
}
render() {
return (
<button onClick={this.handleClick.bind(this)}>Click me</button>
);
}
}</code></pre><p>但是,当在setTimeout、Promise、原生事件监听器等异步场景中调用setState时,setState是同步的。</p><p>例如,在以下代码中,setState是同步的:</p><pre><code>class MyComponent extends React.Component {
componentDidMount() {
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 输出的是更新后的值
}, 1000);
}
render() {
return (
<div>{this.state.count}</div>
);
}
}</code></pre><h2>网络/浏览器</h2><h3>1.说下Http1.0和Http2.0的区别</h3><p>HTTP 超文本传输协议是位于 TCP/IP 体系结构中的应用层协议,它是万维网数据通信的基础。</p><p><strong>HTTP1.1 的缺陷</strong></p><ol><li>高延迟 — 队头阻塞(Head-Of-Line Blocking)</li><li>无状态特性 — 阻碍交互</li><li>明文传输 — 不安全性</li><li>不支持服务端推送</li></ol><p>http2是HTTP协议的的第二个主要版本,使用于万维网。HTTP/2是HTTP协议自1999年HTTP 1.1发布后的首个更新,主要基于SPDY协议。和HTTP1的主要区别有:</p><ul><li>1、http2采用二进制格式而非文本格式。</li><li>2、http2是完全多路复用的,而非有序并阻塞的。</li><li>3、http2采用了报头压缩,降低了成本。</li><li>4、http2让服务器可以主动将响应推送到客户端缓存中</li></ul><p><strong>HTTP2 的缺陷</strong></p><ol><li>TCP 以及 TCP+TLS 建立连接的延时</li><li>TCP 的队头阻塞并没有彻底解决</li><li>多路复用导致服务器压力上升</li><li>多路复用容易 Timeout</li></ol><p>HTTP3是一个基于 UDP 协议的 QUIC 协议,它真正“完美”地解决了“队头阻塞”问题。 <br><img src="/img/remote/1460000044194260" alt="" title=""></p><p><strong>HTTP3的主要特点</strong></p><ol><li>改进的拥塞控制、可靠传输</li><li>快速握手</li><li>集成了 TLS 1.3 加密</li><li>多路复用</li><li>连接迁移</li></ol><p>【参考资料】:</p><ul><li><a href="https://link.segmentfault.com/?enc=VwPcf3dM02JgqNlptBGxvA%3D%3D.RcT7kWvqQSJDhKmvq6cEdggYCP6UB0gJnY4vXP9OyuenWfiAH%2FHRi1NcfqhEtpdS" rel="nofollow">一文读懂 HTTP/1HTTP/2HTTP/3</a></li><li><a href="https://link.segmentfault.com/?enc=wmJJZoktt%2BUBcFiKkTnz1Q%3D%3D.l%2FD9xNXmjczjXU88edoSR7pH3SWoc55Qx5FtIfYa7m5vlWz1Lb3aS4TafJmD8WDH" rel="nofollow">HTTP2、HTTP3优缺点整理</a></li></ul><h3>2.说下HTML渲染过程</h3><ol><li>构建 DOM树</li></ol><p><!----></p><ol><li><ol><li>将 HTML 解析成许多 Tokens</li><li>将 Tokens 解析成 Objects</li><li>将 Objects 组合成为一个 DOM 树</li></ol></li></ol><p><img src="/img/remote/1460000044194261" alt="" title=""></p><ol start="2"><li>构建 CSSOM - CSS对象模型</li></ol><p><!----></p><ol><li><ol><li>解析 CSS 文件,并构建出一个 CSSOM 树(过程类似于 DOM 构建)</li></ol></li></ol><p><img src="/img/remote/1460000044194262" alt="" title=""></p><ol start="3"><li>构建 Render树</li></ol><p><!----></p><ol><li><ol><li>结合 DOM 和 CSSOM 构建出一颗 Render 树</li></ol></li></ol><p>构建过程遵循以下步骤:</p><ol><li><ol><li><ol><li>浏览器从 DOM 树开始,遍历每一个“可见”节点</li><li>对于每一个"可见"节点,在 CSSOM 上找到匹配的样式并应用</li><li>生成 Render Tree</li></ol></li></ol></li></ol><p><img src="/img/remote/1460000044194263" alt="" title=""></p><ol start="4"><li>Layout 布局</li></ol><p><!----></p><ol><li><ol><li>计算出元素相对于 viewport 的相对位置</li></ol></li></ol><p><!----></p><ol start="5"><li>Paint 渲染</li></ol><p><!----></p><ol><li><ol><li>将 Render 树转换成像素,显示在屏幕上</li></ol></li></ol><p>完整流程:</p><p><img src="/img/remote/1460000044194264" alt="" title=""></p><h4>【<strong>扩展</strong>】JS文件引入对CRP的影响</h4><ol><li>解析 HTML 构建 DOM 时,遇到 JavaScript 会被阻塞;</li><li>JavaScript 执行会被 CSSOM 构建阻塞,也就是说,JavaScript 必须等到 CSS 解析完成后才会执行(这只针对在头部放置 <style> 和 <link> 的情况,如果放在尾部,浏览器刚开始会使用 <em>User Agent Style</em> 构建 CSSOM)</li><li>如果使用异步脚本,脚本的网络请求优先级降低,且网络请求期间不阻塞 DOM 构建,直到请求完成才开始执行脚本</li></ol><p>使用异步脚本,其实就是告诉浏览器几件事:</p><ol><li><ol><li>无需阻塞 DOM,在对 Script 执行网络请求期间可以继续构建 DOM,直到拿到 Script 之后再去执行</li><li>将该 Script 的网络请求优先级降低,延长响应时间,确保执行它之前 DOM 和 CSSOM 都构建完成</li></ol></li></ol><p>需要注意如下几点:</p><ol><li><ol><li>异步脚本是网络请求期间不阻塞 DOM,拿到脚本之后马上执行,执行时还是会阻塞 DOM,但是由于响应时间被延长,此时往往 DOM 已经构建完毕(下面的测验图片将会看到,CSSOM 也已经构建完毕而且页面很快就发生第一次渲染),异步脚本的执行发生在第一次渲染之后</li><li>只有外部脚本可以使用 async 关键字变成异步,而且注意其与延迟脚本(<code><script defer /></code>)的区别,后者是在 Document 被解析完毕而 DOMContentLoaded 事件触发之前执行,前者则是在下载完毕后执行</li><li>对于使用 document.createElement 创建的 <code><script /></code>,默认就是异步脚本</li></ol></li></ol><h4>【<strong>扩展</strong>】link标签在不同位置对CRP的影响</h4><ol><li>link标签在结尾的时候,会引起重绘</li><li>link标签在DOM中间位置时,会阻塞渲染</li></ol><h4>【<strong>扩展</strong>】CSS 匹配规则为何从右向左</h4><p>CSS 匹配就发生在 Render Tree 构建时(Chrome Dev Tools 里叫做 <em>Recalculate Style</em>),此时浏览器构建出了 DOM,而且拿到了 CSS 样式,此时要做的就是把样式跟 DOM 上的节点对应上,浏览器为了提高性能需要做的就是快速匹配。</p><p>首先要明确一点,浏览器此时是给一个"可见"节点找对应的规则,这和 jQuery 选择器不同,后者是使用一个规则去找对应的节点,这样从左到右或许更快。但是对于前者,由于 CSS 的庞大,一个 CSS 文件中或许有上千条规则,而且对于当前节点来说,大多数规则是匹配不上的,到此为止,稍微想一下就知道,如果从右开始匹配(也是从更精确的位置开始),能更快排除不合适的大部分节点,而如果从左开始,只有深入了才会发现匹配失败,如果大部分规则层级都比较深,就比较浪费资源了。</p><p>除了上面这点,我们前面还提到 DOM 构建是"循序渐进的",而且 DOM 不阻塞 Render Tree 构建(只有 CSSOM 阻塞),这样也是为了能让页面更早有元素呈现。考虑如下情况,如果我们此时构建的只是部分 DOM,而此时 CSSOM 构建完成,浏览器此时需要构建 Render Tree,如果对每一个节点,找到一条规则进行从左向右匹配,则必须要求其子元素甚至孙子元素都在 DOM 上,但此时 DOM 未构建完成,显然会匹配失败。如果反过来,我们只需要查找该元素的父元素或祖先元素,它们肯定在当前的 DOM 中。</p><h4>【<strong>扩展</strong>】window.onload和DOMContentLoaded的区别</h4><p><img src="/img/remote/1460000044194265" alt="" title=""></p><ol><li>onload事件是当页面所有资源(包括DOM,样式表,脚本,图片等资源)全部加载完成后执行</li><li>DOMContentLoaded事件在DOM结构加载完成后即触发,不等待其他资源的加载。<strong>更快</strong></li></ol><h4>【<strong>扩展</strong>】重绘和重排</h4><h5>概念</h5><p><strong>重排</strong>是什么:重新生成布局。当DOM 的变化影响了元素的几何属性(宽和高)--比如改变边框宽度或给段落增加文字导致行数增加--浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程称为重排。</p><p><strong>重绘</strong>是什么:重新绘制。完成重排后,浏览器会重新绘制受影响的部分到屏幕中。这个过程称为重绘。</p><h5><strong>发生重排的情况</strong></h5><p>当页面布局和几何属性改变时发生“重排”,如下:</p><ul><li>添加或删除可见的DOM 元素</li><li>元素位置改变</li><li>元素尺寸改变(包括外边距、内边距、边框厚度、宽度、高度等属性改变)</li><li>内容改变,例如:文本改变后图片被另一个不同尺寸的图片替代</li><li>页面渲染器初始化</li><li>浏览器窗口尺寸改变</li></ul><h5><strong>强制刷新队列</strong></h5><p>获取布局信息的操作会导致列队刷新,以下属性和方法需要返回最新的布局信息,最好避免使用。</p><p>offsetTop, offsetLeft, offsetWidth, offsetHeight</p><p>scrollTop, scrollLeft, scrollWidth, scrollHeight</p><p>clientTop, clientLeft, clientWidth, clientHeight</p><p>getComputedStyle() (currentStyle in IE)</p><p>clientTop:元素上边框的厚度,当没有指定边框厚底时,一般为0。</p><p>scrollTop:位于对象最顶端和窗口中可见内容的最顶端之间的距离,简单地说就是滚动后被隐藏的高度。</p><p>offsetTop:获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)距离顶端的高度。</p><p>clientHeight:内容可视区域的高度,也就是说页面浏览器中可以看到内容的这个区域的高度,一般是最后一个工具条以下到状态栏以上的这个区域,与页面内容无关。</p><p>scrollHeight:IE、Opera 认为 scrollHeight 是网页内容实际高度,可以小于 clientHeight。FF 认为 scrollHeight 是网页内容高度,不过最小值是 clientHeight。</p><p>offsetHeight:获取对象相对于由offsetParent属性指定的父坐标(css定位的元素或body元素)的高度。IE、Opera 认为 offsetHeight = clientHeight + 滚动条 + 边框。FF 认为 offsetHeight 是网页内容实际高度,可以小于clientHeight。offsetHeight在新版本的FF和IE中是一样的,表示网页的高度,与滚动条无关,chrome中不包括滚动条。</p><p>Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。 私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。</p><h5><strong>减少重排和重绘</strong></h5><ul><li>合并样式修改</li><li>批量操作DOM</li></ul><p><!----></p><ul><li><ul><li>脱离标准流的操作有以下3中:</li></ul></li></ul><p><!----></p><ul><li><ul><li><ul><li>隐藏元素</li><li>使用文档碎片</li><li>拷贝节点</li></ul></li></ul></li></ul><p><!----></p><ul><li>避免多次触发布局</li><li>对于页面中比较复杂的动画,尽量将元素设置为绝对定位,操作元素的定位属性,这样只有这一个元素会回流,如果不是定位的话,容易引起其父元素以及子元素的回流。</li></ul><p>【参考资料】</p><ul><li><a href="https://link.segmentfault.com/?enc=3DY1xDIbCWkMjOw2ey2DcA%3D%3D.ubd909h2lynIEaymTV6qESFabeL7WgD%2BwKgdePDtIIQNCHJCJu7XSA7Pl3qGH27s" rel="nofollow">深入理解浏览器解析渲染 HTML</a></li><li><a href="https://link.segmentfault.com/?enc=G6r6jq4G%2FdyBga4mhRDyCQ%3D%3D.W6yJJJzp7h6NjmsiOhioHAoAf013DEs8GDNUnfMW0nr8s7bUj9OjteE6%2Ba0QwfgK" rel="nofollow">How browsers work</a></li><li><a href="https://link.segmentfault.com/?enc=UpukH%2BS5clCt4nQytcC7Tg%3D%3D.ErEuXKRbGERix%2Fb7ixwHU%2F%2BKHEaenuXSjrm0sHradbf14yrEgj%2BHgxGgyKsBpUTs" rel="nofollow">浏览器渲染之link标签和script标签对页面的影响</a></li><li><a href="https://link.segmentfault.com/?enc=PRw5nE0TKfHfFg0Q9qVpQQ%3D%3D.NqNpgjl9U0aLvfwpNhpvAjmQ4cr1RaUShPpGu7UntfTqtBFkLY6FeQ3dx6FmtyYk" rel="nofollow">重排(reflow)和重绘(repaint)</a></li><li><a href="https://link.segmentfault.com/?enc=IsvwAlRBv3fFQg9fUPzmbw%3D%3D.n7rTuDSmy5HDAMII0lYlz9ECn56cefzxQOJ3ZCjW5lTpvc9gExmNw0MYlfi4xBoGTbUyEnVjnZxXPBobAoqKHg%3D%3D" rel="nofollow">从输入URL到浏览器显示页面过程中都发生了什么</a> 👍</li><li><a href="https://link.segmentfault.com/?enc=uUOrWbjZO7QYwR%2B4Rhuppg%3D%3D.m7d1BqA5%2Fmwbrm1%2BTMfvGwixQpK2TW1paGNCCjp8A82zzkBRlJplktA5B8iSV6ttMHN1pBk7GzVC%2B4gxiKEq7g%3D%3D" rel="nofollow">讲清楚重排或回流、重绘</a></li></ul><h3>3.说下浏览器缓存</h3><p><img src="/img/remote/1460000044194266" alt="" title=""></p><h4>【<strong>扩展</strong>】Last-Modified和Etag的区别?哪个更好一些?</h4><p>Last-Modified和ETag是HTTP协议中用于缓存验证的两种机制。</p><ol><li>Last-Modified:服务器在响应中添加一个Last-Modified头,该头包含了资源的最后修改时间。客户端在后续请求中可以通过发送If-Modified-Since头,将上次获取到的Last-Modified值发送给服务器进行验证。如果资源的最后修改时间与客户端提供的时间相同,则服务器返回304 Not Modified状态码,表示资源未被修改,客户端可以使用缓存副本。</li><li>ETag:服务器在响应中添加一个ETag头,该头包含了资源的唯一标识符(通常是一个哈希值)。客户端在后续请求中可以通过发送If-None-Match头,将上次获取到的ETag值发送给服务器进行验证。如果资源的ETag与客户端提供的值相同,则服务器返回304 Not Modified状态码,表示资源未被修改,客户端可以使用缓存副本。</li></ol><p>两者区别:</p><ul><li>Last-Modified是基于资源的最后修改时间进行验证,而ETag是基于资源的唯一标识符进行验证。</li><li>Last-Modified的精度是以秒为单位,而ETag没有这个限制,可以更精确地表示资源的变化。</li><li>ETag可以在某些情况下提供更好的性能,因为它不依赖于文件系统的时间戳,而是根据资源内容生成唯一标识符。比如回滚时Etag更加便捷。</li></ul><h4>【<strong>扩展</strong>】日常项目中涉及到哪些缓存?</h4><ul><li>HTML:使用协商缓存。</li><li>CSS&JS&图片:使用强缓存,文件命名带上hash值。</li></ul><h4>【<strong>扩展</strong>】webpack打包中的几种hash模式的区别</h4><p>webpack给我们提供了三种哈希值计算方式,分别是hash、chunkhash和contenthash。那么这三者有什么区别呢?</p><ul><li>hash:跟整个项目的构建相关,构建生成的文件hash值都是一样的,只要项目里有文件更改,整个项目构建的hash值都会更改。</li><li>chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。</li><li>contenthash:由文件内容产生的hash值,内容不同产生的contenthash值也不一样。</li></ul><p>显然,我们是不会使用第一种的。改了一个文件,打包之后,其他文件的hash都变了,缓存自然都失效了。这不是我们想要的。那chunkhash和contenthash的主要应用场景是什么呢?在实际在项目中,我们一般会把项目中的css都抽离出对应的css文件来加以引用。如果我们使用chunkhash,当我们改了css代码之后,会发现css文件hash值改变的同时,js文件的hash值也会改变。这时候,contenthash就派上用场了。</p><p>【<strong>参考资料</strong>】</p><ul><li><a href="https://link.segmentfault.com/?enc=ahyoOQohQgW7mosDE7kBCA%3D%3D.cF8t1MPFfWD9MQ5HdiDJHCPqGoWTblqnxFEw6OAEANhrjOihZhx4bx8eqHuvUz8v" rel="nofollow">前端缓存最佳实践</a></li><li><a href="https://link.segmentfault.com/?enc=gJyyBZVd2mQdlZ8I2Xo19A%3D%3D.bqBoGpLmVAubMMCgZYx4m27ic6P%2FXpeZlfq53yxm9bfcTP4mlcTW2FB3d3I2pbZ7" rel="nofollow">彻底弄懂前端缓存</a></li></ul><h3>4.说下V8垃圾回收机制?</h3><p>在JavaScript中,数据类型分为两类,简单类型和引用类型,对于简单类型,内存是保存在栈(stack)空间中,复杂数据类型,内存是保存在堆(heap)空间中。</p><ul><li>基本类型:这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的</li><li>引用类型:引用类型,值大小不固定,栈内存中存放地址指向堆内存中的对象。是按引用访问的。</li></ul><p>而对于栈的内存空间,只保存简单数据类型的内存,由操作系统自动分配和自动释放。而堆空间中的内存,由于大小不固定,系统无法进行自动释放,这个时候就需要JS引擎来手动的释放这些内存。</p><p>V8 将堆分为两类新生代和老生代,新生代中存放的是生存时间短的对象,老生代中存放的生存时间久的对象。</p><p>对于这两块区域,V8 分别使用两个不同的垃圾回收器,以便更高效地实施垃圾回收。</p><ul><li>副垃圾回收器 - Scavenge:主要负责新生代的垃圾回收。</li><li>主垃圾回收器 - Mark-Sweep & Mark-Compact:主要负责老生代的垃圾回收。</li></ul><p>Scavange算法将新生代堆分为两部分,分别叫from-space和to-space,工作方式也很简单,就是将from-space中存活的活动对象复制到to-space中,并将这些对象的内存有序的排列起来,然后将from-space中的非活动对象的内存进行释放,完成之后,将from space 和to space进行互换,这样可以使得新生代中的这两块区域可以重复利用。</p><p><img src="/img/remote/1460000044194267" alt="" title=""></p><p>简单的描述就是:</p><ul><li>标记活动对象和非活动对象</li><li>复制 from space 的活动对象到 to space 并对其进行排序</li><li>释放 from space 中的非活动对象的内存</li><li>将 from space 和 to space 角色互换</li></ul><p>那么,垃圾回收器是怎么知道哪些对象是活动对象和非活动对象的呢?</p><p>有一个概念叫对象的可达性,表示从初始的根对象(window,global)的指针开始,这个根指针对象被称为根集(root set),从这个根集向下搜索其子节点,被搜索到的子节点说明该节点的引用对象可达,并为其留下标记,然后递归这个搜索的过程,直到所有子节点都被遍历结束,那么没有被标记的对象节点,说明该对象没有被任何地方引用,可以证明这是一个需要被释放内存的对象,可以被垃圾回收器回收。</p><p>新生代中的对象什么时候变成老生代的对象呢?</p><p>在新生代中,还进一步进行了细分,分为nursery子代和intermediate子代两个区域,一个对象第一次分配内存时会被分配到新生代中的nursery子代,如果进过下一次垃圾回收这个对象还存在新生代中,这时候我们移动到 intermediate 子代,再经过下一次垃圾回收,如果这个对象还在新生代中,副垃圾回收器会将该对象移动到老生代中,这个移动的过程被称为晋升。</p><p><strong>老生代垃圾回收 - Mark-Sweep & Mark-Compact</strong></p><p>新生代空间中的对象满足一定条件后,晋升到老生代空间中,在老生代空间中的对象都已经至少经历过一次或者多次的回收所以它们的存活概率会更大,如果这个时候再使用scavenge算法的话,会出现两个问题:</p><ul><li>scavenge为复制算法,重复复制活动对象会使得效率低下</li><li>scavenge是牺牲空间来换取时间效率的算法,而老生代支持的容量交大,会出现空间资源浪费问题</li></ul><p>所以在老生代空间中采用了 Mark-Sweep(标记清除) 和 Mark-Compact(标记整理) 算法。</p><p><strong>Mark-Sweep</strong></p><p>Mark-Sweep处理时分为两阶段,标记阶段和清理阶段,看起来与Scavenge类似,不同的是,Scavenge算法是复制活动对象,而由于在老生代中活动对象占大多数,所以Mark-Sweep在标记了活动对象和非活动对象之后,直接把非活动对象清除。</p><ul><li>标记阶段:对老生代进行第一次扫描,标记活动对象</li><li>清理阶段:对老生代进行第二次扫描,清除未被标记的对象,即清理非活动对象</li></ul><p><img src="/img/remote/1460000044194268" alt="" title=""></p><p><strong>Mark-Compact</strong></p><p>由于Mark-Sweep完成之后,老生代的内存中产生了很多内存碎片,若不清理这些内存碎片,如果出现需要分配一个大对象的时候,这时所有的碎片空间都完全无法完成分配,就会提前触发垃圾回收,而这次回收其实不是必要的。</p><p>为了解决内存碎片问题,Mark-Compact被提出,它是在是在 Mark-Sweep的基础上演进而来的,相比Mark-Sweep,Mark-Compact添加了活动对象整理阶段,将所有的活动对象往一端移动,移动完成后,直接清理掉边界外的内存。</p><p><img src="/img/remote/1460000044194270" alt="" title=""></p><p><strong>全停顿 Stop-The-World</strong></p><p>由于垃圾回收是在JS引擎中进行的,而Mark-Compact算法在执行过程中需要移动对象,而当活动对象较多的时候,它的执行速度不可能很快,为了避免JavaScript应用逻辑和垃圾回收器的内存资源竞争导致的不一致性问题,垃圾回收器会将JavaScript应用暂停,这个过程,被称为全停顿(stop-the-world)。</p><h4>【扩展】如何判断哪个变量导致内存泄漏的?</h4><p>在V8的垃圾回收机制中,内存泄漏通常指的是无法被垃圾回收器回收的对象。下面是一些判断哪个变量内存泄漏的常见方法:</p><ol><li>使用内存分析工具:可以使用V8提供的内存分析工具,如Chrome DevTools中的Memory面板,来检测内存泄漏。这些工具可以显示内存使用情况、对象引用关系等信息,帮助定位内存泄漏的对象。</li><li>监测内存使用情况:通过监测内存使用情况,可以观察变量的内存占用是否随时间增长,如果是,则可能存在内存泄漏。可以使用V8提供的内存使用情况API,如process.memoryUsage(),来获取当前进程的内存使用情况。</li><li>分析代码逻辑:检查代码中是否存在未释放的资源,比如未关闭的文件句柄、未清除的定时器、未解绑的事件监听器等。这些资源如果没有正确释放,就会导致内存泄漏。</li><li>使用堆快照:可以使用V8提供的堆快照工具,如Chrome DevTools中的Heap Snapshot,来捕获当前堆中的对象快照,并分析对象的引用关系。通过分析堆快照,可以找到哪些对象被引用了但实际上不再需要,从而判断是否存在内存泄漏。</li><li>监测内存增长:可以在代码中设置定时器,定期检测内存的增长情况。如果发现内存持续增长,可能是因为有对象没有被正确释放,从而导致内存泄漏。</li></ol><p>需要注意的是,判断内存泄漏并不是一件简单的事情,需要综合考虑多个因素。有时候内存增长可能是正常的,例如缓存的对象或者持久化的数据。因此,结合以上方法进行综合分析,可以更准确地判断哪个变量存在内存泄漏。</p><p>【参考资料】</p><ul><li><a href="https://link.segmentfault.com/?enc=ojq9%2FZcVyVKt4ZuxGZpfTA%3D%3D.0Lrf2xKeYvfGhsuU6ESPY4YqIzXho6YsJ%2B0cDzQExfyDU4XqvTdWC6ZJQJGu0cIAeFWLHvpBZdTD8rRzZyxpAg%3D%3D" rel="nofollow">你真的了解垃圾回收机制吗</a></li><li><a href="https://link.segmentfault.com/?enc=YbmpGqry8WAxJUESAZblkg%3D%3D.UKEeZUYvCi0PYpM7eWZzcSY3S%2Fna%2Ft1G6baE%2F2uNtA2h1E%2BYb59Kiccq5munj2mQ" rel="nofollow">深入理解谷歌最强V8垃圾回收机制</a></li><li><a href="https://link.segmentfault.com/?enc=%2FTR7pIwJEHXbUVxFIHsV9Q%3D%3D.%2FGfo8i1VQBRcvEs7wByX4BbMkVfOTkKT5zOjKFa1qGyya5owSmt1VX5D2NGil%2FlG" rel="nofollow">Javascript的垃圾回收机制知多少?</a>👍</li><li><a href="https://link.segmentfault.com/?enc=QKmYZQJzl%2BW5ADueBNEOiA%3D%3D.E5P%2BA9va8JocL7e9yydpoFbplzcxyIQ3Vq8CopEoYymz0rJMAfWiRyCfsD51I5Pa" rel="nofollow">JavaScript中的垃圾回收和内存泄漏</a></li></ul><h3>5.说下浏览器事件循环机制的理解</h3><h4>【扩展】微任务和宏任务</h4><h4>【扩展】如何理解微任务优先于宏任务</h4><p><img src="/img/remote/1460000044194271" alt="" title=""></p><p>从上图我们就可以知道,js的事件循环机制规定了</p><p>1.优先执行一个宏任务</p><p>2.判断是否有微任务(无:继续执行宏任务)</p><p>3.有:执行所有的微任务</p><p>4.开始下一个宏任务</p><p>在js中 script本身就是一个宏任务,所以执行异步队列的时候,会优先执行它的所有微任务,然后执行宏任务</p><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=%2F0nGX8A7t5LW2z7wv9sXag%3D%3D.pumAGHmI8Ft61IWxp21pcEQ29pE4EhfWmMe4tejMFk8ncANafkHcF7%2Bl5xryBypoGVmu%2FrjJNohS0EOuLSk6fJFt18mUVBujbJn9AvD3zgTLvgb%2F8CfMZ03oiSgpGZ8PCzg1nQWIvQaDS6h7l7xhnw%3D%3D" rel="nofollow">说下浏览器事件循环机制</a></p><p><a href="https://link.segmentfault.com/?enc=zvMNpv5WgqwJKfguPop0lw%3D%3D.Thg2q8kCadkoJvKspVsPLL2W74xyWE2E3NSZbTobPeNIkobYn%2BPK2H1NxV8fTSF3" rel="nofollow">🔥「吊打面试官」彻底理解事件循环、宏任务、微任务</a></p><p><a href="https://link.segmentfault.com/?enc=xGXHahM8mS8y9ZlL%2B%2BeW2Q%3D%3D.NJzxwyqrMbVh9yT%2BTIDUBt6oplJ15i4pKnPHTets0He6f0kG8sOyEViV8gdm9d4Q" rel="nofollow">完全理解【宏任务和微任务】</a></p><h3>6.两个tab页如何通信</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=2yEcq9Hn8scZPfOnd9nWfg%3D%3D.BINWAwmMqYPPYFV%2BQwXVj%2FNrd%2BX3oADscSjZA%2BjJ88c36%2B7MkO5TewmWSie2IlM4" rel="nofollow">面试官:你是如何实现浏览器多标签页之间通信的?</a></p><p><a href="https://link.segmentfault.com/?enc=ILwKaZ%2F9FmFe5hwsRc4dCQ%3D%3D.WMfAKZ%2Ba7FDY6eWlgzuwNP8HRetvQ7FAT76Xhk7SD3Y4pJEQdVGpznyzYmx8iLDe" rel="nofollow">JS如何跨标签页通信</a></p><h3>7.说下Websocket的理解</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=rPfGiYd4m1LSXySlKAJY9w%3D%3D.CnnNkxl%2BHfsyJCgZNMNmMLb5Msn%2BwY7ly1y5XR5PPMq2AgnJZmnuxGwf%2Fra0se3B" rel="nofollow">面试官:说说对WebSocket的理解?应用场景?</a></p><h3>8.为什么出现跨域?哪些方式能够解决跨域?CORS是设置哪个请求头?</h3><p><strong>1.什么是跨域?</strong></p><p>跨域全称为Cross-Origin Resource Sharing,意为<strong>跨域资源共享</strong>,是一种<strong>允许当前域(domain)的资源被其他域(domain)的脚本请求访问</strong>的机制,通常由于<strong>同源安全策略</strong>,浏览器会禁止这种跨域请求。</p><p>而我们所说的跨域问题是因为违反了<strong>浏览器</strong>的同源安全策略而产生的。</p><p><strong>同源</strong>是指<strong>协议、域名、端口</strong>三者相同,即使两个不同的域名指向同一个ip地址也是非同源。</p><p><strong>注意</strong>:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。</p><p><strong>2.跨域的解决方法</strong></p><ul><li>jsonp</li><li>cors</li><li>websocket</li><li>postMessage</li><li>document.domain+iframe</li><li>Node代理</li><li>Nginx代理</li></ul><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=z96xe%2B3wsVuOs1Yp2me%2FHA%3D%3D.P7RnhaY3mBbRjDAp1ps%2BppNx25gdkP%2FrfxzNHO9EFZwO7eiHo42b52qqsFCc%2BekK" rel="nofollow">10 种跨域解决方案(附终极方案)</a></p><p><strong>3.CORS是设置哪个请求头</strong></p><ol><li>Origin(来源):指示请求的源地址,即发起请求的网页的地址。例如,Origin: <a href="https://link.segmentfault.com/?enc=3%2BWZ5LF41WsOLBaXcGJUZA%3D%3D.rsRKix4q7Tz1LHrwxqki8iHdp%2BcT%2BsFWmjii9YYvxok%3D" rel="nofollow">http://example.com</a></li><li>Access-Control-Request-Method(访问控制请求方法):用于预检请求(OPTIONS请求)中,指示实际请求使用的HTTP方法。例如,Access-Control-Request-Method: GET。</li><li>Access-Control-Request-Headers(访问控制请求头):用于预检请求(OPTIONS请求)中,指示实际请求中会包含的额外的自定义请求头。例如,Access-Control-Request-Headers: X-Custom-Header。</li><li>Access-Control-Allow-Origin(允许的来源):用于服务器响应中,指示允许访问资源的来源。可以是单个的源地址,也可以是通配符(*)表示允许所有来源。例如,Access-Control-Allow-Origin: <a href="https://link.segmentfault.com/?enc=IdejUXcaKInUcbKMMECYPg%3D%3D.CxONg5cw8hT%2FsVWiz3nK9o0%2Fm5jvNEcRRC1%2BSkGwh9o%3D" rel="nofollow">http://example.com</a></li><li>Access-Control-Allow-Methods(允许的方法):用于服务器响应中,指示允许的HTTP方法。例如,Access-Control-Allow-Methods: GET, POST, PUT。</li><li>Access-Control-Allow-Headers(允许的头部):用于服务器响应中,指示允许的自定义请求头。例如,Access-Control-Allow-Headers: X-Custom-Header。</li><li>Access-Control-Allow-Credentials(允许凭证):用于服务器响应中,指示是否允许发送凭证(如cookies、HTTP认证或客户端SSL证明)。例如,Access-Control-Allow-Credentials: true。</li></ol><h3>9.Options预检请求出现的时机是什么</h3><ul><li>只有跨域的情况下,才会发生预请求</li><li>与前述简单请求不同,“需预检的请求”要求必须首先使用OPTIONS方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。“预检请求”的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。</li></ul><ol start="0"><li>withCredentials为true不会产生预请求</li><li>请求头Content-Type为application/json会产生预请求</li><li>设置了用户自定义请求头会产生预检请求</li><li>delete方法产生预检请求</li></ol><h4>预检请求不一定每一次都会产生</h4><ul><li>这个因为浏览器会对预检请求进行缓存</li><li>同时通过服务器端设置 Access-Control-Max-Age 字段来设置缓存时间</li><li>那么当第一次请求该 URL 时会发出 OPTIONS 请求,浏览器会根据返回的 Access-Control-Max-Age 字段缓存该请求的 OPTIONS 预检请求的响应结果(具体缓存时间还取决于浏览器的支持的默认最大值,取两者最小值,一般为 10 分钟)。在缓存有效期内,该资源的请求(URL 和 header 字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应 Access-Control-Max-Age 时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉 disable cache 勾选。)</li><li>在 Firefox 中,上限是24小时 (即 86400 秒)。</li><li>在 Chromium v76 之前, 上限是 10 分钟(即 600 秒)。</li><li>从 Chromium v76 开始,上限是 2 小时(即 7200 秒)。</li><li>Chromium 同时规定了一个默认值 5 秒。</li><li>如果值为 -1,表示禁用缓存,则每次请求前都需要使用 OPTIONS 预检请求。</li></ul><h3>10.Http2.0有哪些新特性</h3><ul><li>二进制分帧</li><li>首部压缩</li><li>流量控制</li><li>多路复用</li><li>请求优先级</li><li>服务器推送</li></ul><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=lLN8sz9h2dyS%2FWBGF1qBVA%3D%3D.vWpf8UuetCid%2Bssa1UsswP69S6oD5kiLRmS5QXWStMmmx3%2BVZUGSVXh1rOvqzU4j" rel="nofollow">HTTP----HTTP2.0新特性</a></p><h3>11.什么是CDN,它的实现原理</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=uMw3UWnM7BSDAncpNRPT5w%3D%3D.YLE2sWw8Z8h1ziBATUOM%2Fc1PrSLVaovM7xemvwmFk30ZfFu%2B7h%2BhO3pvj8RFwXXD" rel="nofollow">到底什么是CDN?</a></p><p><a href="https://link.segmentfault.com/?enc=qYFK8JZivL8yRHK%2BrHdnZA%3D%3D.L1FHiWWYeIFjs2fE4p5vDgA0BrbRpcgmLWk8xui8%2FfDiqWsPHLooYbQio0Z%2BjJKl" rel="nofollow">CDN原理简单介绍</a></p><p><a href="https://link.segmentfault.com/?enc=QSq1KZmiTJUooG1HlICbjA%3D%3D.th1REtI0R14FeVlKEKosm3VvlWatLYUbuMQ59eVdp96qjxM6xUnBHcaFPpNYvPx4rurIbM%2B0sVF%2BJvTykB0d7Q%3D%3D" rel="nofollow">一文搞懂CDN的技术架构和原理</a></p><p><a href="https://link.segmentfault.com/?enc=MbrOKKrOP9jO8tyTgdkouQ%3D%3D.OpH3xNUG%2BupnaxMRrz3wkpAX7000zemG9P5LuAplfbFzbuZbBzk7VtBNJHNHjL4gbQR3uSuSsRUMyawiagKzTQDoyWYbLm43y6YxCOXqMtTeIAXukvQSb2sfL9OShgYN" rel="nofollow">面试官:如何理解CDN?说说实现原理?</a></p><h3>12.TCP三次握手说一下</h3><p>在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过“三次握手”进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小信息,完成了三次握手,客户端和服务器端就可以开始传送数据。</p><p>第一次握手,是客户端向服务器端发起的,这是用来去发起一个连接建立的请求,那么这个报文中的SYN位会被标记为:1,所以我们也常把它叫做一个SYN包;</p><p>第二次握手,是由服务器向客户端发起的,是来确认服务器的一个请求链接的,这个报文中我们的ACK位和SYN位都被标记为:1,所以我们也把它叫做一个SYN-ACK报文;</p><p>第三次握手,同样是由客户端发个服务器端的,这是对服务器的上一个报文的一个确认报文,这个报文中的ACK位被标记为:1,所以我们也把它叫做一个ACK包。</p><p>以上就是TCP的三次握手。</p><h4>延伸-TCP四次分手的含义</h4><p>当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,相应的就要断开TCP连接。那对于TCP的断开连接,这里就有了“四次分手”。</p><p>1.第一次分手:客户端发送断开请求 <br>2.第二次分手:服务器收到断开请求,发送同意断开连接的请求 <br>3.第三次分手:服务器发送请求断开连接 <br>4.第四次分手:客户端收到,发送同意断开连接</p><p>1.当客户端发送断开请求,只是表示客户端已经没有数据要发送了,客户端告诉服务器,它的数据已经全部发送完毕了,但是,这个时候客户端还是可以接受来自服务器的数据(第一次分手) <br>2.当服务器收到断开请求时,表示它已经知道客户端没有数据发送了并发送同意断开连接的请求,但是服务器还是可以发送数据到客户端的(第二次分手) <br>3.当服务器发送同意断开连接的请求后,这个时候就表示服务器也没有数据要发送了,就会告诉客户端,我也没有数据要发送了(第三次分手) <br>4.当客户端收到服务器发送请求断开连接后,再去告诉服务端我已经知道你没有数据要发给我了,同意断开连接请求(第四次分手)</p><h3>13.UDP协议了解吗</h3><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=AUwicWLceZGpEmW3vxuJIw%3D%3D.VzHlncKPzkke6kNQ6jFFUDOgP3DSlFAuxF9hwigaTLStTTtfNpld15u7wwK5ylL2" rel="nofollow">UDP协议详解</a></p><h2>工程化</h2><h3>1.构建工具使用的哪个?对比一下webpack和vite的差别</h3><p>webpack core 是一个纯打包工具(对标 Rollup),而 Vite 其实是一个更上层的工具链方案,对标的是 (webpack + 针对 web 的常用配置 + webpack-dev-server)。</p><p><strong>从底层原理上来说,Vite是基于esbuild预构建依赖。而esbuild是采用go语言编写,因为go语言的操作是纳秒级别,而js是以毫秒计数,所以vite比用js编写的打包器快10-100倍。</strong></p><p><strong>webpack启动图:</strong></p><p><img src="/img/remote/1460000044194272" alt="" title=""></p><p><strong>Vite启动图:</strong></p><p><img src="/img/remote/1460000044194273" alt="" title=""></p><p><strong>webpack:</strong></p><p>分析依赖=> 编译打包=> 交给本地服务器进行渲染。首先分析各个模块之间的依赖,然后进行打包,在启动webpack-dev-server,请求服务器时,直接显示打包结果。webpack打包之后存在的问题:随着模块的增多,会造成打出的 bundle 体积过大,进而会造成<a href="https://link.segmentfault.com/?enc=SAUWCISlLXCO3ZX7M265pg%3D%3D.MR%2FLRP4KNjxwzVqbFjji1D071W8gTeppzK7S06PS0SKr%2FOLBHgn7SAp4ZJhhjrNjIPJxHwnuVtcvZAn6AXru6HEzThNgL%2Fa1QQLN6cABR0Mh10MjmKhmNFT79LYdoJ%2F9jxYpunzXWabVCAu%2BEKRGC3AO1SRVuytuueQK%2FzgZvIiPr8at3bqjHiPHDHs%2FDBPib%2B45ezX47iszTjseTdWgXdRgxkJYX14pud2QDRlVlXWe%2FalAJGxKTFhxSaeA4QhdphvLm18lpRThWr4KzW%2BW6A%3D%3D" rel="nofollow">热更新</a>速度明显拖慢。</p><p><strong>vite:</strong></p><p>启动服务器=> 请求模块时按需动态编译显示。是先启动开发服务器,请求某个模块时再对该模块进行实时编译,因为现代游览器本身支持ES-Module,所以会自动向依赖的Module发出请求。所以vite就将<a href="https://link.segmentfault.com/?enc=Lh%2F8xec%2BcEzwB7R76UWOMA%3D%3D.xUiP5oXKmwj%2FBqHXIztVUH8xFgh8%2FiPcEqiLd3fqzBxlPVMS8fJ0OSwYLrUXkJYHrHpFuxniiEJ8NJptKsZpYflIcmWOZdLcOWQu1AlsvsyrSWBQtR4YkdGIKlhxXOtxuCNTEyyAD0RLVpnNiQ%2B39VTLQ7TqWEHjGs8D0PcDtUVV40IzcQK8Puk9ydYunAbGqp%2BcU3EC4%2BkDcigxVoH757i%2BcQA%2FNb%2FA5lXR7f7z2l8KhtpYg7%2F3uNCKU6QcJvs%2BBjoMYkBAQYk4S9o2RSUqNA%3D%3D" rel="nofollow">开发环境</a>下的<a href="https://link.segmentfault.com/?enc=L7ezMG1YULDzX%2BtJ7EIcng%3D%3D.%2Be3yEVpFReEXUMhwl9TDD0U8vJH%2BKViN1vW6wSbLEi6TPy7dnYapdYtPuXtyhAWVvILSwVq%2Bgb2Ws9rd4VwZh3yECvU7uRmYAYYnfMUZ7hdD0KTdY54zt7N1WasBNuY0Z5CflmqJ111WUpNYv6Ti5x6WB5QiIJz3KodtCxISGB7hEy4HZ70zpLWlYy%2FhlsGKU948aoaPZqakgrLgSRKbPUH%2BIuSa6c3ABuL2U5TGev%2BmPdPqu2FTQzuwUVXBnO4KSQo%2F0600dkpjA3wVhN7NPQ%3D%3D" rel="nofollow">模块文件</a>作为浏览器的执行文件,而不是像webpack进行打包后交给本地服务器。</p><h4>Webpack</h4><p>Webpack 是一个基于打包器的构建工具,同一个入口文件的代码会打包成一个 Bundle 文件。Webpack 长期来的一个痛点是对于大规模应用的应用启动和热更新速度很慢。</p><p><img src="/img/remote/1460000044194274" alt="" title=""></p><p>当文件发生变动时,整个 JavaScript Bundle 文件会被 Webpack 重新构建,这也是为什么使用 Webpack 的大规模应用在应用启动和热更新时速度很慢的原因。这给进行大规模 JavaScript 应用的开发者造成了很差的开发体验。</p><h4>Webpack工作流程</h4><p>Webpack 打包过程:</p><ul><li>从一个入口文件开始,基于代码文件中的所有 import,export,require 构建依赖树;</li><li>编译 JS/CSS 等模块;</li><li>使用算法排序、重写、连接代码;</li><li>优化。</li></ul><p>开发环境的 Webpack:</p><ul><li>打包所有代码;</li><li>启动 webpack-dev-server 托管打包好的代码;</li><li>启动 websocket 处理热更新 HMR。</li></ul><p>应用规模越大,启动和热更新代码越慢。及时启动了热更新,每次代码变更也需要重新生产 Bundle 文件。</p><h4>Vite</h4><p>Vite 核心借助了浏览器的原生 ES Modules 和像 esbuild 这样的将代码编译成 native code 的打包工具。</p><p>Vite 主要有两方面组成:</p><ul><li>一个开发服务器,基于 ESM 提供丰富的内建能力,比如速度快到惊人的模块热更新(HMR);</li><li>一套构建指令,使用 rollup 进行代码打包,且零配置即可输出用于生产环境的高度优化的静态代码。</li></ul><p>Vite 的核心能力和 webpack + webpack-dev-server 相似,但是在开发者体验上有一些提升:</p><ul><li>无论项目大小有多大,启动应用都只需更少的时间;</li><li>无论项目大小有多大,HMR(Hot Module Replacing)热更新都可以做到及时响应;</li><li>按需编译;</li><li>零配置,开箱即用;</li><li>Esbuild 能力带来的 Typescript/jsx 的原生支持。</li></ul><p>大型的 JavaScript 项目在开发和生产环境有比较差的性能表现,往往是因为我们使用的构建工具没有充分做到并行处理、内存优化和缓存。</p><h4>Vite核心理念:Bundless 开发环境构建</h4><p>浏览器的原生 ES Modules 能力允许在不将代码打包到一起的情况下运行 JavaScript 应用。Vite 的核心理念很简单,就是借助浏览器原生 ES Modules 能力,当浏览器发出请求时,为浏览器按需提供 ES Module 文件,浏览器获取 ES Module 文件会直接执行。</p><h4>Vite应用启动</h4><p>Vite 将应用中的模块分为依赖和源码两类,分别进行服务器启动时间的优化。</p><ul><li>依赖模块,开发过程中基本不会变化。Vite 对依赖采用了 esbuild 预构建的方式,esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍;</li><li>源码模块,是用户自己开发的代码,会经常变动。</li></ul><p>Vite 在浏览器请求时按需转换并以原生 ESM 方式提供源码,让浏览器接管了打包程序的部分工作。</p><h4>Vite工作原理</h4><p>Vite 通过原生 ES Modules 托管源代码,本质上是让浏览器来接管部分打包器的工作。Vite 只会在浏览器请求发生时,按需将源码转成 ES Modules 格式返回给浏览器,由浏览器加载并执行 ES Modules 文件。</p><p><img src="/img/remote/1460000044194275" alt="" title=""></p><h4>Vite的热更新</h4><p>在基于 Bundle 构建的构建器中,当一个文件变动时,重新构建整个 Bundle 文件是非常低效的,且随着应用规模的上升,构建速度会直线下降。</p><p>传统的构建器虽然提供了热更新的能力,但是也会存在随着应用规模上升,热更新速度显著下降的问题。</p><p>Vite 基于 ESM 按需提供源码文件,当一个文件被编辑后,Vite 只会重新编译并提供该文件。因此,无论项目规模多大,Vite 的热更新都可以保持快速更新。</p><p>此外,Vite 合理利用浏览器缓存来加速页面加载,源码模块请求根据 304 Not Modified 进行协商缓存;依赖模块请求通过 Cache-Control: max-age=31536000,immutable 进行强缓存,因此一旦缓存,不会再次请求。</p><p>Vite 依托支持原生 ESM 模块的现代浏览器,极大的降低了应用的启动和重新构建时间。Vite 本质上是一个在开发环境为浏览器按需提供文件的 Web Server,这些文件包含源码模块和在第一次运行时使用 esbuild 预构建的依赖模块。</p><p>Vite 和 Webpack 的主要不同在于开发环境下对于源码如何被托管以及支持哪种模块规范。</p><hr><p>【参考】:</p><p><a href="https://link.segmentfault.com/?enc=FphL%2BXgc64SqrydcdttfLg%3D%3D.8hEDzEQCSNFsTu8tT1wxqf16u25V2F9u7%2BaZ5nbIRbg2mXU%2F3Yn4JEL4Ob3eGMtPWKJ43S5qNZo408FhuVw%2Fig%3D%3D" rel="nofollow">如果能重来,你要选 Vite 还是 Webpack ?</a></p><p><a href="https://link.segmentfault.com/?enc=kVn7d6KdGxusQtUaWUDitg%3D%3D.yl%2B7%2FD4ANM1R8cPXqrHQ8s7%2BHCnQoKqDtkCy3oGy%2BED00oBojN2VZw8QGT1%2FRa%2BT" rel="nofollow">【打包工具】- Vite 和 webpack 原理、优缺点对比</a></p><h3>2.打包构建上做过哪些优化/项目构建部署比较慢,怎么来优化</h3><h3>3.动态引入组件的原理是什么,生成的chunk文件和代码的组件引入如何匹配</h3><h2>小程序</h2><h3>1.Taro开发<strong>小程序中遇到过哪些兼容性问题</strong></h3><h3>2.当有新的小程序api出现后,taro没有实现该怎么使用</h3><h3>3.小程序渲染原理有了解吗</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=PYTq2kBpiWvQzINPrgRyHw%3D%3D.m3XuJvvdindkru3J1rNjVIILgmjTo7SZWzOyiIK8i7mlLw2PB7b8ZrA3VytoWhdW" rel="nofollow">小程序快速渲染的原理及流程解析</a></p><h3>4.小程序滚动穿透如何解决</h3><p>解决办法:</p><ul><li>先说H5的,比较简单,只需要通过弹窗的显示状态,来动态设置底层页面最外层View的overflow属性即可。当弹窗显示的时候,<strong>document.body.style.overflow = 'hidden';</strong> 当弹窗隐藏的时候,<strong>document.body.style.overflow = 'scroll';</strong></li><li>由于小程序没有DOM结构,上面的方案自然是不起任何作用(就是这么傲娇)。小程序要<strong>分两步</strong>来解决,先是mask蒙层会滑动穿透的问题,这个可以采用Taro的<strong>catchMove属性</strong> 来解决,官方文档上也有说,见下方图片。然后是弹窗内部滚动穿透的问题,采用的是将弹窗内部<strong>最外层View改为ScrollView</strong>,光这样改还不行,还要在ScrollView的外层再加个View并且加上<strong>catchMove属性</strong>。直接在ScrollView上面加catchMove不行,ScrollView不支持这个属性。</li></ul><p><img src="/img/remote/1460000044194277" alt="" title=""> <br><strong>注:Taro的CatchMove属性是Taro 3.0.21版本才开始支持</strong></p><p>如上操作,Taro 弹窗滚动穿透问题的问题就完美解决了。可同时兼容H5和小程序。</p><h2>Hybrid混合开发</h2><h3>1.说一下hybrid混合开发中,app端和js端是如何实现通信的?或说下JSBridge的原理</h3><p>在Hybrid模式下,H5会经常需要使用Native的功能,比如打开二维码扫描、调用原生页面、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等,而JavaScript是运行在单独的<strong>JS Context</strong>中(Webview容器、JSCore等),与原生有运行环境的隔离,所以需要有一种机制实现Native端和Web端的<strong>双向通信</strong>,这就是<strong>JSBridge</strong>:以JavaScript引擎或Webview容器作为媒介,通过<strong>协定协议</strong>进行通信,实现Native端和Web端双向通信的一种机制。</p><p>通过JSBridge,Web端可以调用Native端的Java接口,同样Native端也可以通过JSBridge调用Web端的JavaScript接口,实现彼此的双向调用。</p><p><img src="/img/remote/1460000044194278" alt="" title=""></p><p>Web端和Native可以类比于Client/Server模式,Web端调用原生接口时就如同Client向Server端发送一个请求类似,JSB在此充当类似于HTTP协议的角色,实现JSBridge主要是两点:</p><ol><li>将Native端原生接口封装成JavaScript接口</li><li>将Web端JavaScript接口封装成原生接口</li></ol><p>【参考资料】</p><ul><li><a href="https://link.segmentfault.com/?enc=gqlOQmq8LWFTHAs6CRdJWA%3D%3D.EYIrwv1CG8zZWQo7sL9dKSqJEufHEVMaGRepmIksDrWMQPIARh%2Fhks2Q5qV12%2Byq" rel="nofollow">深入浅出JSBridge:从原理到使用</a></li><li><a href="https://link.segmentfault.com/?enc=SLETAKWDrZ4EzFUoDmhSBg%3D%3D.gMSoZe8iKyiAcZfAuGV%2BvwJTfdWYkL892QggKp4Jzk7NVAVqxkexylBF6WyKQm7cX69jpRq62x3S1YTOTXjLTA%3D%3D" rel="nofollow">Hybrid入门及JSBridge原理</a></li></ul><h2>性能优化</h2><h3>1.日常开发中性能优化做过哪些</h3><h4>基本思路</h4><p><img src="/img/remote/1460000044194279" alt="" title=""></p><p>整体的逻辑大体如下:</p><p><img src="/img/remote/1460000044194280" alt="" title=""></p><h4>构建工具分析</h4><ul><li>体积:包体积是否存在杂质,能不能过滤出去?</li><li>构建速度:构建速度影响开发调试效率和发布相关环境的节奏。</li></ul><p>针对体积的分析手段,可以使用webpack-bundle-analyzer来分析当前项目中是否存在<strong>重复依赖</strong>, <strong>影子依赖</strong>, <strong>模块体积差异</strong>等等问题,使用交互式可缩放树图可视化的来梳理自己项目应用中的问题,然后去找到并且优化它。</p><p>针对速度分析, 可以使用speed-measure-webpack-plugin来分析当前每个bundle模块打包构建时的时间数据,分别会包含loader, plugin等处理的时间。根据相关的数据来定点模块,判断是否有优化的空间。</p><h4>项目分析</h4><ul><li><strong>NetWork:</strong> 网络面板,用于侦测浏览器资源下载与上传的能耗视图。</li><li><strong>Performance:</strong> 性能面板:用于侦测浏览器运行时的性能表现,得出项目运行的时序图,同时也可以分析页面的一些隐式问题,如 (内存泄漏)。</li><li><strong>Lighthouse:</strong> 性能评测(灯塔),基于当前浏览器页面的阶段性加载现状来为用户提供结果分析数据指标报告。</li><li><strong>探针:</strong> 前端框架如React有相关Profiler的探针API和chrome插件,能够为开发者探测代码编写时的问题,避免重复或者是频繁异常的Repeat rendering。</li></ul><p>性能监控平台,为线上的前端进行侦测,从<strong>接口调用</strong>、<strong>首屏性能</strong>、<strong>流量保活</strong>等等一系列的性能指标做度量平台。</p><p><strong>如何针对通用问题进行标准化处理???</strong></p><h4>优化手段</h4><p><img src="/img/remote/1460000044194281" alt="" title=""></p><h4>构建工具优化</h4><h5>包体积大小</h5><p>首先,针对包体积大小做一些调整,能做的事情大体上有以下几点:</p><ul><li><strong>按需加载</strong>:针对项目中比较大型的工具类库,如lodash、UI Design、use-hooks等类库,如果只是使用很小的一部分,就需要避免全量加载的情况。</li><li><strong>tree-shaking</strong>:通过ESM的特性,可以在构建阶段将项目模块中的死代码进行移除,从而保证干净的结构和最小的文件大小。</li><li><strong>minify</strong>:通过webpack相关的插件,可以为代码产物做混淆压缩,如JS minify与CSS minify,当项目的代码越大的时候,压缩的效果就越明显。</li><li><strong>CDN依赖</strong>:对于部分共有依赖可以将其放置在CDN中来进行加载,能够极大幅度的减少本地项目产物的体积大小,缺点就是无法进行按需加载,占用浏览器请求资源,具体的收益可以自己权衡。</li></ul><h5>构建速度</h5><p>其次就是构建速度了,可以从以下几个方面来着手:</p><ul><li><strong>持久化缓存</strong>:webpack5本身集成,通过cache字段开启即可,webpack4可以通过cache-loader来实现,不过不足的地方是cache-loader仅对loader起作用。(目前该loader看起来已经暂停维护了)。</li><li><strong>多进程构建</strong>:通过thread-loader可以开启多进程构建,可以提高部分环节的性能效率,比如babel-ast解析等等。webpack4的话则是使用happypack来实现。</li><li><strong>Building</strong>:使用基于Rust编写的高性能编译工具来替换默认的babel,比如将babel-loader替换为swc-loader,这个是一个可选的操作,目前市面上多数cli都支持了该选项,以供开发者抉择。</li><li><strong>Dll</strong>: dll可以将一些依赖库提前进行打包,能够有效的降低打包类库的次数,只有其修改后才会进行重新的构建,通常用于公用类库的处理。可以使用DllPlugin来配置对应的效果。</li><li><strong>Bundless</strong>: Vite是市面上非常火热的Bundless方案之一,已经可以看出越来越多的人开始使用它来代替传统Bundle方案,其优秀的构建速度, HMR深受好评。</li></ul><h4>项目方案</h4><h5>资源加载</h5><ul><li><strong>懒加载</strong>:懒加载我对齐的定义是延迟资源的加载,直到齐第一次触达时在进行资源的引入和展示,这项技术被广泛用于spa-router、图片资源等非阻塞性资源的优化上。对于缩短关键路径渲染非常有帮助,如各位同学耳熟能详的首屏优化。</li><li><strong>预加载</strong>:预加载的目的就是通过在渲染前预先拿到需要展示的数据,从而缩短关键承流页面呈现给用户的渲染时间,大多数情况下可以分为首页预载和关键页预载两方面。</li><li><strong>首页预载</strong>:Web端提供了相关的pre-load、pre-render、pre-fetch相关API实现。小程序方面也提供了相关冷启动获取数据的能力,例如getBackgroundFetch。对于追求极致的性能体验的话不妨一试。</li><li><strong>关键页预载</strong>:关键页预载的话就根据自身业务来实现了,参考商城业务中商品详情页、营销会场等页面都是用户比较熟悉的页面。因此,多数时候都会将页面跳转与数据请求共同进行的。</li><li><strong>接口优化</strong>:耗时较久的接口如果阻塞核心链路,可以将其拆成多个粒度小的接口来渐进式的调用以此来保证页面优先加载。反之亦然,如果接口零散占用了太多的请求资源,则可以将其聚合成一个完整的大接口来获取数据。</li><li><strong>缓存</strong>:浏览器缓存策略大体上分为强缓存和协商缓存。利用好缓存策略可以有效的降低资源的重复加载,从而提高网页的整体加载速度。</li></ul><h5>网络相关</h5><p>在这个过程中,会有两个阶段大家耳熟能详,分别是DNS解析和HTTP连接(三次握手)。它们是这个过程中相对耗时的两个部分。基于此,可以针对网络来对项目做以下一些优化:</p><ul><li><strong>DNS预解析</strong>: 了解DNS解析过程的同学都知道,相当耗时。而浏览器也提供了dns-prefetch的相关操作,用于抢先对origin进行解析。</li><li><strong>PreConnect</strong>:既然DNS可以预解析,那么请求连接的话也相对应的可以抢先执行。</li><li><strong>内容分发网络CDN</strong>:通过内容分发网络CDN可以使用户就近获取所需要的内容资源,从而降低网络拥塞,提高用户访问响应速度和保证访问资源的命中率。</li><li><strong>HTTP/2</strong>:尽可能使用HTTP/2的新特性,它会带来一些意想不到的小惊喜。感兴趣的可以看<a href="https://link.segmentfault.com/?enc=gLqC1ToRTi8CJ3vuVEKXOQ%3D%3D.8YSdzhNQfyR7MriPkXTbeWJsoq8eAKOiTMdrbr5zWsLcNO1g%2FFqkqqiY0b6JVQn5UT%2BviDWN7yenrpX6eStRbgpWPbkzDnIFJGv3VUJ1IvEivUkKgNziZ1JyRtqlQhQh" rel="nofollow">《HTTP2简介和基于HTTP2的Web优化》</a></li><li><strong>Gzip</strong>:很早之前会开启Gzip的压缩方案来减少网络实际传输数据的大小,服务器会将请求资源进行压缩后传输给客户端。</li></ul><h5>交互体验</h5><ul><li><strong>加载态</strong>:提到加载态,可能大多数同学第一想到的就是loading,基于loading的实现之上,很多产品都会对部分页面制作骨架组件/骨架屏,它的优点就是表达能力强,相比于简单的加载loading而言,骨架组件能够给用户呈现预期的页面与区块的显示效果。</li><li><strong>虚拟列表</strong>:虚拟列表已经是近年来老生常谈的一个优化了,其场景主要用于页面中需要展示大量数据时的一个措施手段,如收获地区选择、无限滚动场景,任何需要堆叠数据显示的页面中或多或少都会出现它的身影。</li><li>想了解详细的场景和实现过程的可以查阅我之前写的相关文章<a href="https://link.segmentfault.com/?enc=ggA2DEwXJqZOgczFVB5qsw%3D%3D.YMxjSkLjsultIzaHGdnNW%2FtTDtO3CHbCqO%2Ft42FKSGYJ%2FhebvVxYvumpCCEQ5JgV" rel="nofollow">《百万PV商城实践系列 - 前端长列表渲染优化实战》</a></li></ul><h5>浏览器</h5><ul><li><strong>回流和重绘</strong>: 回流和重绘是一个消费资源的事情,一旦浏览器发生频繁的回流,会造成浏览器资源的紧张,导致项目或多或少出现卡顿的问题。现如今浏览器虽然已经为我们做了Layout相关的合并优化。但还是要从根本上去减少不必要的情况发生。而解决的方案大多数如下几种:</li></ul><p><!----></p><ul><li><ul><li>代码编写:样式集中改变,可以有效减少重排次数,尽可能不要频繁的去获取和写入DOM,需要操作时应提前在内存中准备好对应的操作元素,从而一次性的将其进行读写。</li><li>隐藏修改:当涉及到DOM元素的频繁操作时,尽可能让其先排出文档流,直到最终操作完成后在将其渲染到页面上,以此来降低回流和重绘的频率。</li><li>GPU合成:对于动画,可以使用transform代替position做一些事情,现如今大部分浏览器都会对transform进行借用GPU资源来做渲染优化。</li></ul></li></ul><p><!----></p><ul><li><strong>RAF&RDC</strong>:RAF&RDC分别是requestAnimationFrame和requestIdleCallback的简称,合理的使用可以避免频繁逻辑修改导致延迟关键事件,如动画和输入响应的交互。一个是针对动画帧效果,一个针对页面执行的逻辑。</li><li><strong>事件委托</strong>:通过事件委托来绑定对应的操作,可以有效的减少相关内存的消耗。现如今,我们已经不在需要太过关注原生事件的处理了,多数情况下框架类库都已经能够很好的开始工作。</li></ul><h5>图片</h5><ul><li><strong>WebP</strong>:webp与png一样,是一种图片格式,因为使用更优的图像数据压缩算法、更小的图片体积因此能够带来更好的访问速度优势。需要注意的是可能会朋友不支持webp格式的环境问题,这时候就需要进行降级处理避免出现用户无法访问图片的体验。</li><li><strong>渐进式加载</strong>:渐进式是一个过程,渐进式加载的话就是让一张图片从模糊到清晰慢慢展示的一个过程,避免用户因为网络问题导致图片加载失败看到不友好的体验,本质上的话属于加载状态的一种。和骨架屏区别不大。</li><li><strong>图片切割</strong>:在业务当中,会看到类似商品详情图、宣传海报等相关的图片展示,它本身是一个非常长的图片,且体积因为要保真的原因无法做一些压缩。因此针对类似的场景就可以选择图片切割的方案,将长图切割成若干份小图进行展示,搭配之前提到过的一些优化方案可以完美的避免加载渲染问题。</li><li><strong>图片CDN</strong>:使用图片CDN的好处除了可以获得内容分发来提高响应速度外,还可以使用其提供的图片裁切功能动态的定义分辨率大小、清晰度展示等辅助其在页面上的展示。</li></ul><h4>总结</h4><p><img src="/img/remote/1460000044194282" alt="" title=""></p><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=yp18K6xw2hMxXUs07FnFhw%3D%3D.0Wnmt7MkQgz%2Bh16gXGvT6eZcaZksKbzHGbD9Gea4l1XP5W2QLK3p9xnrOC51Dc6X" rel="nofollow">当面试官问我前端可以做的性能优化有哪些</a></p><p><a href="https://link.segmentfault.com/?enc=lLPS%2Bei8XO9VWGx3rFz5Aw%3D%3D.RDhtwJt%2FCUMfquvnntUB%2BU%2B4Q1eFMyxgpPN0v%2FHNjWXWxHN88FxGtWQ%2Bl0sZ7bmP" rel="nofollow">5000字总结面试常问前端项目优化究竟怎么来做?</a></p><p><a href="https://link.segmentfault.com/?enc=hNl0n%2BG%2FiefuxNvhJ%2BV2tA%3D%3D.9HZb4O1ASYJAayVtaGJgAt7kCheG1IBWhM%2BKrpE7wC%2Fvmd0xGc6npqJM0BNmvwc5" rel="nofollow">面试时我是如何回答“前端性能优化”的</a></p><p><a href="https://link.segmentfault.com/?enc=7R43IRqG08cf6C91CMfQ0A%3D%3D.ufsUmITNOOrb%2FiZGGQJOtN8U%2FBCycS1es1AJYLsnNuA%3D" rel="nofollow">指标-衡量性能和用户体验的标准</a></p><h3>2.错误监控上报怎么做</h3><h3>3.性能监控指标有哪些?首屏时间如何计算</h3><h4>【扩展】如何优化首屏时间</h4><ol><li>http缓存(强缓存和协商缓存), 性价比最高的一个优化方式。需注意的是,浏览器不缓存XHR接口,自己可根据业务特性动态地选择缓存策略。比如一个体积很大的接口,但内容变更频率不是很频繁,这种情况就适合用协商缓存。</li><li>cdn分发(减少传输距离)。通过在多台服务器部署相同的副本,当用户访问时,dns服务器就近(物理距离)选择一台文件服务器并返回它的ip。</li><li>前端的资源动态加载:</li></ol><p><!----></p><ol><li><ol><li>路由动态加载</li><li>组件动态加载</li><li>图片懒加载(offScreen Image),越来越多的浏览器支持原生的懒加载,通过给img标签加上loading="lazy来开启懒加载模式。</li></ol></li></ol><p><!----></p><ol start="4"><li>合并请求。这点在串行接口的场景中作用很明显,比如有两个串行的接口A和B,需要先请求A,然后根据接口A的返回结果去请求接口B。假如server和client的物理距离为D,那么这个串行的场景传输的物理路程为4D。如果合并成一个接口,物理路程可减小为2D。</li><li>页面使用骨架屏。意思是在首屏加载完成之前,通过渲染一些简单元素进行占位。骨架屏的好处在于可以减少用户等待时的急躁情绪。这点很有效,在很多成熟的网站都有大量应用。没有骨架屏的话,一个loading图也是可以的。</li><li>使用ssr渲染。</li><li>service worker:通过sw离线更新缓存的能力,理论上能做到每次访问页面都无需下载html,且所有静态资源都已经在本地有缓存。</li><li>引入<a href="https://link.segmentfault.com/?enc=%2B5XEK9axEjSt84B%2F5PSGDQ%3D%3D.yaZCqCZWbNTQ8DlgJa1kJhwlSZBaCeRXj1itCHcJS%2Fe2nesPE36n23wdJT7dCxEv7zEngauj517w5EGUVLK5MLbLglCdSQWQ2AG9BGchNAgc5C36ZvLAo%2FJp6LpTRmux3cnez%2FDeqcJS4lpfvZJ6bocTzqc0E13Lj1ZdriChR2w%3D" rel="nofollow">http2.0</a>。http2.0对比http1.1,最主要的提升是传输性能,在接口小而多的时候性能优势会更加明显。</li><li>利用好http压缩。即使是最普通的gzip,也能把bootstrap.min.css压缩到原来的17%。可见,压缩的效果非常明显,特别是对于文本类的静态资源。另外,接口也是能压缩的。接口不大的话可以不用压缩,因为性价比低(考虑压缩和解压的时间)。值得注意的是,因为压缩算法的特性,文件越大,压缩效果会越好。</li><li>利用好script标签的<a href="https://link.segmentfault.com/?enc=9vsQCPrckZRhZ8Mbi3zFVA%3D%3D.8kB3qXsDALp5VNEaAFXQuZaH3b3PrEJbfyL9mC4SVZWIbd5Yfk8eTT7fL%2FF0t9qzuOPxhF1hBcqahwzyiItUfbChqnEM8A0kl%2BNaF4j36Gu6L2FvoocTpf2Su09ysKvXamDEM5CVvqe%2FSLXBd%2FUx%2BQ%3D%3D" rel="nofollow">async和defer</a>这两个属性。</li><li>使用 WebP 代替jpg/png。</li></ol><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=IjI%2BKfK8urT9janT%2BVh5eA%3D%3D.WpoyNXFzDv8%2BzoLza0LKt8FPqVv4ZyIkJ0apIjSVSClRVIeYn2B6aRzkVeAiHUWv" rel="nofollow">web的首屏加载时间</a></p><h3>4.如果让你设计一个监控系统,你会如何设计?</h3><h3>5.单页应用性能指标有FMP,了解吗</h3><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=OEAC%2F7PDtMRI%2BvRtiVFX5A%3D%3D.xvSfjRWiWv7AzwKptiPw2lOYy2%2BTReutt71t9RLyP6ch8i8OJ0b8vUTtUnvA7m9v" rel="nofollow">FP、FCP、FMP、LCP都是什么P?</a></p><p><a href="https://link.segmentfault.com/?enc=C3S6O9o11uAc5QgGI90O%2Bw%3D%3D.5UtiaRWKZiGtv8GN9%2FFWLPP1JByFsIebgN9uSVW416hW8TT2L%2FBc5cBIjnFoW%2FlE" rel="nofollow">定位性能指标FMP</a></p><h3>6.单页应用首屏优化方式有哪些</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=BECHuKo%2F1cjv4GypJN48Qg%3D%3D.GotF5135T9eSOIhgHSXmPm%2FGpze%2BLxoTvLNeNcgRlL%2BvSsXJG7JU5hykBbXdsK4VYdzjgqaNmXwuf4NYaaT4qA%3D%3D" rel="nofollow">面试官:SPA首屏加载速度慢的怎么解决?</a></p><h2>设计模式</h2><h3>1.依赖注入是什么</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=Yw3PvtwCZ4ED1AayhimbbA%3D%3D.bNQI2v766TC0Ot5BAALPmo7erUdjWLB8ayPqZChr5515vSQXTtJZGixDs9qGSB33" rel="nofollow">依赖注入 in 前端 && Typescript 实现依赖注入</a></p><p><a href="https://link.segmentfault.com/?enc=LQv9w%2FLta4H%2B4P05eYtoCQ%3D%3D.zDsPf2Sn1NIxLOMEZQw1xkqUGPI5YIXmBhPdsoMX451zPuOKU2NYML2Sng1aXy5c" rel="nofollow">前端架构利器-依赖注入</a></p><p><a href="https://link.segmentfault.com/?enc=SYEe2%2FLXKQMRC%2BTI%2FlRUWA%3D%3D.0EeGbvCtk1QYk5sbbiNKdYdFwG5sJWVRjPmGpt4rzp2tl4wSXUwwElEvUGaTelv1" rel="nofollow">写给前端同学的依赖注入(DI) 介绍</a></p><h3>2.前端框架设计里面常见的设计模式有哪些</h3><ul><li>工厂模式</li><li>单例模式</li><li>代理模式</li><li>策略模式</li><li>观察者/发布-订阅模式</li><li>迭代器模式</li><li>装饰器模式</li><li>原型模式</li></ul><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=wi%2F%2Bdu9br7swRsQf58OcEg%3D%3D.NLby0RmNUbV43Wp7%2BdgcK21%2F%2FnZh6BuemE%2BRaCyjEfeqV4cULR7633DVf5hxxrdJ" rel="nofollow">前端需要了解的9种设计模式</a></p><h2>安全</h2><h3>1.对称加密和非对称加密</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=hCz0cCnXYnvlK9bS%2FroJ6A%3D%3D.MYcbCznlh3JtFTOi1sqPtRpDpR%2FrYtlqogPWaM4IgoZl4IYSCqg84wKW6BIrg7Ax" rel="nofollow">对称加密和非对称加密</a></p><h3>2.网站攻击方式有哪些</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=A%2BAT2T9EgFiSgRK%2BD6opuQ%3D%3D.MKxZPx0vDzoxSKq29t%2FVieuy5H78iUNwuPem31HD9wmRMGAEVYob%2BhRhWoeSTqeBKIPOgf7DiJVqJCy8BW1jTB2tBzJ%2Bq6ycQkjv0pfnuRXj2ZqCkrv5UIsdvD%2BkVBcbZCfsDH6GZsAnLShESQOnzZ%2B9D6OZnoVJY%2B%2Bb834HVPsDwbok%2FgsOW46zuu%2Bky3kBRihCOC5%2FRAK8w3940UZNhHMAz69zapVkVOv%2BWbLpVjvr8fK9iMjuFBfaY7L7s4K5ihBKrjzYLcjKxPgPheGrb55mMFB0pad5iMW6obQTFvM%3D" rel="nofollow">面试官:web常见的攻击方式有哪些?如何防御?</a></p><h2>开放性题</h2><h3>1.前端如何处理并发</h3><p>比如页面初始化需要10个请求,后端只让每次3个,怎么做</p><p>思路拆解:</p><ul><li>urls的长度为0时,results就没有值,此时应该返回空数组</li><li>maxNum大于urls的长度时,应该取的是urls的长度,否则则是取maxNum</li><li>需要定义一个count计数器来判断是否已全部请求完成</li><li>因为没有考虑请求是否请求成功,所以请求成功或报错都应把结果保存在results集合中</li><li>results中的顺序需和urls中的保持一致</li></ul><pre><code>// 并发请求函数
const concurrencyRequest = (urls, maxNum) => {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
// 发送请求
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使result和urls相对应
const url = urls[index];
index++;
console.log(url);
try {
const resp = await fetch(url);
// resp 加入到results
results[i] = resp;
} catch (err) {
// err 加入到results
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
console.log('完成了');
resolve(results);
}
request();
}
}
// maxNum和urls.length取最小进行调用
const times = Math.min(maxNum, urls.length);
for(let i = 0; i < times; i++) {
request();
}
})
}</code></pre><pre><code>function sendRequest(urls, max, callbackFunc) {
const REQUEST_MAX = max;
const TOTAL_REQUESTS_NUM = urls.length;
const blockQueue = []; // 等待排队的那个队列
let currentReqNumber = 0; // 现在请求的数量是
let numberOfRequestsDone = 0; // 已经请求完毕的数量是
const results = new Array(TOTAL_REQUESTS_NUM).fill(false); // 所有请求的返回结果,先初始化上
async function init() {
for (let i = 0; i < urls.length; i++) {
request(i, urls[i]);
}
}
async function request(index, reqUrl) {
// 这个index传过来就是为了对应好哪个请求,
// 放在对应的results数组对应位置上的,保持顺序
if (currentReqNumber >= REQUEST_MAX) {
await new Promise((resolve) => blockQueue.push(resolve)); // 阻塞队列增加一个 Pending 状态的 Promise,
// 进里面排队去吧,不放你出来,不resolve你,你就别想进行下面的请求
}
reqHandler(index, reqUrl); // {4}
}
async function reqHandler(index, reqUrl) {
currentReqNumber++; // {5}
try {
const result = await fetch(reqUrl);
results[index] = result;
} catch (err) {
results[index] = err;
} finally {
currentReqNumber--;
numberOfRequestsDone++;
if (blockQueue.length) {
// 每完成一个就从阻塞队列里剔除一个
blockQueue[0](); // 将最先进入阻塞队列的 Promise 从 Pending 变为 Fulfilled,
// 也就是执行resolve函数了,后面不就能继续进行了嘛
blockQueue.shift();
}
if (numberOfRequestsDone === TOTAL_REQUESTS_NUM) {
callbackFunc(results);
}
}
}
init();
}
// 测试数据
const allRequest = [
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=1",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=2",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=3",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=4",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=5",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=6",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=7",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=8",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=9",
"https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?index=10",
];
sendRequest(allRequests, 2, (result) => console.log(result));</code></pre><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=8Q8U04HJKGYeB2b4s7Vzsw%3D%3D.HH%2FT9JarEVWQlm696Nwz%2BvaeQLqd8zgz3kVYPJM2hWTKHcwWeWEzOCHnoT5wh1si" rel="nofollow">关于前端:如何实现并发请求数量控制?</a></p><p><a href="https://link.segmentfault.com/?enc=NzE06QIncaCp1AmZK5oxAg%3D%3D.%2BBj0AWE5mK9n0gAIJzyM1DbRCr8x%2BnNZW8pcN94R4D0QRi5vkyL07I6E4RKPjJcFlhuvPEz2%2FwYRf8UInb%2B%2Byg%3D%3D" rel="nofollow">30行代码,高大上解决Promise的多并发问题</a></p><h3>2.前端处理大数据会有哪些影响</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=mtZwZVv4sB1Pt3uEuwAdqg%3D%3D.iwddzBWpWg1HTyHrsVPohKowxLis5xqGWT2KwdGSm5b2EPpjgoZ11tka6B2hURet" rel="nofollow">前端优化之分分钟学会渲染百万条数据不卡顿</a></p><h3>3.对比React和Vue</h3><h3>4.说一下对SEO的了解</h3><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=vjhmWd3vVLBhqNiyDrqO%2FA%3D%3D.EITxmIVynGwLHk7i0%2B8wn6e3ZzOdzJagnn8ecFu9xgNWWOqlUmrUOmr%2BYEYO1BAK" rel="nofollow">什么是SEO?如何进行SEO优化?</a></p><h3>5.设计离线包方案</h3><h4>从 WebView 初始化到 H5 页面最终渲染的整个过程:</h4><p><img src="/img/remote/1460000044194283" alt="" title=""></p><p>可以看到,一个完整的WebView加载流程:</p><ol><li>初始化webview;</li><li>建立连接,从后端下载开启WebView需要的相关文件数据包,包括网页中需要请求的文件以及模板Html;</li><li>加载与接受数据,页面的渲染,并通过实现android和js的交互</li></ol><h4>目前主流的优化方式主要包括:</h4><ul><li><strong>针对 WebView 初始化</strong>:该过程大致需耗费 70~700ms。当客户端刚启动时,可以先提前初始化一个全局的 WebView 待用并隐藏,当用户访问了 WebView 时,直接使用这个 WebView 加载对应网页并展示。</li><li><strong>针对向后端发送接口请求</strong>:在客户端初始化 WebView 的同时,直接由 Native 开始网络请求数据,当页面初始化完成后,向 Native 获取其代理请求的数据。</li><li><strong>针对单页面加载的JS动态拼接 Html</strong>:采用多页面打包, 服务端渲染,以及构建时预渲染等方式。</li><li><strong>针对加载页面资源的大小</strong>:可采用懒加载等方式,将需要较大资源的部分分离出来,等整体页面渲染完成后再异步请求分离出来的资源,以提升整体页面加载速度。</li></ul><h4>离线包的具体思路</h4><p>默认情况下,我们先将页面需要的静态资源打包并预先内置到客户端的安装包中,当用户安装应用后,解压资源到本地存储中,当 WebView 加载某个 H5 页面时,拦截发出的所有 http 请求,查看请求的资源是否在本地存在,如果存在则直接加载本地资源;否则加载网络的资源。</p><p><img src="/img/remote/1460000044194284" alt="" title=""></p><p><img src="/img/remote/1460000044194285" alt="" title=""></p><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=eVrST1HvTzUpzUjf4gUSMA%3D%3D.YXM5TpyeutTqNrDqaXUK9nRa8RYXK3W2%2FZHiCk6rtzb4EEex6ulWIGZSB3BpY7yv" rel="nofollow">Hybrid App 离线包方案实践</a></p><p><a href="https://link.segmentfault.com/?enc=QutMf7AAhODQ83C0aKCODQ%3D%3D.kbuklZwNFijSW1hjkaOb7B1Ybg35Ss3sWBHkj7ewrL2mYRxA2SqbpWFJVEIAb27f" rel="nofollow">一起学跨端技术-离线包</a></p><p><a href="https://link.segmentfault.com/?enc=FPf4CKI7m79lI1ebfWTBzA%3D%3D.FrBdvvPXHWm6cfz1bYEL50GgyTUCFNNZE0fqxY8PR4D94PkInIS82WaGS59YLIGt" rel="nofollow">基于 prefetch 的 H5 离线包方案</a></p><h3>7.Vue3的Compisition API和React hooks有哪些区别</h3><p>React Hooks底层是链表,每一个 Hook 的 next 是指向下一个 Hook 的;</p><p>Vue对数据的响应是基于 proxy 的,对数据直接代理观察。这种场景下,只要任何一个更改 data 的地方,相关的 function 或者 template 都会被重新计算,因此避开了 React 可能遇到的性能上的问题。</p><p>代码执行上:</p><ul><li>Vue Composition API 的 setup() 晚于 beforeCreate 钩子,早于 created 钩子被调用</li><li>React Hooks 会在组件每次渲染时候运行,而 Vue setup() 只在组件创建时运行一次</li></ul><p>由于 React Hooks 会多次运行,所以 render 方法必须遵守某些规则,比如:</p><p>不要在循环内部、条件语句中或嵌套函数里调用 Hooks</p><p>声明状态上:</p><p><strong>React</strong></p><p>useState 是 React Hooks 声明状态的主要途径</p><ul><li>可以向调用中传入一个初始值作为参数</li><li>如果初始值的计算代价比较昂贵,也可以将其表达为一个函数,就只会在初次渲染时才会被执行</li></ul><p><strong>Vue</strong></p><p>Vue 使用两个主要的函数来声明状态:ref 和 reactive。</p><p>ref() 返回一个反应式对象,其内部值可通过其 value 属性被访问到。可以将其用于基本类型,也可以用于对象</p><p>reactive() 只将一个对象作为其输入并返回一个对其的反应式代理</p><p><strong>TODO ……</strong></p><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=guVyz%2F4XxeZ5XkJZL%2FFb8w%3D%3D.%2BaoptjSH2Qhz%2FYEsZtuksl0303N2pMB6SH8ujQxZvosQ8TCzn5xWU8D1BToBt4Gr" rel="nofollow">Vue Composition API 和 React Hooks 对比</a></p><h3>8.前端灰度怎么做;如果让你设计一个灰度过程的话如何设计,项目版本灰度如何来做;如何避免再次发版;</h3><p>【参考】</p><p><a href="https://link.segmentfault.com/?enc=QjkY6cmtnlcHFhVU%2FlaKLw%3D%3D.1hfRt76cBbtV%2BEjB38QnN%2FCU%2FjF7AdlqvpfCFmv74vH8pPH%2FNIaaZTc14NSE3Tcm" rel="nofollow">前端灰度-前端项目如何进行灰度?</a></p><p><a href="https://link.segmentfault.com/?enc=RgHzUn0tftB%2FbFh8oc7PAA%3D%3D.FUII1J7b0NdXPUcGxYtxOgupry0sgV1xbiwMn8FACH9FM0zPU1d0Eqz4dMIA%2FYcL" rel="nofollow">你想知道的前端灰度方案都在这了</a></p><h3>9.说下单页应用和多页应用的区别,优缺点</h3><p>【参考资料】</p><p><a href="https://link.segmentfault.com/?enc=db1vd5K8C6TozEA5EFRJ%2Fg%3D%3D.WyMKv87ODUS4rKHrtXbl0g5caIGnlwSkn2MXvKzdkvt9AIO4MqkOj4%2BkP9sJEjqd" rel="nofollow">vue单页面(SPA)和多页面(MPA)的区别(详细答案)</a></p><h3>10.管理系统鉴权如何做的</h3><p>一般我们来说主要针对RBAC鉴权模式进行控制</p><p>页面鉴权用户有没有权限访问该页面,或者有没有查看通往该页面路由的权限。(组件和路由)</p><p>组件: 一.有没有权限访问页面的时候:通过路由守卫结合后端返回来的token,进行页面跳转之前的鉴权,查看token是否过期并且是否拥有该页面的权限。</p><p>路由:判断用户是否有权限查看通过指定页面的路由(或菜单或导航): 1.纯前端处理:在写路由表的时候,我们会在每个路由下加一个meta,然后在meta里面写出可以访问或查看该路由或页面的角色信息,然后我们可以通过该meta下的信息使用addrouter控制该路由下的显隐 2.前后端配合处理:每次登陆的时候,都在后端那里获取token的路由表保存到vuex里,在通过addrouter动态渲染token下的路由及导航</p><p>UI鉴权:它的颗粒度很细,所以说难度较大,我们可以通过统一的自定义指令来进行配置。</p><p>一般来说UI鉴权指的是按钮鉴权处理UI鉴权 简单的方法是我们可以获取token下的角色信息,用v-if处理该UI的显隐,但是这种方式缺点很明显,不易于统一管理,所以我们需要集中封装一个自定义指令,在自定义指令中,集中处理鉴权的逻辑,然后分发在每个鉴权的按钮上。</p><h2>Node</h2><h3>1.<strong>Node用过么,起一个http server的伪代码大概是?</strong></h3><pre><code>const { createServer } = require('http');
const HOST = 'localhost';
const PORT = '8080';
const server = createServer((req, resp) => {
// the first param is status code it returns
// and the second param is response header info
resp.writeHead(200, { 'Content-Type': 'text/plain' });
console.log('server is working...');
// call end method to tell server that the request has been fulfilled
resp.end('hello nodejs http server');
});
server.listen(PORT, HOST, (error) => {
if (error) {
console.log('Something wrong: ', error);
return;
}
console.log(`server is listening on http://${HOST}:${PORT} ...`);
});</code></pre><h2>手写部分</h2><h3>1.括号匹配</h3><h3>2.数组排重</h3><h3>3.数组唯一出现</h3><h3>4.面向对象设计</h3><h3>5.版本号排序</h3><h3>6.函数柯里化</h3><h3>7.数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字</h3><h3>8.手写单例模式</h3><h3>9.手写快速排序</h3><h3>10.单向链表的反转取值,通过迭代/递归方式实现</h3><h2>其他</h2><h3><strong>1.git merge 和 git rebase的区别是什么</strong></h3><ul><li>git merge会创建一个新的合并提交,保留完整的提交历史记录,但可能会导致分支历史变得复杂。</li><li>git rebase会将提交复制到目标分支上,并重新应用目标分支上的更改,改写提交历史记录,使分支历史保持线性,但可能会丢失一些提交信息。</li></ul><p>【参考资料】:</p><p><a href="https://link.segmentfault.com/?enc=8NBfuYSLjzV63KjIzSsDsQ%3D%3D.%2FdQJ6488DltdqbaAvAyOk0NbO4avMFkpLsaYMKvFrv%2BeWeD%2BDSlC9cwoJqF5E5cJ" rel="nofollow">Merge vs Rebase</a></p><p><a href="https://link.segmentfault.com/?enc=2ptzp4NPPD45M5xGFjo1WQ%3D%3D.DefF0zkNwKt%2Fw39L%2F%2Bsv7buL4JB85VEcgv50NJPPFmDT63bKY4a%2FRfPpPl0Tk24SBvaqI0tE3SfZsh2MpYDhlUKwx%2FVgVVcFca5eGl3VXxQ%3D" rel="nofollow">面试官:说说你对git rebase 和 git merge的理解?区别?</a></p><hr><p><a href="https://link.segmentfault.com/?enc=PcG18q4QzABvte0TK%2FeRUg%3D%3D.VeV%2ByBKz65ETbVsV7PeYjS6KpDhJTXTid40fNvpKGPAtTCOxVrXNhbfYBxn4f%2Fsd" rel="nofollow">原文连接</a> 欢迎关注</p>
Vue3 Composable最佳实践(二)
https://segmentfault.com/a/1190000043936026
2023-06-26T12:28:21+08:00
2023-06-26T12:28:21+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>本文为翻译文章,部分内容难免理解有偏差,如有错误欢迎大家指正。原文链接见文章末尾。</blockquote><p>在使用Vue.js中的组合式时,有时你已经有了一个想要使用的<code>ref</code>,而有时你没有。本文将介绍一种模式,让你可以以两种方式使用组合式,从而在编写应用程序时提供更多的灵活性。</p><p>本篇是这个系列文章的第二篇,全部涵盖内容如下:</p><ul><li><a href="https://link.segmentfault.com/?enc=GGGEs1Ysm8nWF8G6dO64qg%3D%3D.od6DkTxdcGEU5GiSn87fb6nM1YZ9iKtWeJmN19kybXuMdQT0azoeFbqYZKX13yM8" rel="nofollow">1.如何通过选项对象参数使您的组合更加可配置;</a></li><li>2.使用ref和unref使我们的参数更加灵活;👈 <strong>本篇主题</strong></li><li>3.如何使你的返回值更有用;</li><li>4.为什么从接口定义开始可以使你的组合式函数更强大;</li><li>5.如何使用异步代码而无需“等待” - 使您的代码更易于理解;</li></ul><h3>使用ref和unref来实现更灵活的参数传递</h3><p>通常,组合式API需要某些类型的参数作为输入,通常会使用到响应式变量参数。它也可以是原始的Javascript类型,如字符串,数字或对象。但是,为了使组合式更具灵活性和可重用性,我们会希望它可以接受任意类型的参数,并在运行时将其转换为所需的类型。</p><pre><code class="javascript">// Works if we give it a ref we already have
const countRef = ref(2);
useCount(countRef);
// Also works if we give it just a number
const countRef = useCount(2);</code></pre><p>我们上篇里面提到的<code>useTitle</code>也适用于这种模式。当我们传入一个<code>ref</code>的时候,页面的标题将会被设置成这个<code>ref</code>的<code>value</code>。</p><pre><code class="javascript">const title = ref('这里是标题');
useTitle(title);
title.value = '这是一个新标题';</code></pre><p>如果我们传入一个字符串变量的话,它将会创建一个新的ref和标题进行关联:</p><pre><code class="javascript">const title = useTitle('这里是标题');
title.value = '这是一个新标题';</code></pre><p>上面的例子可能看起来区别不大,但是,在我们使用到一些方法或者组合式API的时候,我们可能会有不同的引用来源,从而这个组合式可以适应不同的情况。下面让我们来看一下如何在我们的组合式中使用这种模式。</p><h3>在组合式中实现灵活的参数</h3><p>想实现灵活的参数模式,我们需要使用到<code>ref</code>和<code>unref</code>方法在参数上面。</p><pre><code class="javascript">// When we need to use a ref in the composable
export default useMyComposable(input) {
const ref = ref(input);
}
// When we need to use a raw value in the composable
export default useMyComposable(input) {
const rawValue = unref(input);
}</code></pre><p>如果是非响应式的参数变量的话,<code>ref</code>方法会创建一个新的<code>ref</code>变量给我们;如果是响应式的话,它会直接将对应的变量返回给我们。</p><pre><code class="javascript">// Create a new ref
const myRef = ref(0);
// Get the same ref back
assert(myRef === ref(myRef));</code></pre><p><code>unref</code>方法类似,只不过它是将变量的真实值返回给我们</p><pre><code class="javascript">// Unwrap to get the inner value
const value = unref(myRef);
// Returns the same primitive value
assert(value === unref(value));</code></pre><p>下面让我们看看在<code>VueUse</code>中使用这种模式的例子。</p><h3>举例-useTitle</h3><p>让我们继续来看下我们已经熟悉的<code>useTitle</code>方法。它允许我们传入一个字符串或者是响应式字符串变量:</p><pre><code class="javascript">// 传入字符串变量
const titleRef = useTitle('Initial title');
// 传入响应式字符串变量
const titleRef = ref('Initial title');
useTitle(titleRef);</code></pre><p>在它的<a href="https://link.segmentfault.com/?enc=1UAy2DwOgGKoBnQIffitUg%3D%3D.0r6xct%2BF3yUUXFjDcJB1pdSKbLcaX%2F80u8fNYNIQ%2B2o1F7OCDIwZBg9ORkXym94%2BXNieZLNtJvX%2FgXwBvfw%2FU1fbUYof7Xd5a66CU7LqS5F6t%2FjDof0bBLAJAvgBCNJD%2FhN4YuT8nit3cR3WN%2Ff9TvrmQQaEiwDcPviit5c8oyE%3D" rel="nofollow">源码</a>中,我们可以看出,它通过<code>ref</code>方法来处理我们传入的变量</p><pre><code class="js">// ...
const title = ref(newTitle ?? document?.title ?? null)
// ...</code></pre><p>它使用到了JavaScript中的<code>nullish coalescing(空值合并)运算符</code>,其语法为“??”,意思是“如果左侧的值为<code>null</code>或<code>undefined</code>,则使用右侧的值”。在代码中,如果<code>newTitle</code>未定义,则使用<code>document.title</code>,如果<code>document.title</code>也未定义,则使用<code>null</code>。这个运算符可以用于简化代码中的条件判断。</p><p>另外,<code>useTitle</code>的类型定义中还使用到了TypeScript 中的 MaybeRef 类型,它可以是 <code>string</code> 类型或 <code>Ref<string></code> 类型。其中 <code>Ref<T></code> 是一个带有 <code>T</code> 类型值的 <code>ref</code>。</p><pre><code class="ts">type MaybeRef<T> = T | Ref<T></code></pre><h3>举例-useCssVar</h3><p><code>useCssVar</code>允许我们获取到CSS变量的值并在应用程序中使用它。和<code>useTitle</code>不同的是,我们需要传入一个string类型的值,来查询对应DOM上面的CSS变量。通过<code>unref</code>方法,来处理传入的变量 - <code>string</code>类型或者是一个<code>ref</code>:</p><pre><code class="js">// Using a string
const backgroundColor = useCssVar('--background-color');
// Using a ref
const cssVarRef = ref('--background-color');
const backgroundColor = useCssVar(cssVarRef);</code></pre><p>通过查看它的<a href="https://link.segmentfault.com/?enc=HydKvTBqXtHPilG%2F4O6Qig%3D%3D.ZfnBRbVHcYx5CYgVNx%2BBjXHYpUEMHgzhylQZiJXOTL3JB7GovsyazAcungMn2lb1oPulLCo8DG2HXbe1ZYzISoCSGk0Ibmq8SmChw6pL86zEYhRLu8pWfwQq20lE15BC7YcXV0TCNyMt7zDY%2F5EbOLIfGxuXH5jUV6%2FRKye8bbA%3D" rel="nofollow">源码</a>,我们可以看出它使用了<code>unRef</code>方法来处理传入的参数。而且,它还使用了一个辅助函数<code>unrefElement</code>,用于确保获取的是 <code>DOM</code> 元素而不是 <code>Vue</code> 实例。</p><p>在<code>VueUse</code>中很多组合式方法都使用了这个模式,如果你想深入研究的话,选择一个感兴趣的去看它的源码吧。</p><h3>小结</h3><p>本篇我们主要介绍了Vue.js中的可组合函数(composables)的第二个模式。该模式使用<code>ref</code>和<code>unref</code>函数来更灵活地使用参数,使可组合函数能够适应不同的使用情况。还介绍了VueUse库如何实现这种模式,并举例说明了useTitle和useCssVar可组合函数如何使用ref和unref函数。</p><p>下一篇中,我们将会介绍另一种模式,该模式可以根据需要返回单个值或对象,从而使可组合函数更易于使用。该模式可以使可组合函数更简单易用,尤其是在大多数情况下只需要单个值的情况下。</p><pre><code class="js">// Returns a single value
const isDark = useDark();
// Returns an object of values
const {
counter,
pause,
resume,
} = useInterval(1000, { controls: true });</code></pre><hr><p>原文链接: <a href="https://link.segmentfault.com/?enc=94nAyJA1GRqJxvxIuGuHWQ%3D%3D.677Dr7IwhYtR6RtwYsAenkvwguCDH%2BvqNmF1y7vUfoqgbTb%2FRizTlJZxl9cBHsEGqeacq9mxq9c1zi1UcxOz1rE545HpfPS7N1AYdPMpxqc%3D" rel="nofollow"># Coding Better Composables (2/5)</a></p>
浅谈Intl对象(DateTimeFormat、ListFormat、RelativeTimeFormat)
https://segmentfault.com/a/1190000043650494
2023-04-12T10:53:17+08:00
2023-04-12T10:53:17+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<p>在JavaScript中,<code>Intl</code>对象是一个内置对象,它提供了处理国际化(i18n)的API。<code>Intl</code>对象包含了一系列的子对象,其中最常用的三个子对象是:<code>Intl.DateTimeFormat</code>、<code>Intl.ListFormat</code>和<code>Intl.RelativeTimeFormat</code>。下面将分别介绍这三个子对象的作用、使用场景以及使用过程中的注意事项。</p><h2>Intl.DateTimeFormat</h2><p><code>Intl.DateTimeFormat</code>用于格式化日期和时间。它可以根据不同地区的语言和文化习惯来格式化日期和时间,并且支持多种格式。下面是一个使用<code>Intl.DateTimeFormat</code>的示例代码:</p><pre><code class="javascript">const date = new Date();
const options = { year: 'numeric', month: 'long', day: 'numeric' };
const formatter = new Intl.DateTimeFormat('zh-CN', options);
console.log(formatter.format(date)); // 输出:2023年4月12日</code></pre><p>在上面的代码中,我们首先创建了一个<code>Date</code>对象,然后定义了一个<code>options</code>对象,该对象指定了要格式化的日期的具体格式。接着,我们创建了一个<code>Intl.DateTimeFormat</code>对象,并将其传递给指定语言环境('<code>zh-CN</code>'表示中文环境)。最后,我们调用<code>formatter.format()</code>方法来格式化日期,并输出结果。</p><p>需要注意的是,在创建<code>Intl.DateTimeFormat</code>对象时,我们可以传递一个<code>options</code>对象来指定日期的格式。这个<code>options</code>对象可以包含以下属性:</p><ul><li><code>localeMatcher</code>:指定语言环境匹配方式("lookup"或"best fit")。</li><li><code>weekday</code>:指定星期几的格式("narrow"、"short"或"long")。</li><li><code>era</code>:指定年代的格式("narrow"、"short"或"long")。</li><li><code>year</code>:指定年份的格式("numeric"、"2-digit")。</li><li><code>month</code>:指定月份的格式("numeric"、"2-digit"、"narrow"、"short"或"long")。</li><li><code>day</code>:指定日期的格式("numeric"、"2-digit")。</li><li><code>hour</code>:指定小时的格式("numeric"、"2-digit")。</li><li><code>minute</code>:指定分钟的格式("numeric"、"2-digit")。</li><li><code>second</code>:指定秒钟的格式("numeric"、"2-digit")。</li><li><code>timeZoneName</code>:指定时区名称的格式("short"或"long")。</li></ul><h2>Intl.ListFormat</h2><p><code>Intl.ListFormat</code>用于格式化列表。它可以根据不同地区的语言和文化习惯来格式化列表,并且支持多种格式。下面是一个使用<code>Intl.ListFormat</code>的示例代码:</p><pre><code class="javascript">const list = ['苹果', '香蕉', '橙子'];
const formatter = new Intl.ListFormat('zh-CN', { style: 'long', type: 'conjunction' });
console.log(formatter.format(list)); // 输出:苹果、香蕉和橙子</code></pre><p>在上面的代码中,我们首先定义了一个数组list,然后创建了一个<code>Intl.ListFormat</code>对象,并将其传递给指定语言环境('<code>zh-CN</code>'表示中文环境)。接着,我们调用<code>formatter.format()</code>方法来格式化列表,并输出结果。</p><p>需要注意的是,在创建<code>Intl.ListFormat</code>对象时,我们可以传递一个<code>options</code>对象来指定列表的格式。这个<code>options</code>对象可以包含以下属性:</p><ul><li><code>localeMatcher</code>:指定语言环境匹配方式("lookup"或"best fit")。</li><li><code>style</code>:指定列表的样式("long"、"short"或"narrow")。</li><li><code>type</code>:指定列表项之间的连接方式("conjunction"、"disjunction"或"unit")。</li></ul><h2>Intl.RelativeTimeFormat</h2><p><code>Intl.RelativeTimeFormat</code>用于格式化相对时间。它可以根据不同地区的语言和文化习惯来格式化相对时间,并且支持多种格式。下面是一个使用<code>Intl.RelativeTimeFormat</code>的示例代码:</p><pre><code class="javascript">const formatter = new Intl.RelativeTimeFormat('zh-CN', { style: 'long' });
console.log(formatter.format(-1, 'day')); // 输出:1天</code></pre><p>在上面的代码中,我们首先创建了一个<code>Intl.RelativeTimeFormat</code>对象,并将其传递给指定语言环境('<code>zh-CN</code>'表示中文环境)。接着,我们调用<code>formatter.format()</code>方法来格式化相对时间,并输出结果。</p><p>需要注意的是,在调用<code>formatter.format()</code>方法时,我们需要传递两个参数。第一个参数表示相对时间的数值,可以是正数或负数。第二个参数表示相对时间的单位,可以是以下值之一:</p><ul><li>"<code>year</code>":年</li><li>"<code>quarter</code>":季度</li><li>"<code>month</code>":月</li><li>"<code>week</code>":周</li><li>"<code>day</code>":天</li><li>"<code>hour</code>":小时</li><li>"<code>minute</code>":分钟</li><li>"<code>second</code>":秒</li></ul><p>在创建<code>Intl.RelativeTimeFormat</code>对象时,我们可以传递一个<code>options</code>对象来指定相对时间的格式。这个<code>options</code>对象可以包含以下属性:</p><ul><li><code>localeMatcher</code>:指定语言环境匹配方式("lookup"或"best fit")。</li><li><code>numeric</code>:指定相对时间的数值格式("always"或"auto")。</li><li><code>style</code>:指定相对时间的样式("long"、"short"或"narrow")。</li></ul><h2>小结</h2><p>以上就是<code>Intl</code>对象的三个子对象的作用、使用场景以及使用过程中的注意事项的介绍。通过使用这三个子对象,我们可以更方便地处理国际化的问题。</p><p>参考资料:<br><a href="https://link.segmentfault.com/?enc=FSWsyN5VSP2IvV3ThFThrw%3D%3D.6RNG4pkS%2F70u3iSA%2BPzH%2BJlHsOyf53H1F%2FaGKlDEP9GMuU2ClAxRc52kG7Dn8PA%2FBJXkK1u6KF7vB0KhmSaezr5TRUoVW0BF2hwROBeZNcjA%2B7Nhctqm%2B02MxqNQu5Z6" rel="nofollow">MDN-Intl对象</a></p>
浅谈Intl.NumberFormat
https://segmentfault.com/a/1190000043627884
2023-04-06T09:49:24+08:00
2023-04-06T09:49:24+08:00
前端荣耀
https://segmentfault.com/u/lengxing
5
<p>很高兴您对 <code>Intl.NumberFormat</code> 感兴趣。<code>Intl.NumberFormat</code> 是一个内置于 <code>JavaScript</code> 的国际化 <code>API</code>,它提供了一种简单的方法来格式化数字,以便在不同语言环境下进行显示。在本篇博客中,我们将详细介绍 <code>Intl.NumberFormat</code> 的使用方法和使用场景,并配以示例代码作为使用说明。</p><h2>什么是 Intl.NumberFormat?</h2><p><code>Intl.NumberFormat</code> 是一个用于格式化数字的 <code>JavaScript</code> 国际化 <code>API</code>。它使开发人员可以根据用户的语言环境和地区设置自定义数字格式。这意味着您可以使用该 <code>API</code> 来格式化数字,以便在不同的语言和地区中进行显示。</p><h2>如何使用 Intl.NumberFormat?</h2><p>使用 <code>Intl.NumberFormat</code> 非常简单。以下是一个基本的使用示例,用于格式化一个数字:</p><pre><code class="javascript">const number = 123456.789
const formatter = new Intl.NumberFormat()
console.log(formatter.format(number))</code></pre><p>在上面的示例中,我们首先定义了一个数字变量<code>number</code>,然后创建了一个<code>Intl.NumberFormat</code>实例,并将其存储在变量<code>formatter</code>中。最后,我们使用<code>formatter.format()</code>方法来格式化数字,并将结果打印到控制台中。</p><p>输出结果为:</p><pre><code>123,456.789</code></pre><p>在上面的示例中,我们没有提供任何参数来创建<code>Intl.NumberFormat</code>实例。这意味着它将使用默认设置来格式化数字。但是,您可以使用以下参数来创建自定义格式:</p><ul><li><code>locale</code>:用于格式化数字的语言环境。默认值为当前用户的语言环境。</li><li><code>style</code>:数字格式的样式。可以是<code>decimal</code>(十进制)、<code>currency</code>(货币)或<code>percent</code>(百分比)。默认值为<code>decimal</code>。</li><li><code>currency</code>:如果样式为<code>currency</code>,则使用的货币代码。默认值为当前用户的货币代码。</li><li><code>currencyDisplay</code>:如果样式为<code>currency</code>,则货币符号的显示位置。可以是<code>symbol</code>(符号)、<code>code</code>(代码)或<code>name</code>(名称)。默认值为<code>symbol</code>。</li><li><code>minimumIntegerDigits</code>:数字的最小整数位数。默认值为 1。</li><li><code>minimumFractionDigits</code>:数字的最小小数位数。默认值为 0。</li><li><code>maximumFractionDigits</code>:数字的最大小数位数。默认值为 3。</li><li><code>minimumSignificantDigits</code>:数字的最小有效数字位数。默认值为 1。</li><li><code>maximumSignificantDigits</code>:数字的最大有效数字位数。默认值为 21。</li></ul><p>以下是一个示例,展示如何使用这些参数来创建自定义数字格式:</p><pre><code class="javascript">const number = 123456.789
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>$123,456.79</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将样式设置为<code>currency</code>。我们还指定了货币代码为<code>USD</code>,最小小数位数为 2,最大小数位数为 2。这使得输出结果为美元货币格式。</p><h2>使用场景</h2><p><code>Intl.NumberFormat</code> 可用于任何需要格式化数字的场景:</p><p><code>Intl.NumberFormat</code> 支持的类型单位指的是 <code>Intl.NumberFormat</code> 对象支持的数字格式化类型。以下是每种类型的中文解释和代码示例:</p><h3>1. 货币格式化</h3><p>货币格式化是将数字格式化为特定货币的格式。使用 <code>Intl.NumberFormat</code>,您可以轻松地将数字格式化为任何货币,并在不同的语言环境中进行显示。例如,以下代码将格式化数字为美元货币格式:</p><pre><code class="javascript">const number = 1234.56
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>$1,234.56</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将样式设置为<code>currency</code>。我们还指定了货币代码为<code>USD</code>。这使得输出结果为美元货币格式。</p><h3>2. 小数格式化</h3><p>小数格式化是将数字格式化为特定小数位数的格式。使用 <code>Intl.NumberFormat</code>,您可以轻松地将数字格式化为任何小数位数,并在不同的语言环境中进行显示。例如,以下代码将格式化数字为只有两个小数位:</p><pre><code class="javascript">const number = 1234.567
const formatter = new Intl.NumberFormat('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>1,234.57</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将最小小数位数和最大小数位数都设置为 2。这使得输出结果只有两个小数位。</p><h3>3. 整数格式化</h3><p>整数格式化是将数字格式化为特定整数位数的格式。使用 <code>Intl.NumberFormat</code>,您可以轻松地将数字格式化为任何整数位数,并在不同的语言环境中进行显示。例如,以下代码将格式化数字为只有四个整数位:</p><pre><code class="javascript">const number = 12345.6789
const formatter = new Intl.NumberFormat('en-US', {
minimumIntegerDigits: 4,
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>12,346</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将最小整数位数设置为 4。这使得输出结果只有四个整数位。</p><h3>4. 百分比格式化</h3><p>百分比格式化是将数字格式化为百分比格式的格式。使用 <code>Intl.NumberFormat</code>,您可以轻松地将数字格式化为任何百分比格式,并在不同的语言环境中进行显示。例如,以下代码将格式化数字为百分比格式:</p><pre><code class="javascript">const number = 0.75
const formatter = new Intl.NumberFormat('en-US', {
style: 'percent',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>75.00%</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将样式设置为<code>percent</code>。我们还指定了最小小数位数为 2,最大小数位数为 2。这使得输出结果为百分比格式。</p><h3>5. 计数格式化</h3><p>计数格式化是将数字格式化为计数形式的格式。使用 <code>Intl.NumberFormat</code>,您可以轻松地将数字格式化为任何计数形式的格式,并在不同的语言环境中进行显示。例如,以下代码将格式化数字为计数形式的格式:</p><pre><code class="javascript">const number = 12345
const formatter = new Intl.NumberFormat('en-US', {
notation: 'compact',
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>12K</code></pre><p>在上面的示例中,我们使用了<code>en-US</code>作为语言环境,并将</p><h3>6. 多语言支持</h3><p><code>Intl.NumberFormat</code> 还支持多语言。使用该 <code>API</code>,您可以根据用户的语言环境设置数字格式。例如,以下代码将格式化数字为德语货币格式:</p><pre><code class="javascript">const number = 123456.789
const formatter = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
})
console.log(formatter.format(number))</code></pre><p>输出结果为:</p><pre><code>123.456,79 €</code></pre><p>在上面的示例中,我们使用了<code>de-DE</code>作为语言环境,并将样式设置为<code>currency</code>。我们还指定了货币代码为<code>EUR</code>。这使得输出结果为德语货币格式。</p><h2>总结</h2><p><code>Intl.NumberFormat</code> 是一个非常有用的 <code>JavaScript</code> 国际化 <code>API</code>,用于格式化数字以在不同的语言和地区中进行显示。使用该 <code>API</code>,您可以轻松地创建自定义数字格式,并将其应用于任何需要格式化数字的场景。希望本篇博客能够帮助您了解 <code>Intl.NumberFormat</code> 的使用方法和使用场景。</p><hr><p>参考资料:</p><p><a href="https://link.segmentfault.com/?enc=4DQFELyd%2BfZRZ1kGd48Wjg%3D%3D.re%2FBzeRtEUKDZQfntn9t8Ex7AxdEec%2FQ4NYlgy9Ek7nlNayGbAvR%2F2ZdWAOoHsoZeqI%2BVa%2FMaeX7fFUc88Iu6T2c4YvuS7nJQSsZRO6j02xPS79qJgKk1J%2FbdHdpnyzcG4fNFFC%2BsL9FWCjtgbWNaw%3D%3D" rel="nofollow">Intl.NumberFormat MDN</a></p>
前端依赖管理那点事儿
https://segmentfault.com/a/1190000043579669
2023-03-24T11:38:57+08:00
2023-03-24T11:38:57+08:00
前端荣耀
https://segmentfault.com/u/lengxing
4
<h2>主要内容</h2><ul><li>📝 <strong>什么是依赖</strong></li><li>📤 <strong>依赖从哪来</strong></li><li>🤹 <strong>安装到哪儿</strong></li><li>🎥 <strong>版本控制</strong></li><li>🧑💻 <strong>哪些需要装,哪些不需要</strong></li><li>🎨 <strong>package-lock.json</strong></li><li>🛠 <strong>npm install 过程回顾</strong></li></ul><h2>什么是依赖</h2><p>有时候,依赖是一堆 <strong>可执行的代码</strong> ;<br>有时候,依赖只是 <strong>一句声明</strong>。</p><p>1.当我们的业务逻辑中使用到 <code>Vue</code> 时,我们只需要依赖(引入)它,我们就可以使用它的能力</p><pre><code class="js">import vue from 'vue'
or
<script src="https://unpkg.com/vue@next"></script></code></pre><p>此时,<strong>依赖就是获取一堆可用的代码</strong></p><p>2.当我们在开发组件库时,我们需要使用到 <code>Vue</code> 中的一些方法</p><pre><code class="js">import { ref } from 'vue'</code></pre><p>在这种场景下,组件所使用的 <code>vue.js</code>,实际上是宿主环境所依赖的 <code>Vue</code>。</p><p>此时,<strong>依赖仅仅是一个声明</strong></p><h2>依赖从哪来</h2><h3>获取依赖的来源有哪些</h3><ul><li>静态文件打包</li><li>CDN 引入</li><li>仓库级引用</li><li>从 npm 源安装</li></ul><h3>静态文件打包</h3><p>前端资源通过相对路径进行引入,整体打包到项目中。</p><h3>CDN 引入</h3><p>优点:</p><ul><li>多域复用</li><li>就近传输</li><li>通过跨域达到 <strong>突破浏览器并发限制</strong> 的效果</li><li>……</li></ul><p>缺点:</p><p>引入公共 <code>CDN</code> 资源,需要评估风险,避免 <code>CDN</code> 域名污染后,资源异常</p><h3>仓库级引用</h3><pre><code class="js">git submodule</code></pre><p><img src="/img/remote/1460000043579692" alt="" title=""></p><p>文章参考:<a href="https://link.segmentfault.com/?enc=YLEMww8%2Bl1eIcy3WCmslZw%3D%3D.JolFOaVkyPBkg4P7nxIQDiUNrYWzvumGT%2Fz404O2RzOta5RGdQkvQZ53Emz0dc9i" rel="nofollow">《Git 中 submodule 的使用》</a></p><h3>从 npm 源安装</h3><p><img src="/img/remote/1460000043579671" alt="npm源" title="npm源"></p><p>1.公网 <code>npm registry</code></p><pre><code class="js">npm install vue</code></pre><p>2.私有 <code>npm registry</code></p><pre><code class="js">npm install @xxfe/babel-plugin-icover --registry=http://ires.xxcorp.com/repository/xxnpm/</code></pre><p><code>.npmrc</code> 指定仓库源地址</p><pre><code class="js">registry=http://ires.xxcorp.com/repository/xxnpm/
or
// 特定命名空间下
@xxfe:registry=http://ires.xxcorp.com/repository/xxnpm/</code></pre><p>3.指定 <code>git</code> 仓库</p><pre><code class="json">{
"name": "foo",
"version": "0.0.0",
"dependencies": {
"express": "git+ssh://git@github.com:npm/cli.git#v1.0.27"
}
}</code></pre><p>通过指定 <code>协议</code>、<code>仓库地址</code> 以及 <code>tag/commit-hash</code> 等信息,可以精准指定到某个版本的代码</p><p>文档参考:<a href="https://link.segmentfault.com/?enc=4AOq%2FwOQNHNGA%2BW00CSB3g%3D%3D.FTuvFFm9Jaw8Y4W5ot1BjtN7r5bLSE6jBRB1xBP0mSa2R2buj5Ge0qeGcVwXM1RBoQzkXoeXmjO%2FDfvW9fHihBExMyRWEuctbQPgUOabZVdmmN6OtUkxvlDxRV6DsBbX" rel="nofollow">《npm docs》</a></p><p>4.post-install 玩法</p><p>从命名上能够看出,<code>post-install</code> 的意思是指 <code>install</code> 之后之后会主动触发的一个钩子。<br>通过在这个钩子内执行脚本,你可以去下载任何你想要的内容,包括但不限于:<code>.exe</code>、<code>.avi</code>、<code>.pdf</code> 等等...</p><p><code>npm install</code> 执行的过程会经历三个勾子方法:</p><ul><li>preinstall</li><li>install</li><li>postinstall</li></ul><p><code>npm install</code> 命令发起后,根据工程定义决定是否执行 <code>preinstall</code>,<br><code>install</code>、<code>postinstall</code> 是 <code>npm install</code> 命令必然会执行的阶段</p><p>文档参考:</p><ul><li><a href="https://link.segmentfault.com/?enc=luucryYenNvzALlL917rgQ%3D%3D.xjokQvTVNn%2FYXb3mVkupBBFxLvKqGGKJ0VR1XWQwNrvzSlLJ91JEgtk8%2FdErsZCQ" rel="nofollow">《npm docs》</a></li><li><a href="https://link.segmentfault.com/?enc=Tt9QeP8wqjxAsk%2F2g1PnIQ%3D%3D.gcQl1QL0FYiaA1gv%2BcKa%2FtdSujIZhUJ2d3vapIhpk9YD10dsmVT1f51%2Fc0ybpdNn" rel="nofollow">《滥用 npm 库导致数据暗渡》</a></li></ul><h2>安装到哪儿</h2><blockquote>node_modules ? 当然是对的!但是并不准确。</blockquote><h3>依赖地狱</h3><p>说到 node_modules,总是离不开要看看它的<code>依赖地狱图</code></p><p><img src="/img/remote/1460000043579693" alt="" title=""></p><p>我们分别以 <code>React</code> 和 <code>Vue</code> 为例,单独安装和通过 <code>cli</code> 工具进行安装,<code>node_modules</code>的安装结果:</p><ul><li>单独安装 <code>React</code> 和 <code>ReactDOM</code>,占用 <code>5.2M</code> 空间;</li><li>单独安装 <code>Vue(3.x)</code>,占用 <code>16.3M</code> 空间;</li><li>使用 <code>create-react-app</code> 创建一个空白 <code>React</code> 项目,占用 <code>344.8M</code> 空间;</li><li>使用 <code>vue-cli</code> 创建一个空白 <code>Vue</code> 项目,占用 <code>163.9M</code> 空间。</li></ul><h3>node_modules 层级</h3><h4>npm2.x 版本 node_modules 层级 - 递归式</h4><blockquote>先定义一种语法 <code>A{B,C}</code> 代表 <code>A</code> 包依赖了 <code>B</code> 包和 <code>C</code> 包</blockquote><p>A{D@1.0.0}, B{D@2.0.0}, C{D@1.0.0}</p><pre><code>├── node_modules
│ ├── A@1.0.0
│ │ └── node_modules
│ │ │ └── D@1.0.0
│ ├── B@1.0.0
│ │ └── node_modules
│ │ │ └── D@2.0.0
│ └── C@1.0.0
│ │ └── node_modules
│ │ │ └── D@1.0.0</code></pre><p><img src="/img/remote/1460000043579694" alt="" title=""></p><p>可以想象,这样做的确能尽量保证每个模块自身的可用性。但是,当项目规模达到一定程度时,也会造成许多问题:</p><ul><li>依赖树的层级非常深。如果需要定位某依赖的依赖,很难找到该依赖的文件所在(例如,如果想定位模块 <code>D</code>,就不得不先知道他在依赖树中的位置);</li><li>不同的依赖树分支里,可能有大量实际上是同样版本的依赖(例如,<code>D@1.0.0</code> 在 <code>A</code> 和 <code>C</code> 下版本是一致的);</li><li>安装时额外下载或拷贝了大量重复的资源,并且实际上也占用了大量的硬盘空间资源等(例如,<code>D</code> 模块在依赖目录中出现了三次);</li><li>安装速度慢,甚至因为目录层级太深导致文件路径太长的缘故,在 <code>windows</code> 系统下删除 <code>node_modules</code> 文件夹也可能失败!</li></ul><p>这可谓:<strong>子又生孙,孙又生子,子子孙孙无穷尽也......</strong></p><h4>npm3.x 版本 node_modules 层级 - 扁平式</h4><p>在 <code>npm3.x</code> 版本后,<code>npm</code> 采用了更合理的方式去解决依赖地狱问题。<code>npm3.x</code> 尝试把依赖以及依赖的依赖都尽量的平铺在项目根目录下的 <code>node_modules</code> 文件夹下以<strong>共享使用</strong>;如果遇到因为需要的版本要求不一致导致冲突,没办法放在平铺目录下的,回退到 <code>npm2.x</code> 的处理方式,在该模块下的 <code>node_modules</code> 里存放冲突的模块。</p><p>A{D@1.0.0}, B{D@1.0.0}, C{D@2.0.0}</p><pre><code>├── node_modules
│ ├── A@1.0.0
│ ├── B@1.0.0
│ │── C@1.0.0
│ │ └── node_modules
│ │ │ └── D@2.0.0
│ ├── D@1.0.0</code></pre><p><img src="/img/remote/1460000043579695" alt="" title=""></p><p>幽灵依赖问题</p><p>A{D@2.0.0},B</p><pre><code class="js">├── node_modules
│ ├── A@1.0.0
│ ├── B@1.0.0
│ ├── D@2.0.0</code></pre><pre><code class="js">const A = require('A');
const D = require('D'); ???</code></pre><p><li>依赖兼容问题 - 版本不兼容</li><br><li>依赖缺失问题 - 缺失报错</li></p><h5>不确定性问题</h5><p>A@1.0.0{C@1.0.0},B@1.0.0{C@2.0.0}</p><pre><code class="js">node_modules
├── A@1.0.0
├── B@1.0.0
│ └── node_modules
│ └── C@2.0.0
├── C@1.0.0
node_modules
├── A@1.0.0
│ └── node_modules
│ └── C@1.0.0
├── B@1.0.0
├── C@2.0.0
</code></pre><h5>依赖分身/多重依赖问题</h5><p>A{B@1.0.0}, C{B@2.0.0}, D{B@1.0.0}, E{B@2.0.0}</p><pre><code class="js">node_modules
├── A@1.0.0
├── B@1.0.0
├── D@1.0.0
├── C@1.0.0
│ └── node_modules
│ └── B@2.0.0
└── E@1.0.0
└── node_modules
└── B@2.0.0</code></pre><h2>版本控制</h2><ul><li>^1.1.0 和 ~1.1.0 的区别是什么?</li><li>1.01.02 是否合法?</li><li>1.0.1、1.0.1-alpha.2 、1.0.1-rc.2 这三个版本号由大到小的顺序是什么?</li><li>vue@latest 应该命中哪个版本?由谁决定?那么 vue@v2-beta 呢?</li></ul><blockquote>在软件管理领域里存在着被称作“依赖地狱”的死亡之谷。</blockquote><p>系统规模越大,加入的包越多,你就越有可能在未来的某一天发现自己已经深陷绝望之中。</p><p>在依赖高的系统中发布新版本包可能很快会成为噩梦。如果依赖关系过高,可能面临版本控制被锁死的风险(必须对每一个依赖包改版才能完成某次升级)。如果依赖关系过于松散,又将无法避免版本的混乱(假设兼容于未来的多个版本已超出了合理的数量)。</p><p>当你项目的进展因为版本依赖被锁死或版本混乱变得不够简便和可靠,就意味着你正处于依赖地狱之中。</p><p>这就是为什么需要使用<a style="color: red;font-weight: 500;" href="https://semver.org/lang/zh-CN">语义化版本</a>的原因</p><h3>SemVer</h3><p>语义化的版本控制(Semantic Versioning),简称语义化版本,英文缩写为 SemVer。</p><p>语义化版本通过一组简单的规则及条件来约束版本号的配置和增长。这些规则是根据(但不局限于)已经被各种封闭、开发源码软件所广泛使用的惯例所设计。</p><h4>格式</h4><p>语义化版本格式:主版本号.次版本号.修订号(MAJOR.MINOR.PATCH)。</p><p>版本号递增规则如下:</p><ul><li><strong>主版本号(MAJOR)</strong>:通常只有在重构、API 不向下兼容时才会进行升级;</li><li><strong>次版本号(MINOR)</strong>:通常在增加向下兼容新特性时升级此版本号;</li><li><strong>修订号(PATCH)</strong>:通常在发布向下兼容的问题修复时更新</li></ul><p>先行版本号及版本编译信息可以加到 <strong>“主版本号.次版本号.修订号”</strong> 的后面,作为延伸。</p><p><img src="/img/remote/1460000043579696" alt="" title=""></p><h5>先行版本号</h5><p>Snapshot:快照,也被称为开发版,处于开发阶段。这个版本的代码禁止用于生产环境。</p><p>Alpha (α):内测版,内部交流或专业测试人员测试使用。</p><p>Preview:预览版,与 Alpha 类似,有时还会细分 M1,M2 版本。</p><p>Beta (β):公测版,专业爱好者大规模测试使用,存在一些 Bug,不适合一般用户使用。</p><p>Gamma (λ):比较成熟的测试版。</p><p>RC (Release Candidate):候选版本,处于 Gamma 阶段,该版本已经完成了全部功能并清除了大量的 Bug。<br>到了这个阶段,只会修复 Bug,不会对软件做任何大的更改。</p><p>一般来说,Alpha -> Beta -> Gamma 是迭代的关系,RC1 -> RC2 是取舍的关系。</p><p>Release:发行版本,正式发行的版本,已经经过测试,一般不会出现严重的 Bug,适合一般用户使用。<br>对于不开源的软件, Release 可能是带有免费使用时间限制的版本。</p><p>Stable:稳定版,同 Release 版本。</p><h4>版本匹配策略</h4><blockquote>我们会发现安装的依赖版本会出现: ^1.1.0 或 ~1.1.0,这是什么意思呢?</blockquote><h6>模糊匹配策略</h6><ul><li><strong>^1.0.1、1、1.x</strong> 代表了可以命中主版本一致、但更新的版本号。</li><li><strong>~1.0.1、1.1、1.1.x</strong> 代表了可以命中主版本、次版本一致、但更新的版本号。</li><li><strong>* 和 x</strong> 可以命中一切新发布的版本号。</li></ul><h5>dist-tag 和版本号</h5><pre><code class="js">npm install vue@latest
# 或者换一句
npm install vue@next</code></pre><p>npm 指令:<strong>dist-tag</strong></p><pre><code class="js">npm dist-tag add <pkg>@<version> [<tag>]
//发布
npm publish --tag beta</code></pre><p><code>beta、latest、next、preview、legacy、v2-beta</code> ……</p><hr><h2>哪些需要装,哪些不需要</h2><blockquote>Q:你能一口气说清楚项目里 node_modules 里的那些依赖都是怎么来的吗?为什么下载了它们,以及为什么只下载了它们?</blockquote><p><img src="/img/remote/1460000043579697" alt="" title=""></p><h4>package.json 中的各种 dependencies</h4><ul><li>dependencies(应用依赖,或业务依赖)</li><li>devDependencies(开发环境依赖)</li><li>peerDependencies(同等依赖,或同伴依赖)- 指定当前包兼容的宿主版本,如 gulp 插件</li><li>optionalDependencies(可选依赖)- 不阻断整体流程</li><li>bundledDependencies(打包依赖)- 包含依赖包名的数组对象,发布包时会打到最终的发布包里面</li></ul><h5>dependencies VS devDependencies</h5><p><img src="/img/remote/1460000043579698" alt="" title=""></p><p>看一下上面的这个依赖关系图,你能说出哪些会被安装到 node_modules 么?</p><p><br><br>答案是:B、C、D、F</p><p><img src="/img/remote/1460000043579699" alt="" title=""></p><p>dependencies 和 devDependencies 的影响不是直接的,而是跨代的! <a href="https://link.segmentfault.com/?enc=JwxyKQ58DI5p9LIj2Xxhxw%3D%3D.BJABQQUuEnKlMk1VGMqnoiQEV%2BPQ42V4xdhMB4XfJpD76Vwc4LrixbC8Pm0WCgWK" rel="nofollow">参考文章</a></p><h2>package-lock.json</h2><blockquote>已经有了 package.json, 为什么会有 package-lock.json 文件呢?</blockquote><p>项目依赖A@1.0.0,A 依赖了B@1.3.2和C@2.0.3</p><pre><code class="json">{
"dependencies": {
"A": "^1.0.0"
}
}</code></pre><ul><li>依赖 <code>A</code> 安装时下载了最新版,如 <code>1.2.3</code>,出现了兼容问题,项目出现 bug;</li><li>依赖 <code>A</code> 所依赖的 <code>B</code> 和 <code>C</code> 下载了别的版本,导致 <code>A</code> 出现问题,从而项目出现问题</li></ul><p><strong>作用</strong>:锁定安装时的包的版本号及包的依赖的版本号, 以保证其他所有人人在使用 npm install 时下载的依赖包都是一致的</p><h5>关于 package.json 和 package-lock.json 的几个小结论:</h5><ul><li>package.json 用于告诉 npm 项目运行需要哪些包, 但包的最终安装的版本不能够只依靠这个文件进行识别, 还需以 package-lock.json 为准</li><li><strong>package.json 中修改版本号会影响 package-lock.json, 并且 package.json 比 package.lock.json 的优先级高</strong></li><li>为了保证该项目的环境依赖一致, 在项目移动时需要同时复制 package.json 和 package.lock.json 两个文件</li><li><strong>不要轻易动 package.json 与 package-lock.json</strong></li></ul><p><strong>删除重装一时爽,版本不对火葬场!!!</strong></p><h2>npm install 过程回顾</h2><p>最后,咱们来看一下 <code>npm install</code> 的执行过程,来加深一下依赖管理的具体使用场景。</p><p><img src="/img/remote/1460000043579700" alt="" title=""></p><hr><p>内容收录于<a href="https://link.segmentfault.com/?enc=feRDfy3eYYY4S2cTtbunlQ%3D%3D.pakOQ8WdeHCiDQkTQ%2B4kNzGtctanMI4AG0sn7zuL3yvoX8l1dcLorrNe4nlJj5Je" rel="nofollow">github 仓库</a></p><p>参考资料</p><ul><li><a href="https://link.segmentfault.com/?enc=BaEcAHQexsq1GkhLyepi8g%3D%3D.EmxgEVDMDROyywpiKieD04PvOZKqOjF22hfvWhPZU0bTsiaZVwqCgisJ6SoPDbiL" rel="nofollow">前端工程化 - 剖析 npm 的包管理机制</a></li><li><a href="https://link.segmentfault.com/?enc=iwM2AH1WFlkAUGDB8mKpWQ%3D%3D.ErVFeXxByRoKNNplAwgpkGkgd01kBPLOyHCIrHeGoqkt7hTJFGY1E2AqCXRDocTW" rel="nofollow">拿来吧您!把“前端依赖”纳入知识体系</a></li><li><a href="https://link.segmentfault.com/?enc=Ch1UUqALTfk7PPTO4VupMw%3D%3D.ugh8C7tV4WL6zOY%2BICaxbdgwL9BO2fSrHePtq%2FhA0SjUAw4nZrMZW759GeiOIGfE3QYiJMCftxyVNukAHXGuXw%3D%3D" rel="nofollow">npm -- install 安装流程</a></li><li><a href="https://link.segmentfault.com/?enc=bBdkZ1WGxkh1psX3S%2BHVJA%3D%3D.Y%2F5C1%2FvlDgifxDx%2FJVbZU9SHLLEcdCOxg%2FatP4GtbcevcdfwlaSFW2EYJE2ZYNnc" rel="nofollow">详解 package-lock.json 的作用</a></li><li><a href="https://link.segmentfault.com/?enc=x3MZdajLQGp9FuqC6qq6Sw%3D%3D.HrAnMpzyyquSNAVf7MXKkx6T42d3r%2FdF%2FQC9Il%2FyxQU%3D" rel="nofollow">版本校验工具</a></li></ul>
Vue 3中依赖注入与组件定义相关的那点事儿
https://segmentfault.com/a/1190000043552992
2023-03-17T17:42:51+08:00
2023-03-17T17:42:51+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<p>让我们聊聊 <code>Vue 3</code>中依赖注入与组件定义相关的那点事儿。</p><h2>主要内容</h2><p>本次分享,我们主要涉及以下内容:</p><ul><li>📝 <strong>provide() & inject()</strong> - 依赖注入</li><li>🛠 <strong>nextTick()</strong> - 下一个 DOM 更新周期后</li><li><p>🎨 <strong>组件定义</strong></p><ul><li><strong>defineComponent()</strong> - 组件定义类型推导辅助函数</li><li><strong>defineAsyncComponent()</strong> - 异步组件</li><li><strong>defineCustomElement()</strong> - 原生自定义元素类的构造器</li></ul></li></ul><h2>provide() & inject()</h2><h3>provide()</h3><p>提供一个值,可以被后代组件注入。</p><pre><code class="ts">function provide<T>(key: InjectionKey<T> | string, value: T): void</code></pre><p>接收两个参数:</p><ul><li>要注入的 <code>key</code>,字符串或者 <code>Symbol</code>;</li></ul><pre><code class="ts">export interface InjectionKey<T> extends Symbol {}</code></pre><ul><li>对应注入的值</li></ul><p>与注册生命周期钩子的 <code>API</code> 类似,<code>provide()</code> 必须在组件的 <code>setup()</code> 阶段同步调用。</p><h3>inject()</h3><p>注入一个由祖先组件或整个应用 (通过 <code>app.provide()</code>) 提供的值。</p><pre><code class="ts">// 没有默认值
function inject<T>(key: InjectionKey<T> | string): T | undefined
// 带有默认值
function inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
// 使用工厂函数
function inject<T>(
key: InjectionKey<T> | string,
defaultValue: () => T,
treatDefaultAsFactory: true
): T</code></pre><ul><li>第一个参数是注入的 <code>key</code>。<code>Vue</code> 会遍历父组件链,通过匹配 <code>key</code> 来确定所提供的值。如果父组件链上多个组件对同一个 <code>key</code> 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 <code>key</code> 匹配到值,<code>inject()</code> 将返回 <code>undefined</code>,除非提供了一个默认值。</li><li>第二个参数是可选的,即在没有匹配到 <code>key</code> 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 <code>false</code> 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。</li></ul><h3>provide() & inject() - 官方示例</h3><pre><code class="ts">// provide
<script setup>
import {(ref, provide)} from 'vue' import {fooSymbol} from
'./injectionSymbols' // 提供静态值 provide('foo', 'bar') // 提供响应式的值
const count = ref(0) provide('count', count) // 提供时将 Symbol 作为 key
provide(fooSymbol, count)
</script></code></pre><pre><code class="ts">// inject
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 注入值的默认方式
const foo = inject('foo')
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入
const foo2 = inject(fooSymbol)
// 注入一个值,若为空则使用提供的默认值
const bar = inject('foo', 'default value')
// 注入一个值,若为空则使用提供的工厂函数
const baz = inject('foo', () => new Map())
// 注入时为了表明提供的默认值是个函数,需要传入第三个参数
const fn = inject('function', () => {}, false)
</script></code></pre><h3>provide() & inject() - ElementUI Plus 示例 <a href="https://link.segmentfault.com/?enc=x5qx9gZg3SFa%2FqXTci8o3g%3D%3D.s0%2FtmTevFuHUVJ9dBRrseD8v5CQGG1mCPAEZmGmPlQJkwclk%2Ft2NupYeG5AJr8PcrlUeIYeinGz2KxjmhqdoPomxlL%2FHP8zp1oYR%2FO%2BrEqmermhSlix7RQtA%2FeOAaLBgaP7mLLa64FX0XHe6jsIQrspfeIiZDM%2FL7GTe2YhqJJHUb5XemAiAWp0UC%2Bjycnrk" rel="nofollow">Breadcrumb 组件</a></h3><pre><code class="ts"><script lang="ts" setup>
import { onMounted, provide, ref } from 'vue'
import { useNamespace } from '@element-plus/hooks'
import { breadcrumbKey } from './constants'
import { breadcrumbProps } from './breadcrumb'
defineOptions({
name: 'ElBreadcrumb',
})
const props = defineProps(breadcrumbProps)
const ns = useNamespace('breadcrumb')
const breadcrumb = ref<HTMLDivElement>()
// 提供值
provide(breadcrumbKey, props)
onMounted(() => {
......
})
</script></code></pre><pre><code class="ts"><script lang="ts" setup>
import { getCurrentInstance, inject, ref, toRefs } from 'vue'
import ElIcon from '@element-plus/components/icon'
import { useNamespace } from '@element-plus/hooks'
import { breadcrumbKey } from './constants'
import { breadcrumbItemProps } from './breadcrumb-item'
import type { Router } from 'vue-router'
defineOptions({
name: 'ElBreadcrumbItem',
})
const props = defineProps(breadcrumbItemProps)
const instance = getCurrentInstance()!
// 注入值
const breadcrumbContext = inject(breadcrumbKey, undefined)!
const ns = useNamespace('breadcrumb')
......
</script></code></pre><h3>provide() & inject() - VueUse 示例</h3><p><a href="https://link.segmentfault.com/?enc=1nbEe1V3%2BtMcsD3hY1tXMw%3D%3D.grXAKJY8Ay7ddtWO1AUNM9TtlWtEBFru%2BupovqDybi458tlxcyA8NM50mwjzsETnl9hAJ5S9F5ChvUdbYg6oxNcUJC6aPLWtaGab31EPa0IHxpLcGpM4bi6P7Szq%2FeIk70U5Z8ss6rw0P7bHFxGzqXANjRX4LAnBiRpZtHOHAho%3D" rel="nofollow">createInjectionState 源码</a> / <a href="https://link.segmentfault.com/?enc=2tT8MFU8GNyTQWNLh8YoWg%3D%3D.P%2BcOIiXff%2FVCWINxBESUFuogIqL0iduj059F8h3WJbj4wefneY4rzJpsDWSSvHRc" rel="nofollow">createInjectionState 使用</a></p><p><a href="https://link.segmentfault.com/?enc=ezxPvsSicv2awahdOU06%2Bw%3D%3D.Y9d%2FSlJJo2P60QkI1M6ZdJyh18Y2RJG0UvzW7fg6iqdQUc2zYcc2qrwdexe%2B5ImKk2BSqYkQJQrhbl2NoLM5glK3O2E2ZHf8a8cGlD57QkzLd7trR1OaXWkCXSAu1fNfjd%2FTxIDJyJmons%2BdL%2FCDMA3zQOCLdfQpJ1nq%2F5CBEU8%3D" rel="nofollow">package/core/computedInject 源码</a></p><pre><code class="ts">import { type InjectionKey, inject, provide } from 'vue-demi'
/**
* 创建可以注入到组件中的全局状态
*/
export function createInjectionState<Arguments extends Array<any>, Return>(
composable: (...args: Arguments) => Return
): readonly [
useProvidingState: (...args: Arguments) => Return,
useInjectedState: () => Return | undefined
] {
const key: string | InjectionKey<Return> = Symbol('InjectionState')
const useProvidingState = (...args: Arguments) => {
const state = composable(...args)
provide(key, state)
return state
}
const useInjectedState = () => inject(key)
return [useProvidingState, useInjectedState]
}</code></pre><h2>nextTick()</h2><p>等待下一次 DOM 更新刷新的工具方法。</p><pre><code class="ts">function nextTick(callback?: () => void): Promise<void></code></pre><p>说明:当你在 <code>Vue</code> 中更改响应式状态时,最终的 <code>DOM</code> 更新并不是同步生效的,而是由 <code>Vue</code> 将它们缓存在一个队列中,直到下一个<code>“tick”</code>才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。</p><p><code>nextTick()</code> 可以在状态改变后立即使用,以等待 <code>DOM</code> 更新完成。你可以传递一个<strong>回调函数</strong>作为参数,或者 <strong>await 返回的 Promise</strong>。</p><h3>nextTick() 官网示例</h3><pre><code class="ts"><script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
// DOM 还未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此时已经更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template></code></pre><h3>nextTick() - ElementUI Plus 示例</h3><p><a href="https://link.segmentfault.com/?enc=ayhHAP95SHvdd06%2B2ODQjg%3D%3D.iBUDhtA0dTCr6XV4ObQrj1eoFJ14YzxPS%2FyfctOIKcoWxJRiTihR0PRSKlCjFGRXfIQyOajvvMMcXW18mUe4h5P4AFSBwIa8ACXAsHvF%2F9QbjBT7cFbBgWYFpI%2F9hYaDoqJysz0Bgltq9GiPehsqS3if%2Ba5ZAIhzP%2Fd2OAnECp36nljMrTaVoiUbuKqfgPF3" rel="nofollow">ElCascaderPanel 源码</a></p><pre><code class="ts">export default defineComponent({
......
const syncMenuState = (
newCheckedNodes: CascaderNode[],
reserveExpandingState = true
) => {
......
checkedNodes.value = newNodes
nextTick(scrollToExpandingNode)
}
const scrollToExpandingNode = () => {
if (!isClient) return
menuList.value.forEach((menu) => {
const menuElement = menu?.$el
if (menuElement) {
const container = menuElement.querySelector(`.${ns.namespace.value}-scrollbar__wrap`)
const activeNode = menuElement.querySelector(`.${ns.b('node')}.${ns.is('active')}`) ||
menuElement.querySelector(`.${ns.b('node')}.in-active-path`)
scrollIntoView(container, activeNode)
}
})
}
......
})</code></pre><h3>nextTick() - VueUse 示例</h3><p><a href="https://link.segmentfault.com/?enc=uZlb0JUqf3Jpep4fMJ0oTw%3D%3D.XsDFgq5YMu5tx9uSmEJs%2FNR3udprm7G2heZf9pa43GOHbs0QXD1dTPdLuivW%2BVc5C4N8DDH7prFkSQY%2B9YWCNW%2FVqVlua%2Fbk4PWxemmqruwY24H9Z0GbXFAHEZzaKA8lYJF6qB4yzzAMtEzEEaUdXYA8mLgSyxWW%2BWwZ4vKW%2BbM%3D" rel="nofollow">useInfiniteScroll 源码</a></p><pre><code class="ts">export function useInfiniteScroll(
element: MaybeComputedRef<HTMLElement | SVGElement | Window | Document | null | undefined>
......
) {
const state = reactive(......)
watch(
() => state.arrivedState[direction],
async (v) => {
if (v) {
const elem = resolveUnref(element) as Element
......
if (options.preserveScrollPosition && elem) {
nextTick(() => {
elem.scrollTo({
top: elem.scrollHeight - previous.height,
left: elem.scrollWidth - previous.width,
})
})
}
}
}
)
}</code></pre><h4>使用场景:</h4><ol><li>当你需要在修改了某些数据后立即对 <code>DOM</code> 进行操作时,可以使用 <code>nextTick</code> 来确保 <code>DOM</code> 已经更新完毕。例如,在使用 <code>$ref</code> 获取元素时,需要确保元素已经被渲染才能够正确获取。</li><li>在一些复杂页面中,有些组件可能会因为条件渲染或动态数据而频繁地变化。使用 <code>nextTick</code> 可以避免频繁地进行 <code>DOM</code> 操作,从而提高应用程序的性能。</li><li>当需要在模板中访问某些计算属性或者监听器中的值时,也可以使用 <code>nextTick</code> 来确保这些值已经更新完毕。这样可以避免在视图中访问到旧值。</li></ol><p>总之,<code>nextTick</code> 是一个非常有用的 API,可以确保在正确的时机对 <code>DOM</code> 进行操作,避免出现一些不必要的问题,并且可以提高应用程序的性能。</p><h2>defineComponent()</h2><p>在定义 <code>Vue</code> 组件时提供类型推导的辅助函数。</p><pre><code class="ts">function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor</code></pre><p>第一个参数是一个组件选项对象。返回值将是该选项对象本身,因为该函数实际上在运行时没有任何操作,仅用于提供类型推导。</p><p>注意返回值的类型有一点特别:它会是一个构造函数类型,它的实例类型是根据选项推断出的组件实例类型。这是为了能让该返回值在 <code>TSX</code> 中用作标签时提供类型推导支持。</p><pre><code class="ts">const Foo = defineComponent(/* ... */)
// 提取出一个组件的实例类型 (与其选项中的 this 的类型等价)
type FooInstance = InstanceType<typeof Foo></code></pre><p>参考:<a href="https://link.segmentfault.com/?enc=xv2VNjqSXuKIoufU81mxvw%3D%3D.fJuohhZMqS3ZjvzNcfQP3%2F5w8nNZ56hd6hNbmNQv68BYWuTar5xSGR7fC2S%2BRd0GXvhT03GiMxkIBrGXEt%2FI1A%3D%3D" rel="nofollow">Vue3 - defineComponent 解决了什么?</a></p><h3>defineComponent() - ElementUI Plus 示例</h3><p><a href="https://link.segmentfault.com/?enc=sS%2Fgnus%2F00cxxc6mWYHC%2Fg%3D%3D.8Yr%2BUmnI75%2BwCDHYBrnrdGNDNQP4%2B9KyOWNwFj4kd2lBsy7apF5CWcplgsld9zuA%2Bz5Z31ShO8CVyeypXHsZ9aJ%2Bgz4SLQOadX%2F7BqF3I8RVIV6k1DY7kb1PEurXZYLN%2BRt9yYm%2ByWopuK9PBdlztSBV6%2BhnLEJWCx682mr1XUEnYnmuzrHYmtVD73%2FKSKXHJpESA4Of1zLagDm8LX3XqA%3D%3D" rel="nofollow">ConfigProvider 源码</a></p><pre><code class="ts">import { defineComponent, renderSlot, watch } from 'vue'
import { provideGlobalConfig } from './hooks/use-global-config'
import { configProviderProps } from './config-provider-props'
......
const ConfigProvider = defineComponent({
name: 'ElConfigProvider',
props: configProviderProps,
setup(props, { slots }) {
......
},
})
export type ConfigProviderInstance = InstanceType<typeof ConfigProvider>
export default ConfigProvider</code></pre><h3>defineComponent() - Treeshaking</h3><p>因为 <code>defineComponent()</code> 是一个函数调用,所以它可能被某些构建工具认为会产生副作用,如 <code>webpack</code>。即使一个组件从未被使用,也有可能不被 <code>tree-shake</code>。</p><p>为了告诉 <code>webpack</code> 这个函数调用可以被安全地 <code>tree-shake</code>,我们可以在函数调用之前添加一个 <code>/_#**PURE**_/</code> 形式的注释:</p><pre><code class="ts">export default /*#__PURE__*/ defineComponent(/* ... */)</code></pre><p>请注意,如果你的项目中使用的是 <code>Vite</code>,就不需要这么做,因为 <code>Rollup</code> (<code>Vite</code> 底层使用的生产环境打包工具) 可以智能地确定 <code>defineComponent()</code> 实际上并没有副作用,所以无需手动注释。</p><h3>defineComponent() - VueUse 示例</h3><p><a href="https://link.segmentfault.com/?enc=pJ4jbAec2YIZxZNjYU3Ppw%3D%3D.HBAPZ6BlLAz744ltIEeBPIsOxzMR5GHsl%2FXf%2FoWWsTWK9Kd5gr%2FTPJmvQjzh5Qh76tdkl%2BVF0%2FDLNebeGOg%2BYtxaUgQp8ZoqGAdDU3lGQQ3kif2U%2BF8%2FxgZ4jeKmThSctCfg6kVL7mBM6%2BRV3q6Yh674qCgEEkEXxtIIHZKfNKU%3D" rel="nofollow">OnClickOutside 源码</a></p><pre><code>import { defineComponent, h, ref } from 'vue-demi'
import { onClickOutside } from '@vueuse/core'
import type { RenderableComponent } from '../types'
import type { OnClickOutsideOptions } from '.'
export interface OnClickOutsideProps extends RenderableComponent {
options?: OnClickOutsideOptions
}
export const OnClickOutside = /* #__PURE__ */ defineComponent<OnClickOutsideProps>({
name: 'OnClickOutside',
props: ['as', 'options'] as unknown as undefined,
emits: ['trigger'],
setup(props, { slots, emit }) {
... ...
return () => {
if (slots.default)
return h(props.as || 'div', { ref: target }, slots.default())
}
},
})</code></pre><h2>defineAsyncComponent()</h2><p>定义一个异步组件,它在运行时是懒加载的。参数可以是一个异步加载函数,或是对加载行为进行更具体定制的一个选项对象。</p><pre><code class="ts">function defineAsyncComponent(
source: AsyncComponentLoader | AsyncComponentOptions
): Component
type AsyncComponentLoader = () => Promise<Component>
interface AsyncComponentOptions {
loader: AsyncComponentLoader
loadingComponent?: Component
errorComponent?: Component
delay?: number
timeout?: number
suspensible?: boolean
onError?: (
error: Error,
retry: () => void,
fail: () => void,
attempts: number
) => any
}</code></pre><h3>defineAsyncComponent() - 官网示例</h3><pre><code class="vue"><script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
resolve(/* 从服务器获取到的组件 */)
})
})
const AdminPage = defineAsyncComponent(() =>
import('./components/AdminPageComponent.vue')
)
</script>
<template>
<AsyncComp />
<AdminPage />
</template></code></pre><p><code>ES</code> 模块动态导入也会返回一个 <code>Promise</code>,所以多数情况下我们会将它和 <code>defineAsyncComponent</code> 搭配使用。类似 <code>Vite</code> 和 <code>Webpack</code> 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 <code>Vue</code> 单文件组件。</p><h3>defineAsyncComponent() - <a href="https://link.segmentfault.com/?enc=HbeuOXcBrvaHht%2Fw0OAZmw%3D%3D.bZTRJdW0Ch5hMdTm7VeFExpemz2zH9iEWBqsST%2Fko0ygJrsCiyt0Pnl0AMV5pqa7CM10kIgVhqRIlfcH4c9mc4r%2BQ9nNDCm8GCv7SyYnsWYeLAd8icAnO1cWpkZY37yR%2F2dQ2%2Bi2vcMyteQntQIkMB6LLpniHSeOG93qieK%2B9veRYR2TbOGvqeOCdKRQK%2Bss" rel="nofollow">VitePress 示例</a></h3><pre><code class="ts"><script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import type { DefaultTheme } from 'vitepress/theme'
defineProps<{ carbonAds: DefaultTheme.CarbonAdsOptions }>()
const VPCarbonAds = __CARBON__
? defineAsyncComponent(() => import('./VPCarbonAds.vue'))
: () => null
</script>
<template>
<div class="VPDocAsideCarbonAds">
<VPCarbonAds :carbon-ads="carbonAds" />
</div>
</template></code></pre><p><code>defineAsyncComponent()</code>使用场景:</p><ol><li>当你需要异步加载某些组件时,可以使用 <code>defineAsyncComponent</code> 来进行组件懒加载,这样可以提高应用程序的性能。</li><li>在一些复杂页面中,有些组件可能只有在用户执行特定操作或进入特定页面时才会被使用到。使用 <code>defineAsyncComponent</code> 可以降低初始页面加载时的资源开销。</li><li>当你需要动态地加载某些组件时,也可以使用 <code>defineAsyncComponent</code>。例如,在路由中根据不同的路径加载不同的组件。</li></ol><p>除 <code>Vue3</code> 之外,许多基于 <code>Vue 3</code> 的库和框架也开始使用 <code>defineAsyncComponent</code> 来实现组件的异步加载。例如:</p><ul><li><strong>VitePress</strong>: <code>Vite</code> 的官方文档工具,使用 <code>defineAsyncComponent</code> 来实现文档页面的异步加载。</li><li><strong>Nuxt.js</strong>: 基于 Vue.js 的静态网站生成器,从版本 2.15 开始支持 <code>defineAsyncComponent</code>。</li><li><strong>Quasar Framework</strong>: 基于 <code>Vue.js</code> 的 UI 框架,从版本 2.0 开始支持 <code>defineAsyncComponent</code>。</li><li><strong>Element UI Plus</strong>: 基于 <code>Vue 3</code> 的 UI 库,使用 <code>defineAsyncComponent</code> 来实现组件的异步加载。</li></ul><p>总之,随着 Vue 3 的普及,越来越多的库和框架都开始使用 <code>defineAsyncComponent</code> 来提高应用程序的性能。</p><h2>defineCustomElement()</h2><p>这个方法和 <code>defineComponent</code> 接受的参数相同,不同的是会返回一个<strong>原生自定义元素类</strong>的构造器。</p><pre><code class="ts">function defineCustomElement(
component:
| (ComponentOptions & { styles?: string[] })
| ComponentOptions['setup']
): {
new (props?: object): HTMLElement
}</code></pre><p>除了常规的组件选项,<code>defineCustomElement()</code> 还支持一个特别的选项 <code>styles</code>,它应该是一个内联 <code>CSS</code> 字符串的数组,所提供的 <code>CSS</code> 会被注入到该元素的 <code>shadow root</code> 上。<br>返回值是一个可以通过 <code>customElements.define()</code> 注册的自定义元素构造器。</p><pre><code class="ts">import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
/* 组件选项 */
})
// 注册自定义元素
customElements.define('my-vue-element', MyVueElement)</code></pre><h3><a href="https://link.segmentfault.com/?enc=l9VBeb7OEbfq1kTrbuLdyA%3D%3D.jhJwVT1jWuIiqdZHkr%2BuhPa%2BIIkKKwI9BMaUD6qwHC7zr3ncN7yCI25RMwYwRKEyWBimeP2KYNcJ6JpQxDywk6RSuM3GuvP27mywPRX67VTud1pEcr%2F6GUfKRMj9gS4C" rel="nofollow">使用 Vue 构建自定义元素</a></h3><pre><code class="ts">import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// 这里是同平常一样的 Vue 组件选项
props: {},
emits: {},
template: `...`,
// defineCustomElement 特有的:注入进 shadow root 的 CSS
styles: [`/* inlined css */`],
})
// 注册自定义元素
// 注册之后,所有此页面中的 `<my-vue-element>` 标签
// 都会被升级
customElements.define('my-vue-element', MyVueElement)
// 你也可以编程式地实例化元素:
// (必须在注册之后)
document.body.appendChild(
new MyVueElement({
// 初始化 props(可选)
})
)
// 组件使用
<my-vue-element></my-vue-element></code></pre><p>除了 <code>Vue 3</code> 之外,一些基于 <code>Vue 3</code> 的库和框架也开始使用 <code>defineCustomElement</code> 来将 <code>Vue</code> 组件打包成自定义元素供其他框架或纯 HTML 页面使用。例如:</p><ul><li><strong>Ionic Framework</strong>: 基于 <code>Web Components</code> 的移动端 UI 框架,从版本 6 开始支持使用 <code>defineCustomElement</code> 将 <code>Ionic</code> 组件打包成自定义元素。</li><li><strong>LitElement</strong>: Google 推出的 <code>Web Components</code> 库,提供类似 <code>Vue</code> 的模板语法,并支持使用 <code>defineCustomElement</code> 将 <code>LitElement</code> 组件打包成自定义元素。</li><li><strong>Stencil</strong>: 由 <code>Ionic Team</code> 开发的 <code>Web Components</code> 工具链,可以将任何框架的组件转换为自定义元素,并支持使用 <code>defineCustomElement</code> 直接将 <code>Vue</code> 组件打包成自定义元素。</li></ul><p>总之,随着 <code>Web Components</code> 的不断流行和发展,越来越多的库和框架都开始使用 <code>defineCustomElement</code> 来实现跨框架、跨平台的组件共享。</p><h2>小结</h2><p>本次我们围绕着 <code>Vue3</code> 中的依赖注入与组件定义相关的几个 API,学习其基本使用方法,并且结合着目前流行的库和框架分析了使用场景,以此来加深我们对它们的认识。</p><p>内容收录于<a href="https://link.segmentfault.com/?enc=o3AumrifCWW%2Fz2OjYr%2Fsig%3D%3D.ia532a1JXDSKK0ZV4GhyEkH0ur4hgMZoPcwMxZtX0DzcJU5CK8PQ%2BgFIp9Gx1fPG" rel="nofollow">github 仓库</a></p>
2023我还不知道的JSON Schema-基础篇
https://segmentfault.com/a/1190000043529734
2023-03-13T10:01:34+08:00
2023-03-13T10:01:34+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<h3>什么是 JSON</h3><p>JSON 在前端日常开发中再熟悉不过,往往我们和后端的数据交互都是通过 JSON 来进行传输的。那么具体什么是 JSON 呢?</p><blockquote>JSON(<a href="https://link.segmentfault.com/?enc=Ri2XKG%2FQWRR97doYuEnu0w%3D%3D.Sp3tT75wJrif4LinM%2BFxbY8QTN%2FB5nn0tfEUw1riWHldCGAuKsuzQsYvsGuuT9xM" rel="nofollow">JavaScript</a> Object Notation, JS 对象简谱)是一种轻量级的数据交换格式。它基于 <a href="https://link.segmentfault.com/?enc=The17yY9vXqjyAt90iF3%2FQ%3D%3D.PHTtnOyRr%2FCi87k0qEB7H3pvS6g0B2E3C1uhjGdg1ZoZvfpJArPmT6K5GsZp2w%2FS" rel="nofollow">ECMAScript</a>(European Computer Manufacturers Association, 欧洲计算机协会制定的 js 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。</blockquote><p>简单来说就是:</p><ul><li>JSON 指的是 JavaScript 对象标记法(JavaScript Object Notation)</li><li>JSON 是一种轻量级的数据交换格式</li><li>JSON 具有自我描述性且易于理解</li><li>JSON 独立于语言*</li></ul><h3>什么是 JSON Schema</h3><p>JSON Schema,从字面我们就可以知道,它和 JSON 有紧密的关系。那么具体是什么呢?</p><blockquote>JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.</blockquote><p>JSON Schema 可以称为 JSON 模式,是一个提议的 IETF 标准,它可以用来描述和校验你的 JSON 数据。JSON Schema 定义了一套词汇和规则,这套词汇和规则用来定义 JSON 元数据,且元数据也是通过 JSON 数据形式表达的。JSON 元数据定义了 JSON 数据需要满足的规范,规范包括成员、结构、类型、约束等。我们可以通过它来校验我们的 JSON 数据是否有效,是否满足规范。它的作用有些类似于 TypeScript 之于 JavaScript。<br>它的主要作用:</p><ul><li>对 JSON 数据格式进行描述</li><li>提供清晰的人机可读的文档</li><li><p>校验数据</p><ul><li>自动测试</li><li>验证提交数据的质量</li></ul></li></ul><h4>JSON Schema 示例</h4><p>假定我们现在有一个 JSON 数据:</p><pre><code>{
"productId": 1,
"productName": "A green door",
"price": 12.50,
"tags": [ "home", "green" ]
}</code></pre><p>我们来对应声明一个 JSON Schema 如下:</p><pre><code>{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"title": "Product",
"description": "A product",
"type": "object",
"properties": {
"productId": {
"description": "The unique identifier for a product",
"type": "integer"
},
"productName": {
"description": "Name of the product",
"type": "string"
},
"price": {
"description": "The price of the product",
"type": "number",
"exclusiveMinimum": 0
},
"tags": {
"description": "Tags for the product",
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": [ "productId", "productName", "price" ]
}</code></pre><h3>JSON Schema 关键字</h3><p>先让我们来看下开头的几个关键字:</p><ul><li><code>$schema</code> 关键字:用来声明当前 JSON Schema 使用的是哪个版本的 JSON Schema 标准,在声明 JSON Schema 时尽量添加上它,虽然它不是必须的。注:该关键字若使用,其值必须使用官方提供的值,不能自己随便写。</li><li><code>$id</code>关键字:为JSON Schema实例声明了一个唯一标识符,并且可以用来声明一个解析$ref 的 URI 时的基础 URI。最佳实践是,每个顶级 JSON Schema 实例都应该将 $id 设置为一个由你自己控制的域内的绝对 URI。</li><li><code>title</code> 和 <code>description</code> 关键字:仅用于描述性的作用,可以省略。</li><li><code>type</code> 关键字:用于验证 JSON 数据的第一个约束,在上述例子中,JSON 数据必须是一个对象。</li></ul><p>上面几个关键字是针对 Schema 的一些设置和注释,针对对应的 JSON 数据中每个字段的描述,都在 properties 中,具体每种格式的字段相关的描述,我们稍后具体说明。让我们先看下最后一个关键字 required,它用来描述 JSON 数据中哪些字段是必须的,对应的值必须是数组格式。比如我们在更新文章时,提交的字段中文章 id 会是必须的;上面 JSON 数据中 productId 是必须的。<br>下面让我们具体看下 JSON 数据中不同类型的字段对应的描述关键字都有哪些。<br>JSON 数据中每个字段的描述都对应一个 type,type 对应的基本类型主要包括:</p><table><thead><tr><th>类型</th><th>描述</th></tr></thead><tbody><tr><td>string</td><td>字符串型,双引号包裹的 Unicode 字符和反斜杠转义字符</td></tr><tr><td>number</td><td>数字型,包括整型(int)和浮点数型(float)</td></tr><tr><td>boolean</td><td>布尔型,true 或 false</td></tr><tr><td>object</td><td>对象型,无序的键:值对集合</td></tr><tr><td>array</td><td>数组型,有序的值序列</td></tr><tr><td>null</td><td>空型</td></tr></tbody></table><p>其中,针对类型 string、number、array 和 object 有一些相关的关键字:</p><ul><li><p><strong>string</strong></p><table><thead><tr><th><strong>关键字</strong></th><th><strong>描述</strong></th><th><strong>schema 有效值</strong></th><th><strong>json 数据验证</strong></th></tr></thead><tbody><tr><td>maxLength</td><td>最大长度</td><td>大于等于 0 的整数</td><td>字符串的长度必须小于等于该值</td></tr><tr><td>minLength</td><td>最小长度</td><td>大于等于 0 的整数</td><td>字符串的长度必须大于等于该值</td></tr><tr><td>pattern</td><td>模式</td><td>字符串,必须是有效的正则表达式</td><td>当字符串符合正则表达式时,通过验证</td></tr></tbody></table></li><li><p><strong>number</strong></p><table><thead><tr><th><strong>关键字</strong></th><th><strong>描述</strong></th><th><strong>Schema 有效值</strong></th><th><strong>json 数据验证</strong></th></tr></thead><tbody><tr><td>multipleOf</td><td>整数倍</td><td>大于 0 的 JSON 数</td><td>当 JSON 实例的值是其整数倍的时候,通过验证</td></tr><tr><td>maximum</td><td>最大值</td><td>一个 JSON 数</td><td>当 JSON 实例的值小于等于 maximum 的时候,通过验证</td></tr><tr><td>exclusiveMaximum</td><td>包含最大值</td><td>布尔值,必须与 maximum 一起使用</td><td>当其为 true 的时候,JSON 实例不能等于 maximum 的值</td></tr><tr><td>minimum</td><td>最小值</td><td>一个 JSON 数</td><td>当 JSON 实例的值大于等于 minimum 的时候,通过验证</td></tr><tr><td>exclusiveMinimum</td><td>包含最小值</td><td>布尔值,必须与 minimum 一起使用</td><td>当其为 true 的时候,JSON 实例不能等于 minimum 的值</td></tr></tbody></table></li><li><p><strong>array</strong></p><table><thead><tr><th><strong>关键字</strong></th><th><strong>描述</strong></th><th><strong>Schema 有效值</strong></th><th><strong>json 数据验证</strong></th></tr></thead><tbody><tr><td>items</td><td>定义元素</td><td>必须是 Schema 实例对象或者 Schema 实例对象的数组</td><td>用于定义 array 中的元素类型</td></tr><tr><td>additionalItems</td><td>额外项校验</td><td>布尔值或 Schema 实例对象</td><td>当 items 为 Schema 实例的数组,additionalItems 为 false 时,json 数据长度必须小于等于 items 长度,如果 additionalItems 是 Schema 实例,则 items 关键字指定的 Schema 实例数组没有匹配到的其他元素都要符合该实例</td></tr><tr><td>maxItems</td><td>长度限制</td><td>大于等于 0 的整数</td><td>array 实例的长度必须小于等于 maxItems 的值</td></tr><tr><td>minItems</td><td>长度限制</td><td>大于等于 0 的整数</td><td>array 实例的长度必须大于等于 minItems 的值</td></tr><tr><td>uniqueItems</td><td>唯一值</td><td>布尔值,默认值 false</td><td>当 uniqueItems 为 true 的时候,array 实例不能有重复值。</td></tr></tbody></table></li><li><p><strong>object</strong></p><table><thead><tr><th><strong>关键字</strong></th><th><strong>描述</strong></th><th><strong>Schema 有效值</strong></th><th><strong>json 数据验证</strong></th></tr></thead><tbody><tr><td>properties</td><td>属性</td><td>属性的值必须都是有效的 Schema 实例对象</td><td>用于定义属性列表</td></tr><tr><td>maxProperties</td><td>最大属性个数</td><td>大于等于 0 的整数</td><td>object 实例的属性个数必须小于等于 maxProperties 的值</td></tr><tr><td>minProperties</td><td>最小属性个数</td><td>大于等于 0 的整数</td><td>object 实例的属性个数必须大于等于 minProperties 的值</td></tr><tr><td>required</td><td>必须属性</td><td>字符串数组,至少必须有一个元素,数组内不能有重复值</td><td>object 实例必须有所有 required 定义的属性</td></tr><tr><td>patternProperties</td><td>按属性名校验</td><td>必须是有效的 Scheme 实例对象</td><td>Scheme 实例的每一个属性的键都是一个正则表达式,值都是一个 Schema 实例。 指定符合正则表达式的属性的校验规则。</td></tr><tr><td>additionalProperties</td><td>额外属性校验</td><td>Scheme 实例对象或布尔值</td><td>为 false 时不允许拥有除了 properties 和 patternProperties 匹配到的属性外的属性,如果为 Scheme 实例,则没有匹配到的属性要符合该 Scheme</td></tr></tbody></table></li><li><p>另外,还有一些通用关键字</p><table><thead><tr><th><strong>关键字</strong></th><th><strong>描述</strong></th><th><strong>Schema 有效值</strong></th><th><strong>json 数据验证</strong></th></tr></thead><tbody><tr><td>enum</td><td>数据枚举</td><td>必须是数组,而且数组里面的元素至少必须有一个而且不能有重复值。</td><td>当 json 实例的值存在于 enum 列表中时,通过验证</td></tr><tr><td>type</td><td>定义类型</td><td>可以是字符串或者字符串数组,取值必须在 JSON 基本类型范围内</td><td>校验 JSON 实例的类型是否符合定义</td></tr><tr><td>allOf</td><td>数据验证</td><td>必须是 Schema 实例对象数组,而且数组里面的元素至少必须有一个而且不能有重复</td><td>JSON 实例满足其中所有的 Schema 时,通过验证</td></tr><tr><td>anyOf</td><td>数据验证</td><td>同 allOf</td><td>JSON 实例满足其中某一个 Schema 时,通过验证</td></tr><tr><td>oneOf</td><td>数据验证</td><td>同 allOf</td><td>JSON 实例刚好只满足其中某一个 Schema 时,通过验证</td></tr><tr><td>not</td><td>数据验证</td><td>必须是个有效的 Schema 实例对象</td><td>如果不满足 JSON Schema 的定义,则通过验证</td></tr><tr><td>const</td><td>数据验证</td><td>JSON 基本类型</td><td>如果 JSON 实例的值和该关键字指定的值相同,则通过校验。</td></tr></tbody></table></li></ul><h3>小结</h3><p>本次我们主要是初步了解了 JSON Schema 和 JSON 的关系,以及 JSON Schema 的一些基础内容。JSON Schema 的构成主要依托于一些关键字,以此来确定一个 JSON 的数据结构描述。看到这里,可能我们还没有体会到它的作用,我们会在后续具体来看一下它是如何发挥作用,在哪些场景下为我们提供便利的,敬请期待。</p><hr><p>参考:</p><p>1.<a href="https://link.segmentfault.com/?enc=gPywwmoJs12614KRiif9zQ%3D%3D.eMBB0CJGN9BYwUHTQ%2BZlSMx8APeutC3YeolxR6aSeXhLwYeWkLdbZI5XzRNZHOFp" rel="nofollow">JSON Schema 入门</a> </p><p>2.<a href="https://link.segmentfault.com/?enc=YEM9qd09JI00BdaF2rsiaw%3D%3D.Kq87missmXCAaaEmVi2AZMoX%2BN5rBi8ZjCxgGX18tpOHesI578gKe4x9adWUW%2FdbBWUrgn4A%2FMo7e9N29QrCKyzlFdsKTt7lePGaPeZedrE%3D" rel="nofollow">JSON Schema Reference</a> </p><p>3.<a href="https://link.segmentfault.com/?enc=BVSX0foeaP4AGmffD4gSDw%3D%3D.bTWBU5s1Dt5rFGWnIwgSfnw%2FkVnwlc7GHADSVG4ZbD%2BRBxPez2OoHxDpwMT%2FEuPwTJouU2duK3j8NSUsmGbmuVGWXVZ3ubxYbXxlFRkx3HA%3D" rel="nofollow">在线 JSON 转 JSON Schema</a></p>
🍍(Pinia)不酸,保甜
https://segmentfault.com/a/1190000043512697
2023-03-08T09:55:47+08:00
2023-03-08T09:55:47+08:00
前端荣耀
https://segmentfault.com/u/lengxing
3
<h3>为什么是Pinia</h3><p>怎么说呢,其实在过往的大部分项目里面,我并没有引入过状态管理相关的库来维护状态。因为大部分的业务项目相对来说比较独立,哪怕自身功能复杂的时候,可能也仅仅是通过技术栈自身的提供的状态管理能力来处理业务场景问题,比如React中的context,基本都能解决我遇到的问题。</p><p>针对Redux或者Vuex这类状态管理的库,我认为在较为复杂的大型业务场景下才能发挥他们的真实作用,在场景较为简单单一,数据状态相对不复杂的时候,引入他们可能并不能带来那么多的便捷。</p><p>说回Pinia,接触使用到它主要是因为有一次手里的发布功能需要进行重构。虽然仅仅是一个发布页面,但是梳理起来发现,里面涉及几类数据需要维护,包括主表单信息、错误校验信息、公共弹窗信息以及关联用户的各种状态信息等。想起之前有看到过关于小🍍的介绍,抱着试试看的心态接入试试。(不行我就继续Provide/Inject了[捂脸])</p><h3>认识Pinia</h3><blockquote>Pinia 是 Vue 的存储库,它允许您跨组件/页面共享状态。</blockquote><p>上面这个是官网对Pinia的一个定义,从定义上我们其实可以看出来,它<strong>可能</strong>比Vuex要精炼一些(Vuex 是一个专为 Vue.js 应用程序开发的<strong>状态管理模式 + 库</strong>。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。)具体如何我们还是得具体从使用情况来看一下。</p><h3>Pinia的常规用法</h3><h4>安装</h4><p>通过常用的包管理器进行安装:</p><pre><code>yarn add pinia
// 或者
npm install pinia</code></pre><p>安装完成后,我们需要将pinia挂载到Vue应用中,也就是我们需要创建一个根存储传递给应用程式。我们需要修改main.js,引入pinia提供的cteatePinia方法:</p><pre><code>import { createApp } from 'vue';
import { createPinia } from 'pinia';
const pinia = createPinia();
const app = createApp(App);
app.use(pinia).mount('#app');</code></pre><p>上述安装引入基于Vue3,如果使用Vue2的话,轻参照<a href="https://link.segmentfault.com/?enc=0KNsiRlfAMxc%2BDrxp%2BWbFA%3D%3D.4W1uDNIeYCsgSPm73cE%2FWCiYdsOip2K9o103MW3kR4a%2Bja76ARq%2Bpe5foB2oBo5MrV%2FjD3favWuzXeessJJ7M%2Fnn%2Bg2XESU3p9z0LtIR8jc%3D" rel="nofollow">官网相关</a>说明即可。</p><h4>Store</h4><p>store简单来说就是数据仓库的意思,我们 的数据都放在store里面。当然你也可以把它理解为一个公共组件,只不过该公共组件只存放数据,这些数据我们其它所有的组件都能够访问且可以修改。它有三个概念,<strong>state</strong>、<strong>getters</strong>和<strong>actions</strong>,我们可以将它们等价于组件中的“<strong>数据</strong>”、“<strong>计算属性</strong>”和“<strong>方法</strong>”。</p><p>store中应该包含可以在整个应用中访问的数据、全局性数据,我们应该避免将可以管理在具体组件内部的数据放到store中。</p><p>我们需要使用pinia提供的<code>defineStore()</code>方法来创建一个store,该store用来存放我们需要全局使用的数据。我们可以在项目中创建store目录存储我们定义的各种store:</p><pre><code>// src/store/formInfo.js
import { defineStore } from 'pinia';
// 第一个参数是应用程序中 store 的唯一 id
const useFormInfoStore = defineStore('formInfo', {
// 其他配置项,后面逐一说明
})
export default useFormInfoStore;</code></pre><p>defineStore接收两个参数:</p><ul><li>name:一个字符串,必传项,该store的唯一id。</li><li>options:一个对象,store的配置项,比如配置store内的数据,修改数据的方法等等。</li></ul><p>将返回的函数命名为 <em>use...</em> 是组合式开发的约定,使其符合使用习惯。我们可以根据项目情况定义任意数量的store存储不同功能模块的数据,一个store就是一个函数,它和Vue3的实现思想也是一致的。</p><h5>使用store</h5><p>我们可以在任意组件中引入定义的store来进行使用</p><pre><code><script setup>
// 引入定义
import useFormInfoStore = '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
</script></code></pre><p>store 被实例化后,你就可以直接在 store 上访问 <code>state</code>、<code>getters</code> 和 <code>actions</code> 中定义的任何属性。</p><h5>解构store</h5><p><code>store</code> 是一个用<code>reactive</code> 包裹的对象,这意味着不需要在getter 之后写<code>.value</code>,但是,就像<code>setup</code> 中的<code>props</code> 一样,<strong>我们不能对其进行解构</strong>,如果我们想要提取store中的属性同时保持其响应式的话,我们需要使用<code>storeToRefs()</code>,它将为响应式属性创建refs。</p><pre><code><script setup>
import { storeToRefs } from 'pinia';
// 引入定义
import useFormInfoStore = '@/store/formInfo';
// 调用方法,返回store实例
const formInfoStore = useFormInfoStore();
const { name, age } = formInfoStore; // ❌ 此时解构出来的name和age不具有响应式
const { name, age } = storeToRefs(formInfoStore); // ✅ 此时解构出来的name和age是响应式引用
</script></code></pre><h4>State</h4><p>store是用来存储全局状态数据的仓库,那自然而然需要有地方能够保存这些数据,它们就保存在state里面。defineStore传入的第二个参数options配置项里面,就包括state属性。</p><pre><code>// src/store/formInfo.js
import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {
// 推荐使用 完整类型推断的箭头函数
state: () => {
return {
// 所有这些属性都将自动推断其类型
name: 'Hello World',
age: 18,
isStudent: false
}
}
// 还有一种定义state的方式,不太常见,了解即可
// state: () => ({
// name: 'Hello World',
// age: 18,
// isStudent: false
// })
})
export default useFormInfoStore;</code></pre><h5>访问state</h5><p>默认情况下,您可以通过 <code>store</code> 实例来直接读取和写入状态:</p><pre><code><script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
formInfoStore.age++; // 19
</script></code></pre><p>pinia还提供了几个常见场景的方法供我们使用来操作state:<code>$reset</code>、<code>$patch</code>、<code>$state</code>、<code>$subscribe</code>:</p><pre><code><script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
console.log(formInfoStore.name); // 'Hello World'
// 直接修改state中的属性
formInfoStore.age++; // 19
// 1.$reset 重置状态,将状态重置成为初始值
formInfoStore.$reset();
console.log(formInfoStore.age); // 18
// 2.$patch 支持对state对象的部分批量修改
formInfoStore.$patch({
name: 'hello Vue',
age: 198
});
// 3.$state 通过将其 $state 属性设置为新对象来替换 Store 的整个状态
formInfoStore.$state = {
name: 'hello Vue3',
age: 100,
gender: '男'
}
// 4.$subscribe 订阅store中的状态变化
formInfoStore.$subscribe((mutation, state) => {
// 监听回调处理
}, {
detached: true // 💡如果在组件的setup中进行订阅,当组件被卸载时,订阅会被删除,通过detached:true可以让订阅保留
})
</script></code></pre><p>针对上面示例,有几点需要关注一下:</p><ul><li>1.同直接修改state中的属性不同,通过$patch方法更新多个属性时,在devtools的timeline中,是合并到一个条目中的。</li></ul><p><img src="/img/remote/1460000043512699" alt="timelinepatch.png" title="timelinepatch.png"></p><ul><li>2.通过实验得知,$state在进行替换时,会更新已有的属性,新增原来state不存在的属性,针对不在替换范围内的,则保持不变。</li></ul><p><img src="/img/remote/1460000043512700" alt="timelinestate.png" title="timelinestate.png"></p><p>如上图,针对gender属性,执行的是"add"操作,然后这个替换过程我们没有设置isStudent属性,它仍然保持原状态在state中不变。</p><ul><li>3.$subscribe只会订阅到patches引起的变化,即上面的通过$patch方法和$state覆盖时会触发到状态订阅,直接修改state的属性则不会触发。</li></ul><h4>Getters</h4><p>pinia中的getters可以完全等同于Store状态的<code>计算属性</code>,通常在defineStore中的getters属性中定义。同时支持组合多个getter,此时通过<code>this</code>访问其他getter。</p><pre><code>import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {
state: () => {
return {
name: 'Hello World',
age: 18,
isStudent: false,
gender: '男'
};
},
getters: {
// 仅依赖state,通过箭头函数方式
isMan: (state) => {
return state.gender === '男';
},
isWoman() {
// 通过this访问其他getter,此时不可以用箭头函数
return !this.isMan;
}
}
});
export default useFormInfoStore;
</code></pre><p>在使用时,我们可以直接在store实例上面访问getter:</p><pre><code><template>
<div>The person is Man: {{ formInfoStore.isMan }} or is Woman: {{ formInfoStore.isWoman }}</div>
</tempalte>
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script></code></pre><p>通常getter是不支持额外传参的,但是我们可以从getter返回一个函数的方式来接收参数:</p><pre><code>import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {
state: () => {
return {
name: 'Hello World',
age: 18,
isStudent: false,
gender: '男'
};
},
getters: {
isLargeBySpecialAge: (state) => {
return (age) => {
return state.age > age
}
}
}
});
export default useFormInfoStore;</code></pre><p>在组件中使用时即可传入对应参数,注意,在这种方式时,<strong>getter不再具有缓存性</strong>。</p><pre><code><template>
<div>The person is larger than 18 years old? {{ formInfoStore.isLargeBySpecialAge(18) }}</div>
</tempalte>
<script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
</script></code></pre><h4>Actions</h4><p>actions相当于组件中的methods,它们定义在defineStore中的actions属性内,常用于定义业务逻辑使用。<strong>action可以是异步的</strong>,可以在其中<code>await</code> 任何 API 调用甚至其他操作</p><pre><code>import { defineStore } from 'pinia';
const useFormInfoStore = defineStore('formInfo', {
state: () => {
return {
name: 'Hello World',
age: 18,
isStudent: false,
gender: '男'
};
},
getters: {
isMan: (state) => {
return state.gender === '男';
},
isWoman() {
return !this.isMan;
}
},
actions: {
incrementAge() {
this.age++;
},
async registerUser() {
try {
const res = await api.post({
name: this.name,
age: this.age,
isStudent: this.isStudent,
gender: this.gender
});
showTips('用户注册成功~');
} catch (e) {
showError('用户注册失败!');
}
}
}
});
export default useFormInfoStore;
</code></pre><p>使用起来也非常方便</p><pre><code><script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
const registerUser = () => {
formInfoStore.registerUser();
}
</script></code></pre><h5>$onAction()</h5><p>可以使用 <code>store.$onAction()</code> 订阅 action 及其结果。传递给它的回调在 action 之前执行。 <code>after</code> 处理 Promise 并允许您在 action 完成后执行函数,<code>onError</code> 允许您在处理中抛出错误。</p><pre><code>const unsubscribe = formInfoStore.$onAction(
({
name, // action 的名字
store, // store 实例
args, // 调用这个 action 的参数
after, // 在这个 action 执行完毕之后,执行这个函数
onError, // 在这个 action 抛出异常的时候,执行这个函数
}) => {
// 记录开始的时间变量
const startTime = Date.now()
// 这将在 `store` 上的操作执行之前触发
console.log(`Start "${name}" with params [${args.join(', ')}].`)
// 如果 action 成功并且完全运行后,after 将触发。
// 它将等待任何返回的 promise
after((result) => {
console.log(
`Finished "${name}" after ${
Date.now() - startTime
}ms.\nResult: ${result}.`
)
})
// 如果 action 抛出或返回 Promise.reject ,onError 将触发
onError((error) => {
console.warn(
`Failed "${name}" after ${Date.now() - startTime}ms.\nError: ${error}.`
)
})
}
)
// 手动移除订阅
unsubscribe()</code></pre><p>和$subscribe类似,在组件中使用时,组件卸载,订阅也会被删除,如果希望保留的话,需要传入true作为第二个参数。</p><pre><code><script setup>
import useFormInfoStore = '@/store/formInfo';
const formInfoStore = useFormInfoStore();
formInfoStore.$onAction(callback, true);
</script></code></pre><p>Pinia的基础使用我们暂时介绍到这里,其他使用场景大家可以参照官方文档进一步学习。</p><h3>Pinia VS Vuex</h3><p>回过头来,我们再来看一下,Pinia为什么现在这么受到推崇。和我们过往常用的Vuex相比,它到底好在哪里呢?</p><p>对于Vuex,我们知道,它的背后基本思想借鉴了Flux。Flux 是 Facebook 在构建大型 Web 应用程序时为了解决<strong>数据一致性</strong>问题而设计出的一种架构,它是一种描述状态管理的设计模式。绝大多数前端领域的状态管理工具都遵循这种架构,或者以它为参考原型。Vuex在它的基础上进行了一些设计上的优化:</p><p><img src="/img/remote/1460000043512701" alt="vuex.png" title="vuex.png"></p><p>Vuex主要有五部分核心内容:</p><ul><li>📦 <strong>state</strong>:整个应用的状态管理单例,等效于 Vue 组件中的 data,对应了 Flux 架构中的 store。</li><li>🧮 <strong>getter</strong>:可以由 state 中的数据派生而成,等效于 Vue 组件中的计算属性。它会自动收集依赖,以实现计算属性的缓存。</li><li><p>🛠 <strong>mutation</strong>:类似于事件,包含一个类型名和对应的回调函数,在回调函数中可以对 state 中的数据进行同步修改。</p><ul><li>Vuex 不允许直接调用该函数,而是需要通过 <code>store.commit</code> 方法提交一个操作,并将参数传入回调函数。</li><li>commit 的参数也可以是一个数据对象,正如 Flux 架构中的 action 对象一样,它包含了类型名 <code>type</code> 和负载 <code>payload</code>。</li><li>这里要求 mutation 中回调函数的操作一定是同步的,这是因为同步的、可序列化的操作步骤能保证生成唯一的日志记录,才能使得 devtools 能够实现对状态的追踪,实现 time-travel。</li></ul></li><li><p>🔨 <strong>action</strong>:action 内部的操作不受限制,可以进行任意的异步操作。我们需要通过 <code>dispatch</code> 方法来触发 action 操作,同样的,参数包含了类型名 <code>type</code> 和负载 <code>payload</code>。</p><ul><li>action 的操作本质上已经脱离了 Vuex 本身,假如将它剥离出来,仅仅在用户(开发者)代码中调用 <code>commit</code> 来提交 mutation 也能达到一样的效果。</li></ul></li><li><p>📁 <strong>module</strong>:store 的分割,每个 module 都具有独立的 state、getter、mutation 和 action。</p><ul><li>可以使用 <code>module.registerModule</code> 动态注册模块。</li><li>支持模块相互嵌套,可以通过设置命名空间来进行数据和操作隔离。</li></ul></li></ul><p>通过和我们上面学习到的Pinia的基础内容对比可以看出,Pinia舍弃了mutation和module两部分,这样我们在使用时就更加的便捷。</p><p><img src="/img/remote/1460000043512702" alt="pinia流程图.png" title="pinia流程图.png"></p><p>与Vuex3.x/4.x对比,主要区别如下:</p><ul><li><em>mutations</em> 不再存在。他们经常被认为是 <strong>非常 冗长</strong>。他们最初带来了 devtools 集成,但这不再是问题。</li><li>无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。</li><li>不再需要注入、导入函数、调用函数、享受自动完成功能!</li><li>无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。</li><li>不再有 <em>modules</em> 的嵌套结构。您仍然可以通过在另一个 Store 中导入和 <em>使用</em> 来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。 <strong>您甚至可以拥有 Store 的循环依赖关系</strong>。</li><li>没有 <em>命名空间模块</em>。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。</li></ul><p>其实对于我来说,之所以选择Pinia,甚至是喜欢上它,是因为它和Vue3的组合是API形式更加贴合,只需要把它当作一个特殊的数据状态组件来使用就好,不需要那么复杂的流程。</p><h3>小结</h3><p>通过对Pinia的基本功能的使用介绍,以及和Vuex进行对比,让我们比较清晰的来认识Pinia,使我们能够入门使用。在具体业务场景中,有效的划分Store,合理的组合使用,可以帮助我们完成复杂的业务逻辑。</p><hr><p>参考内容:</p><p><a href="https://link.segmentfault.com/?enc=jDcV3rtm%2F4%2BLBS9VvRM92A%3D%3D.SlKhLt5qFUtSwT%2B2xgzqDczUOzqMzkV4aQ8%2BGYB5sqM%3D" rel="nofollow">pinia中文文档</a></p><p><a href="https://link.segmentfault.com/?enc=izaw0Dw9AO90rLn18WNSqg%3D%3D.h%2B8i6%2BKX8itYiXkgp0wOqtH3hcZwaIojZJTwLy0ooQ0e26F2J79zSl3tGUs8g4uf" rel="nofollow">Pinia or Vuex?</a></p><p><a href="https://link.segmentfault.com/?enc=08gESrO%2FpHIVf736D%2FSnIQ%3D%3D.7%2FGt2%2FZyCH8PMV2vV1AdJ%2BJJsbLE4raCLpJKN0WHHkJS2sQKy97AzOZ60LQzMe%2BFroRQ%2FuJ5LOTIg%2FOqmlTeLQ%3D%3D" rel="nofollow">Vuex 与 Pinia 的设计实现对比</a></p>
Vue3 Composable最佳实践(一)
https://segmentfault.com/a/1190000042013681
2022-06-21T13:59:20+08:00
2022-06-21T13:59:20+08:00
前端荣耀
https://segmentfault.com/u/lengxing
5
<p>截至目前,Composable(组合式函数)应该是在VUE 3应用程序中组织业务逻辑最佳的方法。<br>它让我们可以把一些小块的通用逻辑进行抽离、复用,使我们的代码更易于编写、阅读和维护。<br>由于这种编写VUE代码的方式相对较新,因此您可能想知道编写组合式函数的最佳实践是什么呢?本系列教程可以作为您和您的团队在进行组合式开发过程中的参考指南。<br>我们将涵盖以下内容:</p><ul><li>1.如何通过选项对象参数使您的组合更加可配置; 👈 <strong>本篇主题</strong></li><li>2.使用ref和unref使我们的参数更加灵活;</li><li>3.如何使你的返回值更有用;</li><li>4.为什么从接口定义开始可以使你的组合式函数更强大;</li><li>5.如何使用异步代码而无需“等待” - 使您的代码更易于理解;</li></ul><p>不过,首先,我们需要确保我们对组合式函数的理解是一致的。我们先花点时间解释一下什么是组合式函数。</p><h3>什么是Composable-组合式函数?</h3><p>根据官方文档说明,在 Vue 应用的概念中,“组合式函数”是一个利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。<br>这就意味着,任何有状态逻辑,并且使用了响应式处理的逻辑都可以转换成组合式函数。这和我们平时抽离封装的公共方法还是有一些区别的。我们封装的公共方法往往是无状态的:它在接收一些输入后立刻返回所期望的输出。而组合式函数往往是和状态逻辑关联的。</p><p>让我们看看官方给出的<code>useMouse</code>这个组合式函数:</p><pre><code>import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}</code></pre><p>我们把状态定义为<code>refs</code>,当鼠标移动的时候我们更新这个状态。通过返回<code>x</code>和<code>y</code>,我们可以在任何组件中使用它们,甚至我们还可以把多个组合式函数嵌套使用。</p><p>当我们在组件中使用时</p><pre><code><template>
X: {{ x }} Y: {{ y }}
</template>
<script setup>
import { useMouse } from './useMouse';
const { x, y } = useMouse();
</script></code></pre><p>如您所见,通过使用<code>useMouse</code>我们可以轻松的复用这个逻辑。仅仅很少的代码,我们就可以在组件中获取鼠标坐标状态。</p><p>现在我们对组合式函数有了相同的认识,让我们看一下可以帮助我们编写更好的组合式函数的第一个方法吧。</p><h3>选项对象参数</h3><p>大部分组合式函数都会有一个或两个必须的参数,然后有一系列可选的参数来帮助进行一些额外的配置。在配置组合式函数时,我们可以将一系列的可选配置放到一个选项对象参数中,而不是一长串参数的形式。</p><pre><code>// 使用选项对象参数形式
const title = useTitle('A new title', { titleTemplate: '>> %s <<' });
// Title is now ">> A new title <<"
// 使用多参数形式
const title = useTitle('A new title', '>> %s <<');
// Title is now ">> A new title <<"</code></pre><p>选项对象参数的形式可以给我们带来一些便利:<br>首先,我们不必记住参数的正确顺序,特别是参数很多的时候。虽然现在我们可以通过Typescript和编辑器提示功能来避免这类问题,但是通过这种方式仍然是有差别的。使用Javascript对象,参数的顺序就不那么重要了。(当然,这也要求我们的函数定义需要清晰明了,我们后面会谈)<br>其次,代码更可读取,因为我们知道该选项的作用是什么。<br>第三,代码扩展性更好,以后添加新的选项要容易得多。这既适用于为组合式函数本身添加新选项,又适用于嵌套使用时参数传递。</p><p>因此,使用对象参数更加友好,但是我们该如何来实现呢?</p><h3>在组合式函数中的实现</h3><p>现在让我们看下如何在组合式函数中使用选项对象参数。我们来给上面的<code>useMouse</code>进行一些扩展:</p><pre><code>export function useMouse(options) {
const {
asArray = false,
throttle = false,
} = options;
// ...
};</code></pre><p><code>useMouse</code>本身没有必传参数,所以我们直接给它增加一个<code>options</code>参数来进行一些额外的配置。通过解构,我们可以访问所有的选传参数,并且为每个参数设置了默认值,这就避免了有些不需要额外配置的调用时没有传入可选参数的情况。</p><p>现在让我们来看两个VueUse上面的组合式函数是如何使用这个模式的。<a href="https://link.segmentfault.com/?enc=FJ2rsm4BFjPgfgG1FRLs7A%3D%3D.65dNl8MKr4uqtjO1%2BS6s3NzFsOhYVYntHJmk8M8BMQA%3D" rel="nofollow">VueUse</a>是一个服务于Vue3的组合式函数的常用工具集,它的初衷就是将一切原本并不支持响应式的JS API变得支持响应式,省去程序员自己写相关代码。</p><p>我们先来看<code>useTitle</code>,然后再看一下<code>useRefHistory</code>是如何实现的。</p><h3>举例-useTitle</h3><p><code>useTitle</code>的作用非常简单,就是用来更新页面的标题。</p><pre><code>const title = useTitle('Initial Page Title');
// Title: "Initial Page Title"
title.value = 'New Page Title';
// Title: "New Page Title"</code></pre><p>它也有几个选择参数,来促进额外的灵活性。我们可以传入<code>titleTemplate</code>作为模版,并且通过<code>observe</code>来将其设置称为具备观察性(内部通过MutationObserver实现):</p><pre><code>const title = useTitle('Initial Page Title', {
titleTemplate: '>> %s <<',
observe: true,
});
// Title: ">> Initial Page Title <<"
title.value = 'New Page Title';
// Title: ">> New Page Title <<"</code></pre><p>当我们查看它的<a href="https://link.segmentfault.com/?enc=HjCH6fAZ6ooDojYmJbLzpw%3D%3D.3rozg9nMYGNVjPbhYQsI1proCvflz4%2Bv9hB%2BBRr5Uej4%2B1qt7UAWdtw2TRWWD3l%2B7J%2Fq80GQOxq4G3jm76tzS8K9fnDX3c44%2FOSY7hb1Gb9luzP2F4F84h6aQXEzINcXd1G1soYuluyJe6KII9uebvANzhO4r1SAhF6vmqWYmDE%3D" rel="nofollow">源码</a>的时候可以看到以下处理</p><pre><code>export function useTitle(newTitle, options) {
const {
document = defaultDocument,
observe = false,
titleTemplate = '%s',
} = options;
// ...
}</code></pre><p><code>useTitle</code>包含一个必传的参数,以及一个可选参数对象。正如我们上面描述的那样,它完全是按照这个模式来实现的。<br>接下来,让我们看一下一个更复杂的组合式函数是如何使用选项对象模式的。</p><h3>举例-useRefHistory</h3><p><code>useRefHistory</code>可以帮助我们追踪一个响应式变量的所有更改,可以让我们轻松的执行撤销和恢复的操作。</p><pre><code>// Set up the count ref and track it
const count = ref(0);
const { undo } = useRefHistory(count);
// Increment the count
count.value++;
// Log out the count, undo, and log again
console.log(counter.value); // 1
undo();
console.log(counter.value); // 0</code></pre><p>它支持设置许多不同的配置选择</p><pre><code>{
deep: false,
flush: 'pre',
capacity: -1,
clone: false,
// ...
}</code></pre><p>如果想知道这些选项参数的完整列表和对应的功能,可以去查看<a href="https://link.segmentfault.com/?enc=JlhFFgMBAOAMCzEJ93IRJA%3D%3D.ISg3xckwj797yNh%2FpgjIeDzF4qrklSSkzY%2BjNdKYR4TWvLO8ihB2EA8Zixgwpi%2B91h44FLAEGivZi0OLXyTo0Q%3D%3D" rel="nofollow">相关文档</a>,在此不再赘述。</p><p>我们可以将选项对象作为第二个参数传递,以进一步配置该组合函数的行为,与我们上一个示例相同:</p><pre><code>export function useRefHistory(source, options) {
const {
deep = false,
flush = 'pre',
eventFilter,
} = options;
// ...
}</code></pre><p>我们可以看到它内部仅仅解构出了一部分参数值,这是因为<code>useRefHistory</code>内部依赖了<code>useManualHistory</code>这个组合式函数,其他的选项参数将在后面透传给<code>useManualHistory</code>时进行展开合并。</p><pre><code>// ...
const manualHistory = useManualRefHistory(
source,
{
// Pass along the options object to another composable
...options,
clone: options.clone || deep,
setSource,
},
);
// ...</code></pre><p>这也和我们前面说到的内容相符:组合式函数可以嵌套使用。</p><h3>小结</h3><p>本文是“组合式函数最佳实践”的第一部分。我们研究了如何通过选项对象参数提升组合式函数的灵活性。无须担心参数顺序不对导致的问题,并且可以灵活进行配置项的增加扩展。我们不仅仅研究了这个模式本身,我们还通过VueUse中的<code>useTitle</code>和<code>useRefHistory</code>来学习了如何实现此模式。它们略有不同,但是这个模式本身就是很简单的,我们通过它能做到也是有限的。</p><p>下一篇我们将介绍如何通过ref和unref使我们的参数更加灵活</p><pre><code>// Works if we give it a ref we already have
const countRef = ref(2);
useCount(countRef);
// Also works if we give it just a number
const countRef = useRef(2);</code></pre><p>这增加了灵活性,使我们能够在应用程序中的更多情况下使用我们的组合。</p>
11个常用JS小小技巧
https://segmentfault.com/a/1190000041923141
2022-05-31T17:15:33+08:00
2022-05-31T17:15:33+08:00
前端荣耀
https://segmentfault.com/u/lengxing
14
<p>在我们的日常开发过程中,我们经常会遇到数字与字符串转换,检查对象中是否存在对应值,条件性操作对象数据,过滤数组中的错误值,等等这类处理。</p><p>在这里,整理出了一些常用的小技巧,这些技巧是我比较喜欢的❤️,可以使我们的代码更精简、更干净,且非常实用。</p><ol><li>通过条件判断向对象添加属性</li></ol><pre><code>const isValid = false;
const age = 18;
// 我们可以通过展开运算符向对象添加属性
const person = {
id: 'ak001',
name: 'ak47',
...(isValid && {isActive: true}),
...((age > 18 || isValid) && {cart: 0})
}
console.log('person', person)</code></pre><ol start="2"><li>检查对象中是否存在某个属性</li></ol><pre><code>const person = {
id: 'ak001',
name: 'ak47'
}
console.log('name' in person); // true
console.log('isActive' in person); // false</code></pre><ol start="3"><li>解构赋值</li></ol><pre><code>const product = {
id: 'ak001',
name: 'ak47'
}
const { name: weaponName } = product;
console.log('weaponName:', weaponName); // weaponName: ak47
// 通过动态key进行解构赋值
const extractKey = 'name';
const { [extractKey]: data } = product;
console.log('data:', data); // data: ak47</code></pre><ol start="4"><li>循环遍历一个对象的key和value</li></ol><pre><code>const product = {
id: 'ak001',
name: 'ak47',
isSale: false
}
Object.entries(product).forEach(([key, value]) => {
if(['id', 'name'].includes(key)) {
console.log('key:',key, 'value:',value)
}
})
// key: id value: ak001
// key: name value: ak47</code></pre><ol start="5"><li>使用可选链(Optionalchaining)避免访问对象属性报错</li></ol><pre><code>const product = {
id: 'ak001',
name: 'ak47'
}
console.log(product.sale.isSale); // throw error
console.log(product?.sale?.isSale); // undefined</code></pre><p>注意⚠️: 在实际使用场景中,有些场景对于我们要获取的属性是非必需的,我们可以通过上面这种方式去避免报错出现;但是有些场景下一些属性是必须的,不然就会影响我们的实际功能,这个时候还是尽量给出清晰的报错提示来解决这种错误的出现。</p><p>6.检查数组中falsy的值</p><pre><code>const fruitList = ['apple', null, 'banana', undefined];
// 过滤掉falsy的值
const filterFruitList = fruitList.filter(Boolean);
console.log('filterFruitList:', filterFruitList);
// filterFruitList:['apple', 'banana']
// 检查数组中是否有truthy的值
const isAnyFruit = fruitList.some(Boolean);
console.log('isAnyFruit:', isAnyFruit); // isAnyFruit: true</code></pre><p>7.数组去重</p><pre><code>const fruitList = ['apple', 'mango', 'banana', 'apple'];
const uniqList = [...new Set(fruitList)]
console.log('uniqList:', uniqList); // uniqList: ['apple', 'mango', 'banana']</code></pre><ol start="8"><li>检查是否为数组类型</li></ol><pre><code>const fruitList = ['apple', 'mango'];
console.log(typeof fruitList); // object
console.log(Array.isArray(fruiltList)); // true</code></pre><p>9.数字&字符串类型转换</p><pre><code>const personId = '007';
console.log('personId:', +personId, 'type:', typeof +personId);
// personId: 7 type: number
const personId = 119;
console.log('personId:', personId + '', 'type:', typeof (personId + ''));
// personId: 119 type: string</code></pre><ol start="10"><li>巧用空值合并(??)</li></ol><pre><code>let data = undefined ?? 'noData;
console.log('data:', data); // data: noData
data = null ?? 'noData';
console.log('data:', data); // data: noData
data = 0 ?? null ?? 'noData';
console.log('data:', data); // data: 0
// 当我们根据变量自身判断时
data ??= 'noData';
console.log('data:', data); // data: noData</code></pre><blockquote>和或(||) 运算符的区别?\<br>或运算符针对的是falsy类的值 (0,’ ’, null, undefined, false, NaN),而空值合并仅针对null和undefined生效;</blockquote><ol start="11"><li>通过!!进行布尔转换</li></ol><pre><code>console.log('this is not empty:', !!'')
// this is not empty: false
console.log('this is not empty:', !!'Data')
// this is not empty: true</code></pre><p>最后,希望这些小小技巧能够帮助到大家的日常开发,如果大家有更好的方法也欢迎留言分享😊</p>
前端基础回归-URI和URL
https://segmentfault.com/a/1190000041337234
2022-01-25T12:05:17+08:00
2022-01-25T12:05:17+08:00
前端荣耀
https://segmentfault.com/u/lengxing
4
<blockquote>该系列主要围绕前端一些基础性的内容进行回顾整理,为日益纷繁的各类框架打下一个基础的底子,便于理解一些框架内容。</blockquote><p>今天我们主要来回顾一下有关于<code>URI</code>和<code>URL</code>相关的内容。</p><h4>URI是什么</h4><p>统一资源标识符(<code>Uniform Resource Identifier,URI</code>),允许用户对网络中的资源通过特定的协议进行交互操作。<code>RFC2396</code>文档对<code>Uniform Resource Identifier</code>各部分的定义如下。</p><ul><li><code>Uniform</code>:规定统一的语法格式,以方便处理多种不同类型的资源,而无须根据上下文环境来识别资源类型。</li><li><code>Resource</code>:可标识的任何资源。资源不仅可以为单一对象,也可以为多个对象的集合体。</li><li><code>Identifier</code>:表示可标识的对象,也称为标识符。</li></ul><p>在一般情况下,<code>URI</code>为由某个协议方案表示的资源的定位标识符。协议方案是指访问资源时所使用的协议类型名称。<code>HTTP</code>就是协议方案的一种,除此之外,还有<code>FTP</code>、<code>file</code>、<code>TELNET</code>等30种标准URI协议方案。协议方案由互联网号码分配局(IANA)管理颁布。URI使用字符串标识某一互联网资源,常用的<code>URL</code>作为<code>URI</code>的子集,表示某一互联网资源的地点。</p><p><code>URI</code>的通用语法由5个组件组成:</p><pre><code>URI = scheme:[//authority]path[?query][#fragment]</code></pre><p>在URI语法中:</p><ul><li><code>scheme</code>为协议方案名,在使用<code>HTTPS</code>或<code>HTTP</code>等协议方案名时不区分大小写,最后一个符号为冒号“:”。协议方案名也可使用<code>javascript:、data:</code>指定脚本程序或数据。</li><li><code>path</code>为带层次的文件路径,指定服务器上的文件路径,以访问特定的资源。</li><li><code>query</code>为查询字符串,针对指定路径的文件资源,可使用查询字符串传入任意查询参数。</li><li><code>fragment</code>为片段标识符,通常标记已获取资源的子资源,为可选项。</li><li><code>authority</code>可以由以下3分布组成:</li></ul><pre><code>authority = [userinfo@]host[:port]</code></pre><p>在<code>authority</code>中,<code>userinfo</code>作为登录信息,通常形式为指定用户名和密码,当从服务器获取资源时作为身份认证凭证使用。<code>userinfo</code>为可选项。服务器地址<code>host</code>在使用绝对路径<code>URI</code>时需指定访问的服务器地址,地址可以为被<code>DNS</code>解析的域名,如<code>example.com</code>,或者<code>192.168.1.1</code>的IPv4地址及用方括号括起来的IPv6地址<code>[0:0:0:0:0:0:0:1]</code>。<code>port</code>为服务器连接的网络端口号,作为可选项,如果不指定,则自动使用默认的端口号。</p><h4>URL是什么</h4><p>统一资源定位器(<code>UniformResourceLocators,URL</code>)作为URI的一种,如同网络的门牌,标识了一个互联网资源的“住址”,如<code><http://www.example.com></code>表示通过HTTP协议从主机名为<code>www.example.com</code>的主机上获取首页资源。</p><p><code>URL</code>的语法定义与<code>URI</code>是一致的,它属于<code>URI</code>的一个子集。</p><p>统一资源名称(<code>Uniform Resource Name</code>)也是标准格式的<code>URI</code>,指的是资源而不指定其位置或是否存在。鉴于该概念在日常前端的范围内接触较少,仅作为了解即可,有兴趣的可以自行查阅相关内容。</p><h4>URI和URL的关系是什么呢</h4><p><img src="/img/remote/1460000041337236" alt="URI和URL的关系" title="URI和URL的关系"></p><blockquote>统一资源名称(Uniform Resource Name)也是标准格式的URI,指的是资源而不指定其位置或是否存在。鉴于该概念在日常前端的范围内接触较少,仅作为了解即可,有兴趣的可以自行查阅相关内容。</blockquote><p>借用一张图来理解他们之间的关系:<strong>URI可以分为URL,URN或同时具备locators 和names特性的一个东西。URN作用就好像一个人的名字,URL就像一个人的地址。换句话说:URN确定了东西的身份,URL提供了找到它的方式。</strong></p><p>大白话来说,就是<code>URI</code>是抽象的定义,不管用什么方法表示,只要能定位一个资源,就叫<code>URI</code>。本来设想的的使用两种方法定位:1、<code>URL</code>,用地址定位;2、<code>URN</code> 用名称定位。</p><p>举个例子:去村子找个具体的人(<code>URI</code>),如果用地址:某村多少号房子第几间房的主人-就是<code>URL</code>; 如果用身份证号+名字去找-就是<code>URN</code>了。</p><h4>浏览器URI编码</h4><p><code>URI</code>编码使用的是百分号编码(<code>Percent-encoding</code>)。对于需要编码的字符,将其表示为两个十六进制的数字,然后在其前面放置转义字符<code>“%”</code>,并替换原字符相应位置进行编码。</p><p><code>URI</code>中只允许包含未保留字符及所有保留字符。其中,未保留字符包含<code>英文字母(a~z,A~Z),数字(0~9),-、_、.、~4个特殊字符</code>,共66个。对于未保留字符,不需要进行百分号编码。保留字符是那些具有特殊含义的字符。<code>RFC 3986</code> 文档中规定了18个保留字符:</p><pre><code>!*'();:@&=+$,/?#[]</code></pre><p>在<code>URI</code>中,保留字符有特殊的意义,如“?”表示查询,“#”表示片段标识。如果希望保留字符不表示特定的意义,仅表示一般字符,那么需要对保留字符进行URL编码。常用的编码方法主要有<code>encodeURI</code>和<code>encodeURIComponent</code>。</p><h4>encodeURI和encodeURIComponent</h4><p><code>encodeURI()</code>和<code>encodeURIComponent()</code>都是Javascript中对URL编码的函数。</p><p>区别在于:</p><ul><li><p><code>encodeURI</code>是<code>W3C</code>的标准(<code>RFC 3986</code>),不对<code>ASCII字母和数字</code>进行编码,不对20个ASCII标点符号<code>(-、_、.、!、~、*、'、(、)、;、/、?、:、@、&、=、+、$、,、#)</code>进行编码。对于<code>66个未保留字符,18个保留字符</code>,除去2个不安全的保留字符<code>“[”“]”</code>,<code>encodeURI</code>的不编码集为<code>82</code>个。对于非ASCII字符,<code>encodeURI</code>需要将其转换为<code>UTF-8</code>编码字节序,然后在每个字节前面放置转义字符(%)进行百分号编码,并置入URI中的相应位置。</p><blockquote>UTF-8:UTF-8具有无字节序要求、单字节特性节约内存、向后兼容ASCII、错误兼容性好等优点。一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。为传统的扩展ASCII字符集设计的软件通常可以不经修改或经过很少修改就能与UTF-8一起使用。</blockquote></li><li><p><code>encodeURIComponent</code>假定参数是<code>URI</code>的一部分(比如协议、主机名、路径或查询字符串),因此,<code>encodeURIComponent</code>将转义除<code>字母、数字、“(”、“)”、“.”、“!”、“~”、“*”、“'”、“-”和“_”</code>外的所有字符。例如,对<code>“name=val&key=”</code>进行<code>encodeURIComponent</code>编码后结果为<code>“"name%3Dval%26key%3D"”</code>。对于<code>URL</code>组成部分中的特殊字符,通常需要使用<code>encodeURIComponent</code>进行编码,如:</p><pre><code>name=encodeURIComponent('val&key=')
// name=val%26key%3D</code></pre></li></ul><p>相比<code>encodeURIComponent</code>,<code>encodeURI</code>被用作对一个完整的<code>URI</code>进行编码,而<code>encodeURIComponent</code>则被用作对<code>URI</code>的一个组件或者<code>URI</code>中的一个片段进行编码。从上面的编码示例来看,<code>encodeURIComponent</code>编码的字符范围要比<code>encodeURI</code>大。</p><h4>小结</h4><p>以上就是针对<code>URI</code>和<code>URL</code>以及相关编码方法的回顾梳理。在日常的前端开发中,<code>URL</code>等概念是我们经常提及的,而相关的编码转码方法也在我们的日常开发中经常会用到。希望本次回顾也能够帮助大家重温加深一下这些知识。</p><h4>参考资料</h4><p>-《深入理解React Router从原理到实战》</p>
5个不常提及的HTML技巧
https://segmentfault.com/a/1190000039240225
2021-02-20T16:29:10+08:00
2021-02-20T16:29:10+08:00
前端荣耀
https://segmentfault.com/u/lengxing
47
<p><img src="/img/remote/1460000039240227" alt="" title=""></p><blockquote>2021年你需要知道的HTML标签和属性</blockquote><p>Web开发人员都在广泛的使用HTML。无论你使用什么框架或者选择哪个后端语言,框架在变,但是HTML始终如一。尽管被广泛使用,但还是有一些标签或者属性是大部分开发者不熟知的。虽然现在有很多的模版引擎供我们使用,但是我们还是需要尽可能的熟练掌握HTML内容,就像CSS一样。</p><p>在我看来,最好尽可能使用HTML特性来实现我们的功能,而不是使用JavaScript实现相同的功能,尽管我承认编写HTML可能会是重复的和无聊的。</p><p>尽管许多开发人员每天都在使用HTML,但他们并没有尝试改进自己的项目,也没有真正利用HTML的一些鲜为人知的特性。</p><p>下面这5个通过HTML标签/属性实现的功能我觉得需要了解一下:</p><h3>图片懒加载</h3><p>图片懒加载可以帮助提升网站的性能和响应能力。图片懒加载可以避免立即加载那些不在屏幕中立即显示的图片素材,当用户滚动临近图片时再去开始加载。</p><p>换言之,当用户滚动到图片出现时再进行加载,否则不加载。这就降低了屏幕内容展示过程中的图片素材的请求数量,提升了站点性能。</p><p>往往我们都是通过javascript来实现的,通过监听页面滚动事件来确定加载对应的资源。但是,在不完全考虑兼容性的场景下,我们其实可以直接通过HTML来直接实现。</p><blockquote>注:本篇的提到的标签和属性的兼容性需要大家根据实际场景来选取是否使用</blockquote><p>可以通过为图片文件添加<code>loading="lazy"</code>的属性来实现:</p><pre><code class="html"><img src="image.png" loading="lazy" alt="lazy" width="200" height="200" /></code></pre><h3>输入提示</h3><p>当用户在进行输入搜索功能时,如果能够给出有效的提示,这会大大提升用户体验。输入建议和自动完成功能现在到处可见,我们可以使用Javascript添加输入建议,方法是在输入框上设置事件侦听器,然后将搜索到的关键词与预定义的建议相匹配。</p><p>其实,HTML也是能够让我们来实现预定义输入建议功能的,通过<code><datalist></code>标签来实现。需要注意的是,使用时这个标签的id属性需要和input元素的list属性一致。</p><pre><code class="html"><label for="country">请选择喜欢的国家:</label>
<input list="countries" name="country" id="country">
<datalist id="countries">
<option value="UK">
<option value="Germany">
<option value="USA">
<option value="Japan">
<option value="India">
<option value=“China”>
</datalist></code></pre><h3>Picture标签</h3><p>你是否遇到过在不同场景或者不同尺寸的设备上面的时候,图片展示适配问题呢?我想大家都遇到过。</p><p>针对只有一个尺寸的图片素材的时候,我们往往可以通过CSS的<code>object-fit</code>属性来进行裁切适配。但是有些时候需要针对不同的分辨率来显示不同尺寸的图片的场景的时候,我们是否可以直接通过HTML来实现呢?</p><p>HTML提供了<code><picture></code>标签,允许我们来添加多张图片资源,并且根据不同的分辨率需求来展示不同的图片。</p><pre><code class="html"><picture>
<source media="(min-width:768px)" srcset="med_flower.jpg">
<source media="(min-width:495px)" srcset="small_flower.jpg">
<img src="high_flower" style="width: auto;" />
</picture></code></pre><p>我们可以定义不同区间的最小分辨率来确定图片素材,这个标签的使用有些类似<code><audio></code>和<code><video></code>标签。</p><h3>Base URL</h3><p>当我们的页面有大量的锚点跳转或者静态资源加载时,并且这些跳转或者资源都在统一的域名的场景时,我们可以通过<code><base></code>标签来简化这个处理。<br>例如,我们有一个列表需要跳转到微博的不同大V的主页,我们就可以通过设置<code><base></code>来简化跳转路径</p><pre><code class="html"><head>
<base href="https://www.weibo.com/" target="_blank">
</head>
<body>
<a href="jackiechan">成龙</a>
<a href="kukoujialing">贾玲</a>
</body></code></pre><p><code><base></code>标记必须具有<code>href</code>和<code>target</code>属性。</p><h3>页面重定向(刷新)</h3><p>当我们希望实现一段时间后或者是立即重定向到另一个页面的功能时,我们可以直接通过HTML来实现。</p><p>我们经常会遇到有些站点会有这样一个功能,“5s后页面将跳转”。这个交互可以嵌入到HTML中,直接通过<code><meta></code>标签,设置<code>http-equiv="refresh"</code>来实现</p><pre><code class="html"><meta http-equiv="refresh" content="4; URL='https://google.com' /></code></pre><p>这里<code>content</code>属性指定了重定向发生的秒数。值得一提的是,尽管谷歌声称这种形式的重定向和其他的重定向方式一样可用,但是使用这种类型的重定向其实并不是那么的优雅,往往会显得很突兀。<br>因此,最好在某些特殊的情况下使用它,比如在长时间用户不活动之后再重定向到目标页面。</p><h3>后记</h3><p>HTML和CSS是非常强大的,哪怕我们仅仅使用这两种技术也能创建出一些奇妙的网站。虽然它们的使用量很大很普遍,还是有很多的开发者并没有真正的深入了解他们,还有很多的内容需要我们深入的去学习和理解,实践,有很多的技巧等待着我们去发现。</p>
前端H5 Video常见场景浅析
https://segmentfault.com/a/1190000023524620
2020-08-06T15:53:53+08:00
2020-08-06T15:53:53+08:00
前端荣耀
https://segmentfault.com/u/lengxing
15
<h4>1.原生H5 video标签</h4><pre><code class="javascript"><video id="mse" autoplay=true playsinline controls="controls">
<source src="实机视频地址" type="video/mp4">
你的浏览器不支持Video标签
</video></code></pre><h4>2.第三方插件video.js</h4><pre><code class="javascript">_this.player = videojs(
_this.videoNode,
{
autoplay: true,
bigPlayButton : false,
controls: true,
preload: 'auto',
poster: poster,
notSupportedMessage: '视频加载失败,请刷新再试试',
sources: [
{
src: videoUrl,
type: 'video/mp4',
},
],
},
function onPlayerReady() {
this.play();
}
)
<video
ref={(node) => (this.videoNode = node)}
className="video-js vjs-big-play-centered"
preload="auto"
autoplay="autoplay"
playsinline='true'
webkit-playsinline='true'
x5-video-player-type='h5'
x5-video-player-fullscreen='false'
x5-video-orientation='portraint'
></video></code></pre><h5>2.1 支持原生H5 video标签的所有配置参数,并且更加丰富的配置。</h5><h5>2.2 多环境兼容性</h5><h4>3.业务开发中的场景</h4><p>目前基本表现良好</p><h5>3.1 自动播放实现</h5><h6>3.1.1 非微信端</h6><p>目前主要方法是在videojs 的onPlayerReady回调中调用play方法,以及特殊环境下需要用户手动触发</p><h6>3.1.2 微信端</h6><p>微信端(特别是ios)为了能够实现自动播放功能,目前主要通过增加微信WeixinJSBridgeReady事件回调的方式来触发</p><pre><code class="javascript">document.addEventListener("WeixinJSBridgeReady", function () {
this.player.play();
}, false);</code></pre><h4>4.播放过程</h4><p><img src="/img/remote/1460000023524624" alt="image-20200721141051863" title="image-20200721141051863"></p><blockquote>一次播放三次请求</blockquote><p>请求头信息</p><p><img src="/img/remote/1460000023524623" alt="image-20200721141223269" title="image-20200721141223269"></p><p>响应信息</p><p><img src="/img/remote/1460000023524625" alt="image-20200721141320415" title="image-20200721141320415"></p><p><code>range: bytes=0-</code> 首部信息,该信息用于检测服务端是否支持 Range 请求</p><p><code>Accept-Ranges</code>首部(并且它的值不为 “none”),那么表示该服务器支持范围请求</p><p><code>Content-Length</code> 也是有效信息,因为它提供了要下载的视频的完整大小</p><p><code>Content-Range</code> 响应首部则表示这一部分内容在整个资源中所处的位置</p><p>range - 可以分片段请求,此时的Content-Range则返回的对应请求区间的大小</p><h4>5.其他场景</h4><h5>5.1 如何实现视频本地预览</h5><p>视频本地预览的功能主要利用 <code>URL.createObjectURL()</code> 方法来实现。URL.createObjectURL() 静态方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的 URL。这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。</p><pre><code class="html"><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>视频本地预览示例</title>
</head>
<body>
<input type="file" accept="video/*" onchange="loadFile(event)" />
<video
id="previewContainer"
controls
width="480"
height="270"
style="display: none;"
></video>
<script>
const loadFile = function (event) {
const reader = new FileReader();
reader.onload = function () {
const output = document.querySelector("#previewContainer");
output.style.display = "block";
output.src = URL.createObjectURL(new Blob([reader.result]));
};
reader.readAsArrayBuffer(event.target.files[0]);
};
</script>
</body>
</html></code></pre><h5>5.2 如何实现播放器截图</h5><p>播放器截图功能主要利用 <code>CanvasRenderingContext2D.drawImage()</code> API 来实现。Canvas 2D API 中的 CanvasRenderingContext2D.drawImage() 方法提供了多种方式在 Canvas 上绘制图像。</p><p>drawImage API 的语法如下:</p><pre><code class="javascript">void ctx.drawImage(image, dx, dy);
void ctx.drawImage(image, dx, dy, dWidth, dHeight);
void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);</code></pre><pre><code class="html"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>播放器截图示例</title>
</head>
<body>
<video id="video" controls="controls" width="460" height="270" crossorigin="anonymous">
<!-- 请替换为实际视频地址 -->
<source src="请替换为实际视频地址" />
</video>
<button onclick="captureVideo()">截图</button>
<script>
let video = document.querySelector("#video");
let canvas = document.createElement("canvas");
let img = document.createElement("img");
img.crossOrigin = "";
let ctx = canvas.getContext("2d");
function captureVideo() {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
img.src = canvas.toDataURL();
document.body.append(img);
}
</script>
</body>
</html></code></pre><h6>5.3 如何实现 Canvas 播放视频</h6><p>使用 Canvas 播放视频主要是利用 <code>ctx.drawImage(video, x, y, width, height)</code> 来对视频当前帧的图像进行绘制,其中 video 参数就是页面中的 video 对象。所以如果我们按照特定的频率不断获取 video 当前画面,并渲染到 Canvas 画布上,就可以实现使用 Canvas 播放视频的功能。</p><pre><code class="html"><!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>使用 Canvas 播放视频</title>
</head>
<body>
<video id="video" controls="controls" style="display: none;">
<!-- 请替换为实际视频地址 -->
<source src="请替换为实际视频地址" />
</video>
<canvas
id="myCanvas"
width="460"
height="270"
style="border: 1px solid blue;"
></canvas>
<div>
<button id="playBtn">播放</button>
<button id="pauseBtn">暂停</button>
</div>
<script>
const video = document.querySelector("#video");
const canvas = document.querySelector("#myCanvas");
const playBtn = document.querySelector("#playBtn");
const pauseBtn = document.querySelector("#pauseBtn");
const context = canvas.getContext("2d");
let timerId = null;
function draw() {
if (video.paused || video.ended) return;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(video, 0, 0, canvas.width, canvas.height);
timerId = setTimeout(draw, 0);
}
playBtn.addEventListener("click", () => {
if (!video.paused) return;
video.play();
draw();
});
pauseBtn.addEventListener("click", () => {
if (video.paused) return;
video.pause();
clearTimeout(timerId);
});
</script>
</body>
</html></code></pre>
URLSearchParams初体验
https://segmentfault.com/a/1190000019099536
2019-05-07T15:51:14+08:00
2019-05-07T15:51:14+08:00
前端荣耀
https://segmentfault.com/u/lengxing
24
<blockquote>在之前发的<a href="https://link.segmentfault.com/?enc=%2FdPUJ82NJwt94Fyw6xaR5g%3D%3D.iYXCO46RvB1r4uAOC74uDVc1lPiZj9zO8Q7E4bX1s5dxoby2RfzWsYhrYfSsLu7M" rel="nofollow">工具方法文章</a>的留言中有人就关于验证网址的操作时可以使用URL对象,之后有人提到了URLSearchParams这个URL对象接口。由于之前没有接触过,所以搜索了一下具体的用处,发现这个接口的功能很实用,特此整理分享一下。</blockquote><h2>URLSearchParams是什么</h2><p><code>URLSearchParams</code> 接口定义了一些实用的方法来处理 <code>URL</code> 的查询字符串。<a href="https://link.segmentfault.com/?enc=CTJzotTdIim%2ByXyzdINiLA%3D%3D.QIagjCIYcE8Q%2FCliq9lwKK%2F0dy%2B3N6gxrxnKLJFzmo0MteOBQLAmnBz%2BCQKZ1gkSPJ%2FAWlstG2UdqzL2vEtolIeOUuqMKFGuGHskoO6O7Ck%3D" rel="nofollow">参照</a></p><p>从而我们可以知道,它是用来处理URL相关的。那具体都有哪些功能呢?</p><h2>接口方法</h2><p>首先,我们调用<code>new URLSearchParams()</code>会返回一个 <code>URLSearchParams</code> 对象实例。在这个实例下面我们可以调用以下方法:</p><p><strong>append(name, value)</strong>:插入一个指定的键/值对作为新的搜索参数。<br>其中<code>name</code>是需要插入搜索参数的键名,<code>value</code>是需要插入搜索参数的对应值。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//添加第二个foo搜索参数。
params.append('foo', 4);
params.toString();
// 'foo=1&bar=2&foo=4'</code></pre><p><strong>delete(name)</strong>:从搜索参数列表里删除指定的搜索参数及其对应的值。<br><code>name</code>是需要删除的键值名称。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//添加第二个foo搜索参数。
params.delete('foo');
params.toString();
// 'bar=2'</code></pre><p><strong>entries()</strong>:返回一个<code>iterator</code>可以遍历所有键/值对的对象。</p><pre><code>// 创建一个测试用 URLSearchParams 对象
let searchParams = new URLSearchParams("key1=value1&key2=value2");
// 显示键/值对
for(var pair of searchParams.entries()) {
console.log(pair[0]+ ', '+ pair[1]);
}
// key1, value1
// key2, value2</code></pre><p><strong>get(name)</strong>:获取指定搜索参数的第一个值。<br><code>name</code>是将要返回的参数的键名。返回一个<code>USVString</code>;如果没有,则返回<code>null</code>。<br>如果一个页面的URL是 <code>https://example.com/?name=Jonathan&age=18</code> ,你可以这样解析参数<code>name</code>和<code>age</code>:</p><pre><code>let params = new URLSearchParams(document.location.search.substring(1));
let name = params.get("name"); // is the string "Jonathan"
let age = parseInt(params.get("age"), 10); // is the number 18
// 查找一个不存在的键名则返回 null:
let address = params.get("address"); // null</code></pre><p><strong>getAll(name)</strong>:获取指定搜索参数的所有值,返回是一个数组。<br><code>name</code>是返回的参数的名称。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//为foo参数添加第二个值
params.append('foo', 4);
console.log(params.getAll('foo'))' //输出 ["1","4"].</code></pre><p><strong>has(name)</strong>:返回 Boolean 判断是否存在此搜索参数。<br><code>name</code> 是我们要查询的参数的键名。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
params.has('bar') === true; //true</code></pre><p><strong>keys()</strong>:返回iterator 此对象包含了键/值对的所有键名。</p><pre><code>// 建立一个测试用URLSearchParams对象
let searchParams = new URLSearchParams("key1=value1&key2=value2");
// 输出键值对
for(var key of searchParams.keys()) {
console.log(key);
}
// key1
// key2</code></pre><p><strong>set(name, value)</strong>:设置一个搜索参数的新值,<em>假如原来有多个值将删除其他所有的值</em>。<br>其中<code>name</code>是需要插入修改参数的键名,<code>value</code>是需要插入搜索参数的新值。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//Add a third parameter.
params.set('baz', 3);</code></pre><p><strong>sort()</strong>: 按键名排序。</p><pre><code>// Create a test URLSearchParams object
let searchParams = new URLSearchParams("c=4&a=2&b=3&a=1");
// Sort the key/value pairs
searchParams.sort();
// Display the sorted query string
console.log(searchParams.toString());
// a=2&a=1&b=3&c=4</code></pre><p><strong>toString()</strong>:返回搜索参数组成的字符串,可直接使用在URL上。</p><pre><code>let url = new URL('https://example.com?foo=1&bar=2');
let params = new URLSearchParams(url.search.slice(1));
//Add a second foo parameter.
params.append('foo', 4);
console.log(params.toString());
//Prints 'foo=1&bar=2&foo=4'.</code></pre><p><strong>values()</strong>:返回iterator 此对象包含了键/值对的所有值。</p><pre><code>// 创建一个测试用URLSearchParams对象
let searchParams = new URLSearchParams("key1=value1&key2=value2");
// 输出值
for(var value of searchParams.values()) {
console.log(value);
}</code></pre><p>上面就是针对其所有的接口方法进行的一个梳理。然而,感觉好像和我们平时的关联没有很大呢?下面让我们来看几个具体的使用场景。</p><h2>使用场景</h2><h3>场景一 获取浏览器地址参数</h3><p>我们之前在获取浏览器地址参数时很多时候是通过对地址进行分割,然后拼接字段对象的方式来做的,类似</p><pre><code>function GetRequest() {
let url = location.search; //获取url中"?"符后的字串
let theRequest = new Object();
if (url.indexOf("?") != -1) {
let str = url.substr(1);
strs = str.split("&");
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = (strs[i].split("=")[1]);
}
}
return theRequest;
}</code></pre><p>但是我们如果使用<code>URLSearchParams</code>时就不用这么繁琐了</p><pre><code>const params = new URLSearchParams(location.search)
params.get(key)</code></pre><h3>使用URLSearchParams处理axios发送的数据</h3><p>在我们使用<code>axios</code>和<code>fetch</code>来替换之前的<code>ajax</code>进行数据请求时,我们会遇到数据格式不一致的问题。</p><pre><code>axios({
method: 'post',
url: '/test',
data: {
name: 'li lei',
age: 18
}
})</code></pre><p>上面的调用方法和我们使用ajax时非常相似,我们可能也会自然而然的这样来写,但是我们会发现,其默认的数据格式是有差别的:</p><p>axios数据格式:<br><img src="https://user-gold-cdn.xitu.io/2019/5/7/16a91491e8cb50ac?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="axios数据格式" title="axios数据格式"></p><p>ajax数据格式:<br><img src="https://user-gold-cdn.xitu.io/2019/5/7/16a91491e9d58899?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="akax数据格式" title="akax数据格式"></p><p>是的,多了一层包裹,这样和我们后端的对接就出现问题了。哪怕是手动去修改ContentType为<code>application/x-www-form-urlencoded</code>仍然没有解决。</p><p><img src="https://user-gold-cdn.xitu.io/2019/5/7/16a91491e8e0e041?imageView2/0/w/1280/h/960/format/webp/ignore-error/1" alt="axios数据格式修改后" title="axios数据格式修改后"></p><p>那么URLSearchParams能如何解决呢</p><pre><code>let params = new URLSearchParams();
params.append('name', 'li lei');
params.append('age', 18);
axios({
method: 'post',
url: '/test',
data: params
})</code></pre><h4>兼容性解决</h4><p>工具好用,但是不可避免的兼容性并没有那么的理想。那我们该怎么办呢?</p><blockquote>工欲善其事,必先利其器</blockquote><p><a href="https://link.segmentfault.com/?enc=5oNcY1KlS%2BMcyj6cqwUizg%3D%3D.8CsVjSMEs40KsMaYu%2F%2FlrGE52kBR9HFdkLmlaNFser3lad479T7bqr%2FNqnic6pt6uBfldSxpr7Meci0T5kQLWQ%3D%3D" rel="nofollow">url-search-params-polyfill</a></p><p>这是一个专门为URLSearchParams制作的polyfill库。具体的使用方法大家可以参照库的相关说明。在这主要再强调一下,这个库能够解决浏览器的兼容性问题,但是在使用fetch进行请求调用时,我们仍然需要手动去设置ContentType的值。引用该库中给到的一个实例</p><pre><code>function myFetch(url, { headers = {}, body }) {
headers = headers instanceof Headers ? headers : new Headers(headers);
if (body instanceof URLSearchParams) {
headers.set('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
}
fetch(url, {
headers,
body
});
}</code></pre><h2>小结</h2><p>通过我们对官方接口的说明以及实际场景的使用,详细了解了URLSearchParams的主要功能和使用方法,希望能够在我们以后的开发中起到帮助作用。</p><hr><p>参考资料:<br><a href="https://link.segmentfault.com/?enc=FlmY05gq%2BQC49dBd9bJJsA%3D%3D.5bRelirlQqgpXq%2BSUDPHeQDBUVXJktc3dEKnKQLVX9xXyxoWrqzjr8YvUqATAzlGI9jue8i2i4v0X49abDtRSOKIY7%2B1yh%2F%2FcOZst4mixTo%3D" rel="nofollow">URLSearchParams API</a><br><a href="https://link.segmentfault.com/?enc=OP1dSuVrMjFlL29osQQhCQ%3D%3D.yy4TDXygwjsMIREBhM%2BqN0IX7zLN7aVSbdRUiX2iZdkicDwDFzS0AA7zG0QCbGpU" rel="nofollow">使用URLSearchParams处理axios发送的数据</a></p><hr><blockquote><strong>或者可以去<a href="https://link.segmentfault.com/?enc=EmDT4PbUDqzUyDqwd5MXHw%3D%3D.olzqnxE1eKSx6xwPnNSRLNrPYIJjDgwGwmCfmTAEoMdZeCdMo9AxZqMazThxiTAF" rel="nofollow">Github</a>上面给个Star</strong></blockquote>
JavaScript中十种一步拷贝数组的方法
https://segmentfault.com/a/1190000018947028
2019-04-22T14:30:20+08:00
2019-04-22T14:30:20+08:00
前端荣耀
https://segmentfault.com/u/lengxing
46
<p><img src="/img/remote/1460000018947031" alt="十种一步拷贝数组的方法" title="十种一步拷贝数组的方法"></p>
<p>JavaScript中我们经常会遇到拷贝数组的场景,但是都有哪些方式能够来实现呢,我们不妨来梳理一下。</p>
<h2>1、扩展运算符(浅拷贝)</h2>
<p>自从ES6出现以来,这已经成为最流行的方法。它是一个很简单的语法,但是当你在使用类似于React和Redux这类库时,你会发现它是非常非常有用的。</p>
<pre><code class="javascript">numbers = [1, 2, 3];
numbersCopy = [...numbers];</code></pre>
<blockquote>这个方法不能有效的拷贝多维数组。数组/对象值的拷贝是通过引用而不是值复制。</blockquote>
<pre><code class="javascript">// 😊
numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// 只修改了我们希望修改的,原数组不受影响
// 😢
nestedNumbers = [[1], [2]];
numbersCopy = [...nestedNumbers];
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 由于公用引用,所以两个数组都被修改了,这是我们不希望的</code></pre>
<h2>2、for()循环(浅拷贝)</h2>
<p>考虑到函数式编程变得越来越流行,我认为这种方法可能是最不受欢迎的。</p>
<pre><code class="javascript">
numbers = [1, 2, 3];
numbersCopy = [];
for (i = 0; i < numbers.length; i++) {
numbersCopy[i] = numbers[i];
}
</code></pre>
<blockquote>这个方法不能有效的拷贝多维数组。因为我们使用的是<code>=</code>运算符,它在处理数组/对象值的拷贝时通过引用而不是值复制。</blockquote>
<pre><code class="javascript">// 😊
numbersCopy.push(4);
console.log(numbers, numbersCopy);
// [1, 2, 3] and [1, 2, 3, 4]
// 😢
nestedNumbers = [[1], [2]];
numbersCopy = [];
for (i = 0; i < nestedNumbers.length; i++) {
numbersCopy[i] = nestedNumbers[i];
}
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1, 300], [2]]
// [[1, 300], [2]]
// 由于公用引用,所以两个数组都被修改了,这是我们不希望的
</code></pre>
<h2>3、while()循环(浅拷贝)</h2>
<p>和<code>for()</code> 类似。</p>
<pre><code class="javascript">numbers = [1, 2, 3];
numbersCopy = [];
i = -1;
while (++i < numbers.length) {
numbersCopy[i] = numbers[i];
}</code></pre>
<h2>4、Array.map(浅拷贝)</h2>
<p>上面的<code>for</code>和<code>while</code>都是很“古老”的方式,让我们继续回到当前,我们会发现<code>map</code>方法。<code>map</code>源于数学,是将一个集合转换成另一种集合,同时保留结构的概念。</p>
<p>在英语中,它意味着<code>Array.map</code> 每次返回相同长度的数组。</p>
<pre><code class="javascript">numbers = [1, 2, 3];
double = (x) => x * 2;
numbers.map(double);</code></pre>
<p>当我们使用<code>map</code>方法时,需要给出一个<code>callback</code>函数用于处理当前的数组,并返回一个新的数组元素。</p>
<p><strong>和拷贝数组有什么关系呢?</strong></p>
<p>当我们想要复制一个数组的时候,只需要在<code>map</code>的<code>callback</code>函数中直接返回原数组的元素即可。</p>
<pre><code class="javascript">numbers = [1, 2, 3];
numbersCopy = numbers.map((x) => x);</code></pre>
<p>如果你想更数学化一点,<code>(x) => x</code>叫做<code>恒等式</code>。它返回给定的任何参数。</p>
<pre><code class="javascript">identity = (x) => x;
numbers.map(identity);
// [1, 2, 3]</code></pre>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<h2>5、Array.filter(浅拷贝)</h2>
<p><code>Array.filter</code>方法同样会返回一个新数组,但是并不一定是返回同样长度的,这和我们的过滤条件有关。</p>
<pre><code class="javascript">[1, 2, 3].filter((x) => x % 2 === 0)
// [2]</code></pre>
<p>当我们的过滤条件总是true时,就可以用来实现拷贝。</p>
<pre><code class="javascript">numbers = [1, 2, 3];
numbersCopy = numbers.filter(() => true);
// [1, 2, 3]</code></pre>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<h2>6、Array.reduce(浅拷贝)</h2>
<p>其实用<code>reduce</code>来拷贝数组并没有展示出它的实际功能,但是我们还是要将其能够拷贝数组的能力说一下的</p>
<pre><code class="javascript">numbers = [1, 2, 3];
numbersCopy = numbers.reduce((newArray, element) => {
newArray.push(element);
return newArray;
}, []);</code></pre>
<p><code>reduce()</code> 方法对数组中的每个元素执行一个由您提供的<code>reducer</code>函数,将其结果汇总为单个返回值。</p>
<p>上面我们的例子中初始值是一个空数组,我们在遍历原数组的时候来填充这个空数组。该数组必须要从下一个迭代函数的执行后被返回出来。</p>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<h2>7、Array.slice(浅拷贝)</h2>
<p><code>slice</code> 方法根据我们指定的start、end的index从原数组中返回一个浅拷贝的数组。</p>
<pre><code class="javascript">[1, 2, 3, 4, 5].slice(0, 3);
// [1, 2, 3]
// Starts at index 0, stops at index 3
// 当不给定参数时,就返回了原数组的拷贝
numbers = [1, 2, 3, 4, 5];
numbersCopy = numbers.slice();
// [1, 2, 3, 4, 5]</code></pre>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<h2>8、JSON.parse & JSON.stringify(深拷贝)</h2>
<p><code>JSON.stringify</code>将一个对象转成字符串;<br><code>JSON.parse</code>将转成的字符串转回对象。</p>
<p>将它们组合起来可以将对象转换成字符串,然后反转这个过程来创建一个全新的数据结构。</p>
<pre><code class="javascript">nestedNumbers = [[1], [2]];
numbersCopy = JSON.parse(
JSON.stringify(nestedNumbers)
);
numbersCopy[0].push(300);
console.log(nestedNumbers, numbersCopy);
// [[1], [2]]
// [[1, 300], [2]]
// These two arrays are completely separate!</code></pre>
<blockquote>这个可以安全地拷贝深度嵌套的对象/数组</blockquote>
<h3>几种特殊情况</h3>
<p>1、如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;</p>
<pre><code class="javascript">var test = {
name: 'a',
date: [new Date(1536627600000), new Date(1540047600000)],
};
let b;
b = JSON.parse(JSON.stringify(test))
console.log(b)</code></pre>
<p>2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;</p>
<pre><code class="javascript">const test = {
name: 'a',
date: new RegExp('\\w+'),
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.log('ddd', test, copyed)</code></pre>
<p>3、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;</p>
<pre><code class="javascript">const test = {
name: 'a',
date: function hehe() {
console.log('fff')
},
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)</code></pre>
<p>4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null</p>
<p>5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;</p>
<pre><code class="javascript">function Person(name) {
this.name = name;
console.log(name)
}
const liai = new Person('liai');
const test = {
name: 'a',
date: liai,
};
// debugger
const copyed = JSON.parse(JSON.stringify(test));
test.name = 'test'
console.error('ddd', test, copyed)</code></pre>
<blockquote>参考文章:<a href="https://link.segmentfault.com/?enc=htOyxHvqG0nD%2FprSB5wB3w%3D%3D.lohwMfXqq8M8zubdGl1Guy3pJ9O34HvTWJSQ7spvfnQQltDdaH2jpYwpcBAmAl5H" rel="nofollow">关于JSON.parse(JSON.stringify(obj))实现深拷贝应该注意的坑</a>
</blockquote>
<h2>9、Array.concat(浅拷贝)</h2>
<p><code>concat</code>将数组与值或其他数组进行组合。</p>
<pre><code class="javascript">[1, 2, 3].concat(4); // [1, 2, 3, 4]
[1, 2, 3].concat([4, 5]); // [1, 2, 3, 4, 5]</code></pre>
<p>如果我们不指定参数或者提供一个空数组作为参数,就可以进行浅拷贝。</p>
<pre><code class="javascript">[1, 2, 3].concat(); // [1, 2, 3]
[1, 2, 3].concat([]); // [1, 2, 3]</code></pre>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<h2>10、Array.from(浅拷贝)</h2>
<p>可以将任何可迭代对象转换为数组。给一个数组返回一个浅拷贝。</p>
<pre><code class="javascript">console.log(Array.from('foo'))
// ['f', 'o', 'o']
numbers = [1, 2, 3];
numbersCopy = Array.from(numbers)
// [1, 2, 3]</code></pre>
<blockquote>同样的,处理对象和数组的时候是引用而不是值复制。</blockquote>
<hr>
<h2>小结</h2>
<p>上面这些方法都是在使用一个步骤来进行拷贝。如果我们结合一些其他的方法或技术能够发现还有很多的方式来实现数组的拷贝,比如一系列的拷贝工具函数等。</p>
<hr>
<blockquote>原文地址:<br><a href="https://link.segmentfault.com/?enc=lKZ8qdnBX%2Bl%2FQt0YHkg%2FSA%3D%3D.AGvb3iRHQLQwjt%2FHc7s7t0ypK09T1cgl1J92%2B0xAlANaOJNwOguAB0Q%2Fjbl3Ih5wwcpUY6TqDyh5H6oyQEwmdINS%2BvFHoRBePy7SKnGjRHO1olhJQKUXeF2Xa9YHvhsI" rel="nofollow">How to clone an array in JavaScript</a>
</blockquote>
<hr>
<blockquote>
<strong>关注微信公众号同步推送更新</strong><br><img src="https://segmentfault.com/img/bVbrzrM?w=258&h=258" alt="图片描述" title="图片描述"><p><strong>或者可以去<a href="https://link.segmentfault.com/?enc=UxJ3668Oa6Qpv%2BKnxTGsDQ%3D%3D.jAV8S0n1UeAREzObd2nh8DanBZxc1yPGcXLbXXwY%2BjmjziliTkTRgAGJVI1HK231" rel="nofollow">Github</a>上面给个Star</strong></p>
</blockquote>
11个教程中不常被提及的JavaScript小技巧
https://segmentfault.com/a/1190000018897633
2019-04-17T15:12:08+08:00
2019-04-17T15:12:08+08:00
前端荣耀
https://segmentfault.com/u/lengxing
174
<blockquote>这次我们主要来分享11个在日常教程中不常被提及的JavaScript小技巧,他们往往在我们的日常工作中经常出现,但是我们又很容易忽略。</blockquote>
<h2>1、过滤唯一值</h2>
<p><code>Set</code>类型是在<code>ES6</code>中新增的,它类似于数组,但是成员的值都是唯一的,没有重复的值。结合扩展运算符(...)我们可以创建一个新的数组,达到过滤原数组重复值的功能。</p>
<pre><code class="javascript">const array = [1, 2, 3, 3, 5, 5, 1];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // [1, 2, 3, 5]</code></pre>
<p>在ES6之前,我们如果想要实现这个功能的话,需要的处理代码要多很多。<br>这个技巧的适用范围是数组中的数值的类型为:<code>undefined</code>, <code>null</code>, <code>boolean</code>, <code>string</code>, <code>number</code>。当包涵<code>object</code>, <code>function</code>, <code>array</code>时,则不适用。</p>
<h2>2、短路求值(Short-Circuit Evaluation)</h2>
<p>三目运算符是一个很方便快捷的书写一些简单的逻辑语句的方式,</p>
<pre><code class="javascript">x > 100 ? 'Above 100' : 'Below 100';
x > 100 ? (x > 200 ? 'Above 200' : 'Between 100-200') : 'Below 100';</code></pre>
<p>但是有些时候当逻辑复杂之后,三目运算符书写起来可读性也会很难。这个时候,我们就可以使用逻辑与(&&)和逻辑或(||)运算符来改写我们的表达式。</p>
<p>逻辑与和逻辑或操作符总是先计算其做操作数,只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。这被称为“短路求值(Short-Circuit Evaluation)”</p>
<h3>工作原理</h3>
<p>与(&&)运算符将会返回第一个<code>false/‘falsy’</code>的值。当所有的操作数都是<code>true</code>时,将返回最后一个表达式的结果。</p>
<pre><code class="javascript">let one = 1, two = 2, three = 3;
console.log(one && two && three); // Result: 3
console.log(0 && null); // Result: 0</code></pre>
<p>或(||)运算符将返回第一个<code>true/‘truthy’</code>的值。当所有的操作数都是<code>false</code>时,将返回最后一个表达式的结果。</p>
<pre><code class="javascript">let one = 1, two = 2, three = 3;
console.log(one || two || three); // Result: 1
console.log(0 || null); // Result: null</code></pre>
<h3>场景举例</h3>
<p>当我们从服务器端请求数据的过程中,我们在另一个位置来使用这个数据,但是获取数据的状态并不知道,如我们访问<code>this.state</code>的<code>data</code>属性。按照常规的方式我们会先去判断这个<code>this.state.data</code>的有效性,之后根据有效性情况分别进行区分处理。</p>
<pre><code class="javascript">if (this.state.data) {
return this.state.data;
} else {
return 'Fetching Data';
}</code></pre>
<p>但是我们可以通过上面的方式来简写这个逻辑处理</p>
<pre><code class="javascript">return (this.state.data || 'Fetching Data');</code></pre>
<p>对比发现这个方式更加的简洁方便。</p>
<h2>3、转换Boolean型</h2>
<p>常规的boolean型值只有 <code>true</code> 和 <code>false</code>,但是在JavaScript中我们可以将其他的值认为是 <code>‘truthy’</code> 或者 <code>‘falsy’</code>的。</p>
<p>除了<code>0</code>, <code>“”</code>, <code>null</code>, <code>undefined</code>, <code>NaN</code> 和 <code>false</code>,其他的我们都可以认为是<code>‘truthy’</code>的。</p>
<p>我们可以通过负运算符<code>!</code>将一系列的变量转换成“boolean”型。</p>
<pre><code class="javascript">const isTrue = !0;
const isFalse = !1;
const alsoFalse = !!0;
console.log(isTrue); // Result: true
console.log(typeof true); // Result: "boolean"</code></pre>
<h2>4、转换String型</h2>
<p>我们可以通过<code>+</code>连接运算符将一个number类型的变量转换成string类型。</p>
<pre><code class="javascript">const val = 1 + "";
console.log(val); // Result: "1"
console.log(typeof val); // Result: "string"</code></pre>
<h2>5、转换Number类型</h2>
<p>和上面对应的,我们可以通过加法运算符<code>+</code>将一个string类型的变量转回为number 类型的</p>
<pre><code class="javascript">let int = "15";
int = +int;
console.log(int); // Result: 15
console.log(typeof int); Result: "number"</code></pre>
<p>在某些上下文中,<code>+</code>将被解释为连接操作符,而不是加法操作符。当这种情况发生时(您希望返回一个整数,而不是浮点数),您可以使用两个波浪号:<code>~~</code>。(需要注意为英文格式)</p>
<p>一个波浪号<code>~</code>,被称为“按位不运算符”,等价于 <code>- n - 1</code>。所以<code>~15 = -16</code>.</p>
<p>使用两个<code>~~</code>可以有效的否定运算。这是因为 <code>- (- n - 1) - 1 = n + 1 - 1 = n</code>。也就是说 <code>~-16 = 15</code></p>
<pre><code class="javascript">const int = ~~"15"
console.log(int); // Result: 15
console.log(typeof int); Result: "number"</code></pre>
<h2>6、快速求幂</h2>
<p>从<code>ES7</code>开始,我们可以使用幂运算符 <code>**</code> 作为求幂的简写,相对之前的<code>Math.pow(2, 3)</code> 更加快捷。这是一个很简单实用的点,但是大部分的教程并不会专门介绍它。</p>
<pre><code class="javascript">console.log(2 ** 3); // Result: 8</code></pre>
<p>这不应该与 <code>^</code> 符号混淆,<code>^</code> 符号通常用于表示指数,但在JavaScript中是位XOR操作符。</p>
<p>在ES7之前,幂的简写主要依靠的是位左移位操作符 <code><<</code>,几种写法的区别</p>
<pre><code class="javascript">// 下面几种写法是等价的
Math.pow(2, n);
2 << (n - 1);
2**n;</code></pre>
<p>其中需要注意的是 <code>2 << 3 = 16 等价于 2 ** 4 = 16</code></p>
<h2>7、快速Float转Integer</h2>
<p>我们平时可以使用<code>Math.floor(), Math.ceil()和Math.round()</code>将<code>float</code>类型转换成<code>integer</code>类型。</p>
<p>但是还有一种更快的方法可以使用<code>|(位或运算符)</code>将浮点数截断为整数。</p>
<pre><code class="javascript">console.log(23.9 | 0); // Result: 23
console.log(-23.9 | 0); // Result: -23</code></pre>
<p><code>|</code> 的行为取决于处理的是正数还是负数,所以最好只在确定的情况下使用这个快捷方式。</p>
<p>如果n是正数的,则 <code>n | 0</code> 有效地向下舍入。如果n是负数的,它则有效地向上取整。更准确地说,该操作结果是直接删除小数点后的内容,将浮点数截断为整数,和上面提到的其他几个方法是有所区别的。</p>
<p>您还可以使用 <code>~~</code> 来获得相同的舍入效果,如上所述,实际上任何位操作符都会强制浮点数为整数。这些特殊操作之所以有效,是因为一旦强制为整数,值就保持不变。</p>
<h3>使用场景</h3>
<p>位或运算符可以用于从整数的末尾删除任意数量的数字。这意味着我们不必使用这样的代码在类型之间进行转换</p>
<pre><code class="javascript">let str = "1553";
Number(str.substring(0, str.length - 1));</code></pre>
<p>而是可以使用下面的方式来实现我们的功能</p>
<pre><code class="javascript">console.log(1553 / 10 | 0) // Result: 155
console.log(1553 / 100 | 0) // Result: 15
console.log(1553 / 1000 | 0) // Result: 1</code></pre>
<h2>8、类中自动绑定</h2>
<p>我们可以在类中通过使用ES6增加的箭头函数的方式来实现隐形绑定作用域。而按照之前的处理,我们需要显式的去为我们写的方法进行绑定,类似于 <code>this.myMethod = this.myMethod.bind(this)</code>这样。当我们的类中有很多方法时,会增加大量的绑定的代码的书写。现在我们就可以通过箭头函数的方式来简化这个过程。</p>
<pre><code class="javascript">import React, { Component } from React;
export default class App extends Compononent {
constructor(props) {
super(props);
this.state = {};
}
myMethod = () => {
// 隐式绑定
}
render() {
return (
<>
<div>
{this.myMethod()}
</div>
</>
)
}
};</code></pre>
<h2>9、截取数组</h2>
<p>如果您想从数组的末尾删除值,有比使用<code>splice()</code>更快的替代方法。</p>
<p>例如,如果你知道原始数组的长度,就可以通过重新定义它的<code>length</code>属性来实现截取</p>
<pre><code class="javascript">let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array.length = 4;
console.log(array); // Result: [0, 1, 2, 3]</code></pre>
<p>这是一个特别简洁的解决方案。但是,slice()方法的运行时更快。如果速度是你的主要目标,考虑使用下面的方式</p>
<pre><code class="javascript">let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array = array.slice(0, 4);
console.log(array); // Result: [0, 1, 2, 3]</code></pre>
<h2>10、获取数组中的最后的元素</h2>
<p>数组方法slice()可以接受负整数,如果提供它,它将从数组的末尾开始截取数值,而不是开头</p>
<pre><code class="javascript">let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(array.slice(-1)); // Result: [9]
console.log(array.slice(-2)); // Result: [8, 9]
console.log(array.slice(-3)); // Result: [7, 8, 9]</code></pre>
<h2>11、格式化JSON代码</h2>
<p>我们可能在处理一些JSON相关的处理时很多时候都会使用到<code>JSON.stringify</code>,但是你是否意识到它可以帮助缩进JSON呢?</p>
<p><code>stringify()</code>方法接受两个可选参数:一个<code>replacer</code>函数和一个<code>space</code>值,<code>replacer</code>函数可以用来过滤显示的JSON。</p>
<p><code>space</code>值接受一个整数,表示需要的空格数或一个字符串(如<code>'\t'</code>来插入制表符),它可以使读取获取的JSON数据变得容易得多。</p>
<pre><code class="javascript">console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, '\t'));
// Result:
// '{
// "alpha": A,
// "beta": B
// }'</code></pre>
<p>总的来说,我希望当您看到这些技巧时能够和我第一次看到它们一样觉得有用。如果您有自己发现的小技巧,也希望能够留言中写下,我们一起来分享。</p>
<hr>
<blockquote>原文地址(需要友好上网😅):<br><a href="https://link.segmentfault.com/?enc=dfPAMSJ9ZInyENEPX0tevQ%3D%3D.s%2FG0g9IIsONaH2SjdKMO%2Fwub%2BcGXrp72MTIEbVf8G7iwgwtkvUeCLDL3aADlqUAkQKcD0AkpDRojcan%2BrtwczUoFMqMRh3%2B7Fd0UjZ%2FVxSTmpM9eTm2GvPRnCb8DKjlm%2Fd%2BjQSN1DEHXXoSe20Uw5A%3D%3D" rel="nofollow">11 JavaScript Tricks You Won’t Find in Most Tutorials</a>
</blockquote>
<hr>
<blockquote>
<strong>关注微信公众号同步推送更新</strong><br><img src="/img/bVbrzrM?w=258&h=258" alt="图片描述" title="图片描述"><p><strong>或者可以去<a href="https://link.segmentfault.com/?enc=j24flK2ivcWxmPEt4ZHKog%3D%3D.3G%2BWX9m0tgvkU3BYPGVHuInksaXriTI33Q2IdNLgirY1OGADp5K3iXp2koc6SMfL" rel="nofollow">Github</a>上面给个Star</strong></p>
</blockquote>
浅尝Vue.js组件( 二)
https://segmentfault.com/a/1190000018887002
2019-04-16T17:28:08+08:00
2019-04-16T17:28:08+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>插槽(Slot)</h2>
<p>定义一个名child子组件,为该子组件添加内容应该在子组件的template中定义,直接在父组件的<code><child></code>标签中定义的内容不会被渲染。</p>
<p>在子组件中通过加入<code><slot></code>元素占位,便能够渲染父组件中子组件标签中的内容了。</p>
<h3>插槽内容</h3>
<ul>
<li>任何模版代码</li>
<li>HTML代码</li>
<li>其他组件</li>
</ul>
<p>插槽可以有默认内容,当在父组件中没有提供内容的时候,来进行显示。</p>
<pre><code><!-- submit-button -->
<button type="submit">
<slot>Submit</slot>
</button>
1.
<submit-button></submit-button>
⬇️
<button type="submit">
Submit
</button>
2.
<submit-button>
Save
</submit-button>
⬇️
<button type="submit">
Save
</button></code></pre>
<h3>作用域</h3>
<blockquote><strong>父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。</strong></blockquote>
<h3>具名插槽</h3>
<p>试想,我们有一个带有如下模版的<code><base-layout></code>组件</p>
<pre><code><div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div></code></pre>
<p>可以看到,在组件中显示的内容是划分不同的部位的,这个时候就需要使用到<code><slot></code>元素的一个特有的属性:<code>name</code>来实现了。这个特性可以用来定义额外的插槽。</p>
<pre><code><div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div></code></pre>
<p>一个不带 name 的 <code><slot></code> 出口会带有隐含的名字“default”。</p>
<p>在向具名插槽提供内容的时候,我们可以在一个 <code><template></code> 元素上使用 <code>v-slot</code> 指令,并以 <code>v-slot</code> 的参数的形式提供其名称:</p>
<pre><code><base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout></code></pre>
<p>现在 <code><template></code> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 <code>v-slot</code> 的 <code><template></code> 中的内容都会被视为默认插槽的内容。</p>
<p>当然,也可以将默认插槽的内容通过<code>v-slot:default</code>包裹起来。</p>
<blockquote>
<code>v-slot</code> 只能添加在一个 <code><template></code> 上</blockquote>
<h3>作用域插槽</h3>
<p>当我们希望能够让插槽内容能够访问子组件中才有的数据时,我们可以将数据作为一个<code><slot></code>元素的特性绑定上去</p>
<pre><code><span>
<slot v-bind:user="user">
{{ user.name }}
</slot>
</span></code></pre>
<p>绑定在<code><slot></code>元素上的特性被称为<strong>插槽prop</strong>。此时我们在父组件中通过给<code>v-slot</code>带一个值来定义我们提供的插槽prop的名字。</p>
<pre><code><current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.age }}
</template>
</current-user></code></pre>
<h4>独占默认插槽的缩写语法</h4>
<p>当被提供的内容只有默认插槽时,上面的写法可以被简化来写</p>
<pre><code><!-- 简化版 -->
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
<!-- 终极简化版 -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user></code></pre>
<p>需要注意两点:</p>
<ul>
<li>简化写法不能和具名插槽混用,作用域不明确</li>
<li>出现多个插槽时,所有插槽都使用完整的基于<code><template></code>语法</li>
</ul>
<h4>解构插槽Prop</h4>
<p>作用域插槽的内部工作原理是将你的插槽内容包括在一个传入单个参数的函数里:</p>
<pre><code>function (slotProps) {
// 插槽内容
}</code></pre>
<p>这意味着 <code>v-slot</code> 的值实际上可以是任何能够作为函数定义中的参数的 <code>JavaScript</code> 表达式。所以在支持的环境下 (<strong>单文件组件</strong>或<strong>现代浏览器</strong>),你也可以使用 <strong>ES2015</strong> 解构来传入具体的插槽 <code>prop</code>,如下:</p>
<pre><code><current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
⬇️
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user></code></pre>
<h4>使用场景举例</h4>
<p>插槽 <code>prop</code> 允许我们将插槽转换为可复用的模板,这些模板可以基于输入的 <code>prop</code> 渲染出不同的内容。</p>
<p>这在设计封装数据逻辑同时允许父级组件自定义部分布局的可复用组件时是最有用的。</p>
<h3>动态插槽名</h3>
<p><strong>动态指令参数</strong>也可以用在 <code>v-slot</code> 上,来定义动态的插槽名</p>
<pre><code><base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout></code></pre>
<h3>具名插槽缩写</h3>
<p><code>v-slot</code>可以缩写为<code>#</code>。</p>
<p>缩写方式只有在有参数的时候才可以使用</p>
<pre><code><!-- 这样会触发一个警告 -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
<!-- 这样是正确的 -->
<current-user #default="{ user }">
{{ user.firstName }}
</current-user></code></pre>
<h2>动态组件&keep-alive</h2>
<p>当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。为了解决这个问题,我们可以用一个<code><keep-alive></code>元素将动态组件包裹起来</p>
<pre><code><!-- 失活的组件将会被缓存!-->
<keep-alive>
<component v-bind:is="currentTabComponent"></component>
</keep-alive></code></pre>
<blockquote>注意这个 <code><keep-alive></code> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。</blockquote>
<p>更加详细的说明在我们之后的实战过程中遇到的话,再进行专门解说。</p>
<h2>异步组件</h2>
<p>在实际的项目过程中,我们往往会将一系列的功能分割成一个个小的代码块,希望只有在需要的时候才去加载。为了达成这个目的,Vue允许我们以一个工厂函数的方式定义我们的组件,这个工厂函数会异步解析组件的定义。</p>
<p>Vue只有在这个组件需要渲染的时候才会触发这个工厂函数,而且会把结果缓存起来供之后使用。</p>
<pre><code>Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
// 向 `resolve` 回调传递组件定义
resolve({
template: '<div>I am async!</div>'
})
}, 1000)
})</code></pre>
<p>其实,这个过程有些类似于我们设计一个异步函数,这个工厂函数会收到一个resolve回调,这个回调在我们从服务器获取到组件定义的时候被调用,当加载失败的时候我们也可以调用reject(reason)。</p>
<p>一个推荐的做法是异步组件和webpack的code-splitting功能结合使用</p>
<pre><code>Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve)
})</code></pre>
<p>同样,也可以在工厂函数中返回一个<code>Promise</code></p>
<pre><code>Vue.component(
'async-webpack-example',
// 这个 `import` 函数会返回一个 `Promise` 对象。
() => import('./my-async-component')
)</code></pre>
<h3>处理加载状态</h3>
<p>上面的工厂函数可以返回一个下面格式的对象</p>
<pre><code>const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
})</code></pre>
<hr>
<h2>小结</h2>
<p>本篇我们主要围绕着Vue组件中的插槽和相关动态组件、异步组件的内容进行了梳理。通过这两篇的整理,我们对于Vue组件有了一个比较整体的了解。后续我们会在实战过程中针对具体的点再进行详细的说明。</p>
浅尝Vue.js组件(一)
https://segmentfault.com/a/1190000018886952
2019-04-16T17:26:09+08:00
2019-04-16T17:26:09+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>组件名</h2>
<p>1、使用 kebab-case</p>
<pre><code>Vue.component('my-component-name', { /* ... */ })</code></pre>
<p>2、使用 PascalCase</p>
<pre><code>Vue.component('MyComponentName', { /* ... */ })</code></pre>
<p>当使用 PascalCase (首字母大写命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。也就是说 <my-component-name> 和 <MyComponentName> 都是可接受的。</p>
<blockquote>直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的</blockquote>
<h2>组件注册</h2>
<h3>全局注册</h3>
<pre><code>Vue.component('my-component-name', {
// ... 选项 ...
})</code></pre>
<p>注册之后可以用在任何新创建的Vue跟实例(new Vue)的模版中。</p>
<h4>基础组件的自动化全局注册</h4>
<p>我们往往有很多功能单一的基础组件,而这些组件有经常会被各个功能组件频繁的用到。这就会导致每个功能组件都会有一长串基础组件的长列表。</p>
<p>如果使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),则可以通过<code>require.context</code>来全局注册这些常用的基础组件。</p>
<pre><code>import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'
const requireComponent = require.context(
// 其组件目录的相对路径
'./components',
// 是否查询其子目录
false,
// 匹配基础组件文件名的正则表达式
/Base[A-Z]\w+\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 获取组件配置
const componentConfig = requireComponent(fileName)
// 获取组件的 PascalCase 命名
const componentName = upperFirst(
camelCase(
// 剥去文件名开头的 `./` 和结尾的扩展名
fileName.replace(/^\.\/(.*)\.\w+$/, '$1')
)
)
// 全局注册组件
Vue.component(
componentName,
// 如果这个组件选项是通过 `export default` 导出的,
// 那么就会优先使用 `.default`,
// 否则回退到使用模块的根。
componentConfig.default || componentConfig
)
})</code></pre>
<blockquote>全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生</blockquote>
<h3>局部注册</h3>
<p>先通过一个普通的对象来定义组件,然后在需要使用的地方通过属性-值的方式来进行定义引入。</p>
<pre><code>var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
</code></pre>
<p>由于是局部注册,所以只在引入的模块内有效,如果希望在子组件中使用,同样需要定义引入。</p>
<pre><code>var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
或者通过 Babel 和 webpack 使用 ES2015 模块
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}</code></pre>
<h4>在模块系统中局部注册</h4>
<p>在上面例子中通过ES2015的模块方式进行了组件的局部注册。我们可以将所有的组件放到同一个目录下,每一个都在一个单独的文件中,然后在需要使用的地方通过模块引入的方式进行局部注册即可。</p>
<h2>Prop</h2>
<p>1、HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。所以在使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名</p>
<pre><code>Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
<!-- 在 HTML 中是 kebab-case 的 -->
<blog-post post-title="hello!"></blog-post></code></pre>
<blockquote>使用字符串模版时则不存在上述限制</blockquote>
<p>2、可以通过直接赋值的方式来给prop传递一个静态的值,也可以通过v-bind的方式来动态传入prop的值。</p>
<p>3、几种数据类型的传入方式</p>
<pre><code>1.数字类型
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:likes="post.likes"></blog-post>
2.布尔值类型
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。-->
<blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>
3.数组类型
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
4.对象类型
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue -->
<!-- 这是一个 JavaScript 表达式而不是一个字符串。-->
<blog-post
v-bind:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
></blog-post>
<!-- 用一个变量进行动态赋值。-->
<blog-post v-bind:author="post.author"></blog-post>
5.对象的全部属性
post: {
id: 1,
title: 'My Journey with Vue'
}
<blog-post v-bind="post"></blog-post>
等价于
<blog-post
v-bind:id="post.id"
v-bind:title="post.title"
></blog-post></code></pre>
<h3>单向数据流</h3>
<p>通过prop传递数据是的父子组件之间形成了<strong>单向下行绑定</strong>,父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。</p>
<p>每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。</p>
<p>两种在子组件中试图改变一个prop的情况:</p>
<p>1、<strong>这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用</strong>。在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:</p>
<pre><code>props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter
}
}</code></pre>
<p>2、<strong>这个 prop 以一种原始的值传入且需要进行转换</strong>。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:</p>
<pre><code>props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}</code></pre>
<blockquote>注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。</blockquote>
<h3>Prop验证</h3>
<p>我们可以为Prop提供一个验证来保证传入的数据类型的有效性。为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。</p>
<pre><code>Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})</code></pre>
<blockquote>注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。</blockquote>
<h4>类型检查</h4>
<p><code>type</code> 可以是下列原生构造函数中的一个:</p>
<ul>
<li>String</li>
<li>Number</li>
<li>Boolean</li>
<li>Array</li>
<li>Object</li>
<li>Date</li>
<li>Function</li>
<li>Symbol</li>
</ul>
<p><code>type</code> 还可以是一个自定义的构造函数,并且通过 <code>instanceof</code> 来进行检查确认。</p>
<pre><code>function Person (firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
Vue.component('blog-post', {
props: {
author: Person
}
})</code></pre>
<h3>非Prop特性</h3>
<h4>替换/合并已有的特性</h4>
<p>对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。<code>class</code> 和 <code>style</code> 特性会将两边的值合并起来。</p>
<h4>禁用特性继承</h4>
<p>如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 <code>inheritAttrs: false</code>。</p>
<blockquote>注意 <code>inheritAttrs: false</code> 选项不会影响 <code>style</code> 和 <code>class</code> 的绑定。</blockquote>
<h2>自定义事件</h2>
<h3>事件名</h3>
<p>不同于组件和 prop,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的)。所以,<strong>始终使用 kebab-case 的事件名</strong></p>
<h3>自定义组件的v-model</h3>
<p>可以通过model选项将v-model的value特性用于不同的目的。</p>
<pre><code>Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
<base-checkbox v-model="lovingVue"></base-checkbox></code></pre>
<p>这里的 <code>lovingVue</code> 的值将会传入这个名为 <code>checked</code> 的 prop。同时当 <code><base-checkbox></code> 触发一个 <code>change</code> 事件并附带一个新的值的时候,这个 <code>lovingVue</code> 的属性将会被更新。</p>
<h3>将原生事件绑定到组件</h3>
<p>可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符来进行绑定。但是如果是一个类似原生元素的组件时,可能组件内部已经被重构了,此时会.native失效。</p>
<p>为了解决这个问题,Vue 提供了一个 $listeners 属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。有了这个 $listeners 属性,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。</p>
<pre><code>Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})</code></pre>
<h3>.sync修饰符</h3>
<p>在前面我们知道,prop是单行向下的数据流方式,子组件不能直接修改prop的值,我们推荐通过<code>update:myPropName</code> 的模式触发事件取而代之。</p>
<pre><code>//父组件监听事件
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
//子组件触发事件
this.$emit('update:title', newTitle)</code></pre>
<p>我们为这种模式提供一个缩写,即 <code>.sync</code> 修饰符:</p>
<pre><code><text-document v-bind:title.sync="doc.title"></text-document></code></pre>
<blockquote>注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,类似 v-model。</blockquote>
<p>当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:</p>
<pre><code><text-document v-bind.sync="doc"></text-document></code></pre>
<p>这样会把 doc 对象中的每一个属性 (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。</p>
<blockquote>将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。</blockquote>
<hr>
<h2>小结</h2>
<p>本次主要围绕组件注册和组件的Prop,以及组件的自定义事件相关内容进行了梳理。我们知道,在Vue.js的开发过程中,我们会面临各种各样的组件相关的处理,组件也是我们开发过程中十分重要的部分,所以关于Vue.js组件相关的内容我们分两次来进行整理。</p>
Vue.js基础拾遗
https://segmentfault.com/a/1190000018886794
2019-04-16T17:20:15+08:00
2019-04-16T17:20:15+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>模版语法</h2>
<h3>插值</h3>
<p>1、Vue.js的数据绑定形式是使用“Mustache”语法(双大括号)的形式,针对Html代码,需要使用<code>v-html</code>指令。</p>
<pre><code><p>Using v-html directive: <span v-html="rawHtml"></span></p></code></pre>
<p>2、Mustache语法不能作用在HTML特性上面,此时需要使用<code>v-bind</code>指令。</p>
<pre><code><div v-bind:id="dynamicId"></div></code></pre>
<h3>指令</h3>
<p>1、一些指令能够接收一个“参数”,在指令名称之后以冒号表示,如<code>v-bind</code>。</p>
<pre><code><a v-bind:href="url">...</a></code></pre>
<p>2、从2.6.0开始,可以用方括号扩起来的Javascript表达式作为一个指令的参数</p>
<pre><code><a v-bind:[attributeName]='url'>...</a></code></pre>
<p>这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data 属性 attributeName,其值为 "href",那么这个绑定将等价于 v-bind:href。</p>
<p>同样,我们可以使用动态参数为一个动态的事件名绑定处理函数</p>
<pre><code><a v-on:[eventName]="doSomething"> ... </a></code></pre>
<blockquote>动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。<br>动态参数表达式有一些语法约束,因为某些字符,例如空格和引号,放在 HTML 特性名里是无效的。</blockquote>
<p>3、修饰符(modifier)是以半角句号<code>.</code>指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。如<code>.prevent</code>修饰符告诉<code>v-on</code>指令对于触发的事件调用<code>event.prevent</code></p>
<pre><code><form v-on:submit.prevent="onSubmit">...</form></code></pre>
<p>4、指令简写</p>
<h4>v-bind指令</h4>
<pre><code><!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a></code></pre>
<h4>v-on指令</h4>
<pre><code><!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a></code></pre>
<h2>计算属性与侦听器</h2>
<p>计算属性是和普通属性一样在模版中绑定的,只不过该属性的值涉及到一系列的计算,并不是单纯的一个属性值。</p>
<p>计算属性的getter函数是没有副作用(side effect)的。</p>
<h3>计算属性VS方法</h3>
<p>我们可以将同一个函数定义为一个方法而不是一个计算属性,两种方式的最终结果是相同的。不同的是<strong>计算属性是基于他们的依赖进行缓存的</strong>。当相关依赖不变的情况下,计算属性是直接获取缓存的结果,不需要执行函数,而方法的写法每当重新渲染时,都会被再次执行。</p>
<h3>计算属性VS侦听属性</h3>
<p>Vue提供了一种更通用的方式来观察和响应Vue实例上的数据变更:<strong>侦听属性</strong>,通过<code>watch</code>来监听属性的变化。但是通常更好的做法是使用计算属性而不是命令式的<code>watch</code>回调。</p>
<p>那么什么时候选择侦听属性更好呢?</p>
<p>当响应数据变化的处理是执行异步或者开销较大的操作时,使用侦听属性是有用的。</p>
<h2>Class与Style绑定</h2>
<p><code>v-bind</code>用于<code>class</code>和<code>style</code>时,Vue.js做了专门的增强,表达式结果的类型除了字符串之外,还可以是对象或数组。</p>
<h3>绑定HTML Class</h3>
<p>1、<code>v-bind:class</code>指令可以与普通的class属性共存。我们也可以绑定一个返回对象的计算属性,这是非常有用的模式。</p>
<p>2、我们可以把一个数组传给<code>v-bind:class</code>,以应用一个class列表。</p>
<p>3、当在一个自定义组件上使用 class 属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。</p>
<h3>绑定内联样式</h3>
<p>1、<code>v-bind:style</code> 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名.</p>
<p>2、直接绑定到一个样式对象通常更好,这会让模板更清晰:</p>
<p>3、对象语法常常结合返回对象的计算属性使用</p>
<p>4、当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。</p>
<p>5、可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值。这样只会渲染数组中最后一个被浏览器支持的值。</p>
<h2>条件渲染</h2>
<p>1、<code>v-if</code> 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。也可以用 v-else 添加一个“else 块”。<code>v-else</code> 元素必须紧跟在带 <code>v-if</code> 或者 <code>v-else-if</code> 的元素的后面,否则它将不会被识别。</p>
<p>2、在 <code><template></code> 元素上使用 <code>v-if</code> 条件渲染分组</p>
<p>3、通过添加一个具有唯一值的 <code>key</code> 属性来表达“两个元素是完全独立的,不要复用他们”。</p>
<p>4、带有 <code>v-show</code> 的元素始终会被渲染并保留在 DOM 中。<code>v-show</code> 只是简单地切换元素的 CSS 属性 <code>display</code>。</p>
<h3>v-if & v-show</h3>
<p><code>v-if</code> 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。</p>
<p><code>v-if</code> 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。</p>
<p>相比之下,<code>v-show</code> 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。</p>
<p>一般来说,<code>v-if</code> 有更高的切换开销,而 <code>v-show</code> 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 <code>v-show</code> 较好;如果在运行时条件很少改变,则使用 <code>v-if</code> 较好。</p>
<p>5、当 <code>v-if</code> 与 <code>v-for</code> 一起使用时,<code>v-for</code> 具有比 <code>v-if</code> 更高的优先级。</p>
<h2>列表渲染</h2>
<p>1、<code>v-for</code> 指令根据一组数组的选项列表进行渲染。<code>v-for</code> 指令需要使用 <code>item in items</code> 形式的特殊语法,<code>items</code> 是源数据数组并且 <code>item</code> 是数组元素迭代的别名。</p>
<p>2、可以用 <code>of</code> 替代 <code>in</code> 作为分隔符,因为它是最接近 JavaScript 迭代器的语法, <code>v-for="item of items"</code></p>
<p>3、可以用<code>v-for</code>来迭代一个对象的属性。<code>v-for="(value, key) in object"</code></p>
<blockquote>在遍历对象时,是按 <code>Object.keys()</code> 的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下是一致的。</blockquote>
<p>4、为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 <code>key</code> 属性。理想的 <code>key</code> 值是每项都有的唯一 <code>id</code>。</p>
<blockquote>不要使用对象或数组之类的非原始类型值作为 v-for 的 key。用字符串或数类型的值取而代之。</blockquote>
<p>5、由于Javascript的限制,Vue不能检测到下面的变动情况:</p>
<ul><li>当利用索引直接设置一个项目时,不会触发状态更新。可以通过下面方式实现:</li></ul>
<pre><code>// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
// 使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名
vm.$set(vm.items, indexOfItem, newValue)</code></pre>
<ul><li>当直接修改数组长度时,不会触发状态更新。可以通过下面方式实现:</li></ul>
<pre><code>vm.items.splice(newLength)</code></pre>
<p>6、Vue 不能检测对象属性的添加或删除</p>
<p>对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性</p>
<p>7、需要为已有对象赋予多个新属性,比如使用 Object.assign() 或 _.extend()。在这种情况下,你应该用两个对象的属性创建一个新的对象。</p>
<pre><code>// ❌
Object.assign(vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
// ✅
vm.userProfile = Object.assign({}, vm.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})</code></pre>
<p>8、显示过滤/排序结果,可以通过创建返回过滤或排序数组的计算属性来实现。在计算属性不适用的情况下(如嵌套v-for循环中)可以适用method方法来处理。</p>
<p>9、v-for & v-if</p>
<p>当它们处于同一节点,<code>v-for</code> 的优先级比 <code>v-if</code> 更高,这意味着 <code>v-if</code> 将分别重复运行于每个 <code>v-for</code> 循环中。</p>
<p>10、在自定义组件里,你可以像任何普通元素一样用 <code>v-for</code> 。</p>
<blockquote>2.2.0+ 的版本里,当在组件中使用 <code>v-for</code> 时,<code>key</code> 现在是必须的。</blockquote>
<p>然而,任何数据都不会被自动传递到组件里,因为组件有自己独立的作用域。为了把迭代数据传递到组件里,我们要用 <code>props</code>。</p>
<pre><code>// 一个完整的todolist demo
<div id="todo-list-example">
<form v-on:submit.prevent="addNewTodo">
<label for="new-todo">Add a todo</label>
<input
v-model="newTodoText"
id="new-todo"
placeholder="E.g. Feed the cat"
>
<button>Add</button>
</form>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">Remove</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Do the dishes',
},
{
id: 2,
title: 'Take out the trash',
},
{
id: 3,
title: 'Mow the lawn'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
</code></pre>
<h2>事件处理</h2>
<h3>事件修饰符</h3>
<ul>
<li>.stop</li>
<li>.prevent</li>
<li>.capture</li>
<li>.self</li>
</ul>
<pre><code><!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
</code></pre>
<blockquote>使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。</blockquote>
<ul><li>
<p>.once(2.1.4 new)</p>
<pre><code><!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a></code></pre>
</li></ul>
<p>不像其它只能对原生的 DOM 事件起作用的修饰符,.once 修饰符还能被用到自定义的组件事件上。</p>
<ul><li>
<p>.passive(2.3.0 new)</p>
<pre><code><!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div> // 2.3.0 new</code></pre>
</li></ul>
<p>.passive 修饰符尤其能够提升移动端的性能</p>
<blockquote>不要把 .passive 和 .prevent 一起使用,因为 .prevent 将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive 会告诉浏览器你不想阻止事件的默认行为。</blockquote>
<h3>按键修饰符</h3>
<p>在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符</p>
<pre><code><!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit"></code></pre>
<p>Vue提供了常用的按键码的别名</p>
<ul>
<li>.enter</li>
<li>.tab</li>
<li>.delete (捕获“删除”和“退格”键)</li>
<li>.esc</li>
<li>.space</li>
<li>.up</li>
<li>.down</li>
<li>.left</li>
<li>.right</li>
</ul>
<h3>系统修饰键(2.1.0 new)</h3>
<p>可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。</p>
<ul>
<li>.ctrl</li>
<li>.alt</li>
<li>.shift</li>
<li>.meta</li>
</ul>
<blockquote>请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17。</blockquote>
<h4>.exact修饰符(2.5.0 new)</h4>
<p>.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。</p>
<pre><code><!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button></code></pre>
<h4>鼠标按钮修饰符(2.2.0 new)</h4>
<ul>
<li>.left</li>
<li>.right</li>
<li>.middle</li>
</ul>
<p>这些修饰符会限制处理函数仅响应特定的鼠标按钮。</p>
<h2>表单输入绑定</h2>
<p>你可以用 v-model 指令在表单 <code><input></code>、<code><textarea></code> 及 <code><select></code> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。</p>
<blockquote>v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。</blockquote>
<p>v-model 在内部使用不同的属性为不同的输入元素并抛出不同的事件:</p>
<ul>
<li>text 和 textarea 元素使用 value 属性和 input 事件;</li>
<li>checkbox 和 radio 使用 checked 属性和 change 事件;</li>
<li>select 字段将 value 作为 prop 并将 change 作为事件。</li>
</ul>
<blockquote>对于需要使用输入法 (如中文、日文、韩文等) 的语言,你会发现 v-model 不会在输入法组合文字过程中得到更新。如果你也想处理这个过程,请使用 input 事件。</blockquote>
<h3>修饰符</h3>
<h4>.lazy</h4>
<p>在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:</p>
<pre><code><!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" ></code></pre>
<h4>.number</h4>
<p>如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:</p>
<pre><code><input v-model.number="age" type="number"></code></pre>
<blockquote>这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。</blockquote>
<h4>.trim</h4>
<p>如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:</p>
<pre><code><input v-model.trim="msg"></code></pre>
<h2>组件基础</h2>
<p>因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。</p>
<p>1、data必须是函数</p>
<p>一个组件的data选项必须是一个函数,因此每个实例才可以维护一份被返回对象的独立的拷贝。</p>
<p>2、组件有两种注册类型:<strong>全局注册</strong>和<strong>局部注册</strong>。我们的组件都只是通过Vue.component全局注册的</p>
<pre><code>Vue.component('my-component-name', {
// ... options ...
})</code></pre>
<p>全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。</p>
<p>3、通过Prop向子组件传递数据</p>
<p>Prop 是你可以在组件上注册的一些自定义特性。当一个值传递给一个 prop 特性的时候,它就变成了那个组件实例的一个属性。一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。</p>
<p>4、每个组件必须只有一个根元素</p>
<p>5、父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件,同时子组件可以通过调用内建的 $emit 方法 并传入事件名称来触发一个事件:</p>
<pre><code><parentElement
...
v-on:special-event="todo something..."></parentElement>
<childElement v-on:click="$emit('special-event')">
Click to trigger special-enent
</childElement></code></pre>
<h3>插槽</h3>
<p>Vue自定义的<code><slot></code>元素可以实现通过插槽分发内容。</p>
<h3>解析DOM时的注意事项</h3>
<p>HTML元素有些会有严格的限制包含和被包含的关系,这会导致我们使用一些有约束条件的元素时会遇到一些问题。此时可以通过<code>is</code>特性来解决</p>
<pre><code><table>
<tr is="blog-post-row"></tr>
</table></code></pre>
<p>下面情况不会出现限制:</p>
<ul>
<li>字符串模版(例如 template:‘...’)</li>
<li>单文件组件(.vue)</li>
<li><code><script type="text/x-template"></code></li>
</ul>
<hr>
<h2>小结</h2>
<p>以上是在重温Vue.js官方文档基础部分内容时做出的拾遗笔记,记录认为是一些需要特别注意的地方。后续会继续深入进行Vue.js的学习。</p>
Web Worker使用初体验
https://segmentfault.com/a/1190000017829832
2019-01-09T15:53:40+08:00
2019-01-09T15:53:40+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<p><img src="/img/remote/1460000017829835?w=225&h=401" alt="效果示意" title="效果示意"></p>
<blockquote>圣诞节为集团活动制作了一款竞速(<code>戳手指</code>)类的H5互动小游戏,在这个的开发过程中第一次体验了Web Worker的功能,感觉还是不错的,整理分享一下。</blockquote>
<h2>使用缘由</h2>
<p>由于这次制作的H5互动小游戏需要针对点击速度进行动效的更新,然后游戏场景中有很多的元素,在使用<code>canvas</code>的过程中,发现在安卓机上面会出现快速点击之后页面卡顿的现象,由于动画的卡顿,导致页面上面的计时器(<code>setInterval</code>)被卡停了,最终出现了安卓机的游戏结果拉了IOS一条街,这不科学啊。</p>
<blockquote>本次H5游戏的相关处理不做主要内容,可能会是由于自己处理优化不够导致的问题,请大家勿深究。</blockquote>
<p>分析原因,应该是页面的动画频繁渲染,导致计时器处理事件被阻塞了,这也是<code>JavaScript</code>处理是单线程的一个直接结果。所以就想到了<code>Web Worker</code>是否可以解决我的问题,来处理计时器的计算呢?</p>
<blockquote>JavaScript 语言采用的是单线程模型,也就是说,所有任务只能在一个线程上完成,一次只能做一件事。前面的任务没做完,后面的任务只能等着。</blockquote>
<h2>Web Worker</h2>
<p>当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成。</p>
<p><code>Web Worker</code> 是运行在后台的 <code>JavaScript</code>,独立于其他脚本,不会影响页面的性能。<br>您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 <code>Web Worker</code> 在后台运行。</p>
<p><code>Web Worker</code>的基本原理就是在当前<code>JavaScript</code>的主线程中,使用<code>Worker</code>类加载一个<code>JavaScript</code>文件来开辟一个新的线程,起到互不阻塞执行的效果,并且提供主线程和新线程之间数据交换的接口:<code>postMessage</code>,<code>onmessage</code>。<br>所以你可以在前台做一些小规模分布式计算之类的工作,不过Web Worker有以下一些使用限制:</p>
<ul>
<li>Web Worker无法访问DOM节点;</li>
<li>Web Worker无法访问全局变量或是全局函数;</li>
<li>Web Worker无法调用alert()或者confirm之类的函数;</li>
<li>Web Worker无法访问window、document之类的浏览器全局变量;</li>
</ul>
<h2>使用Web Worker</h2>
<h3>支持性检测</h3>
<pre><code> if(typeof(Worker)!=="undefined") {
alert('支持worker')
} else {
alert('不支持worker')
}</code></pre>
<h3>创建worker.js</h3>
<pre><code>var self =this;
self.addEventListener('message', function (e) {
var data = e.data;
switch (data.cmd) {
case 'start':
startTimer();
break;
case 'stop':
stopTimer();
break;
default:
break;
};
}, false);
var timerId = null, gameTime = 0;
var startTimer = function () {
timerId = setInterval(function () {
gameTime += 16;
var million = String(gameTime % 1000).substr(0, 2);
var second = String(Math.floor(gameTime / 1000));
second = second.length >= 2 ? second : '0' + second;
self.postMessage({
second: second,
million: million
});
}, 16);
}
var stopTimer = function () {
clearInterval(timerId);
gameTime = 0;
}</code></pre>
<p>以上代码中重要的部分是 postMessage() 方法 - 它用于向 HTML 页面传回一段消息。当然,我的应用场景太简单了,web worker 通常不用于如此简单的脚本,而是用于更耗费 CPU 资源的任务。</p>
<h3>创建Worker对象</h3>
<pre><code> if(typeof(Worker)!=="undefined") {
let worker = new Worker("./worker.js");
} else {
alert('不支持worker')
}</code></pre>
<h3>消息处理</h3>
<pre><code>worker.onmessage = function (event) {
// 接收到worker计算相关的数据
}</code></pre>
<p>当 web worker 传递消息时,会执行事件监听器中的代码。event.data 中存有来自 event.data 的数据。</p>
<h3>错误处理</h3>
<pre><code>worker.onerror(function (event) {
// 错误处理
});</code></pre>
<h3>终止Web Worker</h3>
<pre><code>worker.terminate();</code></pre>
<h2>使用后记</h2>
<p>在使用Web Worker之后,新开一个线程用于计时器的计算,从而基本解决了游戏时间的问题。由于计时器的间隔时间和发送消息的过程,可能还是和实际的间隔时间会有一定的出入,但是从活动来说已经在接受范围内了。</p>
<p>通过这次简单的使用Web Worker,初步理解了其基本原理和使用方法,也为以后的相似情景找到了解决的路径,希望能够对遇到同样问题的朋友有所帮助。</p>
React组件的State
https://segmentfault.com/a/1190000017471161
2018-12-21T14:23:42+08:00
2018-12-21T14:23:42+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<blockquote>组件<code>state</code>必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变都可以从<code>state</code>的变化中反映出来;同时,<code>state</code>还必须代表一个组件UI呈现的最小状态集,即<code>state</code>中的所有状态都用于反映组件UI的变化,没有任何多余的状态,也不应该存在通过其他状态计算而来的中间状态。</blockquote>
<h3>state vs 普通属性</h3>
<p>首先,什么是普通属性?</p>
<p>我们的组件都是使用<code>ES6</code>的<code>class</code>定义的,所以组件的属性其实也就是<code>class</code>的属性(更确切的说法是<code>class</code>实例化对象的属性,但因为JavaScript本质上是没有类的定义的,<code>class</code>只不过是<code>ES6</code>提供的语法糖,所以这里模糊化类和对象的区别)。</p>
<p>在<code>ES6</code>中,可以使用<code>this.{属性名}</code>定义一个<code>class</code>的属性,也可以说属性是直接挂载到<code>this</code>下的变量。因此,<code>state</code>、<code>props</code>实际上也是组件的属性,只不过它们是<code>React</code>为我们在<code>Component class</code>中预定义好的属性。除了<code>state</code>、<code>props</code>以外的其他组件属性称为组件的普通属性。</p>
<p>比如,组件中需要一个定时器来自动更新显示时间时,我们都会需要一个<code>timerId</code>来保存该定时器,已在需要的能够清除它。这就是一个普通的属性,因为它和组件要渲染的内容没有直接关系。</p>
<p>因此,当我们在组件中需要用到一个变量,并且它与组件的渲染无关时,就应该把这个变量定义为组件的普通属性,直接挂载到<code>this</code>下,而不是作为组件的<code>state</code>。或者更直观的判断是,<code>看组件render方法中是否使用到了这个变量,如果没有,它就是一个普通属性</code>。</p>
<h3>state vs props</h3>
<p><code>state</code>和<code>props</code>又有什么区别呢?</p>
<p><code>state</code>和<code>props</code>都直接和组件的UI渲染有关,它们的变化都会触发组件重新渲染,但<code>props</code>对于使用它的组件来说是只读的,是通过父组件传递过来的,要想修改<code>props</code>,只能在父组件中修改;而<code>state</code>是组件内部自己维护的状态,是可变的。</p>
<p>其实区分<code>state</code>和<code>props</code>的关键就是,‘控制权’是在组件自身,还是由其父组件来控制的。</p>
<h3>state的判断依据</h3>
<p>(1)这个变量是否通过<code>props</code>从父组件中获取?如果是,那么它不是一个状态。</p>
<p>(2)这个变量是否在组件的整个生命周期中都保持不变?如果是,那么它不是一个状态。</p>
<p>(3)这个变量是否可以通过其他<code>状态(state)</code>或者<code>属性(props)</code>计算得到?如果是,那么它不是一个状态。</p>
<p>(4)这个变量是否在组件的<code>render</code>方法中使用?如果不是,那么它不是一个状态。这种情况下,这个变量更适合定义为组件的一个普通属性。</p>
<h3>state的修改</h3>
<p>1、state不能直接进行修改</p>
<p>直接赋值形式修改state,不会触发组件的render。修改<code>state</code>需要通过<code>setState</code>方法进行修改。</p>
<pre><code>// 错误
this.state.title = 'React';
// 正确
this.setState({
title: 'React'
})
</code></pre>
<p>2、state的更新是异步的</p>
<p>调用<code>setState</code>时,组件的<code>state</code>并不会立即改变,<code>setState</code>只是把要修改的状态放入一个队列中,<code>React</code>会优化真正的执行时机,并且出于性能原因,可能会将多次<code>setState</code>的状态修改合并成一次状态修改。</p>
<p>所以不要依赖当前的<code>state</code>,计算下一个<code>state</code>。当真正执行状态修改时,依赖的<code>this.state</code>并不能保证是最新的<code>state</code>,因为<code>React</code>会把多次<code>state</code>的修改合并成一次,这时<code>this.state</code>还是这几次<code>state</code>修改前的<code>state</code>。</p>
<p>另外,需要注意的是,同样不能依赖当前的<code>props</code>计算下一个状态,因为<code>props</code>的更新也是异步的。</p>
<p>如,电商类购物车添加购买数量时,如果连续点击两次,就会连续调用两次<code>this.setState({count: this.state.count + 1})</code>,在<code>React</code>合并多次修改为一次的情况下,相当于等价执行了下面的操作:</p>
<pre><code>Object.assign(
previousState,
{count: this.state.count + 1},
{count: this.state.count + 1}
)</code></pre>
<p>于是,最终只会增加一次操作的值。</p>
<p>如果有这样的需求,可以使用另一个接收一个函数作为参数的<code>setState</code>,这个函数有两个参数,第一个是当前最新状态(本次组件状态修改生效后的状态)的前一个状态<code>preState</code>(本次组件状态修改前的状态),第二个参数是当前最新的属性<code>props</code>。代码如下:</p>
<pre><code>this.setState((preState, props) => ({
count: preState.count + 1;
}))</code></pre>
<p>3、state更新是一个合并的过程</p>
<p>我们在每次调用<code>setState</code>修改state时,并不是需要把所有的值都进行修改,而只是设置我们要修改的部分值。<code>React</code>会合并新值导员的组件<code>state</code>中,同时保留没有发生变化的原来的<code>state</code>的值。</p>
<h3>state与不可变对象</h3>
<p><code>React</code>官方建议把<code>state</code>当作不可变对象,一方面,直接修改<code>this.state</code>,组件并不会重新<code>render</code>;另一方面,<code>state</code>中包含的所有状态都应该是不可变对象。当<code>state</code>中的某个状态发生变化时,应该重新创建这个状态对象,而不是直接修改原来的状态。</p>
<p>可以分为下面三种情况:</p>
<p>1.状态的类型是不可变类型(数字、字符串、布尔值、null、undefined)</p>
<pre><code>this.setState({
count: 1, // 数字类型
title: 'React', // 字符串类型
success: true // 布尔类型
})</code></pre>
<p>2.状态的类型是数组</p>
<pre><code>// 方法一:使用preState、concat创建新数组
this.setState(preState => ({
books: preState.books.concat(['React']);
}))
// 方法二:ES6 spread syntax
this.setState(preState => ({
books: [...preState.books, 'React'];
}))</code></pre>
<p>当从数组中截取部分元素作为新的状态时,可使用数组的<code>slice</code>方法:</p>
<pre><code>this.setState(preState => ({
books: preState.books.slice(1,3);
}))</code></pre>
<p>当从数组中过滤部分元素作为新的状态时,可使用数组的<code>filter</code>方法:</p>
<pre><code>this.setState(preState => ({
books: preState.books.filter(item => {
return item !== 'React';
});
}))</code></pre>
<blockquote>注意,不要使用<code>push、pop、shift、unshift、splice</code>等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改的,而<code>concat、slice、filter</code>会返回一个新的数组。</blockquote>
<p>3.状态的类型是普通对象(不包含字符串、数组)</p>
<p>(1)使用<code>ES6</code>的<code>Object.assgin</code>方法:</p>
<pre><code>this.setState(preState => ({
owner: Object.assign({}, preState.owner, {name: 'Tom'});
}))</code></pre>
<p>(2)使用对象扩展语法(object spread properties):</p>
<pre><code>this.setState(preState => ({
owner: {...preState.owner, name: 'Tom'};
}))</code></pre>
<p>总结一下,创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。当然,也可以使用一些<code>Immutable</code>的JS库(如<code>Immutable.js</code>)实现类似的效果。</p>
<p>为什么<code>React</code>推荐组件的状态是不可变对象呢?一方面是因为对不可变对象的修改会返回一个新对象,不需要担心原有对象在不小心的情况下被修改导致的错误,方便程序的管理和调试;另一方面是出于性能考虑,当对象组件状态都是不可变对象时,在组件的<code>shouldComponentUpdate</code>方法中仅需要比较前后两次状态对象的引用就可以判断状态是否真的改变,从而避免不必要的<code>render</code>调用。</p>
React事件处理
https://segmentfault.com/a/1190000017236572
2018-12-03T16:00:23+08:00
2018-12-03T16:00:23+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h3>React中的事件处理</h3>
<p>在<code>React</code>元素中绑定事件有两点需要注意:</p>
<ul>
<li>(1)在<code>React</code>中,事件命名采用驼峰命名方式,而不是<code>DOM</code>元素中的小写字母命名方式。例如<code>onclick</code>要写成<code>onClick</code>,<code>onchange</code>要写成<code>onChange</code>等。</li>
<li>(2)处理事件的响应函数要以对象的形式赋值给事件属性,而不是<code>DOM</code>中的字符串形式。例如在<code>DOM</code>中绑定一个点击事件应该写成:</li>
</ul>
<pre><code><button onclick="clickButton()">
Click
</button></code></pre>
<p>而在<code>React</code>元素中绑定一个点击事件变成这种形式:</p>
<pre><code><button onClick={clickButton}> // clickButton是一个函数
Click
</button></code></pre>
<p><code>React</code>中的事件是合成事件,并不是原生的<code>DOM</code>事件。</p>
<p><code>React</code>根据<code>W3C</code>规范定义了一套兼容各个浏览器的事件对象。在<code>DOM</code>中可以通过返回<code>false</code>来阻止事件的默认行为,但在<code>React</code>中,必须显式的调用事件对象的<code>preventDefault</code>方法来阻止事件的默认行为。</p>
<p>在某些场景下如果必须使用<code>DOM</code>提供的原生事件,可以通过<code>React</code>事件对象的<code>nativeEvent</code>属性获取。</p>
<p>其实,在平时的开发中,<code>React</code>组件中处理事件最容易出错的地方是事件处理函数中的<code>this</code>的指向问题,因为<code>ES6 class</code>并不会为方法自动绑定<code>this</code>到当前对象。</p>
<p>下面我们具体来看一下常见的三种处理<code>this</code>的方式:</p>
<h3>React事件处理的this处理</h3>
<h4>使用箭头函数</h4>
<p>直接在<code>React</code>元素中采用箭头函数定义事件的处理函数,如:</p>
<pre><code>class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
render() {
return (
<button onClick={(event) => {
console.log(this.state.number);
}}>
Click
</button>
)
}
}</code></pre>
<p>箭头函数中的<code>this</code>指向的是函数定义时的对象,所以可以保证<code>this</code>总是指向当前组件的实例对象。</p>
<blockquote>当事件处理逻辑比较复杂时,如果把所有的逻辑直接写在<code>onClick</code>的大括号中,就会导致render函数变的臃肿,不容易直观地看出组件的UI结构,代码可读性也不好。这样,我们可以把逻辑处理封装成组件的一个方法,然后在箭头函数中调用该方法即可。</blockquote>
<pre><code>class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
handleClick(event) {
const number = ++this.state.number;
this.setState({
number: number
});
}
render() {
return (
<button onClick={(event) => {
this.handleClick(event);
}}>
Click
</button>
)
}
}</code></pre>
<p>直接在render方法中为元素事件定义事件处理函数,最大的问题是,每次render调用时,都会重新创建一个新的事件处理函数,带来额外的性能开销,组件所处层级越低,这种开销就越大。当然,大多数情况下,这种开销是可以接受的。</p>
<h4>使用组件方法</h4>
<p>直接将组件的方法赋值给元素的事件属性,同时在类的构造函数中,将这个方法的this绑定到当前对象。如:</p>
<pre><code>class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
const number = ++this.state.number;
this.setState({
number: number
});
}
render() {
return (
<button onClick={this.handleClick}>
Click
</button>
)
}
}</code></pre>
<p>这种方法的好处是每次<code>render</code>不会重新创建一个回调函数,没有额外的性能损失。但在构造函数中,为事件处理函数绑定<code>this</code>,尤其是存在多个事件处理函数需要绑定时,这种模板式的代码还是会显得繁琐。</p>
<p>有时候我们还会为元素的事件属性赋值时,同时为事件处理函数绑定<code>this</code>,例如:</p>
<pre><code>class MyComponent extends React.Component {
……
render() {
return (
/* 事件属性赋值和this绑定同时 */
<button onClick={this.handleClick.bind(this)}>
Click
</button>
)
}
}</code></pre>
<p>使用<code>bind</code>会创建一个新的函数,因此这种写法依然存在每次<code>render</code>都会创建一个新函数的问题。但是在需要为函数传入额外的参数时,这种写法就比较方便了。</p>
<pre><code>class MyComponent extends React.Component {
……
render() {
const type = 1;
return (
/* 事件属性赋值和this绑定同时 */
<button onClick={this.handleClick.bind(this, type)}>
Click
</button>
)
}
}</code></pre>
<h4>属性初始化语法</h4>
<p>使用<code>ES7</code>的<code>property initializers</code>会自动为<code>class</code>中定义的方法绑定<code>this</code>。例如:</p>
<pre><code>class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
handleClick = (event) => {
const number = ++this.state.number;
this.setState({
number: number
});
}
render() {
return (
<button onClick={this.handleClick}>
Click
</button>
)
}
}</code></pre>
<p>这种方式既不需要在构造函数中手动绑定<code>this</code>,也不需要担心组件重复渲染导致的函数重复创建的问题。不过由于<code>property initializers</code> 这个特性还处于试验阶段,默认有些浏览器是不支持的,需要使用<code>babel</code>来进行支持。</p>
<p>通过上面我们可以看到,只要处理好了<code>React</code>组件中函数的<code>this</code>绑定问题,<code>React</code>的事件处理就没有太大的问题了。</p>
React组件的生命周期
https://segmentfault.com/a/1190000017177702
2018-11-28T16:07:59+08:00
2018-11-28T16:07:59+08:00
前端荣耀
https://segmentfault.com/u/lengxing
3
<blockquote>组建从被创建到被销毁的过程称为组件的生命周期。<code>React</code>为组件在不同的生命周期阶段提供了不同的生命周期方法,可以让我们在组件的生命周期过程中更好的控制组件的行为。通常生命周期我们可以分为三个阶段:<br>挂载阶段 -> 更新阶段 -> 卸载阶段</blockquote>
<h3>挂载阶段</h3>
<p>此阶段组件被创建,执行初始化,并被挂载到DOM中,完成组件第一次渲染。</p>
<p><img src="/img/remote/1460000017178249?w=277&h=601" alt="挂载阶段" title="挂载阶段"></p>
<h4>constructor</h4>
<p><code>constructor</code>本身是ES6的<code>class</code>的构造方法,组件被创建时,会首先调用组件的构造方法。这个构造方法会接受一个props参数,props是从父组件传过来的属性对象,如果父组件没有传入属性而组件自身定义了默认属性,那么这个<code>props</code>指向的就是组件的默认属性。</p>
<p>而且很关键的是,你必须在这个方法中先调用<code>super(props)</code>才能保证<code>props</code>被传入组件中,这部分具体的大家参照ES6中<code>class</code>相关内容就可以了。</p>
<p><code>constructor</code>通常用于初始化组件的<code>state</code>以及绑定事件处理函数等。</p>
<h4>componentWillMount</h4>
<p>该方法在组件被挂载到DOM前调用,且只会被调用一次。这个方法实际上很少被用到,相关阶段的处理完全可以放到<code>constructor</code>中。</p>
<blockquote>在这个方法中调用<code>this.setState</code>不会引起组件的重新渲染。</blockquote>
<h4>render</h4>
<p>该方法是组件定义时唯一必要的方法(组件的其他生命周期函数都可以忽略)。在这个方法中,根据组件的<code>props</code>和<code>state</code>返回一个<code>React</code>元素,用于描述组件的UI,通常<code>React</code>元素使用<code>JSX</code>语法定义。<br>需要注意的是,<code>render</code>并不负责组件的实际渲染工作,它只是返回了一个UI的描述,真正的渲染出页面DOM的工作是由<code>React</code>自身负责。<br><code>render</code>是一个纯函数,在这个方法中不能执行任何有<code>‘副作用’</code>的操作,所以不能在render方法中调用<code>this.setState</code>,这会改变组件的状态,导致程序出现问题。</p>
<h4>componentDidMount</h4>
<p>在组件被挂载到DOM后将会调用这个方法,且只会被调用一次。这个时候已经可以获取到DOM结构,因为依赖DOM节点的操作可以放到这个方法中。这个方法通常还会用于向服务器请求数据。在这个方法中调用this.setState会引起组件的重新渲染。</p>
<h3>更新阶段</h3>
<p>组件被挂载到DOM后,组件的<code>props</code>和<code>state</code>可以引起组件更新。<code>props</code>引起的组件更新,本质上是由渲染该组件的父组件引起的,也就是当父组件的<code>render</code>方法被调用时,组件会发生更新过程。这个时候,组件的<code>props</code>的值是否变更是由父组件来决定的,但是,无论<code>props</code>是否变化,父组件<code>render</code>方法每被调用一次,都会导致组件更新。</p>
<p>state引起的更新过程,是通过调用<code>this.setState</code>修改组件的<code>state</code>来触发的。</p>
<p><img src="/img/remote/1460000017178250?w=284&h=721" alt="更新阶段" title="更新阶段"></p>
<h4>componentWillReceiveProps(nextProps)</h4>
<p>看名称也可以猜到,这个方法和<code>props</code>有关。这个方法只会在props引起的组件更新过程中,才会被调用。<br><code>State</code>引起的组件更新并不会触发该方法。方法的参数<code>nextProps</code>是父组件传递给当前组件的新的<code>props</code>。在前面我们也提到过,父组件render方法的调用并不能保证传递给子组件的props发生变化,也就是说<code>nextProps</code>不一定发生变化,因为往往我们需要先比较<code>nextProps</code>和<code>this.props</code>来决定是否执行<code>props</code>发生变化后的逻辑。比如根据新的<code>props</code>来更新<code>state</code>触发组件的重新渲染。</p>
<blockquote>在<code>componentWillReceiveProps</code>中调用<code>setState</code>,只有在组件<code>render</code>及其之后的方法中,<code>this.state</code>指向的才是更新后的<code>state</code>。在<code>render</code>之前的方法(<code>shouldComponentUpdate</code>、<code>componentWillUpdate</code>)中,<code>this.state</code>依然指向更新前的state<p>通过调用<code>setState</code>更新组件状态并不会触发<code>componentWillReceiveProps</code>的调用,否则可能会进入死循环,<code>componentWillReceiveProps</code> -> <code>this.setState</code> -> <code>componentWillReceiveProps</code> -> <code>this.setState</code> -> ……</p>
</blockquote>
<h4>shouldComponentUpdate(nextProps, nextState)</h4>
<p>这个方法决定组件是否继续执行更新过程。当方法返回true时(true也是这个方法的默认返回值),组件会继续更新过程;当方法返回false时,组件的更新过程将停止,后续的<code>componentWillUpdate</code>、<code>render</code>、<code>componentDidUpdate</code>也不会被调用。</p>
<p>一般通过比较<code>nextProps</code>、<code>nextState</code>的组件当前的<code>props</code>、<code>state</code>决定这个方法的返回结果。这个方法可以用来减少组件不必要的渲染,从而优化组件的性能。</p>
<h4>componentWillUpdate(nextProps, nextState)</h4>
<p>这个方法在组件render调用前执行,可以作为组件更新发生前执行某些工作的地方,一般也较少用到。</p>
<blockquote>
<code>shouldComponentUpdate</code>和<code>componentWillUpdate</code>中都不能调用<code>setState</code>,否则会引起循环调用问题,<code>render</code>永远无法被调用,组件也就不能被更新了。</blockquote>
<h4>componentDidUpdate(prevProps, prevState)</h4>
<p>组件更新后调用,可以作为操作更新后的DOM的地方,这个方法的两个参数<code>prevProps</code>和<code>prevState</code>代表组件更新前的<code>props</code>和<code>state</code>。</p>
<h3>卸载阶段</h3>
<p>该过程是组件从DOM中被卸载的过程,该过程只有一个生命周期方法:<code>componentWillUnmount</code></p>
<h4>componentWillUnmount</h4>
<p>这个方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清除组件中使用的定时器,清除<code>componentDidMount中</code>手动创建的DOM元素等,以避免引起内存泄露。</p>
<h3>结语</h3>
<p>上面我们简单梳理分析了<code>React</code>组件的生命周期方法的不同阶段的执行过程,以及每个生命周期方法的功能的作用。最后要提醒大家,只有类组件才具有生命周期方法,函数组件是没有生命周期方法的。</p>
<hr>
<p>参考资料:</p>
<p><a href="https://link.segmentfault.com/?enc=AKP1HW2VM4ZWJKUR8mC%2FOQ%3D%3D.YFv87FUUB%2Bh%2BNgOMQHBBIXAI831xftG%2BUeLODrQTnGo%3D" rel="nofollow">React中文文档</a><br>《React进阶之路》</p>
地图小区景点边界轮廓实现
https://segmentfault.com/a/1190000016539235
2018-09-27T17:14:05+08:00
2018-09-27T17:14:05+08:00
前端荣耀
https://segmentfault.com/u/lengxing
4
<blockquote>经常的我们在使用地图功能时,会发现在选择一个小区或者一个热门景点的时候,地图上面会给出其边界轮廓,能够方便我们知道其范围大小,有时候在我们使用地图组件的时候,也会面临着类似的需求。比如在地图上面标识出一个商场范围内的热力图,一个热门景点的游览情况等。那么,我们该如何利用地图功能来实现这类效果呢,今天我们一起来探讨一下。</blockquote>
<p><img src="/img/remote/1460000016539238?w=988&h=499" alt="效果截图" title="效果截图"></p>
<p>最近我们就有一个需求,需要标识出一些热门场所的人流的热力图情况,同时需要给出该热门场所的边界轮廓。经过查看百度地图和高德地图的开发者API文档,发现并没有这类公共接口提供我们使用。目前地图能够提供我们使用的,基本只能是一些行政区划的边界范围,这个在我之前的文章中也有写过,大家可以参照<a href="https://link.segmentfault.com/?enc=lEVgH5vpk2vG7E3gMj8b6A%3D%3D.U3hu0ZBSUMaOWl%2F9j3Fj9FIncOqrWcGKvs1kZGknMouxvoFILk2GfbEFtgmibcd6" rel="nofollow">《仿链家地图找房的简单实现》</a>。</p>
<p>那么现在面临的需求该如何实现呢?</p>
<p>通过查看地图功能的接口调用情况和在网上查询相关资料,最终我们找到了下面这个“不算是方法的方法”。</p>
<ul>
<li>使用了地图的相关API接口获取相关数据</li>
<li>API接口不是官方给出的,所以也就面临着稳定性的问题,可能随时被关(高德的只能简单参考,本身就存在较大缺陷,后面会说)</li>
</ul>
<h3>实现思路</h3>
<ul>
<li>
<p>通过地图的POI查询服务获取到兴趣点id</p>
<pre><code>那么什么是POI呢?
> 检索服务提供某一特定地区的兴趣点位置查询服务(POI:Point of Interest,感兴趣点)
相关的官方文档请参照以下地址:
- [百度地图POI查询](http://lbsyun.baidu.com/index.php?title=jspopular/guide/search)
- [高德地图POI查询](https://lbs.amap.com/api/webservice/guide/api/search/)
</code></pre>
</li>
<li>通过兴趣点id获取该兴趣点的详细信息<p>这里面需要用到的相关API就需要我们查看地图的执行过程,找到对应的API了。(也希望各个地图官方能够给出官方的方法吧)</p>
</li>
</ul>
<blockquote>PS:地图功能的使用情况在本篇不做说明,具体申请相关Key的过程请分别参照官网说明即可。</blockquote>
<p>下面我们来分别给出百度地图和高德地图的实现方法:</p>
<h3><a href="https://link.segmentfault.com/?enc=bFSfcQJEZrFSVj3gcn9Hjg%3D%3D.kKVMP19SsUx%2FwbjYwTFfL6tuFq7O9D9XPu9FXGRC0Pvoux6MSHad%2FcZ%2BBh24TPft" rel="nofollow">百度地图实现</a></h3>
<p>闲话休谈,咱们直接上码</p>
<pre><code></html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>百度地图DEMO</title>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你申请的AK"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var queryHouseOutline = function(hid, callback) {
var baseURL = 'http://map.baidu.com/?reqflag=pcmap&coord_type=3&from=webmap&qt=ext&ext_ver=new&l=18';
var url = baseURL + "&uid=" + hid;
callback && (window.queryHouseOutlineCallback = callback);
$.ajax({
type: "get",
async: false,
url: url,
dataType: "jsonp",
jsonpCallback: "queryHouseOutlineCallback",
success: function(datas) {}
});
};
/**
* 模糊查询小区信息, 无返回值
* @param {} house 小区名称
* @param {} city 所属城市名称
* @param {} ak 百度地图AK
* @param {} callback 回调函数,该函数可以接收到请求的返回值
*/
var queryHouse = function(house, city, ak, callback) {
var baseURL = 'http://api.map.baidu.com/place/v2/search?output=json&scope=2';
var url = baseURL + "&q=" + house + "&region=" + city + "&ak=" + ak;
callback && (window.queryHouseCallback = callback);
$.ajax({
type: "get",
async: false,
url: url,
dataType: "jsonp",
jsonpCallback: "queryHouseCallback",
success: function(datas) {}
});
};
/**
* 墨卡托坐标转百度坐标
* @param {} coordinate
* @return {}
*/
var coordinateToPoints = function(map, coordinate) {
var points = [];
if (coordinate) {
var arr = coordinate.split(";");
if (arr) {
for (var i = 0; i < arr.length; i++) {
var coord = arr[i].split(",");
if (coord && coord.length == 2) {
var mctXY = new BMap.Pixel(coord[0], coord[1]);
var project = map.getMapType().getProjection();
var point = project.pointToLngLat(mctXY);
points.push(new BMap.Point(point.lng, point.lat));
}
}
}
}
return points;
};
/**
* 墨卡托坐标解析
* @param {} mocator
* @return {}
*/
var parseGeo = function(mocator) {
if (typeof mocator != 'string') {
return {};
}
var t = mocator.split("|");
var n = parseInt(t[0]);
var i = t[1];
var r = t[2];
var o = r.split(";");
if (n === 4) {
for (var a = [], s = 0; s < o.length - 1; s++) {
"1" === o[s].split("-")[0] && a.push(o[s].split("-")[1]);
}
o = a;
o.push("");
}
var u = [];
switch (n) {
case 1:
u.push(o[0]);
break;
case 2:
case 3:
case 4:
for (var s = 0; s < o.length - 1; s++) {
var l = o[s];
if (l.length > 100) {
l = l.replace(/(-?[1-9]\d*\.\d*|-?0\.\d*[1-9]\d*|-?0?\.0+|0|-?[1-9]\d*),(-?[1-9]\d*\.\d*|-?0\.\d*[1-9]\d*|-?0?\.0+|0|-?[1-9]\d*)(,)/g,
"$1,$2;");
u.push(l);
} else {
for (var c = [], d = l.split(","), f = 0; f < d.length; f += 2) {
var p = d[f];
var h = d[f + 1];
c.push(p + "," + h);
}
u.push(c.join(";"))
}
}
break;
default:
break;
}
if (u.length <= 1) {
u = u.toString();
}
var result = {
type: n,
bound: i,
points: u
};
return result;
};
var map = new BMap.Map("allmap"); // 创建Map实例
map.centerAndZoom("北京", 19);
map.addControl(new BMap.MapTypeControl()); //添加地图类型控件
map.enableScrollWheelZoom(false); //开启鼠标滚轮缩放
/**
* 第一个参数是城市名,第二参数是小区名
*/
var showArea = function(city, area) {
queryHouse(area, city, "你申请的AK", function(data) {
if (data.message == 'ok') {
var houses = data.results;
if (houses && houses.length > 0) {
var house = houses[0];
queryHouseOutline(house.uid, function(houseOutline) {
var geo = houseOutline.content.geo;
if (!geo) {
var location = house.location;
var point = new BMap.Point(location.lng, location.lat);
map.centerAndZoom(point, 19);
var marker = new BMap.Marker(point);
marker.setAnimation(BMAP_ANIMATION_BOUNCE);
map.addOverlay(marker);
} else {
map.clearOverlays();
var geoObj = parseGeo(geo);
//边界点
var points = coordinateToPoints(map, geoObj.points);
var ply = new BMap.Polygon(points, {
strokeWeight: 2,
strokeColor: "#F01B2D",
strokeOpacity: 0.9,
fillColor: "transparent"
}); //建立多边形覆盖物
map.addOverlay(ply); //添加覆盖物
map.setViewport(ply.getPath()); //调整视野
}
});
}
}
});
};
showArea($('#cityId').val(), $('#areaId').val());
$('#showBtn').click(function() {
debugger;
showArea($('#cityId').val(), $('#areaId').val());
});
$("#areaId").keydown(function(e) {
if (event.keyCode == "13") {
showArea($('#cityId').val(), $('#areaId').val());
}
})
});
</script>
</head>
<body>
<table>
<tr>
<td>城市:</td>
<td>
<input id="cityId" type="text" value="北京" />
</td>
<td>小区:</td>
<td>
<input id="areaId" type="text" value="故宫博物院" />
</td>
<td>
<button id="showBtn">显示</button>
</td>
</tr>
</table>
<div id="allmap" style="width: 90vw; height: 90vh;"></div>
</body>
</html></code></pre>
<p>相关的代码注释都有所添加,参照即可。其中需要注意的是百度地图获取到的坐标点需要进行转换成百度地图识别的点位形式。<br>另外,边界的描画使用到的是地图的Polygon功能,相关内容<a href="https://link.segmentfault.com/?enc=Bw127j5JgMk7tgBjNz4dWQ%3D%3D.eIFDMV5eMF8hozhgphTCdfa9s6%2FrL0waQ%2BYbibhSqGqcKaCvTd1sYnGWfvMTzgiSZXnHyGMc1gVVEs7ufJCyktv9si1A%2BAVO880EpgFYsz8%3D" rel="nofollow">请参照</a></p>
<h3><a href="https://link.segmentfault.com/?enc=4owl74yLJW5liIHG1Enivw%3D%3D.SOJt34WVekGr76Ipm362p4GcYJlHv1UcIxBDppkZFb%2BheASKs6osWhEG3YRNkB3N" rel="nofollow">高德地图实现</a></h3>
<pre><code></html>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>高德地图DEMO</title>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.10&key=你申请的AK"></script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var map = new AMap.Map('allmap', {
zoom: 19,
center: [116.397428, 39.90923]
}); // 创建Map实例
/**
* 第一个参数是城市名,第二参数是小区名
*/
var showArea = function(city, area) {
queryHouse(area, city, "你申请的AK", function(data) {
console.error(data)
if (data.status == 1) {
var houses = data.pois;
if (houses && houses.length > 0) {
var house = houses[0];
queryHouseOutline(house.id, function(houseOutline) {
console.error("get outline success");
var pathPoints = houseOutline.data.spec.mining_shape.shape;
var tmpPath = pathPoints.split(";");
var points = [];
tmpPath.forEach(function(value, index, array) {
points.push(value.split(","))
});
map.clearMap();
var ply = new AMap.Polygon({
map: map,
path: points,
strokeColor: "#F01B2D",
fillColor: "transparent"
}); //建立多边形覆盖物
map.setFitView(); //调整最佳显示
});
}
}
});
};
var queryHouseOutline = function(hid, callback) {
var baseURL = 'https://www.amap.com/detail/get/detail';
$.ajax({
type: "get",
data: {
id: hid
},
url: baseURL,
dataType: "json",
success: function(datas) {
callback(datas)
}
});
};
/**
* 模糊查询小区信息, 无返回值
* @param {} house 小区名称
* @param {} city 所属城市名称
* @param {} ak 高德地图AK
* @param {} callback 回调函数,该函数可以接收到请求的返回值
*/
var queryHouse = function(house, city, ak, callback) {
var baseURL = 'http://restapi.amap.com/v3/place/text?&keywords=' + house + '&city=' + city + '&output=json&offset=20&page=1&key=' + ak;
callback && (window.queryHouseCallback = callback);
$.ajax({
type: "get",
async: false,
url: baseURL,
dataType: "jsonp",
jsonpCallback: "queryHouseCallback",
success: function(datas) {}
});
};
showArea($('#cityId').val(), $('#areaId').val());
$('#showBtn').click(function() {
showArea($('#cityId').val(), $('#areaId').val());
});
$("#areaId").keydown(function(e) {
if (event.keyCode == "13") {
showArea($('#cityId').val(), $('#areaId').val());
}
})
});
</script>
</head>
<body>
<table>
<tr>
<td>城市:</td>
<td>
<input id="cityId" type="text" value="北京" />
</td>
<td>小区:</td>
<td>
<input id="areaId" type="text" value="故宫博物院" />
</td>
<td>
<button id="showBtn">显示</button>
</td>
</tr>
</table>
<div id="allmap" style="width: 90vw; height: 90vh;"></div>
</body>
</html></code></pre>
<p>高德地图的实现方式根据实际的效果来看,本身应该是做了API接口限制的处理,经常会出现获取不到详细信息或者给出的详细信息中的边界信息数据不准确。<br>这里只是作为一个对比参照,高德地图不推荐来做这个需求,API接口稳定性太差。</p>
<h3>后记</h3>
<p>①百度地图会涉及到功能接口配额的问题</p>
<p><img src="/img/remote/1460000016539239?w=1184&h=830" alt="配额截图" title="配额截图"></p>
<p>主要会涉及到上面的<code>地点检索</code>配额,如果只是个人简单使用的,可以注册个人开发者,基本配额就够使用了</p>
<p>②高德地图没有找到配额相关的数据,毕竟走的非正规手段吧</p>
<p>③百度地图和高德地图对于一些位置的边界数据不同</p>
<p>有些地点只会在其中一个能够获取到(高德地图能够返回数据的情况下)</p>
<p>④高德地图在检索位置时,能够支持全拼音输入,也能检索出来(感觉这个厉害,但是中文多音字处理不知道会怎么样)</p>
<hr>
<p>参考资料:</p>
<p><a href="https://link.segmentfault.com/?enc=lfceADxRrgD1MknJnABMAQ%3D%3D.yBpSoxaao4NbxMFirlO4S3KgQnYQRR3wiQsfa10tfdn%2BqGps3P9We4L6WSpH9BG84knZHSRPAphI2B1HVBfYJg%3D%3D" rel="nofollow">百度地图小区边界(轮廓)处理</a><br><a href="https://link.segmentfault.com/?enc=L5wVF3d2nhWQXWnFz6Vp%2BA%3D%3D.xNWMUaETVuL8xttkzjANTq4KbXTKuNxZStuvSQJxZ9gl0InTOfSjKQDrPoyFRZ7f8zg7sem5Vv9RT%2FAQl358LA%3D%3D" rel="nofollow">高德地图之python爬取POI数据及其边界经纬度(根据关键字在城市范围内搜索)</a></p>
一个关于柯里化函数的实现解析
https://segmentfault.com/a/1190000015705595
2018-07-20T12:01:28+08:00
2018-07-20T12:01:28+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<blockquote>本篇内容主要参考了以下文章:<br><a href="https://link.segmentfault.com/?enc=3ur4iEKiZPyivagDdTJMyw%3D%3D.2OiskBKgj3qdHfR%2BNCqLVGW2lzxXzPgG8%2BpEm0IPG3wJK3mbE5z4TAxaPr15%2BsMN" rel="nofollow">从 sum(2)(3) == sum(2, 3) 到实现柯里化函数</a><br><a href="https://segmentfault.com/a/1190000010608477">JavaScript专题之函数柯里化</a>
</blockquote>
<h2>柯里化定义</h2>
<blockquote>在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。</blockquote>
<h2>一个柯里化函数的实现</h2>
<p>在上面两篇文章中,两位作者都比较详细的分析了柯里化函数的实现方式,特别是<a href="https://segmentfault.com/u/yayu">冴羽</a>的文章中给出了详细的从零开始实现柯里化函数的方法,在这里我就不再赘述了。</p>
<p>在这里我主要针对上面第一个文章中的实现方法,进行一下执行过程的分析。主要是针对其中的具体的一些执行过程单独看代码还是不甚明了,特此详细执行分析一下以作理解,加深记忆。</p>
<h3>实现方式</h3>
<p>这个方式是我感觉最容易接受的</p>
<pre><code>function curry(fn, argLen, currArgs) {
return function() {
console.log("arguments:", arguments, arguments.length)
var args = [].slice.call(arguments);
console.log("args:", args)
// 首次调用时未提供最后一个参数
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归出口
if (args.length == argLen) {
return fn.apply(this, args);
} else {
return curry(fn, argLen, args);
}
}
}
function sumOf(a, b, c, d) {
return a + b + c + d;
}
// 改造普通函数,返回柯里函数
var sum = curry(sumOf, 4);</code></pre>
<p>下面我们分别来看一下几种不同的传参方式的执行过程是怎么样的</p>
<h4>sum(1,2,3,4)</h4>
<pre><code>>sum(1,2,3,4)
arguments: Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 4
args: (4) [1, 2, 3, 4]
</code></pre>
<h4>sum(1,2,3)(4)</h4>
<pre><code>>sum(1,2,3)(4)
arguments: Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] 3
args: (3) [1, 2, 3]
arguments: Arguments [4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 1
args: [4]</code></pre>
<h4>sum(1,2)(3,4)</h4>
<pre><code>>sum(1,2)(3,4)
arguments: Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] 2
args: (2) [1, 2]
arguments: Arguments(2) [3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 2
args: (2) [3, 4]</code></pre>
<p>通过上面针对几种传参方式的执行过程的分析,我们可以清楚地看出其中一些关键变量的内容,这样就让我们能够清楚地理解了柯里化函数的执行过程</p>
<h4>个人优化</h4>
<p>针对上面的执行方式,我们可以看出除了需要被柯里化的函数外,还需要根据这个函数的参数数量传入对应的数量值,这个并不是那么的方便。不妨改进一下</p>
<pre><code>function curry(fn, currArgs) {
return function() {
console.log("arguments:", arguments, arguments.length)
var args = [].slice.call(arguments);
console.log("args:", args)
// 首次调用时未提供最后一个参数
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归出口
if (args.length == fn.length) {
return fn.apply(this, args);
} else {
return curry(fn, args);
}
}
}</code></pre>
<p>这里我们通过利用length 属性指明函数的形参个数,来获取被柯里化函数的参数数量,这样就不需要我们在额外传入了。但是此时需要注意的是,被柯里化的函数的参数不能有默认值,不然的话,length属性就没有意义了。所以这个方式也不是完美的。</p>
<h2>小结</h2>
<p>简单来说,实现柯里化函数的方式基本都会需要根据参数以及递归方式来实现,这样才能让我们通过拆分参数的方式来调用一个多参数的函数方法。这样的好处是减少重复传递不变的部分参数,将柯里化后的callback参数传递给map, filter等函数</p>
<pre><code>var persons = [{name: 'kevin', age: 11}, {name: 'daisy', age: 24}]
let getProp = _.curry(function (key, obj) {
return obj[key]
});
let names2 = persons.map(getProp('name'))
console.log(names2); //['kevin', 'daisy']
let ages2 = persons.map(getProp('age'))
console.log(ages2); //[11,24]</code></pre>
<p>但是也不得不承认,这是个非常高阶的用法,普通的开发场景中很少会被使用,毕竟理解并不是那么的简单。</p>
React之PureComponent
https://segmentfault.com/a/1190000015575024
2018-07-09T15:22:14+08:00
2018-07-09T15:22:14+08:00
前端荣耀
https://segmentfault.com/u/lengxing
9
<h2>React避免重复渲染</h2>
<p>React在渲染出的UI内部建立和维护了一个内层的实现方式,它包括了从组件返回的React元素。这种实现方式使得React避免了一些不必要的创建和关联DOM节点,因为这样做可能比直接操作JavaScript对象更慢一些,它被称之为“虚拟DOM”。</p>
<p>当一个组件的props或者state改变时,React通过比较新返回的元素和之前渲染的元素来决定是否有必要更新实际的DOM。当他们不相等时,React会更新DOM。</p>
<p>在一些情况下,你的组件可以通过重写这个生命周期函数shouldComponentUpdate来提升速度, 它是在重新渲染过程开始前触发的。 这个函数默认返回true,可使React执行更新:</p>
<pre><code>shouldComponentUpdate(nextProps, nextState) {
return true;
}</code></pre>
<h3>举例</h3>
<p>如果想让组件只在<code>props.color</code>或者<code>state.count</code>的值变化时重新渲染,你可以像下面这样设定<code>shouldComponentUpdate</code>:</p>
<pre><code>class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}</code></pre>
<p>在以上代码中,<code>shouldComponentUpdate</code>只检查<code>props.color</code>和<code>state.count</code>的变化。如果这些值没有变化,组件就不会更新。当你的组件变得更加复杂时,你可以使用类似的模式来做一个“浅比较”,用来比较属性和值以判定是否需要更新组件。这种模式十分常见,因此React提供了一个辅助对象来实现这个逻辑 - 继承自<code>React.PureComponent</code>。以下代码可以更简单的实现相同的操作:</p>
<pre><code>class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}</code></pre>
<h2>PureComponent</h2>
<h3>原理</h3>
<p>当组件更新时,如果组件的 props 和 state 都没发生改变, render 方法就不会触发,省去 Virtual DOM 的生成和比对过程,达到提升性能的目的。具体就是 React 自动帮我们做了一层浅比较:</p>
<pre><code>if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}</code></pre>
<p>而 shallowEqual 又做了什么呢?会比较 Object.keys(state | props) 的长度是否一致,每一个 key 是否两者都有,并且是否是一个引用,也就是只比较了第一层的值,确实很浅,所以深层的嵌套数据是对比不出来的。</p>
<h3>问题</h3>
<p>大部分情况下,你可以使用React.PureComponent而不必写你自己的shouldComponentUpdate,它只做一个浅比较。但是由于浅比较会忽略属性或状态<strong>突变</strong>的情况,此时你不能使用它。</p>
<pre><code>class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// This section is bad style and causes a bug
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}</code></pre>
<p>在ListOfWords中,this.props.words是WordAdder中传入的其state的一个引用。虽然在WordAdder的handelClick方法中被改变了,但是对于ListOfWords来说,其引用是不变的,从而导致并没有被更新。</p>
<h3>解决方法</h3>
<p>在上面的问题中可以发现,当一个数据是不变数据时,可以使用一个引用。但是对于一个易变数据来说,不能使用引用的方式给到PureComponent。简单来说,就是我们在PureComponent外层来修改其使用的数据时,应该给其赋值一个新的对象或者引用,从而才能确保其能够进行重新渲染。例如上面例子中的handleClick可以通过以下几种来进行修改从而确认正确的渲染:</p>
<pre><code>handleClick() {
this.setState(prevState => ({
words: prevState.words.concat(['marklar'])
}));
}
或者
handleClick() {
this.setState(prevState => ({
words: [...prevState.words, 'marklar'],
}));
};
或者针对对象结构:
function updateColorMap(oldObj) {
return Object.assign({}, oldObj, {key: new value});
}</code></pre>
<h3>immutable.js</h3>
<p><a href="https://link.segmentfault.com/?enc=htxAKk5EGyAg9apTEhcU4w%3D%3D.hc0elmPgE8%2BeufNp7VX1BpTiLcMD3mXJaSHsqY3xWO57S8YMgnHpWGKedPdP5XYi" rel="nofollow">Immutable.js</a>是解决这个问题的另一种方法。它通过结构共享提供不可突变的,持久的集合:</p>
<ul>
<li>不可突变:一旦创建,集合就不能在另一个时间点改变。</li>
<li>持久性:可以使用原始集合和一个突变来创建新的集合。原始集合在新集合创建后仍然可用。</li>
<li>结构共享:新集合尽可能多的使用原始集合的结构来创建,以便将复制操作降至最少从而提升性能。</li>
</ul>
<pre><code>// 常见的js处理
const x = { foo: 'bar' };
const y = x;
y.foo = 'baz';
x === y; // true
// 使用 immutable.js
const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
x === y; // false</code></pre>
<h2>总结</h2>
<p><code>PureComponent</code> 真正起作用的,只是在一些纯展示组件上,复杂组件使用的话<code>shallowEqual</code> 那一关基本就过不了。另外在使用的过程中为了确保能够正确的渲染,记得 <code>props</code> 和 <code>state</code> 不能使用同一个引用哦。</p>
【ES6系列】Promise
https://segmentfault.com/a/1190000015450833
2018-07-02T16:00:56+08:00
2018-07-02T16:00:56+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>JS 同步与异步</h2>
<blockquote>Javascript语言的执行环境是"单线程"(single thread)。</blockquote>
<p>所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。</p>
<p>这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。</p>
<p>为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。</p>
<p>"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。</p>
<p>"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。</p>
<h2>常见异步处理</h2>
<p>在我们日常的开发中,特别是在ES6之前我们常用的解决异步编程的方式主要有以下几种:</p>
<ul>
<li>回调函数</li>
<li>事件监听</li>
<li>Deferred对象</li>
</ul>
<p>下面我们分别来看一下,假定我们有两个函数<code>f1</code>和<code>f2</code>,后者等待前者的执行结果再执行。</p>
<h3>回调函数</h3>
<pre><code>function f1(callback) {
setTimeout(function() {
// f1的任务代码
callback();
}, 1000);
}
f1(f2)</code></pre>
<p>采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。</p>
<p>回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。</p>
<h3>事件监听</h3>
<pre><code>f1.on("done", f2);
function f1(callback) {
setTimeout(function() {
// f1的任务代码
f1.trigger("done")
}, 1000);
}</code></pre>
<p>这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。</p>
<h3>Deferred对象</h3>
<p>Jquery中提供了Deferred对象的概念,Deferred对象是一个延迟对象,意思是函数延迟到某个点才开始执行,改变执行状态的方法有两个(成功:resolve和失败:reject),分别对应两种执行回调(成功回调函数:done和失败回调函数fail)</p>
<p>这也是我在ES6之前最常用的自定义异步方法所使用的方法。</p>
<pre><code>function f1(callback) {
var $dfd = $.Deferred();
setTineout(function() {
if(任务完成) {
dfd.resolve({
info: "完成"
})
} else {
dfd.reject({
info: "失败"
})
}
}, 1000);
return dfd.promise();
}
f1().then(f2);</code></pre>
<p>可以发现,通过上面的方法可以实现链式的异步调用,而且执行过程更加的清晰,每个处理之间也达到了“去耦合”的效果。</p>
<h2>Promise对象概念</h2>
<p>Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。其实上面的Deferred对象的使用就已经类似于这种处理了。</p>
<p>Promise对象有以下两个特点:</p>
<ul><li>(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。</li></ul>
<p><img src="/img/bVbcZmh?w=551&h=426" alt="图片描述" title="图片描述"></p>
<ul><li>(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。</li></ul>
<p>有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。</p>
<p>Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。</p>
<h2>Promise对象的用法</h2>
<p>ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。</p>
<pre><code>let ajax=function(){
return new Promise(function(resolve,reject){
setTimeout(function () {
resolve()
}, 1000);
})
};
ajax().then(function(){
console.log('promise','resolve');
})</code></pre>
<p>resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。</p>
<p>Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。</p>
<pre><code>ajax().then(function(){
console.log('promise','resolve');
}, function(){
console.log('promise', 'reject')
})</code></pre>
<p>Promise 新建后就会立即执行。</p>
<pre><code>let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved</code></pre>
<h3>ES6 Promise对象与jQuery Deferred对象</h3>
<p>先看例子:</p>
<pre><code>【ES6 Promise】
var pro=new Promise(function(resolve){
resolve(1);
});
//已经resolve了,再设置then回调。
pro.then(function(v){
alert(v); //1
});
alert(2);
//还是会已异步方式,发生回调。
//先alert(2)再alert(v);
//而且,以后什么时候注册then,都会异步调用。
【Jquery Deferred】
var defer=$.Deferred();
defer.resolve(1);
//deferred对象已经resolve了
defer.done(function(v){
alert(v); //不会执行
});
alert(2);
//只执行alert(2);
//如果需要执行done,就要把注册done回调放到defer.resolve()之前。</code></pre>
<p>另外,ES6的Promise没有resolve,reject,notify方法,不能进行状态更改,<br>只能注册回调。</p>
<h3>Promise对象例子</h3>
<p>下面是一个用Promise对象实现的 常见Ajax 操作的例子:</p>
<pre><code>const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});</code></pre>
<h2>Promise.prototype.then()</h2>
<p>Promise.prototype.then()方法返回的是一个新的Promise对象,因此可以采用链式写法,即一个then后面再调用另一个then。如果前一个回调函数返回的是一个Promise对象,此时后一个回调函数会等待第一个Promise对象有了结果,才会进一步调用。</p>
<p><img src="/img/bVbcZpV?w=1279&h=426" alt="图片描述" title="图片描述"></p>
<p>关于上图中黄圈1的对value的处理是Promise里面较为复杂的一个地方,value的处理分为两种情况:Promise对象、非Promise对象。</p>
<p>当value 不是Promise类型时,直接将value作为第二个Promise的resolve的参数值即可;当为Promise类型时,promise2的状态、参数完全由value决定,可以认为promsie2完全是value的傀儡,promise2仅仅是连接不同异步的桥梁。</p>
<p><img src="/img/bVbcZqj?w=1091&h=510" alt="图片描述" title="图片描述"></p>
<pre><code>Promise.prototype.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) { //此处的Promise标注为promise2
handle({
onFulfilled: onFulfilled,
onRejected: onRejected,
resolve: resolve,
reject: reject
})
});
}
function handle(deferred) {
var handleFn;
if (state === 'fulfilled') {
handleFn = deferred.onFulfilled;
} else if (state === 'rejected') {
handleFn = deferred.onRejected;
}
var ret = handleFn(value);
deferred.resolve(ret); //注意,此时的resolve是promise2的resolve
}
function resolve(val) {
if (val && typeof val.then === 'function') {
val.then(resolve); // if val为promise对象或类promise对象时,promise2的状态完全由val决定
return;
}
if (callback) { // callback为指定的回调函数
callback(val);
}
}</code></pre>
<p>关于then的使用</p>
<pre><code>let ajax = function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve()
}, 1000);
})
};
ajax()
.then(function() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve()
}, 2000);
});
})
.then(function() {
console.log('all done');
})</code></pre>
<h2>Promise.prototype.catch()</h2>
<p>Promise.prototype.catch方法是Promise.prototype.then(null, rejection)的别名,用于指定发生错误时的回调函数。</p>
<pre><code>getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});</code></pre>
<p>使用Promise对象的catch方法可以捕获异步调用链中callback的异常,Promise对象的catch方法返回的也是一个Promise对象,因此,在catch方法后还可以继续写异步调用方法。这是一个非常强大的能力。</p>
<pre><code>const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为 y 没有声明
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]</code></pre>
<p>上面代码中,第二个catch方法用来捕获前一个catch方法抛出的错误。</p>
<p>一般来说,不要在then方法里面定义 Reject 状态的回调函数(即then的第二个参数),总是使用catch方法。</p>
<pre><code>// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});</code></pre>
<h2>Promise.prototype.finally()</h2>
<p><code>finally</code>方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。</p>
<pre><code>promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});</code></pre>
<p>finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。</p>
<h2>Promise异步并发</h2>
<p>如果几个异步调用有关联,但它们不是顺序式的,是可以同时进行的,我们很直观地会希望它们能够并发执行(这里要注意区分“并发”和“并行”的概念,不要搞混)。</p>
<h3>Promise.all()</h3>
<p>Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。</p>
<pre><code>let p = Promise.all([p1, p2, p3]);</code></pre>
<p>Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象实例。</p>
<p>p的状态由p1、p2、p3决定,分两种情况:</p>
<ul>
<li>(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。</li>
<li>(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。</li>
</ul>
<p>一个具体的例子:</p>
<pre><code>// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});</code></pre>
<h3>Promise.race()</h3>
<p>Promise.race()也是将多个Promise实例包装成一个新的Promise实例:</p>
<pre><code>let p = Promise.race([p1,p2,p3]);</code></pre>
<p>上述代码中,p1、p2、p3只要有一个实例率先改变状态,p的状态就会跟着改变,那个率先改变的Promise实例的返回值,就传递给p的返回值。如果Promise.all方法和Promise.race方法的参数不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。</p>
<pre><code>const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);</code></pre>
<p>上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。</p>
<h2>Promise.resolve()</h2>
<p>有时候需将现有对象转换成Promise对象,可以使用Promise.resolve()。</p>
<p>如果Promise.resolve方法的参数,不是具有then方法的对象(又称thenable对象),则返回一个新的Promise对象,且它的状态为fulfilled。</p>
<p>如果Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。</p>
<pre><code>var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello</code></pre>
<h2>Promise.reject()</h2>
<p>Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。</p>
<pre><code>var p = Promise.reject('Wrong');
p.then(null, function (s){
console.log(s)
});
// Wrong</code></pre>
<h2>应用举例</h2>
<h3>加载图片</h3>
<pre><code>let preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};</code></pre>
<h2>小结</h2>
<p>在JS开发过程中,异步编程是非常常见和非常重要的处理方式,而ES6中将Promise对象列为标准,方便了我们的开发使用,使得我们不再需要依赖高耦合的方式或者第三方库来完成这一目标。</p>
<p>本篇参照了一些相关文章的内容,具体可见参考部分。</p>
<hr>
<p>相关参考:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=VpiRMD0nFZu3L0PryvDIPQ%3D%3D.Dmp6cQordUmn29JqR9deQiYM3sWVVcVHWRLva9t3PxpiXPhv4AcbaxXmcibmdrNXmekT4X6b9pOOSCCy2JS60w%3D%3D" rel="nofollow">JavaScript 异步编程 与异步式I/O</a></li>
<li><a href="https://link.segmentfault.com/?enc=82MTwtRGUbFhzRS%2BV5WR9g%3D%3D.%2Fh9pKwR4H2Ac9AEN%2B1zbR7IiMsUIdNgExif%2BMNMjKbDUlY6i0BWSXApDhhl4SF54" rel="nofollow">Promise对象</a></li>
<li><a href="https://link.segmentfault.com/?enc=AXWtbIe7b7L7T12SIR5kKQ%3D%3D.2I3h9E9g0oJhJ%2BvRFheUKlnntW9JZ1AYKwKTJ1fNj9phhEtoQmgs33R0BEbMX8%2BaCdTtClIh2Hrc8bi6bIfB7Q%3D%3D" rel="nofollow">谈谈异步编程</a></li>
</ul>
【ES6系列】Proxy和Reflect
https://segmentfault.com/a/1190000015429928
2018-06-29T16:22:23+08:00
2018-06-29T16:22:23+08:00
前端荣耀
https://segmentfault.com/u/lengxing
5
<blockquote>本篇目录</blockquote>
<ul>
<li>
<p>Proxy</p>
<ul>
<li>Proxy基本概念</li>
<li>
<p>常见的Proxy拦截操作</p>
<ul>
<li>get</li>
<li>set</li>
<li>has</li>
<li>deleteProperty</li>
<li>ownKeys</li>
</ul>
</li>
<li>Proxy.revocable()</li>
<li>Proxy的this问题</li>
</ul>
</li>
<li>
<p>Reflect</p>
<ul>
<li>设计目的</li>
<li>相关方法</li>
</ul>
</li>
<li>Proxy和Reflect实现观察者模式</li>
<li>小结</li>
</ul>
<hr>
<blockquote>猪八戒去高老庄找高翠兰,结果高小姐是孙悟空变的,在这个场景中,对于猪八戒来说,孙悟空可以算是高小姐的一个代理,在长相上来说,他们是一致的。猪八戒只能访问到被孙悟空假扮的高小姐,却见不到真正的高小姐。</blockquote>
<h2>Proxy</h2>
<h3>Proxy基本概念</h3>
<p>在上面的场景中,孙悟空就类似于我们今天要讲的ES6中的Proxy,它是一种“代理”,或者可以称之为“拦截”。外界在对一个对象进行访问的时候,都先必须通过这层拦截,才能进行访问。而这个拦截的过程中可以对外界的访问进行过滤和改写。</p>
<p>我们先来看一个例子:</p>
<pre><code>let obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});</code></pre>
<p>在上面这个例子中,我们通过Proxy对一个空对象进行了拦截,重新定义了对象属性的读取(<code>get</code>)和设置(<code>set</code>)行为。</p>
<p>ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。</p>
<pre><code>let proxy = new Proxy(target, handler);</code></pre>
<p>其中,new Proxy()表示生成一个Proxy实例,target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。</p>
<blockquote>注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。</blockquote>
<p>如果handler没有设置任何拦截,那就等同于直接通向原对象。</p>
<pre><code>let target = {};
let handler = {};
let proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"</code></pre>
<h3>常见的Proxy拦截操作</h3>
<p>Proxy支持的拦截操作有:</p>
<ul>
<li>get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。</li>
<li>set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。</li>
<li>has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。</li>
<li>deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。</li>
<li>ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。</li>
<li>getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。</li>
<li>defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。</li>
<li>preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。</li>
<li>getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。</li>
<li>isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。</li>
<li>setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。</li>
<li>apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。</li>
<li>construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。</li>
</ul>
<p>下面我们就几个常见的拦截进行举例说明。</p>
<p>默认一个对象:</p>
<pre><code>let obj={
time:'2017-03-11',
name:'net',
_r:123
};</code></pre>
<h4>get</h4>
<pre><code>// 拦截对象属性的读取
get(target,key){
return target[key].replace('2017','2018')
}</code></pre>
<h4>set</h4>
<pre><code>// 拦截对象设置属性
set(target,key,value){
if(key==='name'){
return target[key]=value;
}else{
return target[key];
}
}</code></pre>
<h4>has</h4>
<pre><code>// 拦截key in object操作
has(target,key){
if(key==='name'){
return target[key]
}else{
return false;
}
}</code></pre>
<h4>deleteProperty</h4>
<pre><code>// 拦截delete
deleteProperty(target,key){
if(key.indexOf('_')>-1){
delete target[key];
return true;
}else{
return target[key]
}
}</code></pre>
<h4>ownKeys</h4>
<pre><code>// 拦截Object.keys,Object.getOwnPropertySymbols,Object.getOwnPropertyNames
ownKeys(target){
return Object.keys(target).filter(item=>item!='time')
}</code></pre>
<h3>Proxy.revocable()</h3>
<p>Proxy.revocable方法返回一个可以取消的Proxy实例</p>
<pre><code>let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked</code></pre>
<p>Proxy.revocable方法返回一个对象,该对象的proxy属性是Proxy实例,revoke属性是一个函数,可以取消Proxy实例。上面代码中,当执行revoke函数之后,再访问Proxy实例,就会抛出一个错误。</p>
<p>Proxy.revocable的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。</p>
<h3>Proxy的this问题</h3>
<p>虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。</p>
<pre><code>const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true</code></pre>
<p>所以在一些情况下,有些对象的属性只能通过正确的this才能拿到时,由于this指向的变化,导致Proxy无法代理目标对象。</p>
<pre><code>const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);
proxy.getDate();
// TypeError: this is not a Date object.</code></pre>
<h2>Reflect</h2>
<p>为什么我们要一起来说Reflect呢?Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。</p>
<h3>设计目的</h3>
<p>(1) 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。</p>
<p>(2) 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false。</p>
<pre><code>// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}</code></pre>
<p>(3) 让Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。</p>
<pre><code>// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true</code></pre>
<p>(4)Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。</p>
<pre><code>Proxy(target, {
set: function(target, name, value, receiver) {
var success = Reflect.set(target,name, value, receiver);
if (success) {
log('property ' + name + ' on ' + target + ' set to ' + value);
}
return success;
}
});</code></pre>
<h3>相关方法</h3>
<p>Reflect与ES5的Object有点类似,包含了对象语言内部的方法,Reflect也有13种方法,与proxy中的方法一一对应。</p>
<blockquote>Proxy相当于去修改设置对象的属性行为,而Reflect则是获取对象的这些行为。</blockquote>
<p>相关使用大家参照Object和Proxy的使用即可,不再一一赘述。</p>
<h2>Proxy和Reflect实现观察者模式</h2>
<blockquote>观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。</blockquote>
<p>在我们之前的开发过程中,我们如果想要实现观察者模式的话,我们可能需要进行事件绑定和触发来实现。</p>
<pre><code>Event.listen('changeName', name => console.log(name))
Event.trigger('changeName', name )</code></pre>
<p>但是在ES6中,我们可以通过使用Proxy和Reflect来实现这个目的</p>
<pre><code>//添加观察者
const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
//proxy 的set 方法
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
//创建proxy代理
const observable = obj => new Proxy(obj, {set});
//被观察的 对象
const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
function print2() {
console.log(`我是二号观察者:${person.name}, ${person.age}`)
}
//添加观察者
observe(print);
observe(print2);
person.name = '李四';
// 输出
// 李四, 20
// 我是二号观察者:李四, 20</code></pre>
<h2>小结</h2>
<p>Proxy和Reflect都是ES6中针对对象新增的方法,Proxy修改设置对象的属性行为,而Reflect则是获取对象的这些行为。</p>
【ES6系列】Set和Map
https://segmentfault.com/a/1190000015290098
2018-06-14T16:19:27+08:00
2018-06-14T16:19:27+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<blockquote>今天,我们来学习一下ES6中新增的两个数据结构:Set和Map。</blockquote>
<h2>Set</h2>
<p>ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是<strong>唯一</strong>的,没有重复的值。</p>
<h3>创建Set数据结构</h3>
<pre><code>let list = new Set();
或者
let array = [1, 2, 2, 4, 5];
let list = new Set(array);</code></pre>
<p>Set实例的创建可以简单通过new方法来实现,同时可以通过传入一个数组格式的参数来实例化。</p>
<h3>size属性</h3>
<p>size属性返回Set实例的成员总数</p>
<pre><code>let array = [1, 2, 2, 4, 5];
let list = new Set(array);
console.log(list.size);
// 4</code></pre>
<h3>add(value)方法</h3>
<p>添加某个值,返回 Set 结构本身</p>
<pre><code>let list = new Set();
list.add(5);
list.add(7);
console.log('size',list.size); // 2</code></pre>
<h3>delete(value)方法</h3>
<p>删除某个值,返回一个布尔值,表示删除是否成功。</p>
<pre><code>let arr=['add','delete','clear','has'];
let list=new Set(arr);
console.log('delete',list.delete('add'),list);
// delete true Set(3) {"delete", "clear", "has"}</code></pre>
<h3>has(value)方法</h3>
<p>返回一个布尔值,表示该值是否为Set的成员。</p>
<pre><code>let arr=['add','delete','clear','has'];
let list=new Set(arr);
console.log('has',list.has('has'));
// has true</code></pre>
<h3>clear()方法</h3>
<p>清除所有成员,没有返回值。</p>
<pre><code>let arr=['add','delete','clear','has'];
let list=new Set(arr);
list.clear();
console.log('list',list);
// list Set(0) {}</code></pre>
<h3>Set的遍历</h3>
<p>Set 结构的实例有四个遍历方法,可以用于遍历成员。</p>
<ul>
<li>keys():返回键名的遍历器</li>
<li>values():返回键值的遍历器</li>
<li>entries():返回键值对的遍历器</li>
<li>forEach():使用回调函数遍历每个成员</li>
</ul>
<pre><code>let arr=['add','delete','clear','has'];
let list=new Set(arr);
for(let key of list.keys()){
console.log('keys',key);
}
// keys add
// keys delete
// keys clear
// keys has
for(let value of list.values()){
console.log('value',value);
}
// keys add
// keys delete
// keys clear
// keys has
for(let [key,value] of list.entries()){
console.log('entries',key,value);
}
// entries add add
// entries delete delete
// entries clear clear
// entries has has
list.forEach(function(item){console.log(item);})
// add
// delete
// clear
// has</code></pre>
<p>通过上面的示例结果可知,Set结构的key和value是一致的。</p>
<blockquote>※ 需要特别指出的是,Set的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。</blockquote>
<h3>Set结构的应用</h3>
<p>在开始我们已经提到过,Set结构的成员都是唯一的,哪怕我们在传入一个有重复成员的数组时,得到的Set实例也是去重的。是的,Set结构非常重要且常见的应用场景就是:<code>数组去重</code>。</p>
<p>首先我们需要知道,Array.from方法可以将 Set 结构转为数组。</p>
<blockquote>Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。</blockquote>
<pre><code>let items = new Set([1, 2, 3, 4, 5]);
let array = Array.from(items);</code></pre>
<p>从而我们可以这样来实现一个数组去重的方法</p>
<pre><code>function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]</code></pre>
<p>另外,在ES6中数组的扩展中还有扩展运算符(...)同样可以使用到Set实例</p>
<pre><code>let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]</code></pre>
<p>小结:通过Set结构可以很方便的来实现数组去重的处理,这在之前我们可能就需要循环判断或者依赖于第三方库来实现了,方便了很多。</p>
<h2>WeakSet</h2>
<p>WeakSet 结构与 Set 类似,也是不重复的值的集合。</p>
<h3>WeakSet与Set的区别:</h3>
<ul>
<li>WeakSet 的成员只能是对象,而不能是其他类型的值。</li>
<li>其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。</li>
</ul>
<p>对于垃圾回收机制这部分大家可以自行查找相关说明,本篇不做论述。</p>
<h3>创建WeakSet结构</h3>
<p>可以通过new命令来创建WeakSet数据结构</p>
<pre><code>let ws = new WeakSet();</code></pre>
<p>WeakSet 可以接受一个数组或类似数组的对象作为参数。该数组的所有成员,都会自动成为 WeakSet 实例对象的成员,所以数组的成员需要是对象。</p>
<pre><code>const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}</code></pre>
<h3>方法</h3>
<ul>
<li>add(value):向 WeakSet 实例添加一个新成员。</li>
<li>delete(value):清除 WeakSet 实例的指定成员。</li>
<li>has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中。</li>
</ul>
<p>相关使用方法可以参照Set的相关方法来使用。<br>另外,WeakSet 没有size属性,没有办法遍历它的成员。</p>
<blockquote>※WeakSet 不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet 的一个用处,是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。</blockquote>
<h2>Map</h2>
<p>在ES5中,对象的键值对中键只能使用字符串,这就带来了很大的限制。在ES6中增加了Map的数据结构,它类似于对象,但是键的范围不仅仅是字符串,而是各种类型的值都可以当做键。</p>
<h3>创建Map数据结构</h3>
<pre><code>let map = new Map();
// map Map(0) {}</code></pre>
<p>作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。</p>
<pre><code>let map = new Map([['a',123],['b',456]]);
console.log('map ',map);
// map Map(2) {"a" => 123, "b" => 456}</code></pre>
<h3>set(key, value)方法</h3>
<p>通过set(key, value)方法为Map结构添加成员</p>
<pre><code>let map = new Map();
let arr=['123'];
map.set(arr,456);
console.log('map',map);
// map Map(1) {Array(1) => 456}</code></pre>
<h3>get(key)方法</h3>
<p>通过get(key)方法获取Map结构中对应key的value值</p>
<pre><code>let map = new Map();
let arr=['123'];
map.set(arr,456);
console.log('map',map,map.get(arr));
// map Map(1) {Array(1) => 456} 456</code></pre>
<h3>size属性、delete(key)方法、has(key)方法和clear()方法</h3>
<p>Ma数据结构的<code>size</code>属性和<code>delete</code>、<code>has</code>以及<code>clear</code>方法,可以参考Set数据结构的对应方法来使用。唯一的区别是<code>delete</code>和<code>has</code>方法的参数是对应的<code>key</code>值。</p>
<pre><code>let map = new Map([['a',123],['b',456]]);
console.log('size',map.size);
// 2
console.log('has',map.has('a'));
// has true
console.log('delete',map.delete('a'),map);
// delete true Map(1) {"b" => 456}
console.log('clear',map.clear(),map);
// clear undefined Map(0) {}</code></pre>
<h3>Map的遍历</h3>
<p>Map结构的遍历基本和Set结构是一样的,具体可以参照上面Set的遍历,此处就不再一一来说了。</p>
<h2>WeakMap</h2>
<p>WeakMap结构与Map结构类似,也是用于生成键值对的集合。相关创建方法基本和Map结构是一致的。</p>
<h3>WeakMap和Map的区别</h3>
<ul>
<li>WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。</li>
<li>WeakMap的键名所指向的对象,不计入垃圾回收机制。</li>
</ul>
<blockquote>※WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。</blockquote>
<h3>方法</h3>
<p>WeakMap只有四个方法:</p>
<ul>
<li>get(key)</li>
<li>set(key, value)</li>
<li>has(key)</li>
<li>delete(key)</li>
</ul>
<p>除了上面四个方法,WeakMap既不能遍历,也没有长度<code>size</code>属性,同时还不能清空<code>clear</code>。</p>
<hr>
<blockquote>数据结构的操作主要集中在增删查改四个方面,下面我们就从这四个方面分别简单将Set/Map和Array/Object来做下对比:</blockquote>
<h2>Set、Map和Array的对比</h2>
<pre><code>let set = new Set();
let map = new Map();
let array = [];
let item = {
t: 5
}
// 增
set.add(item);
map.set("t", 5);
array.push({
t: 5
});
console.log("set-map-array", set, map, array)</code></pre>
<p><img src="/img/bVbcjKi?w=622&h=169" alt="图片描述" title="图片描述"></p>
<pre><code>// 查
let setExist = set.has(item);
let mapExist = map.has("t");
let arrayExist = array.find(item=>item.t);
console.log("setExist-mapExist-arrayExist", setExist, mapExist, arrayExist)</code></pre>
<p><img src="/img/bVbcjKt?w=517&h=59" alt="图片描述" title="图片描述"></p>
<pre><code>// 改
set.forEach(item=>item.t?item.t=10: "");
map.set("t", 10);
array.forEach(item=>item.t?item.t=10: "");
console.log("set-map-array-modify", set, map, array)</code></pre>
<p><img src="/img/bVbcjKv?w=760&h=167" alt="图片描述" title="图片描述"></p>
<pre><code>// 删
set.delete(item);
map.delete("t");
array.splice(array.findIndex(item=>item.t));
console.log("set-map-array-delete", set, map, array)</code></pre>
<p><img src="/img/bVbcjKx?w=520&h=24" alt="图片描述" title="图片描述"></p>
<h2>Set、Map和Object的对比</h2>
<pre><code>let set = new Set();
let map = new Map();
let obj = {};
let item = {
t: 5
}
// 增
set.add(item);
map.set("t", 5);
obj["t"] = 5
console.log("set-map-obj", set, map, obj)</code></pre>
<p><img src="/img/bVbcjNf?w=667&h=163" alt="图片描述" title="图片描述"></p>
<pre><code>// 查
let setExist = set.has(item);
let mapExist = map.has("t");
let objExist = "t" in obj;
console.log("setExist-mapExist-objExist", setExist, mapExist, objExist)</code></pre>
<p><img src="/img/bVbcjNt?w=511&h=23" alt="图片描述" title="图片描述"></p>
<pre><code>// 改
set.forEach(item=>item.t?item.t=10: "");
map.set("t", 10);
obj["t"] = 10;
console.log("set-map-obj-modify", set, map, obj)</code></pre>
<p><img src="/img/bVbcjNv?w=751&h=167" alt="图片描述" title="图片描述"></p>
<pre><code>// 删
set.delete(item);
map.delete("t");
delete obj["t"];
console.log("set-map-obj-delete", set, map, obj)</code></pre>
<p><img src="/img/bVbcjNB?w=504&h=25" alt="图片描述" title="图片描述"></p>
<h2>总结</h2>
<blockquote>通过上面简单的代码示例我们这次学习了ES6中新增的Set和Map数据结构,并且针对Array和Object数据结构进行了简单的对比分析。不难看出,在使用的过程中明显的Set和Map比我们之前经常使用的Array和Object是有明显的便捷优势的。这也给了我们一个建议,后续的开发过程中,我们可以根据场景需要来通过Set和Map来替代Array和Object来使用。</blockquote>
【ES6系列】Symbol
https://segmentfault.com/a/1190000015264105
2018-06-12T15:09:21+08:00
2018-06-12T15:09:21+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>最近在学习一些第三方代码,发现里面出现了Symbol字段,由于之前ES6系列梳理有个小暂停,所以本篇开始针对Symbol进行一下学习。</blockquote>
<h2>JavaScript 数据类型</h2>
<p>在ES6之前,我们所知道的JavaScript 数据类型有:</p>
<ul>
<li>Null</li>
<li>Undefined</li>
<li>布尔值(Boolean)</li>
<li>字符串(String)</li>
<li>数值(Number)</li>
<li>对象(Object)</li>
<li>数组(Array)</li>
</ul>
<h2>Symbol引入</h2>
<p>在我们常规的开发过程中,特别是在多人协作过程中,总是不可避免的会出现互相冲突覆盖的情况。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。此时我们可能会想,如果能有一个不被覆盖,自己独一无二的属性字段该多好啊。从而ES6中引入了Symbol这一个数据类型,来解决这种冲突问题。</p>
<p>Symbol()函数会返回symbol类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的symbol注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:"new Symbol()"。</p>
<p>每个从Symbol()返回的symbol值都是唯一的。一个symbol值能作为对象属性的标识符;这是该数据类型仅有的目的。</p>
<h3>语法结构</h3>
<p><img src="/img/bVbcb22?w=1086&h=282" alt="图片描述" title="图片描述"></p>
<h3>基本使用</h3>
<p>直接使用Symbol()创建新的symbol类型,并用一个字符串(可省略)作为其描述。</p>
<pre><code>let sym1 = Symbol();
let sym2 = Symbol('foo');
let sym3 = Symbol('foo');</code></pre>
<p>上面的代码创建了三个新的symbol类型。Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。注意,Symbol("foo") 不会强制字符串 “foo” 成为一个 symbol类型。它每次都会创建一个新的 symbol类型:</p>
<pre><code>Symbol("foo") === Symbol("foo"); // false</code></pre>
<p>当使用new运算符的语法时,会抛出TypeError错误,这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。</p>
<pre><code>let sym = new Symbol(); // TypeError</code></pre>
<p>如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。</p>
<pre><code>const obj = {
toString() {
return 'abc';
}
};
const sym = Symbol(obj);
sym // Symbol(abc)</code></pre>
<blockquote>注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。</blockquote>
<pre><code>// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2 // false</code></pre>
<p>Symbol 值不能与其他类型的值进行运算,会报错。</p>
<pre><code>let sym = Symbol('My symbol');
"your symbol is " + sym
// TypeError: can't convert symbol to string
`your symbol is ${sym}`
// TypeError: can't convert symbol to string</code></pre>
<p>但是,Symbol值可以显式转为字符串,或是布尔值,但是不能是数值。</p>
<pre><code>let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
Boolean(sym) // true
!sym // false
if (sym) {
// ...
}
Number(sym) // TypeError
sym + 2 // TypeError</code></pre>
<h2>作为属性名的Symbol</h2>
<p>在前面我们也说到了,Symbol的出现就是解决覆盖冲突问题而产生的,它的主要作用也是被用作对象的属性名来使用,因为它是独一无二的,这样就不会出现同名的属性,也就不会出现覆盖的情况了。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。</p>
<pre><code>let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"</code></pre>
<blockquote>注意,Symbol 值作为对象属性名时,不能用点运算符。</blockquote>
<pre><code>const mySymbol = Symbol();
const a = {};
a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"</code></pre>
<p>上面代码中,因为点运算符后面总是字符串,所以不会读取mySymbol作为标识名所指代的那个值,导致a的属性名实际上是一个字符串,而不是一个 Symbol 值。同理,在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。</p>
<pre><code>let s = Symbol();
let obj = {
[s]: function (arg) { ... }
};
obj[s](123);</code></pre>
<p>Symbol 类型还可以用于定义一组常量,保证这组常量的值都是不相等的。</p>
<pre><code>const log = {};
log.levels = {
DEBUG: Symbol('debug'),
INFO: Symbol('info'),
WARN: Symbol('warn')
};
console.log(log.levels.DEBUG, 'debug message');
console.log(log.levels.INFO, 'info message');
// 另一个例子
const COLOR_RED = Symbol();
const COLOR_GREEN = Symbol();
function getComplement(color) {
switch (color) {
case COLOR_RED:
return COLOR_GREEN;
case COLOR_GREEN:
return COLOR_RED;
default:
throw new Error('Undefined color');
}
}</code></pre>
<p>常量使用 Symbol 值最大的好处,就是其他任何值都不可能有相同的值了,因此可以保证上面的switch语句会按设计的方式工作。</p>
<h3>实例:消除魔术字符串</h3>
<blockquote>魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。</blockquote>
<pre><code>function getArea(shape, options) {
let area = 0;
switch (shape) {
case 'Triangle': // 魔术字符串
area = .5 * options.width * options.height;
break;
/* ... more code ... */
}
return area;
}
getArea('Triangle', { width: 100, height: 100 }); // 魔术字符串</code></pre>
<p>上面这类处理我们可能会感觉到很熟悉,这也是日常开发过程中非常常见的处理方式。而上面的字符串<code>Triangle</code>就是一个魔术字符串。</p>
<p>而消除魔术字符串的方法就是可以通过使用Symbol来把它变成一个常量</p>
<pre><code>const shapeType = {
triangle: 'Triangle'
};
function getArea(shape, options) {
let area = 0;
switch (shape) {
case shapeType.triangle:
area = .5 * options.width * options.height;
break;
}
return area;
}
getArea(shapeType.triangle, { width: 100, height: 100 });</code></pre>
<p>这时我们可以发现<code>shapeType.triangle</code>的具体值我们并不关心,但它恰恰又能满足我们的需求。</p>
<blockquote>还有一点需要注意,Symbol 值作为属性名时,该属性还是公开属性,不是私有属性。</blockquote>
<h2>getOwnPropertySymbols遍历</h2>
<p>Symbol 作为属性名,该属性不会出现在for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有 Symbol 属性名。</p>
<pre><code>const obj = {};
let a = Symbol('a');
let b = Symbol('b');
obj[a] = 'Hello';
obj[b] = 'World';
const objectSymbols = Object.getOwnPropertySymbols(obj);
objectSymbols
// [Symbol(a), Symbol(b)]</code></pre>
<h2>Reflect.ownKeys</h2>
<p>Reflect.ownKeys方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。</p>
<pre><code>let obj = {
[Symbol('my_key')]: 1,
enum: 2,
nonEnum: 3
};
Reflect.ownKeys(obj)
// ["enum", "nonEnum", Symbol(my_key)]</code></pre>
<h2>Symbol.for(),Symbol.keyFor()</h2>
<p>在上面使用<code>Symbol()</code>函数的语法,不会在我们整个代码库中创建出一个可用的全局的Symbol类型的变量,但是有时候我们希望能够重新使用同一个Symbol值。要创建跨文件可用的symbol,甚至跨域(每个都有它自己的全局作用域),我们可以使用<code>Symbol.for()</code> 方法和 <code>Symbol.keyFor()</code>方法从全局的Symbol注册表中创建和获取Symbol。</p>
<pre><code>let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true</code></pre>
<p>Symbol()和Symbol.for()的区别是:<code>前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值</code>。</p>
<pre><code>Symbol.for("bar") === Symbol.for("bar")
// true
Symbol("bar") === Symbol("bar")
// false</code></pre>
<p>Symbol.keyFor方法返回一个已登记的 Symbol 类型值的key。</p>
<pre><code>let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo", 已登记的Symbol值
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined, 未登记的Symbol值</code></pre>
<blockquote>Symbol.for为 Symbol 值登记的名字,是全局环境的,可以在不同的 iframe 或 service worker 中取到同一个值</blockquote>
<pre><code>iframe = document.createElement('iframe');
iframe.src = String(window.location);
document.body.appendChild(iframe);
iframe.contentWindow.Symbol.for('foo') === Symbol.for('foo')
// true</code></pre>
<h2>小结</h2>
<p>本次主要针对ES6中新增的数据类型Symbol进行了梳理,关于内置的Symbol值的相关内容,在后续使用到的过程中再进行分享,基本的一些使用大家可以参照<a href="https://link.segmentfault.com/?enc=nMdO4%2FReKiD%2FHDh6WNzzbw%3D%3D.Jz47S7%2FmuHIWLlDVZ5lKibfcL1%2BB0xB8mJAgYE4M1FzcvF3WO8ij4FjbxindVCKi" rel="nofollow">ECMAScript 6入门中Symbol中的讲解</a>。</p>
仿链家地图找房的简单实现
https://segmentfault.com/a/1190000015223683
2018-06-08T10:29:19+08:00
2018-06-08T10:29:19+08:00
前端荣耀
https://segmentfault.com/u/lengxing
7
<blockquote>本篇目录:</blockquote>
<ul>
<li>
<p>使用入门</p>
<ul><li>简单使用流程</li></ul>
</li>
<li>
<p>链家地图找房效果</p>
<ul>
<li>
<p>区域点位气泡</p>
<ul>
<li>数据结构</li>
<li>实现</li>
<li>addOverlay方法</li>
</ul>
</li>
<li>区域边界</li>
<li>获取区域点位经纬度</li>
<li>获取区域边界</li>
</ul>
</li>
<li>小结</li>
</ul>
<blockquote>最近由于项目需要,开始调研如何使用百度地图实现类似于链家的地图找房的功能,从而开始学习百度地图相关内容。后续会根据一些使用到的知识点进行整理记录,以备不时之需吧。</blockquote>
<h2>使用入门</h2>
<p><img src="https://segmentfault.com/img/bVbbZpN?w=941&h=117" alt="图片描述" title="图片描述"></p>
<p>引用百度地图开放平台里面的一个简单流程,需要用户注册百度账号并且申请百度地图的开发者,之后需要获取自己的服务秘钥(ak),这个服务秘钥在实际开发过程中是需要使用到的。至于开发者所需要的服务功能在申请服务密钥时自行选择,这里不做统一说明。</p>
<h3>简单使用流程</h3>
<ul>
<li>1.<a href="https://link.segmentfault.com/?enc=pN4ZSJDfoSl8D7XxLkRJvw%3D%3D.LCVv05jbhNxABXE9CfMdgi7AZsH5wXA45D9PCeIDQAjyhKMnPDG4OkcjGRdL1fzW" rel="nofollow">申请百度账号和ak</a>
</li>
<li>
<p>2.准备页面<br>根据HTML标准,每一份HTML文档都应该声明正确的文档类型,我们建议您使用最新的符合HTML5规范的文档声明:</p>
<pre><code><!DOCTYPE html></code></pre>
</li>
<li>
<p>3.适应移动端页面展示<br>下面我们添加一个meta标签,以便使您的页面更好的在移动平台上展示。</p>
<pre><code><meta name="viewport" content="initial-scale=1.0, user-scalable=no" /></code></pre>
</li>
<li>4.设置容器样式<br>这个,就根据自己的需要来吧~~~</li>
<li>
<p>5.引用百度地图API文件</p>
<pre><code><script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=您的密钥"></script></code></pre>
<p>开头时申请的服务密钥这会就有用了。</p>
</li>
<li>
<p>6.创建地图容器元素</p>
<pre><code><div id="container"></div> </code></pre>
<p>地图需要一个HTML元素作为容器,这样才能展现到页面上。这里我们创建了一个div元素。</p>
</li>
<li>
<p>7.创建地图实例</p>
<pre><code>var map = new BMap.Map("container"); </code></pre>
<blockquote>注意:<br>1.在调用此构造函数时应确保容器元素已经添加到地图上。<br>2.命名空间 API使用BMap作为命名空间,所有类均在该命名空间之下,比如:BMap.Map、BMap.Control、BMap.Overlay。</blockquote>
</li>
<li>
<p>8.设置中心点坐标</p>
<pre><code>var point = new BMap.Point(116.404, 39.915); </code></pre>
<blockquote>请注意:在使用百度地图JavaScript API服务时,需使用百度BD09坐标,如使用其他坐标( WGS84、GCJ02)进行展示,需先将其他坐标转换为BD09,详细说明请参考坐标转换说明,请勿使用非官方的转换方法!!!</blockquote>
</li>
<li>
<p>9.地图初始化,同时设置地图展示级别</p>
<pre><code>map.centerAndZoom(point, 15); </code></pre>
<p>在创建地图实例后,我们需要对其进行初始化,BMap.Map.centerAndZoom()方法要求设置中心点坐标和地图级别。 地图必须经过初始化才可以执行其他操作。</p>
</li>
</ul>
<p>好的,通过上面的9个步骤我们就能简单的通过调用百度地图JavaScript API来创建一个简单的地图组件了。</p>
<h2>链家地图找房效果</h2>
<p>以链家租房为例,基本效果如下:</p>
<p><img src="/img/bVbcQ73?w=1917&h=882" alt="图片描述" title="图片描述"></p>
<p>通过上图可以看出,我们实现的功能很简单,划分区域并且将区域内的数据以气泡的形式在地图上面显示出来。</p>
<p>我们需要实现的内容可以分为以下四个部分:</p>
<ul>
<li>区域点位气泡</li>
<li>区域边界</li>
<li>获取区域点位经纬度</li>
<li>获取区域边界</li>
</ul>
<p>接下来我们从上面四个角度来逐一分解:</p>
<h3>区域点位气泡</h3>
<h4>数据结构</h4>
<p>先让我们来看一下,对应每一个区域气泡的数据格式是怎么样的。</p>
<pre><code>{
"id": 23008626,
"name": "西城",
"longitude": 116.36960374452,
"latitude": 39.910041817755,
"border": "116.33966497518,39.943764592525;116.3411097524,39.928597618389;116.341315,39.904264;116.344854,39.904291;116.344935,39.903046;116.335774,39.90304;116.33580482633,39.899337316133;116.33242753154,39.899040029285;116.33245527539,39.898197950347;116.33283394656,39.895693053733;116.330977,39.895485;116.330926,39.89293;116.330212,39.892899;116.330222,39.891257;116.33024,39.890212;116.327698,39.890115;116.327761,39.88071;116.329557,39.880786;116.331408,39.880675;116.333043,39.880578;116.333869,39.880564;116.334354,39.88062;116.334696,39.880661;116.336259,39.880772;116.337139,39.880869;116.337606,39.880897;116.337929,39.880952;116.338145,39.881256;116.339457,39.881312;116.340858,39.881326;116.34375,39.881464;116.346194,39.881589;116.347631,39.88163;116.34835,39.881547;116.349571,39.881409;116.350578,39.881326;116.351063,39.881243;116.351386,39.881132;116.351278,39.880357;116.351314,39.879582;116.353811,39.879582;116.356363,39.879568;116.35665,39.878294;116.356884,39.877007;116.357369,39.87446;116.40572867625,39.878578727299;116.4047580463,39.892972104486;116.40452597879,39.901901880113;116.40441622959,39.906532832082;116.40257282982,39.906507681183;116.4020196152,39.913804303627;116.39828683227,39.913832594783;116.39725265797,39.928722718426;116.40581966266,39.929770411996;116.40568598304,39.934729286107;116.40295669491,39.934655327476;116.40254832882,39.939804012204;116.40231814784,39.946463810438;116.40155209008,39.94695247551;116.40049657642,39.94705131884;116.4001439975,39.954900233487;116.39984559727,39.963429984507;116.39296272069,39.962975952981;116.39373616852,39.965896570349;116.39437,39.969363;116.398806,39.969503;116.398806,39.974093;116.398806,39.974991;116.399679,39.977101;116.400523,39.979367;116.387534,39.978897;116.389959,39.974502;116.37665910912,39.973917456809;116.37842636561,39.95481641918;116.36316310982,39.949597406824;116.362735,39.952795;116.361208,39.952753;116.360965,39.953521;116.360732,39.955699;116.362744,39.955602;116.36200826406,39.957387484274;116.35838467559,39.956443466213;116.35758831682,39.952823154673;116.35883299579,39.94855433968;116.35352531625,39.94923292836;116.34933548622,39.948951148112;116.34882794641,39.944143030672",
"count": 1870
}</code></pre>
<p>在上面的数据格式中,共有以下几个字段值:</p>
<ul>
<li>id 区域的id值</li>
<li>name 区域名称</li>
<li>longitude 区域气泡的中心经度值</li>
<li>latitude 区域气泡的中心纬度值</li>
<li>border 区域的边界关键点的经纬度值</li>
<li>count 资源数量</li>
</ul>
<h4>实现</h4>
<p>在说道气泡的时候就不得不提,百度地图提供的覆盖物相关的API,请看下图:<br><img src="https://segmentfault.com/img/bVbbZV8?w=964&h=484" alt="图片描述" title="图片描述"></p>
<p>可以看出,百度地图支持不同类型不同功能的覆盖物功能。然后根据我们结果图的效果来看,我们选择使用Label来实现。主要是因为气泡显示的信息均为文本形式,无需其他的附加功能;Label的内容支持HTML,这样简单地样式设置也满足了。</p>
<p>先来看一下Label的参数说明:</p>
<p><img src="https://segmentfault.com/img/bVbbZXK?w=632&h=80" alt="图片描述" title="图片描述"></p>
<p><img src="https://segmentfault.com/img/bVbbZYE?w=655&h=242" alt="图片描述" title="图片描述"></p>
<p>对于实例内容,我们可以定义一个模板来使用</p>
<pre><code><div class="bubble bubble-1" data-xftrack="10144" data-longitude="#{longitude}" data-latitude="#{latitude}" data-id="#{id}"><p class="name" title="#{name}">#{name}</p><p><span class="count">#{count}</span>个资源</p></div></code></pre>
<p>然后让我们来创建一个Label</p>
<pre><code>var Label = new BMap.Label($.replaceTpl(tpl, data), {
position: new BMap.Point(data.longitude, data.latitude),
offset: BMap.Size(-46, -46)
})</code></pre>
<p>其中,data是我们上面定义的一个数据结构,tpl是我们定义的模板字符串。replaceTpl是自定义的用来替换模板字符串中字段值内容的工具函数,实现方法大家可以自行脑补。</p>
<p>参数部门,首先根据数据结构中的中心经纬度值,获得Label的地理位置,offset是Label的位置偏移值。这样通过上面的方法和我们的数据结构就可以获得一个Label的气泡。</p>
<p>然后我们也可以通过Label提供的方法来调整Label。比如</p>
<pre><code>label.setStyle(styleObject)</code></pre>
<p>其他一些方法请参照:</p>
<p><img src="https://segmentfault.com/img/bVbb2rl?w=633&h=734" alt="图片描述" title="图片描述"></p>
<h4>addOverlay方法</h4>
<p>上面创建了一个Label实例之后发现地图上面并没有出现我们预期的效果,这是因为我们还需要使用地图组件的addOverlay方法将覆盖物添加到地图上面</p>
<pre><code>map.addOverlay(label)</code></pre>
<h3>区域边界</h3>
<p>在地图上面区域的边界其实可以理解为在地图上面的固定区域描画一个多边形,这样就可以知道我们需要使用到的是百度地图中的多边形功能Polygon。</p>
<blockquote>表示地图上的多边形。多边形类似于闭合的折线,另外您也可以为其添加填充颜色</blockquote>
<pre><code>var polygon = new BMap.Polygon(borderData, {
strokeWeight: 2,
strokeColor: "#4285f4",
strokeOpacity: 1,
fillOpacity: .1,
enableMassClear: !1
});</code></pre>
<p>上面是我们这次中的一个简单的多边形实例,其中borderData可以参照上面数据结构中的border字段。我们可以来看一下官方给出的定义</p>
<p><img src="https://segmentfault.com/img/bVbb2vi?w=634&h=81" alt="图片描述" title="图片描述"></p>
<p>可以看出主要有两部分参数构成,一部分是多边形关键点的点位数组,一部分是设置参数,具体参照下图</p>
<p><img src="https://segmentfault.com/img/bVbb2wb?w=654&h=469" alt="图片描述" title="图片描述"></p>
<p>当然,在创建完一个多边形实例之后不要忘记添加到地图上面啊</p>
<pre><code>map.addOverlay(polygon)</code></pre>
<h3>获取区域点位经纬度</h3>
<p>当我们知道了一个市区有哪些区域后,我们该如何来获取对应这个区域的一个点位的经纬度呢?我们可以通过百度地图提供的BMap.Geocoder来获取点位信息</p>
<blockquote>Geocoder:地址解析,提供将地址信息转换为坐标点信息的服务。</blockquote>
<p>地理编码能够将地址信息转换为地理坐标点信息。</p>
<p>百度地图API提供Geocoder类进行地址解析,您可以通过Geocoder.getPoint()方法来将一段地址描述转换为一个坐标。在下面的示例中,我们将获得地址“北京市海淀区”的地理坐标位置。注意在调用Geocoder.getPoint()方法时您需要提供地址解析所在的城市(本例为“北京市”)。</p>
<pre><code>var myGeo = new BMap.Geocoder();
// 将地址解析结果显示在地图上,并调整地图视野
myGeo.getPoint("北京市海淀区", function(point){
if (point) {
// 创建点位标识
// point {lng, lat} 点位的经纬度值
}
}, "北京市");</code></pre>
<p>附官方说明:</p>
<p><img src="/img/bVbcRci?w=667&h=387" alt="图片描述" title="图片描述"></p>
<h3>获取区域边界</h3>
<p>上面我们获取到了区域的点位信息,那么我们怎么能够获取到这个区域的边界信息呢?同样的,百度地图提供给了我们API可以来使用:BMap.Boundary</p>
<p>百度地图API提供Boundary类进行地址解析,您可以通过Boundary.get()方法来将一段地址描述转换为一系列边界关键点的经纬度字符串。在下面的示例中,我们将获得地址“北京市海淀区”的地理区域。</p>
<pre><code>var bdary = new BMap.Boundary();
bdary.get("北京市海淀区", function(rs) { //获取行政区域
console.error(rs);
});</code></pre>
<p>附官方说明:</p>
<p><img src="/img/bVbcRdA?w=660&h=324" alt="图片描述" title="图片描述"></p>
<h2>小结</h2>
<p>其实类似于链家地图找房的实现并没有我们想象的那么复杂,一些关键的实现百度地图都给我们提供了可用的接口,虽然有些接口的数据会有一定的出入,但是基本的一些功能的实现我们是可以通过使用这些接口来实现的。<a href="https://link.segmentfault.com/?enc=I1B5NCP1Vgw%2B0wrTUh8w4g%3D%3D.UbKFOuIWJ3MQJN1kXE6f%2B0J5NjAO4ZbDPw4xkAoWnxjf0rkqeR46fSOLVQjy1jSsE7fs4LGJOkfTDb2mDxnK1A%3D%3D" rel="nofollow">具体Demo请参照</a>。</p>
【ES6系列】数值的扩展
https://segmentfault.com/a/1190000014969726
2018-05-22T12:17:20+08:00
2018-05-22T12:17:20+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>1.Number.isFinite(), Number.isNaN()</h2>
<p>Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。</p>
<pre><code>Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false</code></pre>
<blockquote>※ 参数类型不是数值,一律返回false</blockquote>
<p>Number.isNaN()用来检查一个值是否为NaN。</p>
<pre><code>Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true</code></pre>
<blockquote>※ 参数类型不是NaN,一律返回false<br>※ NaN === NaN // false</blockquote>
<h2>2.Number.parseInt(), Number.parseFloat()</h2>
<p>ES6将全局方法parseInt()和parseFloat()移植到了Number对象上面,使用行为保持不变,只是减少了全局方法的存在,增进了语言的模块化。</p>
<h2>3.Number.isInteger()</h2>
<p>Number.isInteger()用来判断一个数值是否为整数。</p>
<pre><code>Number.isInteger(25) // true
Number.isInteger(25.1) // false
Number.isInteger(25.0) // true, 整数和浮点数采用的是同样的储存方法,所以被认为相同
Number.isInteger() // false
Number.isInteger(null) // false
Number.isInteger('15') // false
Number.isInteger(true) // false</code></pre>
<h3>双精度问题延伸</h3>
<blockquote>※ JavaScript的双精度问题:由于 JavaScript 采用 IEEE 754 标准,数值存储为64位双精度格式,数值精度最多可以达到 53 个二进制位(1 个隐藏位与 52 个有效位)。如果数值的精度超过这个限度,第54位及后面的位就会被丢弃,这种情况下,Number.isInteger可能会误判。</blockquote>
<pre><code>Number.isInteger(3.0000000000000002) // true</code></pre>
<blockquote>当然,针对非高精度的操作这个方法是很实用的。<br>引申一下,关于浮点数的计算的精度问题:0.1 + 0.2 === 0.3 // false, 奇葩啊,计算机居然算不对?</blockquote>
<p>其实对于浮点数的四则运算,几乎所有的编程语言都会有类似精度误差的问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。下面就分析下为什么会有这个精度误差,以及怎样修复这个误差。</p>
<p>首先,我们要站在计算机的角度思考 0.1 + 0.2 这个看似小儿科的问题。我们知道,能被计算机读懂的是二进制,而不是十进制,所以我们先把 0.1 和 0.2 转换成二进制看看:</p>
<pre><code>0.1 => 0.0001 1001 1001 1001…(无限循环)
0.2 => 0.0011 0011 0011 0011…(无限循环)</code></pre>
<p>上面我们发现0.1和0.2转化为二进制之后,变成了一个无限循环的数字,这在现实生活中,无限循环我们可以理解,但计算机是不允许无限循环的,对于无限循环的小数,计算机会进行舍入处理。进行双精度浮点数的小数部分最多支持 52 位,所以两者相加之后得到这么一串 0.0100110011001100110011001100110011001100110011001100 因浮点数小数位的限制而截断的二进制数字,这时候,我们再把它转换为十进制,就成了 0.30000000000000004。从而,就出现了开始时出现的“奇葩”问题。</p>
<h2>4.安全整数和 Number.isSafeInteger()</h2>
<p>JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。</p>
<pre><code>Math.pow(2, 53) // 9007199254740992
9007199254740992 // 9007199254740992
9007199254740993 // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1
// true</code></pre>
<p>具体一些关于安全整数的内容不再详谈,大部分我们的处理过程是不会涉及到这些情况的。</p>
<h2>5.Math对象的方法扩展</h2>
<h3>Math.trunc()</h3>
<p>Math.trunc方法用于去除一个数的小数部分,返回整数部分。</p>
<pre><code>Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
// 对于非数值,Math.trunc内部使用Number方法将其先转为数值。
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
// 对于空值和无法截取整数的值,返回NaN。
Math.trunc(NaN); // NaN
Math.trunc('foo'); // NaN
Math.trunc(); // NaN
Math.trunc(undefined) // NaN</code></pre>
<p>模拟代码:</p>
<pre><code>Math.trunc = Math.trunc || function(x) {
return x < 0 ? Math.ceil(x) : Math.floor(x);
};</code></pre>
<blockquote>※ 使用场景:当在处理一些整数位置,但是却又依赖于一些随机数情况时,可以将生成的随机数取整后进行处理</blockquote>
<h3>Math.sign()</h3>
<p>Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。返回值情况:</p>
<ul>
<li>参数为正数,返回+1;</li>
<li>参数为负数,返回-1;</li>
<li>参数为 0,返回0;</li>
<li>参数为-0,返回-0;</li>
<li>其他值,返回NaN。</li>
</ul>
<pre><code>Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
// 如果参数是非数值,会自动转为数值。对于那些无法转为数值的值,会返回NaN。
Math.sign('') // 0
Math.sign(true) // +1
Math.sign(false) // 0
Math.sign(null) // 0
Math.sign('9') // +1
Math.sign('foo') // NaN
Math.sign() // NaN
Math.sign(undefined) // NaN</code></pre>
<p>模拟代码:</p>
<pre><code>Math.sign = Math.sign || function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};</code></pre>
<blockquote>※ 使用场景:针对一些返回值情况进行区分处理时。</blockquote>
<h2>6.结语</h2>
<blockquote>本次主要针对ES6中数值部分新增的一些方法进行了梳理,只挑选了一些和日常工作相关的内容,其他的一些高精度和数学计算相关的内容没有进行梳理,当有实际应用场景时对应分析处理即可,毕竟目前来看应用场景还是比较少的。针对JavaScript的双精度计算相关的问题只是做了一个基本的分析,一些具体的解决方法后续有机会将会单独整理分享。</blockquote>
【ES6系列】字符串扩展
https://segmentfault.com/a/1190000014957670
2018-05-21T17:02:30+08:00
2018-05-21T17:02:30+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<h2>1.字符串的遍历器</h2>
<p>ES6为字符串添加了遍历器接口,使得字符串可以被<code>for...of</code>循环遍历</p>
<pre><code>for(let code of "foo") {
console.log(code);
}
// "f"
// "o"
// "o"</code></pre>
<h2>2.includes(), startsWith(), endsWith()</h2>
<p>传统JS中字符串中只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6中提供了三种新方法:</p>
<ul>
<li>includes(): 返回布尔值,表示是否找到了参数字符串</li>
<li>startsWith():返回布尔值,表示参数字符串是否在原字符串的头部</li>
<li>endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部</li>
</ul>
<pre><code>let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true</code></pre>
<p>这三个方法都支持第二个参数,表示开始搜索的位置。</p>
<pre><code>let s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false</code></pre>
<p>上面代码中使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。</p>
<h2>3.repeat()</h2>
<p><code>repeat</code>方法返回一个新字符串,表示将原字符串重复<code>n</code>次</p>
<pre><code>'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
'na'.repeat(2.9) // "nana" 参数是小数时会被取整
'na'.repeat(-0.9) // "" 参数是 0 到-1 之间的小数,则等同于 0
'na'.repeat(Infinity)
// RangeError
'na'.repeat(-1)
// RangeError
'na'.repeat(NaN) // "" 参数NaN等同于 0
// repeat的参数是字符串,则会先转换成数字
'na'.repeat('na') // ""
'na'.repeat('3') // "nanana"</code></pre>
<h2>4.padStart(), padEnd()</h2>
<p>ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。padStart()用于头部补全,padEnd()用于尾部补全。</p>
<pre><code>'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'</code></pre>
<p>padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串</p>
<p>如果原字符串的长度,等于或大于指定的最小长度,则返回原字符串。</p>
<pre><code>'xxx'.padStart(2, 'ab') // 'xxx'
'xxx'.padEnd(2, 'ab') // 'xxx'</code></pre>
<p>如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。</p>
<pre><code>'abc'.padStart(10, '0123456789')
// '0123456abc'
'abc'.padEnd(10, '0123456789')
// 'abc0123456'</code></pre>
<p>如果省略第二个参数,默认使用空格补全长度。</p>
<pre><code>'x'.padStart(4) // ' x'
'x'.padEnd(4) // 'x '</code></pre>
<p>padStart的常见用途是为数值补全指定位数。</p>
<pre><code>'1'.padStart(10, '0') // "0000000001"
'12'.padStart(10, '0') // "0000000012"
'123456'.padStart(10, '0') // "0000123456"</code></pre>
<p>另一个用途是提示字符串格式</p>
<pre><code>'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"</code></pre>
<p>这在我们统一处理一些固定格式的字符串或者生成一些固定格式的字符串时是很有帮助的。</p>
<h2>5.模板字符串</h2>
<p>我们在处理一些模板的操作时,往往会依赖于字符串拼接的方式来作为模板字符串来使用</p>
<pre><code>$('#target').append(
'There are <b>' + basket.count + '</b> ' +
'items in your basket, ' +
'<em>' + basket.onSale +
'</em> are on sale!'
);</code></pre>
<p>ES6中引入了模板字符串来简化解决这种问题</p>
<pre><code>$('#target').append(`
There are <b>${basket.count}</b> items
in your basket, <em>${basket.onSale}</em>
are on sale!
`);</code></pre>
<blockquote>模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。</blockquote>
<pre><code>// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`</code></pre>
<p>如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。</p>
<pre><code>$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`);</code></pre>
<p>上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。</p>
<pre><code>$('#list').html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());</code></pre>
<p>在上面的例子可以看出,模板字符串中嵌入变量,需要将变量名写在<code>${}</code>之中</p>
<pre><code>function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}</code></pre>
<p>另外,大括号内部还可以使用表达式,运算,调用函数以及对象属性引用等</p>
<pre><code>let x = 1;
let y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"</code></pre>
<p>模板字符串支持嵌套。</p>
<pre><code>const tmpl = addrs => `
<table>
${addrs.map(addr => `
<tr><td>${addr.first}</td></tr>
<tr><td>${addr.last}</td></tr>
`).join('')}
</table>
`;
const data = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));
// <table>
//
// <tr><td><Jane></td></tr>
// <tr><td>Bond</td></tr>
//
// <tr><td>Lars</td></tr>
// <tr><td><Croft></td></tr>
//
// </table></code></pre>
<h2>6.标签模板</h2>
<blockquote>模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。</blockquote>
<pre><code>alert`123`
// 等同于
alert(123)</code></pre>
<p>标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。<br>但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。</p>
<pre><code>let a = 5;
let b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);</code></pre>
<p>上面代码中,模板字符串前面有一个标识名tag,它是一个函数。整个表达式的返回值,就是tag函数处理模板字符串后的返回值。</p>
<p>tag函数的第一个参数是一个数组,该数组的成员是模板字符串中那些没有变量替换的部分,也就是说,变量替换只发生在数组的第一个成员与第二个成员之间、第二个成员与第三个成员之间,以此类推。</p>
<p>tag函数的其他参数,都是模板字符串各个变量被替换后的值。由于本例中,模板字符串含有两个变量,因此tag会接受到value1和value2两个参数。</p>
<p><code>tag</code>函数所有参数的实际值:</p>
<ul>
<li>第一个参数:['Hello ', ' world ', '']</li>
<li>第二个参数: 15</li>
<li>第三个参数:50</li>
</ul>
<p>一个简单的<code>tag</code>函数的写法:</p>
<pre><code>let a = 5;
let b = 10;
function tag(s, v1, v2) {
console.log(s[0]);
console.log(s[1]);
console.log(s[2]);
console.log(v1);
console.log(v2);
return "OK";
}
tag`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"</code></pre>
<h3>标签模板应用一:过滤HTML字符串</h3>
<p>“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容,xss攻击,常见的是在一些移动应用页面嵌套进入一恶意的代码,如微信红包等。</p>
<pre><code>let message =
SaferHTML`<p>${sender} has sent you a message.</p>`;
function SaferHTML(templateData) {
let s = templateData[0];
for (let i = 1; i < arguments.length; i++) {
let arg = String(arguments[i]);
// Escape special characters in the substitution.
s += arg.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
// Don't escape special characters in the template.
s += templateData[i];
}
return s;
}
let sender = '<script>alert("abc")</script>'; // 恶意代码
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;
message
// <p>&lt;script&gt;alert("abc")&lt;/script&gt; has sent you a message.</p></code></pre>
<h3>标签模板应用二:多语言转换(国际化处理)</h3>
<pre><code>i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`
// "欢迎访问xxx,您是第xxxx位访问者!"</code></pre>
<p>下面引用Jack Hsu给出的一个完整的多语言转换<a href="https://link.segmentfault.com/?enc=hW%2BZQyAAKKaoAC1nVZ0BnQ%3D%3D.Za1FwpYIBST9%2FNDeBy4LYjYqZqOaA8RzJVYRPU0DObWTrPxaXfcP%2FM9faXChxXzQBryiDhCgQMNJlLIrdE2DDHqNyjwFDKWqEHbfJjMnLao%3D" rel="nofollow">Demo</a></p>
<pre><code>// Matches optional type annotations in i18n strings.
// e.g. i18n`This is a number ${x}:n(2)` formats x as number
// with two fractional digits.
const typeInfoRegex = /^:([a-z])(\((.+)\))?/;
let I18n = {
use({locale, defaultCurrency, messageBundle}) {
I18n.locale = locale;
I18n.defaultCurrency = defaultCurrency;
I18n.messageBundle = messageBundle;
return I18n.translate;
},
translate(strings, ...values) {
let translationKey = I18n._buildKey(strings);
let translationString = I18n.messageBundle[translationKey];
if (translationString) {
let typeInfoForValues = strings.slice(1).map(I18n._extractTypeInfo);
let localizedValues = values.map((v, i) => I18n._localize(v, typeInfoForValues[i]));
return I18n._buildMessage(translationString, ...localizedValues);
}
return 'Error: translation missing!';
},
_localizers: {
s /*string*/: v => v.toLocaleString(I18n.locale),
c /*currency*/: (v, currency) => (
v.toLocaleString(I18n.locale, {
style: 'currency',
currency: currency || I18n.defaultCurrency
})
),
n /*number*/: (v, fractionalDigits) => (
v.toLocaleString(I18n.locale, {
minimumFractionDigits: fractionalDigits,
maximumFractionDigits: fractionalDigits
})
)
},
_extractTypeInfo(str) {
let match = typeInfoRegex.exec(str);
if (match) {
return {type: match[1], options: match[3]};
} else {
return {type: 's', options: ''};
}
},
_localize(value, {type, options}) {
return I18n._localizers[type](value, options);
},
// e.g. I18n._buildKey(['', ' has ', ':c in the']) == '{0} has {1} in the bank'
_buildKey(strings) {
let stripType = s => s.replace(typeInfoRegex, '');
let lastPartialKey = stripType(strings[strings.length - 1]);
let prependPartialKey = (memo, curr, i) => `${stripType(curr)}{${i}}${memo}`;
return strings.slice(0, -1).reduceRight(prependPartialKey, lastPartialKey);
},
// e.g. I18n._formatStrings('{0} {1}!', 'hello', 'world') == 'hello world!'
_buildMessage(str, ...values) {
return str.replace(/{(\d)}/g, (_, index) => values[Number(index)]);
}
};
// Usage
let messageBundle_fr = {
'Hello {0}, you have {1} in your bank account.': 'Bonjour {0}, vous avez {1} dans votre compte bancaire.'
};
let messageBundle_de = {
'Hello {0}, you have {1} in your bank account.': 'Hallo {0}, Sie haben {1} auf Ihrem Bankkonto.'
};
let messageBundle_zh_Hant = {
'Hello {0}, you have {1} in your bank account.': '你好{0},你有{1}在您的銀行帳戶。'
};
let name = 'Bob';
let amount = 1234.56;
let i18n;
i18n = I18n.use({locale: 'fr-CA', defaultCurrency: 'CAD', messageBundle: messageBundle_fr});
console.log(i18n `Hello ${name}, you have ${amount}:c in your bank account.`);
i18n = I18n.use({locale: 'de-DE', defaultCurrency: 'EUR', messageBundle: messageBundle_de});
console.log(i18n `Hello ${name}, you have ${amount}:c in your bank account.`);
i18n = I18n.use({locale: 'zh-Hant-CN', defaultCurrency: 'CNY', messageBundle: messageBundle_zh_Hant});
console.log(i18n `Hello ${name}, you have ${amount}:c in your bank account.`);</code></pre>
<h2>7.String.raw()</h2>
<p>String.raw方法,往往用来充当模板字符串的处理函数,返回一个斜杠都被转义(即斜杠前面再加一个斜杠)的字符串,对应于替换变量后的模板字符串。</p>
<pre><code>String.raw`Hi\n${2+3}!`;
// 返回 "Hi\\n5!"
String.raw`Hi\u000A!`;
// 返回 "Hi\\u000A!"</code></pre>
<p>如果原字符串的斜杠已经转义,那么String.raw会进行再次转义。</p>
<pre><code>String.raw`Hi\\n`
// 返回 "Hi\\\\n"</code></pre>
<p>String.raw方法可以作为处理模板字符串的基本方法,它会将所有变量替换,而且对斜杠进行转义,方便下一步作为字符串来使用。</p>
<p>String.raw方法也可以作为正常的函数使用。这时,它的第一个参数,应该是一个具有raw属性的对象,且raw属性的值应该是一个数组。</p>
<pre><code>String.raw({ raw: 'test' }, 0, 1, 2);
// 't0e1s2t'
// 等同于
String.raw({ raw: ['t','e','s','t'] }, 0, 1, 2);</code></pre>
<p>作为函数,String.raw的代码实现基本如下。</p>
<pre><code>String.raw = function (strings, ...values) {
let output = '';
let index;
for (index = 0; index < values.length; index++) {
output += strings.raw[index] + values[index];
}
output += strings.raw[index]
return output;
}</code></pre>
<h2>8.总结</h2>
<blockquote>本次主要针对ES6中字符串方面的扩展进行了梳理,其中比较实用的,当属标签模板的应用:屏蔽非法注入以及多语言转换,都是前端开发过程中比较常见的问题。其他的一些新增方法也为我们提供了便利的处理。</blockquote>
【ES6系列】变量的解构赋值
https://segmentfault.com/a/1190000014824986
2018-05-11T14:51:46+08:00
2018-05-11T14:51:46+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>解构赋值(destructuring assignment)语法是一个Javascript表达式,这种语法能够更方便的提取出 Object 或者 Array 中的数据。这种语法可以在接受提取的数据的地方使用,比如一个表达式的左边。有明确的语法模式来告诉我们如何使用这种语法提取需要的数据值。</blockquote>
<h2>数组的解构赋值</h2>
<blockquote>在ES6中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。</blockquote>
<p>在之前,我们为变量赋值,只能是单一的直接进行赋值</p>
<pre><code>var a = 1;
var b = 2;
var c = 3;</code></pre>
<p>在ES6中通过解构,我们可以一次性对多个变量进行赋值。</p>
<pre><code>let [a, b, c] = [1, 2, 3];</code></pre>
<h3>变量声明并赋值时的解构</h3>
<pre><code>let foo = ["one", "two", "three"];
let [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"</code></pre>
<h3>变量先声明后赋值时的解构</h3>
<pre><code>let a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2</code></pre>
<h4>不完全解构</h4>
<p>即等号左边的模式只匹配一部分等号右边的数组,这种情况下依然能够解构成功。</p>
<pre><code>let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
c // 4</code></pre>
<blockquote><strong>※ 如果等号右边不是数组,则解构将会报错</strong></blockquote>
<h3>默认值</h3>
<p>为了防止从数组中取出一个值为undefined的对象,解构赋值允许指定默认值</p>
<pre><code>let a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'</code></pre>
<blockquote><strong>※注意,ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。</strong></blockquote>
<pre><code>let [x = 1] = [undefined];
x // 1
var [x = 1] = [null];
x // null</code></pre>
<p>如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。</p>
<pre><code>function f() {
return 2;
}
let [x = f()] = [1]
x // 1</code></pre>
<h3>交换变量</h3>
<p>没有解构赋值的情况下,交换两个变量需要一个临时变量。在一个解构表达式中可以直接交换两个变量的值。</p>
<pre><code>let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1</code></pre>
<h3>解析一个从函数返回的数组</h3>
<p>从一个函数返回一个数组是十分常见的情况.。解构使得处理返回值为数组时更加方便。<br>在下面例子中,[1, 2] 作为函数的 f() 的输出值,可以使用解构用一句话完成解析。</p>
<pre><code>function f() {
return [1, 2];
}
let a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2</code></pre>
<h3>忽略某些返回值</h3>
<p>当在处理一些数组时,其中一些值不是我们需要的,我们可以进行忽略。</p>
<pre><code>function f() {
return [1, 2, 3];
}
let [a, , b] = f();
console.log(a); // 1
console.log(b); // 3</code></pre>
<h3>将剩余数组赋值给一个变量</h3>
<p>可以通过rest参数将一个数组后面剩余的一部分作为数组赋值给一个变量</p>
<pre><code>let [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]</code></pre>
<h3>用正则表达式匹配提取值</h3>
<p>用正则表达式方法exec()匹配字符串会返回一个数组,该数组第一个值是完全匹配正则表达式的字符串,然后的值是匹配正则表达式括号内内容部分。解构赋值允许你轻易地提取出需要的部分,忽略完全匹配的字符串——如果不需要的话。</p>
<pre><code>let url = "https://developer.mozilla.org/en-US/Web/JavaScript";
let parsedURL = /^(\w+)\:\/\/([^\/]+)\/(.*)$/.exec(url);
console.log(parsedURL); // ["https://developer.mozilla.org/en-US/Web/JavaScript", "https", "developer.mozilla.org", "en-US/Web/JavaScript"]
let [, protocol, fullhost, fullpath] = parsedURL;
console.log(protocol); // "https"</code></pre>
<h2>对象的解构赋值</h2>
<p>解构不仅可以用于数组,还可以用于对象。</p>
<h3>基本赋值</h3>
<pre><code>let o = {p: 42, q: true};
let {p, q} = o;
console.log(p); // 42
console.log(q); // true</code></pre>
<h3>无声明赋值</h3>
<pre><code>let a, b;
({a, b} = {a: 1, b: 2});</code></pre>
<blockquote>※ 赋值语句周围的( .. ) 是使用对象字面解构赋值时不需要声明的语法。{a, b} = {a: 1, b: 2}不是有效的独立语法,因为左边的{a, b}被认为是一个块而不是对象字面量。然而,({a, b} = {a: 1, b: 2})是有效的,正如 var {a, b} = {a: 1, b: 2}<p>注意:你的( .. ) 表达式需要一个分号在它前面,否则它也许会被当成上一行中的函数来执行。</p>
</blockquote>
<p>对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。</p>
<pre><code>let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined</code></pre>
<h3>给新的变量名赋值</h3>
<p>可以从一个对象中提取变量并赋值给和对象属性名不同的新的变量名。</p>
<pre><code>let o = {p: 42, q: true};
let {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true</code></pre>
<p>这实际上说明,对象的解构赋值是下面形式的简写</p>
<pre><code>let { p: foo, q: bar } = { p: 42, q: true };</code></pre>
<p>也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。</p>
<pre><code>let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined</code></pre>
<p>foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。</p>
<h3>默认值</h3>
<p>变量可以先赋予默认值。当要提取的对象没有对应的属性,变量就被赋予默认值。</p>
<pre><code>let {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5</code></pre>
<h4>给新的变量命名并提供默认值</h4>
<p>一个属性可以是</p>
<ul>
<li>1)从一个对象解构,并分配给一个不同名称的变量,</li>
<li>2)分配一个默认值,以防未解构的值是undefined。</li>
</ul>
<pre><code>let {a:aa = 10, b:bb = 5} = {a: 3};
console.log(aa); // 3
console.log(bb); // 5</code></pre>
<h3>嵌套结构对象的解构</h3>
<pre><code>let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"</code></pre>
<p>注意,这时p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。</p>
<pre><code>let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]</code></pre>
<h3>对象方法赋值</h3>
<p>对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。</p>
<pre><code>let { log, sin, cos } = Math;</code></pre>
<p>上面代码将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。</p>
<h2>字符串的解构赋值</h2>
<p>前面讲了数组可以进行解构赋值,同样的,我们可以将字符串理解成特殊的数组,所以字符串也可以进行解构赋值</p>
<pre><code>const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"</code></pre>
<p>类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。</p>
<pre><code>let {length : len} = 'hello';
len // 5</code></pre>
<h2>数值和布尔值的解构赋值</h2>
<p>解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。对于undefined和null无法转为对象,则它们进行解构赋值,都会报错。</p>
<pre><code>let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError</code></pre>
<h2>函数参数的解构赋值</h2>
<pre><code>function add([x, y]){
return x + y;
}
add([1, 2]); // 3</code></pre>
<p>上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。</p>
<p>函数参数的解构也可以使用默认值。</p>
<pre><code>function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]</code></pre>
<blockquote>结语:本次主要针对ES6新增的解构赋值的知识进行了回顾,从上面的一些代码段中不难发现,解构赋值能够将我们原本需要相对复杂处理的逻辑简单化,如交换赋值等。更多的使用方法也在等着我们慢慢的去发掘与应用。</blockquote>
【ES6系列】函数部分
https://segmentfault.com/a/1190000014341385
2018-04-12T14:21:38+08:00
2018-04-12T14:21:38+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h2>箭头函数</h2>
<p>在之前ES5的版本中,我们定义一个函数的形式如下:</p>
<pre><code>function a() {
// do something……
}</code></pre>
<p>但是在ES6中,则新增了箭头函数的方式,ES6中允许使用“箭头”(<code>=></code>)来定义函数。</p>
<pre><code>() => {
// do something……
}</code></pre>
<p>其中<code>()</code>中代表的是参数部门,<code>{}</code>中是函数体部分。<br>如果箭头函数不需要参数或者需要多个参数时,需要使用一个<code>()</code>来包裹,当只有一个参数时,可以省略<code>()</code>。</p>
<pre><code>var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
var f = v => v;
// 等同于
var f = function (v) {
return v;
};</code></pre>
<p>当我们在函数中返回一个对象时,之前的写法是:</p>
<pre><code>var f = function(id) {
return {
id: id,
name: "name"
}
}</code></pre>
<p>此时如果改为箭头函数的方式来写时,需要注意的是,由于在ES6中<code>{}</code>代表了一个代码块,所以在返回一个对象的时候,需要在对象<code>{}</code>外面使用括号包起来:</p>
<pre><code>let f = id => ({id: id, name: "name"})</code></pre>
<p>如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。</p>
<pre><code>let fn = () => void doesNotReturn();</code></pre>
<p>不难发现,ES6中的箭头函数相对ES5中的函数的写法更加的简洁方便</p>
<pre><code>const isEven = n => n % 2 == 0;
const square = n => n * n;</code></pre>
<p>如上面只是简单地两行,就定义了两个常用的工具函数。按照之前的写法则会书写多行。特别是在针对回调函数的时候,更能够起到简化的作用:</p>
<pre><code>// ES5的写法
var evens = [1,2,3,4,5];
var odds = evens.map(function(v){
return v + 1
})
// ES6的写法
let evens = [1,2,3,4,5];
let odds = evens.map(v => v + 1)</code></pre>
<h3>箭头函数的this的绑定</h3>
<p>箭头函数和普通函数的另一个区别,在于this的绑定。例如:</p>
<pre><code>var factory = function() {
this.a = "a";
this.b = "b";
this.c = {
a : "a+",
b : function() {
return this.a
}
}
}
console.log(new factory().c.b()) // "a+"</code></pre>
<p>通过上面的代码执行可以看出,得到的结果是“a+”,从而可以看出<code>this.a</code>指向的是<code>c.a</code>。有人曾总结过<code>this的指向是该函数被调用的对象</code>。此处b是c调用的,所以指向c中的a。现在修改为ES6的箭头函数的写法:</p>
<pre><code>let factory = function() {
this.a = "a";
this.b = "b";
this.c = {
a : "a+",
b : () => {
return this.a
}
}
}
console.log(new factory().c.b()) // "a"</code></pre>
<p>执行上面的代码发现,输出的结果是<code>“a”</code>,这又是为什么呢?请看下图所示<br><img src="/img/bV8jWc?w=447&h=282" alt="图片描述" title="图片描述"></p>
<h3>注意点</h3>
<p>箭头函数有几个使用注意点</p>
<ul><li>(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。这个我们在上面的例子中也已经见到过了,再举一个比较常见的例子:</li></ul>
<p>在涉及到setTimeout和setInterval时,我们之前的方法是在调用前将函数体内的this通过赋值给一个变量的方法,使得能够在setTimeout和setInterval的回调函数中使用函数体的this</p>
<pre><code>function foo() {
this.id = "111";
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}</code></pre>
<p>但是在ES6中在使用箭头函数之后则不再需要进行赋值替换而能直接使用函数体中的this</p>
<pre><code>function foo() {
this.id = "111";
setTimeout(()=>{
console.log(this.id);
}, 100);
}</code></pre>
<p>this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。</p>
<p>除了this,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变量:<code>arguments、super、new.target</code>。</p>
<ul>
<li>(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。</li>
<li>(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替(下面会讲解rest参数)。</li>
<li>(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数,这部分在后面具体遇到时再进行说明。</li>
</ul>
<h2>函数的默认参数</h2>
<p>对于带有参数的函数,我们往往在一些情况下需要给出一个默认值的设置,当不传入参数时,也能确保函数能够正常执行。在ES5中,我们的写法往往如下:</p>
<pre><code>function f(x, y, z) {
if(y === undefined) {
y = 7;
}
if(z === undefined) {
z = 5;
}
return x + y + z
}
console.log(f(1,3));</code></pre>
<p>在ES6中允许为函数的参数设置默认值,即直接写在参数定义的后面。</p>
<pre><code>function f(x, y = 7, z = 5) {
return x + y + z
}
console.log(f(1,3));</code></pre>
<p>这段代码和上面的执行结果是一致的,可以看出函数默认值的写法使得函数定义更加的简洁清晰。<br>ES6 的写法还有两个好处:</p>
<ul>
<li>首先,阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;</li>
<li>其次,有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。</li>
</ul>
<h3>应用</h3>
<p>另外,在上面的处理中,我们并没有针对x的赋值进行验证,这样当在没有给出x的值的时候,函数会导致报错。这个时候,我们可以来写一个非空验证的方法,来给函数添加校验。</p>
<pre><code>function checkParameter() {
throw new Error('can\'t be empty')
}
function f(x = checkParameter(), y = 7, z = 5) {
return x + y + z
}
console.log(f(1,3));
try{
f()
} catch(e) {
console.log(e)
} finally {
}</code></pre>
<p>这样给x设置默认值的方式来执行校验函数,就可以确认x的赋值的有效性了。这也是函数默认值的一种使用技巧。</p>
<h2>rest参数</h2>
<p>上面针对参数默认值做了说明,下面我们考虑一下一个概念,<code>可变参数</code>,比如我们现在来做一个求和运算的函数,但是是几个数的求和是不一定的,按照ES5中的处理,我们的写法:</p>
<pre><code>function sum() {
var array = Array.prototype.slice.call(arguments);
// arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组。
var sum = 0;
array.forEach(function(item){
sum += item * 1
})
return sum;
}
sum(1,2,3,4)</code></pre>
<p>主要是通过arguments来获取函数的参数列表来获取参数。但是在ES6中却并不需要这么麻烦,ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。</p>
<pre><code>function sum(...a) {
var sum = 0;
a.forEach(item => sum+= item*1)
return sum;
}
sum(1,2,3,4)</code></pre>
<p>通过ES6的rest参数,能够更加简洁的来实现不定参数的函数的实现。rest参数是一个真正的数组,数组特有的方法都可以使用,完全比上面通过arguments的方法更加自然、简洁。</p>
<p><code>注:rest参数之后不能再有其他参数(即只能是最后一个参数)。</code></p>
<h2>小结</h2>
<blockquote>本次主要针对ES6中对于函数部分的相关扩展内容作了梳理,并没有非常的全面,而是选择其中比较常用比较重要的箭头函数、函数默认值和rest参数这几部分进行了说明。更加细致的体会还是需要在后续的不断使用中进行加强和熟悉。</blockquote>
前端资料分享
https://segmentfault.com/a/1190000014314544
2018-04-11T16:44:20+08:00
2018-04-11T16:44:20+08:00
前端荣耀
https://segmentfault.com/u/lengxing
13
<p>由来:</p>
<ul>
<li>整理、记录整理日常收集的前端资料</li>
<li>方便大家有效查阅自己需要的东西</li>
</ul>
<p>具体可<a href="https://link.segmentfault.com/?enc=aBu6qKEj4fH0NREF9iMpMg%3D%3D.ybxk%2BWLABpeHdO4ZTlqMkPXtSSqXVKDpAedFiRrNNyjZCwRQ4bColHI5AOqhJc4z" rel="nofollow">参考</a></p>
<h2>>Javascript部分</h2>
<h4>①相关书籍</h4>
<ul><li><a href="https://link.segmentfault.com/?enc=BYa%2Bk0XKfE9Y%2BHy0CDl86A%3D%3D.gN3dTyYf5TnfNiiom0odHU7CfaBUXpqlE%2BBTZtAYeIA7Q%2FPJNF8JpNNCZuf4wOuY" rel="nofollow">JavaScript 标准参考教程</a></li></ul>
<h4>②博文精贴</h4>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=Vj7E%2BDBb%2Bs2I0kd233xl2g%3D%3D.hXykyRLRYH69Cu1i38%2F7sXrgX45Ql08mFEX0VpKflKDEMKieSq3%2BWOKoYrfTCOF5" rel="nofollow">深入理解JavaScript系列</a> <a href="https://link.segmentfault.com/?enc=45kRtBdXUBQvutVeGKUoOw%3D%3D.hWIe2cag75TpqKMnug1bPvjPgWEQpc0a7f%2FvRTzSGPBAikx4d9yA9od%2FZdna6eCz4c4Ey8c5F%2FKy44r911fqYA%3D%3D" rel="nofollow">备用地址</a>
</li>
<li><a href="https://link.segmentfault.com/?enc=Fw85HDvgNHtIyR4fy36xyg%3D%3D.7m%2FVoTXcN8D%2F05qlHUm22wzyDs3LMNGbbkMg%2F%2FUtv7yweGFEysj2extMDJRso3%2BgEaP9y4HaFQd5R1SGnR6%2BTQ%3D%3D" rel="nofollow">深入理解javascript原型和闭包系列</a></li>
<li><a href="https://link.segmentfault.com/?enc=HK4W7pQHDkeTruOaBoBq%2Fw%3D%3D.QXQcfAICCLOUIMx5bAvm2H%2BVmCOs8E5s2uNIcs%2FKbtaZaJD921xV2oRSJX2UxbERnVngGidiDXRKXYvnLX84wg%3D%3D" rel="nofollow">深入浅出React</a></li>
<li><a href="https://link.segmentfault.com/?enc=yAGdKQPJ4kWVENAYivd%2BfQ%3D%3D.5d9wsYEAsLSzaPPuvy1TwOhCVJiAGhq10ceBrsoCPPhwPTf9cAqIp1Kl82kgF%2BFZ" rel="nofollow">React-Native学习指南整理</a></li>
<li><a href="https://link.segmentfault.com/?enc=TnMMWo55CwzhbGy7NGan%2BA%3D%3D.yDSYpdL4%2Bvv8E6Ruzh%2F7niUW74LMYNkIFLNk44zfd5xGX%2Fnek7GNUJN2kW%2FKcpFU" rel="nofollow">underscore-1.8.3.js 源码解读</a></li>
<li><a href="https://link.segmentfault.com/?enc=Tfc%2BOkXWtzvWliMvZc5V%2BQ%3D%3D.97Kb6IgvjDJjlMJnyb8I79VkSyCUh5DkyCORrRMF5HnxqvTYYSZggQqFxfXzYmO5Km0woTvR2McFFtWa3F2e9A%3D%3D" rel="nofollow">AngularJS 学习</a></li>
</ul>
<h4>③框架精选</h4>
<h5>插件</h5>
<ul>
<li>页面弹窗插件:<a href="https://link.segmentfault.com/?enc=1NKfJdlbLM9RKhye95j6bg%3D%3D.s6CJUJYufYqg7lARqzAEzN0G6I879fnWUlyiqdzK5p4%3D" rel="nofollow">Layer</a>
</li>
<li>
<p>日期时间选择器插件:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=Xfv9PocR7ex2XEZwNvbLMQ%3D%3D.OyNiC7ysVPi23MlIQnYXr9rW%2FFN7vqGxzswI3VMYppGGSZeiX%2BrZlmx3vE21LzMjiRpnO2DJ9xWIKJ8psSePhw%3D%3D" rel="nofollow">bootstrap-datetimepicker</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=2HFNFrTh1U7ABYv8k7WHWQ%3D%3D.y9bVNwk7urSnYWvNG%2B%2BxsP3Fk2pK7UtKZiYCbb5O6Fk3nrLW8QfKeGXl448snDFF0IvnKhDx9hvf53EvWiqlLMmYX5UCJvJNCaN6WDISbt0%3D" rel="nofollow">DateRange</a>(时间段选择)</li>
<li><a href="https://link.segmentfault.com/?enc=eKmYchDJbteqj4nQz7WJXQ%3D%3D.ihnlR%2B9fna2m1I99JI%2FOOytFrlKSPgnSfgNowI7Av%2FJ2gUAqgTr5jiy0s1WolYZG" rel="nofollow">datetimepicker</a></li>
</ul>
</li>
<li>跨浏览器复制插件:<a href="https://link.segmentfault.com/?enc=yMoVir%2FnHxiBNReOPRhStQ%3D%3D.nb2caiPCn1zdm2sEDPU59yddo8iJrIh1zUsb0mha6IaTPCKKQNFtcQCteQ9zbTr%2FVuicnWvVyFb3phXZaxAKR3Lty67rEWYiIrEmiqHkASM%3D" rel="nofollow">jQuery ZeroClipboard</a>
</li>
<li>
<p>表单校验插件:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=q25Gj7m5TTo%2F%2BFYJJLDtpQ%3D%3D.%2BwRZpHu7WeaJQ8yd0vdUUZnXi9b%2BRkkoGkDXLdBKE7k%3D" rel="nofollow">niceValidator</a></li>
<li><a href="https://link.segmentfault.com/?enc=syeIHUkcCzGxKPAETdDF7Q%3D%3D.Rbdz9uMzPIOnp9DFPY6AkB1iw%2FJ6gIOOChQfskJaOLY%3D" rel="nofollow">Bootstrap Form Validation</a></li>
</ul>
</li>
<li>
<p>地域选择器插件:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=SuJqwNei0tV5TCrNQsd9Cw%3D%3D.dvVBi4HDS5C%2BVgOuz6toOdxrwr7kncURkk43PG0pAazL8iIibC2C7QyYxRwVYeFA" rel="nofollow">Region</a></li>
<li><a href="https://link.segmentfault.com/?enc=XHDInjnOAXXPeNeU1tihVQ%3D%3D.PFC9rHxwq0pblYCkJo4aGsVv7LsCaqG0ZutfPWesscKNQe3Ia9H0OYuwGXT53RU9" rel="nofollow">Chineserp</a></li>
</ul>
</li>
<li>
<p>图表插件:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=043id4eMpGi646Wms5%2BgVA%3D%3D.BWzP89KnrEp0ocp4bup9HxLXCk3yP6jgApAD5Vau7iM%3D" rel="nofollow">echarts</a></li>
<li><a href="https://link.segmentfault.com/?enc=Pwp2JIuXFtdvIRcnc4tgIw%3D%3D.K9tOXDGuca7J6OMt1IwDIEDnLr4Y%2F%2B0hFycLXPho4io%3D" rel="nofollow">Highcharts</a></li>
<li><a href="https://link.segmentfault.com/?enc=sgsOZh0RZzi%2B9tWO%2BsfJ%2Fg%3D%3D.i%2Bbv6HvLXvWu4r0kUoE7k71EdqUTSU4hlykh8nQC6Rc%3D" rel="nofollow">Chart.js</a></li>
</ul>
</li>
<li>焦点图/幻灯片插件:<a href="https://link.segmentfault.com/?enc=B7l80c6oQMTUKrEvmyN8qw%3D%3D.uz8eYsNooJKvW3qiSIgHpAtK5fCJWpQZUVHF5wxzcSg%3D" rel="nofollow">SuperSlide</a>
</li>
<li>图片弹出框插件:<a href="https://link.segmentfault.com/?enc=2VoN97FwMcHHAeQs2a6fhA%3D%3D.C0EWJJ5hMdb9Dou7WreYo0kmLw6OWjZoxcnuVx83XZ0%3D" rel="nofollow">fancyBox</a>
</li>
<li>HTML5视频播放器插件:<a href="https://link.segmentfault.com/?enc=HzjKwKJKVTtxqdXiHzhSnw%3D%3D.6pDuzL44rVEL5OlxJ8uJctpQ2rKBio9uaL499GyvpAY%3D" rel="nofollow">Video.js</a>
</li>
<li>全屏滚动插件:<a href="https://link.segmentfault.com/?enc=VCe6azitcDXyUz1eXzDqWg%3D%3D.3Ft8VWNXGySlvDWEkd%2FLr85EIuWKXNw1bZ2BF1oSC8GMn6KyzAU8Q%2FHedbqBWY%2BG" rel="nofollow">fullPage.js</a>
</li>
<li>颜色选择器插件:<a href="https://link.segmentfault.com/?enc=wuRzKXjYruTC0pigdFvihA%3D%3D.2fJESoLMdrdrxWfh9FEaPCJlTae2x1Zt7RC%2BtEFKSpE03Sz%2BumyWoGyCt3XKWEqa" rel="nofollow">spectrum</a>
</li>
<li>
<p>排序插件:</p>
<ul><li><a href="https://link.segmentfault.com/?enc=Ylc18bLFeaLZ4Q5a1S6oEQ%3D%3D.hiXHaQPEJZZamQKGYyiaHEK0pdoZrrySAoLTSmHOou6%2BRIP8Zsn1RV1bL1qsvGDv" rel="nofollow">Sortable</a></li></ul>
</li>
<li>延时加载插件:<a href="https://link.segmentfault.com/?enc=9DeQy0QF69Y4JcVZT%2Fw28g%3D%3D.7jEzzbIBExbmut%2FgWblrxEXD7N5R75ZaAK1c5K9BTK0n8KgEK9gWTpR3mjTH3v%2BZ" rel="nofollow">jquery.lazyload.js</a>
</li>
<li>通知插件: <a href="https://link.segmentfault.com/?enc=QP5bTjVaCWq2cs28jpcwmQ%3D%3D.4CrwwfTQReryy5Uho39rjRGblF7KyBlRjAYG9%2BQGwZePDY4ND%2Fn4fR9HRcKG8%2FpP" rel="nofollow">toastr</a>
</li>
<li>无缝滚动: <a href="https://link.segmentfault.com/?enc=zWyWJKqShrxPBZhn0O5bOg%3D%3D.8KJRKoy4ilP8LRW6LVB857ZZ5cjTHM4f6LM5tQ8KJd5W3vlZ0w6eyk7DgD6wLC%2Fh" rel="nofollow">kxbdmarquee</a>
</li>
<li>移动端触摸滑动插件: <a href="https://link.segmentfault.com/?enc=uz51zHvp%2Fa3Ol6djRD%2Bc2Q%3D%3D.JkC4BBZ4AKfqI6dzW7yeWDqNBtx28bRb4JSBTPupKQE%3D" rel="nofollow">Swiper</a>
</li>
<li>序列帧图片播放:<a href="https://link.segmentfault.com/?enc=8ZD9Q0YVJGXsJsXXoRMX8Q%3D%3D.v8%2FmKzUTZIgjbN0EQVvp%2BFuRqoPXFkTDTWKijNPjEmAgOc6uw7xEwxR8h%2B2Pq5j1" rel="nofollow">vFramePlayer</a>
</li>
</ul>
<h5>工具库</h5>
<ul>
<li>模板引擎库:<a href="https://link.segmentfault.com/?enc=qA8gZ4XvA2rMGo%2F2GT04yQ%3D%3D.d9LcRHrb6h%2BYy1WhSb83Ghj12uYaUKueHbnLfomS11L7fB539M5idAP5PmUmiRHg" rel="nofollow">Mustache</a>
</li>
<li>
<p>UI库:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=XHE4U6LlxOx5r9FZfPPrpg%3D%3D.R9nqqav7w4jQZvLtJNgFlfadhD%2BeoMUYZCYpKMkvMV0%3D" rel="nofollow">Knockout</a></li>
<li><a href="https://link.segmentfault.com/?enc=Ah7T3hgaf46nJumLcS2tWw%3D%3D.fGOnvVN2MB9VPubATTjapv7h5zJEj0A7EwGfekQEp4U%3D" rel="nofollow">jQuery UI</a></li>
<li><a href="https://link.segmentfault.com/?enc=qglqkaAB4HYtQyllPyUU2Q%3D%3D.vw29RMR%2BbC5TJIIi1lNXEt%2BCItNZJqhrvfbfm%2BqrEVU%3D" rel="nofollow">Chico UI</a></li>
<li><a href="https://link.segmentfault.com/?enc=Z0PrXYuJYsABDDPDUrllxg%3D%3D.P05iVY46VplAmtkfxPlXZ1RVw4G104vkcAgffPVxDl8%3D" rel="nofollow">Easy UI</a></li>
</ul>
</li>
<li>
<p>基础工具库:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=hO9xu1jaNXLffA%2BJa%2FqMtw%3D%3D.uIXG6LUPjeir5IR6XHlOs8mp5V%2BMmy5ct9XujQ6xflg%3D" rel="nofollow">jQuery</a></li>
<li><a href="https://link.segmentfault.com/?enc=zfpkBp2n8JxrddfOCr2IQA%3D%3D.uxqFIxc2asquRQbp%2FJqPqoNWJSp7nxrmAKIM5Fihfbg%3D" rel="nofollow">Underscore</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=%2FogC5Jsi5r1D1EEIFR0HkQ%3D%3D.MR1y6bwlyOxRNMnpOuNiXJGfBSXu54UMZc54mHrl8Mg%3D" rel="nofollow">zepto</a>:另一个jquery</li>
</ul>
</li>
<li>异步编程库: <a href="https://link.segmentfault.com/?enc=5OqDktr%2Blw54FmfplBUkSw%3D%3D.CdN%2FqUL3SqNQv9CdJwXjfQw8nenBTznsBGE34H2MG60%3D" rel="nofollow">queue.js</a>
</li>
<li>异步JS加载器和依赖管理器库:<a href="https://link.segmentfault.com/?enc=8r8CcxrNACe8EJLZ%2FRW4tw%3D%3D.yBT9ad9Wd9JgaYRNFOibDRecNMCOYhJPgDphBDqpEGhHmbppKlejHlvVlIcD2WRk" rel="nofollow">script.js</a>
</li>
<li>本地存储工具库:<a href="https://link.segmentfault.com/?enc=6tq838y%2B9VaXBbYTrZOq%2BA%3D%3D.gERNGNTUvvJmCLxTGzmzUIOc39nYSfzZU0LbjQ6YzGsGvzK94D8gHcM5ZWpKMZqo" rel="nofollow">store.js</a>
</li>
<li>视差滚动: <a href="https://link.segmentfault.com/?enc=QGHoaTg6u9PZ1VY5IXb7dg%3D%3D.cOCBY52weIcxnISec0cpNOA8o9ctoHXGpNV1vxyZFJ77uSCEhKxP797DZ8BFMT26" rel="nofollow">skrollr</a>
</li>
</ul>
<h5>框架</h5>
<ul>
<li>
<p>模块加载框架:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=jwfomdXaf6LYo%2Bl5aHdshg%3D%3D.9AVh7ia0GfX566RbbhDTb3s6IlLiXoRooIiwP0oYiOY%3D" rel="nofollow">SeaJS</a></li>
<li><a href="https://link.segmentfault.com/?enc=WTcuQwt0TOfKdQzM%2BP8suw%3D%3D.QVhR0mUFklYXKtEJbkxUs1YPdbL1j38lL8lQSSes8nU%3D" rel="nofollow">RequireJS</a></li>
</ul>
</li>
<li>
<p>MVC应用框架:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=H0KThRa6WACZZbpvPswvfw%3D%3D.wBJKts0QpwxEHfQ97JWA7GJo9Sz%2FGFpaTdmylvmiDHI%3D" rel="nofollow">Backbone.js</a></li>
<li><a href="https://link.segmentfault.com/?enc=1Pec9lGpDMseydheF3Uv1Q%3D%3D.1s9FycP2T0YSB%2FEUyiNIRLpJpEpqTOASdhSkYFFeq9M%3D" rel="nofollow">Ember.js</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=DTNIyygzCItGW7jfdzaZHw%3D%3D.geDOGbPyxar9NfSG8RGNWV%2Bjmdguo6a3%2F5%2FjH%2FP3eGQ%3D" rel="nofollow">JX</a> (<em>腾讯web前端框架</em>)</li>
<li><a href="https://link.segmentfault.com/?enc=W8dTpcPF8fXRJKRh6cWN2Q%3D%3D.kD6rVbIf3KxG09leWpJWJVDYlZ8fnc6iIVPY6zCzV8U%3D" rel="nofollow">Spine.js</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=fGXJ5Sdp%2FEYSZsZ%2BFStP2w%3D%3D.ytzFcHoPVCrsaG5TbVbf%2FjD4d2yuIBIJflOqrjxJeu0cfq7xdAiH%2B1dBD3eeTJOm" rel="nofollow">Reactjs</a> (<em>時下火爆的前端框架</em>)</li>
</ul>
</li>
<li>
<p>MVVM应用框架:</p>
<ul>
<li><a href="https://link.segmentfault.com/?enc=7CKkbVK2F%2FD6ROiWjetPXA%3D%3D.82cAIyGwvPenPCprEg9XAgyV488PFoerw9YI2Za9JWY%3D" rel="nofollow">AngularJS</a></li>
<li><a href="https://link.segmentfault.com/?enc=8tvdTVkEFeWKi1ISxtNWng%3D%3D.cyOBaPkkgG7CGnBA2CTzgGPd%2FMrxCkFgAFkUiyalGfs%3D" rel="nofollow">Vue.js</a></li>
<li><a href="https://link.segmentfault.com/?enc=HNEbEuEchJ7te2dDmda0ig%3D%3D.hEju%2F1treUORp8Q0qBZCAdYk%2BsczvxndYmJVjQatJm8%3D" rel="nofollow">avalonJS</a></li>
</ul>
</li>
<li>MVP框架: <a href="https://link.segmentfault.com/?enc=N38nyj8dK6M9bXz0XR5hMg%3D%3D.ap%2Bmh%2F%2Bm7cI4TpqfLqzMd4An2FMEwXhwT3w3KMqSJM8%3D" rel="nofollow">Riot.js</a>
</li>
<li>
<p>前端开发框架:</p>
<ul><li><a href="https://link.segmentfault.com/?enc=usLolkT4ZNVDROFn8CCgCw%3D%3D.tU39JBpEkzPBDCrwezJfklNlYCwxcsnirN7h7P4pMmg%3D" rel="nofollow">Bootstrap</a></li></ul>
</li>
<li>
<p>HTML5开源免费游戏框架:</p>
<ul><li><a href="https://link.segmentfault.com/?enc=gbgUHoI39cppCdULlEHtuw%3D%3D.TGuTWEam7h%2BsS0iu%2F4IpaoGa9pfBYXWh1srm4ydzM54%3D" rel="nofollow">Egret</a></li></ul>
</li>
</ul>
<h2>>CSS部分</h2>
<h4>①相关书籍</h4>
<h4>②博文精贴</h4>
<h4>③框架/库精选</h4>
<h5>工具库</h5>
<ul><li>预设css3动画库:<a href="https://link.segmentfault.com/?enc=R73iLx69BlhSLxs8hvCPmw%3D%3D.yfNIraRZtpYYr1a0iXw4DDVw3S54cPnozGCnYyUg6Fh5VVsWGOvENR1V5wc6z7nH" rel="nofollow">animate.css</a>
</li></ul>
<h2>>HTML部分</h2>
<h4>①相关书籍</h4>
<h4>②博文精贴</h4>
<h2>>其他</h2>
<h4>①浏览器相关</h4>
<ul><li>
<h5>博文精贴</h5>
<ul><li><a href="https://link.segmentfault.com/?enc=ukBWKJu8dbzJbNEqIxXB3A%3D%3D.FJclhNdSURkvj2Eragsaax%2FUfHpC7WQZD2nkdXKAnuq9j84grXsnPMuirUY6mF4aopk8wXjY%2FsZuc1T%2BgOKm3Pn3A2%2F0MogGV%2BbDb4MWR3hMmL%2BFFgrUveuQhjzmtKbhxFZokoXL%2BBNE8CElTTwdc3vJgAPmEoLXsBwdDf49Lxo%3D" rel="nofollow">How browsers work--Behind the scenes of modern web browsers (前端必读)</a></li></ul>
</li></ul>
<h4>②热门社区/论坛</h4>
<ul>
<li>
<h5>前端</h5>
<ul>
<li><a href="https://link.segmentfault.com/?enc=%2FZIGiXJCWiHQbgcIVynIyA%3D%3D.tKjjhdfSqKC6DwVZ3fTHaanOXVcgu1yBSB52eZTiPc8%3D" rel="nofollow">前端乱炖</a></li>
<li><a href="https://link.segmentfault.com/?enc=ZWvCX5G%2FBBhynUR%2FeA7XUw%3D%3D.exlPl7Jc3PY8ow6IjuBK2ORGSs9ABWlmT3VOFtquH80%3D" rel="nofollow">前端网</a></li>
<li><a href="https://link.segmentfault.com/?enc=N8bfbYFHiTnl5QAgRHj24g%3D%3D.Gr%2FxqDVgqD6wcnifbA5OdUTJzzRiFfWPlZjDYciTP1Y%3D" rel="nofollow">F2E</a></li>
<li><a href="https://link.segmentfault.com/?enc=jeNIKjvqpCuBWu6v7RsmnA%3D%3D.XxiJ%2BOXUoT%2FmB2x3cUNe6m1Rv1aWjYEcO5PwgDDBxMk%3D" rel="nofollow">爱思社区</a></li>
<li><a href="https://link.segmentfault.com/?enc=s108fYs8raPCwLzrgItZUw%3D%3D.UqKY5P%2F0FEbIVZ44Nwr0pa9MzJItNrP3bcUHdiyTvI0%3D" rel="nofollow">前端里</a></li>
<li><a href="https://link.segmentfault.com/?enc=L7XufnoN1joJQECjGfRSPg%3D%3D.7K7HsJm9dRv2jV7t%2FgHtePeCHj6Iw1zuVQyNfzc6CwI%3D" rel="nofollow">前端观察</a></li>
<li><a href="https://link.segmentfault.com/?enc=tw6ayccxOXR8dYfjfDDgfQ%3D%3D.l8NcFj7tykPw1U3lZ5yBRw%3D%3D" rel="nofollow">Div.IO</a></li>
<li><a href="https://link.segmentfault.com/?enc=hDNfHGyiudibEh43e1QIMA%3D%3D.O2FiwW0mD9Mzarz3bqaXkKUb8%2FJFbPOg%2F9%2FE0hGr9YM%3D" rel="nofollow">w3cplus</a></li>
<li><a href="https://link.segmentfault.com/?enc=EPkJBJzPJtOczq%2F2aUnyLg%3D%3D.Q8aACZYA7d9d1I4za2l0UK5lPrxHHoaR9sy%2B3pKtx2w%3D" rel="nofollow">大前端</a></li>
<li><a href="http://segmentfault.com/">segmentfault</a></li>
<li><a href="https://link.segmentfault.com/?enc=5F%2FDOl5ADlJ4WeFM7O2IXA%3D%3D.tkEoKIvvo7OhUwGYDvdMjLgukdEtYSlWNdTX4lOUwFs%3D" rel="nofollow">Node.js 中文社区</a></li>
<li><a href="https://link.segmentfault.com/?enc=j%2FogN9ml5c%2BHaZTUptYuIQ%3D%3D.7InRuePZseM3xnuTnlxY%2BYUSAZgpkNPNr1Ic3uhp0OU%3D" rel="nofollow">stackoverflow</a></li>
<li><a href="https://link.segmentfault.com/?enc=c9lCVAeai3adfsi9bVASdQ%3D%3D.3%2Bloeme%2F4vn9e3BKAD1QlD67e9A9A8fuIyiaQXh4dQ4%3D" rel="nofollow">前端De早读课</a></li>
<li><a href="https://link.segmentfault.com/?enc=5ZG68bCmzSOoXPaUer2bNQ%3D%3D.z%2Bh3h5k25BYF0CLVqnv%2BQr0u0xyG8AzBeVHXr4ym6dU%3D" rel="nofollow">前端范</a></li>
<li><a href="https://link.segmentfault.com/?enc=adOjo2D4T6Z5LDC4cR5POg%3D%3D.%2FKf3%2BDwzympQgxFt5kB4bAIlCgdReLmy1LQQoTP3QVI%3D" rel="nofollow">愚人码头</a></li>
<li><a href="https://link.segmentfault.com/?enc=6EMeAqkde%2FW7PHljsVdB3A%3D%3D.5GgRbnieTXSl6VrX4Az5qTnHGh6G%2BCPE7YZpWJwCOs8%3D" rel="nofollow">ourjs社区</a></li>
<li><a href="https://link.segmentfault.com/?enc=pchMS9yytuxUSITRbS2ZSA%3D%3D.2t5SfgT4%2Bx94MUULWXaDxVm180Wk5gnyQA1EYBe%2F0es%3D" rel="nofollow">有料到</a></li>
</ul>
</li>
<li>
<h5>开源</h5>
<ul><li><a href="https://link.segmentfault.com/?enc=VtOP0yH06nprV8tb2Tl%2BLg%3D%3D.qH7CeQE1xR%2FXmSiY2n9%2FD515jfbFsGcuZaugOjE2Pjs%3D" rel="nofollow">深度开源</a></li></ul>
</li>
<li>
<h5>网站导航</h5>
<ul>
<li><a href="https://link.segmentfault.com/?enc=e1k%2BPS5FmQdSEyW6Qe0CzA%3D%3D.M1R2tF2y4SayAuU0nWxWB8ppc8QdP9iDW43951OSTZE%3D" rel="nofollow">醉牛前端</a></li>
<li><a href="https://link.segmentfault.com/?enc=w7kA05%2FpkPzfskSexZFJ1Q%3D%3D.yQrqJDa%2FZpGPj%2Fqf3g4r4j0uQ20M04R305fWWjdWOyE%3D" rel="nofollow">whycss</a></li>
<li><a href="https://link.segmentfault.com/?enc=gHW9qEepxQGR%2BW19fTQWJA%3D%3D.VcRvd2xugRVQBL0GyHyLjqJCsciCh944SbGu7ZKitDE%3D" rel="nofollow">大前端</a></li>
<li><a href="https://link.segmentfault.com/?enc=m%2BzvZ1gW0YLu7GCYGcepsg%3D%3D.Dj9anEbY4RVCCh283EKPO%2BJ8sP7WY1pTryNwVDJi3T4%3D" rel="nofollow">前端名录</a></li>
<li><a href="https://link.segmentfault.com/?enc=XNSikMA0CgwMGS0s4hVuxw%3D%3D.q7jFThaN9vqacc80%2FCMh8wmkQZUmMXEazudKZe%2F1YrU%3D" rel="nofollow">愚人码头</a></li>
<li><a href="https://link.segmentfault.com/?enc=a%2BLVEeQfqTuZGm9%2BIAuPoA%3D%3D.gps1ndTJr3jzmd4582UovWAYQas951dtYloXcsZ722o%3D" rel="nofollow">前端网址导航</a></li>
<li><a href="https://link.segmentfault.com/?enc=RfWY2UcX0KeMrItEZQuQHg%3D%3D.vlULUpttf1UMuBC8KtKd5b0ZDwzXfK2iivNHAoHSS3U%3D" rel="nofollow">UEDH</a></li>
<li><a href="https://link.segmentfault.com/?enc=gXf05mdG%2FhlaqUZtXJSilQ%3D%3D.ip66fmkCWXFBcaBCikBNmc2dFowQ5H9K0AOc92auuhc%3D" rel="nofollow">H-ui IT网址导航</a></li>
<li><a href="https://link.segmentfault.com/?enc=XxAN8ojteorvpsTWzRSTOw%3D%3D.WM2US2mbOnvmT7j%2Br7Bc4xocopHJWkRa6F2xYBGPpEg%3D" rel="nofollow">前端开发点</a></li>
<li><a href="https://link.segmentfault.com/?enc=mmfH1luER3iKlFONA4wREg%3D%3D.%2FFkLaa2ybiN8YYPVqwv3t2z7fBb65HjREHGfyCOfEZE%3D" rel="nofollow">前端资源导航</a></li>
</ul>
</li>
<li>
<h5>热门博主</h5>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=KGgDTJo8jw1AK%2F1p7NRXLw%3D%3D.8bQEbQi7J%2B%2Bdx4fjGtSXN6se58NwVPrwKQH2C8QwhCi8OmKeNACkHU5KTcVA899B" rel="nofollow">Douglas Crockford</a>,JSLint 作者,《语言精粹》作者</li>
<li>
<a href="https://link.segmentfault.com/?enc=UtpoqC6OdpnDWxCqF3OJdQ%3D%3D.%2B7x4I2Cjgh152hFPsBdkC2XaPuzl8xuPgoVIQ5wYK4U%3D" rel="nofollow">John Resig</a>,jQuery 作者</li>
<li>
<a href="https://link.segmentfault.com/?enc=kAEH8F1vZuOz4XXEwI7oAQ%3D%3D.DIOueQAqeidSD9bc6kkN174EEPpj19pK%2BvsZrXY3wmI%3D" rel="nofollow">Eric Meyer</a>,CSS 相关的 web 标准推广人</li>
<li>
<a href="https://link.segmentfault.com/?enc=FhUz8KwHvM68qGwa5H%2BnXQ%3D%3D.Ai%2BO3Y9HRmCTK0Syrzq1Tg6keMZZ3U42wYihePpuvck%3D" rel="nofollow">Peter Koch</a>,主要是 JavaScript API</li>
<li>
<a href="https://link.segmentfault.com/?enc=wws9YEPKtTEXhpr99FkPRA%3D%3D.jeELo4DMj4SEvWNTdXVmr%2BJ%2B3vL%2FiCzRsOt3f3fPEZA%3D" rel="nofollow">Paul Irish</a>,HTML5 Boilerplate 发起人之一</li>
<li>
<a href="https://link.segmentfault.com/?enc=YC%2B85ANj5uSvvSxXvxzTeQ%3D%3D.mS%2B7tU67dNbJRNR%2FqKKxH66CL4KZCKwlGjI4NwsQ5Rw%3D" rel="nofollow">Alex Russell</a>,Chrome team 成员</li>
<li>
<a href="https://link.segmentfault.com/?enc=XX8WAH3xJ1f9vznjo8ydOA%3D%3D.UWaCylf%2FNT%2BBftM%2FwtW6w9Q%2FZRbiB0HShzx0lcyZJyM%3D" rel="nofollow">Nicholas Zakas</a>,High Performance JavaScript 作者</li>
<li>
<a href="https://link.segmentfault.com/?enc=L1WY1FAnLWbNQDD%2FK8Sy7A%3D%3D.voI%2BMaazacUxklVSkQ5XP%2FKNoS%2FS0jhOJ3kWHHdWp9o%3D" rel="nofollow">Dustin Diaz</a>,前 Googler,现 Twitter</li>
<li>
<a href="https://link.segmentfault.com/?enc=1Tck1nLZnWjecvV8lKU7QQ%3D%3D.vJTvf50D%2FT4kmdO9SIbhhoxrRzWHc1qKJ40wSpSSWy0%3D" rel="nofollow">DailyJS</a>,业界新闻博客</li>
<li><a href="https://link.segmentfault.com/?enc=Kc%2FSjLAUBRMLpDczN5i9nA%3D%3D.L067GVSK9t2A16EE1j6bQMkmZtDs%2BeUQ1cbhuK%2BtT9g%3D" rel="nofollow">limu的砖篮儿</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=ZkkRcyCFncx%2FedjnCflZIA%3D%3D.OJKHcXH3yp42bjGb4ugwiBy%2F4youZ0EebYJsCGvjir4%3D" rel="nofollow">VILIC’s Blog</a>:一员小将,功力不俗</li>
<li><a href="https://link.segmentfault.com/?enc=1HpA5IRNBvr%2FPd4X753sTQ%3D%3D.I9mcazfB5yCZmkd5GCEN02FeVl9JGYW6YCffaxcek9c%3D" rel="nofollow">老赵</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=qd%2BHGtaYeu15T7UtpAmEJQ%3D%3D.ZmQWIG4%2B4vRCWVueQcnfWZ%2B7c51Wg5hzsSoGACdHJA3zqP0Cvo98KWsZ7aVcOqa7" rel="nofollow">博客园-三生石上</a>:亦是js领域牛人,耳熟能详的js秘密花园就是其译作。</li>
<li>
<a href="https://link.segmentfault.com/?enc=jnOm0rFengi6N35SRWLj3A%3D%3D.5tPnUmVMtJo6YSOebPMWC8Bufv0kkOYZXZT6JojH2co%3D" rel="nofollow">博客园-Novice Doodle from Gray Zhang</a> & <a href="https://link.segmentfault.com/?enc=Mp8vb%2BgDN%2FLLIgDzBFrd9A%3D%3D.aJsL7X6sywYLFWgkJ0aVmvsbIJKxDxj6VQsmFHcKvdas6ucaS5NMYmLpNoAaaugZ" rel="nofollow">宅居</a>:他有两个blog,百度的前端大牛</li>
<li>
<a href="https://link.segmentfault.com/?enc=fdhtkHiyRi9AoinLDQdsYQ%3D%3D.o8ICn4IX6EWMe%2B1gg4oPkqX805epnvpsNDWpCJc6KUv%2F3Hkb4gH%2BcBfFpkI%2F0mk3" rel="nofollow">阮一峰</a>:由于也有很多关于前端的blog,姑且也算他是前端人士吧。</li>
<li><a href="https://link.segmentfault.com/?enc=YL3H%2FgOtx7jOr5Wii9eaJA%3D%3D.6skWClYwpeCRgi4fdwkULK8vc4RLxOTUVD5LmA0HDBY%3D" rel="nofollow">INVIS是主要作者,大牛,写js编译器的,关键是,此人才大一啊,不可估量啊</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=wUl28TKybhoN2wg3xJwmBw%3D%3D.L%2B7PYpqb2qARjmq1ASQcnqNs%2FpqC6xbc2h3CJv1IHbM%3D" rel="nofollow">hax的技术部落格</a>:也有人提到了,看其博文是种大涨眼界的享受</li>
<li><a href="https://link.segmentfault.com/?enc=sNa1UPpMK%2B0dTj%2FX7iH19g%3D%3D.zxz%2Fj2d%2BwkUeE%2BTxbJIINgB6vERk%2Byb7aCIy65mZXK8%3D" rel="nofollow">aimingoo的专栏:爱民老师</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=BNeeWDRsTS2ybLMjD49hdg%3D%3D.3tjZLDkTdnXAUt%2FZcrznBaMlKUYa2mjEWkNAr4rMDzSlU2tOQbZLKiYx6bxxnvKs" rel="nofollow">博客园-Ruby’s Louvre:司徒正美</a>,除了众多博文外,最近亦有mass Framework 释出</li>
<li><a href="https://link.segmentfault.com/?enc=ZcQHTxnsw5yhtgxkxWCb7g%3D%3D.j6UEn5eCYohq6IRm%2BA%2FEqNN%2Bp4WZwPYsrswThNQRUss%3D" rel="nofollow">博客园-Franky</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=vAflGYPViNx0mCLc5qYh7g%3D%3D.5851WV%2FTNXs5MDQhV9Wys%2Fu8I7MPa4F7ZBAPvVXiAvY%3D" rel="nofollow">玉伯</a>:现在在<a href="https://link.segmentfault.com/?enc=bQKIqh%2FuLBIo%2Bx30IwYgKA%3D%3D.GPEcR24oRh%2BgDqrDqAapxhC%2FlNREvevamjjEuZkJaJ644kAKwmWJBfXZR2r9%2BMLh" rel="nofollow">http://wordpress.com活动</a>,可惜需要翻墙</li>
<li><a href="https://link.segmentfault.com/?enc=cLJpgHXENZnZ4xCnBk392A%3D%3D.S%2BbGzvJgYTxvZs8CbaUT1KRs97IuE2qSKRm9ZrX9wdSrBAfl36T8ZtKfat5UdkkM" rel="nofollow">国外23位知名JavaScript开发者</a></li>
</ul>
</li>
<li>
<h5>资源网站</h5>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=BQ8kJwbSqxHa5VamINmIjw%3D%3D.RL2Nvnpno0aqAlWETHQrqQg6lRiv4aFLQprB%2BNVqnwE%3D" rel="nofollow">看云</a>:在线文档创作、协作、分享与托管</li>
<li>
<a href="https://link.segmentfault.com/?enc=1zaR2qwpHOaPFQoLwlzy4Q%3D%3D.puElp0mBAI6Rxxj1dWu2hGQNme9zMhwOBzXVkHFly1DiR776yqrB4l8WUzDx0Lx4kpwGckV92WvA9hLRn%2B1ixg%3D%3D" rel="nofollow">w3cfuns资源</a> :可以下载相关电子书籍</li>
<li><a href="https://link.segmentfault.com/?enc=x35ja%2Bi01nfsenV3ydeGmg%3D%3D.3mpO9mz%2F5WUN2I1s20YTogI2gF%2BZKlFafKt8e%2BWIpFg%3D" rel="nofollow">IT Ebooks</a></li>
</ul>
</li>
<li>
<h5>其他汇总整理</h5>
<ul>
<li>
<a href="https://link.segmentfault.com/?enc=bKyWeWa%2F%2F9wv5TxQlX8Nhg%3D%3D.36If7v%2Fl3HoMiUb7klTzclu0W47LjXbA7PkZKY8nBes%3D" rel="nofollow">FKS</a> <a href="https://link.segmentfault.com/?enc=zP4fgINgFZAFfIod26wLVQ%3D%3D.ezhWq5DCqWww1tqIvWLSz07bt0zwRN%2BwM7hygpJ5fTw%3D" rel="nofollow">备用地址</a>
</li>
<li>
<a href="https://link.segmentfault.com/?enc=%2BEK%2FPWPgA1dw3PShFbDuKw%3D%3D.n5QXwwKdPSp23sY0v%2B68wbmJrrRccqDGY6B8N1vB8ZL%2F3VFKZKfAaCnHyPNWdKiM" rel="nofollow">大前端工具集</a> <a href="https://link.segmentfault.com/?enc=XRjoTtxs3F8W9g%2FYm38H6Q%3D%3D.U6ZDu%2FrZlDrFl4R7xIpDLssbQ86xuZ5nLJ%2Bey2ucZTU%3D" rel="nofollow">备用地址</a>
</li>
<li><a href="https://link.segmentfault.com/?enc=WOeyyMGRsl9UmbxPB82D2g%3D%3D.MC8zpEzEFpmTb4j78ILYuMp046VuoNTk8TCOZWJ3pDU94uWTbi%2BVnVWlxTFusPmxBWfL3J1ZtyE6wMTesZnn3w%3D%3D" rel="nofollow">JavaScript-material-collection</a></li>
<li><a href="https://link.segmentfault.com/?enc=9UrVyQTVMxDN%2FxorA9a%2BBA%3D%3D.oZd%2FjzRe4SfIdDoUyj%2Ft2k0ztC7WBPZyyJcOjzaVmJIdFz3XmgFDOx3WOT1CEafpqlUls%2FsaX6oV5wUtpmaPdw%3D%3D" rel="nofollow">front-end-interview-questions前端开发面试题基础篇</a></li>
<li><a href="https://link.segmentfault.com/?enc=Xlt20lvsAlQigi8cJFlMNA%3D%3D.cSkwJ9iH5JG9SWL0TukZdUv1qV3lVOs6Ltp2UbuRpzDEK7ov0CIgL4Z4QHYZazE3" rel="nofollow">frontend-system-map前端知识体系</a></li>
<li>
<a href="https://link.segmentfault.com/?enc=J%2BySWg5vT91cLgOcBwEPxQ%3D%3D.yHWLMh10H%2FMw0E0KGvjo9a8jncssgTEpD7SmmtDknoSjBmLlLWWuEpXifGSgiY3C" rel="nofollow">前端收集</a> <a href="https://link.segmentfault.com/?enc=q3ZuJ5ceIqjibpBlrMZELw%3D%3D.Z40Har06f2TcVeELqiQFh7oD%2FAsM2gfg%2Blptld1X8XQN72hl%2FqsQBK0NQqdNfSiA" rel="nofollow">备用地址</a>
</li>
<li><a href="https://link.segmentfault.com/?enc=RH1ZLwQyQQXvCiUku97y6A%3D%3D.JmQK6XDudIOEMBxfhPJ5nES4jEXaG%2BdOIztB42vRbh%2B5krUwpMNebxQCPOX3VboLTjsd7XSHaT22JdOJVINiMQ%3D%3D" rel="nofollow">(精华)前端资源教程</a></li>
</ul>
</li>
<li>
<h5>知乎精选</h5>
<ul><li><a href="https://link.segmentfault.com/?enc=DuCjCNbnRu3XMp0PlWr7%2BQ%3D%3D.rFobZVsx5xAnqBDIKSegyX7jlFhV9GaCCcUI4PViVuSh%2FqTH94tYzfZpo3gTD0wZ" rel="nofollow">前端大牛们都学过哪些东西</a></li></ul>
</li>
</ul>
【ES6系列】变量与块级作用域
https://segmentfault.com/a/1190000014287310
2018-04-10T16:30:44+08:00
2018-04-10T16:30:44+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<blockquote>ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。</blockquote>
<p>ES6的出现已经很久了,由于之前的项目只是基于ES5以及之前的版本内容进行开发的,所以没有系统性的梳理学习ES6的相关知识。感觉自己已经被发展的车轮落下了好远好远(@﹏@)~,所以在接下来这段时间针对性的进行学习整理,希望能够让自己有所提高,也希望能帮助和我一样的人……</p>
<h2>变量</h2>
<h3>常量 const命令</h3>
<h4>常量的定义</h4>
<p>在之前的版本ES5甚至更早的版本(后面统称ES5)中,很少能够听到常量的概念,我们也知道,JS不同于其他的语言,如Java、C++等,有常量的概念。虽然通过其他的方法能够达到实现常量的方法,但是并不直接、方便。</p>
<p>如可通过下述方式进行常量的实现:</p>
<pre><code>// 此处在window对象上面定义了一个圆周率变量PI,使之不能被修改,模拟常量
Object.defineProperty(window, "PI", {
value: 3.1415926,
writable: false
})</code></pre>
<p>在ES6中,定义一个常量则非常简单,通过const进行声明即可。</p>
<blockquote>const声明一个只读的常量。一旦声明,常量的值就不能改变。</blockquote>
<pre><code>const PI = 3.1415926;</code></pre>
<p>const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。</p>
<h4>const定义常量的本质</h4>
<p>const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。</p>
<p>如:</p>
<pre><code>const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only</code></pre>
<h3>let命令</h3>
<p>在ES6中新增了let命令,可以用来声明一个变量,作用类似于之前常用的var,但是和var声明的变量其有效的作用域不同,let声明的变量只在let命令所在的代码块内有效。</p>
<p>如:</p>
<pre><code>{
let a = 1;
var b = 2;
}
console.log(a); // Uncaught ReferenceError: a is not defined
console.log(b); // 2</code></pre>
<p>通过上面代码的执行结果来看能够看出,let声明的变量只在它所在的代码块中有效,即{}所在的代码块内。</p>
<p>那么let声明的变量在什么情况下会比较有用呢?先让我们来看下下面一段代码:</p>
<pre><code>var arrays = [];
for (var i = 0; i <= 2; i++) {
arrays[i] = function() {
return i * 2;
}
}
console.table([
arrays[0](),
arrays[1](),
arrays[2]()
])</code></pre>
<p>可能我们预期的结果是显示0,2,4,但是从执行代码的结果中能够看出,得到的却是6,6,6,这是为什么呢?<br>首先我们上面是使用的var声明的一个i变量,此处会有一个变量提升,相当于在for循环外面声明了var i = 0变量。当在for循环中时,arrays[i]中的i即为每次循环的i的值(0,1,2),但是在函数体中并没有将i变量取值为每次循环的值,它只是对变量的一个引用,并不是值的引用。当在执行循环中的函数体时,i变量已经变成了3,所以每次的结构输出都是6。这个时候,ES6中的let命令就能够满足我们的要求了。</p>
<pre><code>var arrays = [];
for (let i = 0; i <= 2; i++) {
arrays[i] = function() {
return i * 2;
}
}
console.table([
arrays[0](),
arrays[1](),
arrays[2]()
])</code></pre>
<p>上面代码中变量i只在for循环体内有效,只在其所在的块作用域内有效,每次for循环时会将i变量的值保存在其对应的块作用域中(for循环的{}),每一轮循环都是重新生成一个新的作用域,在执行for循环中声明的函数时,都会获取其对应作用域内的i的值。这部分涉及到的作用域相关的概念,我们后面会说到。</p>
<p>1、在前面提到过,var命令声明的变量会发生变量提升的现象,即变量可以在声明之前使用,值为undefined。按照一般的逻辑来说,变量应该在声明语句之后才可以使用。所以在ES6中为了纠正这个现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。</p>
<p>2、只要在块级作用域中存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。</p>
<pre><code>var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}</code></pre>
<p>ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。</p>
<p>总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。</p>
<p>3、typeof不再是一个完全安全的操作。之前我们知道,针对一个没有生命的变量调用typeof方法时会返回undefined,但是在上面出现的“暂时性死区”中,typeof将会被报错。</p>
<blockquote>使用let声明变量时,只要变量在还没有声明完成前使用,就会报错。</blockquote>
<p>4、let不允许在相同作用域内,重复声明同一个变量。</p>
<h2>块级作用域</h2>
<h3>为什么需要块级作用域</h3>
<p>1、内层变量可能会覆盖外层变量</p>
<pre><code>var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined</code></pre>
<p>内层的tmp变量由于变量提升覆盖了外层的tmp变量,导致输出不符合预期</p>
<p>2、计数的循环变量泄露为全局变量</p>
<p>我们上面用到的for循环的例子就是这个现象的证明,var声明的变量i在循环结束后泄露成了全局变量。</p>
<h3>ES6的块级作用域</h3>
<p>在ES6中{}包裹的块即为一个块级作用域,并且块级作用域支持嵌套,如</p>
<pre><code>{{{{{let insane = 'Hello World'}}}}}; // 5层块级作用域</code></pre>
<p>并且外层作用域不能读取内层作用域的变量;<br>内层作用域可以定义外层作用域的同名变量。</p>
<p>在之前的版本中,当我们想要实现一个变量只在一个代码块中运行,我们往往需要通过使用立即执行函数来实现。如</p>
<pre><code>(function () {
var tmp = ...;
...
}());</code></pre>
<p>但是在ES6中则不再必要了,我们可以通过块级作用域就能够实现</p>
<pre><code>{
let tmp = ...;
...
}</code></pre>
<blockquote>本次主要针对ES6中的变量和块级作用域进行了梳理学习,并且通过与ES5的实现方式进行了对比,从而看出其变化以及快捷与便利。希望能够对您有所帮助,如有写的不对的地方望请指正,从而共同进步。</blockquote>
【微信小程序】初识小程序
https://segmentfault.com/a/1190000014095312
2018-03-30T17:06:39+08:00
2018-03-30T17:06:39+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<h2>什么是微信小程序</h2>
<p>按照微信之父张小龙的定义,小程序是一种不需要下载安装即可使用的应用。小程序实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用。同时,体现了“用完即走”的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无须安装卸载。</p>
<p>微信团队原计划称之为微信应用(App)号,不过由于Apple App Store的审核规则不允许包含应用分发、应用推荐,因此微信团队将之称为“小程序”。</p>
<h2>小程序的开发框架</h2>
<blockquote>小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生App体验的服务。</blockquote>
<h3>小程序底层实现</h3>
<p><img src="/img/bV7i03?w=275&h=234" alt="底层实现" title="底层实现"></p>
<p>小程序的底层实现可以用一句话进行概括:<strong>统一接口,不同实现</strong>。小程序到微信采用统一的接口,开发者只需考虑框架的语法和规则,不用关心底层如何实现。</p>
<h3>小程序 VS HTML 5</h3>
<p>1.开发语言不同</p>
<p><img src="/img/bV7iES?w=601&h=106" alt="小程序 VS HTML 5" title="小程序 VS HTML 5"></p>
<p>我们知道,小程序开发框架中脚本部分采用JavaScript语言进行开发。不过与HTML 5不同的是,框架提供自己的视图层描述语言WXML和WXSS,然后对这些文件进行编译,打包为类似于原生应用的形式进行分发。小程序的WXML标签语言和WXSS样式语言并非标准的HTML 5和CSS 3。</p>
<p>2.组件封装不同<br>小程序独立出来很多原生App的组件。在HTML5需要模拟才能实现的功能,在小程序里可以直接调用组件,如预览图片、录音等功能。</p>
<p>3.执行效率不同</p>
<p><img src="/img/bV7iIW?w=683&h=209" alt="加载流程" title="加载流程"></p>
<p>HTML 5在加载时受限于网络环境,需要顺序加载HTML、CSS、JS,然后返回数据,最后渲染页面,并显示在浏览器中。用户经常需要等待很长时间,体验会受影响。</p>
<p>相比之下,小程序的两个线程——Appservice Thread和View Thread会同时进行、并行加载,甚至Appservice Thread会更早执行。当视图线程加载完通知Appservice时,Appservice会把准备好的数据用setData的方法返回给视图线程。</p>
<p>小程序的这种优化策略可以减少用户的等待时间,加快小程序的响应速度。</p>
<h3>小程序结构概览</h3>
<p>小程序由以下5个部分组成。</p>
<p>App:指小程序整个项目。</p>
<p>window:用于设置小程序的状态栏、导航条、标题、窗口背景色。</p>
<p>页面:一个App包含若干页面。微信规定,同时打开的页面最多不超过5个。</p>
<p>组件:框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。这些组件是构成页面的基本元素。</p>
<p>路由:不同页面之间的跳转称为路由。</p>
<h3>小程序的生命周期</h3>
<p><img src="/img/bV7iMy?w=623&h=323" alt="生命周期" title="生命周期"></p>
<p>生命周期分为3个阶段:</p>
<p>第一阶段是App启动阶段,主要加载小程序window配置,注册程序和页面;<br>第二阶段是Page启动阶段,主要完成页面资源加载、页面渲染、页面挂载;<br>第三阶段是销毁阶段,主要完成页面隐藏、卸载和销毁。</p>
<h3>小程序的项目架构</h3>
<p><img src="/img/bV7iSI?w=468&h=432" alt="项目架构" title="项目架构"></p>
<p>pages文件夹用于放置页面文件。</p>
<p>app.js是App的启动脚本,可以处理一些App启动过程中页面加载之前要处理的事情。</p>
<p>app.json是App的配置文件,配置项包括window、页面、tab菜单栏等。</p>
<p>app.wxss是App的公共样式,类似于css。</p>
<p>一个App可包含多个页面,页面的相对访问路径被配置在app.json里,每个页面可以包含[页面名].js、[页面名].wxml、[页面名].wxxs以及配置文件[页面名].json。其中,WXSS和json文件不是必要的。如果这两个文件存在,就会覆盖app.json和app.wxss的相同配置项。</p>
<p>一个框架程序主体部分由3个文件组成,必须放在项目的根目录:</p>
<p><img src="/img/bV7iTE?w=613&h=113" alt="clipboard.png" title="clipboard.png"></p>
<p>一个框架页面由4个文件组成:</p>
<p><img src="/img/bV7iT6?w=610&h=137" alt="clipboard.png" title="clipboard.png"></p>
<h3>小程序配置</h3>
<p>小程序有两种配置:<br>(1)全局配置文件,用于小程序的全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多tab等。<br>(2)页面配置文件,用于配置各个页面,将覆盖app.json的window配置项内容。</p>
<h4>全局配置</h4>
<p>全局配置列表:<br><img src="/img/bV7iVw?w=616&h=165" alt="clipboard.png" title="clipboard.png"></p>
<p>1.Pages</p>
<p>Pages设置程序中所有页面,第一个页面为初始页面。<br>Pages每一项的命名为路径+文件名,一般为pages/dictionary/file。不需要加文件后缀。</p>
<p>2.window</p>
<p>window设置程序中默认页面的状态栏、导航条、标题、窗口背景色等<br><img src="/img/bV7iWr?w=616&h=223" alt="clipboard.png" title="clipboard.png"><br><strong>注意,如果页面需要启动下拉刷新,务必将enablePullDownRefresh设置为true。可以用onPullDownRefresh监听用户下拉刷新事件。</strong></p>
<p>3.tarBar</p>
<p>tabBar可以配置多个tab页面,配置项包括指定tab栏的表现和tab切换时显示的对应页面。<br>tabBar的位置可以在顶部(top)或底部(bottom)</p>
<p>属性表:<br><img src="/img/bV7iW6?w=609&h=199" alt="clipboard.png" title="clipboard.png"></p>
<p>list属性表<br><img src="/img/bV7iXe?w=615&h=140" alt="clipboard.png" title="clipboard.png"></p>
<p>4.networkTimeout</p>
<p>设置各种网络请求的超时时间</p>
<p><img src="/img/bV7iXo?w=610&h=157" alt="clipboard.png" title="clipboard.png"></p>
<p>5.debug</p>
<p>可以在开发者工具中开启debug模式,在开发者工具的控制台面板调试信息以info的形式给出,信息有Page的注册、页面路由、数据更新、事件触发等,可以帮助开发者快速定位一些常见问题。</p>
<h4>页面配置</h4>
<p>每一个小程序页面都可以使用.json文件对本页面的窗口表现进行配置,页面的配置只能设置app.json中的window配置项的内容,页面中的配置项会覆盖app.json的window中相同的配置项。页面的.json只能设置window相关的配置项,以决定本页面的窗口表现,所以无须写window键。</p>
<h2>小结</h2>
<p>本次主要介绍了小程序框架的底层实现、配置和逻辑层,讲解了小程序的项目目录架构、配置文件、生命周期、全局设置、页面设置等知识点。</p>
<hr>
<p>参考资料:<a href="https://link.segmentfault.com/?enc=vBk3Wu1cpJMH8Dm6Vt2aMg%3D%3D.wGuRodW68qzWgGS7xcPx0EuyQwI%2BiqEi%2FnUInmIvKXwE55e%2FlQZToP8F5xCBPYPJkPUSDeW8TCDLMBSPW8ffJdeDDbq7fp8ZcmCD7ZqkRJ5AN2S5Hq9mCxzc5pqDvAT7Bc8445vd8olRzkXtl%2FTsYw%3D%3D" rel="nofollow">《微信小程序开发详解》</a></p>
【便捷插件】地域选择插件Region
https://segmentfault.com/a/1190000012890965
2018-01-18T15:08:49+08:00
2018-01-18T15:08:49+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<h2>基本功能</h2>
<p>Region插件主要用于地域选择使用,可以同时多个地区添加</p>
<h2>插件由来</h2>
<p>由于项目功能需要,需要有一个能够多选国内市区功能的插件,通过在网上的查找,找到了百度的<a href="https://link.segmentfault.com/?enc=vrKoTwdIXRbcWtrCIw9fKg%3D%3D.xKWb4OA4nmcvmdbaAqZQCq6gzk08gsVJEuGeXk%2BkIJ5mu4%2F5hjRedCrEMqQrq3LzaTIvBYCuhsZdxElG%2FdtOsQ%3D%3D" rel="nofollow">ESUI组件库</a></p>
<blockquote>ESUI is a Collection of JavaScript Controls.</blockquote>
<p>但是由于ESUI内容包含广泛,而且内部依赖非常之多(应该这套内容是百度前端“生态”中的一部分,但是维护来看截止13年就不在更新了)所以在ESUI的基础上面,将地域选择的功能进行了摘取。从而,有了此独立功能插件。</p>
<p>由于目前的项目需要,所以只是摘取了多选功能部分,其他部分功能暂时没有进行完善。<br> 后续会尽可能的不断完善。</p>
<h2>使用说明</h2>
<p><a href="https://link.segmentfault.com/?enc=bX8ITu1e%2BUF35qlsUOaRRw%3D%3D.0F5CjAqRDDqvpY8VGiv%2BnNEuPyxUAz%2F%2FYmbxHStfWYwvfCSucBaVgQBGyoFwd%2BcfA6HjUXgVl1fZeHD6xrtUR7EoDHmE07GB%2Fbcw%2BkCOVmd1DjdHyR5M85zZXZ0Pofda" rel="nofollow">Demo</a></p>
<p><img src="/img/bV2fFQ?w=701&h=445" alt="效果示意图" title="效果示意图"></p>
<p>HTML:</p>
<pre><code> <div id="region_test" class="ui-ctrl ui-region"></div>
</code></pre>
<p>Javascript:</p>
<pre><code> <script>
$(function () {
var options = {
main: "#region_test",
value: '268,25,10,778'
}
var region = new Region(options);
})
</script>
</code></pre>
<p>依赖:</p>
<pre><code> - jquery.js
- underscore.js
</code></pre>
<p>接口方法:</p>
<pre><code> - getRawValue 获取选择区域的数组值
</code></pre>
<p>数据结构:</p>
<pre><code>请参照
</code></pre>
<p><img src="/img/bV2fGy?w=436&h=705" alt="数据结构" title="数据结构"></p>
<p>更多详情请参照<a href="https://link.segmentfault.com/?enc=Bp65n8ZI8MGcJ70eU1FRSw%3D%3D.BEWL%2BBrhns8aFlQz4afY2S%2BueykiLzqMLi0pn4xqqGFr2%2FthG6VWW8XlBJ1HSJnV" rel="nofollow">以下地址</a></p>
【JavaScript设计模式】单例模式
https://segmentfault.com/a/1190000012687318
2018-01-03T16:36:10+08:00
2018-01-03T16:36:10+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<h2>单例模式</h2>
<blockquote>又被称为单体模式,是只允许实例化一次的对象类。有时我们也用一个对象来规划一个命名空间,井井有条的管理对象上面的属性和方法。</blockquote>
<p>传统的面向对象语言中单例模式的实现,均是单例对象从“类”中创建而来,在以类为中心的语言中,这是很常见的做法。如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来。但是在JavaScript中却是并不需要这样做。</p>
<pre><code>单例模式的核心是确保只有一个实例,并提供全局访问。
</code></pre>
<p>全局变量不是单例模式,但是在JavaScript中,我们经常会把全局变量当成单例来使用。如浏览器中的window对象。再比如:<br>var a = {}; <br>当用这种方式创建对象a 时,对象a 确实是独一无二的。如果a 变量被声明在全局作用域下,<br>则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就满足了单例模式的两个条件。<br>但是全局变量存在很多问题,它很容易造成命名空间污染。在大中型项目中,如果不加以限制和管理,程序中可能存在很多这样的变量。JavaScript 中的变量也很容易被不小心覆盖,相信每个JavaScript 程序员都曾经历过变量冲突的痛苦,就像上面的对象var a = {};,随时有可能被<br>别人覆盖。</p>
<h2>命名空间</h2>
<p>适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。 <br>最简单的方法依然是用对象字面量的方式:</p>
<pre><code>var namespace1 = {
a: function(){
alert (1);
},
b: function(){
alert (2);
}
};</code></pre>
<p>把a 和b 都定义为namespace1 的属性,这样可以减少变量和全局作用域打交道的机会。另外<br>我们还可以动态地创建命名空间,代码如下(引自Object-Oriented JavaScrtipt 一书):</p>
<pre><code>var MyApp = {};
MyApp.namespace = function( name ){
var parts = name.split( '.' );
var current = MyApp;
for ( var i in parts ){
if ( !current[ parts[ i ] ] ){
current[ parts[ i ] ] = {};
}
current = current[ parts[ i ] ];
}
};
MyApp.namespace( 'event' );
MyApp.namespace( 'dom.style' );
console.dir( MyApp );
// 上述代码等价于:
var MyApp = {
event: {},
dom: {
style: {}
}
};</code></pre>
<h2>模块管理</h2>
<p>其实在JavaScript中单例模式除了定义命名空间外,还有一个作用,就是通过单例模式来管理代码库的各个模块。这样我们就可以通过单例模式来创建一个自己的小型代码方法库,进而来规范我们自己代码库的各个模块的功能和方法。比如:</p>
<pre><code>var OurLib = {
// 公共模块部分
Util: {
util_method1: function(){},
util_method2: function(){}
// ...
},
// 工具模块部分
Tool: {
tool_method1: function(){},
tool_method2: function(){}
// ...
},
// ajax模块部分
Ajax: {
get: function(){},
post: function()
// ...
},
others: {
// ...
}
}</code></pre>
<h2>维护静态变量</h2>
<p>我们知道的是JavaScript中并没有static这类关键字(暂不考虑es6等新语法),所以定义任何变量理论上都是可以更改的,所以在JavaScript中实现创建静态变量又是很重要的。根据静态变量只能访问不能修改并且创建后就能使用这一特点,可以如此来实现:能访问的变量定义的方式有很多,比如定义在全局空间中,或者定义一个函数内部,并定义一个特权方法来进行访问等。但是既然不能修改,定义在全局空间里面就不行了,我们可以将变量放在一个函数内部,通过特权方法进行访问,并且不提供赋值变量的方法只提供获取变量的方法,就可以了。由于放在了函数内部,如果想要能够供外界访问,我们需要让创建的函数执行一遍。此时我们创建的对象内保存静态变量通过取值器访问,最后将这个对象作为一个单例放在全局空间里面作为静态变量单例对象供他人使用。如:</p>
<pre><code>var Conf = (function(){
// 私有变量
var conf = {
MAX_NUM: 100,
MIN_NUM: 1,
COUNT: 1000
}
// 返回取值器对象
return {
// 取值器方法
get: function(name) {
return conf[name] ? conf[name] : null;
}
}
})();</code></pre>
<p>这样我们就可以来维护我们的静态变量了。</p>
<h2>惰性单例</h2>
<p>有时候对于单例对象我们需要延迟创建,所以在单例中还存在一种延迟创建的形式,也被称为“惰性创建”。例子如下:</p>
<pre><code>// 惰性载入单例
var LazySingle = (function(){
// 单例实例引用
var _instance = null;
// 单例
function Single() {
/* 这里定义私有属性和方法 */
return {
publicMethod: function(){},
publicProperty: '1.0'
}
}
// 获取单例对象接口
return function(){
// 如果未创建单例,将创建单例
if(!_instance) {
_instance = Single();
}
// 返回单例
return _instance;
}
})();</code></pre>
<h2>总结</h2>
<p>通过上面对于单例模式在JavaScript中的一些提现形式,我们对于单例模式的使用有了一个基本的了解,同样也理解了为什么很多代码库都提供一个变量(当然还有一些还提供了简单的别名,如jQuery的$别名,underscore的_别名等)来管理每个模块的方式。这也为我们以后来设计我们自己的公共代码库提供了参考。但是同样的,也能发现,单例模式在JavaScript中是有别于其他的面向对象语言中的实现方式的。</p>
<p>最后,欢迎大家拍砖,但是请轻拍:)</p>
2018年系列之设计模式汇总贴
https://segmentfault.com/a/1190000012669119
2018-01-02T14:49:22+08:00
2018-01-02T14:49:22+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<blockquote>转眼间已经进入了2018年,回顾过去的一年,才发现自己并没有达到预期的目标。虽然有所提高,但是和自己的预期相差甚远。希望能够借着这个系列的开始,给自己的2018年开个好头。</blockquote>
<h3>设计模式</h3>
<pre><code>设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。</code></pre>
<p>我们使用设计模式的目的:</p>
<ul>
<li>为了代码可重用性</li>
<li>让代码更容易被他人理解</li>
<li>保证代码可靠性。</li>
</ul>
<p>设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。<br>设计模式本身是和语言没有直接关联的,它是属于一种经验的总结。而我们这个系列,将通过JavaScript来作为分析的主要开发语言。</p>
<h3>设计模式的六大原则</h3>
<ul>
<li>1、开闭原则(Open Close Principle)<p>开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。</p>
</li>
<li>2、里氏代换原则(Liskov Substitution Principle)<p>里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。—— From Baidu 百科</p>
</li>
<li>3、依赖倒转原则(Dependence Inversion Principle)<p>这个是开闭原则的基础,具体内容:真对接口编程,依赖于抽象而不依赖于具体。</p>
</li>
<li>4、接口隔离原则(Interface Segregation Principle)<p>这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。还是一个降低类之间的耦合度的意思,从这儿我们看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。降低依赖,降低耦合。</p>
</li>
<li>5、迪米特法则(最少知道原则)(Demeter Principle)<p>为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。</p>
</li>
<li>6、合成复用原则(Composite Reuse Principle)<p>原则是尽量使用合成/聚合的方式,而不是使用继承。</p>
</li>
</ul>
<p>目前的基本计划是,通过JavaScript来展示不同的设计模式的实现方法,进而归纳整理,本文将会作为整个系列的汇总贴不定时进行更新。希望大家多多关注。</p>
<p>PS:之所以优先写出这篇汇总贴,也算是对自己的一个鞭策吧,毕竟承诺了的不能不兑现呢!<img src="/img/bV1jZ2?w=36&h=36" alt="图片描述" title="图片描述"></p>
<p>附:设计模式间的关系:<br><img src="/img/bVMTPY?w=761&h=914" alt="图片描述" title="图片描述"></p>
<h3>设计模式汇总:</h3>
<ul><li><a href="https://segmentfault.com/a/1190000012687318">单例模式</a></li></ul>
【工作技巧篇】移动端顶部搜索功能实现
https://segmentfault.com/a/1190000011914440
2017-11-08T16:51:31+08:00
2017-11-08T16:51:31+08:00
前端荣耀
https://segmentfault.com/u/lengxing
1
<blockquote>
<p>最近公司产品需要在微信内部做一个minisite,其中有一个列表页需要有顶部的搜索功能,类似京东那种,有搜索框和筛选条件。产品需要的一个操作是,当用户下滑列表时,需要顶部的搜索只保留条件筛选,搜索框等需要隐藏;当往上滑动或者滑动到列表底部(无新数据加载)时,需要将顶部的搜索功能再显示完全。</p>
<p>上面是现实背景,下面直接列出基本的实现代码以供大家参考:</p>
</blockquote>
<p><code>`</code></p>
<pre><code>var oldScrollTop = 0,
filterFixed = 1,
fscrollTimer = null,
filterTop = 0;
function doScroll() {
return function() {
var st = $(window).scrollTop();
filterFixedDeal(st);
}
}
function resetSearchHeadwh() {
/*重置顶部搜索功能的样式*/
$('#searchHead').height($('#searchHeadFixer').height());
filterTop = $('#proFilterWrap').position().top;
}
/*判断滑动的方向*/
function filterFixedDeal(st) {
if (st > oldScrollTop) {
isScrollBottom(st);
if ((filterFixed === 1 || filterFixed === 2) && st > filterTop + 40) {
filterFixed = 0;
setFixedAnim();
}
} else if (st < oldScrollTop) {
if (filterFixed === 0 || filterFixed === 2) {
if (st <= filterTop - 44) {
filterFixed = 1;
setFixedAnim();
} else if (filterFixed === 0) {
filterFixed = 2;
setFixedAnim();
}
}
}
oldScrollTop = st;
}
/*顶部搜索框等的显隐切换,含基本动画*/
function setFixedAnim() {
fscrollTimer && window.clearTimeout(fscrollTimer);
fscrollTimer = window.setTimeout(function() {
var shf = $('#searchHeadFixer'),
temp = 0;
if (filterFixed == 0) {
shf.addClass('search_head_fixer');
temp = -4;
} else if (filterFixed == 1) {
shf.removeClass('search_head_fixer');
resetSearchHeadwh();
}
shf.css({
'-webkit-transform': 'translate3d(0,' + temp + 'rem,0)',
'transition': 'transform 0.5s ease'
});
}, 100);
}
/*绑定页面滚动事件*/
$(window).on("scroll", doScroll());</code></pre>
<p><code>`</code></p>
【React系列】如何构建React应用程序
https://segmentfault.com/a/1190000011190715
2017-09-15T17:40:32+08:00
2017-09-15T17:40:32+08:00
前端荣耀
https://segmentfault.com/u/lengxing
6
<blockquote><p>前面几篇内容简单整理了一些React的基础知识点,以方便大家能够简单的了解React的一些相关概念。在本节中,我们来试着以一个简单的例子来分析,如何构建一个React应用程序,该如何去思考。</p></blockquote>
<p>首先,我们来选择比较常见的一个示例程序:TodoList来作为本次的分析案例。</p>
<p><img src="/img/bVU6Se?w=360&h=112" alt="图片描述" title="图片描述"></p>
<p>上面是一个简单的TodoList的的样子,此处并没有进行样式修饰,大家可以后面自己完善。我们将按照以下几个步骤进行分析设计:</p>
<h2>步骤1:将 UI 拆解到组件层次结构中</h2>
<p>当我们拿到一个UI设计时,需要我们将之进行拆解,使之成为由每个组件(和子组件)的结构构成的一个整体,并且可以根据功能给各个部分进行命名。</p>
<p>但是你该如何拆分组件呢?其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是<strong>单一职责原则</strong>,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。</p>
<p>React最大的卖点是轻量组件化。我们分析一下以上截图中的页面,如果要分组件的话,我们大约可以分成一个总组件和两个子组件。一个输入内容的组件,一个显示内容列表(带删除功能)的组件,外面再用一个总组件将两个子组件包括起来。</p>
<p><img src="/img/bVU6WI?w=577&h=117" alt="图片描述" title="图片描述"></p>
<p>这样,我们的应用程序的结构就清晰了:</p>
<ul><li>
<p>todoList --- 整个应用程序用例</p>
<ul>
<li>typeNew --- 接收用户的输入</li>
<li>listTodo --- 显示所有的list item</li>
</ul>
</li></ul>
<h2>步骤2: 用 React 构建一个静态版本</h2>
<p>通过上面的结构划分,我们代码的整体结构大致如下:</p>
<pre><code>import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todoList: []
}
}
render() {
return (
<div>
<TypeNew />
<ListTodo />
</div>
);
}
};
// TypeNew 组件用于新增数据,
class TypeNew extends Component {
render() {
return (
<form>
<input type="text" placeholder="typing a newthing todo" autoComplete="off" />
</form>
);
}
};
// ListTodo 组件用于展示列表,并可以删除某一项内容,
class ListTodo extends Component {
render() {
return (
<ul id="todo-list">
{/* 其中显示数据列表 */}
</ul>
);
}
};
// 将 TodoList 组件渲染到页面
ReactDOM.render(<TodoList />, document.getElementById('root'));</code></pre>
<p>目前为止你已经有了组件层次结构,现在是时候实现你的 app 了。最简单的方法是构建一个采用数据模型并渲染 UI 但没有交互性的版本。最好解耦这些处理,因为构建静态版本需要 大量的代码 和 少量的思考,而添加交互需要 大量思考 和 少量的代码。我们将看到原因。</p>
<p>要构建你 app 的一个静态版本,用于渲染数据模型, 您将需要构建复用其他组件并使用 props 传递数据的组件。props 是将数据从 父级组件 传递到 子级 的一种方式。如果你熟悉 state 的概念,在构建静态版本时 <em>不要使用 </em>state ** 。state 只用于交互,也就是说,数据可以随时被改变。由于这是一个静态版本 app,所以你并不需要使用 state 。</p>
<p>您可以 自上而下 或 自下而上 构建。也就是说,您可以从构建层次结构中顶端的组件开始(即从 FilterableProductTable 开始),也可以从构建层次结构中底层的组件开始(即 ProductRow )。在更简单的例子中,通常 自上而下 更容易,而在较大的项目中,自下而上,更有利于编写测试。</p>
<p>在这一步结束时,你已经有了一个可重用的组件库,用于渲染你的数据模型。组件将只有 render() 方法,因为这是你应用程序的静态版本。层次结构顶部的组件( FilterableProductTable )应该接收你的数据模型作为 prop 。如果您对基础数据模型进行更改,并再次调用 ReactDOM.render(),UI 将同步更新。这有利于观测UI的更新以及相关的数据变化,因为这中间没有做什么复杂的事情。React 的 单向数据流(也称为 单向绑定 )使所有模块化和高性能。</p>
<blockquote><p>♥ 小插曲 区分: Props(属性) vs State(状态)</p></blockquote>
<h2>步骤3: 确定 UI state(状态) 的最小(但完整)表示</h2>
<p>为了你的 UI 可以交互,你需要能够触发更改底层的数据模型。React 通过 state 使其变得容易。</p>
<p>要正确的构建应用程序,你首先需要考虑你的应用程序需要的可变 state(状态) 的最小集合。这里的关键是:不要重复你自己 (DRY,don't repeat yourself)。找出你的应用程序所需 state(状态) 的绝对最小表示,并且可以以此计算出你所需的所有其他数据内容。正如我们在构建一个 TODO 列表,只保留一个 TODO 元素数组即可;不需要为元素数量保留一个单独的 state(状态) 变量。相反,当你要渲染 TODO 计数时,只需要获取 TODO 数组的长度即可。</p>
<p>在我们的例子中,主要出现的数据有两个,一个是todo列表,一个是用户输入的新的todo项目,todo列表会根据用户的添加和删除发生变化,可以认为是属于state的,对于用户输入的todo项目,我们发现它只是一个中间的临时值,并不需要设置相应的变量进行存储,所以我们的最终的State是:</p>
<ul><li>todo列表</li></ul>
<h2>步骤4:确定 state(状态) 的位置</h2>
<p>既然是展示数据,首先要考虑数据存储在哪里,来自于哪里。现在这里放一句话——React提倡所有的数据都是由父组件来管理,通过props的形式传递给子组件来处理——先记住,接下来再解释这句话。</p>
<p>上面提到,做一个todolist页面需要一个父组件,两个子组件。父组件当然就是todolist的『总指挥』,两个子组件分别用来add和show、delete。用通俗的方式讲来,父组件就是领导,两个子组件就是协助领导开展工作的,一切的资源和调动资源的权利,都在领导层级,子组件配合领导工作,需要资源或者调动资源,只能申请领导的批准。</p>
<p>这么说来就明白了吧。数据完全由父组件来管理和控制,子组件用来显示、操作数据,得经过父组件的批准,即——父组件通过props的形式将数据传递给子组件,子组件拿到父组件传递过来的数据,再进行展示。</p>
<p>另外,根据React开发的规范,组件内部的数据由state控制,外部对内部传递数据时使用 props 。这么看来,针对父组件来说,要存储todolist的数据,那就是内部信息(本身就是自己可控的资源,而不是『领导』控制的资源),用state来存储即可。而父组件要将todolist数据传递给子组件,对子组件来说,那就是传递进来的外部信息(是『领导』的资源,交付给你来处理),需要使用props。</p>
<p>此时我们的代码可以修改为:</p>
<pre><code>// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {
todoList: []
}
}
render() {
return (
<div>
<TypeNew />
<ListTodo />
</div>
);
};
};</code></pre>
<p>现在,已经确定了应用所需 state(状态) 的最小集合。接下来,需要确定是哪个组件可变,或者说哪个组件拥有这些 state(状态) 。</p>
<p>记住:React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:</p>
<p>对于你应用中的每一个 state(状态) :</p>
<ul>
<li>确定每个基于这个 state(状态) 渲染的组件。</li>
<li>找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。愚人码头注:父级组件)。</li>
<li>公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。</li>
<li>如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。</li>
</ul>
<h2>步骤5:添加反向数据流</h2>
<p>目前,构建的应用已经具备了正确渲染 props(属性) 和 state(状态) 沿着层次结构向下传播的功能。现在是时候实现另一种数据流方式:层次结构中深层的 form(表单) 组件需要更新 TodoList中的 state(状态) 。</p>
<p>想想我们希望发生什么。我们期望当用户改变表单输入进行提交的时候,我们更新 state(状态) 来反映用户的输入。由于组件只能更新它们自己的 state(状态) ,TodoList将传递回调到 TypeNew,然后在 state(状态) 被更新的时候触发。我们可以使用表单的onSubmit事件来接收通知。而且通过 TodoList传递的回调调用 setState(),然后应用被更新。同理删除处理在ListTodo中触发按钮的点击onClick事件来调用TodoList传递的回掉函数来更新删除后的state。</p>
<p>最终我们的代码结果如下:</p>
<pre><code>import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来
class TodoList extends Component {
constructor(props) {
super(props);
this.state = {
todoList: []
}
this.handleAddNewItem = this.handleAddNewItem.bind(this);
this.handleDelItem = this.handleDelItem.bind(this);
}
handleAddNewItem(todo) {
var todoList = this.state.todoList;
if(todo === "") {
return;
}
todoList.push(todo);
this.setState({
todoList: todoList
})
}
handleDelItem(index) {
var todoList = this.state.todoList;
todoList.splice(index, 1);
this.setState({
todoList: todoList
})
}
render() {
return (
<div>
<TypeNew typeNewItem={this.handleAddNewItem} />
<ListTodo todoList={this.state.todoList} onDelItem={this.handleDelItem} />
</div>
);
}
};
// TypeNew 组件用于新增数据,
class TypeNew extends Component {
constructor(props){
super(props);
this.onHandleAddNewItem = this.onHandleAddNewItem.bind(this);
}
onHandleAddNewItem(e) {
e.preventDefault();
var inputDom = this.textInput;
var newthing = inputDom.value.trim();
this.props.typeNewItem(newthing);
inputDom.value = '';
}
render() {
return (
<form onSubmit={this.onHandleAddNewItem}>
<input type="text" ref={(input) => { this.textInput = input; }} placeholder="typing a newthing todo" autoComplete="off" />
</form>
);
}
};
// ListTodo 组件用于展示列表,并可以删除某一项内容,
class ListTodo extends Component {
constructor(props){
super(props);
this.onHandleDelItem = this.onHandleDelItem.bind(this);
}
onHandleDelItem(e){
this.props.onDelItem(e.target.getAttribute("data-key"));
}
render() {
return (
<ul id="todo-list">
{
// this.props.todo 获取父组件传递过来的数据
// {/* 遍历数据 */}
this.props.todoList.map(function (item, i) {
return (
<li>
<label>{item}</label>
<button onClick={this.onHandleDelItem} data-key={i}>delete</button>
</li>
);
}.bind(this))
}
</ul>
);
}
};
// 将 TodoList 组件渲染到页面
ReactDOM.render(<TodoList />, document.getElementById('root'));</code></pre>
<p>上面就是我们一步步进行分析拆分来设计我们的应用程序的过程。由于例子比较简单,所以相对来说思路更加清晰,当我们面对大型的应用程序时,往往分析方式会有所变化,但是根本的原理是不变的。</p>
<hr>
<p><strong>参考资料</strong>:</p>
<p><a href="https://link.segmentfault.com/?enc=bboXZ%2B4EpDZtMjn%2F5aCJLw%3D%3D.16ZVA%2BNUOeiLMB2nsM8h5oZ9pB%2Bydu605%2FrupSEe4x1zasU3%2B854gKTXA3j2By2YEy6uilfuEOqedBYFUcJlQQ%3D%3D" rel="nofollow">React 的编程思想</a><br><a href="https://link.segmentfault.com/?enc=MvuhKdd8QPqoDpn7gMYR9A%3D%3D.WnsfTdT9aE63FvWhaRrCsD5ZooxdInbBO53lc%2FbJCnaKizR2vVzlWcUnXHYKZpDbj7tJUyvwyYiQqiRAVBNV%2Bw%3D%3D" rel="nofollow">使用React并做一个简单的to-do-list</a></p>
【React系列】受控组件(Controlled Components)和不受控组件
https://segmentfault.com/a/1190000011004617
2017-09-04T16:58:00+08:00
2017-09-04T16:58:00+08:00
前端荣耀
https://segmentfault.com/u/lengxing
7
<h2>受控组件</h2>
<h3>表单</h3>
<p>HTML 表单用于搜集不同类型的用户输入。</p>
<h4><form> 元素</h4>
<p><form> 元素定义 HTML 表单:</p>
<pre><code><form>
.
form elements
.
</form></code></pre>
<h4>表单元素</h4>
<p>表单元素指的是不同类型的 input 元素、复选框、单选按钮、提交按钮等等。<br>当用户提交表单时浏览器会打开一个新页面,如果你希望 React 中保持这个行为,也可以工作。但是多数情况下,用一个处理表单提交并访问用户输入到表单中的数据的 JavaScript 函数也很方便。实现这一点的标准方法是使用一种称为“受控组件(controlled components)”的技术。</p>
<h3>受控组件(Controlled Components)</h3>
<p>在 HTML 中,表单元素如 <input>,<textarea> 和 <select> 表单元素通常保持自己的状态,并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新。</p>
<p>我们可以通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。</p>
<p>一个常规的受控组件的形式例子:</p>
<pre><code>class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
/*① 设置表单元素的value属性之后,其显示值将由this.state.value决定,以满足React状态的同一数据理念。*/
/*② 每次键盘敲击之后会执行handleChange方法以更新React状态,显示值也将随着用户的输入改变。*/
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}</code></pre>
<p>由于 value 属性设置在我们的表单元素上,显示的值总是 this.state.value,以满足 state 状态的同一数据理念。由于 handleChange 在每次敲击键盘时运行,以更新 React state(状态),显示的值将更新为用户的输入。</p>
<p>对于受控组件来说,每一次 state(状态) 变化都会伴有相关联的处理函数。这使得可以直接修改或验证用户的输入。比如,如果我们希望强制 name 的输入都是大写字母,可以这样来写 handleChange 方法:</p>
<pre><code>handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}</code></pre>
<h4>受控表单的表单元素</h4>
<p>上面例子中是以input为例来写的,其实其他的表单元素基本形式是差不多的。</p>
<h5>textare</h5>
<pre><code><textarea value={this.state.value} onChange={this.handleChange} /></code></pre>
<h5>select</h5>
<pre><code><select value={this.state.value} onChange={this.handleChange}>
……
</select></code></pre>
<h4>处理多个输入元素</h4>
<p>当您需要处理多个受控的 表单元素时,您可以为每个元素添加一个 name 属性,并且让处理函数根据 event.target.name 的值来选择要做什么。</p>
<p>由于 setState() 自动将部分状态合并到当前状态,所以我们只需要调用更改的部分即可。</p>
<h3>受控组件的替代方案</h3>
<p>有时使用受控组件有些乏味,因为你需要为每一个可更改的数据提供事件处理器,并通过 React 组件管理所有输入状态。当你将已经存在的代码转换为 React 时,或将 React 应用程序与非 React 库集成时,这可能变得特别烦人。在这些情况下,您可能需要使用不受控的组件,用于实现输入表单的替代技术。</p>
<h2>不受控组件</h2>
<p>在大多数情况下,我们推荐使用受控组件来实现表单。在受控组件中,表单数据由 React 组件负责处理。另外一个选择是不受控组件,其表单数据由 DOM 元素本身处理。</p>
<p>要编写一个未控制组件,你可以使用一个 ref 来从 DOM 获得 表单值,而不是为每个状态更新编写一个事件处理程序。</p>
<p>例如,在不受控组件中,以下代码接受一个单独的名字 :</p>
<pre><code>class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}</code></pre>
<h2>受控组件与不受控组件的选取</h2>
<p>因为不受控组件的数据来源是 DOM 元素,当使用不受控组件时很容易实现 React 代码与非 React 代码的集成。如果你希望的是快速开发、不要求代码质量,不受控组件可以一定程度上减少代码量。否则。你应该使用受控组件。</p>
【React系列】列表(Lists)和键(Keys)
https://segmentfault.com/a/1190000010942767
2017-08-31T11:15:02+08:00
2017-08-31T11:15:02+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>本篇我们来认识一下react中的列表(Lists)和键(Keys)。首先让我们回顾一下在 JavaScript 中如何转换列表。</blockquote>
<p>我们知道,在JavaScript中map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。map() 方法按照原始数组元素顺序依次处理元素。如:</p>
<pre><code>/*返回一个数组,数组中元素为原始数组的平方根*/
var numbers = [4, 9, 16, 25];
function myFunction() {
x = document.getElementById("demo")
x.innerHTML = numbers.map(Math.sqrt);
}</code></pre>
<p>在 React 中,转换数组为 元素列表 的方式,和上述方法基本相同。</p>
<h2>列表(Lists)</h2>
<h3>多组件渲染</h3>
<p>可以创建元素集合,并用一对大括号 {} 在 JSX 中直接将其引用即可。</p>
<p>下面,我们用 JavaScript 的 map() 函数将 numbers 数组循环处理。对于每一项,我们返回一个 <li> 元素。最终,我们将结果元素数组分配给 listItems:</p>
<pre><code>const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);</code></pre>
<p>把整个 listItems 数组包含到一个 <ul> 元素,并渲染到 DOM:</p>
<pre><code>ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);</code></pre>
<p>这样就可以创建出一个列表元素了,包含5个li元素的列表。</p>
<h3>基本列表组件</h3>
<p>上面的例子只是简单的元素列表的创建过程,而在React中,往往我们使用的是组件列表的创建,我们可以简单改造上面的例子。</p>
<pre><code>function ListItem(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<ListItem numbers={numbers} />,
document.getElementById('root')
);</code></pre>
<p>这样就转化成了基本的React的组件形式了。</p>
<blockquote>❤当运行上述代码的时候,将会收到一个警告:<img src="/img/bVT4IZ?w=560&h=77" alt="图片描述" title="图片描述">这个警告在非 min 版本的就可以看到。当创建元素列表时,“key” 是一个你需要包含的特殊字符串属性,下面会有说明。</blockquote>
<h2>键(Keys)</h2>
<p>上面的例子中出现的警告告诉我们数组中的每一个元素都需要一个“key”(键)来进行标识。键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。有点类似于元素的唯一标识,相信会比较好理解。</p>
<p>挑选 key 最好的方式是使用一个在它的<strong>同辈元素</strong>中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys:</p>
<pre><code>const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);</code></pre>
<p>当要渲染的列表项中没有稳定的 IDs 时,你可以使用数据项的索引值作为 key 的最后选择:</p>
<pre><code>const todoItems = todos.map((todo, index) =>
// 只是当列表元素没有稳定的ids时使用索引index
<li key={index}>
{todo.text}
</li>
);</code></pre>
<blockquote>❤如果列表项可能被重新排序时,我们不建议使用索引作为 keys,因为这导致一定的性能问题,会很慢。</blockquote>
<h3>使用 keys 提取组件</h3>
<p>keys 只在数组的上下文中存在意义。例如,如果你提取 一个 ListItem 组件,应该把 key 放置在数组处理的 <ListItem /> 元素中,不能放在 ListItem 组件自身中的 <li> 根元素上。</p>
<p><strong>例子:错误的 key 用法</strong></p>
<pre><code>function ListItem(props) {
const value = props.value;
return (
// 错误!不需要在这里指定 key:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!key 应该在这里指定:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);</code></pre>
<p><strong>例子:正确的 key 用法</strong></p>
<pre><code>function ListItem(props) {
// 正确!这里不需要指定 key :
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在这里被指定
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);</code></pre>
<blockquote>❤一个好的经验准则是元素中调用 map() 需要 keys 。因为列表都会通过map()方法进行实现。</blockquote>
<h3>keys 在同辈元素中必须是唯一的</h3>
<p>在数组中使用的 keys 必须在它们的<strong>同辈之间唯一</strong>。然而它们并不需要全局唯一。我们可以在操作两个不同数组的时候使用相同的 keys。也就是说,在一个列表中选取keys时,这个属性需要在数组中的元素中是唯一的,不会重复,具有<strong>唯一性</strong>。这也和我们常规见到的key(键值、主键)的概念是一致的。</p>
<h3>keys的内部性</h3>
<p>键是React的一个内部映射,但其不会传递给组件的内部。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。</p>
<pre><code>const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);</code></pre>
<p>如上面的例子,在组件Post中可以通过props.id获取值,但是无法获取props.key的值。</p>
<h2>在 JSX 中嵌入 map()</h2>
<p>JSX允许在大括号中嵌入任何表达式,因此可以 内联 map():</p>
<pre><code>function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}</code></pre>
<p>有时这可以产生清晰的代码,但是这个风格也可能被滥用。就像在 JavaScript 中,是否有必要提取一个变量以提高程序的可读性,这取决于你。但是记住,如果 map() 体中有太多嵌套,可能是提取组件的好时机。总之要在适当的时机使用组件,这样就能使你的结构更加的清晰。</p>
【React系列】Props 验证
https://segmentfault.com/a/1190000010921839
2017-08-30T10:51:02+08:00
2017-08-30T10:51:02+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<blockquote>Props 验证使用 propTypes,它可以保证我们的应用组件被正确使用,React.PropTypes 提供很多验证器 (validator) 来验证传入数据是否有效。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。</blockquote>
<p>常见的验证器:</p>
<pre><code> React.createClass({
propTypes: {
// 可以声明 prop 为指定的 JS 基本数据类型,默认情况,这些数据是可选的
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// 可以被渲染的对象 numbers, strings, elements 或 array
optionalNode: React.PropTypes.node,
// React 元素
optionalElement: React.PropTypes.element,
// 用 JS 的 instanceof 操作符声明 prop 为类的实例。
optionalMessage: React.PropTypes.instanceOf(Message),
// 用 enum 来限制 prop 只接受指定的值。
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// 可以是多个对象类型中的一个
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// 指定类型组成的数组
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// 指定类型的属性构成的对象
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// 特定 shape 参数的对象
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// 任意类型加上 `isRequired` 来使 prop 不可空。
requiredFunc: React.PropTypes.func.isRequired,
// 不可空的任意类型
requiredAny: React.PropTypes.any.isRequired,
// 自定义验证器。如果验证失败需要返回一个 Error 对象。不要直接使用 `console.warn` 或抛异常,
// 因为这样 `oneOfType` 会失效。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error('Validation failed!');
}
}
}
});</code></pre>
<blockquote>注意: React.PropTypes 自 React v15.5 起已弃用。请使用 prop-types 库代替。</blockquote>
<pre><code>import PropTypes from 'prop-types';
MyComponent.propTypes = {
// 你可以将属性声明为以下 JS 原生类型
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,
// 任何可被渲染的元素(包括数字、字符串、子元素或数组)。
optionalNode: PropTypes.node,
// 一个 React 元素
optionalElement: PropTypes.element,
// 你也可以声明属性为某个类的实例,这里使用 JS 的
// instanceof 操作符实现。
optionalMessage: PropTypes.instanceOf(Message),
// 你也可以限制你的属性值是某个特定值之一
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 限制它为列举类型之一的对象
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 一个指定元素类型的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
// 一个指定类型的对象
optionalObjectOf: PropTypes.objectOf(PropTypes.number),
// 一个指定属性及其类型的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),
// 你也可以在任何 PropTypes 属性后面加上 `isRequired`
// 后缀,这样如果这个属性父组件没有提供时,会打印警告信息
requiredFunc: PropTypes.func.isRequired,
// 任意类型的数据
requiredAny: PropTypes.any.isRequired,
// 你也可以指定一个自定义验证器。它应该在验证失败时返回
// 一个 Error 对象而不是 `console.warn` 或抛出异常。
// 不过在 `oneOfType` 中它不起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},
// 不过你可以提供一个自定义的 `arrayOf` 或 `objectOf`
// 验证器,它应该在验证失败时返回一个 Error 对象。 它被用
// 于验证数组或对象的每个值。验证器前两个参数的第一个是数组
// 或对象本身,第二个是它们对应的键。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};</code></pre>
【React系列】状态(State)和生命周期
https://segmentfault.com/a/1190000010851100
2017-08-25T14:14:57+08:00
2017-08-25T14:14:57+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>在上一篇中写过,组件可以分为函数式组件和类组件,并且更新组件的方法也给出了通过传入ReactDOM.render()方法进行更新。但是这种方式并不能很好地进行封装成独立功能的组件,一些操作会由外部进行控制。而我们理想中的组件应该是一个功能独立的个体,只是不同场合不同的数据才会出现不同。<br>而这就就关联到了我们这次的主题---状态(State)</blockquote>
<h2>状态(State)</h2>
<h3>什么是状态</h3>
<p>状态(state) 和 属性(props) 类似,都是一个组件所需要的一些数据集合,但是它是私有的,并且由组件本身完全控制,可以认为它是组件的“<strong>私有属性(或者是局部属性)</strong>”。</p>
<h3>函数式组件转化为类组件</h3>
<p>如果想要使用状态(State)的话,则需要我们在构建组件的时候是要以类组件为形式的。<br>针对直接以类组件形式构造的组件不需要变化,那么针对函数式组件该如何转化呢?</p>
<ul>
<li>创建一个继承自 React.Component 类的 ES6 class 同名类。</li>
<li>添加一个名为 render() 的空方法。</li>
<li>把原函数中的所有内容移至 render() 中。</li>
<li>在 render() 方法中使用 this.props 替代 props。</li>
<li>删除保留的空函数声明。</li>
</ul>
<p>例如:</p>
<pre><code>class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}</code></pre>
<p>上面示例就是一个简单的类组件Clock。</p>
<h3>类组件中添加State</h3>
<p>1) 替换 render() 方法中的 this.props.date 为 this.state.date:</p>
<pre><code>class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}</code></pre>
<p>2) 添加一个 类构造函数(class constructor) 初始化 this.state:</p>
<pre><code>class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}</code></pre>
<blockquote>❤ 注意我们如何将 props 传递给基础构造函数:</blockquote>
<pre><code>constructor(props) {
super(props); ***
this.state = {date: new Date()};
}</code></pre>
<p>3) 移除 <Clock /> 元素中的 date 属性:</p>
<pre><code>ReactDOM.render(
<Clock />,
document.getElementById('root')
);</code></pre>
<p>通过上面的步骤就可以实现添加State到类组件了,最终结果:</p>
<pre><code>class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);</code></pre>
<h3>如何修改State</h3>
<p>State的初始化和常规的赋值形式是一样的,但是如果我们需要更新相关数据的话,则需要通过setState方法进行修改,如:</p>
<pre><code>this.setState({
date: new Date()
});</code></pre>
<h3>正确地使用 State(状态)</h3>
<p>关于如何正确使用State以及setState() 有三个方面需要注意:</p>
<h4>不要直接修改 state(状态)</h4>
<p>正如上面提到的如何修改State那样,在初始化后,如果修改的话只能通过setState方法进行修改。</p>
<h4>state(状态) 更新可能是异步的</h4>
<p>React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。</p>
<p>因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。<br>如:</p>
<pre><code>// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});</code></pre>
<p>要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:</p>
<pre><code>// 正确
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));</code></pre>
<h4>state(状态)更新会被合并</h4>
<p>当你调用 setState(), React 将合并你提供的对象到当前的状态中。所以当State是一个多键值的结构时,可以单独更新其中的一个,此时会进行“差分”更新,不会影响其他的属性值。</p>
<h2>生命周期方法</h2>
<h3>什么是生命周期</h3>
<blockquote>生命周期就是指一个对象的生老病死。</blockquote>
<p>而组件的生命周期则是这个组件从创建到销毁的一个过程。在这个过程中会有不同的阶段,从而会产生一些对应的生命周期函数来供我们使用,以便能够进行一些渲染、更新等处理。</p>
<p>可以简单分为几个阶段:</p>
<ul>
<li>
<p>初始化阶段</p>
<ul>
<li>getDefaultProps:获取实例的默认属性(即使没有生成实例,组件的第一个实例被初始化CreateClass的时候调用,只调用一次,)</li>
<li>getInitialState:获取每个实例的初始化状态(每个实例自己维护)</li>
<li>componentWillMount:组件即将被装载、渲染到页面上(render之前最好一次修改状态的机会)</li>
<li>render:组件在这里生成虚拟的DOM节点(只能访问this.props和this.state;只有一个顶层组件,也就是说render返回值值职能是一个组件;不允许修改状态和DOM输出)</li>
<li>componentDidMount:组件真正在被装载之后,可以修改DOM</li>
</ul>
</li>
<li>
<p>运行中状态</p>
<ul>
<li>componentWillReceiveProps:组件将要接收到属性的时候调用(赶在父组件修改真正发生之前,可以修改属性和状态)</li>
<li>shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止render调用,后面的函数不会被继续执行了)</li>
<li>componentWillUpdate:不能修改属性和状态</li>
<li>componentDidUpdate:可以修改DOM</li>
</ul>
</li>
<li>
<p>销毁阶段</p>
<ul><li>componentWillUnmount:开发者需要来销毁(组件真正删除之前调用,比如计时器和事件监听器)</li></ul>
</li>
</ul>
<h2>数据向下流动</h2>
<p>无论作为父组件还是子组件,它都无法获悉一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。</p>
<p>这就是 state(状态) 经常被称为 本地状态 或 封装状态的原因。 它不能被拥有并设置它的组件 以外的任何组件访问。</p>
<p>一个组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):</p>
<pre><code><h2>It is {this.state.date.toLocaleTimeString()}.</h2></code></pre>
<p>这通常称为一个“从上到下”,或者“单向”的数据流。任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件。</p>
<p>如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。</p>
<h2>结语</h2>
<p>在 React 应用中,一个组件是否是有状态或者无状态的,被认为是组件的一个实现细节,随着时间推移可能发生改变。你可以在有状态的组件中使用无状态组件,反之亦然。</p>
【React系列】元素、组件与属性
https://segmentfault.com/a/1190000010833420
2017-08-24T16:04:10+08:00
2017-08-24T16:04:10+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<h3>元素(Elements)</h3>
<p>元素(Elements)是React应用中最小的构建部件,用于描述能够在屏幕上看到的内容,如:</p>
<pre><code>
const element = <h1> Hello World ! </h1>
</code></pre>
<p>元素是构成组件的“材料”。</p>
<h4>渲染元素到DOM</h4>
<p>要渲染一个React元素到DOM上面需要有一个根节点,之后把他们传递给ReactDOM.render()方法。</p>
<pre><code><div id="root"></div>
const element = <h1>Hello World!</h1>;
ReactDOM.render(
element,
document.getElementById("root")
);</code></pre>
<h4>更新已经渲染的元素</h4>
<p>React元素是不可突变(immutable)的。一旦创建了一个元素,就不能更改其子元素或者属性。只能创建一个新的元素,通过传入ReactDOM.render()方法进行更新。<br> 另外,React DOM在更新的时候,会将元素及其子元素与之前版本逐一逐级对比,只更新对应有变化的,并不会整体进行刷新。</p>
<h3>组件(Components)</h3>
<p>组件是整个UI实现中划分出来的一个个独立可复用的小部件,每个部件间互不干涉,可以单独设计。<br> 组件可以通过接收属性(Props)输入从而返回对应的React元素,用以描述显示的内容。</p>
<h4>组件的分类</h4>
<ul>
<li>
<p>函数式组件</p>
<pre><code>function Welcome(props) {
return <h1> Hi, {props.name} ! </h1>;
}</code></pre>
</li>
<li>
<p>类组件</p>
<pre><code>class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}</code></pre>
</li>
</ul>
<h4>渲染组件</h4>
<p>以函数式组件为例:</p>
<pre><code>function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);</code></pre>
<p>当 React 遇到一个代表用户定义组件的元素时,它将 JSX 属性以一个单独对象的形式传递给相应的组件。 我们将其称为 "props" 对象。简单顺序如下:</p>
<ul>
<li>我们调用了 ReactDOM.render() 方法并向其中传入了 <Welcome name="Sara" /> 元素。</li>
<li>React 调用 Welcome 组件,并向其中传入了 {name: 'Sara'} 作为 props 对象。</li>
<li>Welcome 组件返回 <h1>Hello, Sara</h1>。</li>
<li>React DOM 迅速更新 DOM ,使其显示为 <h1>Hello, Sara</h1>。</li>
</ul>
<blockquote>❤ 组件名称总是以大写字母开始。</blockquote>
<p>另外,组件间可以进行引用嵌套。组件可以在它们的输出中引用其它组件。</p>
<blockquote>❤ 组件必须返回一个单独的根元素。这就是为什么我们添加一个 <div> 来包含所有 <Welcome /> 元素的原因。</blockquote>
<h3>属性(Props)</h3>
<h4>Props是只读的</h4>
<blockquote>❤ 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。</blockquote>
投屏类H5应用开发分析
https://segmentfault.com/a/1190000009263747
2017-05-02T16:33:29+08:00
2017-05-02T16:33:29+08:00
前端荣耀
https://segmentfault.com/u/lengxing
2
<blockquote>最近公司设计了一款基于自己产品的投屏类H5应用,能够使得用户通过我们的产品平台来设计启用用于投屏功能的H5页面,实现留言互动等功能。整体的实现和用户体验都是基于前端实现来完成的,特此再次整理一下自己的设计实现思路以供大家分享。</blockquote>
<h3>使用场景</h3>
<p>投屏类应用操作简便,用户仅需要填写简单信息便可实现上墙服务,进行活动现场创意互动,点燃现场情绪。特别是在几乎人人都拥有一部智能手机的今天,投屏类应用在峰会、活动、婚礼现场等场所更是大放异彩——简单“扫一扫”屏幕上二维码,通过投影将自己的观点与建议上墙,参与讨论,分享观点,大幅度提升会场互动效果与亮点呈现。</p>
<h3>主要问题以及解决方法</h3>
<h4>1.如何使得普通用户能够获取自己的上墙页面</h4>
<blockquote>作为用户,不需要复杂的设计和编码能力,只需要几步设置就可以搭建起来自己的投屏应用,这样才能够吸引用户的使用,降低用户的使用成本和学习成本。</blockquote>
<p>我们的主要实现方法是使用户通过我们的产品平台进行选择性的设置将要用来投屏的页面或者模块,只要几步简单的背景、动画的选择即可获取到一个投屏页面地址,从而能够进行投屏展示。</p>
<h4>2.如何能够及时的展现互动信息</h4>
<blockquote>每一个参与投屏应用的人都希望自己的信息能够及时的展示在投屏上面,从而能够带动现场活动的热情和大家的互动氛围。</blockquote>
<p>常规的实现方案有以下两个:</p>
<h5>一、定时轮询</h5>
<p>客户端通过设置一定的轮询时间来定时的向服务器请求问询是否有新的消息,从而将新内容更新到投屏上面。<br>这样就会带来一个问题:轮询时间的设定?<br>轮询时间较长的话,就会导致一定时间内的消息堆积,以及互动的及时性;<br>轮询时间较短的话,又会增加请求次数,从而导致影响性能,所以这个方案是弊端较大的。</p>
<h5>二、WebSocket通信</h5>
<blockquote>WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——可以通俗的解释为服务器主动发送信息给客户端。</blockquote>
<p>现在多数投屏互动的实现方式主要是依靠浏览器的WebSocket即时通信技术,包括国外许多案例,在以前传统的网站为了实现这种技术基本都是前面提到的轮询。</p>
<p>在 WebSocket API中,浏览器和服务器只需要要做一个握手的动作,然后浏览器和服务器之间就形成了一条快速通道,两者之间就可以直接实时的互相传送数据。 采用websocket技术的页面不同于普通页面,而是需要特殊的服务器环境支持。 </p>
<p>这样就能够实现消息互动的及时性,由服务器接收到新的互动消息之后直接通过websocket通知到客户端,客户端只要在接收到消息的时候进行及时的展示即可。</p>
<h4>3.互动消息的处理</h4>
<blockquote>通过上面的分析可以知道,通过websocket的连接建立可以实现消息互动的及时性展示。但是整体的消息展示的处理该如何来做呢?新老消息数据的处理又该如何来融合呢?</blockquote>
<p>我的处理是这样进行的:</p>
<ol>
<li>进入页面后获取到当前时间的互动消息的总数据,存储为baseArray作为总的消息队列(约定按照创建时间的新旧顺序排列);</li>
<li>拷贝上面的baseArray到runArray作为展示的执行队列;</li>
<li>根据展示情况,每次从runArray队列的头部选取一定数量的消息数据进行一屏展示(设定一屏能够展示n条数据)</li>
<li>如果没有新消息的时候,则按照展示时间间隔重复上面的3来获取数据进行展示;当runArray队列中不足以展示时,则再次拷贝baseArray接续到runArray后面;</li>
<li>当有新消息item来到时(约定每次通知一条数据),此时为了确保新消息展示的及时性,此时将item分别放到两个队列的头部,baseArray主要用于保证总消息的顺序性;runArray主要用来确保下次切屏展示时新消息的及时性。</li>
</ol>
<p>按照上面的逻辑即可实现互动过程中消息的展示以及确保消息展示的及时性。</p>
<h3>总结</h3>
<p>上面就是针对这次产品开发进行的一个经验总结,文笔有限,希望能够阐明了自己的一些观点吧:)</p>
<hr>
<h3>拓展延伸</h3>
<p>websocket技术基本可以实现所有及时互动类场景的交互开发,一般的多屏互动有四种互动模式:</p>
<ul>
<li>单人模式:适合线上PC互联网广告和线下长期固定场所。 </li>
<li>双人模式:适合线上PC互联网广告、双屏移动互联网广告和线下营销。</li>
<li>多人模式:适合线下酒吧、咖啡厅、KTV等场所。 </li>
<li>不限人数模式:适合线下大型商场、机场、活动、庆典、年会等。</li>
</ul>
<h2>PS:大家可以微信添加订阅号“<strong>冷星学前端</strong>”,同步更新文章内容</h2>
input[file]标签的accept=”image/*”属性响应很慢的解决办法
https://segmentfault.com/a/1190000009185962
2017-04-25T10:39:06+08:00
2017-04-25T10:39:06+08:00
前端荣耀
https://segmentfault.com/u/lengxing
0
<blockquote>input[file]标签的accept属性可用于指定上传文件的 MIME类型 。</blockquote>
<p>例如,想要实现默认上传图片文件的代码,代码可如下:</p>
<pre><code><input type="file" name="file" class="element" accept="image/*"></code></pre>
<p>效果就是会默认过滤掉所有非图片文件。</p>
<p><strong>但是!</strong></p>
<p>这段代码在Chrome和Safari等Webkit浏览器下却出现了响应滞慢的问题,可能要等 6~10s 才能弹出文件选择对话框。简直不能忍呀。</p>
<p>在IE和Firefox中使用 accept=”image/*” 属性则没有发现响应延迟的问题。</p>
<p>于是几经尝试后,发现是 accept=”image/<em>” 属性的问题,删掉它或者将 </em> 通配符修改为指定的MIME类型,就可以解决Webkit浏览器下的对话框显示滞慢的问题。</p>
<p>解决办法如下:</p>
<pre><code><input type="file" accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"></code></pre>
<p>accept=”image/<em>”属性会对每一个文件都遍历一次所有的”image/</em>”文件类型,当文件较多时,文件的检验时间较长,这可能是Webkit的底层实现的bug。</p>
<p>另外,</p>
<p>accept=”audio/<em>”和 accept=”video/</em>” 属性 在 Webkit浏览器下也会有同样的响应延迟的问题。同理,通过将 * 通配符 修改成指定的MIME类型就可解决。</p>
<h2>PS:大家可以微信添加订阅号“<strong>冷星学前端</strong>”,同步更新文章内容</h2>