JavaScript里的函数是什么

1.定义

i 匿名函数

var fn = function(){return 1} 有name fn.name = 'fn'

​ ii 具名函数

function fn(){ return 1}

var fn2 = function fn3(){ return 2 }

​ 两者的区别在于函数作用域

console.log(fn()) fn()

console.log(fn3()) undefined

​ iii 箭头函数

var fn = (i,j)=>{return i+j} fn.name = fn

###2.词法作用域(静态作用域)

  var global1 = 1

     function fn1(param1){

       var local1 = 'local1'
       var local2 = 'local2')
       function fn2(param2){
           var local2 = 'inner local2'
           console.log(local1)
           console.log(local2)
       }

     function fn3(){
         var local2 = 'fn3 local2'
         fn2(local2)
     }
   }


var i = 1,j = 2,k = 3;

function a(o,p,x,q){

var x = 4;
    alert(i+'a');
function b(r,s) {
    var i = 11,y = 5;
        alert(i+ 'b');
    function c(t){
      var z = 6;
            alert(i+'c');
    };
        //函数表达式
    var d = function(){
            alert(y+'y d');
        };
        c(60);
        d();
};
    b(40,50);
    }

a(10,20,30);

通过画出词法树分析。先找同级有没有,再找上级的。

方方的视频

参考

###3.call stack

​ i stack

​ 栈,先进后出,后进先出。

1+1+1

1+2+3

递归

斐波那契数列

function fb(n){

console.log(‘我正在计算’+ n );

if( n>=3 ){

return fb(n-1) + fb(n-2)

}else{

return 1;

}

}

4.this && arguments

  • this 是隐藏的第一个参数,而且必须是对象。

    • 1
      2
      3
      4
      function f(){console.log(this); console.log(arguments)}
      f.call() // window []
      f.call({name:'zty'}) // {name:'zty'} []
      f.call({name:'zty'},1,2) // {name:'zty} [1,2]
    • 调用一个函数,开始需要三部

      • 记录函数的位置,放入 call stack

      • 传入 this 对象

      • 传入 arguments 数组(伪)

    • 为什么要用 call

      • fn() 是 fn.call() 的阉割版
  • 为什么 this 必须是对象

    • 因为 this 就是函数与对象之间的羁绊

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      var person = {
      name : 'zty',
      sayHi : function(person){
      console.log(person.name ,'HI')
      }
      }
      person.sayHi(person) // zty HI 这种方式很蠢,前面有 person 后面有 person, 为什么 JS 不能帮我把 参数中的person 指定了呢,于是改造一下
      var person = {
      name : 'zty',
      sayHi : function(){
      console.log(this.name ,'HI')
      }
      }
      person.sayHi.call(person) // zty HI 不吃语法糖 this === person ,指定this
      person.sayHi() // 吃语法糖
      person.sayHi.call({name:'xxx'}) // xxx HI 说明里面的函数其实跟person对象没有任何关系,是独立的。(this 真的很不靠谱)
      ------------------------------
      var fn = person.sayHi;
      fn() // HI 显示不出 'zty',这是因为fn()其实是 fn.call() => this为window
      fn(person) // HI this === window
      fn.call(person) // zty HI this === persom

5.call/aplly

​ i call 用是函数调用标准写法,传递this , xxx.call(ASthis , arguments)

​ ii 当你不确定参数的个数时候(数组),使用apply , xxx.apply(ASthis , [ … ])

1
2
3
4
5
6
7
8
9
10
11
12
function sum () {
var n = 0 ;
for(var i = 0 ; i<arguments.length ; i++){
n+=arguments[i]
}
return n ;
} // 无论有多少参数都可以求和的函数
sum.call(undefined , 1,2,3) // 6
sum.call(undefined , 1,2,3,4) // 10
//假如你不知道有多少参数呢?传给你一个数组 a , 并不知道里面的内容,如何求和?
//或者 a.length === 100 ,你如何传参呢?
sum.apply(undefined , a);

6.bind

​ call 和 apply 都是直接调用函数,而 bind 是返回一个新的函数 , 这个新函数会call 原来的参数 ,call 的参数由你指定。

1
2
3
4
5
6
7
8
9
10
11
12
var view = {
element : '#div',
bindEvents : function (){
this.element.onClick = this.onClick.bind(this)
//view.element.onClick = view.onClick.bind(view)
//this.onClick.bind(this) bind的参数this ,会变成 onClick 的第一个参数
//为什么这么做?因为 onClick 的 this , 是被点击元素,我们需要的 this === view
}
onClick: function (){
this.element.addClass('active')
}
}

7.回调 callback

​ i 名词形式 : 被当做参数的函数 array.sort(function(a,b){return a-b})

​ ii 动词形式 : 调用这个回调

​ 回调跟异步没有一点关系

8.构造函数

​ i 返回对象的函数就是构造函数 new Number(1)

​ ii 一般首字母大写。

9.箭头函数

​ i 箭头函数没有 this

​ ii 有 arguments

前端中的MVC

前端中的MVC

需求:通过AJAX获取数据,展示一个图书的书名,数量,并且用户可以修改数量,并上传到服务器。

###1.意大利面条写法

1
<div id="app"> </div>
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*因为没有后台,默认bookdata就是从服务器传来的数据,由fethDb获取,saveDb更新 */
var bookData = {
"id": "1",
"name": "javascript高程",
"number": 2 //数量
}
function fetchDb() { //获取数据
return bookData;
}
function saveDb() { //更新数据
return bookData;
}

/*我们需要把内容放到HTML里,所以需要有个模板 */

var template = `
书名<span id="name">《__name__》</span>
数量<span id="number"> __number__</span>
<div>
<button id="add">加一</button>
<button id="minus">减一</button>
</div>
`
/*将我们的数据,放入模板*/
function showBook(result){
var html = template.replace('__name__', result.name).replace('__number__', result.number);
$('#app').html(html)
}
showBook(fetchDb()) //渲染模板
/*写加减数量的方法 */
function add() {
bookData.number+=1
var newdata = saveDb(); //假设更新了,并从服务器获取了修改后的新数据
$('#number').text( newdata.number)

}
$('#add').on('click',add)
function minus() {
bookData.number-=1
var newdata = saveDb();
$('#number').text( newdata.number)

}
$('#minus').on('click',minus)

这种代码写法叫做意大利面条式代码,表示代码长短不一,很杂乱,如果我想要读懂这段代码,必须从开头读起。

2.MVC

一些程序员想出了解决办法

一些程序员通过自己的总结,发现这些代码总是可以分成三类:

  1. 专门操作远程数据的代码(fetchDb 和 saveDb 等等)
  2. 专门呈现页面元素的代码(innerHTML 等等)
  3. 其他控制逻辑的代码(点击某按钮之后做啥的代码)

为什么分成这三类呢?因为我们前端抄袭了后端的分类思想,后端代码也经常分为三类:

  1. 专门操作 MySQL 数据库的代码
  2. 专门渲染 HTML 的代码
  3. 其他控制逻辑的代码(用户请求首页之后去读数据库,然后渲染 HTML 作为响应等等)

