Giter VIP home page Giter VIP logo

blog's Introduction

前端学习相关

issue里面是学习BLOG

blog's People

Contributors

tailorr avatar

Stargazers

 avatar

Watchers

James Cloos avatar

blog's Issues

跨域的几种方式

跨域以及跨域的几种方式

讲解跨域之前我们先来看看什么是同源策略

什么是同源策略

通常来说,浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不许读写对方的资源,
本域指的是
-同协议:比如都是http或者https
-同域名:比如都是http://baidu.com/ahttp://baidu.com/b
-同端口:比如都是80端口

同源:

http://baidu.com/a/b.js http://baidu.com/index.php

不同源:

http://baidu.com/main.js https://baidu.com/a.php (协议不同)
http://baidu.com/main.js http://bbs.baidu.com/a.php (域名不同,域名必须完全相同才可以)
http://baidu.com/main.jshttp://baidu.com:8080/a.php (端口不同,第一个是80)

***需要注意的是: 对于当前页面来说页面存放 JS 文件的域不重要,重要的是加载该该JS的页面所在什么域 **

什么是跨域?跨域有几种实现形式

跨域就是实现不同域的接口可以进行数据交互

  • JSONP----JSONP 的原理是什么
    html中script标签可以引入其他域下的js,比如引入线上的jquery库。利用这个特性,可实现跨域访问接口。不过需要后端支持。
    web页面中,通过script标签获取js代码可以进行跨域,我们可以动态的创建script标签,设置src属性指向我们请求资源的url地址,然后放到head标签中。这样就可以把不同域的数据加载到本域名下,不过需要后端支持jsonp,也就是通过前端的url中的callback参数动态的生成回调函数名,通过传参的形式执行获取到的数据,这样的话,前端预定义好的函数就可以让传来的数据自动运行。

  • CORS

    CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。CORS兼容IE10+ 。

  • 降域

    降域是相对于iframe元素而言的 。

    一般来说域名为http://b.baidu.com/b(A)的网页以iframe的形式嵌在域名为http://a.baidu.com/a(B)的网页中,浏览器发现该请求不符合同源策略,正常情况下不能进行跨域访问,但是当我们在两个页面中分别设置documet.domain = baidu.com的时候(注意观察这个方法有一个限制就是 域名中必须有相同的部分),B页面就可以访问到A页面中的资源了,比如下面的代码:

  • postMessage()

    window.postMessage()是HTML5的新方法,可以使用它来向其它的window对象发送数据,无论这个window对象是属于同源或不同源,IE8+支持。

postMessage(data, origin)方法接收两个参数

  1. **data:**要传递的数据。html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
  2. **origin:**字符串参数,用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * ,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"

跨域方式的演示

介绍跨域方式之前,我们先来修改本机的hosts文件如下:

127.0.0.1       localhost
127.0.0.1       test.com
127.0.0.1       a.test.com
127.0.0.1       b.test.com

这样本地地址就对应了不同的域

JSONP

JSONP的跨域方式是利用<script>标签的src属性可以跨域引用资源的特点,有这些属性的标签还有<img><iframe>,但是JSONP只支持GET方式。

下面我们以点击获取随机新闻列表的例子来演示一下JSONP的具体工作原理(test.com访问a.test.com)

HTML如下:

<div class="container">
  <ul class="news">
    <li>第11日前瞻:**冲击4金 博尔特再战</li>
    <li>男双力争会师决赛 </li>
    <li>女排将死磕巴西!</li>
  </ul>
  <button class="change">换一组</button>
</div>

首先,我们在前端要在调用资源的时候动态创建script标签,并设置src属性指向资源的URL地址,代码如下:

document.querySelector('.change').addEventListener('click', function() {
  var script = document.createElement('script')
  script.setAttribute('src', '//a.test.com:8080/getNews?callback=appendHtml')	//callback=appendHtml是给后端资源打包数据用的参数,同时也是前端定义的回调函数
  document.head.appendChild(script)
  document.head.removeChild(script)	//删除script标签是因为script标签插入页面的时候资源已经请求到了
})

定义获取资源后需要执行的回调函数:

function appendHtml(news) {
	var html = ''
	for (var i = 0; i < news.length; i++) {
		html += '<li>' + news[i] + '</li>'
	}
	document.querySelector('.news').innerHTML = html
}

后端是把前端发送的URL地址拿到的数据以前端定义的回调函数(appendHtml)的参数的形式返回给前端,这样到了前端就可以调用执行了:

var news = [
	"第11日前瞻:**冲击4金 博尔特再战200米羽球",
	"正直播柴飚/洪炜出战 男双力争会师决赛",
	"女排将死磕巴西!郎平安排男陪练模仿对方核心",
	"没有**选手和巨星的110米栏 我们还看吗?",
	"中英上演奥运金牌大战",
	"博彩赔率挺**夺回第二纽约时报:**因对手服禁药而丢失的奖牌最多",
	"最“出柜”奥运?同性之爱闪耀里约",
	"下跪拜谢与洪荒之力一样 都是真情流露"
]
var data = [];
for (var i = 0; i < 3; i++) {
  var index = Math.floor(Math.random() * news.length);

  data.push(news[index]);
}
var callback = req.query.callback;   //查询前端有没有传入回调函数
if (callback) {
	res.send(callback + '(' + JSON.stringify(data) + ')');    //数据以函数参数的方式传给前端
} else {
	res.send(data);
}

这样我们就从test.com访问到了a.test.com下的资源

CORS

CORS是AJAX跨域请求的一种方式,目前只支持IE10以上浏览器,具体兼容如下图

同样以上面的列子来演示

首先JS部分,发起AJAX请求(与同域相同):

document.querySelector('.change').addEventListener('click', function () {
  var xhr = new XMLHttpRequest();
  xhr.open('get', '//a.test.com:8080/getNews', true);
  xhr.send();
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
      appendHtml(JSON.parse(xhr.responseText))
    }
  }
  window.xhr = xhr
})
function appendHtml(news) {
  var html = ''
  for (var i = 0; i < news.length; i++) {
    html += '<li>' + news[i] + '</li>'
  }
  document.querySelector('.news').innerHTML = html
}

后端在向前端发送资源之前,设置请求头 "Access-Control-Allow-Origin":

var news = [
    "第11日前瞻:**冲击4金 博尔特再战200米羽球",
    "正直播柴飚/洪炜出战 男双力争会师决赛",
    "女排将死磕巴西!郎平安排男陪练模仿对方核心",
    "没有**选手和巨星的110米栏 我们还看吗?",
    "中英上演奥运金牌大战",
    "博彩赔率挺**夺回第二纽约时报:**因对手服禁药而丢失的奖牌最多",
    "最“出柜”奥运?同性之爱闪耀里约",
    "下跪拜谢与洪荒之力一样 都是真情流露"
  ]
  var data = [];
  for (var i = 0; i < 3; i++) {
    var index = Math.floor(Math.random() * news.length);
    data.push(news[index]);
  }
  res.header("Access-Control-Allow-Origin", "//test.com:8080"); // //test.com:8080表示只有//test.com:8080下发起请求才可以调用本域下的资源
  //res.header("Access-Control-Allow-Origin", "*");   //*表示任何域下发起请求都可以调用本域下的资源
  res.send(data);

