tabs组件

base on jQuery(基于jquery,否则请别继续)

tabs组件

HTML

1
2
3
4
5
6
7
8
9
10
11
12
<div class="tabs">
<ol class="tabs-bar">
<li>1</li>
<li>2</li>
<li>3</li>
</ol>
<ol class="tabs-content">
<li>content 1</li>
<li>content 2</li>
<li>content 3</li>
</ol>
</div>

CSS

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
.tabs{}
.tabs > ol{
list-style: none;
margin: 0; padding: 0;
}
.tabs > ol.tabs-bar{
display: flex;
border-bottom: 1px solid red;
}
.tabs > ol.tabs-bar > li{
padding: 4px 8px;
border: 1px solid transparent;
border-bottom: 0;
}
.tabs > ol.tabs-bar > li:hover{
border-color: red;
}
/* 选中项显示边框 */
.tabs > ol.tabs-bar > li.active{
border-color: blue;
}
/* 内容默认隐藏 */
.tabs > ol.tabs-content > li{
display: none;
}
/* 选中项显示内容 */
.tabs > ol.tabs-content > li.active{
display: block;
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
$('.tabs').each(function(index,element) {
$(element).children('.tabs-bar').children('li').eq(0).addClass('active')
$(element).children('.tabs-content').children('li').eq(0).addClass('active')
})

$('.tabs').on('click', '.tabs-bar > li', function(e) {
var $li = $(e.currentTarget)
$li.addClass('active').siblings().removeClass('active')
var index = $li.index()
//从当前选中的li往外找父级元素 而不是 $('.tabs .tabs-content>li') 因为可能有多个 tabs组件
var $content = $li.closest('.tabs').find('.tabs-content>li').eq(index)
$content.addClass('active').siblings().removeClass('active')
})

但是这样只是页面上没法复用

面向对象的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
function Tabs(selector) {
this.elements = $(selector)
this.init()
this.bindEvents()
}

Tabs.prototype.init = function() {
this.elements.each(function(index, element) {
$(element).children('.tabs-bar').children('li').eq(0).addClass('active')
$(element).children('.tabs-content').children('li').eq(0).addClass('active')
})
}

Tabs.prototype.bindEvents = function() {
this.elements.on('click', '.tabs-bar > li', function(e) {
var $li = $(e.currentTarget)
$li.addClass('active').siblings().removeClass('active')
var index = $li.index()
var $content = $li.closest('.tabs').find('.tabs-content>li').eq(index)
$content.addClass('active').siblings().removeClass('active')
})
}

var tabs = new Tabs('.tabs')

ES6写法(class)

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 Tabs {
constructor(selector) {
this.elements = $(selector)
this.init()
this.bindEvents()
}
init() {
this.elements.each(function(index, element) {
$(element).children('.tabs-bar').children('li').eq(0).addClass('active')
$(element).children('.tabs-content').children('li').eq(0).addClass('active')
})
}
bindEvents() {
this.elements.on('click', '.tabs-bar > li', function(e) {
var $li = $(e.currentTarget)
$li.addClass('active').siblings().removeClass('active')
var index = $li.index()
var $content = $li.closest('.tabs').find('.tabs-content>li').eq(index)
$content.addClass('active').siblings().removeClass('active')
})
}
}

var tabs = new Tabs('.tabs')

造轮子-组件实现思路

前端有哪些轮子

  1. 标准库的扩充

    1. underscore.js 扩充了 Array 和 Object 相关 API
    2. moment.js 扩充了 Date
    3. bluebird.js / hax/my-promise 实现了 Promise
    4. async.js 模拟了 async 操作符
    5. es5shim 用 ES 3 语法部分实现了 ES 5 特性
    6. handlebars.js 实现模板字符串功能
  2. DOM 的扩充

    1. jQuery.js 操作 DOM
    2. video.js 操作 video
    3. Fabric.js 操作 canvas
  3. UI 组件

    1. 纯 CSS 的 UI 组件库,如 Bulma
    2. 大而全的 UI 框架(CSS + JS),如 Bootstrap、Element UI
    3. 垂直领域的 UI 组件
      • 专门做轮播的 Swiper
      • 专门做输入提示的 typeahead
      • 专门做文件上传的 fine-uploader
      • 专门做 3D 瓦片效果的 vanilla-tilt.js
      • 专门做视差效果的 parallax.js
      • 专门做数据可视化的 D3.js
      • 专门做图表的 echarts.js
      • 专门做动画的 velocity.js
      • 专门做粒子效果的 particle.js
      • 专门做手势识别的 hammer.js
  4. 编程思想类的轮子
    1. 实现 MVC 思想的 backbone.js
    2. 实现 MVVM 思想的 AngularJS 1 和 Vue 1
    3. 实现 Virtual DOM 的 React 和 Preact
    4. 实现单向数据流(FLUX)思想的 Redux
    5. 实现 Reactive 思想的 Rx.js
    6. 实现 Rails 思想的 Ember.js
    7. 实现函数式思想的 Ramda

我们的重点

重点是 UI 组件,因为

  1. 我们日常工作中经常用到的就是 UI 组件
  2. UI 组件一般是由 HTML、CSS 和 JS 组成,把 UI 组件做好了,就能更好的做网页
  3. UI 组件做起来更有趣,所有效果你都能用眼睛看到,而做编程思想类、DOM扩充类组件可能过于抽象

原则

  • (内部)分层原则:正交原则
  • (对外)封装原则:面向接口编程

正交原则之x/y/z

永远不要用

1
2
$('#div').show() 
$('#div').hide()

因为你不知道 div以前display是什么

你应该做的是用状态控制

1
2
$('#div').addClass('active')
$('#div').removeClass('active')

内容、行为、样式分离原则

面向接口编程

你不该首先考虑代码怎么写

你该考虑用户如何调用

异步代码烧脑的原因

同步与异步

同步:等待结果
异步:不等待结果

注意,异步常常伴随回调一起出现,但是异步不是回调,回调也不一定是异步

如下代码就是同步,只要fn内部的循环完毕才能执行下面的代码

1
2
3
4
5
6
7
8
9
10
function fn(){
for(var i=0;i<5000000000;i++){

}
}
var startTime = new Date().getTime();
console.log('开始了')
fn();
var exeTime = new Date().getTime() - startTime;
console.log('执行了'+exeTime/1000+'秒')

异步代码

  • 定时器就相当于浏览器设置了一个闹钟
  • 之后继续执行
  • 闹钟到了时间再去执行相应的代码
1
2
3
4
5
6
7
//异步的 asyncFn
function asyncFn(fn){
setTimeout(fn, 3000)
}
console.log(1)
asyncFn(()=> console.log('wake up'))
console.log(2)

前端经常遇到的异步

1
2
3
//因为图片还没有加载完毕所以并不知道高度,(请禁用浏览器缓存在执行)
document.getElementsByTagNames('img')[0].width // 宽度为 0
console.log('done')

你应该在图片加载成功后在获取图片的高度

1
2
3
4
5
document.getElementsByTagNames('img')[0].onload = function(){
console.log(this.width) // 宽度不为 0
console.log('real done')
}
console.log('done')

面试题中的异步

为啥点击li每次都打印 6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<li>11111111</li>
<li>22222222</li>
<li>33333333</li>
<li>44444444</li>
<li>55555555</li>
<li>66666666</li>

......

let liList = document.querySelectorAll('li')
for(var i=0; i<liList.length; i++){
liList[i].onclick = function(){
console.log(i)
}
}
console.log(i) //6
  1. for循环用极短的时间执行完毕 我们假设 3 毫秒
  2. var i 有变量提升 即使循环之后仍然可以访问到i的值
  3. 用户看到 Li 的时间绝对大于3 毫秒,每个 Li 点击事件已经初始化结束
  4. log(i) 还是外面的 i

如何解决 把 var i 改为 let i 就可以了

详情参考

AJAX 中的异步

这样调用虽然是同步的,但是页面会失去响应,直到结果回来

1
2
3
4
5
let request = $.ajax({
url: '.',
async: false
})
console.log(request.responseText)
1
2
3
4
5
6
7
8
//异步方式
$.ajax({
url: '/',
async: true,
success: function(responseText){
console.log(responseText)
}
})

异步的形式

一般有两种方式拿到异步结果

  1. 傻逼方法:轮训
    老板叫你去买土豆,然后他就隔1分钟问一次买到土豆了吗?
  2. 正规方法:回调
    老板叫你去买土豆并告诉你回来之后通知他,20分钟后你回来了告诉老板是否买到了土豆

回调的形式

  1. Node.js 的 error-first 形式
1
2
3
4
5
6
7
fs.readFile('./1.txt', (error, content)=>{
if(error){
// 失败
}else{
// 成功
}
})
  1. jQuery 的 success / error 形式
1
2
3
4
5
$.ajax({
url:'/xxx',
success:()=>{},
error: ()=>{}
})
  1. jQuery 的 done / fail / always 形式
1
2
3
$.ajax({
url:'/xxx',
}).done( ()=>{} ).fail( ()=>{} ).always( ()=> {})
  1. Prosmise 的 then 形式
1
2
3
4
//成功调第一个函数,失败调用第二个函数
$.ajax({
url:'/xxx',
}).then( ()=>{}, ()=>{} ).then( ()=>{})
  • ajax的 Prosmise跟真正的Promise有点不同
  • 它也可以一直.then().then().then() 这样链式操作

Promise只是一种回调的形式

Promise规范

1
2
3
4
5
axios({
url:'xxx'
}).then(s1,e1)
.then(s2,e2)
.then(s3,e3)

如果是jquery,成功只会走进成功的分支

1
2
s1 ==> s2 ==> s3 
e1 ==> e2 ==> e3

Promise规范是责任制

1
2
3
s1 e1 是第一责任人
s2 e2 是第二责任人
s3 e3 是第三责任人
1
2
3
4
5
6
7
8
9
10
11
12
axios({
url:'/aaa'
}).then(()=>{
console.log('成功1')
alert(xxxxx) //xxxxx 没有定义
},()=>{
console.log('失败1')
}).then(()=>{
console.log('成功2')
},()=>{
console.log('失败2')
})
  1. s1 执行过程不够顺利报错了(就会产生错误的结果)
  2. 于是第二责任人接收到的是一个烂尾工程
  3. 当做错误处理 走进e2

如果没有 e2 就会报错到浏览器显示给开发者

帮你兜底的两种写法
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
axios({
url:'/aaa'
}).then(()=>{
console.log('成功1')
alert(xxxxx) //xxxxx 没有定义
},()=>{
console.log('失败1')
}).then(()=>{
console.log('成功2')
}).catch((err)=>{
console.log(err)
})


axios({
url:'/aaa'
}).then(()=>{
console.log('成功1')
alert(xxxxx) //xxxxx 没有定义
},()=>{
console.log('失败1')
}).then(()=>{
console.log('成功2')
}).then(undefined,(err)=>{
console.log(err)
})

自己返回 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function buyApple(){
var fn =(succ,err)=>{
var rnd = Math.random()
setTimeout(()=>{
if(rnd>0.5){
succ('买到了苹果')
}else{
err('没买到苹果')
}
},2000)
}
return new Promise(fn) //fn.call(undefined,success,err)
}


var promise = buyApple();
promise.then(
(data)=>{console.log(data)},
(err)=>{console.log(err)}
)
1
2
3
4
5
6
7
8
9
10
function ajax(){
return new Promise((resolve, reject)=>{
做事
如果成功就调用 resolve
如果失败就调用 reject
})
}

var promise = ajax()
promise.then(successFn, errorFn)

Promise 深入阅读:http://www.cnblogs.com/hustskyking/p/promise.html
Promise/A+ 规范:https://segmentfault.com/a/1190000002452115

async / await

await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function buyApple(){
var fn =(succ,err)=>{
var rnd = Math.random()
setTimeout(()=>{
if(rnd>0.5){
succ('买到了苹果')
}else{
err('没买到苹果')
}
},10000)
}
return new Promise(fn) //fn.call(undefined,success,err)
}

var result = await buyApple();
console.log(2)

10秒之后返回买苹果的结果(成功/失败)
打印2

  • await相当于 用异步的方式写同步的代码
  • 直到异步代码结果回来才把值赋给 result

await的用法就是 后面接一个返回Promise对象的函数

async
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function buyApple(){
var fn =(succ,err)=>{
var rnd = Math.random()
setTimeout(()=>{
if(rnd>0.5){
succ('买到了苹果')
}else{
err('没买到苹果')
}
},10000)
}
return new Promise(fn) //fn.call(undefined,success,err)
}

async function fn(){
var result = await buyApple()
return result
}

var r = await fn().then(
(data)=>{console.log(data)},
(err)=>{console.log(err)})
console.log(1)

分析1

1
2
3
4
5
6
7
8
var r = await fn().then(
(data)=>{console.log(data)},
(err)=>{console.log(err)})
console.log(2)
/*
1.会直接打印2
2. 10秒后打印 买苹果的结果
*/

分析2

1
2
3
4
5
6
7
8
var r = await fn().then(
(data)=>{console.log(data)},
(err)=>{console.log(err)})
console.log(2)
/*
10秒后打印 买苹果的结果
然后打印2
*/

你真的懂函数吗

函数声明

匿名函数 就是声明的时候不给名字

function(){ return 1 ;} 你声明了它 但是又不能引用到它

1
2
3
4
5
6
7
8
// function(){ return 1 ;}  匿名函数 相当于废话    会报错   你只有给了引用才能使用
//fn 记录的是函数的地址 ==>引用
var fn = function(){
return 1;
}
console.log(fn.name) //fn
var fn2 = fn; //这里不是把function(){ return 1 ;} 复制给fn2 而是把地址复制给fn2
console.log(fn2.name) //fn

具名函数

1
2
3
4
function fn3(){
return 1;
}
console.log(fn3); //可以访问 fn3它是一个变量 作用域是整个区域

将具名函数赋值给一个变量 它的作用域就变了

1
2
3
4
5
var fn4 = function fn5(){return 1;}
console.log(fn4.name) //fn5
// 这个具名函数的作用域就是他函数本身那部分 外部访问不到
// console.log(fn5) //fn5 is not defined
// console.log(fn5.name) //报错

全局作用域的函数 赋值给变量他的作用域不受影响

1
2
3
4
5
function fn6(){return 1;}
var fn7 = fn6;
console.log(fn6.name) //fn6
console.log(fn7.name) //fn6
console.log(fn7) //function fn6(){return 1;}

箭头函数

1
2
3
4
5
6
7
8
9
var fn8 = ()=>1; //无参数 返回1
var fn9 = i=>i+1; //有参数返回 参数+1;
var fn10 =(i,j)=>{
console.log(i)
console.log(j)
return i+j
} //有参数 并且函数内部有多条语句
console.log(fn8.name) //fn8
// 箭头函数和 匿名函数 具名函数唯一的区别就是 this(口诀:箭头函数内外this不变)

fn1 调用的时候不会立即执行
而是会生成一个抽象语法树
校验里面的每句话 如果有错误就停止执行
没错再从头开始执行

词法作用域

一个函数能访问那些变量 在做词法分析的时候就确定了

词法树分析的是语义
只能确认a就是这个函数内部的a
不能确定的是“值”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var global = 1;
function fn1(param1){
var local1 = 'local1';
var local2 = 'local2';
function fn2(param2){
var local2 = 'fn2 local2';
console.log(local1);
console.log(local2);
}

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

经典面试题

1
2
3
4
var a = 1;
function b(){
console.log(a) // a一定是1吗?
}

你在看看

1
2
3
4
5
6
7
var a =1;
function b(){
console.log(a);
}
...若干代码后 突然一句
a = 2;
b(); //打出来的一定是1吗?

词法作用域只能确定这个a是不是那个a,不能确定a的值是不是那个值

call stack 调用栈

以后再更新。。。(没人考)

this & arguments

  • call的第一个参数就是this
  • call的第的哥参数后面的就是arguments(伪数组)
  • fn() 等同于 fn.call()
  • 非严格模式下,fn()和fn.call() this会转变为window
  • fn()是阉割版的fn.call()
1
2
3
4
5
6
7
8
function f(){
console.log(this)
console.log(arguments)
}
f.call() // window
f.call({name:'aaa'}) // {name: 'aaa'}, []
f.call({name:'aaa'},1) // {name: 'aaa'}, [1]
f.call({name:'aaa'},1,2) // {name: 'aaa'}, [1,2]

尽可能避免使用fn()的调用形式,因为会让你产生不知道this是谁的恐慌

  • this 为什么必须是对象
    因为 this 就是函数与对象之间的羁绊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 第一步思考
var person = {
name: 'frank',
sayHi: function(person){
console.log('Hi, I am' + person.name)
},
sayBye: function(person){
console.log('Bye, I am' + person.name)
},
say: function(person, word){
console.log(word + ', I am' + person.name)
}
}

//这样写法很恶心每次都要把person传进去
person.sayHi(person)
person.sayBye(person)
person.say(person, 'How are you')
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
// 第二步思考
// 能不能变成这样调用
person.sayHi()
person.sayBye()
person.say('How are you')

// 那么源代码就要改了
var person = {
name: 'frank',
sayHi: function(){
console.log('Hi, I am' + this.name)
},
sayBye: function(){
console.log('Bye, I am' + this.name)
},
say: function(word){
console.log(word + ', I am' + this.name)
}
}


// 如果你不想吃语法糖
person.sayHi.call(person)
person.sayBye.call(person)
person.say.call(person, 'How are you')

// 还是回到那句话:this 是 call 的第一个参数
// this 是参数,所以,只有在调用的时候才能确定
person.sayHi.call({name:'haha'}) // 这时 sayHi 里面的 this 就不是 person 了
// this 真的很不靠谱

// 新手疑惑的两种写法
var fn = person.sayHi
person.sayHi() // this === person
fn() // this === window

person.sayHi() 等价于 person.sayHi(person)
fn() 等价于 fn.call()

call / apply

  • 第一原则call的第一个参数是this
  • apply是另一版本的call

apply的使用场景就是在你不知道要传递的参数有多少个的情况

1
2
3
fn.call(asThis, p1,p2) 是函数的正常调用方式
当你不确定参数的个数时,就使用 apply
fn.apply(asThis, params)

bind

call 和 apply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由你指定。

待更新。。。。。

柯里化

返回函数的函数

  • 柯里化:将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)x

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //柯里化之前
    function sum(x,y){
    return x+y
    }
    //柯里化之后
    function addOne(y){
    return sum(1, y)
    }
    //柯里化之前
    function Handlebar(template, data){
    return template.replace('{{name}}', data.name)
    }
    //柯里化之后
    function Handlebar(template){
    return function(data){
    return template.replace('{{name}}', data.name)
    }
    }

    柯里化可以将真实计算拖延到最后再做
    关于柯里化的高级文章:

柯里化唯一的好处就是惰性求值

高阶函数:

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
接受一个或多个函数作为输入:forEach sort map filter reduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//接收一个函数

[3,1,2].sort(function(a,b){return a-b}) [3,1,2].sort.call([3,1,2],fn)
[1,2,3].forEach(function(val,idx,arr){}) [3,1,2].forEach.call([3,1,2],fn)
[1,2,3].map(function(val,idx,arr){}) [3,1,2].map.call([3,1,2],fn)
[1,2,3].filter(function(val,idx,arr){}) [3,1,2].filter.call([3,1,2],fn)
[1,2,3].reduce(function(){}) [3,1,2].reduce.call([3,1,2],fn)

//输出一个函数
function sum(a,b){return a+b}
function addOne(b){
return sum(1,b)
}

//输入一个函数,输出一个函数
fn.bind.call(fn,{},1,2,3);
  • 输出一个函数:lodash.curry
  • 不过它也可以同时满足两个条件:
  • Function.prototype.bind

高阶函数的好处就是任意组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var arr = [1,2,3,4,5,6,7,8];
var sum = 0;
//求偶数和
for(var i=0;i<arr.length;i++){
if(arr[i]%2===0){
sum+=arr[i]
}
}

// 高阶写法1
arr.filter(function(n){
return n%2===0
}).reduce(function(prev,next){
return prev+next
},0)

//高阶写法2
reduce(filter(arr,function(n){return n%2}),function(prev,next){return prev+next},0)
sort(filter(arr,function(n){return n%2===1}),function(a,b){return a-b})

回调函数

  • 名词形式:被当做参数的函数就是回调
  • 动词形式:调用这个回调
  • 注意回调跟异步没有任何关系

构造函数

  • 返回对象的函数就是构造函数
  • 一般首字母大写
1
2
3
4
5
6
7
8
9
10
11
12
var n = new Number(1)
var bool = new Boolean(true)
var s = new String('xxx');
var obj = new Object();


function Animal(){
this.name = '动物'
//return this; // 自动帮你返回一个对象
}
// 只要你new一个函数 就会帮你返回一个对象
var animal = new Animal(); //Animal.call({})

箭头函数

1
2
3
4
5
6
7
8
9
10
function fn(x,y){
return x+y;
}

var fn = (x,y)=>x+y
var fn = (x,y)=>{
//多句话要执行的时候
console.log(x)
return x+y
}

箭头函数没有this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setTimeout(function(){
console.log(this)
}.bind({name:'aaa'}),1000)
//打印出 {name:'aaa'}

setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
},1000)
}.bind({name:'aaa'}),1000)
//第一秒 {name:'aaa'}
//第二秒 window 第二个延时器相当于 fn.call()
//call里没传递this就是 window