这些思路经过慢慢的演化,最终被广大程序员完善为 MVC 思想。

  1. M 专门负责数据
  2. V 专门负责表现
  3. C 负责其他逻辑

如果我们来反思一下,会发现这个分类是无懈可击的:

  1. 每个网页都有数据
  2. 每个网页都有表现(具体为 HTML)
  3. 每个网页都有其他逻辑

于是乎,MVC 成了经久不衰的设计模式(设计模式就是「套路」的意思)

我们改写一下上面的代码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*创建一个model层 负责存储数据、请求数据、更新数据*/
/*因为没有后台,默认bookdata就是从服务器传来的数据,由fethDb获取,saveDb更新 */
let model = {
bookData: {
"id": "1",
"name": "javascript高程",
"number": 2 //数量
},
fetchDb() {
return this.bookData;
},
saveDb() {
return this.bookData;
}
}
/*view层,渲染视图*/
let view = {
el: '#app',
template : `
书名<span id="name">《__name__》</span>
数量<span id="number"> __number__</span>
<div>
<button id="add">加一</button>
<button id="minus">减一</button>
</div>
`,
render(result) { //此处将showbook改名为render
var html = this.template.replace('__name__', result.name).replace('__number__', result.number);
$('#app').html(html)
}
}

/*控制器,负责调度 model 和 view */
let controller = {
init({model,view}){
this.model = model
this.view = view
this.view.render(model.fetchDb()) //渲染模板
this.bindEvents()
},
events:[
{type:'click',selector:'#add',fn:'add'},
{type:'click',selector:'#minus',fn:'minus'},
],
bindEvents(){
this.events.map((e)=>{
$(this.view.el).on(e.type,e.selector,this[e.fn].bind(this)) //事件绑定 注意this需要使用外面的this
})
},
add() {
model.bookData.number += 1
var newdata = model.saveDb(); //假设更新了,并从服务器获取了修改后的新数据
$('#number').text(newdata.number)

},
minus() {
model.bookData.number -= 1
var newdata = model.saveDb();
$('#number').text(newdata.number)

}
}

controller.init({model,view})

3.模板代码(也就是类)

一个页面或模块只需要 model view controller 三个对象
第二个页面就需要再来 model2 view2 controller2 三个对象
第三个页面就需要再来 model3 view3 controller3 三个对象
……
第N个页面就需要再来 modelN viewN controllerN 三个对象

你每次写一个 model 都要写很类似的代码
你每次写一个 view 都要写很类似的代码
你每次写一个 controller 都要写很类似的代码

为什么不利用模板代码(俗称面向对象)把重复的代码写到一个类呢(JS里面就是把「共有属性」放到原型里)

代码如下

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/*创建一个model calss 负责存储数据、请求数据、更新数据*/
/*因为没有后台,默认bookdata就是从服务器传来的数据,由fethDb获取,saveDb更新 */
class Model{
constructor(options){
this.data = options.data || {}
}
fetchDb(){
return this.data
}
saveDb(){
return this.data
}
}
var model = new Model({
data: {
"id": "1",
"name": "javascript高程",
"number": 2 //数量
}
})

/*view class,渲染视图*/
class View{
constructor(options){
this.el = options.el
this.template = options.template
}
render(result){
var html = this.template.replace('__name__', result.name).replace('__number__', result.number);
$(this.el).html(html)
}
}
var view = new View({
el:'#app',
template:`
书名<span id="name">《__name__》</span>
数量<span id="number"> __number__</span>
<div>
<input type="text">
<button id="add">加一</button>
<button id="minus">减一</button>
</div>
`,
})

/*控制器 class,负责调度 model 和 view */
class Controller{
constructor({view ,model , events , init , ...rest}){
this.view = view
this.model = model
this.events = events
Object.assign(this,rest)
this.bindEvents()
//this.view.render(this.model.fetchDb())
init.apply()
}
bindEvents(){
this.events.map((e)=>{
$(this.view.el).on(e.type,e.selector,this[e.fn].bind(this)) //事件绑定 注意this需要使用外面的this
})
}
}

var controller = new Controller({
view : view,
model : model,
events:[
{type:'click',selector:'#add',fn:'add'},
{type:'click',selector:'#minus',fn:'minus'},
],
init(){
this.view.render(this.model.fetchDb());
},
add() {
this.model.data.number += 1
var newdata = this.model.saveDb(); //假设更新了,并从服务器获取了修改后的新数据
this.view.render(this.model.saveDb());

},
minus() {
this.model.data.number -= 1
if(this.model.data.number <0){
this.model.data.number = 0
}
var newdata = this.model.saveDb();
this.view.render(this.model.saveDb());

}

})

这么写,有一个bug ,每次更新数据,我们都要用 innerHtml 替换 #app里的东西,里面如果有 input 标签,用户的数据就无法保存

4.Vue的双向绑定

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/*创建一个model calss 负责存储数据、请求数据、更新数据*/
/*因为没有后台,默认bookdata就是从服务器传来的数据,由fethDb获取,saveDb更新 */
class Model {
constructor(options) {
this.data = options.data || {}
}
fetchDb() {
return this.data
}
saveDb() {
return this.data
}
}
var model = new Model({
data: {
"id": "1",
"name": "javascript高程",
"number": 2 //数量
}
})

/*把 View 换成 VUE*/
var view = new Vue({
el: '#app',
data: {
book: {
id:null,
name: '未命名',
number: 0,
},
n: 100

},
template: `
<div>
书名<span id="name" >{{ book.name }}</span>
数量<span id="number" > {{book.number}}</span>
<div>
<input type="text" v-model = 'n'>
<button id="add" v-on:click = 'add'>加一</button>
<button id="minus" v-on:click = 'minus'>减一</button>
</div>
</div>
`,
created() {

view.book= model.fetchDb()

},
methods: {
add() {
model.data.number += 1
console.log(model.saveDb().number)
this.book.number = model.saveDb().number; //假设更新了,并从服务器获取了修改后的新数据

},
minus() {
model.data.number -= 1
if (model.data.number < 0) {
model.data.number = 0
}
this.book.number = model.saveDb().number;

}
}
})

Vue 代替了 View,这就是 Vue 的名字和其读音的来历。

Vue 的双向绑定(也是 Angular 的双向绑定)有这些功能:

  1. 只要 JS 改变了 view.number 或 view.name 或 view.n (注意 Vue 把 data 里面的 number、name 和 n 放到了 view 上面,没有 view.data 这个东西), HTML 就会局部更新
  2. 只要用户在 input 里输入了值,JS 里的 view.n 就会更新。

这就像双向绑定:JS 数据与页面元素互相绑定。

Vue的双向绑定原理

使用了getter setter,缺点是无法监听不存在的属性

原理:

1
2
3
4
5
6
7
var data = {name:'aaa'}
var _name = 'zty'
Object.defineProperty(data,'name',{
get(){return _name}
set(value){ _name = value ; console.log('更新了name')} //在这里做一个拦截,我们就可以知道用户修改了某个值,比如说input
})
console.log(data.name)

但是假如之前没有在 data里声明,,vue就无法监听了

强制监听

vm.$set()

向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新属性,因为 Vue 无法探测普通的新增属性 (比如 this.myObject.newProperty = 'hi')