降域

降域是相对于iframe元素而言的 。

一般来说域名为http://b.test.com/b(A)的网页以iframe的形式嵌在域名为http://a.test.com/a(B)的网页中,浏览器发现该请求不符合同源策略,正常情况下不能进行跨域访问,但是当我们在两个页面中分别设置documet.domain = test.com的时候(注意观察这个方法有一个限制就是 域名中必须有相同的部分),B页面就可以访问到A页面中的资源了,比如下面的代码:

B页面:

<html>
<style>
    html,
    body {
        margin: 0;
    }

    input {
        margin: 20px;
        width: 200px;
    }
</style>

<input id="input" type="text" placeholder="http://b.test.com/b.html">
<script>
    // URL: http://b.test.com/b.html

    document.querySelector('#input').addEventListener('input', function () {
        window.parent.document.querySelector('input').value = this.value;
    })

    document.domain = 'test.com';

</script>

</html>

A页面:

<html>
<style>
    .ct {
        width: 910px;
        margin: auto;
    }

    .main {
        float: left;
        width: 450px;
        height: 300px;
        border: 1px solid #ccc;
    }

    .main input {
        margin: 20px;
        width: 200px;
    }

    .iframe {
        float: right;
    }

    iframe {
        width: 450px;
        height: 300px;
        border: 1px dashed #ccc;
    }
</style>

<div class="ct">
    <h1>使用降域实现跨域</h1>
    <div class="main">
        <input type="text" placeholder="http://a.test.com:8080/a.html">
    </div>

    <iframe src="http://b.test.com:8080/b.html" frameborder="0"></iframe>
</div>

<script>
    //URL: http://a.test.com:8080/a.html

    document.querySelector('.main input').addEventListener('input', function () {
        console.log(this.value);
        window.frames[0].document.querySelector('input').value = this.value;
    })

    document.domain = "test.com"

</script>

</html>

通过a.test.com/a.html访问b.test.com/b.html,实现了跨域访问,效果如下:

postMessage()

window.postMessage()是HTML5的新方法,可以使用它来向其它的window对象发送数据,无论这个window对象是属于同源或不同源,IE8+支持。

postMessage(data, origin)方法接收两个参数

  1. **data:**要传递的数据。html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
  2. **origin:**字符串参数,用来限定接收消息的那个window对象所在的域,如果不想限定域,可以使用通配符 * ,这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"

同样以上面将域的例子来说明postMessage()的原理(A页面中嵌套了一个B页面)

那么我们可以在A页面中通过postMessage()方法向跨域的B页面传递数据

<html>
<style>
    .ct {
        width: 910px;
        margin: auto;
    }

    .main {
        float: left;
        width: 450px;
        height: 300px;
        border: 1px solid #ccc;
    }

    .main input {
        margin: 20px;
        width: 200px;
    }

    .iframe {
        float: right;
    }

    iframe {
        width: 450px;
        height: 300px;
        border: 1px dashed #ccc;
    }
</style>

<div class="ct">
    <h1>使用降域实现跨域</h1>
    <div class="main">
        <input type="text" placeholder="http://a.test.com:8080/a.html">
    </div>

    <iframe src="//b.test.com:8080/b.html" frameborder="0"></iframe>
</div>

<script>

    document.querySelector('.main input').addEventListener('input', function () {
        window.frames[0].postMessage(this.value, '*')
    })

    window.addEventListener('message', function (e) {
         document.querySelector('input').value = e.data
    })
</script>

</html>

那么,我们怎么在B页面上接收A页面传递过来的数据呢,我们只要在B页面监听window的message事件就可以,消息内容储存在该事件对象的data属性中。

<html>
<input id="input" type="text" placeholder="http://b.test.com/b.html">
<script>
    
    document.querySelector('input').addEventListener('input', function () {
        window.parent.postMessage(this.value, '*')
    })

    window.addEventListener('message', function (e) {
         document.querySelector('input').value = e.data
    })

</script>
</html>

同样,如果想要在B页面发送数据,A页面接受数据,只要在B页面使用postMessage()方法,然后在A页面监听window的message事件即可。

最终页面得到的效果如下:

apply、call 、bind有什么作用,什么区别

apply、call 、bind有什么作用,什么区别

apply()、call()方法在指定this值和参数的情况下调用函数,或者说在调用函数时动态的指定执行上下文和参数。

bind()是ES5的新方法,该方法创建一个新的函数(称为绑定函数),当被调用时,第一个参数决定了运行时的this,之后传入的参数将会作为函数执行时的实参来使用。

相同:三者都是用来改变函数的this对象的指向的;第一个参数都是this要指向的对象,也就是想指定的上下文;都可以利用后续参数传参;

不同:bind返回对应的函数,便于稍后调用;apply,call则是立即调用。apply参数是数组对象或类数组形式,call参数是参数列表。

//apply/call/bind的使用
var bar = {
  param: value
}
var foo = {
  getParam:function(){
        return this.param
    },
  getSum:function(a,b){
      return this.param + a + b
  }
}

foo.getX.bind(bar)()
foo.getX.apply(bar)
foo.getSum.call(bar,1,2)
foo.getSum.apply(bar,[1,2])
foo.getSum.bind(bar,1,2)()

模块化

模块化的由来

JS发展初期就是为了实现简单的页面交互逻辑,如今CPU、浏览器性能得到了极大的提升,很多页面逻辑迁移到了客户端(表单验证等),随着web2.0时代的到来,Ajax技术得到广泛应用,jQuery等前端库层出不穷,前端代码日益膨胀。

这时候JavaScript作为嵌入式的脚本语言的定位动摇了,JavaScript却没有为组织代码提供任何明显帮助,甚至没有类的概念,更不用说模块(module)了,JavaScript极其简单的代码组织规范不足以驾驭如此庞大规模的代码。

既然JavaScript不能handle如此大规模的代码,我们可以借鉴一下其它语言是怎么处理大规模程序设计的,在Java中有一个重要的概念——package,逻辑上相关的代码组织到同一个包内,包内是一个相对独立的王国,不用担心命名冲突什么的,那么外部如果使用呢?直接import对应的package即可

import java.util.ArrayList;

遗憾的是JavaScript在设计时定位原因,没有提供类似的功能,开发者需要模拟出类似的功能,来隔离、组织复杂的JavaScript代码,我们称为模块化。

一个模块就是实现特定功能的文件,有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。模块开发需要遵循一定的规范,各行其是就都乱套了

规范形成的过程总是痛苦的,前端的先驱在刀耕火种、茹毛饮血的阶段开始,发展到现在初具规模,我们来简单了解一下这段不凡的历程

函数封装

我们都知道函数的一个功能就是对一组实现特定逻辑的语句进行打包,而且JS的作用域是基于函数的,所以我们首先想到的是把函数作为模块化的第一步

function fn1(){
  //statement
}

function fn2(){
  //statement
}

这样之后我们在使用函数里面的功能的时候只要调用函数即可

但是因为函数的定义是在全局的,可能会发生命名冲突,污染全局变量,而且模块之间也没有关联

对象

为了解决函数封装的缺点,对象的写法应用而生,如下,我们可以把所有模块成员封装在一个对象中