//如果想两次都打印 {name:'aaa'}就要这样
setTimeout(function(){
console.log(this)
setTimeout(function(){
console.log(this)
}.bind(this),1000)
}.bind({name:'aaa'}),1000)
//第二个延时器里的bind(this) this就是 {name:'aaa'}

箭头函数干掉了this

1
2
3
4
5
6
setTimeout(function(){
console.log(this)
setTimeout(()=>{
console.log(this)
},1000)
}.bind({name:'aaa'}),1000)

js的漏洞 function(){} 等价于 function(){}.call()
call的第一个参数就是this
call不传递参数就是window

强迫症就是要给箭头函数加this?

答案是不行 会被忽略

1
2
3
4
var fn =()=>{console.log(this)}
fn.call({name:'aaa'})

//打印window

vue组件间通信

vue 父子通信

爸爸是咋叫儿子的!

  • 父组件在使用子组件时 绑定属性 :xxx=”你定义的值”
  • 子组件设置props属性,可以在页面上插值

    1
    2
    3
    4
    {
    props:['xxx'],
    template:`<div>{{xxx}}</div> `
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div id="app">
<child :msg="data"></child>
</div>

Vue.component('child',{
props:['msg'],
template:`<div>
<div>子组件==>{{msg}}</div>
</div>`
})
new Vue({
el:'#app',
data:{
data:'我是父组件的数据'
}
})

儿子是怎样叫爸爸的!

  • 子组件向上要通过emit(‘xxx’),xxx是事件名称
  • 父组件使用子组件时,在子组件上监听事件 @xxx=”相应处理”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- html如下 -->
<div id="app">
<child @close="show=false" v-show="show"></child>
</div>

//js如下
Vue.component('child',{
template:`<div>
<div>子组件</div>
<button @click="$emit('close')">close</button>
</div>`
})
new Vue({
el:'#app',
data:{
msg:'父组件的数据',
show:true
}
})

爷孙通信

如果是爷孙?

vue是不支持的,需要儿子叫爸爸,爸爸叫爷爷,这样层层传递

  • 所以在vue里爷孙通信是很麻烦的
  • 不过你可以使用单一事件管理组件eventHub来解决
  • 这里用使用最通俗的方式,你也可以挂载在Vue原型上

    1
    Vue.prototype.$eventHub = new Vue()
  • 重新构造一个Vue对象 专门用来负责管理通信

  • 在嵌套的组件内部的created()里 eventHub.$on(‘xxx’,()=>{相关逻辑处理})
  • 在子组件里eventHub.$emit(‘xxx’)触发
    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
    <!-- html -->
    <div id="app">
    <father></father>
    </div>

    ....


    //js部分

    // 单一事件管理组件 vuex的前身
    var eventHub =new Vue();

    Vue.component('son',{
    template:`<div>
    <div>我是子组件
    <button @click="callFather">发送给爸爸</button>
    <button @click="callGrandPa">发送给爷爷</button>
    </div>
    </div>`,
    methods:{
    callFather(){
    eventHub.$emit('father')
    },
    callGrandPa(){
    eventHub.$emit('grandpa')
    }
    }
    })

    Vue.component('father',{
    template:`<div>
    <div>父组件div>
    <son></son>
    </div>`,
    created(){
    eventHub.$on('father',()=>{alert('儿子叫爸爸')})
    }
    })

    new Vue({
    el:'#app',
    data:{
    data:'我是爷爷',

    },
    created(){
    eventHub.$on('grandpa',()=>{alert('儿子叫爷爷')})
    }
    })

vue-cli使用

如果你从零开始构建vue-cli项目

  1. 你需要安装node环境

node官网 下载对应版本(建议下载LTS版本相对稳定)

一路next千万别手欠把add to path 默认选中的取消了

  1. windows用户请打开cmd 输入
1
2
3
4
5
# 查看node 版本 我的是8.9.4
node -v

# 查看npm版本 我的是5.6.0
npm -v
  1. 终于你有了node的命令行环境这样你就可以安装前端相关依赖了
1
2
3
4
5
6
npm install 包名

# eg 这些只是例子
npm install jquery
npm install vue
npm install ...

安装vue-cli

  1. vue官网
  2. 点击「起步」如果是英文 「get stared」
  3. 左侧菜单栏点击 安装
  4. 找到命令行工具(CLI)

    1
    2
    3
    4
    5
    6
    7
    # 全局安装 vue-cli
    $ npm install --global vue-cli
    # 创建一个基于 webpack 模板的新项目
    $ vue init webpack my-project
    # 安装依赖,走你
    $ cd my-project
    $ npm run dev
  5. 以上命令你就可以本地初始化一个 vue的官方模板了

如果是别人构建好的项目

给你一个项目目录(注意给别人项目的时候是不要连带node_modules的)

1
2
cd 项目目录
npm install
  • 注意下载的时候由于是国外的包可能会出现丢包的情况
  • 一旦丢包或者报错,就把整个node_modules目录删除
  • 再次运行 npm install

其实你可以使用淘宝镜像

使用说明 淘宝镜像如何设置

你可以使用 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:

1
$ npm install -g cnpm --registry=https://registry.npm.taobao.org

webpack

webpack

webpack:从入门到真实项目配置

关于 webpack 的面试题有哪些?

什么是工程化

回到之前的项目

基础版本项目地址

  1. 安装node-sass

    github 搜索 node-sass

    cnpm install node-sass -g

    -g 这样你就可以任何目录都能执行自动化工具

    • 如果你安装node-sass出错 直接google搜索 node-sass安装失败(window通常会失败)
  2. 安装成功

    • 如何使用node-sass

      1. 修改后缀。直接把你的main.css修改为 main.scss
      2. 查看文档发现如下

        1
        node-sass src/style.scss dest/style.css
  3. sass语法

    • 嵌套

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      css写法
      .topNavBar nav {padding:5px;}
      .topNavBar nav ul{ list-style:none;margin:0;padding:0;}

      scss写法 是不是简洁了很多

      .topNavBar{
      nav {
      padding:5px;
      ul{
      list-style:none;
      margin:0;
      padding:0;
      }
      }
      }
  4. sass和scss的区别

    ruby社区写了sass规则比scss更简洁,但是前端大多数看不懂

    aa.sass

    1
    2
    3
    4
    body
    p
    a
    background : red
aa.css

1
2
3
body p a {
background: red;
}
sass可以自动加“{}” 和“;” 但是新手看不懂啊。。。 > 于是出现了 scss aa.scss
1
2
3
4
5
6
7
body{
p{
a{
background:red;
}
}
}
> 前端一看: 懂了。。。
  1. 但是即使这样我们引用的却不能是scss文件,而是转化后的css文件

    改个背景色还要重新编译一次才能看到效果 ? 484傻

    • google node sass watch

      1
      node-sass  main.scss main.css -w main.scss

Babel

我们自己写项目可以大量使用ES6,而真实项目可能需要在IE上运行

  1. babel

    1. 第一步
      1
      2
      安装官方提示的步骤
      npm install --save-dev babel-cli babel-preset-env
2. 第二步 新建.babelrc
1
2
3
{
"presets": ["env"]
}
3. 第三步点击 向导 点击cli下 > ![](https://raw.githubusercontent.com/slTrust/note/master/img/note045_02.png) > ![](https://raw.githubusercontent.com/slTrust/note/master/img/note045_03.png) 4. 继续按照步骤
1
npm install --save-dev babel-cli
5. 由于我们没有pageage.json
1
npm init 一直回车回车回车
6. 因为 npm install --save-dev babel-cli的时候我们还没有 pageage.json 7. 再次运行
1
npm install --save-dev babel-cli
> 你会发现增加了一行
1
2
3
4
5
{
"devDependencies": {
+ "babel-cli": "^6.0.0"
}
}
8. 在pageage.json里添加如下代码
1
2
3
4
5
6
7
8
9
10
  {
"name": "my-project",
"version": "1.0.0",
+ "scripts": {
+ "build": "babel src -d lib"
+ },
"devDependencies": {
"babel-cli": "^6.0.0"
}
}
> ![](https://raw.githubusercontent.com/slTrust/note/master/img/note045_04.png) > ![](https://raw.githubusercontent.com/slTrust/note/master/img/note045_05.png) 9. 这个时候提示你可以运行 npm run build > 此时报错了 ! 因为命令行已经发展到了非常变态的地步,就算你是个老手也会报错 - 因为npm run bulid 实际运行的是 script里的 "babel src -d lib" - 而"babel src -d lib"会优先寻找./node_modules/.bin/目录 - 此时你就要知道全局安装和局部安装的区别了 > ### 由于我们是局部安装 所以我们要这样运行
1
./node_modules/.bin/babel src -d lib
10. 我们肯定也要时时修改,而不是边保存边编译
1
./node_modules/.bin/babel src -d lib --watch

以上叫自动化

但是有一个问题 CSS自动化要开一个命令行,JS也要开一个,这样无形中多了好多窗口

实际开发中如下图

改变目录解构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--|app
----|src
------|css
--------|a.scss
--------|b.scss
--------|c.scss
------|js
--------|a.js
--------|b.js
--------|c.js
------|img
--------|a.jpg
--------|b.jpg
--------|c.jpg
------|index.html

------|dist
//这个目录是根据src里生成的

------|node_modules 第三方包
------|vendors 第三方文件
  1. node-sass 命令的变化监听目录

    1
    node-sass src/css/ -o dist/css/  -w src/css/
  2. babel 变化

    1
    ./node_modules/.bin/babel src/js -d dist/js --watch

而我们不仅仅会修改js/css还会修改 html/img文件 这个时候又怎么办呢?

于是我们又发现了watch-cli一个命令行工具可以监听文件变化

watch-cli

监听html文件的变化

1
watch -p "src/index.html" -c "cp src/index.html  dist/index.html"

监听img文件的变化

1
watch -p "src/img/**/*" -c "cp src/img  dist/img"

经过如上步骤 我们每次开发一个项目就要开四个命令行工具

在node诞生之后 前端想尽办法代替上面的四个命令行

  • grunt 不用学了 已经过时了 被gulp替代了 gulp比它快
  • gulp 也不用学了 被webpack替代了

webpack

我的webpack示例仓库

parcel代替webpack(依然不稳定,需要时间去磨合,建议还是webpack虽然快死了)

尝试去试一下这个 秒杀webpack啊简直!


webpack就是干(实践webpack加入es6 加入sass)

不知有多少人想说 fack webpack

google 搜索 webpack

进入官网 点击github

提示你安装

1
2
# 建议安装 webpack@3.10.0 因为我用的是这个 这步骤可以先跳过因为你还没有package.json
npm i --save-dev webpack

继续往下看,发现毫无线索,…. 发 现get started 点击

step1 新建目录

1
2
3
4
5
6
7
8
9
mkdir webpack_simple
cd webpack_simple
# 你想使用webpack安装进入你的依赖一定要先保证你有package.json文件

npm init
... 一路回车 你有了自己的package.json

# 安装webpack依赖 --save-dev是添加到package.json里 这样别人拿到你的这个文件直接 npm i就可以直接安装依赖了
npm i webpack@3.10.0 --save-dev

step2 我们的目录结构如下

1
2
3
4
5
 webpack_simple
|- package.json
+ |- index.html
+ |- /src
+ |- index.js

step3 webpack的命令需要一个配置文件 改变目录如下

1
2
3
4
5
6
7
  webpack-demo
|- package.json
+ |- webpack.config.js
|- /dist
|- index.html
|- /src
|- index.js

step4 webpack.config.js的编写

1
2
3
4
5
6
7
8
9
const path = require('path');

module.exports = {
entry: './src/index.js', //入口文件
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist') //打包后的目录
}
};

step5 好了 在确保你有node的情况下

1
2
# 注意是 webpack_simple目录下运行如下命令
webpack

step6 大功告成 你发现 多了一个dist目录

小小细节 提交的时候千万不要提交如下目录的东西

你该新建 .gitignore 文件

1
2
node_modules/
/dist/

你需要新建.gitignore文件 内容如上

现在你已经成功的实现打包js

现在都ES8了 我们至少得使用ES6吧!

目标引入 babel-loader

github里搜索 babel-loader

看文档 注意看你当前依赖的webpack的版本 它会有对应的跳转链接

看文档 注意看你当前依赖的webpack的版本 它会有对应的跳转链接

看文档 注意看你当前依赖的webpack的版本 它会有对应的跳转链接

知道为啥说三次吗? 因为你不点这个链接估计卡你一宿都不知道为啥错了

进入对应版本链接说明文档

webpack 3.x babel-loader 7.x | babel 6.x 我选择了这个 376 于是点击了 7.x branch

继续看 7.x版本对应的安装依赖文档

Install

webpack 1.x | babel-loader <= 6.x

webpack 2.x | babel-loader >= 7.x (recommended) (^6.2.10 will also work, but with deprecation warnings)

webpack 3.x | babel-loader >= 7.1

1
2
3
4
5
6
7
yarn add babel-loader babel-core babel-preset-env webpack --dev
We recommend using yarn, but you can also still use npm:

# 我用的npm所以用这个
npm install --save-dev babel-loader babel-core babel-preset-env
# 安装之后你肯定心急了 运行下webpack
webpack

你可能发现npm下载好慢 那是因为是从国外下载所以慢

1
2
3
4
5
# 设置淘宝镜像 cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org

以后你就可以用 cnpm i 包名
的方式下载国内的资源镜像

根据babel-loader的文档提示 它说有个module参数 你可以试试

自行脑补然后把module 结构添加到你的webpack.config.js里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const path = require('path');

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist/js/')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
}
]
}
};

