哈哈,各位園友新年快樂!愚安很久沒在園子裏寫東西了,此次決定針對javascript作一個系列,叫作《小王子淺讀Effective javascript》,主要是按照David Herman的《Effective javascript》的內容作一些解讀和擴展。原書中有68點針對javascript編程的小tips,非常經典,園友們也能夠網上找找PDF讀一下,寫的很不錯,也能夠買買原著讀讀,提醒下,這本書的中文譯本也已經出了。爲何叫「淺讀」,一方面是愚安並非專業javascript程序員,有些地方的理解可能不夠深入,也不會按照原著條目的順序和數量來寫,另外一方面,人要低調嘛,哈哈哈!爲何叫小王子呢?愚安個人一系列ID常常被同窗吐槽太屌絲,因此在這個系列中暫用「小王子」這個高大上的ID。javascript
廢話說的有點多,進入正題。html
JavaScript是ECMAScript腳本語言的一個分支。ECMAScript 是Ecma國際組織標準化的,這門語言的另外2個分支是ActionScript(macroMedia,Adobe)和JScript(微軟)。JavaScript是有Netscape的Brendan Eich開發的,最初叫Mocha然後是LiveScript,最後改成JavaScript。1993.3月,Sun公司發佈了支持JavaScript的Navigator2.0(譯者:我感受這是錯誤的,可查看原文)。鑑於JavaScript做爲客戶端腳本語言取得普遍流行,微軟制定了本身的腳本語言JScript,發佈於1996.8月的ie3.0中。Netscape公司在日內瓦提交了JavaScript給Ecma國際標準化組織,申請成爲標準。java
版本一覽及瀏覽器支持狀況node
須要指出的是,目前全部的主流 Web 瀏覽器都支持 ECMA-262 第三版,即JavaScript 1.5版本,JavaScript 1.6-1.9只是ECMAScript (JavaScript on Gecko)升級至JavaScript 2.0的臨時代號。python
var JS_ver = [];
(Number.prototype.toFixed)?JS_ver.push("1.5"):false;
([].indexOf && [].forEach)?JS_ver.push("1.6"):false;
((function(){try {[a,b] = [0,1];return true;}catch(ex) {return false;}})())?JS_ver.push("1.7"):false;
([].reduce && [].reduceRight && JSON)?JS_ver.push("1.8"):false;
("".trimLeft)?JS_ver.push("1.8.1"):false;
JS_ver.supports = function()
{
if (arguments[0])
return (!!~this.join().indexOf(arguments[0] +",") +",");
else
return (this[this.length-1]);
}
alert("Latest Javascript version supported: "+ JS_ver.supports());
alert("Support for version 1.7 : "+ JS_ver.supports("1.7"));
於某種緣由,Javascript 1.7版本的某些特性是沒有獲得普遍的支持。不過大部分瀏覽器都支持了1.8版和1.8.1版的特性。(注:全部的IE瀏覽器(IE8或者更老的版本)只支持1.5版的Javascript)這個腳本,既能經過檢測特徵來檢測JavaScript版本,它還能檢查特定的Javascript版本所支持的特性。程序員
因爲最終用戶可能使用不一樣Web瀏覽器的不一樣版本,所以,咱們必須精心地編寫Web程序,使得其在全部的瀏覽器上始終工做如一。es6
接下來,咱們來聊一下各個版本的javascript的一些不一樣特性;golang
首先,原書中講到const定義常量,這個特性在ECMAScript標準中,並未定義,但不少版本的js中都把const做爲關鍵字,用於定義常量。但各個實現並不相同,有的沒法更改,有的只是跟var同樣,能夠更改。web
const PI = 3.141592653589793;
PI = "modified!";
PI; // 3.141592653589793
或者ajax
const PI = 3.141592653589793;
PI = "modified!";
PI; // "modified!"
在ie下,對const定義的常量進行再賦值,會彈出語法錯誤。
書中,提到的第二個例子是「use strict」。咱們知道,爲了更好的規範,和更好的兼容性,咱們會在js代碼中加入「use strict」。一樣,你也能夠在函數體的開始處加入這句指令以啓用該函數的嚴格模式。
function (x){
"use strict";
//....
}
使用字符串字面量做爲指令語法看起來有點怪異,但它的好處是向後兼容。因爲解釋執行字符串字面量並無任何反作用,因此ES3引擎執行這條指令是無傷大雅的。ES3引擎解釋執行該字符串,而後當即丟棄其值。這使得編寫的嚴格模式的代碼能夠運行在舊的JavaScript引擎上,但有一個重要的限制:舊的引擎不會進行任何的嚴格模式檢查。若是你沒有在ES5環境中作過測試,那麼,編寫的代碼運行於ES5環境中就很容易出錯。
function f() {
var arguments = []; // error: redefinition of arguments
// ...
}
在嚴格模式下,不容許重定義arguments變量,但沒有實現嚴格模式檢查的環境會接受這段代碼。然而,這段代碼部署在實現ES5的產品環境中將致使程序出錯。因此,你應該老是在徹底兼容ES5的環境中測試嚴格代碼。
「use strict」指令只有在腳本或函數的頂部才能生效,這也是使用嚴格模式的一個陷阱。這樣,腳本鏈接變得頗爲敏感。對於一些大型的應用軟件,在開發中使用多個獨立的文件,然而部署到產品環境時卻須要鏈接成一個單一的文件。例如,想將一個文件運行於嚴格模式下,咱們只須要在文件開頭加入「use strict」,然而將多個javascript文件合併時,「use strict」只在開頭時出現,其後面的代碼才生效,因此在合併文件時,應當將其放在腳本的最開頭。如:
// file2.js
// 非嚴格模式
function g() {
var arguments = [];
// ...
} // ...
// file1.js
"use strict";
function f() { // 嚴格模式不在生效
// ...
} // ...
在本身的項目中,你能夠堅持只使用「嚴格模式」或只使用「非嚴格模式」的策略,但若是你要編寫健壯的代碼應對各類各樣的代碼鏈接,你有兩個可選的方案。
第一個解決方案是不要將進行嚴格模式檢查的文件和不進行嚴格模式檢查的文件鏈接起來。這多是最簡單的解決方案,但它無疑會限制你對應用程序或庫的文件結構的控制力。在最好的狀況下,你至少要部署兩個獨立的文件。一個包含全部指望進行嚴格檢查的文件,另外一個則包含全部無須進行嚴格檢查的文件。
第二個解決方案是經過將其自身包裹在當即調用的函數表達式(Immediately Invoked Function Expression, IIFE)中的方式鏈接多個文件。總之,將每一個文件的內容包裹在一個當即調用的函數中,即便在不一樣的模式下,它們都將被獨立地解釋執行。
// 未定義嚴格模式
(function () {
// file1.js
"use strict";
function f() {
// ...
}// ...
})();
(function () { // file2.js
// 非嚴格模式
function f() {
var arguments = []; // ...
}// ...
})();
編寫文件使其在兩種模式下行爲一致。想要編寫一個庫,使其能夠工做在儘量多的環境中,你不能假設庫文件會被腳本鏈接工具置於一個函數中,也不能假設客戶端的代碼庫是否處於嚴格模式或者非嚴格模式。要想構建代碼以得到最大的兼容性,最簡單的方法是在嚴格模式下編寫代碼,並顯式地將代碼內容包裹在本地啓用了嚴格模式的函數中。這種方式相似於前面描述的方案——將每一個文件的內容包裹在一個當即調用的函數表達式中,但在這種狀況下,你是本身編寫當即調用的函數表達式而且顯式地選擇嚴格模式,而不是採用腳本鏈接工具或模塊系統幫你實現。
(function () {
"use strict";
function f() {
// ...
}
// ...
})();
要注意的是,不管這段代碼是在嚴格模式仍是在非嚴格模式的環境中鏈接的,它都被視爲是嚴格的。相比之下,即便一個函數沒有選擇嚴格模式,若是它鏈接在嚴格代碼以後,它仍被視爲是嚴格的。因此,爲了達到更爲廣泛的兼容性,建議在嚴格模式下編寫代碼。
小王子擴展閱讀:
ECMAScript 6 是JavaScript的下一個標準,正處在快速開發之中,大部分已經完成了。預計Mozilla將在這個標準的基礎上,推出JavaScript的 2.0。ECMAScript 6 的目標,是使得JavaScript能夠用來編寫複雜的應用程序、函數庫和代碼的自動生成器(code generator)。最新的瀏覽器已經部分支持ECMAScript 6 的語法,能夠經過《ECMAScript 6 瀏覽器兼容表》查看瀏覽器支持狀況。
在Chrome下,能夠訪問chrome://flags 下,開啓javascript實驗特性,能夠作一些測試
let關鍵字相似於var,用來聲明變量,可是該變量只在聲明所在的塊級做用域有效。
下面的代碼若是使用var,最後輸出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6]();
若是使用let,聲明的變量僅在塊級做用域內有效,最後輸出的是6。
var a = [];
for (var i = 0; i < 10; i++) {
let c = i;
a[i] = function () {
console.log(c);
};
}
a[6]();
let實際上爲JavaScript新增了塊級做用域。
function doSomething() {
let N = 5;
if (someCondition) {
let N = 10;
doSomethingElse(N);
}
console.log(N); // 5
}
上面的代碼有兩個代碼塊,都聲明瞭變量N。能夠看到,外層代碼塊不受內層代碼塊的影響。若是使用var定義變量,最後輸出的值就是10。
const與let的做用類似,也用來在塊級做用域聲明變量。可是,它聲明的是常量,一旦聲明,它的值就不能改變。
const PI = 3.1415;
PI // 3.1415
PI = 3;
PI // 3.1415
const PI = 3.1; PI // 3.1415
ECMAScript 6 提供了新的數據結構Set。它相似於數組,可是全部值都是惟一的。
var e = new Set(); // 新建集合
e.add("1") // 加入集合
e.add("2")
e.add("3")
e.add("4")
e.add("4") // 注意「4」被加入了兩次
e.has("1") // true
e.has("4") // true
e.has("5") // false
e.delete("4"); // delete item
e.has("4") // false
ECMAScript 6 還提供了map數據結構。它就是一個鍵值對的數據結構,相似於對象,可是「鍵」的範圍不限於字符串。
var es6 = new Map(); // 新建Map
es6.set("edition", 6) // 鍵是字符串
es6.set(262, "standard") // 鍵是數值
es6.set(undefined, "nah") // 鍵是undefined
var hello = function() {
console.log("hello");
}
es6.set(hello, "Hello ES6!") // 鍵是函數
es6.has("edition") // true
es6.has("years") // false
es6.has(262) // true
es6.has(undefined) // true
es6.has(hello) // true
es6.delete(undefined) // delete map
es6.has(undefined) // false
es6.get(hello) // Hello ES6!
es6.get("edition") // 6
ECMAScript 6引入擴展運算符(...),容許獲取函數的多餘參數。
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
console.log( item );
});
}
var planets = [];
console.log("太陽系的內層行星是:" ); // 1個固定參數 + 4個可變參數
push(planets, "Mercury", "Venus", "Earth", "Mars");
**********golang玩家,是否是以爲很熟悉,O(∩_∩)O哈哈~
這種表示法不只能夠用於函數定義,還能夠用於函數調用。
function createURL (comment, path, protocol, subdomain, domain, tld) {
var url = comment + ": " + protocol + "://" + subdomain + "." + domain + "." + tld + "/" + path;
console.log(url);
}
var weblink = ["hypertext/WWW/TheProject.html", "http", "info", "cern", "ch"],
comment = "世界上第一個網站";
createURL(comment, ...weblink ); // spread operator
從上面的例子能夠看出,擴展運算符能夠將數組轉變成正常的參數序列。
var max = Math.max(...[14, 3, 77]);
ECMAScript 6 引入了generator 函數,容許函數內部暫停執行某些操做。
function* foo() {
yield 'foo';
yield 'bar';
yield 'baz';
}
上面就是一個generator函數,定義時function關鍵字後面加上星號。而後,函數內部就可使用yield關鍵字,表示暫停執行某個操做,等到外部調用next方法時再執行。
var x = foo();
x.next().value // 'foo'
x.next().value // 'bar'
x.next().value // 'baz'
關於generator,node.js-11.24(unstable)已經開始支持。因爲小王子我也曾經用node.js寫過一些東西,對其關注度還算高,最新的Koa框架就是基於這一特性編寫的。稍後,
我將爲generator專門寫一遍隨筆,向一些對其還不是很瞭解的園友,介紹下這方面的內容。
ECMAScript 6 容許直接寫入函數,做爲對象的方法。這樣的書寫更加簡潔。
// ES 6
var Person = {
name: 'Joe',
hello() {
console.log('Hello, my name is', this.name);
}
};
ECMAScript 6 容許函數的簡寫形式做爲回調函數,再也不須要function和return關鍵,最後一個表達式就是函數的返回值。
// ES 5
[1,2,3].map(function (x) {
return x * x;
});
// ES 6
[1,2,3].map(x => x * x);
ECMAScript 6 容許爲函數的參數設置默認值。
function history(lang = "C", year = 1972) {
return lang + " was created around the year " + year;
}
**********PHPer是否是也以爲很熟悉啊O(∩_∩)O哈哈~
ECMAScript 6 提供簡潔寫法,對數組進行處理。
// ES 5
[1, 2, 3].map(function (i) {
return i * i;
});
// ES 6
[for (i of [1, 2, 3]) i * i];
// ES 5
[1,4,2,3,-8].filter(function(i) {
return i < 3;
});
// ES 6
[for (i of [1,4,2,3,-8]) if (i < 3) i];
新引入的for...of運算符,能夠直接跟在表達式的前面或後面。
// 一重循環
var temperature = [0, 37, 100];
[t + 273 for (t of temperature)]; // [273, 310, 373]
// 三重循環
var a1 = ["x1", "y1"],
a2 = ["x2", "y2"],
a3 = ["x3", "y3"];
[(console.log(s + w + r))
for (s of a1)
for (w of a2)
for (r of a3)
];
ECMAScript 6 容許簡潔地對多變量賦值。
正常狀況下,將數組元素賦值給多個變量,只能一次次分開賦值。
var first = someArray[0]; var second = someArray[1]; var third = someArray[2];
在ECMAScript 6 中能夠寫成
var [first, second, third] = someArray;
這種賦值寫法在語法上很是靈活。
var [ start, end ] = ["earth", "moon"]
[start, end] = [end, start] // 變量互換
var [foo, [[bar], baz]] = [1, [[2], 3]]
var [,,third] = ["foo", "bar", "baz"]
var [head, ...tail] = [1, 2, 3, 4]
*********上面這個變量互換,是否是又讓golang玩家興奮了,因此說現代語言的進步,一大方面,就是簡化一些操做的寫法,提升編程效率
它還能夠接受默認值。
var [missing = true] = [];
console.log(missing) // true
var { x = 3 } = {};
console.log(x) // 3
它不只能夠用於數組,還能夠用於對象。
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo) // "lorem"
console.log(bar) // "ipsum"
var o = { p1: [ "Hello", { p2: "World" } ] };
var { a: [p1, { p2 }] } = o;
console.log(p1) // "Hello"
console.log(p2) // "World"
有了這種用法,函數定義和調用時,使用參數就很方便。
function f({p1, p2, p3}) { // ... }
賦給函數參數默認值,也容易多了。
jQuery.ajax = function (url,{
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true, // ... more config }
) {
// ... do stuff
};
JavaScript的for...in結構,只能得到鍵,不能直接獲取值。
var planets = ["Mercury", "Venus", "Earth", "Mars"];
for (p in planets) {
console.log(p); // 0,1,2,3
}
var es6 = {
edition: 6, committee: "TC39", standard: "ECMA-262"
};
for (e in es6) {
console.log(e); // edition, committee, standard
}
ECMAScript 6 提供for...of結構,容許得到值。
var planets = ["Mercury", "Venus", "Earth", "Mars"];
for (p of planets) {
console.log(p); // Mercury, Venus, Earth, Mars
}
var engines = Set(["Gecko", "Trident", "Webkit", "Webkit"]);
for (var e of engines) {
console.log(e); // Set only has unique values, hence Webkit shows only once
}
var es6 = new Map();
es6.set("edition", 6);
es6.set("committee", "TC39");
es6.set("standard", "ECMA-262");
for (var [name, value] of es6) {
console.log(name + ": " + value);
}
ECMAScript 6 提供了「類」。在此以前,通常用構造函數模擬「類」。
// ES5
var Language = function(config) {
this.name = config.name;
this.founder = config.founder;
this.year = config.year;
};
Language.prototype.summary = function() {
return this.name + " was created by " + this.founder + " in " + this.year;
};
ECMAScript 6 容許使用class結構,達到一樣的效果。
// ES6
class Language {
constructor(name, founder, year) {
this.name = name;
this.founder = founder;
this.year = year;
}
summary() {
return this.name + " was created by " + this.founder + " in " + this.year;
}
}
// 生成實例
var js = new Language;
上面代碼的constructor方法,就是類的構造函數。
class結構還容許使用extends關鍵字,表示繼承。
class MetaLanguage extends Language {
constructor(x, y, z, version) {
super(x, y, z);
this.version = version;
}
}
上面代碼的super方法,表示調用父類的構造函數。
************在面向對象這一點上,javascript一直備受詬病,固然也有人以爲原型鏈的繼承方式很贊。但,大致上,仍是批評者較多,因此Google推出了Dart語言,基本上與ES6的特性相似。
ECMAScript 6 容許定義模塊。也就是說,容許一個JavaScript腳本文件調用另外一個腳本文件。
假設有一個circle.js,它是一個單獨模塊。
// circle.js
const PI = 3.1415;
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
而後,main.js引用這個模塊。
// main.js
import { area, circumference } from 'circle';
console.log("Area of the circle: " + area(4) + " meter squared");
console.log("Circumference of the circle: " + circumference(14) + " meters");
*****************import,from的寫法與python又很相似,仍是那句話javascript語言的進步都是在從別的語言中學習優勢。
好了,第一篇就寫到這裏了,目測,有點長,有什麼疏漏的地方,還望各位園友指出批評。
謝謝你們!!!