var moduleA = {
  a: 1,
  
  b: 2,
  
  fn1:function() {
    
  },
  
  fn2: function() {
    
  }
}

调用的时候只要引用文件,然后

moduleA.fn1()

这样避免了变量污染,只要保证模块名唯一即可,同时同一模块内的成员也有了关系

看似不错的解决方案,但是也有缺陷,外部可以随意修改内部成员

moduleA.b = 50;

可能导致意想不到的安全问题

立即执行函数

可以通过立即执行函数,来达到隐藏细节的目的

var myModule = (function(){
    var a = 1;
    var b = 2;

    function fn1(){

    }

    function fn2(){

    }

    return {
        fn1: fn1,
        fn2: fn2
    };
})();

这样在模块外部无法修改我们没有暴露出来的变量、函数

上述做法就是我们目前实现模块化的基础,目前,通行的JavaScript模块规范主要有两种:CommonJSAMD

为什么要使用模块化?

主要目的

  • 解决命名冲突
  • 依赖管理

其他价值

  • 提高代码可读性

参考--前端模块化开发的价值

CommonJS 、CMD、AMD规范分别指什么?有哪些应用

CommonJS

首先,CommonJS是服务器端模块的规范,Node.js就是基于这个规范。Node.JS首先实现了JS模块化的概念。

根据CommonJS 规范,CommonJS的基本使用主要由以下三个步骤

  1. 定义模块 根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。
  2. 输出模块 模块只有一个出口,module.exports对象,这里存放的是模块希望输出的内容
  3. 加载模块 加载模块使用require方法,该方法读取一个文件并且执行,返回文件内部的module.exports对象

举个例子:

//定义模块 myModule.js
var name =  'tail'

function printName(){
 console.log(name)
}

function printFullName(firstName){
   console.log(firstName + name);
}

//输出模块
module.exports = {
   printName: printName,
   printFullName: printFullName
}

//加载模块
var nameModule = require('./myModule.js') //不同的实现对require时的路径有不同要求,一般情况可以省略js拓展名,可以使用相对路径,也可以使用绝对路径,甚至可以省略路径直接使用模块名(前提是该模块是系统内置模块)

//使用模块
nameModule.printName();

AMD

AMD 即Asynchronous Module Definition,中文名是异步模块定义的意思。它是一个在浏览器端模块化开发的规范

由于不是JavaScript原生支持,使用AMD规范进行页面开发需要用到对应的库函数,也就是大名鼎鼎RequireJS,实际上AMD 是RequireJS 在推广过程中对模块定义的规范化的产出

RequireJS 主要解决两个问题

  1. 实现js文件的异步加载,避免网页失去响应;js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应时间越长
  2. 管理模块之间的依赖性,多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器,便于代码的编写和维护。
//定义模块myModule.js
define(['依赖1,依赖2,依赖3'],function(依赖1,依赖2,依赖3){
    var name = 'tail';
    function printName(){
        console.log(name);
    }

    return {
        printName: printName
    };
})

//加载模块
require(['myModule'],function(my){
  my.printName(); });
})

语法

requireJS定义了一个函数 define,它是全局变量,用来定义模块

define(id?, dependencies?, factory);

  • id: 定义模块的名字,可选;如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。
  • 依赖dependencies: 是一个当前模块依赖的,已被模块定义的模块标识的数组字面量。 依赖参数是可选的,如果忽略此参数,它应该默认为["require", "exports", "module"]。然而,如果工厂方法的长度属性小于3,加载器会选择以函数的长度属性指定的参数个数调用工厂方法。
  • 工厂方法factory,模块初始化要执行的函数或对象。如果为函数,它应该只被执行一次。如果是对象,此对象应该为模块的输出值。

在页面上使用require函数加载模块

require([dependencies], function(dependencies){});

require()函数接受两个参数

  1. 第一个参数是一个数组,表示所依赖的模块
  2. 第二个参数是一个回调函数,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块

require()函数在加载依赖的函数的时候是异步加载的,这样浏览器不会失去响应,它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

CMD

CMD 即Common Module Definition通用模块定义,CMD规范是国内发展出来的,AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同

语法

Sea.js 推崇一个模块一个文件,遵循统一的写法

define

define(id?, deps?, factory)

因为CMD推崇

  1. 一个文件一个模块,所以经常就用文件名作为模块id
  2. CMD推崇依赖就近,所以一般不在define的参数中写依赖,在factory中写,factory有三个参数
function(require, exports, module)

require

require 是 factory 函数的第一个参数

require(id)

require 是一个方法,接受 模块标识 作为唯一参数,用来获取其他模块提供的接口

exports

exports 是一个对象,用来向外提供模块接口

module

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

//定义模块
define(function(require, exports, module){
  var $ = require('jquery.js')
  $('div').removeClass('active')
})

 //加载模块
seajs.use(['myModule.js'], function(my){
  
})

AMD与CMD区别

关于这两个的区别网上可以搜出一堆文章,简单总结一下

最明显的区别就是在模块定义时对依赖的处理不同

  1. AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块
  2. CMD推崇就近依赖,只有在用到某个模块的时候再去require

这种区别各有优劣,只是语法上的差距,而且requireJS和SeaJS都支持对方的写法

AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时机或者方式不同

很多人说requireJS是异步加载模块,SeaJS是同步加载模块,这么理解实际上是不准确的,其实加载模块都是异步的,只不过AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能来带来开发的便利性,实际上解析模块用的时间短到可以忽略

为什么我们说两个的区别是依赖模块执行时机不同,为什么很多人认为ADM是异步的,CMD是同步的(除了名字的原因。。。)

同样都是异步加载模块,AMD在加载模块完成后就会执行该模块,所有模块都加载执行完后会进入require的回调函数,执行主逻辑,这样的效果就是依赖模块的执行顺序和书写顺序不一定一致,看网络速度,哪个先下载下来,哪个先执行,但是主逻辑一定在所有依赖加载完成后才执行

CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的

这也是很多人说AMD用户体验好,因为没有延迟,依赖模块提前执行了,CMD性能好,因为只有用户需要的时候才执行的原因

HTTP以及浏览器缓存

HTTP 的工作原理

HTTP简介

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是一个客户端终端(用户)和服务器端(网站)请求和响应的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器网关或者隧道(tunnel)。

在了解HTTP如何工作之前,我们需要先了解计算机之间的通信。

计算机之间的通信

互联网的关键技术就是TCP/IP协议。两台计算机之间的通信是通过TCP/IP协议在因特网上进行的。实际上这是两个协议:

  • TCP : Transmission Control Protocol 传输控制协议
  • IP: Internet Protocol 网际协议。

IP:计算机之间的通信
IP协议是计算机用来相互识别的通信的一种机制,每台计算机都有一个IP用来在internet上标识这台计算机。 IP 负责在因特网上发送和接收数据包。通过 IP,消息(或者其他数据)被分割为小的独立的包,并通过因特网在计算机之间传送。IP 负责将每个包路由至它的目的地。

IP协议仅仅是允许计算机相互发消息,但它并不检查消息是否以发送的次序到达而且没有损坏(只检查关键的头数据)。为了提供消息检验功能,直接在IP协议上设计了传输控制协议TCP.