现在你可以写一写ES6语法了

我们还有scss

github里搜索 sass-loader

看文档 安装依赖

1
npm install sass-loader node-sass webpack --save-dev

添加模块 rules是个数组 脑补添加该对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// webpack.config.js
module.exports = {
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]
}]
}
};
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
const path = require('path');

module.exports = {
entry: './src/js/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist/js/')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['env']
}
}
},
{
test: /\.scss$/,
use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}]
}
]
}
};

然后写对应的scss文件

1
2
3
4
5
# 在index.js里引入
import '../css/main.scss'

... 命令行输入 webpack
报错 说没有style-loader

诀窍 只要报错一般提示少什么东西 然后 你就 cnpm install 提示的文件名

1
2
cnpm i style-loader  --save-dev
cnpm i css-loader --save-dev

突然性的报错 有可能是依赖包下载有问题

1
2
最佳实践就是  rm -rf node_modules
然后 cnpm i 重新安装依赖

千万别参考别人的配置然后一路查

这样你就会陷入绝境,因为别人的配置也不知道是从哪里copy来的他能跑通你就不一定了。出了错你就哭吧!

最佳实践就是照着官方demo写,因为你发现错了,别人还可能跟你遇到同样的问题,这样你还可以参考,一旦你去用了别人的配置,webpack配置这么繁琐,有经验的前端还会翻车呢?更何况你呢?而且你英文不一定很好!

