随着组件的细化,就会遇到多组件状态共享的情况, Vuex当然可以解决这类问题,不过就像 Vuex官方文档所说的,如果应用不够大,为避免代码繁琐冗余,最好不要使用它,今天我们介绍的是 vue.js 2.6 新增加的 Observable API ,通过使用这个 api 我们可以应对一些简单的跨组件数据状态共享的情况。
首先创建一个 store.js,包含一个 store和一个 mutations,分别用来指向数据和处理方法。1
2
3
4
5
6
7
8
9
10
11
12//store.js
import Vue from 'vue';
export let store =Vue.observable({count:0,name:'李四'});
export let mutations={
setCount(count){
store.count=count;
},
changeName(name){
store.name=name;
}
}
在组件xxx.vue中引用,在组件里使用数据和方法:
1 | <template> |
1 | const arr = [2, 6, 4, 20, -3, 8] |
1 | Array.prototype.max = function() { |
1 | const max02 = arr.sort((a, b) => { |
1 | const max03 = Math.max.apply(null, arr) |
1 | const max04 = Math.max(...arr) |
1 | const max05 = eval('Math.max(' + arr + ')') |
最后这个比较难理解,涉及到隐式类型转换和eval的用法。
首先,进行隐式转换1
2
3
4
5
6
7'Math.max(' + arr + ')''
// 相当于
'Math.max(' + arr.toString() + ')'
// 相当于
'Math.max(' + '2, 6, 4, 20, -3, 8' + ')'
// 相当于
'Math.max(2, 6, 4, 20, -3, 8)'
然后就是eval的用法
eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
语法:
eval(string)
string必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。该方法只接受原始字符串作为参数,如果 string 参数不是原始字符串,那么该方法将不作任何改变地返回。因此请不要为 eval() 函数传递 String 对象来作为参数。
简单来说吧,就是用JavaScript的解析引擎来解析这一堆字符串里面的内容,这么说吧,你可以这么理解,你把eval看成是script标签。
1 | eval('Math.max(2, 6, 4, 20, -3, 8)') |
如有问题,欢迎指正!!!
]]>首先,通过两种方式来声明一个类:
第一种,构造函数的方式
1 | function Animal1(name) { |
第二种,ES6中的class声明
1 | class Animal2 { |
两种方式生成实例的方式是一样的
1 | var animal = new Animal1('dog') |
1 | function Parent1() { |
这种继承的方式只能继承构造函数内部的属性,缺点是不能继承构造函数原型对象上的属性,如下1
2
3Parent1.prototype.say = function() {
console.log('hello')
}
这时候,say方法Child1是继承不到的
1 | function Parent2() { |
这个方法中,child的属性方法先有构造函数Child2内部的属性age,然后我们知道每个对象都有一个_ proto _ 属性,指向构造函数的prototype,这时child._ proto _ === Child2.prototype,然后就继承到Parent2上的属性及方法了
这个方法也有个缺点,如果Parent2中有个属性值是引用类型的,如下1
2
3
4function Parent2() {
this.name = 'parent2'
this.color = ['red', 'green']
}
这时候我们生成两个child实例1
2
3var child1 = new Child2()
var child2 = new Child2()
child1.color.push('blue')
这时候child2.color也会有’blue’,这肯定不是我们想要的结果。这时因为child1._ proto _ 和child2._ proto _都指向Child2.prototype,而Child2.prototype的值是一个实例对象
1 | function Parent3() { |
这种方式可以实现构造函数体内的属性以及原型对象上的属性与方法,但有个缺点,就是我们新生成一个child实例时,Parent3构造函数执行了两次,这样是没必要的,所以继续优化一下,如下:1
2
3
4
5
6
7
8function Parent4() {
this.name = 'parent4'
}
function Child4() {
Parent3.call(this)
this.age = 16
}
Child4.prototype = Parent4.prototype
这种方式也有一个缺点,就是新生成的child实例的constructor会指向Parent4而不是Child4,那继续来优化一下:1
2
3
4
5
6
7
8
9function Parent5() {
this.name = 'parent5'
}
function Child5() {
Parent3.call(this)
this.age = 16
}
Child5.prototype = Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5
至此,继承就完美实现了。
有问题,欢迎指正!
顾名思义,扁平化就是将嵌套的多维数组变成一维数组的过程。今天将通过几种方式来实现数组的扁平化。
先定义这几种方法公用的一个数组
1 | const arr = [1, 2, [3, 4, [5, 6]]] |
初级版也通过两种方式来实现
利用数组的concat方法
1 | function flatten(arr) { |
使用参数默认值的方法
1 | function flatten(arr, res = []) { |
使用…三点运算符结合concat方法
1 | function flatten(arr) { |
Array.flat(n)是ES10扁平数组的api, n表示维度, n值为 Infinity时维度为无限大。
我们的例子中是个三维数组,所以,写法为:1
arr.flat(3)
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
用法也很简单:1
2
3
4
5<keep-alive>
<component>
<!-- 该组件将被缓存! -->
</component>
</keep-alive>
1 | // 组件 a |
1 | <keep-alive include="a"> |
1 | <keep-alive exclude="a"> |
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存。1
2
3
4
5<keep-alive>
<router-view>
<!-- 所有路径匹配到的视图组件都会被缓存! -->
</router-view>
</keep-alive>
如果只想 router-view 里面某个组件被缓存,怎么办?
1 | // 组件 a |
1 | <keep-alive include="a"> |
exclude 例子类似。
缺点:需要知道组件的 name,项目复杂的时候不是很好的选择
1 | // routes 配置 |
1 | <keep-alive> |
假设这里有 3 个路由: A、B、C。
需求:
在 A 路由里面设置 meta 属性:1
2
3
4
5
6
7
8{
path: '/',
name: 'A',
component: A,
meta: {
keepAlive: true // 需要被缓存
}
}
在 B 组件里面设置 beforeRouteLeave:1
2
3
4
5
6
7
8
9
10
11export default {
data() {
return {};
},
methods: {},
beforeRouteLeave(to, from, next) {
// 设置下一个路由的 meta
to.meta.keepAlive = true; // 让 A 缓存,即不刷新
next();
}
};
在 C 组件里面设置 beforeRouteLeave:1
2
3
4
5
6
7
8
9
10
11export default {
data() {
return {};
},
methods: {},
beforeRouteLeave(to, from, next) {
// 设置下一个路由的 meta
to.meta.keepAlive = false; // 让 A 不缓存,即刷新
next();
}
};
这样便能实现 B 回到 A,A 不刷新;而 C 回到 A 则刷新。
当引入keep-alive 的时候,页面第一次进入,钩子的触发顺序created-> mounted-> activated,退出时触发deactivated。当再次进入(前进或者后退)时,只触发activated。
路由大法不错,不需要关心哪个页面跳转过来的,只要 router.go(-1) 就能回去,不需要额外参数。
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
.sync 修饰符所提供的功能。当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。就是说我们可以直接在我们需要传的prop后面加上 .sync
比如我需要绑定 mes,然后我在他后面加上.sync。1
<son :mes.sync="message"></son>
他会扩展成:1
<son :mes="message" @update:mes="val => message= val"></son>
举个栗子:
父组件代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<template>
<div>
<son :mes.sync='message'>
</son>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'fathor',
components: {
Son
},
data() {
return {
message: '我是父组件的数据'
}
}
</script>
子组件代码:
1 | <template> |
看完上面的代码,你可能觉得这好像和“通过自定义事件(emit)从子组件向父组件中传递数据”是一样的。
其实并不一样, 两者有着父子组件关系上的不同, 下面我通过一行关键的代码证明它们的区别所在:
在我们使用.sync修饰符中, 自定义事件发生时候运行的响应表达式是:
1 | <son :mes="message" @update:mes="val => message= val"></son> |
在“通过自定义事件从子组件向父组件中传递数据” 里,自定义事件发生时候运行的响应表达式是:
1 | <son @eventYouDefined = "arg => functionYours(arg)"></son> |
对前者, 表达式 val => message = val意味着强制让父组件的数据等于子组件传递过来的数据, 这个时候,我们发现父子组件的地位是平等的。 父可以改变子(数据), 子也可以改变父(数据)。
对后者, 你的functionYours是在父组件中定义的, 在这个函数里, 你可以对从子组件接受来的arg数据做任意的操作或处理, 决定权完全落在父组件中, 也就是: 父可以改变子(数据), 但子不能直接改变父(数据)。 父中数据的变动只能由它自己决定。
最近换了个电脑,于是出现在新电脑上如何更新hexo个人网站的问题,网上各种方法,个人觉得还是有些不明白的地方,踩坑无数。最后终于成功,所以特此记下,也希望给其他人能有一些帮助。文章中用‘旧电脑’指代原来已经搭建好hexo的电脑,‘新电脑’指代即将要搭建hexo环境的电脑。
首先确保自己已经使用hexo在github搭建好了自己的个人博客,github仓库中如下图显示:
在Github的username.github.io(就是hexo网站的仓库)仓库上新建一个xxx(自己取名,我取名为hexo)分支,并切换到该分支,并在该仓库->Settings->Branches->Default branch中将默认分支设为xxx,save保存。
新建分支,并选中新建的分支,如下图:
设置新建的xxx分支为默认分支,如下图:
然后将该仓库克隆到本地,进入到本地的username.github.io文件目录。
进入本地的username.github.io后,在当前目录使用git bash执行git branch命令查看当前所在分支,应为新建的分支xxx。
先将本地博客的部署文件(就是之前存放hexo项目的目录下的全部文件)全部拷贝进username.github.io文件目录中去。
接下来,进入username.github.io文件目录下,依次执行:
即可将博客的hexo部署环境提交到GitHub个人仓库的xxx分支。
此时部署到github的步骤就已经完成了。master分支和xxx分支各自保存着一个版本,master分支用于保存博客静态资源,提供博客页面供人访问;xxx分支用于备份博客部署文件,供自己修改和更新,两者在一个GitHub仓库内互不冲突,完美!
到这一步,你的博客已经可以在其他电脑上进行同步的维护和更新了,方法很简单:
1 | $ npm install -g hexo-cli |
首先注意一点:每次换电脑进行博客更新时,不管上次在其他电脑有没有更新,最好先git pull,防止冲突,这是一个好习惯。
然后,依次执行以下步骤:
到此,就可以在多台电脑上更新个人网站啦!如有疑问,欢迎留言!
我们都知道,在Vue中,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。
看看代码:1
2
3
4
5
6
7{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
1 | <!-- 这是语句,不是表达式 --> |
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:
1 | <div id="example"> |
在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性。一个简单的例子:
html:
1 | <div id="example"> |
js:
1 | var vm = new Vue({ |
结果:
Original message: “Hello”
Computed reversed message: “olleH”
这里我们声明了一个计算属性 reversedMessage。我们提供的函数将用作属性 vm.reversedMessage的 getter 函数:
1 | console.log(vm.reversedMessage) // => 'olleH' |
你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。
你可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect)的,这使它更易于测试和理解。
你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:
html:1
<p>Reversed message: "{{ reversedMessage() }}"</p>
js:1
2
3
4
5
6// 在组件中
methods: {
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
我们可以将同一函数定义为一个方法(methods)而不是一个计算属性(computed)。两种方式的最终结果确实是完全相同的。然而, 不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。 这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:1
2
3
4
5computed: {
now: function () {
return Date.now()
}
}
相比之下,每当触发重新渲染时,调用方法(methods)将总会再次执行函数。
我们为什么需要缓存?假设我们有一个性能开销比较大的的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A 。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法(methods)来替代。
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性(watch)。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性(computed)而不是命令式的 watch 回调。细想一下这个例子:
html:1
<div id="demo">{{ fullName }}</div>
js:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
上面代码是命令式且重复的。将它与计算属性(computed)的版本进行比较:1
2
3
4
5
6
7
8
9
10
11
12var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
是不是好了很多呢?
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// ...
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
// ...
现在再运行 vm.fullName = ‘John Doe’ 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。
专门记一篇关于vue条件渲染的文章似乎没有必要,刚开始也没打算记。但在应用过程中有时会有点迷惑,什么时候用v-if,什么时候用v-show,这两者有什么区别?所以,还是决定记一下,虽然大部分都是官网的介绍,但俗话说,眼过千变不如手过一遍。所以,记此篇。
在字符串模板中,比如 Handlebars,我们得像这样写一个条件块:
html代码:1
2
3
4<!-- Handlebars 模板 -->
{{#if ok}}
<h1>Yes</h1>
{{/if}}
在 Vue 中,我们使用 v-if 指令实现同样的功能:
html代码:1
<h1 v-if="ok">Yes</h1>
也可以用 v-else 添加一个“else 块”:
html代码:1
2<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
因为 v-if是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 template 元素当做不可见的包裹元素,并在上面使用 v-if。最终的渲染结果将不包含 template 元素。
1 | <template v-if="ok"> |
你可以使用 v-else 指令来表示 v-if 的“else 块”:
1 | <div v-if="Math.random() > 0.5"> |
v-else 元素必须紧跟在带 v-if 或者 v-else-if 的元素的后面,否则它将不会被识别。
v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:1
2
3
4
5
6
7
8
9
10
11
12<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
类似于 v-else,v-else-if 也 必须紧跟 在带 v-if 或者 v-else-if 的元素之后!
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使Vue变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
html代码:1
2
3
4
5
6
7
8<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,input标签不会被替换掉——仅仅是替换了它的 placeholder。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:1
2
3
4
5
6
7
8<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
现在,每次切换时,输入框都将被重新渲染。
注意,label元素仍然会被高效地复用,因为它们没有添加 key 属性。
另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:1
<h1 v-show="ok">Hello!</h1>
不同的是带有v-show的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
注意,v-show 不支持 template 元素,也不支持 v-else。
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
]]>以前我们遍历数组通常使用for循环,例如:
1 | var arr = ['apple', 'orange', 'banana'] |
ES5的话也可以使用forEach,例如:
1 | var arr = ['apple', 'orange', 'banana'] |
但是使用forEach遍历数组的话,使用break不能中断循环,使用return也不能返回到外层函数。
ES5具有遍历数组功能的还有map、filter、some、every等,只不过他们的返回结果不一样。
使用for in也可以遍历数组,例如:
1 | Array.prototype.method = function(){ |
打印的结果:
1 | apple |
由打印的结果可以看出,使用for in会遍历数组所有的可枚举属性,包括原型。例如上面栗子中的原型方法method和name属性。
除此之外,使用for in遍历数组还会存在以下问题:
所以for in更适合遍历对象,不要使用for in遍历数组。
那么除了使用for循环,如何更简单的正确的遍历数组达到我们的期望呢(即不遍历method和name),ES6中的for of是个很好的选择。
1 | Array.prototype.method = function(){ |
打印结果可以看出,for of遍历的只是数组内的元素,而不包括数组的原型属性method和索引name。
通常用for in来遍历对象的键名,例如:1
2
3
4
5
6
7
8
9
10
11Object.prototype.method = function() {
console.log(this);
}
var myObject={
a:1,
b:2,
c:3
}
for (var key in myObject) {
console.log(key)
}
for in可以遍历到myObject的原型方法method。
如果不想遍历原型方法和属性的话,可以在循环内部判断下,hasOwnPropery方法可以判断某属性是否是该对象的实例属性:1
2
3
4
5for (var key in myObject) {
if(myObject.hasOwnProperty(key)){
console.log(key);
}
}
或者可以通过ES5的Object.keys(myObject)获取对象的实例属性组成的数组,不包括原型方法和属性。
1 | Object.keys(myObject).forEach(function(key) { |
1 | var str = 'helloolleh'; |
1 | let arr = [1, 13, 24, 11, 11, 14, 1, 2]; |
1 | let unique = function (arr) { |
1 | let newArr = Array.from(new Set(arr)); |
1 | let unique02 = function(arr){ |
1 | let str = 'afjghdfraaaasdenas'; |
1 | let arr = ['a','d','c','b']; |
1 | function bubbleSort(arr){ |
1 | function quickSort(arr) { |
1 | //参数n为要生成的字符串长度 |
1 | var arr = [10,5,11,7,8,9]; |
很明显我们知道,最大差值肯定是一个数组中最大值与最小值的差。1
2
3
4
5
6
7
8
9
10
11
12
13function getMaxProfit(arr) {
var minPrice = arr[0];
var maxProfit = 0;
for (var i = 0; i < arr.length; i++) {
var currentPrice = arr[i];
minPrice = Math.min(minPrice, currentPrice);
var potentialProfit = currentPrice - minPrice;
maxProfit = Math.max(maxProfit, potentialProfit);
}
return maxProfit;
}
console.log(getMaxProfit(arr));//6
利用script标签没有跨域限制的“漏洞”,来达到与第三方通讯的目的。
第三方产生的响应为json数据的包装(故称之为jsonp,即json padding)。
它的基本思想是,网页通过添加一个script元素,向服务器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
首先,网页动态插入script元素,由它向跨源网址发出请求。
1 | function addScriptTag(src) { |
上面代码通过动态添加script元素,向服务器example.com发出请求。注意,该请求的查询字符串有一个callback参数,用来指定回调函数的名字,这对于JSONP是必需的。
服务器收到这个请求以后,会将数据放在回调函数的参数位置返回。1
2
3foo({
"ip": "8.8.8.8"
});
由于script元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
在js中,我们经常会遇到这种情况,需要监听页面的scroll事件或者鼠标的mousemove事件等。由于这些事件在鼠标移动的过程中会被浏览器频繁的触发,会导致对应的事件也会被频繁的触发,这样就会造成很大的浏览器资源开销,而且好多中间的处理是不必要的,这样就会造成浏览器卡顿的现象。我们无法做到让浏览器不去触发对应的事件,但是可以做到让处理事件的方法执行频率减少(throttle)或者在执行动作完成后执行一次(debounce),从而减少对应的处理开销。
throttle指,在动作执行过程中,隔断时间触发一次事件,这样可以减少事件的方法执行的频率。debounce指只在动作结束后触发一次,把中间的处理函数全部过滤掉了,只执行规判定时间内的最后一个事件。
一般window的resize,input的keyup事件使用debounce;scroll、mousemove等事件使用throttle。
直接上代码
1 | //实现思路 |
接下来就是调用了
1 | let throttleRun = throttle(() => { |
这时候疯狂的滚动页面,会发现会400ms打印一个123,而没有节流的话会不断地打印。
但是到这里,我们的节流方法是不完善的,因为我们的方法没有获取事件发生时的this对象,而且由于我们的方法简单粗暴的通过判断这次触发的时间和上次执行时间的间隔来决定是否执行回调,这样就会造成最后一次触发无法执行,或者用户触发的间隔确实很短,也无法执行,造成了误杀,所以需要对方法进行完善。
1 | function throttle(func, wait) { |
这样我们的方法就相对完善了,调用方法和之前相同。
去抖的方法,和节流思路一致,但是只有在动作被判定结束后,方法才会得到执行。
1 | function debounce(method, delay) { |
调用方法和之前相同。
如有错误,欢迎指正!
]]>由于 VSCode 没有提供直接在浏览器中打开文件的内置界面,所以此插件在快捷菜单中添加了在默认浏览器查看文件选项,以及在客户端(Firefox,Chrome,IE)中打开命令面板选项。
让 VSCode 资源树目录加上图标,必备良品!
自动路劲补全,默认不带这个功能的
Beautify css/sass/scss/less
react-beautify
格式化代码
使用指南:按F1,然后输入bea—>选择你要格式的文件类型 js/css/html
让括号拥有独立的颜色,易于区分。可以配合任意主题使用。
自动闭合HTML标签
更改HTML/XML标签名时,自动更新相对应的开始标签或结束标签的标签名
这个便捷的插件,将为你提供你在 CSS 中使用颜色的相关信息。你只需在颜色上悬停光标,就可以预览色块中色彩模型的(HEX、 RGB、HSL 和 CMYK)相关信息了。
超级实用且初级的 H5代码片段以及提示
让 html 标签上写class 智能提示当前项目所支持的样式
jquery 重度患者必须品,废话不多说,上图:
html代码检测
使用此插件,你可以追踪至样式表中 CSS 类和 ids 定义的地方。当你在 HTML 文件中右键单击选择器时,选择“ Go to Definition 和 Peek definition ”选项,它便会给你发送样式设置的 CSS 代码。
VUE —— 语法高亮、智能感知、Emmet等
VUE —— snippet代码片段
会在左下角显示文件大小,很不错
按F1,输入open preview to the side
设置 – 用户设置 – 添加”editor.wordWrap”: “on”
可以给文件自动加上头部说明信息
设置 – 用户设置 – 添加 :
“fileheader.Author”: “your name”,
“fileheader.LastModifiedBy”: “your name”
使用方式:在文件中按alt + ctr + i,mac按option + control + i
设置 – 用户设置 – 添加 :”editor.renderWhitespace”: “all”
]]>前几天在工作中遇到一个需求,需要在一个url中截取第二个’/‘开始后面的字符,于是就需要获取到第二个’/‘的小标。如何在字符串中获取某个字符第n次出现的下标呢?用此文记录一下。
1 | function find(str, cha, num) { |
1 | function find(arr, cha) { |
在网页中,我们有时候需要展示多张图片。如果图片太多,页面的加载速度会慢很多,这样会非常影响用户体验。所以,在图片过多的页面,为了加速页面的加载进度,我们需要将页面中没出现在可视区域内的图片先不做加载,等图片滚动到可视区域内时,再把图片加载出来,这样对于页面的加载性能会有很大的提升。
将页面中所有img标签的src统一设置为一张占位图进行占位,然后给每个img标签添加一个属性data-src(这个属性可以自定义)指向图片的真实地址。
当载入页面时,先把可视区域内的img标签的data-src属性值赋给src属性。然后监听滚动事件,当img标签出现在可视区域时,把data-src属性值赋给src属性。
这样,便实现了懒加载。
直接上代码。
1 | <body> |
上面为html部分,是一些图片。
接下来,我们给img标签加一些样式。
1 | img { |
现在,页面中就有一些一列排的占位图片。
接下来,就来实现懒加载。
1 | function lazyload(){ |
这样,我们就实现了懒加载的基本逻辑。
接下来我们需要在页面刚开始加载时,就把可视区域的图片加载出来。
1 | let lazyInit = lazyload(); |
然后,我们监听页面的滚动事件。
1 | window.addEventListener('scroll', lazyInit); |
但到这里,我们的懒加载是不完善的。
在js中的函数节流(throttle)和函数去抖(debounce)中说过,当需要监听scroll事件时,我们一般会使用throttle节流方法。
接下来,我们就来完善一下我们的代码。
首先,先定义一个节流的函数。
1 | function throttle(func, wait) { |
然后,再调用节流的方法
1 | let lazyScroll = throttle(lazyInit, 800); |
最后,我们修改scroll事件的回调方法为lazyScroll。
1 | window.addEventListener('scroll', lazyScroll); |
到这里,我们就实现了一个简单的懒加载。
如有错误,欢迎指正!
移动浏览器为什么会设置300毫秒的等待时间呢?这与双击缩放的方案有关。平时我们可能已经注意到了,双击缩放,即用手指在屏幕上快速点击两次,可以看到内容或者图片放大,再次双击,浏览器会将网页缩放至原始比例。
浏览器捕获第一次单击后,会先等待一段时间,如果在这段时间区间里用户未进行下一次点击,浏览器会做单击事件的处理。如果这段时间里用户进行了第二次单击操作,则浏览器会做缩放处理。这段时间就是移动端点击事件的300ms延迟。
1 | <meta name = "viewport" content="user-scalable=no" > |
使用这个方法是通过完全禁用缩放来达到目的,虽然大部分移动端能解决这个延迟问题,但是部分苹果手机还是不行。
FastClick 是 FT Labs 专门为解决移动端浏览器 300 ms点击延迟问题所开发的一个轻量级的库。简而言之,FastClick 在检测到touchend事件的时候,会通过 DOM 自定义事件立即触发一个模拟click事件,并把浏览器在 300 ms之后真正触发的click事件阻止掉。使用方法如下:
第一步:在页面中引入fastclick.js文件;
第二步:在js文件中添加以下代码:
1 | // 原生js写法 |
在开发过程中,我们经常会遇到让元素的宽高成一定比例的情况。或者在使用轮播图时,如果我们只是靠图片撑开元素的高度,那图片未加载出来时,页面会出现抖动的情况。为了避免这种情况,我们就需要让容器宽高成一定比例,如果图片是600*300的尺寸,我们就需要让容器的宽高比为2:1。
我们可以使容器的宽度为100%,但高度不能使用50%,因为元素的高度是根据父元素的高度为基数的,并不是宽度。那我们这时候就需要用一点小技巧了,我们知道元素的padding值都是以父元素的宽度为基数的。那我们就可以这样实现:1
2
3
4
5
6div {
width: 100%;
height: 0; // 防止内容撑开高度
padding-bottom: 50%;
overflow:hidden;
}
其实代码很简单,主要是记录一下思路。
还有另一种实现方式:使用vw,相对于视口的宽度,视口被均分为100单位的vw1
2
3
4div{
width:100vw;
height:50vw;
}
但是这种目前兼容性不是很好,所以如果需要兼容性好的话,还是使用第一种方法吧!
]]>在上一篇实现了双飞翼布局,这篇来实现一下圣杯布局。圣杯布局和双飞翼实现的效果是一样的,都是三栏布局,两侧栏宽度固定,中间栏自适应并且能在浏览器中优先展示渲染。
废话不多说,直接上代码吧。
首先,先给出页面结构,在三栏外面套一个容器,如下代码:1
2
3
4
5
6
7<body>
<div class="container">
<div class="center col"></div>
<div class="left col"></div>
<div class="right col"></div>
</div>
</body>
上面就是我们第一步的页面结构,把center写在第一个是为了实现在浏览器优先渲染。
然后,开始写样式,首先给三栏写上基本样式如下,高度都为400px:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.col{
height:400px;
}
.center{
width:100%;
background:red;
}
.left{
width:200px;
background:green;
}
.right{
width:200px;
background:blue;
}
到这里,我们三栏的基本样式就出来了。此时,三栏并不在一行,那块级元素怎么才能一行显示呢?那就是通过浮动。所以,我们再加上1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16.col{
height: 400px;
float: left;//新增
}
.center{
width: 100%;
background: red;
}
.left{
width: 200px;
background: green;
}
.right{
width: 200px;
background: blue;
}
这时候center的宽度是占满屏幕的,那这样才能让它在中间呢?这时给外层的container加上样式:1
2
3.container {
padding: 0 200px;
}
这时候,已经实现了三栏都在中间,但由于center宽度为100%,左右两栏被挤到了第二行,这时,就需要用上负边距来使三栏在同一行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21.container {
padding: 0 200px;
}
.col{
height: 400px;
float: left;
}
.center{
width: 100%;
background: red;
}
.left{
width: 200px;
background: green;
margin-left: -100%; // 新增
}
.right{
width: 200px;
background: blue;
margin-left: -200px; // 新增
}
到这里,三栏已经在同一行了,但三栏现在都是在中间,怎么让左右两栏位于两侧呢?这时我们就需要给左右两栏加上相对定位来改变他们自身的位置:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25.container {
padding: 0 200px;
}
.col{
height: 400px;
float: left;
}
.center{
width: 100%;
background: red;
}
.left{
width: 200px;
background: green;
margin-left: -100%;
position: relative; // 新增
left: -200px; // 新增
}
.right{
width: 200px;
background: blue;
margin-left: -200px;
position: relative; // 新增
left: 200px; // 新增
}
至此,我们就实现了圣杯布局!
]]>双飞翼布局和圣杯布局,都是三栏布局,两侧栏宽度固定,中间栏自适应并且能在浏览器中优先展示渲染。它们实现的效果是一样的,差别在于其实现的思想。
既然了解了这两种布局的功能,接下来我们就来看看具体怎么实现吧。文章中的两种方式都是兼容目前所有的主流浏览器,包括IE6在内;所以是不使用‘box-sizing’属性的。第一个先看看双飞翼布局。
首先,先给出页面结构,在三栏外面套一个容器,如下代码:1
2
3
4
5
6
7<body>
<div class="container">
<div class="center col"></div>
<div class="left col"></div>
<div class="right col"></div>
</div>
</body>
上面就是我们第一步的页面结构,把center写在第一个是为了实现在浏览器优先渲染。
接下来我们先给三栏各自设置宽高,这里我们高度都用400px。因为中间栏宽度要自适应,所以我们设置为100%。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15.col{
height:400px;
}
.center{
width:100%;
background:red;
}
.left{
width:200px;
background:green;
}
.right{
width:200px;
background:blue;
}
到这里,我们三栏的基本样式就出来了。此时,三栏并不在一行,那块级元素怎么才能一行显示呢?那就是通过浮动。所以,我们再加上1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16.col{
height:400px;
float:left;//新增
}
.center{
width:100%;
background:red;
}
.left{
width:200px;
background:green;
}
.right{
width:200px;
background:blue;
}
浮动的特点就是这一行占满了就会换行。因为中间栏已经占满了一行,所以此时两侧栏是在第二行的。那怎么才能让两个侧栏上来呢?这就是负外边距的作用。
1 | .col{ |
到这里,三栏的基本布局就已经完成了。但是还有个问题,就是当我们在center中加入内容后,两边的内容会被两侧栏挡住,前面我们说了为了兼容性,我们是不使用box-sizing属性的,所以不能直接给center加padding属性。这里不理解的可以参考我另一篇文章:关于盒模型的理解
这时我们可以在center中新增一个子元素用来放置中间的内容,同时让它的左右外边距为左右两栏的宽度:1
2
3
4
5
6
7
8
9<body>
<div class="container">
<div class="center col">
<div class="main"></div>//新增
</div>
<div class="left col"></div>
<div class="right col"></div>
</div>
</body>
1 | .col { |
至此,我们就实现了双飞翼布局。须注意一点:因为两侧栏宽度是一定的,中间自适应,这样如果宽度缩小到一定程度中间的内容就会乱,所以一般我们都会给body加上min-width属性。
圣杯布局,下篇文章再见吧。
]]>