TCP : 应用程序之间的通信
TCP确保数据包以正确的次序到达,并且尝试确认数据包的内容没有改变。TCP在IP地址之上引端口(port),它允许计算机通过网络提供各种服务。

当应用程序希望通过 TCP 与另一个应用程序通信时,它会发送一个通信请求。这个请求必须被送到一个确切的地址。在双方“握手”之后,TCP 将在两个应用程序之间建立一个全双工 (full-duplex) 的通信,占用两个计算机之间整个的通信线路。TCP 用于从应用程序到网络的数据传输控制。TCP 负责在数据传送之前将它们分割为 IP 包,然后在它们到达的时候将它们重组。

TCP/IP 就是TCP 和 IP 两个协议在一起协同工作,有上下层次的关系。

TCP 负责应用软件(比如你的浏览器)和网络软件之间的通信。IP 负责计算机之间的通信。TCP 负责将数据分割并装入 IP 包,IP 负责将包发送至接受者,传输过程要经IP路由器负责根据通信量、网络中的错误或者其他参数来进行正确地寻址,然后在它们到达的时候重新组合它们。

基于HTTP协议的客户(C)/服务器(S)模式的信息交换过程,分为四个过程:建立连接、发送请求信息、发送响应信息、关闭连接。

HTTP工作过程

一次HTTP操作称为一个事物,整个工作过程如下:
以访问https://www.google.co.jp/?gfe_rd=cr&ei=7pVcWfe0Guzd8Afx_JGQCg为例
1. 域名解析: 从URL中解析出协议类型、主机名、端口、向服务端发送的参数,通过域名系统DNS解析域名得到IP地址。
2. 封装HTTP请求数据包: 把上面解析得到的信息结合本机信息封装成一个HTTP请求数据包。
3. 封装成TCP包,建立TCP 连接: 在HTTP开始工作之前,客户端首先要与服务端建立连接,该连接是通过TCP来完成的,该协议与IP协议共同构建Internet,即TCP/IP协议族,HTTP是比TCP更高层次的应用层协议,根据规则,只有低层协议建立之后才能,才能进行更高层协议的连接,因此,首先要建立TCP连接,一般TCP连接的端口号是80。
4. 客户端发送请求命令: 连接后,客户机发送一个请求给服务器,请求方式的格式为:URL(统一资源定位符)、协议版本号,后边是MIME信息,包括请求修饰符、客户端信息。
5. 服务器响应: 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
**6. 一般情况下,**一旦web服务器向浏览器发送了请求数据,它就要关闭TCP连接,如果览器或者服务器在其头信息加入了Connection:keep-alive,这样TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

服务器将响应信息传给客户端,响应体中的内容可能是一个html页面,也可能是一张图片,通过输入流将其读出,并显示到显示器上。

URL 的格式是什么?常见的协议有哪些

通用的URL由9部分组成

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<hash>

对于web页面来说最常用的协议就是http和https

userpassword现在不常见了,不会在URL明文书写用户名和密码了,都是通过登录的方式

主机可以是IPO地址或者域名

端口号用来区分主机上的进程,方便找到web服务器,http默认是80

path是资源的路径,也就是存放位置,不一定和物理路径完全对应,符合web服务器路由约定即可

params在一些协议中需要参数来访问资源,例如ftp是二进制还是文本传输,参数是名值对,用;隔开

query是get请求最常用的传递参数方式了 ?a=1&b=2&=3

hash也称为片段,设计为标识文档的一部分,很多MVVM框架用作了路由功能

常见的网络协议

1.IP协议:互联网协议

主要用于负责IP寻址、路由选择和IP数据包的分割和组装。通常我们所说的IP地址可以理解为符合IP协议的地址。

2.TCP协议:传输控制协议

该协议主要用于在主机间建立一个虚拟连接,以实现高可靠性的数据包交换。IP协议可以进行IP数据包的分割和组装,但是通过IP协议并不能清楚地了解到数据包是否顺利地发送给目标计算机。而使用TCP协议就不同了,在该协议传输模式中在将数据包成功发送给目标计算机后,TCP会要求发送一个确认;如果在某个时限内没有收到确认,那么TCP将重新发送数据包。另外,在传输的过程中,如果接收到无序、丢失以及被破坏的数据包,TCP还可以负责恢复。
3.FTP(File Transfer Protocol):远程文件传输协议,允许用户将远程主机上的文件拷贝到自己的计算机上。

4.HTTP:超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。

5.ARP协议:AddressResolutionProtocol地址解析协议

简单地说,ARP协议主要负责将局域网中的32为IP地址转换为对应的48位物理地址,即网卡的MAC地址。

HTTP 协议有几种和服务器交互的方法

HTTP最大的作用就是客户端发送请求,服务器给出响应,客户端向服务器发送请求的方式有很多

GET

GET是最常用的方法,通常用于请求服务器发送某个资源

我们平时在浏览器输入网页地址,就是给服务器发送了一个get请求,希望得到这个网页

HEAD

HEAD方法和GET类似,但是在服务器的响应中没有资源的内容,只有资源的一些基本信息,主要用于

在不获取资源的情况下获取资源信息(类型、大小等)

通过状态码产看资源是否存在

通过查看首部,测试资源是否被修改了

PUT

和GET从服务器获取资源相反,PUT用于向服务器写入资源。PUT的语义就是让服务器用请求的主体部分创建一个请求URL命名的文档,如果存在就替换

当然处于安全原因,并不是所有的服务器都实现,当然最近大热的RESTful API使它有了用武之地

POST

POST用于想服务器发送数据,通常用来支持HTML的表单(input、select、textarea),表单中的数据会被发送到服务器

TRACE

客户端发送一个请求的时候,这个请求可能会穿过防火墙、代理、网关和一些其它应用程序,每个中间节点都有可能修改HTTP请求,TRACE方法允许客户端在最终请求发往服务器的时候,看看它变成了什么样子

TRACE请求会在目的服务器端发送一个“闭环”诊断,行程最后一站服务器会弹回一条TRACE响应,并在响应主题中携带它收到的原始请求报文

DELETE

DELETE方法用于要求服务器删除请求的URL,和PUT一样,服务器可能会不支持

OPTIONS

OPTIONS方法用于请求 web服务器告知其支持的各种功能

状态码200,301,304,403,404,500,503分别代表什么意思

状态代码 状态信息 含义
200 OK 一切正常,对GET和POST请求的应答文档跟在后面
301 Moved Permanently 客户请求的文档在其他地方,新的URL在Location头中给出,浏览器应该自动地访问新的URL
304 Not Modified 客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告 诉客户,原来缓冲的文档还可以继续使用。
403 Forbidden 资源不可用,服务器理解客户的请求,但拒绝处理它。通常由于服务器上文件或目录的权限设置导致
404 Not Found 无法找到指定位置的资源。这也是一个常用的应答
500 Internal Server Error 服务器遇到了意料不到的情况,不能完成客户的请求。
503 Service Unavailable 服务器由于维护或者负载过重未能应答。例如,Servlet可能在数据库连接池已满的情况下返回503。服务器返回503时可以提供一个 Retry-After头

报文有哪几部分组成?

