javascript高級實戰學習

學習目標:
  - 理解面向對象開發思想
  - 掌握 JavaScript 面向對象開發相關模式
  - 掌握在 JavaScript 中使用正則表達式
  - 
typora-copy-images-to media

JavaScript 高級

 

課程介紹

課程大綱

在線地址:JavaScript 高級javascript

目標

  • 理解面向對象開發思想html

  • 掌握 JavaScript 面向對象開發相關模式java

  • 掌握在 JavaScript 中使用正則表達式node

案例演示


基本概念複習

因爲 JavaScript 高級仍是針對 JavaScript 語言自己的一個進階學習,因此在開始以前咱們先對之前所學過的 JavaScript 相關知識點作一個快速複習總結。github

從新介紹 JavaScript

JavaScript 是什麼

  • 解析執行:輕量級解釋型的,或是 JIT 編譯型的程序設計語言正則表達式

  • 語言特色:動態,頭等函數 (First-class Function)數據庫

    • 又稱函數是 JavaScript 中的一等公民編程

  • 執行環境:在宿主環境(host environment)下運行,瀏覽器是最多見的 JavaScript 宿主環境數組

    • 可是在不少非瀏覽器環境中也使用 JavaScript ,例如 node.js

  • 編程範式:基於原型、多範式的動態腳本語言,而且支持面向對象、命令式和聲明式(如:函數式編程)編程風格

JavaScript 與瀏覽器的關係

 

JavaScript 的組成

組成部分 說明
Ecmascript 描述了該語言的語法和基本對象
DOM 描述了處理網頁內容的方法和接口
BOM 描述了與瀏覽器進行交互的方法和接口

JavaScript 能夠作什麼

Any application that can be written in JavaScript, will eventually be written in JavaScript. 凡是能用 JavaScript 寫出來的,最終都會用 JavaScript 寫出來

JavaScript 發展歷史

JavaScript 標準參考教程 - JavaScript 語言的歷史

  • JavaScript 的誕生

  • JavaScript 與 Ecmascript 的關係

  • JavaScript 與 Java 的關係

  • JavaScript 的版本

  • JavaScript 周邊大事記

小結

基本概念

本小節快速過便可,主要是對學過的內容作知識點梳理。

  • 語法

    • 區分大小寫

    • 標識符

    • 註釋

    • 嚴格模式

    • 語句

  • 關鍵字和保留字

  • 變量

  • 數據類型

    • typeof 操做符

    • Undefined

    • Null

    • Boolean

    • Number

    • String

    • Object

  • 操做符

  • 流程控制語句

  • 函數

JavaScript 中的數據類型

JavaScript 有 5 種簡單數據類型:Undefined、Null、Boolean、Number、String 和 1 種複雜數據類型 Object

基本類型(值類型)

  • Undefined

  • Null

  • Boolean

  • Number

  • String

複雜類型(引用類型)

  • Object

  • Array

  • Date

  • RegExp

  • Function

  • 基本包裝類型

    • Boolean

    • Number

    • String

  • 單體內置對象

    • Global

    • Math

類型檢測

  • typeof

  • instanceof

  • Object.prototype.toString.call()

值類型和引用類型在內存中的存儲方式(畫圖說明)

  • 值類型按值存儲

  • 引用類型按引用存儲

值類型複製和引用類型複製(畫圖說明)

  • 值類型按值複製

  • 引用類型按引用複製

值類型和引用類型參數傳遞(畫圖說明)

  • 值類型按值傳遞

  • 引用類型按引用傳遞

值類型與引用類型的差異

  • 基本類型在內存中佔據固定大小的空間,所以被保存在棧內存中

  • 從一個變量向另外一個變量複製基本類型的值,複製的是值的副本

  • 引用類型的值是對象,保存在堆內存

  • 包含引用類型值的變量實際上包含的並非對象自己,而是一個指向該對象的指針

  • 從一個變量向另外一個變量複製引用類型的值的時候,複製是引用指針,所以兩個變量最終都指向同一個對象

小結

  • 類型檢測方式

  • 值類型和引用類型的存儲方式

  • 值類型複製和引用類型複製

  • 方法參數中 值類型數據傳遞 和 引用類型數據傳遞

JavaScript 執行過程

JavaScript 運行分爲兩個階段:

  • 預解析

    • 全局預解析(全部變量和函數聲明都會提早;同名的函數和變量函數的優先級高)

    • 函數內部預解析(全部的變量、函數和形參都會參與預解析)

      • 函數

      • 形參

      • 普通變量

  • 執行

先預解析全局做用域,而後執行全局做用域中的代碼,在執行全局代碼的過程當中遇到函數調用就會先進行函數預解析,而後再執行函數內代碼。


JavaScript 面向對象編程

 

面向對象介紹

什麼是對象

Everything is object (萬物皆對象)

 

對象究竟是什麼,咱們能夠從兩次層次來理解。

(1) 對象是單個事物的抽象。

一本書、一輛汽車、一我的均可以是對象,一個數據庫、一張網頁、一個與遠程服務器的鏈接也能夠是對象。當實物被抽象成對象,實物之間的關係就變成了對象之間的關係,從而就能夠模擬現實狀況,針對對象進行編程。

(2) 對象是一個容器,封裝了屬性(property)和方法(method)。

屬性是對象的狀態,方法是對象的行爲(完成某種任務)。好比,咱們能夠把動物抽象爲animal對象,使用「屬性」記錄具體是那一種動物,使用「方法」表示動物的某種行爲(奔跑、捕獵、休息等等)。

在實際開發中,對象是一個抽象的概念,能夠將其簡單理解爲:數據集或功能集

ECMAScript-262 把對象定義爲:無序屬性的集合,其屬性能夠包含基本值、對象或者函數嚴格來說,這就至關於說對象是一組沒有特定順序的值。對象的每一個屬性或方法都有一個名字,而每一個名字都映射到一個值。

<p class="tip"> 提示:每一個對象都是基於一個引用類型建立的,這些類型能夠是系統內置的原生類型,也能夠是開發人員自定義的類型。</p>

什麼是面向對象

面向對象不是新的東西,它只是過程式代碼的一種高度封裝,目的在於提升代碼的開發效率和可維護性。

 

面向對象編程 —— Object Oriented Programming,簡稱 OOP ,是一種編程開發思想。它將真實世界各類複雜的關係,抽象爲一個個對象,而後由對象之間的分工與合做,完成對真實世界的模擬。

在面向對象程序開發思想中,每個對象都是功能中心,具備明確分工,能夠完成接受信息、處理數據、發出信息等任務。所以,面向對象編程具備靈活、代碼可複用、高度模塊化等特色,容易維護和開發,比起由一系列函數或指令組成的傳統的過程式編程(procedural programming),更適合多人合做的大型軟件項目。

面向對象與面向過程:

  • 面向過程就是親力親爲,事無鉅細,面面俱到,步步緊跟,有條不紊

  • 面向對象就是找一個對象,指揮得結果

  • 面向對象將執行者轉變成指揮者

  • 面向對象不是面向過程的替代,而是面向過程的封裝

