前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端

智雲程式設計發表於2019-05-21

JavaScript 高階

學習目標:

  • 理解物件導向開發思想
  • 掌握 JavaScript 物件導向開發相關模式
  • 掌握在 JavaScript 中使用正規表示式

自己是個做了幾年開發的老碼農,希望本文對你有用! 這裡推薦一下我的前端學習交流圈:767273102 ,裡面都是學習前端的,從最基礎的HTML+CSS+JS【炫酷特效,遊戲,外掛封裝,設計模式】到移動端HTML5的專案實戰的學習資料都有整理,送給每一位前端小夥伴。2019最新技術,與企業需求同步。好友都在裡面學習交流,每天都會有大牛定時講解前端技術!

物件導向介紹

程式中物件導向的基本體現

在 JavaScript 中,所有資料型別都可以視為物件,當然也可以自定義物件。
自定義的物件資料型別就是物件導向中的類( Class )的概念。
我們以一個例子來說明程式導向和麵向物件在程式流程上的不同之處。
假設我們要處理學生的成績表,為了表示一個學生的成績,程式導向的程式可以用一個物件表示:

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

而處理學生成績可以透過函式實現,比如列印學生的成績:

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

如果採用物件導向的程式設計思想,我們首選思考的不是程式的執行流程,

而是 Student 這種資料型別應該被視為一個物件,這個物件擁有 name 和 score 這兩個屬性(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 既包含資料,又包含運算元據的方法。


建立物件三種方法

1、呼叫系統的建構函式

我們可以直接透過 new Object() 建立:

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

2、字面量建立

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

對於上面的寫法固然沒有問題,但是假如我們要生成兩個 person 例項物件呢?

3、工廠函式建立

我們可以寫一個函式,解決程式碼重複問題:

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

建構函式的問題

使用建構函式帶來的最大的好處就是建立物件更方便了,但是其本身也存在一個浪費記憶體的問題:

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)

在該示例中,從表面上好像沒什麼問題,但是實際上這樣做,有一個很大的弊端。那就是對於每一個例項物件,type 和 sayHello 都是一模一樣的內容,每一次生成一個例項,都必須為重複的內容,多佔用一些記憶體,如果例項物件很多,會造成極大的記憶體浪費。

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 物件,因此就提高了執行效率。


image.png
image.png

任何函式都具有一個 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

例項物件讀寫原型物件成員

更簡單的原型語法

我們注意到,前面例子中每新增一個屬性和方法就要敲一遍 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
  • ...

練習:為陣列物件和字串物件擴充套件原型方法


繼承

什麼是繼承

  • 現實生活中的繼承
  • 程式中的繼承

建構函式的屬性繼承:借用建構函式

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 張三

函式進階

函式內 this 指向的不同場景

函式的呼叫方式決定了 this 指向的不同:

前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端

這就是對函式內部 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值和初始化引數改造的原函式複製。

小結

  • 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()

什麼是閉包

閉包就是能夠讀取其他函式內部變數的函式,

由於在 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)

正規表示式

  • 瞭解正規表示式基本語法
  • 能夠使用JavaScript的正則物件

正規表示式簡介

前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端
前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端

什麼是正規表示式

正規表示式是對字串操作的一種邏輯公式,就是用事先定義好的一些特定字元、及這些特定字元的組合,組成一個“規則字串”,這個“規則字串”用來表達對字串的一種過濾邏輯。

JavaScript 中使用正規表示式

建立正則物件

方式1:

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

方式2:

var reg = /\d/i;
var reg = /\d/gi;
前端進階(二)JS高階講解物件導向,原型,繼承,閉包,正規表示式,讓你徹底愛上前端

案例

正則提取

 // 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 2、emailenglish@emailenglish.englishtown.com 286669312@qq.com...";
var array = str.match(/\w+@\w+\.\w+(\.\w+)?/g);
console.log(array);
// 3\. 分組提取  
// 3\. 提取日期中的年部分  
2015-5-10var 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;
}



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901074/viewspace-2645125/,如需轉載,請註明出處,否則將追究法律責任。

相關文章