HTTP报文是简单的格式化数据块,每个报文都包含一条来自客户端的请求或者一条来自服务器的响应,由3个部分组成

对报文进行描述的起始行 —— start line
包含属性的首部块 —— header
可选的包含数据的主体部分 —— body

HTTP/1.0 200 OK				//start line
content-type: text/plain
content-length: 19 			//header

Hi, I'm a message 			//body

起始行和首部就是由行分隔的ASCII文本,主题是一个可选的数据块,可能是文本、二进制或者空

主体的作用是什么?给个范例

主体就是客户端和服务端之间传输的主要数据内容

主体可以是很多类型的数据,包括图片、视频、HTML文档、软件应用程序等

HTTP缓存

Http 缓存机制作为 web 性能优化的重要手段,有如下作用:

  • 减少网络带宽消耗:无论对于网站运营者或者用户,带宽都代表着金钱,过多的带宽消耗,只会便宜了网络运营商。当Web缓存副本被使用时,只会产生极小的网络流量,可以有效的降低运营成本。
  • 降低服务器压力:给网络资源设定有效期之后,用户可以重复使用本地的缓存,减少对源服务器的请求,间接降低服务器的压力。同时,搜索引擎的爬虫也能根据过期机制降低爬取的频率,也能有效降低服务器的压力。
  • 减少网络延迟,加快页面打开速度:带宽对于个人网站运营者来说是十分重要,而对于大型的互联网公司来说,可能有时因为钱多而真的不在乎。那Web缓存还有作用吗?答案是肯定的,对于最终用户,缓存的使用能够明显加快页面打开速度,达到更好的体验。

以下是对HTTP中控制客户端缓存的几种方式以及优先级的梳理

强制缓存

浏览器在请求已经访问过的URL的时候, 会判断是否使用缓存, 判断是否使用缓存主要通过判断缓存是否在有效期内, 通过两个字段来判断:

  • Expires, 有效期, 返回的是一个GMT时间,这是一个绝对时间, 但是使用的是客户端时间, 与服务器时间存在一定时间差
  • Cache-Control => max-age, 最大有效时间,这是一个相对时间, 单位是s, 优先级比expires高, 为了解决expires时间差的问题而出现的

对比缓存

当缓存过期后, 浏览器不会直接去服务器上拿缓存, 而是判断缓存是否有更新, 能否继续使用, 判断的方法有两种:

  • Last-Modified 和 If-Modified-Since: 服务器会响应一个Last-Modified字段, 表示最近一次修改缓存的时间, 当缓存过期后, 浏览器就会把这个时间放在If-Modified-Since去请求服务器, 判断缓存是否有更新
  • Etag和If-None-Match: 服务器会响应一个Etag字段, 一个表示文件唯一的标识符, 一旦文件更新, Etag也会跟着更改, 当缓存过期后, 浏览器会把这个唯一标识符放在If-None-Match去请求服务器, 判断是否有更新, Etag的优先级比Last-Modified的更高

那么既生Last-Modified何生Etag?你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

参考:
彻底弄懂HTTP缓存机制及原理
知乎专栏-浏览器是如何控制缓存的

Promise对象

Promise规范

promise最早是出现在CommonJs社区提出来的,当时出现了好多种规范。接收比较多的是promise/A规范,在此基础上,提出了promise/A+规范,就是目前业内推行的promise规范,ES6采用的也是这种规范。

需要了解规范的可以移步这里

中文版在这里

下面我们来看一下promise的具体用法

promise对象

promise对象表示一个异步操作的最终结果,用来传递异步数据。一个promise对象有三种状态 pending fulfilled rejected,promise对象的初始状态是pending,状态的转变只能是下面两种 (To be or not to be)

  • pending => fulfilled
  • pending => rejected

promise对象的构造函数:

var promise = new Promise(function(resolve,reject){
    if(`报错`){
        reject(new Error('promise出错啦。。'))
    }
  	if(`成功`){
        resolve(data)
    }
    //某个异步操作,比如ajax请求
    setTimeout(function(){
        resolve('异步请求结束了。。变成完成态')
    },1000)
})

Promise的构造函数接收一个参数,是函数,并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数。其实这里用“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promise的状态置为fullfiled,reject是将Promise的状态置为rejected。不过在我们开始阶段可以先这么理解,后面再细究概念。

这里需要注意的是new出来的promise对象会立即执行,所以我们在使用promise对象的时候一般把它包裹在一个函数中,在需要的时候去调用这个函数,比如

function runPromise(){
    return new Promise(function(resolve,reject){
        //异步操作
      	setTimeout(function(){
            console.log('执行完成');
          	resolve(data)
        },2000)
    })
}

//调用
runPromise()

then方法

我们可以使用promise对象的then方法往这个对象里面添加回调函数。调用方式为:

promise.then(onFulfilled, onRejected)

then接受一个成功回调,还有失败回调。都是可选参数。
当promise对象是pending状态时这些函数不会立即执行。而是等待。
当promise对象变成了Fulfilled态会调用onFulfilled,参数为resolve传递的值。
变成rejected状态则会调用onRejected,参数为reject传递的值。

根据规范then方法返回一个新的promise对象。所以支持链式调用:

promise.then(onFulfilled, onRejected).then(onFulfilled, onRejected)

then负责往promise对象里添加回调函数,随便添加多少。

  • 如果promise对象处于pending状态就等待。一直到改变状态才开始执行。
  • 如果promise对象已经处于结束态(成功或者失败)再用then添加回调就直接调用对应的回调。
  • 此外前一个onFulfilled函数的返回值如果不是promise。会作为下一个onFulfilled的参数。onRejected类似。

看下面:

//onRejected可以为null也可以省略
promise.then(function(prevValue){
    console.log('resolve的值:'+prevValue)
    return "我是传递给第二个回调的参数"
},null).then(function(value){
    console.log('报告:'+ value)
    console.log('我是最后一个')
})

/*
----------执行结果--------------------------------
resolve的值:异步请求结束了。。变成完成态
报告:我是传递给第二个回调的参数
我是最后一个
*/

可以看到一直等到前面的异步操作结束了,后面的才会执行。

此外如果onFulfilled返回一个新的promise对象,那么之后的then添加的操作函数会被托管给新的promise对象。然后之后的操作函数执不执行就由新的promise对象说了算了。

比如:

promise.then(function(prevValue){

    console.log('resolve的值:'+prevValue)

    var newPromise =  new Promise(function(resolve,reject){
         setTimeout(function(){
            resolve('2秒后,新的promise变成完成态')
         },2000)

    })
    //返回新的promise
    console.log('返回一个promise,开始托管。')
    return newPromise

},null).then(function(value){
    console.log('报告:'+ value)
    console.log('我是最后一个')
})
/*
*结果
resolve的值:异步请求结束了。。变成完成态
返回一个promise,开始托管。
报告:2秒后,新的promise变成完成态
我是最后一个
*/

catch方法

catch方法是特殊的then方法,专门用来捕获上一个then方法里面reject过来的错误

promise.catch(function(error) {
  console.log('发生错误!', error);
});

//等价于
promise.then(null,function(error) {
  console.log('发生错误!', error);
});

all方法