Cache_Control

Cache Control

稍微有点相关web性能优化

1
2
3
4
5
6
7
8
9
10
11
node.js后台
//缓存30秒
response.setHeader('Cache-Control','max-age=30')

//这样你访问对应资源的时候就会多了一个响应头
Cache-Control:max-age=30
//意思是30秒内不要再次请求

30秒内下载资源的时间为 0ms 是从内存里或者磁盘上读取

30秒后再次请求就会重新下载资源

更新缓存

如main.js你缓存了10年但是,你更新后修改了main.js的内容

缓存的策略就是url一致,如果你想更新缓存那就更改url

1
2
3
4
<script src="main.js?v=1"></script>

//更新版本
<script src="main.js?v=2"></script>
1
2
// 文件上加一个随机字符串,这个如果你是webpack会帮你自动生成
<script src="main.fdaslj312ljkklj321.js"></script>

这样就实现了更新缓存

什么是Expire

  • 它与 Cache Control 效果一样
  • 出现在 Cache Control之前
  • 现在被Cache Control替代了

区别就是:

  • Cache Control 是多长时间过期
  • Expire 是 某年某月某日几时几分几秒过期
  • Expire 的时间指的是本地时间

如果你系统时间是2050年,而缓存时间是2020年,你就没法缓存了

