JavaScript 10分鐘入門

二胡槽子發表於2016-03-28

JavaScript 10分鐘入門

隨著公司內部技術分享(JS進階)投票的失利,先譯一篇不錯的JS入門博文,方便不太瞭解JS的童鞋快速學習和掌握這門神奇的語言。

以下為譯文,原文地址:http://www.codeproject.com/Articles/1006192/JavaScript-Summary


簡介

JavaScript是一門物件導向的動態語言,他一般用來處理以下任務:

  1. 修飾網頁
    • 生成HTML和CSS
    • 生成動態HTML內容
    • 生成一些特效
  2. 提供使用者互動介面
    • 生成使用者互動元件
    • 驗證使用者輸入
    • 自動填充表單
  3. 能夠讀取本地或者遠端資料的前端應用程式,例如http://web-engineering.info/JsFrontendApp-Book
  4. 通過Nodejs實現像JAVA,C#,C++一樣的服務端程式
  5. 實現分散式WEB程式,包括前端和服務端

當前瀏覽器所支援的JavaScript的版本被稱為“ECMAScript的5.1”,或簡單的“ES5”,但接下來的兩個版本,稱為“ES6”和“ES7”(或“ES2015”和“ES2016”,新版以本年命名),有很多的附加功能和改進的語法,是非常值得期待的(並已部分被當前的瀏覽器和後端JS的環境支援)。

此篇博文,引自《Building Front-End Web Apps with Plain JavaScript》一書。

JavaScript型別和常量

JS有3個值型別:string,number和boolean,我們可以用一個變數v儲存不同型別的值用來和typeof(v)比較, typeof(v)==="number"。

JS有5個引用型別:Object, Array, Function, Date 和 RegExp。陣列,函式,日期和正規表示式是特殊型別的物件,但在概念上,日期和正規表示式是值型別,被包裝成物件形式體現。

變數,陣列,函式的引數和返回值都可以不宣告,它們通常不會被JavaScript引擎檢查,會被自動進行型別轉換。

變數值可能為:

  1. 資料,如string,number,boolean
  2. 物件的引用:如普通物件,陣列,函式,日期,正規表示式
  3. 特殊值null,其通常用作用於初始化的物件變數的預設值
  4. 特殊值undefined,已經宣告但沒有初始化的初始值

string是Unicode字元序列。字串常量會被單引號或雙引號包裹著,如“Hello world!”,“A3F0',或者空字串""。兩個字串表示式可以用+操作符連線,並可通過全等於比較:

 if (firstName + lastName === "James Bond") 

字串的字元數量可以通過length屬性獲得:

console.log( "Hello world!".length);  // 12

所有的數字值都是在64位浮點數字。整數和浮點數之間沒有明確的型別區別。如果一個數字常量不是數字,可以將其值設定為NaN(“not a number”),它可以用isNaN方法來判斷。

不幸的是,直到ES6才有Number.isInteger方法,用於測試一個數是不是一個整數。因此在還不支援它的瀏覽器中,為確保一個數字值是一個整數,或者一個數字的字串被轉換為一個整數,就必須使用parseInt函式。類似地,包含小數的字串可用與parseFloat方法轉換。將一個數子n轉換成字串,最好的方法是使用String(n)。

就像Java,我們也有兩個預先定義好的布林型值,true與false,以及布林運算子符號: ! (非),&&(與),||(或)。當非布林型值與布林型值比較時,非布林型值會被隱式轉換。空字串,數字0,以及undefined和null,會被轉換為false,其他所有值會轉換為true。

通常我們需要使用全等符號符號(===和!==)而不是==和!=。否則,數字2是等於的字串“2”的, (2 == "2") is true

VAR= [] 和var a = new Array() 都可以定義一個空陣列。(二胡:推薦前者)

VAR O ={} 和 var o = new Obejct() 都可以定義個空物件(二胡:還是推薦前者)。注意,一個空物件{}不是真的空的,因為它包含的Object.prototype繼承屬性。所以,一個真正的空物件必須以Null為原型, var o = Object.create(null)。

表1 型別測試和轉換

JavaScript 10分鐘入門

變數作用域

在JavaScript的當前版本ES5,有兩種範圍變數:全域性作用域和函式作用域,沒有塊作用域。因此,應該避免宣告在塊內宣告變數。

function foo() {
  for (var i=0; i < 10; i++) {
    ...  // do something with i
  }
}

我們應該這樣寫