Promise.all用来包装一系列的promise对象返回一个包装后的promise对象,比如我们称之为A。

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。

这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:

  • 当所有的promise对象都变成成功状态(fulfilled)后。这个包装后的A才会把自己变成成功态。A会等最慢的那个promise对象变成成功态(fulfilled)后把自己变成成功态。
var a = new Promise(function(resolve,reject){})
var b = Promise.resolve(true)

Promise.all([a,b,c])

race方法

Promise.race也是用来包装一系列的promise对象返回一个包装后的promise对象,比如我们称之为B。跟all不同的是,只要有一个对象变成了成功状态(fulfilled),B就会变成成功状态。

  • 只要其中一个promise对象变成失败态(rejected),包装后的A就变成rejected,并且第一个rejected传递的值,会传递给A后面使用then添加的onRejected回调。
Promise.race([a,b,c]).then()

这里需要注意的是,then里面的回调开始执行时,b和c并没有停止,依然在执行。

参考文档

JS中的Event Loop (Macrotask与Microtask)

要想搞明白Javascript的Event Loop我们首先要了解Javascript的运行环境的运行机制。

Javascript是单线程的

我们都知道JS的最大特点是单线程,这就意味着所有的任务需要排队,后一个任务执行,需要等待前一个任务执行完毕,如果前一个任务执行时间比较久,后一个任务就需要一直等待。等待的过程是可能是处理跟不上或者是硬件I/O效率(比如Ajax操作从网络读取数据)导致的。
Javascript语言的设计者为了解决这个等待问题,让主线程执行的时不管I/O,挂起处于等待中的任务,先运行排在后面的任务。等到I/O返回了结果,再回过头,把挂起的任务继续执行下去。这就是异步任务的执行机制。这样,Javascript就有了同步任务和异步任务两种。

任务队列(Macrotask queue和Microtask queue)

Javascript中的同步任务都是在主线程上执行的,异步任务则是由浏览器执行的,不管是AJAX请求,还是setTimeout等 API,浏览器内核会在其它线程中执行这些操作,当操作完成后,将操作结果以及事先定义的回调函数放入JavaScript 主线程的任务队列中。

在Javascript的Event Loop机制中,存在两个任务队列:Macrotask queue和Microtask queue。

  • macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
  • microtasks: process.nextTick, Promises, Object.observe(废弃), MutationObserver

Macrotask还是Microtask?

可以这样简单理解:**如果你想让一个异步任务尽快执行,那么就把它设置为Microtask,除此之外都用Macrotask。**因为,虽然Javascript是异步非阻塞的,但在一个事件循环中,Microtask的执行方式基本上就是用同步的。

Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future). Otherwise, stick to macrotasks.

可能存在的问题

相信读到这里你已经意识到,如果一个Microtask队列太长,或者执行过程中不断加入新的Microtask任务,会导致下一个Macrotask任务很久都执行不了。结果就是,你可能会遇到UI一直刷新不了,或者I/O任务一直完成不了。

或许是考虑到了这一点,至少Microtask queue中的process.nextTick任务,是被设置了(在一个事件循环中的)最大调用次数process.maxTickDepth的,默认是1000。一定程度上避免了上述情况。

Event Loop

按照 WHATWG 规范,每个Event Loop周期内,会经历如下步骤:

  1. 检查Macrotask queue,把最先入列的Macrotask压入执行栈(每个Event Loop周期内只会执行一个Macrotask),开始执行,如果Macrotask queue为空,跳到步骤3。
  2. 待该 Macrotask执行完毕后,清空执行栈。
  3. 遍历执行Microtask queue中的Microtask。遍历这些 Microtask 的过程中,还可以将更多的 Microtask加 入Microtask queue,它们会一一执行,直到整个 Microtask 队列处理完。
  4. 执行完Microtask queue中的所有Microtask后,回到步骤1。

以上就是Event Loop的循环机制。

参考
Understanding the Node.js Event Loop

理解 Node.js 事件循环

Difference between microtask and macrotask within an event loop context

Linux命令行

Linux命令行的一般格式

命令 [长选项列表] [短选项列表] [参数列表]

其中,长选项是指以双横线引导的选项,短选项是指以单横线引导的单个字母,而且不同的短选项可
以合并成只使用一个短横线引导,例如-a -b可以合并成-ab的形式;参数是指前面没有短横线引导的字母
或短语。

基本命令

ls(列出目录)

   语法 ls [option] 
   选项与参数:
          -a: 列出当前目录全部文件 ,连同开头为. 的文件一起列出来
          -d: 仅列出目录本身,而不是列出目录内的文件数据
          -l: 长数据列出,包含文件的属性与权限等数据
          -la: 将目录下的所有文件列出来(含属性等)

cd(切换目录)

   语法 cd [相对路径或绝对路径]
          cd /a/b 使用绝对路径切换到b目录 
          cd ./b 使用相对路径切换到b目录
          cd ~ 切换到根目录
          cd .. 回到上一级目录

(显示当前所在目录)

   语法 pwd

mkdir(创建新目录)

   语法 mkdir [option] 目录名称
   选项与参数:
          -m: 配置文件的权限喔
          -p : 帮助你直接将所需要的目录(包含上一级目录)递归创建起来
                 mkdir -p test1/test2/test3/test4

touch(创建文件或修改文件时间)

   touch [options] file
   选项与参数:
   -a 只更新访问时间,不改变修改时间
   -c 不创建不存在的文件
   -m 只更新修改时间,不改变访问时间
   -r file 使用文件file的时间更新文件的时间
   -t 将时间修改为参数指定的日期,如:07081556代表7月8号15点56分

主要用来创建新文件

rmdir (删除空的目录)

   语法:rmdir [-p] 目录名称
   选项与参数:
          -p :连同上一级『空的』目录也一起删除

rm (移除文件或目录)

   语法:rm [-fir] 文件或目录
   选项与参数:
   -f :就是 force 的意思,忽略不存在的文件,不会出现警告信息
   -i :互动模式,在删除前会询问使用者是否动作
   -r :递归删除

