何學習HTML語言。
初學者有一個常見的錯誤就是猶豫于判斷哪種編程語言是做好的、最該先學的。 我們有很多的選擇,但你不能說那種語言“最好”。
我們應該理解:說到底,什么語言并不重要。 重要的是理解數據結構、控制邏輯和設計模式。 任何一種語言—甚至一種簡單的腳本語言—都會具有所有編程語言都共有的各種特征,也就是說各種語言是貫通的。
我編程使用Pascal,匯編,和C語言,事實上我從來沒有把它當成職業以求獲得回報。 我一直在自學編程,工作上用不到它,我使用現有的知識,參考各種文檔和書本,學習它們的用法。
因此,不要急于選擇何種編程語言。 找出你想要開發的東西,使用一種能夠完成這項任務的語言,這就可以了。
這里以html為例說一下:
html的意義就在于放置內容,無論是文本還是圖片,它們都屬于內容。
按照規定,所有的內容都必須放在容器里,否則無法享受五折優惠…對不起走錯片場了。而容器就是html的標簽,語法結構是:
<容器名>各種文本內容</容器名>
容器名當然是英文字母的,并且有許多種。它們的規則很簡單:
1.一般成對出現,一個表開始一個表結束
2.結束的那個前面需要加一條斜線
3.容器名放在尖角符號內
另外,容器之間可以嵌套,也就是說容器里面可以再放容器。
到這里為止,你已經可以開始寫html了,隨便百度一下html有哪些標簽,然后跟著套用即可。
但如果還是有點懵,可以按如下順序做一遍:
1.找一個盡可能簡單的html源碼頁面看一遍,把每個標簽干嘛的鬧清楚
2.假裝有一篇word文檔要轉成html,親手寫一遍寫一個html頁面
3.沒了,html入門真的就那么簡單……
桌面腳本
想要動手在Windows里或蘋果系統里編程,最簡單的方法是從一種腳本語言或宏語言開始,例如AutoHotkey(Windows) 或Automator(蘋果系統)。 如今一些硬件程序員沖著他們的屏幕大喊大叫,說AHK和AppleScript并不是“真正”的編程語言。 也許他們說的是對的—技術上,這些種類的語言只能做一些上層的編程。 但是對于那些只是想來脫盲、想在他們的電腦里實現一些能自動運行的程序的新手來說,這些語言會是一個絕妙的入門入口—而且你會吃驚于它們豐富的功能。
Web開發
HTML 和 CSS:開發網站,你第一件要知道的事情就是HTML(網頁就是由它組成的)和CSS(一種讓外觀更好看的樣式標記)。 HTML 和 CSS 并不是編程語言—它們只是頁面的結構和樣式信息。 然而,在開始開發web應用程序之前你必須要學會如何手工的編寫簡單的HTML和CSS,web頁面是任何webapp的前端顯示部分。 這個HTML 指導是你入手的好地方。
JavaScript:當你可以通過HTML和CSS構建一個靜態頁面后,事情就開始變得有趣了—因為到了該學JavaScript的時候了。 JavaScript是一種web瀏覽器上的編程語言,它的魔力就是能在頁面里制造一些動態效果。
JavaScript可以做bookmarklets,Greasemonkey腳本, 和Ajax, 所以它是web上各種好東西的關于因素。學習JavaScript從這里開。
服務器端腳本:
一旦你學會了網頁里的知識,你就要開始對它添加一些動態服務器操作—為了實現這些,你需要把目光轉移到服務器端腳本語言,例如PHP, Python, Perl, 或 Ruby。
舉個例子,如果想要制作一個網頁形式的聯系方式表單,根據用戶的輸入發送郵件,你就需要使用服務器端腳本來實現。 像PHP這樣的腳本語言可以讓你跟web服務器上的數據庫進行溝通,所以如果你想搭建一個用戶可以登錄注冊的網站,這樣的語言正是你需要的。
Web框架:
過去數年里,web開發人員在開發動態網站的過程中不得不一遍又一遍的針對重復遇到的問題寫出重復的代碼。 為了避免這種每次開發一些新網站都會重復勞動一次的問題,一些程序員動手搭建了一些框架,讓框架替我們完成重復性的工作。
非常流行的Ruby on Rails框架,作為一個例子,它利用Ruby編程語言,為我們提供了一個專門面向web的架構,普通的web應用程序都能使用它來完成。 事實上,Adam使用Rails開發了他的第一個正式的(而且是嘆為觀止的!)web應用程序,MixTape.me。這就是他的如何在沒有任何經驗的情況下搭建一個網站。還有一些其他的web開發框架包括CakePHP(針對 PHP 編程者),Django(針對 Python 編程中), 以及jQuery(針對 JavaScript).
Web APIs:
API (應用層序編程接口)是指不同的軟件之間相互交換的程序途徑。 例如,如果你想在你的網站上放一個動態的地圖,你可以使用Google Map,而不需要開發自己的地圖。The Google Maps API可以輕松的讓你通過JavaScript在程序中引入一個地圖到你的頁面上。 幾乎所有的現代的你所知道的和喜愛的web服務都提供了API,通過這些API你可以獲取到他們的數據和小工具,在你的應用程序里就可以使用這些交互過來的東西了,例如Twitter, Facebook, Google Docs, Google Maps, 這個列表遠不止這些。
通過API把其他web應用集成到你的web應用里是現在富web開發的前沿地帶。 每個優秀的主流的web服務API都附帶有完整的文檔和一些快速入手的指導(例如,這個就是Twitter的)。 瘋狂吧。
命令行腳本
如果你想開發一個程序,讓它讀取文字或文件、輸入輸出一些有用的東西,那么,命令行腳本語言將是個不錯的選擇。 然而它并不像web應用程序和桌面應用程序那樣有吸引力和好看的外觀,但是作為快速開發的腳本語言,你卻不能忽視它們。
很多的在linux平臺上運行的web腳本同樣能以命令行模式運行,例如Perl,Python和PHP,所以如果你學會了使用它們,你將能在兩種環境中使用它們。 我的學習道路一直沒離開Perl太遠,我自學Python使用的是這本優秀的在線免費書Dive into Python。
如果成為一個Unix高手也是你學習的目標,那么你絕對要精通bash這個腳本語言。 Bash是Unix和Linux環境下的一種命令行腳本語言,它能夠為你做所以的事情:從自動備份數據庫腳本到功能齊全的用戶交互程序。 起初我沒有任何使用bash腳本的經驗,但最終我用bash開發了一個全功能的個人代辦任務管理器:Todo.txt CLI。
插件(Add-ons)
如今的web應用程序和瀏覽器都可以通過一些擴展軟件來豐富自己的功能。 由于一些現有的軟件,例如Firefox、WordPress越來越受到開發人員的關注,插件的開發也日益流行,人們都在說“But if only it could do THIS…”
只要你掌握了HTML,JavaScript和CSS,你就可以在任何的瀏覽器里開發你想要的很多東西。 Bookmarklets,Greasemonkeyuser scripts, 和Stylishuser styles這些軟件都是用的更普通頁面一樣的語言寫成的, 這幾個東西都值得你去研究一些。
更高級的瀏覽器擴展程序,例如Firefox的擴展,它們可以幫助你很多。 開發Firefox的擴展.
舉個例子,需要你精通JavaScript和XML(一種標記語言,類似HTML,但具有更嚴格的格式)。 早在2007年我就寫下來how to build a Firefox extension, 這是我在笨手笨腳的研究網上的一些學習資料后獲得的成果。
很多免費的、受歡迎的web應用程序都提供了擴展框架,例如WordPress 和 MediaWiki。 這些應用程序都是用PHP寫成的,所以只有對PHP熟悉你才能做這些事情。 這個就是如何編寫WordPress插件。 而想駕馭Google Wave前沿技術的開發人員可以從使用HTML, JavaScript, Java, 和 Python 寫小組件和小工具開始。 我寫的第一個Wave bot是跟著這個一個下午時間的快速入門指導開始的。
開發桌面上的Web應用程序
學習編程最好的結果是你在一個環境下學的東西可以應用到另外的環境中。 先學習開發web應用程序的好處就是我們有一些方法可以讓web應用程序直接在桌面上運行。 例如,Adobe AIR是一個跨平臺的即時運行平臺,它能讓你編寫的程序運行在任何裝有AIR的操作系統的桌面上。
AIR應用程序都是由HTML, Flash, 或 Flex 寫成的,所以它能讓你的web程序在桌面環境中運行。 AIR是開發部署桌面應用程序的一個優秀的選擇,就像我們提到過的10個讓你值得去安裝AIR的應用程序。
- 移動應用開發
能在iPhone或者Android智能手機上運行的手機應用程序的開發如今正呈現井噴之勢,所以你也可以夢想一下如何在iTunes應用商店里通過你的天才程序大賺一筆。 但是,作為一個編碼新手,直接奔向移動開發所經歷的學習曲線可能會很陡,因為它需要你熟悉高級的編程語言,例如Java和Objective C。
然而,你當然應該看看iPhone 和 Android 編程究竟是什么樣子的。 閱讀這個簡單的iPhone應用開發例子可以初步認識一下iPhone程序的開發過程。 Android 程序都是由Java寫成的,這有一個簡單的視頻教程教你如何開發第一個”Hello Android“程序
耐心,刻苦,嘗試,失敗
作為一個好的程序員,都有一個不達目的誓不罷休的品質,他們會驚喜于通過長期推敲和失敗換來的一點成績。 學會編程會有很好的回報的,但是學習的過程可能會是飽受挫折和孤獨的。 如果有可能,最好找個伴一起陪你做這件事。 想精通編程,這和其他事情一樣,需要堅持,反復嘗試,獲得更多的經驗。
his是JavaScript中的一個關鍵字,但是又一個相對比較特別的關鍵字,不像function、var、for、if這些關鍵字一樣,可以很清楚的搞清楚它到底是如何使用的。
this會在執行上下文中綁定一個對象,但是是根據什么條件綁定的呢?在不同的執行條件下會綁定不同的對象,這也是讓人捉摸不定的地方。
這一次,我們一起來徹底搞定this到底是如何綁定的吧!
在常見的編程語言中,幾乎都有this這個關鍵字(Objective-C中使用的是self),但是JavaScript中的this和常見的面向對象語言中的this不太一樣:
使用this有什么意義呢?下面的代碼中,我們通過對象字面量創建出來一個對象,當我們調用對象的方法時,希望將對象的名稱一起進行打印。
如果沒有this,那么我們的代碼會是下面的寫法:
var obj={
name: "why",
running: function() {
console.log(obj.name + " running");
},
eating: function() {
console.log(obj.name + " eating");
},
studying: function() {
console.log(obj.name + " studying");
}
}
事實上,上面的代碼,在實際開發中,我們都會使用this來進行優化:
var obj={
name: "why",
running: function() {
console.log(this.name + " running");
},
eating: function() {
console.log(this.name + " eating");
},
studying: function() {
console.log(this.name + " studying");
}
}
所以我們會發現,在某些函數或者方法的編寫中,this可以讓我們更加便捷的方式來引用對象,在進行一些API設計時,代碼更加的簡潔和易于復用。
當然,上面只是應用this的一個場景而已,開發中使用到this的場景到處都是,這也是為什么它不容易理解的原因。
我們先說一個最簡單的,this在全局作用域下指向什么?
console.log(this); // window
var name="why";
console.log(this.name); // why
console.log(window.name); // why
但是,開發中很少直接在全局作用域下去使用this,通常都是在函數中使用。
所有的函數在被調用時,都會創建一個執行上下文:
我們先來看一個讓人困惑的問題:
// 定義一個函數
function foo() {
console.log(this);
}
// 1.調用方式一: 直接調用
foo(); // window
// 2.調用方式二: 將foo放到一個對象中,再調用
var obj={
name: "why",
foo: foo
}
obj.foo() // obj對象
// 3.調用方式三: 通過call/apply調用
foo.call("abc"); // String {"abc"}對象
上面的案例可以給我們什么樣的啟示呢?
那么this到底是怎么樣的綁定規則呢?一起來學習一下吧
我們現在已經知道this無非就是在函數調用時被綁定的一個對象,我們就需要知道它在不同的場景下的綁定規則即可。
什么情況下使用默認綁定呢?獨立函數調用。
案例一:普通函數調用
function foo() {
console.log(this); // window
}
foo();
案例二:函數調用鏈(一個函數調用另外一個函數)
// 2.案例二:
function test1() {
console.log(this); // window
test2();
}
function test2() {
console.log(this); // window
test3()
}
function test3() {
console.log(this); // window
}
test1();
案例三:將函數作為參數,傳入到另一個函數中
function foo(func) {
func()
}
function bar() {
console.log(this); // window
}
foo(bar);
我們對案例進行一些修改,考慮一下打印結果是否會發生變化:
function foo(func) {
func()
}
var obj={
name: "why",
bar: function() {
console.log(this); // window
}
}
foo(obj.bar);
另外一種比較常見的調用方式是通過某個對象進行調用的:
案例一:通過對象調用函數
function foo() {
console.log(this); // obj對象
}
var obj={
name: "why",
foo: foo
}
obj.foo();
案例二:案例一的變化
function foo() {
console.log(this); // obj對象
}
var obj1={
name: "obj1",
foo: foo
}
var obj2={
name: "obj2",
obj1: obj1
}
obj2.obj1.foo();
案例三:隱式丟失
function foo() {
console.log(this);
}
var obj1={
name: "obj1",
foo: foo
}
// 講obj1的foo賦值給bar
var bar=obj1.foo;
bar();
隱式綁定有一個前提條件:
如果我們不希望在 對象內部 包含這個函數的引用,同時又希望在這個對象上進行強制調用,該怎么做呢?
因為上面的過程,我們明確地綁定了this指向的對象,所以稱之為 顯示綁定。
通過call或者apply綁定this對象
function foo() {
console.log(this);
}
foo.call(window); // window
foo.call({name: "why"}); // {name: "why"}
foo.call(123); // Number對象,存放時123
如果我們希望一個函數總是顯示的綁定到一個對象上,可以怎么做呢?
方案一:自己手寫一個輔助函數(了解)
function foo() {
console.log(this);
}
var obj={
name: "why"
}
function bind(func, obj) {
return function() {
return func.apply(obj, arguments);
}
}
var bar=bind(foo, obj);
bar(); // obj對象
bar(); // obj對象
bar(); // obj對象
方案二:使用Function.prototype.bind
function foo() {
console.log(this);
}
var obj={
name: "why"
}
var bar=foo.bind(obj);
bar(); // obj對象
bar(); // obj對象
bar(); // obj對象
有些時候,我們會調用一些JavaScript的內置函數,或者一些第三方庫中的內置函數。
案例一:setTimeout
setTimeout(function() {
console.log(this); // window
}, 1000);
為什么這里是window呢?
案例二:數組的forEach
數組有一個高階函數forEach,用于函數的遍歷:
var names=["abc", "cba", "nba"];
names.forEach(function(item) {
console.log(this); // 三次window
});
我們是否可以改變該函數的this指向呢?
forEach參數
var names=["abc", "cba", "nba"];
var obj={name: "why"};
names.forEach(function(item) {
console.log(this); // 三次obj對象
}, obj);
案例三:div的點擊
如果我們有一個div元素:
<style>
.box {
width: 200px;
height: 200px;
background-color: red;
}
</style>
<div class="box"></div>
獲取元素節點,并且監聽點擊:
var box=document.querySelector(".box");
box.onclick=function() {
console.log(this); // box對象
}
所以傳入到內置函數的回調函數this如何確定呢?
JavaScript中的函數可以當做一個類的構造函數來使用,也就是使用new關鍵字。
使用new關鍵字來調用函數時,會執行如下的操作:
// 創建Person
function Person(name) {
console.log(this); // Person {}
this.name=name; // Person {name: "why"}
}
var p=new Person("why");
console.log(p);
學習了四條規則,接下來開發中我們只需要去查找函數的調用應用了哪條規則即可,但是如果一個函數調用位置應用了多條規則,優先級誰更高呢?
1.默認規則的優先級最低
毫無疑問,默認規則的優先級是最低的,因為存在其他規則時,就會通過其他規則的方式來綁定this
2.顯示綁定優先級高于隱式綁定
顯示綁定和隱式綁定哪一個優先級更高呢?這個我們可以測試一下:
function foo() {
console.log(this);
}
var obj1={
name: "obj1",
foo: foo
}
var obj2={
name: "obj2",
foo: foo
}
// 隱式綁定
obj1.foo(); // obj1
obj2.foo(); // obj2
// 隱式綁定和顯示綁定同時存在
obj1.foo.call(obj2); // obj2, 說明顯式綁定優先級更高
3.new綁定優先級高于隱式綁定
function foo() {
console.log(this);
}
var obj={
name: "why",
foo: foo
}
new obj.foo(); // foo對象, 說明new綁定優先級更高
4.new綁定優先級高于bind
new綁定和call、apply是不允許同時使用的,所以不存在誰的優先級更高
function foo() {
console.log(this);
}
var obj={
name: "obj"
}
var foo=new foo.call(obj);
new和call同時使用
但是new綁定是否可以和bind后的函數同時使用呢?可以
function foo() {
console.log(this);
}
var obj={
name: "obj"
}
// var foo=new foo.call(obj);
var bar=foo.bind(obj);
var foo=new bar(); // 打印foo, 說明使用的是new綁定
優先級總結:
我們講到的規則已經足以應付平時的開發,但是總有一些語法,超出了我們的規則之外。(神話故事和動漫中總是有類似這樣的人物)
如果在顯示綁定中,我們傳入一個null或者undefined,那么這個顯示綁定會被忽略,使用默認規則:
function foo() {
console.log(this);
}
var obj={
name: "why"
}
foo.call(obj); // obj對象
foo.call(null); // window
foo.call(undefined); // window
var bar=foo.bind(null);
bar(); // window
另外一種情況,創建一個函數的 間接引用,這種情況使用默認綁定規則。
我們先來看下面的案例結果是什么?
var num1=100;
var num2=0;
var result=(num2=num1);
console.log(result); // 100
我們來下面的函數賦值結果:
function foo() {
console.log(this);
}
var obj1={
name: "obj1",
foo: foo
};
var obj2={
name: "obj2"
}
obj1.foo(); // obj1對象
(obj2.foo=obj1.foo)(); // window
在ES6中新增一個非常好用的函數類型:箭頭函數
箭頭函數不使用this的四種標準規則(也就是不綁定this),而是根據外層作用域來決定this。
我們來看一個模擬網絡請求的案例:
var obj={
data: [],
getData: function() {
var _this=this;
setTimeout(function() {
// 模擬獲取到的數據
var res=["abc", "cba", "nba"];
_this.data.push(...res);
}, 1000);
}
}
obj.getData();
上面的代碼在ES6之前是我們最常用的方式,從ES6開始,我們會使用箭頭函數:
var obj={
data: [],
getData: function() {
setTimeout(()=> {
// 模擬獲取到的數據
var res=["abc", "cba", "nba"];
this.data.push(...res);
}, 1000);
}
}
obj.getData();
思考:如果getData也是一個箭頭函數,那么setTimeout中的回調函數中的this指向誰呢?
var obj={
data: [],
getData: ()=> {
setTimeout(()=> {
console.log(this); // window
}, 1000);
}
}
obj.getData();
var name="window";
var person={
name: "person",
sayName: function () {
console.log(this.name);
}
};
function sayName() {
var sss=person.sayName;
sss();
person.sayName();
(person.sayName)();
(b=person.sayName)();
}
sayName();
這道面試題非常簡單,無非就是繞一下,希望把面試者繞暈:
function sayName() {
var sss=person.sayName;
// 獨立函數調用,沒有和任何對象關聯
sss(); // window
// 關聯
person.sayName(); // person
(person.sayName)(); // person
(b=person.sayName)(); // window
}
var name='window'
var person1={
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: ()=> console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return ()=> {
console.log(this.name)
}
}
}
var person2={ name: 'person2' }
person1.foo1();
person1.foo1.call(person2);
person1.foo2();
person1.foo2.call(person2);
person1.foo3()();
person1.foo3.call(person2)();
person1.foo3().call(person2);
person1.foo4()();
person1.foo4.call(person2)();
person1.foo4().call(person2);
下面是代碼解析:
當我們開始在瀏覽器中輸入網址的時候,瀏覽器其實就已經在智能的匹配可能得 url 了,他會從歷史記錄,書簽等地方,找到已經輸入的字符串可能對應的 url,然后給出智能提示,讓你可以補全url地址。對于 google的chrome 的瀏覽器,他甚至會直接從緩存中把網頁展示出來,就是說,你還沒有按下 enter,頁面就出來了。
1、請求一旦發起,瀏覽器首先要做的事情就是解析這個域名,一般來說,瀏覽器會首先查看本地硬盤的 hosts 文件,看看其中有沒有和這個域名對應的規則,如果有的話就直接使用 hosts 文件里面的 ip 地址。
2、如果在本地的 hosts 文件沒有能夠找到對應的 ip 地址,瀏覽器會發出一個 DNS請求到本地DNS服務器 。本地DNS服務器一般都是你的網絡接入服務器商提供,比如中國電信,中國移動。
3、查詢你輸入的網址的DNS請求到達本地DNS服務器之后,本地DNS服務器會首先查詢它的緩存記錄,如果緩存中有此條記錄,就可以直接返回結果,此過程是遞歸的方式進行查詢。如果沒有,本地DNS服務器還要向DNS根服務器進行查詢。
4、根DNS服務器沒有記錄具體的域名和IP地址的對應關系,而是告訴本地DNS服務器,你可以到域服務器上去繼續查詢,并給出域服務器的地址。這種過程是迭代的過程。
5、本地DNS服務器繼續向域服務器發出請求,在這個例子中,請求的對象是.com域服務器。.com域服務器收到請求之后,也不會直接返回域名和IP地址的對應關系,而是告訴本地DNS服務器,你的域名的解析服務器的地址。
6、最后,本地DNS服務器向域名的解析服務器發出請求,這時就能收到一個域名和IP地址對應關系,本地DNS服務器不僅要把IP地址返回給用戶電腦,還要把這個對應關系保存在緩存中,以備下次別的用戶查詢時,可以直接返回結果,加快網絡訪問。
下面這張圖很完美的解釋了這一過程:
DNS(Domain Name System,域名系統),因特網上作為域名和IP地址相互映射的一個分布式數據庫,能夠使用戶更方便的訪問互聯網,而不用去記住能夠被機器直接讀取的IP數串。通過主機名,最終得到該主機名對應的IP地址的過程叫做域名解析(或主機名解析)。
通俗的講,我們更習慣于記住一個網站的名字,比如www.baidu.com,而不是記住它的ip地址,比如:167.23.10.2。而計算機更擅長記住網站的ip地址,而不是像www.baidu.com等鏈接。因為,DNS就相當于一個電話本,比如你要找www.baidu.com這個域名,那我翻一翻我的電話本,我就知道,哦,它的電話(ip)是167.23.10.2。
1、遞歸解析
當局部DNS服務器自己不能回答客戶機的DNS查詢時,它就需要向其他DNS服務器進行查詢。此時有兩種方式,如圖所示的是遞歸方式。局部DNS服務器自己負責向其他DNS服務器進行查詢,一般是先向該域名的根域服務器查詢,再由根域名服務器一級級向下查詢。最后得到的查詢結果返回給局部DNS服務器,再由局部DNS服務器返回給客戶端。
2、迭代解析
當局部DNS服務器自己不能回答客戶機的DNS查詢時,也可以通過迭代查詢的方式進行解析,如圖所示。局部DNS服務器不是自己向其他DNS服務器進行查詢,而是把能解析該域名的其他DNS服務器的IP地址返回給客戶端DNS程序,客戶端DNS程序再繼續向這些DNS服務器進行查詢,直到得到查詢結果為止。也就是說,迭代解析只是幫你找到相關的服務器而已,而不會幫你去查。比如說:baidu.com的服務器ip地址在192.168.4.5這里,你自己去查吧,本人比較忙,只能幫你到這里了。
我們在前面有說到根DNS服務器,域DNS服務器,這些都是DNS域名稱空間的組織方式。按其功能命名空間中用來描述 DNS 域名稱的五個類別的介紹詳見下表中,以及與每個名稱類型的示例
當一個網站有足夠多的用戶的時候,假如每次請求的資源都位于同一臺機器上面,那么這臺機器隨時可能會蹦掉。處理辦法就是用DNS負載均衡技術,它的原理是在DNS服務器中為同一個主機名配置多個IP地址,在應答DNS查詢時,DNS服務器對每個查詢將以DNS文件中主機記錄的IP地址按順序返回不同的解析結果,將客戶端的訪問引導到不同的機器上去,使得不同的客戶端訪問不同的服務器,從而達到負載均衡的目的?例如可以根據每臺機器的負載量,該機器離用戶地理位置的距離等等。
拿到域名對應的IP地址之后,瀏覽器會以一個隨機端口(1024<端口<65535)向服務器的WEB程序(常用的有httpd,nginx等)80端口發起TCP的連接請求。這個連接請求到達服務器端后(這中間通過各種路由設備,局域網內除外),進入到網卡,然后是進入到內核的TCP/IP協議棧(用于識別該連接請求,解封包,一層一層的剝開),還有可能要經過Netfilter防火墻(屬于內核的模塊)的過濾,最終到達WEB程序,最終建立了TCP/IP的連接。
TCP連接如圖所示:
建立了TCP連接之后,發起一個http請求。一個典型的 http request header 一般需要包括請求的方法,例如 GET 或者 POST 等,不常用的還有 PUT 和 DELETE 、HEAD、OPTION以及 TRACE 方法,一般的瀏覽器只能發起 GET 或者 POST 請求。
客戶端向服務器發起http請求的時候,會有一些請求信息,請求信息包含三個部分:
請求方法URI協議/版本
請求頭(Request Header)
請求正文
下面是一個完整的HTTP請求例子:
GET/sample.jspHTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window?NT5.0)
Accept-Encoding:gzip,deflate
username=jinqiao&password=1234
注意:最后一個請求頭之后是一個空行,發送回車符和換行符,通知服務器以下不再有請求頭。
1.請求的第一行是“方法URL議/版本”:GET/sample.jsp HTTP/1.1
2.請求頭(Request Header)
請求頭包含許多有關的客戶端環境和請求正文的有用信息。例如,請求頭可以聲明瀏覽器所用的語言,請求正文的長度等。
Accept:image/gif.image/jpeg.*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows?NT5.0)
Accept-Encoding:gzip,deflate.
3.請求正文
請求頭和請求正文之間是一個空行,這個行非常重要,它表示請求頭已經結束,接下來的是請求正文。請求正文中可以包含客戶提交的查詢字符串信息:
username=jinqiao&password=1234
第一次握手:客戶端A將標志位SYN置為1,隨機產生一個值為seq=J(J的取值范圍為=1234567)的數據包到服務器,客戶端A進入SYN_SENT狀態,等待服務端B確認;
第二次握手:服務端B收到數據包后由標志位SYN=1知道客戶端A請求建立連接,服務端B將標志位SYN和ACK都置為1,ack=J+1,隨機產生一個值seq=K,并將該數據包發送給客戶端A以確認連接請求,服務端B進入SYN_RCVD狀態。
第三次握手:客戶端A收到確認后,檢查ack是否為J+1,ACK是否為1,如果正確則將標志位ACK置為1,ack=K+1,并將該數據包發送給服務端B,服務端B檢查ack是否為K+1,ACK是否為1,如果正確則連接建立成功,客戶端A和服務端B進入ESTABLISHED狀態,完成三次握手,隨后客戶端A與服務端B之間可以開始傳輸數據了。
如圖所示:
《計算機網絡》第四版中講“三次握手”的目的是“為了防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤”
書中的例子是這樣的,“已失效的連接請求報文段”的產生在這樣一種情況下:client發出的第一個連接請求報文段并沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以后的某個時間才到達server。本來這是一個早已失效的報文段。但server收到此失效的連接請求報文段后,就誤認為是client再次發出的一個新的連接請求。于是就向client發出確認報文段,同意建立連接。
假設不采用“三次握手”,那么只要server發出確認,新的連接就建立了。由于現在client并沒有發出建立連接的請求,因此不會理睬server的確認,也不會向server發送數據。但server卻以為新的運輸連接已經建立,并一直等待client發來數據。這樣,server的很多資源就白白浪費掉了。采用“三次握手”的辦法可以防止上述現象發生。例如剛才那種情況,client不會向server的確認發出確認。server由于收不到確認,就知道client并沒有要求建立連接。”。主要目的防止server端一直等待,浪費資源。
第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態。
第二次揮手:Server收到FIN后,發送一個ACK給Client,確認序號為收到序號+1(與- SYN相同,一個FIN占用一個序號),Server進入CLOSE_WAIT狀態。
第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態。
第四次揮手:Client收到FIN后,Client進入TIME_WAIT狀態,接著發送一個ACK給Server,確認序號為收到序號+1,Server進入CLOSED狀態,完成四次揮手。
這是因為服務端在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送。
服務器給瀏覽器響應一個301永久重定向響應,這樣瀏覽器就會訪問http://www.google.com/而非http://google.com/。
為什么服務器一定要重定向而不是直接發送用戶想看的網頁內容呢?其中一個原因跟搜索引擎排名有關。如果一個頁面有兩個地址,就像http://www.yy.com/和http://yy.com/,搜索引擎會認為它們是兩個網站,結果造成每個搜索鏈接都減少從而降低排名。而搜索引擎知道301永久重定向是什么意思,這樣就會把訪問帶www的和不帶www的地址歸到同一個網站排名下。還有就是用不同的地址會造成緩存友好性變差,當一個頁面有好幾個名字時,它可能會在緩存里出現好幾次。
301和302狀態碼都表示重定向,就是說瀏覽器在拿到服務器返回的這個狀態碼后會自動跳轉到一個新的URL地址,這個地址可以從響應的Location首部中獲取(用戶看到的效果就是他輸入的地址A瞬間變成了另一個地址B)——這是它們的共同點。
他們的不同在于。301表示舊地址A的資源已經被永久地移除了(這個資源不可訪問了),搜索引擎在抓取新內容的同時也將舊的網址交換為重定向之后的網址;
302表示舊地址A的資源還在(仍然可以訪問),這個重定向只是臨時地從舊地址A跳轉到地址B,搜索引擎會抓取新的內容而保存舊的網址。SEO302好于301
網站調整(如改變網頁目錄結構);
網頁被移到一個新地址;
網頁擴展名改變(如應用需要把.php改成.Html或.shtml)。
這種情況下,如果不做重定向,則用戶收藏夾或搜索引擎數據庫中舊地址只能讓訪問客戶得到一個404頁面錯誤信息,訪問流量白白喪失;再者某些注冊了多個域名的網站,也需要通過重定向讓訪問這些域名的用戶自動跳轉到主站點等。
當一個網站或者網頁24—48小時內臨時移動到一個新的位置,這時候就要進行302跳轉,而使用301跳轉的場景就是之前的網站因為某種原因需要移除掉,然后要到新的地址訪問,是永久性的。
清晰明確而言:使用301跳轉的大概場景如下:
域名到期不想續費(或者發現了更適合網站的域名),想換個域名。
在搜索引擎的搜索結果中出現了不帶www的域名,而帶www的域名卻沒有收錄,這個時候可以用301重定向來告訴搜索引擎我們目標的域名是哪一個。
空間服務器不穩定,換空間的時候。
現在瀏覽器知道了 http://www.google.com/ 才是要訪問的正確地址,所以它會發送另一個http請求。
經過前面的重重步驟,我們終于將我們的http請求發送到了服務器這里,其實前面的重定向已經是到達服務器了,那么,服務器是如何處理我們的請求的呢?
后端從在固定的端口接收到TCP報文開始,它會對TCP連接進行處理,對HTTP協議進行解析,并按照報文格式進一步封裝成HTTP Request對象,供上層使用。
一些大一點的網站會將你的請求到反向代理服務器中,因為當網站訪問量非常大,網站越來越慢,一臺服務器已經不夠用了。于是將同一個應用部署在多臺服務器上,將大量用戶的請求分配給多臺機器處理。
此時,客戶端不是直接通過HTTP協議訪問某網站應用服務器,而是先請求到Nginx,Nginx再請求應用服務器,然后將結果返回給客戶端,這里Nginx的作用是反向代理服務器。同時也帶來了一個好處,其中一臺服務器萬一掛了,只要還有其他服務器正常運行,就不會影響用戶使用。
如圖所示:
通過Nginx的反向代理,我們到達了web服務器,服務端腳本處理我們的請求,訪問我們的數據庫,獲取需要獲取的內容等等,當然,這個過程涉及很多后端腳本的復雜操作。由于對這一塊不熟,所以這一塊只能介紹這么多了。
客戶端本來可以直接通過HTTP協議訪問某網站應用服務器,網站管理員可以在中間加上一個Nginx,客戶端請求Nginx,Nginx請求應用服務器,然后將結果返回給客戶端,此時Nginx就是反向代理服務器。
經過前面的6個步驟,服務器收到了我們的請求,也處理我們的請求,到這一步,它會把它的處理結果返回,也就是返回一個HTPP響應。
HTTP響應與HTTP請求相似,HTTP響應也由3個部分構成,分別是:
狀態行
響應頭(Response Header)
響應正文
HTTP/1.1?200?OK
Date:?Sat,?31?Dec?2005?23:59:59?GMT
Content-Type:?text/html;charset=ISO-8859-1
Content-Length:?122
<html>
<head>
<title>http</title>
</head>
<body>
<!--?body?goes?here?-->
</body>
</html>
狀態行由協議版本、數字形式的狀態代碼、及相應的狀態描述,各元素之間以空格分隔。
格式: HTTP-Version Status-Code Reason-Phrase CRLF
例如: HTTP/1.1 200 OK
協議版本:是用http1.0還是其他版本
狀態描述:狀態描述給出了關于狀態代碼的簡短的文字描述。比如狀態代碼為200時的描述為 ok
狀態碼:狀態代碼由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值,如下:
1xx:信息性狀態碼,表示服務器已接收了客戶端請求,客戶端可繼續發送請求。
100 Continue
101 Switching Protocols
2xx:成功狀態碼,表示服務器已成功接收到請求并進行處理。
200 OK 表示客戶端請求成功
204 No Content 成功,但不返回任何實體的主體部分
206 Partial Content 成功執行了一個范圍(Range)請求
3xx:重定向狀態碼,表示服務器要求客戶端重定向。
301 Moved Permanently 永久性重定向,響應報文的Location首部應該有該資源的新URL
302 Found 臨時性重定向,響應報文的Location首部給出的URL用來臨時定位資源
303 See Other 請求的資源存在著另一個URI,客戶端應使用GET方法定向獲取請求的資源
304 Not Modified 服務器內容沒有更新,可以直接讀取瀏覽器緩存
307 Temporary Redirect 臨時重定向。與302 Found含義一樣。302禁止POST變換為GET,但實際使用時并不一定,307則更多瀏覽器可能會遵循這一標準,但也依賴于瀏覽器具體實現
4xx:客戶端錯誤狀態碼,表示客戶端的請求有非法內容。
400 Bad Request 表示客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthonzed 表示請求未經授權,該狀態代碼必須與 WWW-Authenticate 報頭域一起使用
403 Forbidden 表示服務器收到請求,但是拒絕提供服務,通常會在響應正文中給出不提供服務的原因
404 Not Found 請求的資源不存在,例如,輸入了錯誤的URL
5xx:服務器錯誤狀態碼,表示服務器未能正常處理客戶端的請求而出現意外錯誤。
500 Internel Server Error 表示服務器發生不可預期的錯誤,導致無法完成客戶端的請求
503 Service Unavailable 表示服務器當前不能夠處理客戶端的請求,在一段時間之后,服務器可能會恢復正常
響應頭部:由關鍵字/值對組成,每行一對,關鍵字和值用英文冒號”:”分隔,典型的響應頭有:
包含著我們需要的一些具體信息,比如cookie,html,image,后端返回的請求數據等等。這里需要注意,響應正文和響應頭之間有一行空格,表示響應頭的信息到空格為止,下圖是fiddler抓到的請求正文,紅色框中的:響應正文:
在瀏覽器沒有完整接受全部HTML文檔時,它就已經開始顯示這個頁面了,瀏覽器是如何把頁面呈現在屏幕上的呢?不同瀏覽器可能解析的過程不太一樣,這里我們只介紹webkit的渲染過程,下圖對應的就是WebKit渲染的過程,這個過程包括:
解析html以構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹
瀏覽器在解析html文件時,會”自上而下“加載,并在加載過程中進行解析渲染。在解析過程中,如果遇到請求外部資源時,如圖片、外鏈的CSS、iconfont等,請求過程是異步的,并不會影響html文檔進行加載。
解析過程中,瀏覽器首先會解析HTML文件構建DOM樹,然后解析CSS文件構建渲染樹,等到渲染樹構建完成后,瀏覽器開始布局渲染樹并將其繪制到屏幕上。這個過程比較復雜,涉及到兩個概念: reflow(回流)和repain(重繪)。
DOM節點中的各個元素都是以盒模型的形式存在,這些都需要瀏覽器去計算其位置和大小等,這個過程稱為relow;當盒模型的位置,大小以及其他屬性,如顏色,字體,等確定下來之后,瀏覽器便開始繪制內容,這個過程稱為repain。
頁面在首次加載時必然會經歷reflow和repain。reflow和repain過程是非常消耗性能的,尤其是在移動設備上,它會破壞用戶體驗,有時會造成頁面卡頓。所以我們應該盡可能少的減少reflow和repain。
當文檔加載過程中遇到js文件,html文檔會掛起渲染(加載解析渲染同步)的線程,不僅要等待文檔中js文件加載完畢,還要等待解析執行完畢,才可以恢復html文檔的渲染線程。因為JS有可能會修改DOM,最為經典的document.write,這意味著,在JS執行完成前,后續所有資源的下載可能是沒有必要的,這是js阻塞后續資源下載的根本原因。所以我明平時的代碼中,js是放在html文檔末尾的。
JS的解析是由瀏覽器中的JS解析引擎完成的,比如谷歌的是V8。JS是單線程運行,也就是說,在同一個時間內只能做一件事,所有的任務都需要排隊,前一個任務結束,后一個任務才能開始。但是又存在某些任務比較耗時,如IO讀寫等,所以需要一種機制可以先執行排在后面的任務,這就是:同步任務(synchronous)和異步任務(asynchronous)。
JS的執行機制就可以看做是一個主線程加上一個任務隊列(task queue)。同步任務就是放在主線程上執行的任務,異步任務是放在任務隊列中的任務。所有的同步任務在主線程上執行,形成一個執行棧;異步任務有了運行結果就會在任務隊列中放置一個事件;腳本運行時先依次運行執行棧,然后會從任務隊列里提取事件,運行任務隊列中的任務,這個過程是不斷重復的,所以又叫做事件循環(Event loop)。具體的過程可以看這篇文章:
http://www.cnblogs.com/xianyulaodi/p/6414805.html
其實這個步驟可以并列在步驟8中,在瀏覽器顯示HTML時,它會注意到需要獲取其他地址內容的標簽。這時,瀏覽器會發送一個獲取請求來重新獲得這些文件。比如我要獲取外圖片,CSS,JS文件等,類似于下面的鏈接:
圖片:http://static.ak.fbcdn.net/rsrc.php/z12E0/hash/8q2anwu7.gif
CSS式樣表:http://static.ak.fbcdn.net/rsrc.php/z448Z/hash/2plh8s4n.css
JavaScript 文件:http://static.ak.fbcdn.net/rsrc.php/zEMOA/hash/c8yzb6ub.js
這些地址都要經歷一個和HTML讀取類似的過程。所以瀏覽器會在DNS中查找這些域名,發送請求,重定向等等…
不像動態頁面,靜態文件會允許瀏覽器對其進行緩存。有的文件可能會不需要與服務器通訊,而從緩存中直接讀取,或者可以放到CDN中
*請認真填寫需求信息,我們會在24小時內與您取得聯系。