面向對象的特性:

  • 封裝性

  • 繼承性

  • [多態性]

擴展閱讀:

程序中面向對象的基本體現

在 JavaScript 中,全部數據類型均可以視爲對象,固然也能夠自定義對象。自定義的對象數據類型就是面向對象中的類( Class )的概念。

咱們以一個例子來講明面向過程和麪向對象在程序流程上的不一樣之處。

假設咱們要處理學生的成績表,爲了表示一個學生的成績,面向過程的程序能夠用一個對象表示:

var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }

而處理學生成績能夠經過函數實現,好比打印學生的成績:


function printScore (student) {
 console.log('姓名:' + student.name + ' ' + '成績:' + student.score)
}

若是採用面向對象的程序設計思想,咱們首選思考的不是程序的執行流程,而是 Student 這種數據類型應該被視爲一個對象,這個對象擁有 namescore 這兩個屬性(Property)。若是要打印一個學生的成績,首先必須建立出這個學生對應的對象,而後,給對象發一個 printScore 消息,讓對象本身把本身的數據打印出來。

抽象數據行爲模板(Class):

function Student (name, score) {
 this.name = name
 this.score = score
}

Student.prototype.printScore = function () {
 console.log('姓名:' + this.name + ' ' + '成績:' + this.score)
}

根據模板建立具體實例對象(Instance):


var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)

實例對象具備本身的具體行爲(給對象發消息):


std1.printScore() // => 姓名:Michael 成績:98
std2.printScore() // => 姓名:Bob 成績 81

面向對象的設計思想是從天然界中來的,由於在天然界中,類(Class)和實例(Instance)的概念是很天然的。Class 是一種抽象概念,好比咱們定義的 Class——Student ,是指學生這個概念,而實例(Instance)則是一個個具體的 Student ,好比, Michael 和 Bob 是兩個具體的 Student 。

因此,面向對象的設計思想是:

  • 抽象出 Class

  • 根據 Class 建立 Instance

  • 指揮 Instance 得結果

面向對象的抽象程度又比函數要高,由於一個 Class 既包含數據,又包含操做數據的方法。

建立對象

簡單方式

咱們能夠直接經過 new Object() 建立:


var person = new Object()
person.name = 'Jack'
person.age = 18

person.sayName = function () {
 console.log(this.name)
}

每次建立經過 new Object() 比較麻煩,因此能夠經過它的簡寫形式對象字面量來建立:


var person = {
 name: 'Jack',
 age: 18,
 sayName: function () {
   console.log(this.name)
}
}

對於上面的寫法當然沒有問題,可是假如咱們要生成兩個 person 實例對象呢?


var person1 = {
 name: 'Jack',
 age: 18,
 sayName: function () {
   console.log(this.name)
}
}

var person2 = {
 name: 'Mike',
 age: 16,
 sayName: function () {
   console.log(this.name)
}
}

經過上面的代碼咱們不難看出,這樣寫的代碼太過冗餘,重複性過高。

簡單方式的改進:工廠函數

咱們能夠寫一個函數,解決代碼重複問題:


function createPerson (name, age) {
 return {
   name: name,
   age: age,
   sayName: function () {
     console.log(this.name)
  }
}
}

而後生成實例對象:


var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)

這樣封裝確實爽多了,經過工廠模式咱們解決了建立多個類似對象代碼冗餘的問題,但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)。

構造函數

內容引導:

  • 構造函數語法

  • 分析構造函數

  • 構造函數和實例對象的關係

    • 實例的 constructor 屬性

    • instanceof 操做符

  • 普通函數調用和構造函數調用的區別

  • 構造函數的返回值

  • 構造函數的靜態成員和實例成員

    • 函數也是對象

    • 實例成員

    • 靜態成員

  • 構造函數的問題

更優雅的工廠函數:構造函數

一種更優雅的工廠函數就是下面這樣,構造函數:


function Person (name, age) {
 this.name = name
 this.age = age
 this.sayName = function () {
   console.log(this.name)
}
}

var p1 = new Person('Jack', 18)
p1.sayName() // => Jack

var p2 = new Person('Mike', 23)
p2.sayName() // => Mike

解析構造函數代碼的執行

在上面的示例中,Person() 函數取代了 createPerson() 函數,可是實現效果是同樣的。這是爲何呢?

咱們注意到,Person() 中的代碼與 createPerson() 有如下幾點不一樣之處:

  • 沒有顯示的建立對象

  • 直接將屬性和方法賦給了 this 對象

  • 沒有 return 語句

  • 函數名使用的是大寫的 Person

而要建立 Person 實例,則必須使用 new 操做符。以這種方式調用構造函數會經歷如下 4 個步驟:

  1. 建立一個新對象

  2. 將構造函數的做用域賦給新對象(所以 this 就指向了這個新對象)

  3. 執行構造函數中的代碼

  4. 返回新對象

下面是具體的僞代碼:


function Person (name, age) {
 // 當使用 new 操做符調用 Person() 的時候,實際上這裏會先建立一個對象
 // var instance = {}
 // 而後讓內部的 this 指向 instance 對象
 // this = instance
 // 接下來全部針對 this 的操做實際上操做的就是 instance

 this.name = name
 this.age = age
 this.sayName = function () {
   console.log(this.name)
}

 // 在函數的結尾處會將 this 返回,也就是 instance
 // return this
}

構造函數和實例對象的關係

使用構造函數的好處不只僅在於代碼的簡潔性,更重要的是咱們能夠識別對象的具體類型了。在每個實例對象中的_proto_中同時有一個 constructor 屬性,該屬性指向建立該實例的構造函數:


console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true

對象的 constructor 屬性最初是用來標識對象類型的,可是,若是要檢測對象的類型,仍是使用 instanceof 操做符更可靠一些:


console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true

總結:

  • 構造函數是根據具體的事物抽象出來的抽象模板

  • 實例對象是根據抽象的構造函數模板獲得的具體實例對象

  • 每個實例對象都具備一個 constructor 屬性,指向建立該實例的構造函數

    • 注意: constructor 是實例的屬性的說法不嚴謹,具體後面的原型會講到

  • 能夠經過實例的 constructor 屬性判斷實例和構造函數之間的關係

    • 注意:這種方式不嚴謹,推薦使用 instanceof 操做符,後面學原型會解釋爲何

構造函數的問題

使用構造函數帶來的最大的好處就是建立對象更方便了,可是其自己也存在一個浪費內存的問題:


function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = function () {
   console.log('hello ' + this.name)
}
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

在該示例中,從表面上好像沒什麼問題,可是實際上這樣作,有一個很大的弊端。那就是對於每個實例對象,typesayHello 都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容,多佔用一些內存,若是實例對象不少,會形成極大的內存浪費。


console.log(p1.sayHello === p2.sayHello) // => false

對於這種問題咱們能夠把須要共享的函數定義到構造函數外部:


function sayHello = function () {
 console.log('hello ' + this.name)
}

function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = sayHello
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true