vm-set

模拟上面的set 和 get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class X{
constructor({data}){
for(let key in data){
Object.defineProperty(this, key, {
get (){
return data[key]
},
set (value){
console.log('有人修改了' + key)
data[key] = value
}
})
}
}
}

var view = new X({
data: {
name: 'frank'
}
})

console.log(view.name === 'frank') // 输出 true
view.name = 'jack' // 输出「有人修改了 name」

重构

1.代码优化基本原则

1.易读性优先

2.如果不是性能瓶颈,就不要为了性能改代码

3.复杂性守恒原则:无论你怎么写代码,复杂性都是不会消失的
推论:如果逻辑很复杂,那么代码看起来就应该是复杂的。如果逻辑很简单,代码看起来就应该是简单的。

2.命名

程序员三大难题

  1. 变量命名
  2. 缓存失效
  3. 循环边界

可见变量命名的重要性。

网上有很多命名规范,大家可以参考。这里只讲基本原则。

  1. 注意词性

    • 普通变量/属性用「名词」

      1
      2
      3
      4
      5
      6
      7
      var person = {
      name: 'Frank'
      }
      var student = {
      grade: 3,
      class: 2
      }
    • bool变量/属性用「形容词」或者「be动词」或者「情态动词」或者「hasX」

      1
      2
      3
      4
      5
      6
      var person = {
      dead: false, // 如果是形容词,前面就没必要加 is,比如isDead 就很废话
      canSpeak: true, //情态动词有 can、should、will、need 等,情态动词后面接动词
      isVip: true, // be 动词有 is、was 等,后面一般接名词
      hasChildren: true, // has 加名词
      }
    • 普通函数/方法用「动词」开头

      1
      2
      3
      4
      5
      var person = {
      run(){}, // 不及物动词
      drinkWater(){}, // 及物动词
      eat(foo){}, // 及物动词加参数(参数是名词)
      }
    • 回调、钩子函数用「介词」开头,或用「动词的现在完成时态」

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      var person = {
      beforeDie(){},
      afterDie(){},
      // 或者
      willDie(){}
      dead(){} // 这里跟 bool 冲突,你只要不同时暴露 bool dead 和函数 dead 就行,怕冲突就用上面的 afterDie
      }
      button.addEventListener('click', onButtonClick)
      var component = {
      beforeCreate(){},
      created(){},
      beforeMount(){},
      mounted(){},
      beforeUpdate(){},
      updated(){},
      activated(){},
      deactivated(){},
      beforeDestroy(){},
      destroyed(){},
      errorCaptured(){}
      }
    • 容易混淆的地方加前缀

      1
      2
      3
      4
      5
      div1.classList.add('active') // DOM 对象
      div2.addClass('active') // jQuery 对象
      不如改成
      domDiv1 或 elDiv1.classList.add('active')
      $div2.addClass('active')
    • 属性访问器函数可以用名词

      1
      2
      $div.text() // 其实是 $div.getText()
      $div.text('hi') // 其实是 $div.setText('hi')
  2. 注意一致性

    • 介词一致性
      如果你使用了 before + after,那么就在代码的所有地方都坚持使用
      如果你使用了 before + 完成时,那么就坚持使用
      如果你改来改去,就「不一致」了,不一致将导致「不可预测」

    • 顺序一致性
      比如 updateContainerWidth 和 updateHeightOfContainer 的顺序就令人很别扭,同样会引发「不可预测」

    • 表里一致性
      函数名必须完美体现函数的功能,既不能多也不能少。
      比如

      1
      2
      3
      4
      5
      function getSongs(){
      return $.get('/songs).then((response){
      div.innerText = response.songs
      })
      }

      就违背了表里一致性,getSongs 表示获取歌曲,并没有暗示这个函数会更新页面,但是实际上函数更新了 div,这就是表里不一,正确的写法是

      • 要么纠正函数名

        1
        2
        3
        4
        5
        function getSongsAndUpdateDiv(){
        return $.get('/songs).then((response){
        div.innerText = response.songs
        })
        }
      • 要么写成两个函数

        1
        2
        3
        4
        5
        6
        7
        8
        9
        function getSongs(){
        return $.get('/songs)
        }
        function updateDiv(songs){
        div.innerText = response.songs
        }
        getSongs().then((response)=>{
        updateDiv(response.songs)
        })
    • 时间一致性
      有可能随着代码的变迁,一个变量的含义已经不同于它一开始的含义了,这个时候你需要及时改掉这个变量的名字。
      这一条是最难做到的,因为写代码容易,改代码难。如果这个代码组织得不好,很可能会出现牵一发而动全身的情况(如全局变量就很难改)

改代码

如果你的代码有单元测试,那么改起来就很放心。如果没有单元测试,就需要用「小步快跑」的策略来修改。

小步快跑的意思是说,每次只修改一点点,测试通过后,再修改一点点,再测试,再修改一点点……如此反复。

那么如何修改一点点呢?《重构》这本书介绍了很多方法,但是讲得挺啰嗦的,如果你有时间可以看看。

我这里只说两个经久不衰的方法。

一、使用函数来改代码

步骤:

  1. 将一坨代码放到一个函数里
  2. 将代码依赖的外部变量作为参数
  3. 将代码的输出作为函数的返回值
  4. 给函数取一个合适的名字
  5. 调用这个函数并传入参数
  6. 这个函数里的代码如果超过 5 行,则依然有优化的空间,请回到第 1 步

二、使用对象来改代码

如果使用了函数改造法改造后,发现有太多的小函数,则可以使用对象讲这个函数串起来。

记得我们讲过「this 是函数和对象的桥梁」吗,我们会用 this 来串联这个对象和所有函数。

最终代码:http://js.jirengu.com/mimazaboke/1/edit?html,js,output

一些固定的套路

  1. 表驱动编程(《代码大全》里说的)
    所有一一对应的关系都可以用表来做

    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
    26
    27
    28
    //传入分数,返回等级
    function calculateGrade(score) {
    if(score>=90){
    return 'A'
    }else if(score>=80){
    return 'B'
    }else if(score>=70){
    return 'C'
    }else if(score>=60){
    return 'D'
    }else{
    return 'E'
    }
    }
    //表驱动编程
    function calculateGrade(score) {
    var table = {
    10 : 'A',
    9 : 'A',
    8 : 'B',
    7 : 'C',
    6 : 'D',
    others : 'E',
    }

    var level = parseInt(score/10 , 10)
    return table[level] || table['others']
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //同样也是表驱动编程
    //提供数据,提供对应关系,然后我在通过一些方法,一些循环搞定,不需要每个都写函数、
    function pre(){}
    function next(){}
    function cleartimer(){}
    function resettimer(){} //四个事件需要绑定到几个元素上
    var events = [
    {el:'.prebtn',event:'click',fn:pre},
    {el:'.nextbtn',event:'click',fn:next},
    {el:'.timer',event:'mouseenter',fn:cleartimer},
    {el:'.timer',event:'mouseleave',fn:resettimer},
    ];
    events.forEach((eventobject)=>{
    $(eventobject.el).on(eventobject.event,eventobject.fn)
    })

  2. 自说明代码(以 API 参数为例)
    把别人关心的东西放在显眼的位置

bad smell(坏味道)

有些代码可以用,但是很「臭」。

哪些代码是有坏味道的

  1. 表里不一的代码
  2. 过时的注释
  3. 逻辑很简单,但是看起来很复杂的代码
  4. 重复的代码
  5. 相似的代码
  6. 总是一起出现的代码

破窗效应

此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。一面墙,如果出现一些涂鸦没有被清洗掉,很快的,墙上就布满了乱七八糟、不堪入目的东西;一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。这个现象,就是犯罪心理学中的破窗效应。

程序员要做到:只要是经过你手的代码,都会比之前好一点。

HTTP

HTTP

1.HTTP为什么重要

  1. HTTP 是前后端合作的重要方式
    99%的需求都是通过 HTTP 做到,小部分需求可以通过 WebSockets 做到。
  2. HTTP 能帮你从本质上理解 HTML、CSS、JS、图片、JSON、JSONP 等不同形式的资源
  3. Web 性能优化基本等价于对 HTTP 传输效率优化
  4. 前端工程化需要你对 HTTP 缓存有深入了解

2.什么是HTTP

####1.四个概念

  1. server
  2. client
  3. request
  4. response
  • server

    服务器端

  • client

    客户端

2.request 请求

请求包含四个部分

  1. 动词 路径 协议/版本号 — GET ./style.css HTTP/1.1

  2. 一些 key : value ,用回车分割

    Host : xxx.xxx.xxx

    Connection : keep-alive

    Content-Type : application/javascript

    User-Agent : xxxxxxxxxxx

    Accept : xxxxx

  3. 回车,作用:分割第二部分和第三部分

  4. 什么都行,但是内容的格式必须要在第二部分的 Content-Type中声明

3.response 响应

响应也包含四个部分

  1. 协议/版本号 状态码 状态信息 HTTP/1.1 200 OK
  2. 一些 key : value ,用回车分割
  3. 回车,作用:分割第二部分和第三部分
  4. 什么都行,但是内容的格式必须要在第二部分的 Content-Type中声明

3.HTML,CSS,JS,JSON,JSONP都是什么?

1.HTML,CSS,JS的本质

本质是字符串,只是 Content-Type不同

  • HTML Content-Type : text/html
  • CSS Content-Type : text/css
  • JS Content-Type : application/javascript

注意 url 里的后缀是废话,毫无意义。

  1. 浏览器通过地址栏、iframe 来请求 HTML
  2. 浏览器通过 link 标签获取 CSS,然后渲染
  3. 浏览器通过 script 标签获取 JS,然后执行
  4. 浏览器通过 image 标签获取图片,然后展示

2.JSON

json也是字符串

JSON Content-Type : application/json

3.JSONP

本质就是字符串,只不过

  1. Content-Type 为application/javascript 或者 text/javascript
  2. 内容格式为 functionName( {"format": "JSON"} )

4.缓存

  1. 使用 Cache-Control 缓存是常用的缓存方式
  2. 想要更新缓存只需稍微变更一下 url
    1. app-2131312362387123.js
    2. app.js?v=1
    3. app.js?t=201801012334
    4. 不要缓存入口页面,这是你更新资源的唯一入口

并不是缓存

Etag 和 304 会避免下载,但是还是会发出请求,并不属于缓存

因为HTTP协议是 无状态 的,通俗来说是 没脑子,它没有记忆。

假如用户登录成功后,刷新页面,用户的登录状态就没有了,这里可以用 Cookie 解决。

保存登录状态

jack登录a.com发出请求 uername:jack ; password : 123 —->

后台接收到数据,账号密码正确,发出响应,响应头header写上'Set-Cookie','login = jack'—–>

浏览器收到响应,种上 Cookie —->

当jack再次访问a.com的其他页面时候,请求头中会带上Cookie : login=jack——>

jack发现他还是登录状态,非常高兴 : ) —– end

区分用户

frank 这时候也登录 a.com 这时候他获得的 响应头就是'Set-Cookie','login = frank'了,这样我们也能区分jack 和 frank 了

但是Cookie有一个很不安全的地方,任何一个前端都可以使用document.cookie = xxx来伪造。

我们需要一个方法既需要能标识用户,又可以防止前端伪造

2.Session

因为Cookie不安全,所以出现了Session

Session是存在数据库的,因此前端无法修改。

流程

用户输入账户密码登录 ——> 发送请求 ———> 服务器验证账户密码正确———>

分配一个session,通过cookie告诉浏览器———>Set-Cookie,sessionId = 'xxxxxxxxxxxxx'

剩下的就是和Cookie一样了。

3.两者不同

  1. 使用时间:用Cookie分辨两个不同的用户,用Session存放用户的敏感数据。
  2. 实现方式:Cookie使用Set-Cookie实现,Session你想怎么实现就怎么实现。
  3. 存放方式:Cookie存放在浏览器里,Session存在服务器端的文件,数据库,内存等等。
  4. 两者关系:Session一般基于Cookie,存在里面。但也可以放到 localStorage 里面,放到 URL 里等等

Vue 动画

Vue动画

1.简单例子

1
2
3
4
5
6
7
8
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
1
2
3
4
5
6
new Vue({
el: '#demo',
data: {
show: true
}
})
1
2
3
4
5
6
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

该例子可以实现 p 标签的渐隐效果。

2.使用CSS添加稍微多一点动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.test{
width:100px;
border:1px solid red;
text-align: center;
}
.slide-enter-active,.slide-leave-active{
transition:all 1s;
}
.slide-enter{
opacity: 0;
transform:translateX(100px)
}
.slide-leave-to{
opacity: 0;
transform:translateX(-100px)
}

实现了一个类似轮播感觉的动画。

# 使用 animation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.test {
width: 100px;
border: 1px solid red;
text-align: center;
}
.slide-enter-active {
animation: bounce-in 2s
}
.slide-leave-active {
animation: bounce-in 2s reverse
}
@keyframes bounce-in {
0% {
transform: scale(0)
}
50% {
transform: scale(2)
}
100% {
transform: scale(1)
}
}