什么是 ETag

这样不得不提 MD5(摘要算法)

假如你下载一个linux系统400MB 下载过程中如何检测你下载的对不对呢?

  • MD5就是为了这个场景出现的
1
2
如果网上的 linux系统 400MB  MD5值是 XXXXXXXX
自己下载的 linux系统 400MB 你的MD5值和网上的一致才说明下载的是正确的

每个文件对应一个 MD5值
文件改动越小MD5差异越大

  1. 应用ETag
1
2
3
4
引入MD5模块算出 文件对应的MD5值

let fileMd5 = md5(文件资源字符串)
response.setHeader('ETag',fileMd5)
  1. 这样你访问资源的时候响应头里就有了一个ETag头
1
2
形如
ETag:440c53453212aac08c2321sd
  1. 以后你在访问同一资源的时候,请求头里会有一个「if-None-Match」头

「if-None-Match」就是如果不匹配,就是如果你的MD5跟我一样就不需要下载

1
if-None-Match:440c53453212aac08c2321sd

ETag 和 Cache Control的区别

  • 用 Cache Control是直接不请求,从盘符里读取资源
  • 用 ETag 是直接不下载,但是仍然会发请求

结论就是使用 Cache Control

Session

Cookie存在的问题

用户可以篡改,假如是多个用户,我只要知道他的Cookie就能冒充他,访问他的隐私信息