function foo() {
  var i=0;
  for (i=0; i < 10; i++) {
    ...  // do something with i
  }
}

所有變數應在函式的開始宣告。只有在JavaScript的下一個版本ES6中,我們可以用let關鍵詞宣告一個塊級變數。

嚴格模式

從ES5開始,我們可以使用嚴格模式,獲得更多的執行時錯誤檢查。例如,在嚴格模式下,所有變數都必須進行宣告。給未宣告的變數賦值丟擲異常。

我們可以通過鍵入下面的語句作為一個JavaScript檔案或script元素中的第一行開啟嚴格模式:'use strict';

通常建議您使用嚴格模式,除非你的程式碼依賴於與嚴格的模式不相容的庫。

不同型別的物件

JS物件與傳統的OO/UML物件不同。它們可以不通過類例項化而來。它們有屬性、方法、鍵值對三種擴充套件。

JS物件可以直接通過JSON產生,而不用例項化一個類。

var person1 = { lastName:"Smith", firstName:"Tom"};
var o1 = Object.create( null);  // an empty object with no slots

物件屬性可以以兩種方式獲得:

  1. 使用點符號(如在C ++/ Java的):

    person1.lastName = "Smith"

  2. 使用MAP方式

    person1["lastName"] = "Smith"

JS物件有不同的使用方式。這裡有五個例子:

  1. 記錄,例如,

    var myRecord = {firstName:"Tom", lastName:"Smith", age:26}
  2. MAP(也稱為“關聯陣列”,“詞典”或其他語言的“雜湊表”)

    var numeral2number = {"one":"1", "two":"2", "three":"3"}

  3. 非型別化物件

    var person1 = { lastName: "Smith", firstName: "Tom", getFullName: function () { return this.firstName +" "+ this.lastName; } };

  4. 名稱空間

    var myApp = { model:{}, view:{}, ctrl:{} };

    可以由一個全域性變數形式來定義,它的名稱代表一個名稱空間字首。例如,上面的物件變數提供了基於模型 - 檢視 - 控制器(MVC)架構模式,我們有相應的MVC應用程式的三個部分。

  5. 正常的類

陣列

可以用一個JavaScript陣列文字進行初始化變數:
var a = [1,2,3];

因為它們是陣列列表,JS陣列可動態增長:我們可以使用比陣列的長度更大的索引。例如,上面的陣列變數初始化後,陣列長度為3,但我們仍然可以操作第5個元素 a[4] = 7;

我們可以通過陣列的length屬性得到陣列長度:

`for (i=0; i < a.length; i++) { console.log(a[i]);} //1 2 3 undefined 7 ` 

我們可以通過 Array.isArray(a) 來檢測一個變數是不是陣列。

通過push方法給陣列追加元素:a.push( newElement);

通過splice方法,刪除指定位置的元素:a.splice( i, 1);

通過indexOf查詢陣列,返回位置或者-1:if (a.indexOf(v) > -1) ...

通過for或者forEach(效能弱)遍歷陣列:

var i=0;
for (i=0; i < a.length; i++) {
  console.log( a[i]);
}
a.forEach(function (elem) {
  console.log( elem);
}) 

通過slice複製陣列:var clone = a.slice(0);

Maps