一个 Scale-in-out 的动画效果

3.使用 Animate.css

动画这个东西写起来还是比较复杂的,但是有大牛帮我们写好了。

1
2
3
4
5
6
7
8
9
10
11
12
<!--  <link href="https://cdn.bootcss.com/animate.css/3.5.2/animate.css" rel="stylesheet"> -->
<div id="demo">
<button v-on:click="visable = !visable">
Toggle
</button>
<transition
name="custom-classes-transition"
enter-active-class="animated tada"
leave-active-class="animated bounceOutRight">
<p class=test v-if="visable">hello</p>
</transition>
</div>

这样不用写 CSS 了,这里的 name 可写可不写。

但是标签里的 过度类名 和之前不一样了:

  • enter-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (2.1.8+)

各种效果预览:

4.使用 JavaScript 钩子

有时候我们需要在动画的过程中做一些事情,这就需要钩子函数了。

首先在属性中声明钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>

然后在实例中的 methods 中:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ...
methods: {
// --------
// 进入中
// --------

beforeEnter: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},

// --------
// 离开时
// --------

beforeLeave: function (el) {
// ...
},
// 此回调函数是可选项的设置
// 与 CSS 结合时使用
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}

只用 JavaScript 过渡的时候,在 enter 和 leave 中必须使用 done 进行回调