Session的出现

Session实质就是服务器的一小块内存——哈希

1
2
3
4
5
6
7
8
9
10
11
12
//node.js服务器
step000 session定义实际就是一个哈希
let sessions = {};

//step001 设置一个随机数的session_id
let session_id = Math.random()*100000;

//step002 将用户信息存入sessions里
sessions[session_id] = {sign_in_email:emaiil}

//ste003 在你登录的一瞬间 设置Cookie的值为session_id
response.setHeader('Set-Cookie', `session_id=${session_id}`)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//你登录成功后,访问自己的资源时候会传递cookie
//假如你的 session如下 cookie:session_id=54342.312312312

//*我这里就不进行是否传递cookie的校验了,你自己干掉cookie那就重新登录呗
//*我这里就不进行是否传递cookie的校验了,你自己干掉cookie那就重新登录呗
//*我这里就不进行是否传递cookie的校验了,你自己干掉cookie那就重新登录呗

let hash = {};
//可能有多个cookie
let cookies = request.headers.cookie.split(';');
for(let i=0;i<cookies.length;i++){
//每个cookie形如 session_id=54342.312312312
let current = cookies[i].split('=');
let key = current[0]; // session_id
let value = current[1]; //54342.312312312
hash[key] = value
}

// 根据你登录时存的session_id 查询出 email
let email = sessions[hash['session']]['sign_in_email'];