mv (移动文件与目录,或重命名)

   语法:  mv [-fiu] source destination
          mv [options] source1 source2 source3 .... directory
   选项与参数:
          -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖;
          -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖!
          -u :若目标文件已经存在,且 source 比较新,才会升级 (update
          cd /DIR
          cp ~/.FILE FILE  复制一文件
          mkdir SUBDIR         创建一目录
          mv bashrc DIR  将文件移动到目录中
          mv SUBDIR SUBDIR2    将SUBDIR更名为 SUBDIR2

cp(复制文件)

  语法:  cp [options] source destination
  选项与参数:
         -r: 递归处理,将指定目录下的文件与子目录一并复制

vim(切换到vim模式)

vim name编辑name文件
i: 编辑文件
:wq 保存并写入

使用CSS实现栅格布局

使用CSS实现栅格布局

栅格系统暴露给开发者的概念只有行(Row)和列(Column),但其内部实现还是CSS布局的应用,实现一响应式的栅格布局我们可以使用css grid, Flextbox, 或者float,目前而言,为了浏览器兼容,我们选择使用float布局来实现栅格系统
一个栅格系统主要包含四部分 1. Container(容器) 2.Row(行) 3. Column(列) 4. Gutter(列间距)

首先,我们来定义Container

/* 容器样式设置 */  
.container, 
.container-fluid {
  padding-left: 15px;/* 设置padding是为了后面Column直接嵌套Row预留 */
  padding-right: 15px;
  margin-left: auto;
  margin-right: auto;
}

.container *,
.container-fluid * {
    box-sizing: border-box;  
}

然后,我们来定义Row

.row {
  margin-left: -15px;
  margin-right: -15px;  /* Container设置了padding,所以Row需要设置-margin让Row占满Container的宽度 */
}

.row:after { /* float会引起父元素的高度塌陷,所以要使用一些clearfix技巧来清除浮动 */
  content: "";
  display: table;
  clear: both;
}       

接下来,我们来定义Column,同时要考虑Gutter的设置

 [class*='col-'] {
   float: left;
   min-height: 1px; 
   padding: 15px;  /* Gutter的设置 同时和前面Container设置的一样,所以Column里面可以直接嵌套Row,而不需要Container */
   border: 1px solid #F6A1A1;
   background-color: #FFDCDC;
}

这里,我们定义每个单元列为Container的1/12,SASS代码如下:

@for $i from 1 through 12 {
  .col-#{$i} {
    width: $i/12 * 100%;
  }
}

编译后的css

.col-1 {
    width: 8.33333%;
}

.col-2 {
    width: 16.66667%;
}

.col-3 {
    width: 25%;
}

.col-4 {
    width: 33.33333%;
}

.col-5 {
    width: 41.66667%;
}

.col-6 {
    width: 50%;
}

.col-7 {
    width: 58.33333%;
}

.col-8 {
    width: 66.66667%;
}

.col-9 {
    width: 75%;
}

.col-10 {
    width: 83.33333%;
}

.col-11 {
    width: 91.66667%;
}

.col-12 {
    width: 100%;
}

到这里,我们就实现了一个简单的12列栅格系统

demo---栅格系统

响应式

然后我们来通过media query实现带响应式的栅格系统

/* 小屏幕 平板等 屏幕宽度大于等于768px */
@media (min-width: 768px) {
  .col-sm-1 {
    width: 8.33333%;
  }
  .col-sm-2 {
    width: 16.66667%;
  }
  .col-sm-3 {
    width: 25%;
  }
  .col-sm-4 {
    width: 33.33333%;
  }
  .col-sm-5 {
    width: 41.66667%;
  }
  .col-sm-6 {
    width: 50%;
  }
  .col-sm-7 {
    width: 58.33333%;
  }
  .col-sm-8 {
    width: 66.66667%;
  }
  .col-sm-9 {
    width: 75%;
  }
  .col-sm-10 {
    width: 83.33333%;
  }
  .col-sm-11 {
    width: 91.66667%;
  }
  .col-sm-12 {
    width: 100%;
  }
}
/* 中等屏幕 桌面显示器等 屏幕宽度大于等于992px */

@media (min-width: 992px) {
  .col-md-1 {
    width: 8.33333%;
  }
  .col-md-2 {
    width: 16.66667%;
  }
  .col-md-3 {
    width: 25%;
  }
  .col-md-4 {
    width: 33.33333%;
  }
  .col-md-5 {
    width: 41.66667%;
  }
  .col-md-6 {
    width: 50%;
  }
  .col-md-7 {
    width: 58.33333%;
  }
  .col-md-8 {
    width: 66.66667%;
  }
  .col-md-9 {
    width: 75%;
  }
  .col-md-10 {
    width: 83.33333%;
  }
  .col-md-11 {
    width: 91.66667%;
  }
  .col-md-12 {
    width: 100%;
  }
}
/* 大屏幕 大桌面显示器等 屏幕宽度大于等于1200px */

@media (min-width: 1200px) {
  .col-lg-1 {
    width: 8.33333%;
  }
  .col-lg-2 {
    width: 16.66667%;
  }
  .col-lg-3 {
    width: 25%;
  }
  .col-lg-4 {
    width: 33.33333%;
  }
  .col-lg-5 {
    width: 41.66667%;
  }
  .col-lg-6 {
    width: 50%;
  }
  .col-lg-7 {
    width: 58.33333%;
  }
  .col-lg-8 {
    width: 66.66667%;
  }
  .col-lg-9 {
    width: 75%;
  }
  .col-lg-10 {
    width: 83.33333%;
  }
  .col-lg-11 {
    width: 91.66667%;
  }
  .col-lg-12 {
    width: 100%;
  }
}

demo---带响应式的栅格系统

XSS 和 CSRF

前端安全—XSS

概念

