第6章 进阶必会知识

第6章 进阶知识

虽然是进阶知识,但也是必学内容。

6.1 js的作用域与this

无论是JavaScript还是emscript,变量的作用域都属于高级知识。我们想考查一个js程序员的水平如何,可以直接用作用域进行提问。

6.1.1 作用域

无论是JavaScript还是emscript,对于作用域的使用基本相同,后者更加严密一些。

1. 全局变量可以直接引用
//全局变量 a:
var a = 1;
function one() {
  console.info(a)
}
打印结果是1
2. 函数内的普通变量
function two(a ){
  console.info('a is' + a)
}
two(2)打印结果是a is 2
3. 普通函数可以对全局变量做赋值
var a = 1;
function four(){
  console.info(' in four, before a=4: ' + a)
  if(true) a = 4;
  console.info(' in four, after a=4: ' + a)
}

运行结果如下:

four(4)
in four, before a=4: 1  ( 这个是符合正常的scope逻辑的。)
in four, after a=4: 4   ( 这个也是符合)

再次运行console.info(a),输出结果为4,说明全局变量a在four()函数中已经发生了永久的变化。

4. 通过元编程定义的函数
var six = ( function(){
  var foo = 6;
  return function(){
    return foo;
  }
}
)();

在上述代码中,js解析器会先运行(忽略最后的())。

var temp = function(){
  var foo = 6;
  return function(){
    return foo;
  }
}

然后运行var six = (temp)(),six就是:

function(){
   return foo;
}

上面的foo是来自方法最开始定义的var foo=6,而这个变量的定义,是在一个function()中的。它不是一个全局变量。

如果在console中输入foo,就会看到报错信息。

Uncaught ReferenceError: foo is not defined
5. 通过元编程定义的函数中的变量,不会污染全局变量
var foo = 1;

var six = ( function(){ var foo = 6; return function(){ console.info("in six, foo is: " + foo); } } )();

在上面的代码中,我们先定义了一个全局变量foo,然后定义了一个方法six,其中定义了一个临时方法foo,并进行了一些操作。

运行:

six()  // 返回:   in six, foo is: 6
foo    // 返回: 1
6.1.2 this

对于this的使用,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this指出了对于JavaScript中this的详细用法。在emscript中也一样。

简单地说,大家只要记住this是指当前作用域的对象实例就可以了。

var apple = {
  color: 'red',
  show_color: function() {
    return this.color
  }
}

输入apple.show_color即可看到输出red,这里的this指的就是apple变量。

6.1.3 实战经验
1. 在Vue的方法定义中容易用错

当代码看起来没问题,但console总报“xx undefined”时,十有八九是忘记加this了。

例如:

使用浏览器加载上述代码,我们会发现报错,如图6-1所示的浏览器报错is not defined。

图6-1 浏览器报销is not defined

在message += ...一行代码中,message是当前vue实例的一个"property"(属性),如果希望在methods中引用这个属性,就需要使用this.message才行。这里的this对应的就是var app = new Vue()中定义的app。

2. 在发起HTTP请求时容易用错

我们看下面的例子,是一段代码片段。

在上面的代码中,定义了一个cities属性和一个my_http_request方法。my_http_request方法会向远程发起一个请求,然后把返回的response中的值赋给cities。

通过上面的代码可知,需要先在axios.get之前定义一个变量let that=this。此时,this和that都处于"Vue"的实例中。

但是,在axios.get(..).then()函数中,就不能再使用this了。因为在then(...)中,这是function callback,其中的this会代表这个HTTP request event。

注意,如果使用了emscript的=>,就可以避免上述问题。

3. 在event handler中容易用错

道理同上。

6.2 Mixin

Mixin是一种更好的代码复用模式。

我们知道Java、Object C中的interface、implements、extends等关键字的意义是为了让代码可以复用、继承。但是这几种方法理解起来很不直观,给人一种模糊的感觉,特别是不习惯“设计模式”的用户。

在js、Ruby等动态语言中,如果需要复用代码,可直接使用mixin。

1. Mixin的概念