否则,它们将被同步调用,过渡会立即完成。

# 使用 Velocity.js

这是一个很出名的使用 JavaScript 做动画的库,与其自己写,不如直接用他的。

(当然,我们一般用CSS )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="example-4">
<button @click="show = !show">
Toggle
</button>
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:leave="leave"
v-bind:css="false"
>
<p v-if="show">
Demo
</p>
</transition>
</div>
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
26
new Vue({
el: '#example-4',
data: {
show: false
},
methods: {
beforeEnter: function (el) {
el.style.opacity = 0
el.style.transformOrigin = 'left'
},
enter: function (el, done) {
Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
Velocity(el, { fontSize: '1em' }, { complete: done })
},
leave: function (el, done) {
Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
Velocity(el, {
rotateZ: '45deg',
translateY: '30px',
translateX: '30px',
opacity: 0
}, { complete: done })
}
}
})

5.多个元素的过度

上面我们一直在写单个元素的动画,涉及到多个元素,该如何操作呢?

1
2
3
4
5
6
new Vue({
el: '#demo',
data: {
status: 'on'
}
})
1
2
3
4
5
6
<div id="demo">
<transition name='slide' mode = "out-in">
<button v-if="status === 'off'" key="on" @click="status='on'">on</button>
<button v-else="status === 'on'" key="off" @click="status='off'">off</button>
</transition>
</div>
  • name : 类名
  • mode : in-out, out-in
  • key: 两个元素的时候一定要加上 key 属性,里面写啥不重要,但是一定要写上,并且不重复
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#demo {
position: relative;
}
button {
position: absolute;
}
.slide-enter-active,
.slide-leave-active {
transition: all 1s;
}
.slide-enter {
opacity: 0;
transform: translateX(100px)
}
.slide-leave-to {
opacity: 0;
transform: translateX(-100px)
}

6.组件之间的过度

1
2
3
4
5
6
7
<div id="demo">
<button @click="temp='v-a'">A</button>
<button @click="temp='v-b'">B</button>
<transition name='slide' mode="out-in">
<component v-bind:is='temp'></component>
</transition>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
new Vue({
el: '#demo',
data: {
temp: 'v-a'
},
components: {
'v-a': {
template: '<div>Component A</div>'
},
'v-b': {
template: '<div>Component B</div>'
}

}
})
1
2
3
4
5
6
7
8
9
10
11
12
.slide-enter-active,
.slide-leave-active {
transition: all 1s;
}
.slide-enter {
opacity: 0;
transform: translateX(100px)
}
.slide-leave-to {
opacity: 0;
transform: translateX(-100px)
}

组件之间的过度不需要 key

CSS-宽度高度,对齐

CSS

记住:能不用 height width 就不用他们,使用 padding , line-height 来替代

1.中文对齐

  • 当我要写一个表单,有姓名联系方式,需要姓和联对齐,名和式对齐。如何去做?
1
2
3
<span>姓名</span>
<br>
<span>联系方式</span>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
span{
display:inline-block;
width:5em;
text-align:justify;
font-size:20px;
line-height:20px;
height:20px;
overflow:hidden
}
span::after{
content:'';
display:inline-block;
width:100%;
}
  • 当我有很多文字,靠到最右边要换行了,发现文字和最右边有间距怎么办?

    text-align:justify

2.空格问题

HTML会把两个 inline 元素之间看不见的字符都替换为一个空格,包括空格、TAB、回车。

所以有时候就会莫名其妙中间多一个空格,因为代码里有回车。

解决方法:不用 inline 元素。

3.清除浮动

在浮动元素的爸爸身上加一个 class

1
2
3
4
5
clearfix::after{
content:'';
display:block;
clear:both;
}

4.文字中断

有时候文字太长了超出屏幕了,两种解决方式。

1
<span>helooooooooooooooooooooooooooooooooooooooooooooooooo</span>

上面这种情况下浏览器会认为这是一个单词,一个单词是不会自动换行的。

可以加分隔符

1
<span>helooooooo-ooooooooo-ooooooooo-oooooooo-ooooooo-ooooo-oooo</span>

可以使用 word-break

1
span { word-break:break-all }

5.文字溢出省略

当文字的长度超出了 div 的宽度,多余的部分,我们想用省略号代替。

  • 一行的时候