這樣確實能夠了,可是若是有多個須要共享的函數的話就會形成全局命名空間衝突的問題。

你確定想到了能夠把多個函數放到一個對象中用來避免全局命名空間衝突的問題:


var fns = {
 sayHello: function () {
   console.log('hello ' + this.name)
},
 sayAge: function () {
   console.log(this.age)
}
}

function Person (name, age) {
 this.name = name
 this.age = age
 this.type = 'human'
 this.sayHello = fns.sayHello
 this.sayAge = fns.sayAge
}

var p1 = new Person('lpz', 18)
var p2 = new Person('Jack', 16)

console.log(p1.sayHello === p2.sayHello) // => true
console.log(p1.sayAge === p2.sayAge) // => true

至此,咱們利用本身的方式基本上解決了構造函數的內存浪費問題。可是代碼看起來仍是那麼的格格不入,那有沒有更好的方式呢?

小結

  • 構造函數語法

  • 分析構造函數

  • 構造函數和實例對象的關係

    • 實例的 constructor 屬性

    • instanceof 操做符

  • 構造函數的問題

原型

內容引導:

  • 使用 prototype 原型對象解決構造函數的問題

  • 分析 構造函數、prototype 原型對象、實例對象 三者之間的關係

  • 屬性成員搜索原則:原型鏈

  • 實例對象讀寫原型對象中的成員

  • 原型對象的簡寫形式

  • 原生對象的原型

    • Object

    • Array

    • String

    • ...

  • 原型對象的問題

  • 構造的函數和原型對象使用建議

更好的解決方案: prototype

Javascript 規定,每個構造函數都有一個 prototype 屬性,指向另外一個對象。這個對象的全部屬性和方法,都會被構造函數的實例繼承。

這也就意味着,咱們能夠把全部對象實例須要共享的屬性和方法直接定義在 prototype 對象上。


function Person (name, age) {
 this.name = name
 this.age = age
}

console.log(Person.prototype)

Person.prototype.type = 'human'

Person.prototype.sayName = function () {
 console.log(this.name)
}

var p1 = new Person(...)
var p2 = new Person(...)

console.log(p1.sayName === p2.sayName) // => true

這時全部實例的 type 屬性和 sayName() 方法,其實都是同一個內存地址,指向 prototype 對象,所以就提升了運行效率。

構造函數、實例、原型三者之間的關係

 

任何函數都具備一個 prototype 屬性,該屬性是一個對象。


function F () {}
console.log(F.prototype) // => object

F.prototype.sayHi = function () {
 console.log('hi!')
}

構造函數的 prototype 對象默認都有一個 constructor 屬性,指向 prototype 對象所在函數。


console.log(F.constructor === F) // => true

經過構造函數獲得的實例對象內部會包含一個指向構造函數的 prototype 對象的指針 __proto__


var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true

<p class="tip"> __proto__ 是非標準屬性。</p>

實例對象能夠直接訪問原型對象成員。


instance.sayHi() // => hi!

總結:

  • 任何函數都具備一個 prototype 屬性,該屬性是一個對象

  • 構造函數的 prototype 對象默認都有一個 constructor 屬性,指向 prototype 對象所在函數

  • 經過構造函數獲得的實例對象內部會包含一個指向構造函數的 prototype 對象的指針 __proto__

  • 全部實例都直接或間接繼承了原型對象的成員

屬性成員的搜索原則:原型鏈

瞭解了 構造函數-實例-原型對象 三者之間的關係後,接下來咱們來解釋一下爲何實例對象能夠訪問原型對象中的成員。

每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定名字的屬性

  • 搜索首先從對象實例自己開始

  • 若是在實例中找到了具備給定名字的屬性,則返回該屬性的值

  • 若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性

  • 若是在原型對象中找到了這個屬性,則返回該屬性的值

也就是說,在咱們調用 person1.sayName() 的時候,會前後執行兩次搜索:

  • 首先,解析器會問:「實例 person1 有 sayName 屬性嗎?」答:「沒有。

  • 」而後,它繼續搜索,再問:「 person1 的原型有 sayName 屬性嗎?」答:「有。

  • 」因而,它就讀取那個保存在原型對象中的函數。

  • 當咱們調用 person2.sayName() 時,將會重現相同的搜索過程,獲得相同的結果。

而這正是多個對象實例共享原型所保存的屬性和方法的基本原理。

總結:

  • 先在本身身上找,找到即返回

  • 本身身上找不到,則沿着原型鏈向上查找,找到即返回

  • 若是一直到原型鏈的末端尚未找到,則返回 undefined

實例對象讀寫原型對象成員

讀取:

  • 先在本身身上找,找到即返回

  • 本身身上找不到,則沿着原型鏈向上查找,找到即返回

  • 若是一直到原型鏈的末端尚未找到,則返回 undefined

值類型成員寫入(實例對象.值類型成員 = xx):

  • 當實例指望重寫原型對象中的某個普通數據成員時實際上會把該成員添加到本身身上

  • 也就是說該行爲實際上會屏蔽掉對原型對象成員的訪問

引用類型成員寫入(實例對象.引用類型成員 = xx):

  • 同上

複雜類型修改(實例對象.成員.xx = xx):

  • 一樣會先在本身身上找該成員,若是本身身上找到則直接修改

  • 若是本身身上找不到,則沿着原型鏈繼續查找,若是找到則修改

  • 若是一直到原型鏈的末端尚未找到該成員,則報錯(實例對象.undefined.xx = xx

更簡單的原型語法

咱們注意到,前面例子中每添加一個屬性和方法就要敲一遍 Person.prototype爲減小沒必要要的輸入,更常見的作法是用一個包含全部屬性和方法的對象字面量來重寫整個原型對象:


function Person (name, age) {
 this.name = name
 this.age = age
}

Person.prototype = {
 type: 'human',
 sayHello: function () {
   console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
}
}

在該示例中,咱們將 Person.prototype 重置到了一個新的對象。這樣作的好處就是爲 Person.prototype 添加成員簡單了,可是也會帶來一個問題,那就是原型對象丟失了 constructor 成員。

因此,咱們爲了保持 constructor 的指向正確,建議的寫法是:


function Person (name, age) {
 this.name = name
 this.age = age
}

Person.prototype = {
 constructor: Person, // => 手動將 constructor 指向正確的構造函數
 type: 'human',
 sayHello: function () {
   console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
}
}

原生對象的原型

<p class="tip"> 全部函數都有 prototype 屬性對象。</p>

  • Object.prototype

  • Function.prototype

  • Array.prototype

  • String.prototype

  • Number.prototype

  • Date.prototype

  • ...

練習:爲數組對象和字符串對象擴展原型方法。

原型對象的問題

  • 共享數組

  • 共享對象

若是真的但願能夠被實例對象之間共享和修改這些共享數據那就不是問題。可是若是不但願實例之間共享和修改這些共享數據則就是問題。

一個更好的建議是,最好不要讓實例之間互相共享這些數組或者對象成員,一旦修改的話會致使數據的走向很不明確並且難以維護。

原型對象使用建議

  • 私有成員(通常就是非函數成員)放到構造函數中

  • 共享成員(通常就是函數)放到原型對象中

  • 若是重置了 prototype 記得修正 constructor 的指向

案例:隨機方塊


面向對象遊戲案例:貪吃蛇

案例相關源碼以上傳到 GitHub :https://github.com/lipengzhou/new-snake

案例介紹

遊戲演示

在線演示地址:貪吃蛇

案例目標

遊戲的目的是用來體會js高級語法的使用 不須要具有抽象對象的能力,使用面向對象的方式分析問題,須要一個漫長的過程。

功能實現

搭建頁面

放一個容器盛放遊戲場景 div#map,設置樣式


#map {
 width: 800px;
 height: 600px;
 background-color: #ccc;
 position: relative;
}