Mixin实际上是利用语言的特性(关键字),以更加简洁易懂的方式实现了“设计模式”中的“组合模式”。可以定义一个公共的类,这个类叫做mixin,然后让其他的类通过include的语言特性来包含mixin,直接具备了mixin的各种方法。

下面看一下在Vue.js中如何使用mixin这种强大的工具。

2. 建立一个Mixin文件

可以在src/mixin目录下创建,例如src/mixin/common_hi.js文件:

3. 使用

Mixin使用起来十分简单,在对应的js文件,或者vue文件的<script>代码中引用即可。

例如,新建一个vue文件:src/components/SayHiFromMixin.vue,内容如下:

注意:

  • mixins: [CommonHi]中的中括号表示数组。
  • 在mounted()中调用的话,需要带有this关键字,如this.hi()。

路由如下:

运行结果如图6-2所示的调用例子。

图6-2 Mix的调用例子

6.3 使用Computed Properties(计算得到的属性)和watchers(监听器)

我们想要在页面上显示某个变量的值时,必须经过一些计算。例如:

<div id="example">
  {{ some_string.split(',').reverse().join('-') }}
</div>

越是复杂,后期就越容易出错。此时,我们需要一种机制,可以方便地创建通过计算得来的数据,Computed Properties就是我们的解决方案。

6.3.1 典型例子

上面的关键代码是在Vue的构造函数中传入一个Computed的段落。

使用浏览器运行后,可以看到如图6-3所示的使用Computed Properties翻转字符串。

图6-3 使用Computed Properties翻转字符串

也可以打开console进行查看。输入:

> app.my_text

得到"good good study, day day up"。输入:

> app.my_computed_text

得到转换后的"up-day-day-study-good-good"。

6.3.2 Computed Properties与普通方法的区别

根据上面的例子,我们可以使用普通方法来实现。

运行上面的代码后,如图6-4所示的通过某个普通函数翻转字符串。

图6-4 通过某个普通函数翻转字符串

可以发现两者达到的效果是一样的。

区别在于:使用Computed Properties的方式,会把结果“缓存”起来,每次调用对应的Computed Properties时,只要对应的依赖数据没有改动,那么就不会变化;使用function实现的版本,则不存在缓存问题,每次都会重新计算对应的数值。因此,我们需要按照实际情况,选择使用Computed Properties还是普通function的形式。

6.3.3 watched property

Vue.js中的property(属性)是可以根据计算发生变化(computed),或者根据监听(watch)其他变量的变化而发生变化的。

下面看一下,如何根据监听(watch)其他的变量而自身发生变化的例子。

在上面的代码中,watch: { city: ..., district: ...}表示city和district已经被监听了,这两个都是watched properties。只要city和district发生变化,full_address就会随之变化。

使用浏览器打开上面的代码,此时由于city和district没有发生变化,因此full_address的值还是"某市某区"。如图6-5所示为城市和街道变化前的页面。

图6-3 城市和街道变化前的页面

当在“我所在街道:”的文本框中添加“望京街道”后,可以看到下面的“我所在的详细一些的地址:”发生了变化。如图6-6所示为城市和街道变化后的页面。

图6-6 城市和街道变化后的页面

使用computed会比watch更加简洁。

上面的例子,我们也可以使用computed来改写。

可以看到方法少了一个,data中定义的属性也少了一个,简洁了不少。代码简洁,维护起来就会很容易(代码量越少,程序越好理解)。

6.3.4 Computed Property的setter(赋值函数)

从原则上来说,Computed Property是根据其他的值经过计算得来的,不应该被修改。不过在开发中,确实有一些情况需要对Computed Property做修改,同时影响某些对应的属性(过程与上面是相反的)。

我们看下面的代码。

在上面的代码中有这样一段:

可以看出,上面的get代码段就是原来的代码内容。而set端中,则定义了如果Computed Property(也就是full_address)发生变化时,city和district的值应该如何变化。

使用浏览器打开后,在“我所在的详细一些的地址:”中输入一些文字,可以看到对应的“我所在的街道”发生了变化。如图6-7所示使用setter修改computed properties。

图6-7 使用setter修改computed properties

6.4 Component(组件)进阶