1
2
3
4
5
div{
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
  • 多行
1
2
3
4
5
div{
display: -webkit-box;
-webkit-line-clamp: 3; /*3行写3,n行写n */
-webkit-box-orient: vertical;
}

6.文字垂直居中

Div 的高度是跟随你文字的行数变化的,所以永远不要使用 height 给 Div 高度

那假如我想要 div 的高度是40px , 怎么办? 使用 line-height 和 padding

1
2
3
4
5
div{
line-height:24px;
padding:8px 0;
text-align:center;
}

24 + 8+ 8 = 40

7.margin 合并

当子元素有 margin 的时候, 父元素如果没有“挡住”它的属性,那么就会出现父子margin 合并。

解决方案 border/ padding/ overflow(不推荐)

1
2
3
4
5
.dad{
border:0.1px solid white;
padding:0.1px;
overflow:hidden
}

8.div 的高度怎么确定的?

div 的高度是由文档流中所有素高度的总和决定的。

文档流:内联元素从左到右,块级元素从上到下,块级元素总是另起一行。

所以脱离文档流的翻译就是:算高度别算我。(position:absolute,fixed ; float; )

9.内联元素的宽度和高度

宽度是受 文字的多少 和 margin padding boder 影响。

高度只受行高决定。

10.DIV水平垂直居中

高度不定,宽度不定的div水平垂直居中 flex

1
2
3
4
5
6
7
8
.dad{
display:flex;
justify-content:center;
align-items:center
}
.son{
...
}

11.写一个 1:1 的 div

使用padding撑起高度。

1
2
3
.one{
padding-top:100%;
}

CSS-堆叠上下文

堆叠上下文

1.堆叠顺序

div是有空间层次关系的。

原则:相同类型的元素,后出现的会覆盖之前的。

层次顺序(底层-上层):

  1. background
  2. border
  3. 块级
  4. 浮动
  5. 内联
  6. z-index : 0 ( position :relative , absolute)
  7. z-index : +

注意:z-index 只有在 position 不是 static 的时候才有用,比如 relative , absolute

2.怎么样形成堆叠上下文

可以理解为堆叠作用域。跟 BFC 一样,我们只知道一些属性会触发堆叠上下文,但并不知道堆叠上下文是什么。

  • 根元素 (HTML),
  • z-index 值不为 “auto”的 绝对/相对定位,
  • 一个 z-index 值不为 “auto”的 flex 项目 (flex item),即:父元素 display: flex|inline-flex,
  • opacity 属性值小于 1 的元素(参考 the specification for opacity),
  • transform 属性值不为 “none”的元素,
  • mix-blend-mode 属性值不为 “normal”的元素,
  • filter值不为“none”的元素,
  • perspective值不为“none”的元素,
  • isolation 属性被设置为 “isolate”的元素,
  • position: fixed
  • 在 will-change 中指定了任意 CSS 属性,即便你没有直接指定这些属性的值(参考 这篇文章)
  • -webkit-overflow-scrolling 属性被设置 “touch”的元素

单元测试

单元测试

1.BDD&&TDD

  1. BDD( Behavior-Driven-Development ):行为驱动开发 , 主要是设计一些行为,进行对代码的测试

    1
    2
    3
    4
    5
    describe('Button',()=>{  //Mocha语法,下面会介绍Mocha
    it('存在',()=>{
    //测试
    })
    })

    这样的写法,我们甚至可以读出来, 描述 Button , 存在.

  2. TDD(Test-Driven-Development): 测试驱动开发,自动化单元测试来推动软件设计并强制依赖关系解耦的技术,可以单独进行.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const expect = chai.expect
    {
    const Construstor = Vue.extend(Button);
    const vm = new Construstor({
    propsData:{
    icon : 'setting'
    }
    });
    vm.$mount();
    let useElement = vm.$el.querySelector('use');
    let href = useElement.getAttribute('xlink:href');
    expect(href).to.eq('#i-setting'); // 期望 href === '#i-setting' chai.js语法
    vm.$el.remove();
    vm.$destroy();
    }

2.chai.js

chai.js 是专门用来执行单元测试的一个库 , 有 assert(断言) , should(应该) , expect(期望) 三种语法.

expect常用语法:

1
2
3
4
5
const expect = chai.expect;
expect(foo).to.be.a('string');
expect(foo).to.equal('mount');
expect(foo).to.have.lengthOf(3);
expect(foo).to.have.property('flavors').with.lengthOf(3);

语法很简单,详情查阅文档

3.使用 Karma + Mocha做单元测试

1.工具

  1. Karma(卡玛)是一个测试运行器,它可以呼起浏览器,加载测试脚本,然后运行测试用例

  2. Mocha(摩卡)是一个单元测试框架库,它用来写测试用例

  3. Sinon(西农) 是一个 spy/mock/stub 库.

    安装工具

    npm i -D karma karma-chrome-launcher karma-mocha karma-sinon-chai mocha sinon sinon-chai karma-chai karma-chai-spies

2.步骤

  1. 创建Karma配置

    里面有两个关键部位已经标出

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    // 新建 karma.conf.js,内容如下
    module.exports = function (config) {
    config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',
    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['mocha', 'sinon-chai'],
    client: {
    chai: {
    includeStack: true
    }
    },


    // list of files / patterns to load in the browser
    files: [
    'dist/**/*.test.js',
    'dist/**/*.test.css'
    ],


    // list of files / patterns to exclude
    exclude: [],


    // preprocess matching files before serving them to the browser
    // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
    preprocessors: {},


    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['progress'],


    // web server port
    port: 9876,


    // enable / disable colors in the output (reporters and logs)
    colors: true,


    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes
    autoWatch: true,


    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: ['ChromeHeadless'],


    // Continuous Integration mode
    // if true, Karma captures browsers, runs the tests and exits
    singleRun: false,

    // Concurrency level
    // how many browser should be started simultaneous
    concurrency: Infinity
    })
    }
    1. 创建 test/button.test.js 文件

      • $mount()

        如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。

        如果没有提供 elementOrSelector 参数,模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。

        这个方法返回实例自身,因而可以链式调用其它实例方法。

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
const expect = chai.expect;
import Vue from 'vue'
import Button from '../src/button'

Vue.config.productionTip = false
Vue.config.devtools = false

describe('Button', () => {
it('存在.', () => {
expect(Button).to.be.ok
})
it('可以设置icon.', () => {
//使用extend方法,将Button对象构造成一个构造函数
const Constructor = Vue.extend(Button)
//然后通过构造函数实例化Button
const vm = new Constructor({
propsData: {
icon: 'settings'
}
}).$mount() //使用$mount()挂载,详情看上面
const useElement = vm.$el.querySelector('use')
expect(useElement.getAttribute('xlink:href')).to.equal('#i-settings')
vm.$destroy()
})
it('可以设置loading.', () => {
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
loading: true
}
}).$mount()
const useElements = vm.$el.querySelectorAll('use')
expect(useElements.length).to.equal(1)
expect(useElements[0].getAttribute('xlink:href')).to.equal('#i-loading')
vm.$destroy()
})
it('icon 默认的 order 是 1', () => {
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
}
}).$mount(div)
const icon = vm.$el.querySelector('svg')
expect(getComputedStyle(icon).order).to.eq('1')
vm.$el.remove()
vm.$destroy()
})
it('设置 iconPosition 可以改变 order', () => {
const div = document.createElement('div')
document.body.appendChild(div)
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
iconPosition: 'right'
}
}).$mount(div)
const icon = vm.$el.querySelector('svg')
expect(getComputedStyle(icon).order).to.eq('2')
vm.$el.remove()
vm.$destroy()
})
it('点击 button 触发 click 事件', () => {
const Constructor = Vue.extend(Button)
const vm = new Constructor({
propsData: {
icon: 'settings',
}
}).$mount()

const callback = sinon.fake(); //使用间谍函数,捕获是否进行了回调
vm.$on('click', callback) //
vm.$el.click()
expect(callback).to.have.been.called //

})
})

3.创建测试脚本

在package.json里找到 scripts

1
2
3
4
"scripts": {
"test": "parcel build test/* --no-minify --no-cache && karma start --single- run",
"dev-test": "parcel watch test/* --no-cache & karma start",
},