分析對象

  • 遊戲對象

  • 蛇對象

  • 食物對象

建立食物對象

  • Food

    • 屬性

      • x

      • y

      • width

      • height

      • color

    • 方法

      • render 隨機建立一個食物對象,並輸出到map上

  • 建立Food的構造函數,並設置屬性


var position = 'absolute';
var elements = [];
function Food(x, y, width, height, color) {
 this.x = x || 0;
 this.y = y || 0;
 // 食物的寬度和高度(像素)
 this.width = width || 20;
 this.height = height || 20;
 // 食物的顏色
 this.color = color || 'green';
}
  • 經過原型設置render方法,實現隨機產生食物對象,並渲染到map上


Food.prototype.render = function (map) {
 // 隨機食物的位置,map.寬度/food.寬度,總共有多少分food的寬度,隨機一下。而後再乘以food的寬度
 this.x = parseInt(Math.random() * map.offsetWidth / this.width) * this.width;
 this.y = parseInt(Math.random() * map.offsetHeight / this.height) * this.height;

 // 動態建立食物對應的div
 var div = document.createElement('div');
 map.appendChild(div);
 div.style.position = position;
 div.style.left = this.x + 'px';
 div.style.top = this.y + 'px';
 div.style.width = this.width + 'px';
 div.style.height = this.height + 'px';
 div.style.backgroundColor = this.color;
 elements.push(div);
}
  • 經過自調用函數,進行封裝,經過window暴露Food對象


window.Food = Food;

建立蛇對象

  • Snake

  • 屬性

    • width 蛇節的寬度 默認20

    • height 蛇節的高度 默認20

    • body 數組,蛇的頭部和身體,第一個位置是蛇頭

    • direction 蛇運動的方向 默認right 能夠是 left top bottom

  • 方法

    • render 把蛇渲染到map上

  • Snake構造函數


var position = 'absolute';
var elements = [];
function Snake(width, height, direction) {
 // 設置每個蛇節的寬度
 this.width = width || 20;
 this.height = height || 20;
 // 蛇的每一部分, 第一部分是蛇頭
 this.body = [
  {x: 3, y: 2, color: 'red'},
  {x: 2, y: 2, color: 'red'},
  {x: 1, y: 2, color: 'red'}
];
 this.direction = direction || 'right';
}
  • render方法


Snake.prototype.render = function(map) {
 for(var i = 0; i < this.body.length; i++) {
   var obj = this.body[i];
   var div = document.createElement('div');
   map.appendChild(div);
   div.style.left = obj.x * this.width + 'px';
   div.style.top = obj.y * this.height + 'px';
   div.style.position = position;
   div.style.backgroundColor = obj.color;
   div.style.width = this.width + 'px';
   div.style.height = this.height + 'px';
}
}
  • 在自調用函數中暴露Snake對象


window.Snake = Snake;

建立遊戲對象

遊戲對象,用來管理遊戲中的全部對象和開始遊戲

  • Game

    • 屬性

      • food

      • snake

      • map

    • 方法

      • start 開始遊戲(繪製全部遊戲對象)

 

  • 構造函數


function Game(map) {
 this.food = new Food();
 this.snake = new Snake();
 this.map = map;
}
  • 開始遊戲,渲染食物對象和蛇對象


Game.prototype.start = function () {
 this.food.render(this.map);
 this.snake.render(this.map);
}

遊戲的邏輯

寫蛇的move方法

  • 在蛇對象(snake.js)中,在Snake的原型上新增move方法

  1. 讓蛇移動起來,把蛇身體的每一部分往前移動一下

  2. 蛇頭部分根據不一樣的方向決定 往哪裏移動


Snake.prototype.move = function (food, map) {
 // 讓蛇身體的每一部分往前移動一下
 var i = this.body.length - 1;
 for(; i > 0; i--) {
   this.body[i].x = this.body[i - 1].x;
   this.body[i].y = this.body[i - 1].y;
}
 // 根據移動的方向,決定蛇頭如何處理
 switch(this.direction) {
   case 'left':
     this.body[0].x -= 1;
     break;
   case 'right':
     this.body[0].x += 1;
     break;
   case 'top':
     this.body[0].y -= 1;
     break;
   case 'bottom':
     this.body[0].y += 1;
     break;
}
}
  • 在game中測試


this.snake.move(this.food, this.map);
this.snake.render(this.map);

讓蛇本身動起來

  • 私有方法

    什麼是私有方法?
      不能被外部訪問的方法
    如何建立私有方法?
      使用自調用函數包裹
    

  • 在game.js中 添加runSnake的私有方法,開啓定時器調用蛇的move和render方法,讓蛇動起來

  • 判斷蛇是否撞牆


function runSnake() {
 var timerId = setInterval(function() {
   this.snake.move(this.food, this.map);
   // 在渲染前,刪除以前的蛇
   this.snake.render(this.map);

   // 判斷蛇是否撞牆
   var maxX = this.map.offsetWidth / this.snake.width;
   var maxY = this.map.offsetHeight / this.snake.height;
   var headX = this.snake.body[0].x;
   var headY = this.snake.body[0].y;
   if (headX < 0 || headX >= maxX) {
     clearInterval(timerId);
     alert('Game Over');
  }

   if (headY < 0 || headY >= maxY) {
     clearInterval(timerId);
     alert('Game Over');
  }

}.bind(that), 150);
}
  • 在snake中添加刪除蛇的私有方法,在render中調用


function remove() {
 // 刪除渲染的蛇
 var i = elements.length - 1;
 for(; i >= 0; i--) {
   // 刪除頁面上渲染的蛇
   elements[i].parentNode.removeChild(elements[i]);
   // 刪除elements數組中的元素
   elements.splice(i, 1);
}
}
  • 在game中經過鍵盤控制蛇的移動方向


function bindKey() {
 document.addEventListener('keydown', function(e) {
   switch (e.keyCode) {
     case 37:
       // left
       this.snake.direction = 'left';
       break;
     case 38:
       // top
       this.snake.direction = 'top';
       break;
     case 39:
       // right
       this.snake.direction = 'right';
       break;
     case 40:
       // bottom
       this.snake.direction = 'bottom';
       break;
  }
}.bind(that), false);
}
  • 在start方法中調用


bindKey();

判斷蛇是否吃到食物