在Web开发中,Component是十分常见的,只要是生产环境的项目,就一定会有Component。

6.4.1 实际项目中的Component

下面是一个实际项目中的例子,该项目只做了两个月,其中就发展到了32个Component。如图6-8所示实际项目中复杂的component文件结构。

图6-6 实际项目中复杂的component文件结构

很多时候,一个Component中嵌套着另一个,这个component再嵌套另外5个。例如:

popup-picker这个Component中,看起来是这样的:

可以看到,这个Component中还包含另外两个,即popup和picker。

这个时候,新人往往会眼花缭乱,如果看到this.$emit,就更晕了。因此,要做好实际项目,一定要学好本章内容。

Component命名规则

官方建议每个Component的命名都使用小写字母+横线的形式。例如:

Vue.component('my-component-name', { /* ... */ })

这个是符合W3C规范的。也可以定义为:

Vue.component('MyComponentName', { /* ... */ })

可以使用<MyComponentName/>调用,也可以使用<my-component-name/>调用。

6.4.2 Prop
1. Prop命名规则

Prop命名规则同component,建议使用小写字母+ '-'连接。

Prop有多种类型,包括字符串、数字、bool、数组和Object。例如:

props: {
  title: "Triple Body",
  likes: 38444,
  isPublished: true,
  commentIds: [30, 54, 118, 76],
  author: {
      name: "Liu Cixin",
      sex: "male"
  }
}
2. 可以动态为prop赋值

下面是一个静态的赋值。

<blog-post title="Vue.js的学习笔记"></blog-post>

下面是一个动态的赋值。

赋值时,只要符合标准的类型,都可以传入(包括String、bool、array等)。

3. 使用Object为Prop赋值

假设定义有:

post = {
    author: {
      name: "Big Liu",
      sex: 'male'
   }
}

那么下面的代码:

<blog-post v-bind:author></blog-post>

等价于:

<blog-post v-bind:name="author.name" v-bind:sex="author.sex"></blog-post>
4. 单向的数据流

当“父页面”引用一个“子组件”时,如果“父页面”中的变量发生了变化,那么对应的“子组件”也会发生页面的更新,反之则不行。

5. Prop的验证

Vue.js的组件Prop是可以被验证的。如果验证不匹配,浏览器的console就会弹出警告(warning),这个对于开发非常有利。

例如下面的代码:

其中,name必须是字符串,sexandheight必须是数组。第一个元素是String,第二个元素是Number,weigh必须是Number,sex是String,默认值是'male'。

Prop支持的类型有String、Number、Boolean、Array、Object、Date、Function、Symbol等。

6. Non Prop(非Prop)的属性

很多时候,因为Component的作者无法预见应该用哪些属性,所以Vue.js在设计时,支持让Component接受一些没有预先定义的prop。例如:

Vue.component('my-component', {
    props: ['title']
})

<my-component title='三体' second-title='第二册: 黑暗森林'></my-component>

上面代码中的title就是预先定义的"Prop",second-title就是“非Prop”。

如果想传递一个non-pro,非常简单,prop怎么传non-prop就怎么传。

6.4.3 Attribute
1. Attribute的合并和替换

如果component中定义了一个attribute,例如:

<template>
   <div color="red">我的最终颜色是蓝色</div>
</template>

如果在引用了这个“子组件”的“父页面”中也定义了同样的attribute,例如:

<div>
    <my-component color="blue"></my-component>
</div>

那么“父页面”传递进来的color="blue"就会替换“子组件”中的color="red"。

但是,对于class和style是例外的。上面的例子中,如果将attribute换成class,那么最终component中class的值就是"red blue"(发生了合并)。

5. 避免子组件的attribute被父页面影响

根据以上的分析,我们知道“父页面”的值总会替换“子组件”中的同名attribute。如果不希望有这样的情况发生,就可以在定义component时,这样做:

Vue.component('my-component', {
  inheritAttrs: false,
  // ...
})

6.5 Slot

作为对Component的补充,Vue.js增加了Slot功能。

6.5.1 普通的Slot

我们通过具体的例子来说明。

从上面的代码中可以看到,我们先定义了一个Component。

在Component的template中,是这样的:

template: '<div><slot></slot></div>'

这里就是我们定义的slot。在调用Component时:

<study-process>
    我学习到了Slot 这个章节
</study-process>

“我学习到了Slot这个章节”就好像一个参数一样传入到了Component中。Component发现自身已经定义了slot,就会把这个字符串放到slot的位置并显示出来。

如图6-9所示的使用Slot的页面。

图6-9 使用Slot的页面

6.5.2 named slot

named slot也就是带有名字的slot,很多时候我们可能需要多个slot。来下面的例子:

在上面的代码中,我们定义了这样的component:

Vue.component('study-process', {
  template: '<div>' +
  '<slot name="slot_top"></slot>' +
  '<slot></slot>' +
  '<slot name="slot_bottom"></slot>' +
  '</div>'
})

其中,<slot name="slot_top"></slot>就是一个named slot(具备名字的slot)。这样,在后面对于component的调用中:

<p slot='slot_top'>
    Vue.js 比起别的框架真的简洁好多
</p>

就会渲染在对应的位置了。

6.5.3 slot的默认值

我们可以为slot加上默认值,这样当“父页面”没有指定某个slot时,就会显示这个默认值了。例如:

<slot name="slot_top">这里 top slot的默认值 </slot>

6.6 Vuex

Vuex是状态管理工具,与React中的Redux相似,但是更加简洁、直观。

简单地说,Vuex可以帮我们管理“全局变量”,供任何页面在任何时候使用。与其他语言中的“全局变量”相比,Vuex的优点如下。

(1)Vuex中的变量状态是响应式的。当某个组件读取该变量时,只要Vuex中的变量发生变化,对应的组件就会发生变化(类似于双向绑定)。

(2)用户或程序无法直接改变Vuex中的变量,必须通过Vuex提供的接口来操作,该接口就是通过"commit mutation"实现的。

Vuex非常重要,不管是大项目还是小项目都会用到它,我们必须会用。完整的官方文档可参见:

https://vuex.Vue.js.org/zh-cn/getting-started.html

Vuex的内容很庞大,用到了比较“烧脑”的设计模式(这是由于JavaScript语言本身不够严谨和成熟决定的),因此笔者不打算把源代码和实现原理详细讲一遍,大家只要熟练使用就可以了。

6.6.1 正常使用的顺序

假设有两个页面:页面1和页面2共同使用一个变量counter。页面1对"counter" + 1后,页面2的值也会发生变化。

1. 修改package.json

增加vuex的依赖声明,代码如下:

"dependencies": {
  "vuex": "^2.3.1"
},

如果不确定vuex用哪个版本,就先手动安装一下。

$ npm install vuex --verbose

然后看安装的版本号就可以了。

2. 新建store文件

文件名:src/vuex/store.js。这个文件的作用是在整个Vue.js项目中声明:我们要使用Vuex进行状态管理。

文件内容如下:

import Vue from 'vue'
import Vuex from 'vuex'