重回公园实例

公园系统升级了,因为他发现有人拿着别人的票混进去

  • 于是你每次购票的时候发给你一个票,票上有一个「id号」

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //比如 购票者   
    12386 : 猴哥
    12903 : 八戒
    12982 : 沙师弟

    其实就是 服务器里的一小块内存
    sessions = {
    "12386" : User("猴哥"),
    "12903" : User("八戒"),
    "12982" : User("沙师弟")
    }

    sessions的 key就是一个「随机数」绝不是流水号那样别人就能轻易冒充你
  • 第二天猴哥去公园了,公园就会说 「欢迎你!猴哥」

什么是session

  1. 服务器通过cookie给用户一个session_id
  2. session_id对应服务器里的一小块内存
  3. 每次用户访问服务器的时候,服务器通过session_id读取用户信息,识别用户是谁

为什么session_id是一个随机数

假如你的session_id = 321341.31239089089054

请问用户2的session_id是什么?我怎么知道用户2的随机数是啥
那我要是删了cookie呢?
  • 你自己把票扔了,公园让你进去?
  • 你说可以翻墙进公园啊,那我还说啥啊,你咋不开坦克碾过去呢

当然是重新买票了,或者重新登录喽

为啥是随机数

因为如果是流水号你按顺序排下去就知道别人的session_id了