// 在Snake的move方法中

// 在移動的過程當中判斷蛇是否吃到食物
// 若是蛇頭和食物的位置重合表明吃到食物
// 食物的座標是像素,蛇的座標是幾個寬度,進行轉換
var headX = this.body[0].x * this.width;
var headY = this.body[0].y * this.height;
if (headX === food.x && headY === food.y) {
 // 吃到食物,往蛇節的最後加一節
 var last = this.body[this.body.length - 1];
 this.body.push({
   x: last.x,
   y: last.y,
   color: last.color
})
 // 把如今的食物對象刪除,並從新隨機渲染一個食物對象
 food.render(map);
}

其它處理

把html中的js代碼放到index.js中

避免html中出現js代碼

自調用函數的參數


(function (window, undefined) {
 var document = window.document;

}(window, undefined))
  • 傳入window對象

未來代碼壓縮的時候,能夠吧 function (window) 壓縮成 function (w)

  • 傳入undefined

在未來會看到別人寫的代碼中會把undefined做爲函數的參數(當前案例沒有使用)由於在有的老版本的瀏覽器中 undefined能夠被從新賦值,防止undefined 被從新賦值

整理代碼

如今的代碼結構清晰,誰出問題就找到對應的js文件便可。經過自調用函數,已經防止了變量命名污染的問題

可是,因爲js文件數較多,須要在頁面上引用,會產生文件依賴的問題(先引入那個js,再引入哪一個js)未來經過工具把js文件合併並壓縮。如今手工合併js文件演示

  • 問題1


// 若是存在多個自調用函數要用分號分割,不然語法錯誤
// 下面代碼會報錯
(function () {
}())

(function () {
}())
// 因此代碼規範中會建議在自調用函數以前加上分號
// 下面代碼沒有問題
;(function () {
}())

;(function () {
}())
  • 問題2


// 當自調用函數 前面有函數聲明時,會把自調用函數做爲參數
// 因此建議自調用函數前,加上;
var a = function () {
 alert('11');
}
   
(function () {
 alert('22');
}())

繼承

什麼是繼承

  • 現實生活中的繼承

  • 程序中的繼承

構造函數的屬性繼承:借用構造函數


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

function Student (name, age) {
 // 借用構造函數繼承屬性成員
 Person.call(this, name, age)
}

var s1 = Student('張三', 18)
console.log(s1.type, s1.name, s1.age) // => human 張三 18

構造函數的原型方法繼承:拷貝繼承(for-in)


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

Person.prototype.sayName = function () {
 console.log('hello ' + this.name)
}

function Student (name, age) {
 Person.call(this, name, age)
}

// 原型對象拷貝繼承原型對象成員
for(var key in Person.prototype) {
 Student.prototype[key] = Person.prototype[key]
}

var s1 = Student('張三', 18)

s1.sayName() // => hello 張三

另外一種繼承方式:原型繼承


function Person (name, age) {
 this.type = 'human'
 this.name = name
 this.age = age
}

Person.prototype.sayName = function () {
 console.log('hello ' + this.name)
}

function Student (name, age) {
 Person.call(this, name, age)
}

// 利用原型的特性實現繼承
Student.prototype = new Person()

var s1 = Student('張三', 18)

console.log(s1.type) // => human

s1.sayName() // => hello 張三

函數進階

函數的定義方式

  • 函數聲明

  • 函數表達式

  • new Function

函數聲明


function foo () {

}

函數表達式


var foo = function () {

}

函數聲明與函數表達式的區別

  • 函數聲明必須有名字

  • 函數聲明會函數提高,在預解析階段就已建立,聲明先後均可以調用

  • 函數表達式相似於變量賦值

  • 函數表達式能夠沒有名字,例如匿名函數

  • 函數表達式沒有變量提高,在執行階段建立,必須在表達式執行以後才能夠調用

下面是一個根據條件定義函數的例子:


if (true) {
 function f () {
   console.log(1)
}
} else {
 function f () {
   console.log(2)
}
}

以上代碼執行結果在不一樣瀏覽器中結果不一致。

不過咱們可使用函數表達式解決上面的問題:


var f

if (true) {
 f = function () {
   console.log(1)
}
} else {
 f = function () {
   console.log(2)
}
}

函數的調用方式

  • 普通函數

  • 構造函數

  • 對象方法

函數內 this 指向的不一樣場景

函數的調用方式決定了 this 指向的不一樣:

調用方式 非嚴格模式 備註
普通函數調用 window 嚴格模式下是 undefined
構造函數調用 實例對象 原型方法中 this 也是實例對象
對象方法調用 該方法所屬對象 緊挨着的對象
事件綁定方法 綁定事件對象  
定時器函數 window  

這就是對函數內部 this 指向的基本整理,寫代碼寫多了天然而然就熟悉了。

函數也是對象

  • 全部函數都是 Function 的實例

call、apply、bind

那瞭解了函數 this 指向的不一樣場景以後,咱們知道有些狀況下咱們爲了使用某種特定環境的 this 引用,這時候時候咱們就須要採用一些特殊手段來處理了,例如咱們常常在定時器外部備份 this 引用,而後在定時器函數內部使用外部 this 的引用。然而實際上對於這種作法咱們的 JavaScript 爲咱們專門提供了一些函數方法用來幫咱們更優雅的處理函數內部 this 指向問題。這就是接下來咱們要學習的 call、apply、bind 三個函數方法。

call

call() 方法調用一個函數, 其具備一個指定的 this 值和分別地提供的參數(參數的列表)。

<p class="danger"> 注意:該方法的做用和 apply() 方法相似,只有一個區別,就是 call() 方法接受的是若干個參數的列表,而 apply() 方法接受的是一個包含多個參數的數組。</p>

語法:


fun.call(thisArg[, arg1[, arg2[, ...]]])

參數:

  • thisArg

    • 在 fun 函數運行時指定的 this 值

    • 若是指定了 null 或者 undefined 則內部 this 指向 window

  • arg1, arg2, ...

    • 指定的參數列表

apply

apply() 方法調用一個函數, 其具備一個指定的 this 值,以及做爲一個數組(或相似數組的對象)提供的參數。

<p class="danger"> 注意:該方法的做用和 call() 方法相似,只有一個區別,就是 call() 方法接受的是若干個參數的列表,而 apply() 方法接受的是一個包含多個參數的數組。</p>

語法:


fun.apply(thisArg, [argsArray])

參數:

  • thisArg

  • argsArray

apply()call() 很是類似,不一樣之處在於提供參數的方式。apply() 使用參數數組而不是一組參數列表。例如:


fun.apply(this, ['eat', 'bananas'])

bind

bind() 函數會建立一個新函數(稱爲綁定函數),新函數與被調函數(綁定函數的目標函數)具備相同的函數體(在 ECMAScript 5 規範中內置的call屬性)。當目標函數被調用時 this 值綁定到 bind() 的第一個參數,該參數不能被重寫。綁定函數被調用時,bind() 也接受預設的參數提供給原函數。一個綁定函數也能使用new操做符建立對象:這種行爲就像把原函數當成構造器。提供的 this 值被忽略,同時調用時的參數被提供給模擬函數。