// 这个就是我们后续会用到的counter状态 import counter from '@/vuex/modules/counter'
Vue.use(Vuex)
const debug = process.env.NODE_ENV !== 'production' export default new Vuex.Store({ modules: { counter // 所有要管理的module都列在这里 }, strict: debug, middlewares: [] })

在上面代码中,大部分是“鸡肋”代码。有用的代码如下:

import counter from '@/vuex/modules/counter'
...
    modules: {
        counter
    }
...

这里定义了所有的vuex module。

3. 新建vuex/module文件

文件名:src/vuex/modules/counter.js。内容如下:

上面是一个典型的vuex module,其作用就是计数。

  • state:表示状态,可以认为state是一个数据库,保存了各种数据,但无法直接访问里面的数据。
  • mutations:表示变化,可以认为所有的state都是由mutation来驱动变化的,也可以认为它是setter。
  • getter:取值的方法,与setter相对。

如果希望获取某个数据,就需要调用vuex module的getter方法;如果希望更改某个数据,就需要调用vuex module的mutation方法。

4. 新增文件:src/vuex/mutation_types.js
export const INCREASE = 'INCREASE'

大家在做项目时,要统一把mutation type定义在这里,类似于方法列表。

这个步骤不能省略,Vue.js官方也建议这样写。好处是维护时可以看到某个mutation有多少种状态。

5. 新增路由:src/routers/index.js
6. 新增两个页面:src/components/ShowCounter1.vue和src/components/ShowCounter2.vue

这两个页面基本相同。

我们可以在<script>中调用vuex的module方法。例如:

increase() {
    store.commit(INCREASE, store.getters.get_points + 1)
}

store.getters.get_points就是通过getter获取到状态"points"的方法。store.commit(INCREASE, .. )则是通过INCREASE这个action来改变"points"的值。

6.6.2 Computed属性

Computed代表的是某个组件(component)的属性,该属性是计算出来的。每当计算因子发生变化时,这个结果也要重新计算。

下面的代码中:

<script>
export default {
  computed: {
    points() {
      return store.getters.get_points
    }
  },
</script>

就是定义了一个叫作"points"的"computed"属性。然后在页面中显示这个“计算属性”:

<template>
 <div>
   </div>
</template>

就可以把state中的数据显示出来,并自动更新。

重启服务器($ npm run dev)之后运行,可以看到如图6-10所示的使用Vuex实现的计数器。单击按钮,计数器的数值就会加1。

图6-10 使用Vuex实现的计数器

6.6.3 Vuex原理图

如图6-11所示的Vuex原理图。

图6-9 Vuex原理图

可以看到:

(1)总体包括Action、Mutation和State三个概念,State由Mutation来变化。

(2)Vuex通过Action与后端API进行交互。

(3)Vuex通过State渲染前端页面。

(4)前端页面通过触发Vuex的Action来提交mutation,以达到改变"state"的目的。

6.7 Vue.js的生命周期

每个Vue.js实例都会经历如图6-12所示的生命周期。

图6-12 Vue.js的生命周期

可以看出基本周期如下。

(1)created(创建好DOM)。

(2)mounted(页面基本准备好了)。

(3)updated(update可以理解为手动操作触发)。

(4)destroyed(销毁)。

上面周期中的(1)、(3)、(4)都是自动触发的,每一步都有对应的beforeXyz方法。因此,我们一般使用mounted作为页面初始化时执行的方法。

6.8 最佳实践

1. 适当地使用vuex

能不用就不用,不要为了使用而使用,如一个小方法就可以“搞定”的事情,非要使用5个设计模式来实现。

6. 不要过度使用CSS框架

CSS框架通常会增加文件体积,如bootstrap、ele.me前端框架,特别是使用Android中的Webview加载H5页面时,基本上1k的CSS就会消耗1ms。

7. 使用CDN存放图片文件

upyun就是一个不错的选择,阿里的oss也很好。

8. js、css尽量使用压缩

让js、css都以zip的形式发送和接收,一般会减少30%~60%的体积和传送时间,具体可参考nginx文档。

9. 灵活使用第三方Vue插件

第三方Vue插件有轮播图、表单验证等。

好的程序员不一定算法好,但一定要对各种第三方插件特别熟知。

10. 前端逻辑务必简单

能在后台处理的,绝对不要放在前端处理,因为Vue.js擅长的不是处理数据结构。例如,前端需要展示一个列表,后端的接口就应该给出JSON中的数组,而不是给出一个字符串由前端去解析。

11. 不用写行末分号

Vue.js源代码中没有一行有“行末分号”。

12. 灵活使用CSS、HTML预处理工具

我们知道JADE、HAML可以生成HTML;SASS、SCSS、LESS可以生成CSS。如果公司的员工比较多,那么建议直接使用原生的HTML、CSS;如果是一个人独立负责整个项目,那么用JADE、SCSS也没问题。

6.9 Event Handler事件处理

Event Handler之所以会被Vue.js放到很重要的位置,是基于以下考虑。

(1)把与事件相关的代码独立写出来,非常容易定位各种逻辑,维护起来也方便。

(2)event handler被独立出来之后,页面的DOM元素看起来就会很简单,容易理解。

(3)当一个页面被关闭时,对应的ViewModel会被回收,该页面定义的各种event handler也会被一并垃圾回收,不会造成内存溢出。

6.9.1 支持的Event

我们在前面曾经看到过v-on:click,那么都有哪些事件可以被v-on支持呢?只要是标准的HTML定义的Event,都可以被Vue.js支持,如focus(元素获得焦点)、blur(元素失去焦点)、click(单击鼠标左键)、dblclick(双击鼠标左键)、contextmenu(单击鼠标右键)、mouseover(指针移到有事件监听的元素或其子元素内)、mouseout(指针移出元素或其子元素上)、keydown(键盘动作:按下任意键)及keyup(键盘动作:释放任意键)。

可以在下面链接中查看所有HTML标准事件。

https://developer.mozilla.org/zh-CN/docs/Web/Events

一共定义了162个标准事件和几十个非标准事件,以及Mozilla的特定事件。如图6-13所示的HTML标准事件。

图6-13 HTML标准事件

不用全部记住,在日常开发中只有十几个是常见的event。

6.9.2 使用v-on进行事件绑定

我们可以认为,几乎所有的事件都是由v-on这个directive来驱动的。

1. 在v-on中使用变量

可以在v-on中引用变量,代码如下:

使用浏览器打开后,单击按钮,就可以看到count变量会随之+1。如图6-14所示计数器每次单击都会加1。

图6-14 计数器每次单击都会加1

2. 在v-on中使用方法名

上面的例子也可以通过下面的代码来实现。

可以看到,在v-on:click='increase_count'中,increase_count就是一个方法名。

3. 在v-on中使用方法名+ 参数

也可以直接使用v-on:click='some_function("your_parameter")'这样的写法。例如:

使用浏览器打开后,单击按钮就可以看到结果。如图6-15所示的通过click事件来调用方法。

图6-15 通过click事件来调用方法

4. 重新设计按钮的逻辑

在实际开发中往往会遇到这样的情况:单击某个按钮,或者触发某个事件后,希望停止按钮的默认动作。

例如,提交表单时,我们希望先对该表单进行验证,如果验证不通过,该表单就不提交。此时,如果希望表单不提交,就需要让submit按钮不进行下一步动作。在所有的开发语言中,都会有一个对应的方法叫作"preventDefault"(停止默认动作)。

下面来看一个例子。

从上面的代码中可以看到,我们定义了一个变量url,并通过代码<a v-bind:href="this.url"v-on:click='validate($event)'>点我确定</a>做了以下两件事情:

(1)把url绑定到了该元素上。

(2)该元素在触发click事件时会调用validate方法。validate方法传递了一个特殊的参数$event,该参数是当前事件的一个实例(MouseEvent)。

在validate方法中是这样定义的:先验证是否符合规则,若符合,则放行,然后继续触发<a/>元素的默认动作(让浏览器发生跳转);否则会弹出一个"alert"提示框。

使用浏览器打开,可以看到页面如图6-16所示改变按钮的默认逻辑,触发表单验证。

图6-16 改变按钮的默认逻辑,触发表单验证

先输入一个合法的地址:http://baidu.com,单击后页面发生了跳转,跳转到了百度。再输入一个不合法的地址:https://baidu.com,该地址不是以"http://"开头,所以Vue.js代码不会放行。如图6-17所示验证不符合规则提示。

图6-17 验证不符合规则提示

5. Event Modifiers事件修饰语

有时我们希望把代码写的优雅一些,但使用传统的方式可能会把代码写的很“臃肿”。如果某个元素在不同的event下有不同的表现,那么代码看起来会有很多个if ...else ...分支。因此,Vue.js提供了Event Modifiers。例如,可以把上面的例子略加修改:

可以看出上面的代码核心是:

首先在<a/>中定义了两个click事件:click和click.prevent,后者表示如果该元素的click事件被阻止了,应该触发什么动作。然后在methods代码段中专门定义了show_message,用于给click.prevent使用。上面的代码运行与前一个例子是一样的,只是抽象分类的程度更高一些,在复杂项目中有用。

这样的Event Modifiers有以下几种。

  • stop propagation停止(调用了event.stopPropagation()方法)后,被触发。
  • prevent调用了event.preventDefault()后被触发。
  • capture子元素中的事件可以在该元素中被触发。
  • self事件的event.target是本元素时被触发。
  • once事件最多被触发一次。
  • passive为移动设备使用(在addEventListeners定义时增加passive选项)。

以上的Event Modifiers也可以连接起来使用,如v-on:click.prevent.self。

6. Key Modifiers按键修饰语

Vue.js很贴心地提供了Key Modifiers,也就是一种支持键盘事件的快捷方法。看下面的例子:

在上面的代码中,v-on:keyup.enter="show_message"为<a/>元素定义了事件,该事件对应回车键(严格地说,是回车键被按下后松开弹起来的那一刻)。

使用浏览器打开上面代码对应的文件,输入一段文字并按回车键后,就可以看到事件已经被触发了。如图6-18所示使用Key Modifiers触发事件。

图6-18 使用Key Modifiers触发事件

Vue.js支持以下Key Modifiers。

  • Enter回车键
  • tab Tab键
  • delete同时对应Backspace和Delete键
  • esc ESC键
  • space空格键
  • up向上键
  • down向下键
  • left向左键
  • right向右键

随着Vue.js版本的不断迭代和更新,越来越多的Key modifiers被添加了进来,如page down,ctrl。对于这些键的用法,大家可以查阅官方文档。

6.10 与CSS预处理器结合使用

《程序员修炼之道》这本书中曾提到程序员的一个职业习惯—DRY(Don't Repeat Yourself),即不要做重复的事情。

目前的编程语言几乎都具备了消灭重复代码的能力,但是CSS是唯一不具备支持变量的编程语言。因为CSS本身只是一个DSL(Domain Specific Language,领域特定的语言),不是“编程语言”。这样也就决定了它的特点:上手快,可以很好地表现HTML中某个元素的外观。缺点就是无法通过常见的重构手法(Extract Method,Extract Variable等)精简代码。

因此,SCSS、SASS、LESS等一系列的“CSS预处理器”(precompiler)应运而生。

6.10.1 SCSS

SCSS的全称为Sassy CSS(时髦的CSS),是SASS 3引入的新语法,其语法完全兼容CSS 3,并且继承了SASS的强大功能。也就是说,任何标准的CSS 3样式表是具有相同语义的、有效的SCSS文件。官方网站同SASS。

由于SCSS是CSS的扩展,因此所有在CSS中正常工作的代码也能在SCSS中正常工作。也就是说,对于一个SASS用户,只需要理解SASS扩展部分如何工作的,就能完全理解SCSS。

大部分的用法都与SASS相同。唯一不同的是,SCSS需要使用分号和大括号。SCSS可以说是全面取代了SASS。

看下面的例子:

$font-stack:    Helvetica, sans-serif;
$primary-color: #333;

body { font: 100% $font-stack; color: $primary-color; }

在上面的代码中,定义了两个变量:$font-stack和$primary-color。编译后的CSS如下:

body {
  font: 100% Helvetica, sans-serif;
  color: #333;
}

更多内容可以到官方网站进行学习,网址为https://sass-lang.com/guide。

6.10.2 LESS

LESS也是一种CSS预处理器。它是只多了“一丢丢”内容的CSS(It's CSS, with just a little more)。

LESS的官方网址为http://lesscss.org/,github的官方网址为https://github.com/less/less.js。其作用与SCSS一样,也是为了让代码更加精简,删除无意义的重复代码。我们来看下面的例子:

// Variables
@link-color:        #428bca; // sea blue
@link-color-hover:  darken(@link-color, 10%);

// Usage a, .link { color: @link-color; } a:hover { color: @link-color-hover; } .widget { color: #fff; background: @link-color; }

上面的例子中定义了两个变量:@link-color和@link-color-hover,并且在下方进行了引用。同时还使用了换算功能darken(@link-color, 10%)。

上面的代码会被编译成下面的CSS:

a,
.link {
  color: #428bca;
}
a:hover {
  color: #3071a9;
}
.widget {
  color: #fff;
  background: #428bca;
}

可以看到,LESS的功能非常强大。

6.10.3 SASS

提到SCSS、LESS,就不得不提SASS。SASS的官方网址为:https://sass-lang.com/,github的官方网址为https://github.com/sass/sass。

SASS的特点是去掉了大括号和分号,看起来特别简单,使用空格来标记不同的段落层次。与HAML基本是一样的。

我们来看下面的例子:

$font-stack:    Helvetica, sans-serif
$primary-color: #333
body
  font: 100% $font-stack
  color: $primary-color

在上面的代码中定义了两个变量:$font-stack和$primary-color,并且在下面对它们进行了引用。

我们来看编译后的结果:

body {
  font: 100% Helvetica, sans-serif;
  color: #333;
}

不过,在实际应用中却很少使用该语言。因为在实际应用中,程序员喜欢把UI或美工或前端工程师给过来的CSS文件直接使用。如果使用SASS的话,就很尴尬,还需要再动手做一遍转换,比较浪费时间。而且虽然美工可以看懂CSS,但是看不懂SASS。因此,这个技术比较落后,慢慢地被SCSS(SASS 3.0)取代。

6.10.4 在Vue.js中使用CSS预编译器

CSS预编译器使用的前提是我们以Webpack的形式使用Vue.js。这里以SASS为例:

安装依赖"sass-loader"和"node-sass"。运行下面的命令:

$ npm i sass-loader node-sass -D

在"webpack.base.conf.js"中添加相关配置。

{
   test: /\.s[a|c]ss$/,
   loader: 'style!css!sass'
}

在对应的".vue"文件中,可以这样定义某个样式:

<style lang='sass'>
td {
  border-bottom: 1px solid grey;
}
</style>

上面的代码在运行时,会被Webpack编译成对应的CSS文件。

6.11 自定义Directive

Vue.js除了自身提供的v-if、v-model等标准的Directive外,还提供了非常强大的自定义功能。使用这个功能,可以定义属于自己的Directive。

6.11.1 例子

我们来看下面的例子:

上面的代码中,先在Vue中定义了一个directives代码段。

  • myinput:自定义Directive的名字。使用时就是v-myinput。
  • inserted:这是一个定义好的方法(钩子方法),表示页面被Vue.js渲染的过程中,在该DOM被"insert"(插入)到页面时被触发,内容是element.focus()。

使用浏览器打开后,可以看到<input/>标签是会自动聚焦的。此时,用户就可以直接输入内容了,如图6-19所示的自定义标签实现自动聚焦。

图6-19 自定义标签实现自动聚焦

如果在"Webpack"环境下,也可以使用这样的方法:

Vue.directive('myinput', {
  inserted: function (elemenet) {
    element.focus()
  }
})
6.11.2 自定义Directive的命名方法

如果希望把v-myinput的调用写成v-my-input,在定义时,就应该:

这样就可以在View中使用了。

<input v-my-input />
6.11.3 钩子方法(Hook Functions)

我们在上面的例子中知道了inserted是一个钩子方法。下面是一个完整的列表:

  • bind:只运行一次,当该元素首次被渲染时(绑定到页面时)。
  • inserted:该元素被插入到父节点时(也可以认为是该元素被Vue渲染时)。
  • update:该元素被更新时。
  • componentUpdated:包含的component被更新时。
  • unbind:只运行一次,当该元素被Vue.js从页面解除绑定时。
6.11.4 自定义Directive可以接收到的参数

Vue.js为自定义Directive实现了强大的功能,可以接收多个参数。看下面的例子:

如图6-20所示的自定义Directive接收的参数打印结果。

图6-20 自定义Directive接收的参数打印

从图6-20中,可以看出,自定义Directive在声明时,接收了三个参数:function(element、binding、vnode)。通过这三个参数,就可以看到很多对应的内容,包括binding.name、binding.value和binding.expression,它们的含义都是字面上的意思。借助这些内容,可以实现自己想要的Directive。

6.11.5 实战经验

(1)优先考虑使用Component。

考虑到维护成本,其作用与JSP中的自定义标签是一样的。与其使用Directive,不如使用Component。

(2)如果一定要用,就把它实现的尽量简单。

如果接手的新人水平太浅,那么很可能读不懂这段代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值