map(也稱為“雜湊對映”或“關聯陣列')提供了從鍵及其相關值的對映。一個JS map的鍵是可以包含空格的字串:

var myTranslation = { 
"my house": "mein Haus", 
"my boat": "mein Boot", 
"my horse": "mein Pferd"
}

通過Object.keys(m)可以獲得map中所有的鍵:

var i=0, key="", keys=[];
keys = Object.keys( myTranslation);
for (i=0; i < keys.length; i++) {
  key = keys[i];
  alert('The translation of '+ key +' is '+ myTranslation[key]);
}

通過直接給不存在的鍵賦值來新增元素:

myTranslation["my car"] = "mein Auto";

通過delete刪除元素:

delete myTranslation["my boat"];

通過in搜尋map:

`if ("my bike" in myTranslation)  ...`

通過for或者forEach(效能弱)和Object.keys()遍歷map:

var i=0, key="", keys=[];
keys = Object.keys( m);
for (i=0; i < keys.length; i++) {
  key = keys[i];
  console.log( m[key]);
}
Object.keys( m).forEach( function (key) {
  console.log( m[key]);
}) 

通過 JSON.stringify 將map序列化為JSON字串,再JSON.parse將其反序列化為MAP物件 來實現複製:

var clone = JSON.parse( JSON.stringify( m)) 

請注意,如果map上只包含簡單資料型別或(可能巢狀)陣列/map,這種方法效果很好。在其他情況下,如果map包含Date物件,我們必須寫我們自己的clone方法。

Functions

JS函式是特殊的JS的物件,它具有一個可選的名字屬性和一個長度屬性(引數的數目)。我們可以這樣知道一個變數是不是一個函式:

if (typeof( v) === "function") {...}

JS函式可以儲存在變數裡、被當作引數傳給其他函式,也可以被其他函式作為返回值返回。JS可以被看成一個函式式語言,函式在裡面可以說是一等公民。

正常的定義函式方法是用一個函式表示式給一個變數賦值:

var myFunction = function theNameOfMyFunction () {...}
function theNameOfMyFunction () {...}

其中函式名(theNameOfMyFunction)是可選的。如果省略它,其就是一個匿名函式。函式可以通過引用其的變數呼叫。在上述情況下,這意味著該函式通過myFunction()被呼叫,而不是通過theNameOfMyFunction()呼叫。

JS函式,可以巢狀內部函式。閉包機制允許在函式外部訪問函式內部變數,並且建立閉包的函式會記住它們。

當執行一個函式時,我們可以通過使用內建的arguments引數,它類似一個引數陣列,我們可以遍歷它們,但由於它不是常規陣列,forEach無法遍歷它。arguments引數包含所有傳遞給函式的引數。我們可以這樣定義一個不帶引數的函式,並用任意數量的引數呼叫它,就像這樣:

var sum = function () {
  var result = 0, i=0;
  for (i=0; i < arguments.length; i++) {
    result = result + arguments[i];
  }
  return result;
};
console.log( sum(0,1,1,2,3,5,8));  // 20

prototype原型鏈可以訪問函式中的每一個元素,如Array.prototype.forEach(其中Array代表原型鏈中的陣列的建構函式)。

var numbers = [1,2,3];  // create an instance of Array
numbers.forEach( function (n) {
  console.log( n);
});

我們還可以通過原型鏈中的prototype.call方法來處理:

var sum = function () {
  var result = 0;
  Array.prototype.forEach.call( arguments, function (n) {
    result = result + n;
  });
  return result;
};

Function.prototype.apply是Function.prototype.call的一個變種,其只能接受一個引數陣列。

立即呼叫的JS函式表示式優於使用純命名物件,它可以獲得一個名稱空間物件,並可以控制其變數和方法哪些可以外部訪問,哪些不是。這種機制也是JS模組概念的基礎。在下面的例子中,我們定義了一個應用程式,它對外暴露了指定的元素和方法:

myApp.model = function () {
  var appName = "My app's name";
  var someNonExposedVariable = ...;
  function ModelClass1 () {...}
  function ModelClass2 () {...}
  function someNonExposedMethod (...) {...}
  return {
    appName: appName,
    ModelClass1: ModelClass1,
    ModelClass2: ModelClass2
  }
}();  // immediately invoked

這種模式在WebPlatform.org被當作最佳實踐提及:https://docs.webplatform.org/wiki/tutorials/javascript_best_practices

定義和使用類

類是在物件導向程式設計的基礎概念。物件由類例項化而來。一個類定義了與它建立的物件的屬性和方法。

目前在JavaScript中沒有明確的類的概念。JavaScript中定義類有很多不同的模式被提出,並在不同的框架中被使用。用於定義類的兩個最常用的方法是:

建構函式法,它通過原型鏈方法來實現繼承,通過new建立新物件。這是Mozilla的JavaScript指南中推薦的經典方法。

工廠方法:使用預定義的Object.create方法建立類的新例項。在這種方法中,基於建構函式繼承必須通過另一種機制來代替。

當構建一個應用程式時,我們可以使用這兩種方法建立類,這取決於應用程式的需求 。mODELcLASSjs是一個比較成熟的庫用來實現工廠方法,它有許多優點。(基於構造的方法有一定的效能優勢)

JavaScript 10分鐘入門

ES6中建構函式法建立類

在ES6,用於定義基於建構函式的類的語法已推出(新的關鍵字類的建構函式,靜態類和超類)。這種新的語法可以在三個步驟定義一個簡單的類。

基類Person 定義了兩個屬性firstName 和lastName,以及例項方法toString和靜態方法checkLastName:

class Person {
  constructor( first, last) {
    this.firstName = first;
    this.lastName = last;
  }
  toString() {
    return this.firstName + " " +
        this.lastName;
  }
  static checkLastName( ln) {
    if (typeof(ln)!=="string" || 
        ln.trim()==="") {
      console.log("Error: " +
          "invalid last name!");
    }
  }
}

類的靜態屬性如下定義:

Person.instances = {};

一個子類定義的附加屬性和可能會覆蓋超類的方法:

 class Student extends Person {
  constructor( first, last, studNo) {
    super.constructor( first, last);
    this.studNo = studNo; 
  }
  // method overrides superclass method
  toString() {
    return super.toString() + "(" +
        this.studNo +")";
  }
}

ES5中建構函式法建立類

在ES5,我們可以以建構函式的形式定義一個基於建構函式的類結構,下面是Mozilla的JavaScript指南中推薦的編碼模式。此模式需要七個步驟來定義一個簡單的類結構。由於這種複雜的模式可能很難記住,我們可能需要使用cLASSjs之類的庫來幫助我們。

首先定義建構函式是隱式建立一個新的物件,並賦予它相應的值:

function Person( first, last) {
  this.firstName = first; 
  this.lastName = last; 
}

這裡的this指向新建立的物件。

在原型中定義例項方法:

Person.prototype.toString = function () {
  return this.firstName + " " + this.lastName;
}

可以在建構函式中定義靜態方法,也可以用.直接定義:

Person.checkLastName = function (ln) {
  if (typeof(ln)!=="string" || ln.trim()==="") {
    console.log("Error: invalid last name!");
  }
}

定義靜態屬性:

Person.instances = {};

定義子類並增加屬性:

function Student( first, last, studNo) {
  // invoke superclass constructor
  Person.call( this, first, last);
  // define and assign additional properties
  this.studNo = studNo;  
}

通過Person.call( this, ...) 來呼叫基類的建構函式。

將子類的原型鏈改為基類的原型鏈,以實現例項方法的繼承(建構函式得改回來):

// Student inherits from Person
Student.prototype = Object.create( 
    Person.prototype);
// adjust the subtype's constructor property
Student.prototype.constructor = Student;

通過Object.create( Person.prototype) 我們基於 Person.prototype建立了一個新的物件原型。

定義覆蓋基類方法的子類方法:

Student.prototype.toString = function () {
  return Person.prototype.toString.call( this) +
      "(" + this.studNo + ")";
};

最後通過new關鍵字來例項化一個類

var pers1 = new Person("Tom","Smith");

JavaScript的prototype

prototype是函式的一個屬性(每個函式都有一個prototype屬性),這個屬性是一個指標,指向一個物件。它是顯示修改物件的原型的屬性。

__proto__是一個物件擁有的內建屬性(prototype是函式的內建屬性。__proto__是物件的內建屬性),是JS內部使用尋找原型鏈的屬性。

每個物件都有個constructor屬性,其指向的是建立當前物件的建構函式。

JavaScript 10分鐘入門

工廠模式建立類

在這種方法中,我們定義了一個JS物件Person,並在其內部定義了一個create方法用來呼叫Object.create來建立類。

var Person = {
  name: "Person",
  properties: {
    firstName: {range:"NonEmptyString", label:"First name", 
        writable: true, enumerable: true},
    lastName: {range:"NonEmptyString", label:"Last name", 
        writable: true, enumerable: true}
  },
  methods: {
    getFullName: function () {
      return this.firstName +" "+ this.lastName; 
    }
  },
  create: function (slots) {
    // create object
    var obj = Object.create( this.methods, this.properties);
    // add special property for *direct type* of object
    Object.defineProperty( obj, "type", 
        {value: this, writable: false, enumerable: true});
    // initialize object
    Object.keys( slots).forEach( function (prop) {
      if (prop in this.properties) obj[prop] = slots[prop];
    })
    return obj;
  }
};  

這樣,我們就有了一個Person的工廠類,通過呼叫create方法來例項化物件。

var pers1 = Person.create( {firstName:"Tom", lastName:"Smith"});

擴充套件閱讀

Speaking JavaScript, by Dr. Axel Rauschmayer.

Eloquent JavaScript, by Marijn Haverbeke.

Building Front-End Web Apps with Plain JavaScript, by Gerd Wagner


文章翻譯至此,喜歡的童鞋記得點贊,博文中的到的變數作用域、閉包、自執行函式、模組等等都是可以繼續說道說道的,如果有機會,我會分享在JS進階技術交流中。

相關文章