語法:


fun.bind(thisArg[, arg1[, arg2[, ...]]])

參數:

  • thisArg

    • 當綁定函數被調用時,該參數會做爲原函數運行時的 this 指向。當使用new 操做符調用綁定函數時,該參數無效。

  • arg1, arg2, ...

    • 當綁定函數被調用時,這些參數將置於實參以前傳遞給被綁定的方法。

返回值:

返回由指定的this值和初始化參數改造的原函數拷貝。

示例1:


this.x = 9;
var module = {
 x: 81,
 getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種狀況下,"this"指向全局做用域

// 建立一個新函數,將"this"綁定到module對象
// 新手可能會被全局的x變量和module裏的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

示例2:


function LateBloomer() {
 this.petalCount = Math.ceil(Math.random() * 12) + 1;
}

// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
 window.setTimeout(this.declare.bind(this), 1000);
};

LateBloomer.prototype.declare = function() {
 console.log('I am a beautiful flower with ' +
   this.petalCount + ' petals!');
};

var flower = new LateBloomer();
flower.bloom();  // 一秒鐘後, 調用'declare'方法

小結

  • call 和 apply 特性同樣

    • 都是用來調用函數,並且是當即調用

    • 可是能夠在調用函數的同時,經過第一個參數指定函數內部 this 的指向

    • call 調用的時候,參數必須以參數列表的形式進行傳遞,也就是以逗號分隔的方式依次傳遞便可

    • apply 調用的時候,參數必須是一個數組,而後在執行的時候,會將數組內部的元素一個一個拿出來,與形參一一對應進行傳遞

    • 若是第一個參數指定了 null 或者 undefined 則內部 this 指向 window

  • bind

    • 能夠用來指定內部 this 的指向,而後生成一個改變了 this 指向的新的函數

    • 它和 call、apply 最大的區別是:bind 不會調用

    • bind 支持傳遞參數,它的傳參方式比較特殊,一共有兩個位置能夠傳遞

        1. 在 bind 的同時,以參數列表的形式進行傳遞

        1. 在調用的時候,以參數列表的形式進行傳遞

      • 那到底以誰 bind 的時候傳遞的參數爲準呢仍是以調用的時候傳遞的參數爲準

      • 二者合併:bind 的時候傳遞的參數和調用的時候傳遞的參數會合併到一塊兒,傳遞到函數內部

函數的其它成員

  • arguments

    • 實參集合

  • caller

    • 函數的調用者

  • length

    • 形參的個數

  • name

    • 函數的名稱


function fn(x, y, z) {
 console.log(fn.length) // => 形參的個數
 console.log(arguments) // 僞數組實參參數集合
 console.log(arguments.callee === fn) // 函數自己
 console.log(fn.caller) // 函數的調用者
 console.log(fn.name) // => 函數的名字
}

function f() {
 fn(10, 20, 30)
}

f()

高階函數

  • 函數能夠做爲參數

  • 函數能夠做爲返回值

做爲參數


function eat (callback) {
 setTimeout(function () {
   console.log('吃完了')
   callback()
}, 1000)
}

eat(function () {
 console.log('去唱歌')
})

做爲返回值


function genFun (type) {
 return function (obj) {
   return Object.prototype.toString.call(obj) === type
}
}

var isArray = genFun('[object Array]')
var isObject = genFun('[object Object]')

console.log(isArray([])) // => true
console.log(isArray({})) // => true

函數閉包


function fn () {
 var count = 0
 return {
   getCount: function () {
     console.log(count)
  },
   setCount: function () {
     count++
  }
}
}

var fns = fn()

fns.getCount() // => 0
fns.setCount()
fns.getCount() // => 1

做用域、做用域鏈、預解析

  • 全局做用域

  • 函數做用域

  • 沒有塊級做用域


{
 var foo = 'bar'
}

console.log(foo)

if (true) {
 var a = 123
}
console.log(a)

做用域鏈示例代碼:


var a = 10

function fn () {
 var b = 20

 function fn1 () {
   var c = 30
   console.log(a + b + c)
}

 function fn2 () {
   var d = 40
   console.log(c + d)
}

 fn1()
 fn2()
}
  • 內層做用域能夠訪問外層做用域,反之不行

什麼是閉包

閉包就是可以讀取其餘函數內部變量的函數,因爲在 Javascript 語言中,只有函數內部的子函數才能讀取局部變量,所以能夠把閉包簡單理解成 「定義在一個函數內部的函數」。因此,在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。

閉包的用途:

  • 能夠在函數外部讀取函數內部成員

  • 讓函數內成員始終存活在內存中

一些關於閉包的例子

示例1:


var arr = [10, 20, 30]
for(var i = 0; i < arr.length; i++) {
 arr[i] = function () {
   console.log(i)
}
}

示例2:


console.log(111)

for(var i = 0; i < 3; i++) {
 setTimeout(function () {
   console.log(i)
}, 0)
}
console.log(222)

示例3:投票

示例4:判斷類型

示例5:沙箱模式

閉包的思考題

思考題 1:


var name = "The Window";
var object = {
 name: "My Object",
 getNameFunc: function () {
   return function () {
     return this.name;
  };
}
};

console.log(object.getNameFunc()())

思考題 2:


var name = "The Window";  
var object = {    
 name: "My Object",
 getNameFunc: function () {
   var that = this;
   return function () {
     return that.name;
  };
}
};
console.log(object.getNameFunc()())

小結

函數遞歸

遞歸執行模型


function fn1 () {
 console.log(111)
 fn2()
 console.log('fn1')
}

function fn2 () {
 console.log(222)
 fn3()
 console.log('fn2')
}

function fn3 () {
 console.log(333)
 fn4()
 console.log('fn3')
}

function fn4 () {
 console.log(444)
 console.log('fn4')
}

fn1()

舉個栗子:計算階乘的遞歸函數


function factorial (num) {
 if (num <= 1) {
   return 1
} else {
   return num * factorial(num - 1)
}
}

遞歸應用場景

  • 深拷貝

  • 菜單樹

  • 遍歷 DOM 樹


正則表達式

  • 瞭解正則表達式基本語法

  • 可以使用JavaScript的正則對象

正則表達式簡介

什麼是正則表達式

正則表達式:用於匹配規律規則的表達式,正則表達式最初是科學家對人類神經系統的工做原理的早期研究,如今在編程語言中有普遍的應用。正則表一般被用來檢索、替換那些符合某個模式(規則)的文本。正則表達式是對字符串操做的一種邏輯公式,就是用事先定義好的一些特定字符、及這些特定字符的組合,組成一個「規則字符串」,這個「規則字符串」用來表達對字符串的一種過濾邏輯。

正則表達式的做用

  1. 給定的字符串是否符合正則表達式的過濾邏輯(匹配)

  2. 能夠經過正則表達式,從字符串中獲取咱們想要的特定部分(提取)

  3. 強大的字符串替換能力(替換)