跨站脚本(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言

XSS攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但实际上也可以包括JavaVBScriptActiveXFlash或者甚至是普通的HTML。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话cookie等各种内容。------------ wikipedia

XSS的目标是让其他站点的js文件运行在目标站点的上,这主要发生在页面渲染阶段。在该阶段发生了某些非预期的脚本行为,该脚本可能来自用户的输入,也可能来自域外的其他js文件,不一而足。XSS的发生起源来自于用户输入,因此XSS根据用户输入数据以何种形式、何时触发XSS、是否有后端服务器的参与划分为三种类型,分别是( Reflected XSS)反射型XSS、(Stored XSS)持久型XSS和(DOM-based or local XSS)DOM XSS。

  • 反射型XSS

基于反射型的XSS攻击,原理是网站站点的服务端返回脚本,网站用户在客户端触发执行从而发起Web攻击 。

当用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。Web服务器将注入脚本,比如一个错误信息,搜索结果等 返回到用户的浏览器上。浏览器会执行这段脚本,因为,它认为这个响应来自可信任的服务器。

  • 存储型XSS

注入型脚本永久存储在目标服务器上。当浏览器请求数据时,脚本从服务器上传回并执行。

  • 基于DOM的XSS

被执行的恶意脚本会修改页面脚本结构。

XSS 的成因以及如何避免

之所以恶意脚本能直接执行,有两个可能

  1. 后台模板问题
<p>
评论内容:<?php echo $content; ?>
</p>

$content 的内容,没有经过任何过滤,原样输出。

要解决这个原因,只需要后台输出的时候,将可疑的符号 < 符号变成 &lt; (HTML实体)就行。

  1. 前端代码问题
$p.html(content)

或者

$p = $('<p>'+ content +'</p>')

content 内容又被原样输出了。解决办法就是不要自己拼 HTML,尽量使用 text 方法。如果一定要使用 HTML,就把可疑符号变成 HTML 实体。

  1. 其它
  • 将重要的cookie标记为http only

这样的话当浏览器向Web服务器发起请求的时就会带上cookie字段,但是在脚本中却不能访问这个cookie,这样就避免了XSS攻击利用JavaScript的document.cookie获取cookie

  • 使用XSS Filter

输入过滤,对用户提交的数据进行有效性验证,仅接受指定长度范围内并符合我们期望格式的的内容提交,阻止或者忽略除此外的其他任何数据。

*参考

XSS 和 CSRF 攻击的一些非常规防御方法

Promise的简单伪实现

MyPromise的简单伪实现

Promise对象表示一个异步操作的最终结果,用来传递异步传输的数据。实现Promise之前我们需要先了解一下Promise/A+规范

规范主要有以下几点:

  1. Promise有三种状态,pending、已完成fulfilled、rejected
  2. Promise的状态只能从pending转到fullfilled或者从pending转向rejected,不能逆向转换,同时fullfilled和rejected之间也不能相互转换
  3. Promise必须有一个then方法,而且要返回一个Promise,供then方法的链式调用,也就是可thenable的
  4. then接受俩个回调函数(resolve与reject),在相应的状态转变时触发,回调可返回Promise,等待此Promise被resolved后,继续触发then链式调用

知道这几个重要的特点,我们就可以参考浏览器内置的api来实现一个Promise了,本文使用ES6语法

  • 首先,我们来看看Promise是如何使用的,以及常用的一些API
let P = new Promise()

let func1 = () => {
    console.log('func1')
    setTimeout(() => {
        P.resolve('1')
    }, 1000)
    return P
}

let func2 = result => {
    console.log('func2', result)
    setTimeout(() => {
        P.reject('2')
    }, 1000)
}

let func3 = result => {
    console.log('func3', result)
    setTimeout(() => {
        P.resolve('3')
    }, 1000)
}

let func4 = result => {
    console.log('func4', result)
    setTimeout(() => {
        P.resolve('4')
    }, 1000)
}

let func5 = result => {
    console.log('func5', result)
}

//调用
func1().then(func2, func3).then(func3).then(func4).catch(func5)

以上就是一个Promise的基本使用,通过分析上面的使用方法,我们发现Promise对象拥有then和catch方法,同时可以链式调用,接下来我们参照上面的案列来实现自己的Promise---MyPromise

  • 第一步 我们创建一个MyPromise类,拥有then和catch方法,
calss MyPromise {
  constructor(){

  }
  then(){

  }
  catch(){

  }
}
  • 第二步 添加状态管理,因为有fullfiled和rejected两种状态,那么我们就添加resolve方法和reject方法进行状态管理
calss MyPromise {
  constructor(){

  }
  then(){

  }
  catch(){

  }
  // fullfiled状态的管理
  resolve() {

  }

  // rejected状态的管理
  reject() {

  }
}
  • 第三步 注册回调函数与执行回调,设定一个数组对需要执行的方法进行暂存,以及一个方法的执行器,该执行器依赖于resolve和reject传入的状态进行相应的执行
calss MyPromise {
  constructor(){
    this.callbacks = []	//回调函数的储存容器
  }
  then(){

  }
  catch(){

  }

  resolve() {	 // fullfilled的管理

  }

  reject() {	  // rejected的管理

  }

  execute(){	//回调函数触发方法

  }
}
  • 第四步 编写then函数与执行器中的逻辑
    在写then函数之前,先看看最开始Promise的调用方式 func1().then(func2,func3).then(func4).catch(fuc5),then方法接收两个参数,一个成功回调一个失败回调,同时可以进行链式调用
then(onSuccess, onFail) {
  this.callbacks.push({
    resolve: onSuccess,
    reject: onFail
  })
  return this	//链式调用
}

这时候在调用func1时他会先返回Promise对象,然后再调用setTimeout里面的resolve回调并传入参数,而在resolve函数中调用了执行器execute,并且传入了resolve这个状态和在func1中传入的参数;

// fullfilled的管理
resolve(result) {
  this.execute('resolve', result)
}

// rejected的管理
reject(result) {
  this.execute('reject', result)
}

// 执行execute函数,其实分析到了这一步就很简单了,不过是将先前传入callbaks中的函数取出来,然后执行其中的成功回调就是了

execute(status, result) {
    // 取出之前传入的回调函数对象(包含成功和失败回调),然后执行
    let handlerObj = this.callbacks.shift()
    handlerObj[type](result)
}

整体代码

class MyPromise {
    constructor() {
        this.callbacks = []
    }

   then(onSuccess, onFail) {
     this.callbacks.push({
       resolve: onSuccess,
       reject: onFail
     })
     return this	//链式调用
   }

    catch (fail) {

        return this
    }

    resolve(result) {
        this.actuator('resolve', result)   // fullfilled的管理
    }

    reject(result) {
        this.actuator('reject', result)    // rejected的管理
    }

    // 执行器
    execute(status, result) {
        // 取出之前传入的回调函数,执行
        let handlerObj = this.callbacks.shift()
        handlerObj[status](result)
    }
}
  • 其实到了这一步,Promise的基本功能then(以及回调resolve和reject)已经实现了,接下来我们来实现catch方法,catch方法就是在Promise变成rejected状态的时候,调用执行的回调

    class MyPromise {
       constructor() {
           this.callbacks = [] //then的回调
           this.catchcallback = null //catach的回调
       }
     
     	//此处省略其他代码...............
       
       catch (onFail) {
           this.catchcallback = onFail // 保存传入的失败回调
           return this // 用于链式调用
       }
     
     
     	//此处省略其他代码..............
     
     
       // 执行器
       execute(status, result) {
           if (status === 'reject' && this.catchcallback) {
               this.callbacks = [] //catch方法的处理
               this.catchcallback(result)
           } else if (this.callbacks[0]) {
               // 取出之前传入的回调函数对象(包含成功和失败回调),然后执行
               let handlerObj = this.callbacks.shift()
               handlerObj[status](result)
           }
       }
    }  

下面来看看func1().then(func2, func3).then(func3).then(func4).catch(func5)的执行结果吧

  1. 全部fullfilled状态

  1. fun2为rejected状态

至此,一个Promise的简单伪实现就完成了,行文大概理清了promise的工作原理。

ES6中的super关键字

ES6中的super关键字

  1. 子类必须在constructor方法中调用super方法,否则新建子类实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

  2. ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。

  3. 如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

  4. 另一个需要注意的地方是,在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

super可以当做对象使用,也可以当做函数使用,两种情况的用法完全不同

  • 当做函数使用

    当做函数使用的super关键字,代表的是父类的构造函数,ES6要求,子类的构造函数必须执行一次super函数,而且super只能用在子类的构造函数之中,用在其它地方会报错的

class A{}
class B extends A {
  constructor () {
    super();//代表父类A上的constructor
  }
}

但是这里需要注意的是super虽然代表了父类的构造函数,但是返回的是子类的实例,就是说super内部的this指向子类,所以super()在这里相当于A.prototype.constructor.call(this).

  • 当做对象使用
    super当做对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class A {
  p(){
    return 2
  }
}
class B extens A{
  constructor() {
    super()
    console.log(super.p())  // 2
  }
}
let b = new B()

上面的代码中,子类B当中的super.p(),就是将super当作一个对象使用。这时,super在普通方法中,指向的是A.prototype,super.p()就相当于A.prototype.p()。

这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。

class A {
  constructor() {
    this.p = 2;
  }
}

class B extends A {
  get m() {
    return super.p;
  }
}

let b = new B();
b.m // undefined

上面代码中,p是父类A实例的属性,super.p就引用不到它。

如果属性定义在父类的原型对象上,super就可以取到。

class A {}
A.prototype.x = 2;

class B extends A {
  constructor() {
    super();
    console.log(super.x) // 2
  }
}

let b = new B();

ES6 规定,通过super调用父类的方法时,super会绑定子类的this。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.