解释一下

  • parcel build使用parcel 打包
  • test/* test文件夹下面的所有文件
  • --no-minify 不懂,不加会出错,加上
  • --no-cache 不从缓存;里运行,有缓存也容易出错
  • karma start karma启动
  • --single-run 只运行一次
  • parcel watch parcel一直盯着,有改动就打包
  • karma strat 后面没加别的,一直运行

    4.运行测试脚本

npm run test

npm run dev-test

4.持续测试

​ 使用 Travis Ci 来进行持续测试 , 就是我们把测试跑到他的服务器上,每提交一次仓库 ,就执行一次测试.

  1. 在目录中添加 .travis.yml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    language: node_js   //指定语言 : nodejs
    node_js: //指定版本,可以做多版本测试哦,很强很牛逼
    - "8"
    - "9"
    - "10"
    addons:
    chrome: stable //使用chrome测试
    sudo: required // 需要管理员权限
    before_script: //不懂,不加可能有BUG
    - "sudo chown root /opt/google/chrome/chrome-sandbox"
    - "sudo chmod 4755 /opt/google/chrome/chrome-sandbox"
  2. 将代码提交到GitHub

  3. Travis 上使用 GITHub登录,然后将你要测试的项目添加进去

  4. 等着就完事儿了

Vue-Router

Vue-Router

1.简单配置

创建router.js

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import Router from 'vue-router'
import index from './components/index'
import user from './components/user'
Vue.use(Router)
export default new Router({
routes:[
{path:'/',component:index},
{path:'/user',component:user},
]
})

在main.js里加入根实例

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false;

new Vue({
render: h => h(App),
router
}).$mount('#app')

在App.vue里使用

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div id="app">
<router-link to="/">index</router-link>
<router-link to="/user">info</router-link>
<router-view></router-view>
</div>
</template>

<script>
export default {
name: 'app',
}
</script>

2.基础用法

1.动态路由匹配
1
2
3
4
5
6
7
//router.js
export default new Router({
routes:[
{path:'/',component:index},
{path:'/user/:id',component:user},
]
})

可以以 冒号 标记参数,该参数可以通过 this.$router.params.id获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- user.vue -->
<template>
<div>id:{{id}}</div>
</template>

<script>
export default {
name: "info",
computed:{
id(){
return this.$route.params.id
}
},
}
</script>

响应路由参数的变化

提醒一下,当使用路由参数时,例如从 /user/1 导航到 /user/2原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用

复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
name: "info",
computed:{
id(){
return this.$route.params.id
}
},
watch:{
'$route'(to,from){
//(到哪去,从哪来)
//对路由变化做出响应
}
}
}

或者使用beforeRouteUpdate(to,from,next)守卫

2.嵌套路由 children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default new Router({
routes: [
{
path: '/',
component: index
},
{
path: '/user/:id/',
component: user,
children: [
{
path: 'profile',
component: profile
}
]
},
]
})

加上 children 以后不要以为万事大吉了,需要在 user 里面加上 router-view 标签,不然无法展示。

1
2
3
4
5
6
7
<template>
<div>
id:{{$route.params.id}}
<router-view></router-view>
</div>

</template>

要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。

你会发现,children 配置就是像 routes 配置一样的路由配置数组,所以呢,你可以嵌套多层路由。

3.编程式的导航

之前我们写导航是这么写的:<router-link to="/user/1/profile"></router-link>,这叫做声明式的导航。

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

route.push(location,onComplete?,onAbort?)

注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)

声明式 编程式
<router-link :to="..."> router.push(...)

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

1
2
3
4
5
6
7
8
9
10
11
// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: 123 }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:

1
2
3
4
5
const userId = 123
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

同样的规则也适用于 router-link 组件的 to 属性。

在 2.2.0+,可选的在 router.pushrouter.replace 中提供 onCompleteonAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。

注意:如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。

route.replace(location,onComplete?,onAbort?)

和 push 很像,唯一不同的是,它不会向 history 添加记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。 这意味着,浏览器上的后退按钮将不会后退到前一个页面。

声明式 编程式
<router-link :to="..." replace> router.replace(...)

router.go(n)

参数 n 是整数,意思是在 histroy 记录中向前或者向后几步。类似 window.history.go(n)

1
2
3
4
5
6
7
8
9
10
11
12
// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)
4.命名路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
routes: [
{
name:'index',
path: '/',
component: index
},
{
name:'user',
path: '/user/:id',
component: user,
children: [
{
name:'profile',
path: 'profile',
component: profile
}
]
},
]

加上 name 会使我们在 push 和 to 操作的时候更加方便

route.push({name:'user',params:{id:123}})

<router-link :to='{name:"user",params:{id:123}}'></router-link>

5.命名视图

当我们想在一个页面中展示两个视图组件的时候就用到命名视图了。

首先,在 router.js里修改。

1
2
3
4
5
6
7
8
{
name:'index',
path: '/',
components: { //component ---> components
default:index, //默认index
a:profile // a是profile
}
},

在视图里增加入口。

1
2


这样就可以展示两个视图组件了。

嵌套命名视图

简单的来说就是children里有命名视图

比如,我想在 /user/123/profile 里展示 profile 和 photo ,就像下面这么写。这样,user,profile,photo 这些视图组件就都在一个页面里了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
name:'user',
path: '/user/:id',
component: user,
children: [
{
name:'profile',
path: 'profile',
components: {
default: profile,
a:photo
}
}
]
},
1
2
<router-view></router-view>
<router-view name="a"></router-view>
6.重定向

重定向的三种方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const router = [
{
name:'foo'
path:'/a',
redirect:'/b'
},
{
path:'/c',
redirect:'{name:foo}'
},
{
path:'/a',
redirect:to=>{
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}
}
}
]

注意导航守卫并没有应用在跳转路由,上而仅仅应用在其目标上 ,第下面例子中,为 /a 路由添加一个 beforeEachbeforeLeave 守卫并不会有任何效果。

1
2
3
4
5
6
7
8
const router = [
{
name:'foo'
path:'/a',
redirect:'/b',
beforeLeave(to,from,next){}
}
]
7.别名

当我们想从/a跳转到/b,但是又不想改变URL,就用到了别名alias

1
2
3
4
5
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})

/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

8.路由组件传参
  • $route

比如我们要展示用户 alan 的个人主页,路由是/user/alan/profile , 可以通过 $route.params.id

1
<div>{{$route.params.id}}</div>
  • props 布尔模式

在路由配置中写入props:true,这样route.params会被设置为组件属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
name:'user',
path: '/user/:id',
component: user,
props:true,
children: [
{
name:'profile',
path: 'profile',
components: {
default: profile,
a:index
},
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
props: {default:true,a:false}
}
]
},
1
2
3
4
5
6
7
8
9
10
<template>
<div>
{{id}}
</div>
</template>
<script>
export default{
props:['id']
}
</script>
  • props 对象模式
1
2
3
4
5
6
7
8
routes: [
{
name:'index',
path: '/',
props:{username:'map_name'},
component:index
},
]
1
2
3
4
5
6
7
8
9
10
<template>
<div>
{{username}}
</div>
</template>
<script>
export default{
props:['username']
}
</script>

注意,此时的 props 必须是静态的。

  • props 函数模式

这样就可以动态生成 props ,比如Url 是 /user/:id/postId/:postId,我现在想把所有参数传进去

1
2
3
4
5
6
7
8
routes: [
{
name:'user',
path: '/user/:id/postId/:postId',
props: (route) => ({ params: route.params }) ,
component:User
},
]
1
2
3
4
5
6
7
8
9
10
<template>
<div>
{{params.id}}--{{params.postId}}
</div>
</template>
<script>
export default{
props:['params']
}
</script>

Vue--小知识

1.Vue的双向绑定其实是单向绑定

双向绑定的假设

假设,我有两个儿子,大儿子和二儿子

我有500元

大儿子要花500买衣服,把钱花了

二儿子要花200买书籍,钱没了。

原因:大儿子花钱的时候没有告诉我。

所以:我定了规矩,以后花钱必须先跟我说,然后我再把钱给你!

  • 因此,Vue的 v-model 变成了两个单项绑定组成的双向绑定,把 v-model 拆分可以变成这样
  • 现在我加一个子组件child ,有一个属性 selected ,通过 button 切换值
  • 这样能改,但是会报出警告,告诉你别在子组件里自己改,你要通过父亲修改(上面的假设)

​ 所以我改成这样

这样子组件的修改需要通过父组件,流程

Click button =>

触发@click事件=>

$emit触发当前实例上的事件“xxx”,传递$events=>

父组件监听 xxx方法,并将 value = $events =>

data中的value改变 =>

selected改变为当前value值

  • 上面的实例更加证明了,Vue是单向数据流

    但是双向绑定很爽,于是作者给了我们一个语法糖 .sync语法糖

原理写在下面注释里了,其实还是 监听了 @updata:selected 方法

2.created()和mounted() 父子传参

1
2
var div = document.createElement('div') //这是created ,将div写入内存
document.body.appendChild(div) //这是mounted ,将div挂到页面里

问题来了,如果div里有子元素,那么是什么顺序呢?

1
2
3
4
var div = document.createElement('div') // div created
var chilid = document.createElement('div') // child created
document.body.appendChild(chilid) // child mounted
document.body.appendChild(div) // div mouted

所以我们可以看到顺序是

创建父亲 —-> 创建儿子 —> mounted儿子 —>mounted 父亲

因此,得到在父子组件传参的时候,父亲的mouted()里面,一定能拿到所有儿子:this.$children

3.data和computed

当一个属性需要跟随另一个属性变化的时候使用 computed

data里的属性不会跟随其他属性变化

4.Vue开发插件

在写Toast组件的时候使用到了这个方法。

Vue.js的插件应当有一个公开方法install,第一个参数是vue,第二个参数是可选的选项对象options

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
26
27
28
//Plugin.js
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
// 逻辑...
}

// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})

// 3. 注入组件
Vue.mixin({
created: function () {
// 逻辑...
}
...
})

// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}

然后在 app.js 里注册

1
2
3
import Vue from 'vue'
import Plugin from './plugin'
Vue.use(Plugin,{someOptions:true})

然后就可以使用了

5.Element.getBoundingClientRect()

返回Element的大小以及相对与视口的位置。

在使用vm.$el.style.height获取不到高度等属性的时候可以尝试使用它

文档

6.ref

在Toast里使用了ref。

在普通的DOM 元素上使用,引用指向的就是 DOM 元素

1
2
3
4
5
6
7
<template> 
<div>
<p ref='test'>
123
</p>
</div>
</template>
1
2
//获取	
this.$refs.test // <p>123</p> 获取node元素

文档

如果用在子组件上,引用就指向组件实例:

1
<child ref='child'></child>

7.vm.$slots

1
2
3
<div slot = 'header'>
header
</div>
1
vm.$slot.header//header

文档

8.元素放到最后面

1
.a{margin-left:auto}

9.EventBus

当我们写父子传参,爷儿孙传参的时候,可以使用EventBus,会很方便.

因为Vue是不允许儿子改变父亲的值,只能告诉父亲我要改了,父亲再改

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//爷爷组件
import Vue form 'vue'
export default{
name:'爷爷',
props:['selected'], //当前选择的tab
data(){
return{
eventBus:new Vue() //通过data生成一个eventBus
}
},
provide(){
return {
eventBus:this.eventBus //使用provide给所有子组件注入这个值
}
},
mounted(){
this.eventBus.$emit('update:selected',this.selected) //初始化,也可以不初始化,看实际代码需不需要
}
}
}
//子组件1
export default{
name:'儿子1号',
inject:['eventBus'],
props:['name']
created(){
this.eventBus.$on('update:selected',(name)=>{
console.log(name) //儿子1号监听事件.
})
},
methods:{
change(){
this.eventBus.$emit('update:selected',this.name) //儿子1号主动触发事件
}
}
}
//子组件2
export default{
name:'儿子2号',
inject:['eventBus'],
props:['name']
created(){
this.eventBus.$on('update:selected',(name)=>{
console.log(name) //儿子2号监听事件.
})
},
}
//孙子组件同理
//........
//........
  1. 创建一个EventBus为啥要用 new Vue()呢?

    因为我们使用了 $on $emit,只要符合这个条件就可以了,当然有时候还要用到$off

  2. EventBus的作用(不仅是父子,还有子子)

    我们在爷爷里声明了EventBus后,他就是我们的一个数据处理中心,例如上面的例子

    儿子1号通过EventBus触发update事件:selected='儿子1号',

    与此同时,儿子2号一直在监听update事件,他就知道了,现在selected === ‘儿子一号’(子组件之前的传递),

    与此同时,爷爷也在监听update事件(父子传递),

    与此同时,孙子也在监听update事件(父子传递),

  3. EventBus 永远是 让爸爸更新自己,永远不要自己更新自己,让爸爸操控所有的事情,参考collapse组件的eventBus。

10.vue 里面绑定 class

推荐使用对象加数组的形式。

计算属性里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
computed:{
itemclass(){
return `position-${this.position}`
}
}
computed:{
someClasses(){
return [`position-${this.position}`,`text-${this.text}`]
}
}
computed:{
someClasses(){
return {[`position-${this.position}`]:true,[`text-${this.text}`]:true}
}
}

template里直接放进 :class 里就行。

11.组件的递归

这个需求是做多级联动选择是出现的,因为你无法判断用户提供给你的数组的深度,有可能是5层,也有可能是6层,这时候就用到了组建的递归。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div>
{{source.item}}
<cascader v-if='source.children'
v-for='item in source.children'
:source='item'>
</cascader>
</div>
</template>
<script>
export default{
name:'cascader',
props:{
source:{
type:Object
}
}
}
</script>
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
26
27
28
29
30
// source 格式 这里是三层 省-市-区
source:[
{
name:'山东',
children:[
{
name:'济南',
children:[
{
name:'市中区'
},
{
name:'历下区'
}
]
},
{
name:'淄博',
children:[
{
name:'张店区'
},
{
name:'临淄区'
}
]
}
]
}
]

注意:该组件 template 中使用的标签名要和 name 相同,这样 Vue 才能知道这是一个递归组件。

该组件,接收一个类型为 Object 的 source 数据,展示 source.item 。同时在组件里面继续调用自己,并进行三个操作。

1. 判断是否还有下一层 children。
   2. 循环下一层。
   3. 将下一层的数据绑定到source。

这样就形成了组件的复用。

12.轮子中写函数(名字没想好,瞎起的)

在制作级联组件的时候遇到一个需求,就是用户需要ajax传输信息,用户获取了数据,怎么能放到组件上应用呢?这个就是需要轮子制作者做的了。

用户使用时需要做的事

  1. 定义一个函数,获取数据后通过 callback 调用
  2. 在标签上绑定定义的函数,传给子组件,传过去的值就是一个 Function
1
2
3
<template>
<div :load-data='loadData'></div>
</template>
1
2
3
4
5
6
7
methods:{
loadData(callback){
axios.get('xxx/data.php',(res)=>{
callback(res)
})
}
}

我需要做的事情

  1. 定义好 props
  2. 定义好 callback 函数
  3. 调用 this.loadData(callback)
1
2
3
4
5
6
7
8
9
10
11
12
13
export default {
props:{
loadData:{
type:Function
}
},
methods:{
resovleData(){
let callback = (result)=>{return result+'some'};
this.loadData(callback)
}
}
}

回调 : 把别人给我的函数调用一下。

总结:其实就是用户再前面写好获取数据的方法,然后把这个方法传到子组件,这样我们就能在子组件中获取到用户的数据,然后处理数据。