暴力穷举呢?

有点难啊!

1
2
3
4
5
6
7
8
9
假如session_id是16位随机数字

那你可能要试10的16次方那就是 10,000,000,000,000,000

假如是session_id是16位随机的数字和字母

那你可能要试36的16次方那就是 。。。。

一天时间是86400秒 你够快1秒来一次 你去算吧!
一个用户大约占据多少内存

如果1M一个用户 1000个用户就是1G内存了
但是实际上每个用户给个 「几K」就够了

重要概念

  1. 服务器通过Set-Cookie 头给客户端的一串字符串
  2. 客户端每次访问相同域名的网页时,必须带上这段字符串
  3. 客户端要在一段时间内保存这个Cookie
  4. Cookie 在用户关闭用户的时候就失效(后台代码可以任意设置过期时间)

什么是 Session

  1. 将 SessionID 通过Cookie发送给客户端
  2. 客户端访问服务器时,服务器读取 SessionID
  3. 服务器有一块内存(哈希表)保存了所有 session
  4. 通过 SessionID 我们可以得到对应的用户隐私信息,如id/email
  5. 这块内存(哈希表)就是服务器上的所有 session

什么是 LocalStorage

html5 提供的api

1
2
3
4
5
6
7
//注意只能存 string 即使你存一个对象它也会调用对象的 obj.toString()
window.localStorage.setItem('a','1')
windwo.localStorage.getItem('a')

//如果你想存复杂数据类型只能通过序列化
let a = {name:"aaa",age:18}
window.localStorage.setItem('a',JSON.stringify(a))

session最大问题就是耗内存,因为存在服务器

localStorage存在客户端的盘符里——数据的持久化存储

答案如下

  1. LocalStorage 跟 HTTP 无关
  2. HTTP 不会带上 LocalStorage 的值
  3. 只有相同域名的页面才能相互读取 LocalStorage(没有同源那么严格)
  4. 每个域名 LocalStorage 最大存储量不同为 5M左右(每个浏览器不一样)
  5. 常用场景:提示信息(没有用的信息,不能记录密码)
  6. LocalStorage 永久有效,除非用户自己清理缓存

什么是 SessionStorage

1,2,3,4 同 LocalStorage

  1. SessionStorage 在用户关闭用户的时候就失效

请问session和cookie什么关系

session是基于cookie实现的因为session_id必须放到cookie里发给客户端,没有cookie就没有session,
cookie是session的基石

Cookie 是每次请求都带给服务器
Cookie 只有4K

LocalStorage 跟HTTP无关不会被携带到服务器
LocalStorage 有5M

LocalStorage 和 SessionStorage 的区别

SessionStorage 在用户关闭用户的时候就失效 (会话结束)

实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
后端
1.登录的时候不传递cookie
2.通过json传递给前端
response.write(`{"session_id":${session_id}}`)

前端
1.登录的时候通过响应的第四部分也就是json解析传递的session_id
2.存入到localStorage

localStorage.setItem('session_id',obj.session_id)

当访问用户信息的时候通过url参数传递session_id

window.location.href = `/?session_id=${obj.session_id}`

session大部分时间是基于cookie来存储id的,也可以通过localStorage + 查询参数传递

前端那时无法持久化数据所以跨页面数据有时就使用了 Cookie

问题来了 Cookie每次请求是会被带到服务器的

假设你有1K的cookie这样每个请求就要多携带1K的cookie到服务器,而服务器的数据可能只有 200B (请求就慢了)

就像公园卖票一样,前端就相当于游客,你自己造票?

Cookie

什么是Cookie

Cookie维基百科

方大文章

出现场景

登陆注册

  1. 在你登录成功的一瞬间,后台设置cookie
1
2
3
4
5
// node.js后端设置响应头
// 假如你的登录邮箱是
let email = 'trustfor@sian.cn';
//登录成功设置响应头
response.setHeader('Set-Cookie':'sign_in_email=${email}')

你可以在浏览器里看到你设置的响应头 (Response Headers)

1
Set-Cookie:sign_in_email=trustfor@sian.cn
  1. 只有被设置了Cookie,你每次访问同一域名下资源都会在请求头里传递 Cookie

    1
    Cookie:sign_in_email=trustfor@sian.cn
  2. 后台得到Cookie就知道用户是谁了

    将Cookie映射到生活中

  • 你去公园,公园给你发了个票,票的有效期是2天
  • 假设你中途离开了公园,下午再次进入你就需要提供你的门票
    • 即使你是去看公园里的狗,也得有门票才行

Cookie的特点

  1. 服务器通过Set-Cookie 响应头设置 Cookie
  2. 浏览器得到Cookie之后,每次请求都要带上Cookie
  3. 服务器读取Cookie 就知道登录用户的信息(email)

问题1

我在Chrome登录了并得到了Cookie,用Safari访问Safari会带上Cookie吗?

No,北京动物园和天津动物园的门票你觉得能一样吗?

问题2

问题3

票能作假吗?

可以,只要你稍微有一点前端知识就可以伪造Cookie
如果是Chrome你可以F12–>Application–>Cookies就能修改了

问题4

Cookie 有有效期吗?

当然有了,你见过7天免登陆吗?
默认有效期20分钟,防止别人拿着你的票查看你隐私信息,

问题5

Cookie 遵守同源策略吗?

也有,不过跟 AJAX 的同源策略稍微有些不同。

注意

这里的场景是非常非常简单的,后台设置cookie肯定不会拿你的邮箱,这样太好冒充了