正則表達式的特色

  1. 靈活性、邏輯性和功能性很是的強

  2. 能夠迅速地用極簡單的方式達到字符串的複雜控制

  3. 對於剛接觸的人來講,比較晦澀難懂

正則表達式的測試

  • 在線測試正則

  • 工具中使用正則表達式

    • sublime/vscode/word

    • 演示替換全部的數字

正則表達式的組成

  • 普通字符

  • 特殊字符(元字符):正則表達式中有特殊意義的字符

示例演示:

  • \d 匹配數字

  • ab\d 匹配 ab一、ab2

元字符串

經過測試工具演示下面元字符的使用

經常使用元字符串

元字符 說明
\d 匹配數字
\D 匹配任意非數字的字符
\w 匹配字母或數字或下劃線
\W 匹配任意不是字母,數字,下劃線
\s 匹配任意的空白符
\S 匹配任意不是空白符的字符
. 匹配除換行符之外的任意單個字符
^ 表示匹配行首的文本(以誰開始)
$ 表示匹配行尾的文本(以誰結束)

限定符

限定符 說明
* 重複零次或更屢次
+ 重複一次或更屢次
? 重複零次或一次
{n} 重複n次
{n,} 重複n次或更屢次
{n,m} 重複n到m次

其它

[] 字符串用中括號括起來,表示匹配其中的任一字符,至關於或的意思
[^]  匹配除中括號之內的內容
\ 轉義符
| 或者,選擇二者中的一個。注意|將左右兩邊分爲兩部分,而無論左右兩邊有多長多亂
() 從兩個直接量中選擇一個,分組
   eg:gr(a|e)y匹配gray和grey
[\u4e00-\u9fa5]  匹配漢字


案例

驗證手機號:


^\d{11}$

驗證郵編:


^\d{6}$

驗證日期 2012-5-01


^\d{4}-\d{1,2}-\d{1,2}$

驗證郵箱 xxx@itcast.cn


^\w+@\w+\.\w+$

驗證IP地址 192.168.1.10


^\d{1,3}\(.\d{1,3}){3}$

JavaScript 中使用正則表達式

建立正則對象

方式1:


var reg = new Regex('\d', 'i');
var reg = new Regex('\d', 'gi');

方式2:


var reg = /\d/i;
var reg = /\d/gi;

參數

標誌 說明
i 忽略大小寫
g 全局匹配
gi 全局匹配+忽略大小寫

正則匹配


// 匹配日期
var dateStr = '2015-10-10';
var reg = /^\d{4}-\d{1,2}-\d{1,2}$/
console.log(reg.test(dateStr));

匹配正則表達式

// console.log(/./.test("除了回車換行覺得的任意字符"));//true// console.log(/.*/.test("0個到多個"));//true// console.log(/.+/.test("1個到多個"));//true// console.log(/.?/.test("哈哈"));//true// console.log(/[0-9]/.test("9527"));//true// console.log(/[a-z]/.test("what"));//true// console.log(/[A-Z]/.test("Are"));//true// console.log(/[a-zA-Z]/.test("幹啥子"));//false// console.log(/[0-9a-zA-Z]/.test("9ebg"));//true// console.log(/b|(ara)/.test("abra"));//true// console.log(/[a-z]{2,3}/.test("arfsf"));//true

    console.log(/\d/.test("998"));//true
    console.log(/\d*/.test("998"));//true
    console.log(/\d+/.test("998"));//true
    console.log(/\d{0,}/.test("998"));//true
    console.log(/\d{2,3}/.test("998"));//true
    console.log(/\D/.test("eat"));//true
    console.log(/\s/.test("  "));//true
    console.log(/\S/.test("嘎嘎"));//true
    console.log(/\w/.test("_"));//true
    console.log(/\W/.test("_"));//true


正則表達式案例

1.驗證密碼強弱2.驗證郵箱:[0-9a-zA-Z.-]+[@][0-9a-zA-Z.-]+(.+){1,2}3.驗證中文名字[\u4e00-\u9fa5]

正則提取


// 1. 提取工資
var str = "張三:1000,李四:5000,王五:8000。";
var array = str.match(/\d+/g);
console.log(array);

// 2. 提取email地址
var str = "123123@xx.com,fangfang@valuedopinions.cn 286669312@qq.com 二、emailenglish@emailenglish.englishtown.com 286669312@qq.com...";
var array = str.match(/\w+@\w+\.\w+(\.\w+)?/g);
console.log(array);

// 3. 分組提取  
// 3. 提取日期中的年部分 2015-5-10
var dateStr = '2016-1-5';
// 正則表達式中的()做爲分組來使用,獲取分組匹配到的結果用Regex.$1 $2 $3....來獲取
var reg = /(\d{4})-\d{1,2}-\d{1,2}/;
if (reg.test(dateStr)) {
 console.log(RegExp.$1);
}

// 4. 提取郵件中的每一部分
var reg = /(\w+)@(\w+)\.(\w+)(\.\w+)?/;
var str = "123123@xx.com";
if (reg.test(str)) {
 console.log(RegExp.$1);
 console.log(RegExp.$2);
 console.log(RegExp.$3);
}

正則替換


// 1. 替換全部空白
var str = "   123AD asadf   asadfasf adf ";
str = str.replace(/\s/g,"xx");
console.log(str);

// 2. 替換全部,|,
var str = "abc,efg,123,abc,123,a";
str = str.replace(/,|,/g, ".");
console.log(str);

案例:表單驗證


QQ號:<input type="text" id="txtQQ"><span></span><br>
郵箱:<input type="text" id="txtEMail"><span></span><br>
手機:<input type="text" id="txtPhone"><span></span><br>
生日:<input type="text" id="txtBirthday"><span></span><br>
姓名:<input type="text" id="txtName"><span></span><br>

//獲取文本框
var txtQQ = document.getElementById("txtQQ");
var txtEMail = document.getElementById("txtEMail");
var txtPhone = document.getElementById("txtPhone");
var txtBirthday = document.getElementById("txtBirthday");
var txtName = document.getElementById("txtName");

//
txtQQ.onblur = function () {
 //獲取當前文本框對應的span
 var span = this.nextElementSibling;
 var reg = /^\d{5,12}$/;
 //判斷驗證是否成功
 if(!reg.test(this.value) ){
   //驗證不成功
   span.innerText = "請輸入正確的QQ號";
   span.style.color = "red";
}else{
   //驗證成功
   span.innerText = "";
   span.style.color = "";
}
};

//txtEMail
txtEMail.onblur = function () {
 //獲取當前文本框對應的span
 var span = this.nextElementSibling;
 var reg = /^\w+@\w+\.\w+(\.\w+)?$/;
 //判斷驗證是否成功
 if(!reg.test(this.value) ){
   //驗證不成功
   span.innerText = "請輸入正確的EMail地址";
   span.style.color = "red";
}else{
   //驗證成功
   span.innerText = "";
   span.style.color = "";
}
};

表單驗證部分,封裝成函數:


var regBirthday = /^\d{4}-\d{1,2}-\d{1,2}$/;
addCheck(txtBirthday, regBirthday, "請輸入正確的出生日期");
//給文本框添加驗證
function addCheck(element, reg, tip) {
 element.onblur = function () {
   //獲取當前文本框對應的span
   var span = this.nextElementSibling;
   //判斷驗證是否成功
   if(!reg.test(this.value) ){
     //驗證不成功
     span.innerText = tip;
     span.style.color = "red";
  }else{
     //驗證成功
     span.innerText = "";
     span.style.color = "";
  }
};
}

經過給元素增長自定義驗證屬性對錶單進行驗證:


<form id="frm">
QQ號:<input type="text" name="txtQQ" data-rule="qq"><span></span><br>
郵箱:<input type="text" name="txtEMail" data-rule="email"><span></span><br>
手機:<input type="text" name="txtPhone" data-rule="phone"><span></span><br>
生日:<input type="text" name="txtBirthday" data-rule="date"><span></span><br>
姓名:<input type="text" name="txtName" data-rule="cn"><span></span><br>
</form>

// 全部的驗證規則
var rules = [
{
   name: 'qq',
   reg: /^\d{5,12}$/,
   tip: "請輸入正確的QQ"
},
{
   name: 'email',
   reg: /^\w+@\w+\.\w+(\.\w+)?$/,
   tip: "請輸入正確的郵箱地址"
},
{
   name: 'phone',
   reg: /^\d{11}$/,
   tip: "請輸入正確的手機號碼"
},
{
   name: 'date',
   reg: /^\d{4}-\d{1,2}-\d{1,2}$/,
   tip: "請輸入正確的出生日期"
},
{
   name: 'cn',
   reg: /^[\u4e00-\u9fa5]{2,4}$/,
   tip: "請輸入正確的姓名"
}];

addCheck('frm');


//給文本框添加驗證
function addCheck(formId) {
 var i = 0,
     len = 0,
     frm =document.getElementById(formId);
 len = frm.children.length;
 for (; i < len; i++) {
   var element = frm.children[i];
   // 表單元素中有name屬性的元素添加驗證
   if (element.name) {
     element.onblur = function () {
       // 使用dataset獲取data-自定義屬性的值
       var ruleName = this.dataset.rule;
       var rule =getRuleByRuleName(rules, ruleName);

       var span = this.nextElementSibling;
       //判斷驗證是否成功
       if(!rule.reg.test(this.value) ){
         //驗證不成功
         span.innerText = rule.tip;
         span.style.color = "red";
      }else{
         //驗證成功
         span.innerText = "";
         span.style.color = "";
      }
    }
  }
}
}

// 根據規則的名稱獲取規則對象
function getRuleByRuleName(rules, ruleName) {
 var i = 0,
     len = rules.length;
 var rule = null;
 for (; i < len; i++) {
   if (rules[i].name == ruleName) {
     rule = rules[i];
     break;
  }
}
 return rule;
}

補充

僞數組和數組

在JavaScript中,除了5種原始數據類型以外,其餘全部的都是對象,包括函數(Function)。

對象與數組的關係

在說區別以前,須要先提到另一個知識,就是 JavaScript 的原型繼承。全部 JavaScript 的內置構造函數都是繼承自 Object.prototype在這個前提下,能夠理解爲使用 new Array()[] 建立出來的數組對象,都會擁有 Object.prototype 的屬性值。


var obj = {};// 擁有 Object.prototype 的屬性值
var arr = [];
//使用數組直接量建立的數組,因爲 Array.prototype 的屬性繼承自 Object.prototype,
//那麼,它將同時擁有 Array.prototype 和 Object.prototype 的屬性值

能夠獲得對象和數組的第一個區別:對象沒有數組 Array.prototype 的屬性值。

什麼是數組

數組具備一個最基本特徵:索引,這是對象所沒有的,下面來看一段代碼:


var obj = {};
var arr = [];

obj[2] = 'a';
arr[2] = 'a';

console.log(obj[2]); // => a
console.log(arr[2]); // => a
console.log(obj.length); // => undefined
console.log(arr.length); // => 3
  • obj[2]輸出'a',是由於對象就是普通的鍵值對存取數據

  • 而arr[2]輸出'a' 則不一樣,數組是經過索引來存取數據,arr[2]之因此輸出'a',是由於數組arr索引2的位置已經存儲了數據

  • obj.length並不具備數組的特性,而且obj沒有保存屬性length,那麼天然就會輸出undefined

  • 而對於數組來講,length是數組的一個內置屬性,數組會根據索引長度來更改length的值

  • 爲何arr.length輸出3,而不是1

    • 在給數組添加元素時,並無按照連續的索引添加,因此致使數組的索引不連續,那麼就致使索引長度大於元素個數

什麼是僞數組

  1. 擁有 length 屬性,其它屬性(索引)爲非負整數(對象中的索引會被當作字符串來處理,這裏你能夠當作是個非負整數串來理解)

  2. 不具備數組所具備的方法

僞數組,就是像數組同樣有 length 屬性,也有 0、一、二、3 等屬性的對象,看起來就像數組同樣,但不是數組,好比:


var fakeArray = {
 "0": "first",
 "1": "second",
 "2": "third",
 length: 3
};

for (var i = 0; i < fakeArray.length; i++) {
 console.log(fakeArray[i]);
}

Array.prototype.join.call(fakeArray,'+');

常見的僞數組有:

  • 函數內部的 arguments

  • DOM 對象列表(好比經過 document.getElementsByTags 獲得的列表)

  • jQuery 對象(好比 $("div")

僞數組是一個 Object,而真實的數組是一個 Array。

僞數組存在的意義,是可讓普通的對象也能正常使用數組的不少方法,好比:


var arr = Array.prototype.slice.call(arguments);

Array.prototype.forEach.call(arguments, function(v) {
 // 循環arguments對象
});

// push
// some
// every
// filter
// map
// ...

以上在借用數組的原型方法的時候均可以經過數組直接量來簡化使用:


var obj = {
 0: 'a',
 1: 'b',
 2: 'c',
 length: 3
}

;[].push.call(obj, 'd')

console.log([].slice.call(obj))

;[].forEach.call(obj, function (num, index) {
 console.log(num)
})

小結

  • 對象沒有數組 Array.prototype 的屬性值,類型是 Object ,而數組類型是 Array

  • 數組是基於索引的實現, length 會自動更新,而對象是鍵值對

  • 使用對象能夠建立僞數組,僞數組能夠正常使用數組的大部分方法

JavaScript 垃圾回收機制

JavaScript 運行機制:Event Loop

Object

靜態成員

  • Object.assign()

  • Object.create()

  • Object.keys()

  • Object.defineProperty()

實例成員

  • constructor

  • hasOwnProperty()

  • isPrototypeOf

  • propertyIsEnumerable()

  • toString()

  • valueOf()


附錄

A 代碼規範

代碼風格

校驗工具

B Chrome 開發者工具

C 文檔相關工具

2019-08-0318:11:32

做者:何秀好

相關文章
相關標籤/搜索