前言
最近這些年前端相關(guān)的技術(shù)的發(fā)展速度猶如坐上了火箭一般一日千里,新技術(shù)新框架層出不窮:Node.js、React、Angular、Vue... 端上有React Native、node-webkit,JavaScript竟然隱約有一統(tǒng)前端、后端、移動(dòng)端、PC端之勢...
不過與其說進(jìn)擊的JavaScript,我更覺得不如說天下技術(shù)本一家。愛掏網(wǎng) - it200.com就像“前端開發(fā)”“后端開發(fā)”“客戶端開發(fā)”等各種開發(fā)之間并沒有那么大的區(qū)別、只是對(duì)技術(shù)的細(xì)分,不同的編程語言、工具也都是在朝著高性能、可復(fù)用、開發(fā)友好的方向前進(jìn)。愛掏網(wǎng) - it200.comJavaScript也是在朝著這個(gè)方向前進(jìn),良好的生態(tài)和無數(shù)開發(fā)人員的努力使得它迎來了百花齊放的今天。愛掏網(wǎng) - it200.com
寫這篇文章的原因
這幾年作為“Java后端開發(fā)”的我其實(shí)做Windows C++客戶端開發(fā)的時(shí)間和寫Java后端的時(shí)間幾乎是五五開(對(duì),開掛般的工種...),前端技術(shù)其實(shí)以前也是我的心頭愛,不過這幾年算是荒廢了,只能看著前端同學(xué)的IDE流口水再厚著臉皮問“這幾行代碼是什么意思啊?”,前端GG心情好還會(huì)耐心解釋一番、要是心情不好就只能接收到“說了你也不懂”的眼神了...
最近終于有些時(shí)間,可以系統(tǒng)地學(xué)習(xí)梳理一遍前端知識(shí)了,這篇文章既是學(xué)習(xí)成果的自我總結(jié),也希望能對(duì)有興趣的同學(xué)有所裨益,由于我本身也還只是個(gè)前端菜鳥,文章如果有錯(cuò)誤或不當(dāng)之處還望大家斧正。愛掏網(wǎng) - it200.com
面向的對(duì)象
如前面所說,這篇文章分享給有興趣的、對(duì)前端技術(shù)不太熟悉的同學(xué)。愛掏網(wǎng) - it200.com但你應(yīng)該至少掌握了以下基礎(chǔ)知識(shí)(否則需要先充下電了哦):
- HTML,至少得能手寫個(gè)html、header、body吧。愛掏網(wǎng) - it200.com
- CSS,我知道大家都煩這個(gè)、下面我也不會(huì)說它的,但你至少應(yīng)該知道怎么在html中插入/引用css吧,再給body設(shè)置個(gè)背景色試試?
- JavaScript,我指的是最基礎(chǔ)的,比如知道怎樣在html中插入/引用JavaScript,能看懂"document.getElementById('demo').innerHTML='Hello JavaScript'; "這行代碼。愛掏網(wǎng) - it200.com
- 表單提交與Ajax,是不是就是一個(gè)刷新一個(gè)不刷新?... 但你至少應(yīng)該能用JavaScript寫出Ajax提交數(shù)據(jù)的Demo來。愛掏網(wǎng) - it200.com
如果你能熟練使用jQuery、Bootstrap,那么恭喜你,作為一個(gè)上古時(shí)代的前端高手,我相信你可以無障礙地閱讀下面的內(nèi)容!
參考資料
下面是相關(guān)的參考資料,本文充其量只是一個(gè)速成教程,真正的精華都在下面:
- JavaScript 標(biāo)準(zhǔn)參考教程 ,阮一峰老師著。愛掏網(wǎng) - it200.com
- ECMAScript 6 入門,還是阮一峰老師著,此書有紙質(zhì)版可以支持下哦。愛掏網(wǎng) - it200.com
- MDN JavaScript 參考文檔,Mozilla的JavaScript參考文檔,信Firefox、得永生。愛掏網(wǎng) - it200.com
下文中還直接使用了很多阮一峰老師著作中的內(nèi)容,有些不成段落的語句可能沒有標(biāo)記成引用還望諒解。愛掏網(wǎng) - it200.com
JavaScript的核心語法部分相當(dāng)精簡,只包括兩個(gè)部分:基本的語法構(gòu)造(比如操作符、控制結(jié)構(gòu)、語句)和標(biāo)準(zhǔn)庫(就是一系列具有各種功能的對(duì)象比如Array、Date、Math等)。愛掏網(wǎng) - it200.com
不同的運(yùn)行環(huán)境(如瀏覽器、Node.js)也會(huì)提供額外的API供JavaScript調(diào)用,以瀏覽器為例,它提供的額外API可以分成三大類:
- 瀏覽器控制類:操作瀏覽器,如window.open()。愛掏網(wǎng) - it200.com
- DOM 類:操作網(wǎng)頁的各種元素,如getElementById()。愛掏網(wǎng) - it200.com
- Web 類:實(shí)現(xiàn)互聯(lián)網(wǎng)的各種功能,如XMLHttpRequest。愛掏網(wǎng) - it200.com
這一部分只介紹JavaScript的核心語法,示例代碼基本上都可以在Chrome或其它瀏覽器的開發(fā)者工具的Console中執(zhí)行。愛掏網(wǎng) - it200.com
ECMAScript與JavaScript
1996 年 11 月,JavaScript 的創(chuàng)造者 Netscape 公司,決定將 JavaScript 提交給國際標(biāo)準(zhǔn)化組織 ECMA,希望這種語言能夠成為國際標(biāo)準(zhǔn)。愛掏網(wǎng) - it200.com次年,ECMA 發(fā)布 262 號(hào)標(biāo)準(zhǔn)文件(ECMA-262)的第一版,規(guī)定了瀏覽器腳本語言的標(biāo)準(zhǔn),并將這種語言稱為 ECMAScript,這個(gè)版本就是 1.0 版。愛掏網(wǎng) - it200.com
阮一峰:《ECMAScript 6 入門》
簡而言之,ECMAScript是標(biāo)準(zhǔn),JavaScript是其實(shí)現(xiàn)(另外的ECMAScript方言還有Jscript和ActionScript)。愛掏網(wǎng) - it200.com
這好比Java標(biāo)準(zhǔn)也由JCP(Java Community Process)在維護(hù),因此既有Oracle JDK,還有OpenJDK、IBM JDK。愛掏網(wǎng) - it200.com
ECMAScript 6(下述簡稱ES6)
由于時(shí)間跨度、瀏覽器兼容性等原因,這篇文章將以2024年正式發(fā)布ECMAScript 6標(biāo)準(zhǔn)(也即ECMAScript 2024)作為基礎(chǔ)。愛掏網(wǎng) - it200.com與“上古時(shí)代”的JavaScript(ECMAScript 5于2009年發(fā)布)相比,ECMAScript 6加入了眾多的新特性,我認(rèn)為這也是這些年JavaScript騰飛的基礎(chǔ)之一。愛掏網(wǎng) - it200.com
自ECMAScript 6開始ECMAScript將每年發(fā)布一個(gè)版本,ECMAScript 2024、2024也已在當(dāng)年發(fā)布,不過其跨度不大、改變也不算多,有興趣的同學(xué)可以自行學(xué)習(xí)。愛掏網(wǎng) - it200.com
這么看來ECMAScript 6的意義相當(dāng)于C++11,同樣是時(shí)隔多年(C++11之前的一個(gè)標(biāo)準(zhǔn)是C++03...),同樣是堪稱大刀闊斧地“重新定義”...
哪里不一樣
let:你還在用"var"嗎?
ES6 新增了let命令,用來聲明變量。愛掏網(wǎng) - it200.com它的用法類似于var,但是所聲明的變量,只在let命令所在的代碼塊內(nèi)有效。愛掏網(wǎng) - it200.com
阮一峰:《ECMAScript 6 入門》
如下所示,"let"只在其所在的大括號(hào)范圍內(nèi)有效,可以視為“局部變量”。愛掏網(wǎng) - it200.com
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
另外不同于在Scala、Swift中"let"用來表示不變量(不要跟ES6記混了哦),ES6還是很常規(guī)地引入了"const"來表示常量:
const PI = 3.1415;
PI = 3;
解構(gòu)賦值:我連賦值都快看不懂了
ES6 允許按照一定模式,從數(shù)組和對(duì)象中提取值,對(duì)變量進(jìn)行賦值,這被稱為解構(gòu)(Destructuring)。愛掏網(wǎng) - it200.com
阮一峰:《ECMAScript 6 入門》
先看數(shù)組的解構(gòu),還是很簡單的:
let [a, b, c] = [1, 2, 3];
a //1
b //2
c //3
再看對(duì)象解構(gòu)并賦值到變量,初看也比較簡單:
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
但如果變量名與屬性名不一致,必須寫成下面這樣:
let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined
上面代碼中,foo是匹配的模式,baz才是變量。愛掏網(wǎng) - it200.com真正被賦值的是變量baz,而不是模式foo。愛掏網(wǎng) - it200.com你記住了嗎?
Class:終于有正宮對(duì)象了
在ES6之前,生成實(shí)例對(duì)象的方法是通過構(gòu)造函數(shù)。愛掏網(wǎng) - it200.com下面就是這樣一個(gè)反人類的例子。愛掏網(wǎng) - it200.com
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
是的,ES6英雄般地又一次拯救了我們的腦細(xì)胞,請(qǐng)看下面的例子!沒錯(cuò),那個(gè)constructor函數(shù)代表了類的構(gòu)造方法(也可以不顯式定義哦)。愛掏網(wǎng) - it200.com
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString();
Proxy、Reflect:動(dòng)態(tài)代理和反射不只Java有哦
不多說,直接上代碼,先看Proxy的例子,Proxy支持的攔截操作共13種,這里只展示它攔截get的操作。愛掏網(wǎng) - it200.com
let person = {
name: "張三"
};
let proxy = new Proxy(person, {
get: function(target, property) {
if (property === 'name') {
return "李四";
} else {
return "你猜";
}
}
});
person.name // "張三"
proxy.name // "李四"
proxy.age // "你猜"
再看Reflect的,與Proxy對(duì)應(yīng)、它也有13種方法,這里也只展示對(duì)get的反射。愛掏網(wǎng) - it200.com
var myObject = {
foo: 1,
bar: 2,
get baz() {
return this.foo + this.bar;
},
}
Reflect.get(myObject, 'foo') // 1
Reflect.get(myObject, 'bar') // 2
Reflect.get(myObject, 'baz') // 3
lambda:請(qǐng)叫我“箭頭函數(shù)”
作為這幾年的大熱門,lambda不出意外地也被加入到了ES6中,不過它的正式名稱是Arrow Functions即箭頭函數(shù)。愛掏網(wǎng) - it200.com其示例如下。愛掏網(wǎng) - it200.com
//箭頭函數(shù)
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
有沒有發(fā)現(xiàn)這個(gè)箭頭(=>)是用等號(hào)寫的箭頭,不同于Java里面那個(gè)橫線箭頭(->)?讓我們來復(fù)習(xí)下不同語言中的lambda表達(dá)式吧:
[](int a) -> bool { return a > 0; } // C++,此例可以省略箭頭和"bool"返回值類型聲明
a -> a > 0 // Java
a => a > 0 // C#
a => a > 0; //JavaScript
lambda a: a > 0 # Python
(lambda (a) (> a 0)) ;; Lisp
Promise:異步編程你怕不怕
Promise是ES6中提出的異步編程的一個(gè)解決方案,Java中與之接近的概念是Future(你也可以用Future來實(shí)現(xiàn)Promise機(jī)制,在Scala、Netty中也實(shí)現(xiàn)了Promise機(jī)制)。愛掏網(wǎng) - it200.com但也只能是接近,因?yàn)镴avaScript語言本身是單線程的(不討論Web Worker)、永不阻塞的、基于事件循環(huán)模型(Event Loop)的,用單純Java的思想會(huì)很難理解下面這段代碼的執(zhí)行結(jié)果為什么是“2 1”:
setTimeout(function(){console.log(1);}, 0);
console.log(2);
言歸正傳,ES6 規(guī)定,Promise對(duì)象是一個(gè)構(gòu)造函數(shù),用來生成Promise實(shí)例。愛掏網(wǎng) - it200.com下面代碼創(chuàng)造了一個(gè)Promise實(shí)例。愛掏網(wǎng) - it200.com
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 異步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
你可能會(huì)問“resolve”、“reject”是什么?可以說它們代表了“當(dāng)異步操作成功時(shí)的回調(diào)函數(shù)”、“當(dāng)異步操作失敗時(shí)的回調(diào)函數(shù)”,它們現(xiàn)在還未定義,它們會(huì)在后面由你在“.then”方法中傳入。愛掏網(wǎng) - it200.com
先看一個(gè)簡單的例子,這里在then方法中只傳入了resolve函數(shù),未傳入reject回調(diào)函數(shù)。愛掏網(wǎng) - it200.com
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
再看一個(gè)Ajax的例子,好好理解下哦。愛掏網(wǎng) - it200.com
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出錯(cuò)了', error);
});
Module:官方模塊化,遠(yuǎn)離CommonJs/AMD/CMD保平安!
歷史上,JavaScript 一直沒有模塊(module)體系,無法將一個(gè)大程序拆分成互相依賴的小文件,再用簡單的方法拼裝起來。愛掏網(wǎng) - it200.com
阮一峰:《ECMAScript 6 入門》
簡單地說,Java有import、Python有import、C++有#include,可JavaScript一直沒法“引用”別的js文件。愛掏網(wǎng) - it200.com在此背景下,出現(xiàn)了:
- CommonJs規(guī)范,用于服務(wù)端,Node.js的require是其實(shí)現(xiàn)。愛掏網(wǎng) - it200.com
- AMD(Asynchronous Module Definition)規(guī)范,用于瀏覽器環(huán)境,RequireJS是其實(shí)現(xiàn)。愛掏網(wǎng) - it200.com
- CMD(Common Module Definition)規(guī)范,由玉伯老師提出,用于瀏覽器環(huán)境,Sea.js是其實(shí)現(xiàn)。愛掏網(wǎng) - it200.com
怎么樣,頭暈了沒?還好自從有了ES6,生命里都是奇跡... ES6在語言層面實(shí)現(xiàn)了模塊功能,完全可以一統(tǒng)服務(wù)端和客戶端取代CommonJs/AMD/CMD,我們可以告別那個(gè)八仙過海各顯神通的時(shí)代了,感謝ES6為我們的防脫發(fā)事業(yè)做出的卓越貢獻(xiàn)!
上面純屬玩笑,無論如何,我們都要感謝為了JavaScript模塊化標(biāo)準(zhǔn)做出過貢獻(xiàn)的各位大神,他們的探索無疑為ECMAScript的模塊化標(biāo)準(zhǔn)做出了不可磨滅的貢獻(xiàn)!
言歸正傳,現(xiàn)在最新的主流瀏覽器都已經(jīng)支持ES Module,Node.js也開始支持ES Module標(biāo)準(zhǔn),我們現(xiàn)在可以只學(xué)習(xí)ES6的Module了,不用再關(guān)注CommonJs/AMD/CMD等之前的規(guī)范。愛掏網(wǎng) - it200.com
讓我們先在profile.js中用export來導(dǎo)出一組變量,注意除此之外還可以導(dǎo)出函數(shù)和類:
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
再在main.js中用import來導(dǎo)入它們:
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
是不是很簡單呢?
其它:數(shù)組的map、reduce你造嗎?(ECMAScript 5.1)
這兩個(gè)數(shù)組的方法是早就在2011年發(fā)布的ECMAScript 5.1中就發(fā)布的,但我覺得還是有必要提一下的。愛掏網(wǎng) - it200.com它們的作用也相當(dāng)于Java8的Stream API中的map和reduce(及collect),示例如下:
// map示例
let numbers = [1, 5, 10, 15];
let doubles = numbers.map( x => x ** 2);
// doubles is now [1, 25, 100, 225]
// numbers is still [1, 5, 10, 15]
//reduce示例,后面那個(gè)"0"表示給sum的初始值
var total = [0, 1, 2, 3].reduce(function(sum, currentValue) {
return sum + currentValue;
}, 0);
// total is 6
你應(yīng)該有注意到在上面的map示例中使用了箭頭函數(shù),如果你找茬能力強(qiáng)力的話,應(yīng)該發(fā)現(xiàn)了當(dāng)數(shù)組的map、reduce方法發(fā)布時(shí),那時(shí)還沒有箭頭函數(shù)呢!
那么問題來了,在沒有箭頭函數(shù)的時(shí)候,map、reduce是怎么用的呢? 答案很簡單,當(dāng)年用的是普通的回調(diào)函數(shù):
var numbers = [1, 5, 10, 15];
var doubles = numbers.map(function(value){return value ** 2;});
// doubles is now [1, 25, 100, 225]
// numbers is still [1, 5, 10, 15]
簡介
Node.js是一個(gè)JavaScript運(yùn)行環(huán)境(runtime),發(fā)布于2009年5月,由Ryan Dahl開發(fā),實(shí)質(zhì)是對(duì)Chrome V8引擎進(jìn)行了封裝。愛掏網(wǎng) - it200.com
簡單地說,Node.js是在瀏覽器之外的一個(gè)JavaScript運(yùn)行時(shí),在安裝了Node.js之后,你就可以在命令行中執(zhí)行JS了:
echo "console.log('Hello Node.js!');" > index.js
node index.js
//Hello Node.js!
與瀏覽器一樣,Node.js也為JavaScript提供了大量的額外API,如 fs(文件系統(tǒng))、net(網(wǎng)絡(luò))、os(操作系統(tǒng))等等。愛掏網(wǎng) - it200.com可以說在Node.js的加持下,JavaScript擁有了類似于Python的能力。愛掏網(wǎng) - it200.com
安裝
如何安裝Node.js就不詳述了,Mac下可以使用brew安裝,Windows下也有安裝包。愛掏網(wǎng) - it200.com
注意我安裝的node已經(jīng)是v9.3.0了,此章的Demo是以該版本為基本的。愛掏網(wǎng) - it200.com
npm
如果還是要類比的話,npm類似于Java體系中的Maven,負(fù)責(zé)進(jìn)行包管理。愛掏網(wǎng) - it200.com
有個(gè)不同之處在于Node.js已經(jīng)自帶了npm,不需要再額外安裝了。愛掏網(wǎng) - it200.com
開始之前
在使用之前,可以先做件事情,將npm的切換到淘寶鏡像,可以有效地解決下載包太慢的問題:
npm config set registry " https://registry.npm.taobao.org"
全局安裝和本地安裝
在使用"npm install xxx"命令時(shí)(這里xxx僅為示例),可以有一個(gè)"-g"的參數(shù),它表示將xxx安裝到全局目錄(我這是/usr/local/lib/node_modules)下,同時(shí)用"which xxx"命令可以發(fā)現(xiàn)在"/usr/local/bin"下面已經(jīng)有了一個(gè)xxx的軟鏈接,這樣我們就可以在任何目錄下執(zhí)行tnpm命令了。愛掏網(wǎng) - it200.com
如果不加"-g"呢? 那包就會(huì)被安裝到當(dāng)前目錄的"node_modules"目錄下,記住這一點(diǎn),下面我們的Demo馬上就要用到。愛掏網(wǎng) - it200.com
package.json
如果說npm類似于Maven,那么package.json就是pom.xml了,里面同樣記錄了項(xiàng)目信息及依賴。愛掏網(wǎng) - it200.com
用Node.js寫一個(gè)Web服務(wù)器
有了上面這些知識(shí)之后,我們馬上就可以開始實(shí)現(xiàn)一個(gè)Web服務(wù)器,目前使用Node.js下比較廣泛的Web框架是Express,好比Python里面的Django。愛掏網(wǎng) - it200.com
1.創(chuàng)建一個(gè)項(xiàng)目文件夾
你一定不想把你的home目錄弄亂是吧?
mkdir ExpressStarter
cd ExpressStarter
2.初始化項(xiàng)目
用npm init可以直接創(chuàng)建模塊,生成package.json,npm會(huì)詢問你包名、版本、入口點(diǎn)、Git倉庫之類的,直接回車可以使用默認(rèn)值,但在此Demo中入口點(diǎn)(entry point)請(qǐng)使用"index.mjs",后面我會(huì)說原因的。愛掏網(wǎng) - it200.com
npm init
package name: (expressstarter) com.spirit.test
version: (1.0.0)
description:
entry point: (index.js) index.mjs
test command:
git repository:
keywords:
author:
license: (ISC)
此時(shí)的package.json內(nèi)容如下:
{
"name": "com.spirit.test",
"version": "1.0.0",
"description": "",
"main": "index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
3.安裝express并寫入package.json依賴
用一行命令就可以搞定了:
npm install --save express
沒錯(cuò),這行命令的意思就是在當(dāng)前目錄的"node_modules"下安裝express(及其所有依賴),并將express依賴寫到package.json中去,是不是很方便?
此時(shí)的package.json的內(nèi)容如下,express前面那個(gè)"^"相信你能猜到是什么意思:
{
"name": "com.spirit.test",
"version": "1.0.0",
"description": "",
"main": "index.mjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.16.2"
}
}
4.編寫index.mjs
接下來就可以開始搬磚了,請(qǐng)?jiān)贓xpressStarter文件夾下創(chuàng)建index.mjs文件,再掏出你最愛的IDE/編輯器,把下面的代碼放進(jìn)去。愛掏網(wǎng) - it200.comDemo代碼僅僅11行,相信不用解釋你也能看懂。愛掏網(wǎng) - it200.com
import express from 'express'
let app = express();
app.get('/', function (req, res) {
res.send('Hello World!');
});
app.listen(3000, function(){
console.log('Example app listening on port 3000!');
});
5.運(yùn)行Demo
使用下述命令即可運(yùn)行我們的Demo:
node --experimental-modules index.mjs
如果你看到了"Example app listening on port 3000!"這行提示,那么馬上打開瀏覽器訪問"http://127.0.0.1:3000/" 看看效果吧!
你應(yīng)該注意到了"--experimental-modules"這個(gè)參數(shù),它跟我們使用".mjs"的文件名后綴的原因一樣:因?yàn)镹ode.js之前使用CommonJs規(guī)范,在引用其它模塊是使用的是"require"關(guān)鍵字,這里我們直接使用了ES6的"import",所以需要開啟這項(xiàng)目前還是實(shí)驗(yàn)性的功能,而為了與以前保持兼容、使用ES6 Module規(guī)范的模塊都需要使用".mjs"作為后綴名。愛掏網(wǎng) - it200.com
6.其它說明
Demo到這里就演示結(jié)束了,你心里可能有些疑惑,但如果想要深入鉆研就要靠你自己了。愛掏網(wǎng) - it200.com
但可以多說一些的是,在常規(guī)項(xiàng)目中,"node_modules"文件夾是要被加入到.gitignore里面去的、不要把它一起提交到git上去,其他同學(xué)在clone該項(xiàng)目后,直接在項(xiàng)目文件夾下執(zhí)行"npm install"就可以把依賴下載到"node_modules"文件夾里面了。愛掏網(wǎng) - it200.com
而且在package.json的"scripts"中可以加入編譯、清理之類的命令,其作用大致相當(dāng)于mvn compile、mvn clean。愛掏網(wǎng) - it200.com
先告訴大家一個(gè)不幸的消息:
上面的內(nèi)容其實(shí)還只介紹了JavaScript語法、Node.js及項(xiàng)目基本結(jié)構(gòu),還有一個(gè)用Node.js開發(fā)Web服務(wù)的Demo,可以說跟“前端開發(fā)”還沒有半毛錢的關(guān)系。愛掏網(wǎng) - it200.com
但是!Node.js、npm、package.json是現(xiàn)代前端工程的基石,我們需要它們來完成對(duì)前端工程的構(gòu)建(包括“編譯”、打包、測試等等)。愛掏網(wǎng) - it200.com
下面我們先來學(xué)習(xí)大名鼎鼎的React,并以此為出發(fā)點(diǎn)來學(xué)習(xí)各種前端構(gòu)建工具。愛掏網(wǎng) - it200.com
簡介
React是起源于Facebook的一個(gè)前端框架,虛擬DOM技術(shù)是其基石。愛掏網(wǎng) - it200.comReact提出了JSX語法,而組件化是其核心思想。愛掏網(wǎng) - it200.com
以“傳統(tǒng)”的方式將React引入頁面
先拋開Node.js那些東西,讓我們以傳統(tǒng)的方式在在頁面中引入React。愛掏網(wǎng) - it200.com你可以將下面的html代碼拷貝下來放到一個(gè)htm文件里面去,再用瀏覽器打開看看效果。愛掏網(wǎng) - it200.com
React
來看html中的那段JavaScript, "
Hello, world!
"是什么? 它不是字符串,因?yàn)闆]有引號(hào),很明顯它也不像是一個(gè)“正常的對(duì)象”。愛掏網(wǎng) - it200.com 這就是React獨(dú)有的JSX語法,是React創(chuàng)建虛擬DOM的一種方式,這個(gè)h1標(biāo)簽在稍后會(huì)被“編譯”(或稱翻譯)為一個(gè)真正的JavaScript對(duì)象。愛掏網(wǎng) - it200.com可以發(fā)生html中的那個(gè)script標(biāo)簽的type是"text/babel"、而常規(guī)的JavaScript的type是"text/javascript",這是為什么? 這是因?yàn)镴SX語法很明顯和JavaScript是不兼容的,所以用到了JSX的地方都聲明為"text/babel"。愛掏網(wǎng) - it200.com
那babel又是什么?Babel是一個(gè)JavaScript“編譯器”,它可以使用了ES6特性的JavaScript代碼編譯為符合ES 5.1標(biāo)準(zhǔn)的代碼,也可以將JSX語法編譯為符合JavaScript語法的代碼,這里我們就是使用它來編譯了含JSX的代碼。愛掏網(wǎng) - it200.com
需要注意的是,在實(shí)際情況下編譯這一步是預(yù)先完成的、而不是像本Demo這樣在客戶端加載babel來完成,在Chrome的控制臺(tái)中你也可以看到Babel輸出的警告,不過這一章我們只講“傳統(tǒng)”方式,先不講構(gòu)建。愛掏網(wǎng) - it200.com
Component-組件
React中的組件是其基本設(shè)計(jì)思想,通過組件與JSX的組合,更是可以發(fā)揮出無窮的威力。愛掏網(wǎng) - it200.com那接下來就把Demo改造成一個(gè)使用組件完成試試,這里只展示JavaScript代碼了:
HelloMessage類中的render()方法就好比各種GUI庫里面的paint方法,在界面需要繪制時(shí)會(huì)被調(diào)用。愛掏網(wǎng) - it200.com
那props怎么來的? 在此例中,React會(huì)將{name:"Spirit"}作為props傳給HelloMessage組件。愛掏網(wǎng) - it200.com
State
React 把組件看成是一個(gè)狀態(tài)機(jī)(State Machines)。愛掏網(wǎng) - it200.com通過與用戶的交互,實(shí)現(xiàn)不同狀態(tài),然后渲染 UI,讓用戶界面和數(shù)據(jù)保持一致。愛掏網(wǎng) - it200.com
簡單地說,每個(gè)組件除開有props屬性外,還有一個(gè)state屬性,當(dāng)state發(fā)生改變時(shí),會(huì)觸發(fā)組件的render()方法。愛掏網(wǎng) - it200.com
所以我們?cè)賮砝^續(xù)修改Demo,實(shí)現(xiàn)在點(diǎn)擊時(shí)切換內(nèi)容顯示。愛掏網(wǎng) - it200.com
在此例中,我們實(shí)現(xiàn)了構(gòu)建方法并顯式地接收了props,還調(diào)用了super方法(與Java一樣,這應(yīng)該是構(gòu)建方法中的第一行代碼),并設(shè)置了state對(duì)象的值,當(dāng)組件被點(diǎn)擊時(shí)、state會(huì)被改變、render方法也會(huì)被再次調(diào)用。愛掏網(wǎng) - it200.com
其中的"onClick"是JSX的語法,與傳統(tǒng)的HTML寫法有所不同,具體的知識(shí)需要詳細(xì)學(xué)習(xí)JSX語法及事件相關(guān)的知識(shí)。愛掏網(wǎng) - it200.com
在上一章我們學(xué)習(xí)了React的基礎(chǔ)知識(shí),但我們都知道現(xiàn)在前端js與我們的html頁面是分離的,而且前端發(fā)布時(shí)一般都需要先進(jìn)行構(gòu)建,那么前端構(gòu)建又是怎么回事呢? 讓我們繼續(xù)用上面的Demo來管窺一番吧。愛掏網(wǎng) - it200.com
Gulp
Gulp是現(xiàn)在流行的前端構(gòu)建工具,其作用類似于Java體系中的Maven...等等,之前不是說npm也相當(dāng)于Maven嗎,為什么要使用Gulp而不直接使用npm scripts? 按我的理解,npm scripts更通用更靈活更底層,而Gulp更適合應(yīng)對(duì)前端資源構(gòu)建。愛掏網(wǎng) - it200.com
接下來我們就一步一步地搭建構(gòu)建環(huán)境,來構(gòu)建上一章的那個(gè)React Demo。愛掏網(wǎng) - it200.com
1.初始化項(xiàng)目
很常規(guī)地,創(chuàng)建文件夾,npm init即可。愛掏網(wǎng) - it200.com
mkdir ReactStarter
cd ReactStarter
npm init
2.安裝react依賴
通過npm安裝react、react-dom的Node.js包
npm install --save react react-dom
3.碼代碼
在項(xiàng)目目錄下創(chuàng)建"index.htm"文件,其內(nèi)容如下:
React
在項(xiàng)目目錄下創(chuàng)建"src"目錄,在其下新建"index.js"文件,內(nèi)容如下:
import React from 'react';
import ReactDOM from 'react-dom';
class HelloMessage extends React.Component {
constructor(props) {
super(props);
/*
* 如果不寫這行在下面的handleClick方法中獲取到的this指針將是undifined。愛掏網(wǎng) - it200.com
* 具體原因可以看阮一峰老師的《ECMAScript 6 入門》。愛掏網(wǎng) - it200.com
*/
this.handleClick = this.handleClick.bind(this);
this.state = {liked: false};
}
handleClick() {
this.setState({liked: !this.state.liked});
}
render() {
let text = this.state.liked ? 'like' : "don't like"
return Hello, I {text} {this.props.name}
;
}
}
ReactDOM.render(
,
document.getElementById('root')
);
4.配置Babel
前面有提到,JSX語法不是JavaScript標(biāo)準(zhǔn)、ES6也不一定是所有瀏覽器都兼容的,我們需要使用Babel來進(jìn)行“編譯”。愛掏網(wǎng) - it200.com
首先是通過npm安裝Babel及React、ES2024(即ES6)插件,注意指令是"--save-dev",將它們安裝為開發(fā)依賴。愛掏網(wǎng) - it200.com
npm install --save-dev babel-cli babel-preset-react babel-preset-es2024
然后在項(xiàng)目目錄下新建".babelrc"文件,這是Babel的配置文件,其內(nèi)容如下:
{
"presets": [
"react",
"es2024"
],
"plugins": []
}
5.配置Gulp
同樣地,先安裝Gulp及其Babel插件到開發(fā)依賴中去:
npm install --save-dev gulp gulp-babel
然后在項(xiàng)目目錄下新建"gulpfile.js"文件,這是Gulp的配置文件,其內(nèi)容如下所示,注意下這里仍然使用Node.js的"require"而不是"import"。愛掏網(wǎng) - it200.com
const gulp = require('gulp');
const babel = require('gulp-babel');
gulp.task('default', function() {
// 將你的默認(rèn)的任務(wù)代碼放在這
gulp.src(['src/*.js'])
.pipe(babel())
.pipe(gulp.dest('build'));
});
好了,Gulp和Babel都配置好了,在項(xiàng)目目錄下執(zhí)行下"gulp"命令構(gòu)建一下試試。愛掏網(wǎng) - it200.com
在build目錄下我們找到了"index.js",用編輯器打開其內(nèi)容,可以發(fā)現(xiàn)原來的JSX語法和ES6語法確實(shí)被“編譯”了,但卻是被編譯為了Node.js的語法,如"require",React也還是作為一個(gè)Node.js依賴沒有被編譯到一起,這樣的編譯結(jié)果無法在瀏覽器中正常使用,那要怎么辦呢?
6.配置Webpack
Webpack是一個(gè)前端資源打包工具,它能夠?qū)δK的依賴關(guān)系進(jìn)行分析,并將這些資源都打包成可在瀏覽器上運(yùn)行的形式。愛掏網(wǎng) - it200.com
同樣,先安裝Webpack的依賴:
npm install --save-dev gulp-webpack
再來修改下gulpfile.js,加上Webpack的流程:
const gulp = require('gulp');
const babel = require('gulp-babel');
const webpack = require('gulp-webpack');
gulp.task('default', function() {
// 將你的默認(rèn)的任務(wù)代碼放在這
gulp.src(['src/*.js'])
.pipe(babel())
.pipe(gulp.dest('build'))
.pipe(webpack({
output:{filename:'bundle.js'}
}))
.pipe(gulp.dest('build'));
});
再執(zhí)行"gulp"命令,"build"目錄下會(huì)生成"bundle.js",其中的內(nèi)容整合了"index.js"及其所有依賴。愛掏網(wǎng) - it200.com
現(xiàn)在打開項(xiàng)目目錄下的"index.htm",頁面應(yīng)該是運(yùn)行正常的,這下我們實(shí)現(xiàn)了一個(gè)初具雛形的前端構(gòu)建環(huán)境。愛掏網(wǎng) - it200.com
7.善后工作
此時(shí)可以再修改一個(gè)"package.json",去掉里面的"main"這項(xiàng),再往"script"中加入"build"腳本:
"scripts": {
"build": "gulp"
}
這樣當(dāng)執(zhí)行"npm run build"命令時(shí),實(shí)際上也是啟動(dòng)了gulp進(jìn)行構(gòu)建工作。愛掏網(wǎng) - it200.com
總結(jié)
需要注意的是,這個(gè)構(gòu)建的Demo配置得比較簡單,代碼沒有壓縮、混淆,也沒有去除注釋,如果你在代碼里面寫了什么不夠友善的注釋、在單量之類的數(shù)據(jù)上用了隨機(jī)數(shù),小心被用戶一眼就看出來了哦。愛掏網(wǎng) - it200.com
實(shí)際的前端工程配置會(huì)比這復(fù)雜不少,各個(gè)BU、各個(gè)前端團(tuán)隊(duì)目前應(yīng)該也有不同的構(gòu)建工具及配置,革命尚未成功,同志仍需繼續(xù)學(xué)習(xí)!
這篇文章只是我作為一個(gè)前端菜鳥、淺嘗輒止地學(xué)習(xí)了一下前端相關(guān)的部分知識(shí)后的一個(gè)總結(jié),相信既不全面、也會(huì)有不少錯(cuò)誤之處,發(fā)出來一是希望能給同樣對(duì)前端技術(shù)感興趣的同學(xué)一點(diǎn)參考,也是希望能接受到大家的指導(dǎo)。愛掏網(wǎng) - it200.com
前端技術(shù)仍在飛速發(fā)展,想要真正的做到有些理解和感悟,還得不斷地學(xué)習(xí)與練習(xí),學(xué)而時(shí)習(xí)之,不亦說乎?