基本概念
- 語(yǔ)法
- 區(qū)分大小寫
- 標(biāo)識(shí)符
- 注釋
- 嚴(yán)格模式
- 語(yǔ)句
- 關(guān)鍵字和保留字
- 變量
- 數(shù)據(jù)類型
- typeof 操作符
- Undefined
- Null
- Boolean
- Number
- String
- Object
- 操作符
- 流程控制語(yǔ)句
- 函數(shù)
JavaScript 中的數(shù)據(jù)類型
JavaScript 有 5 種簡(jiǎn)單數(shù)據(jù)類型:Undefined、Null、Boolean、Number、String 和 1 種復(fù)雜數(shù)據(jù)類型 Object 。
基本類型(值類型)
- Undefined
- Null
- Boolean
- Number
- String
復(fù)雜類型(引用類型)
- Object
- Array
- Date
- RegExp
- Function
- 基本包裝類型
- Boolean
- Number
- String
- 單體內(nèi)置對(duì)象
- Global
- Math
類型檢測(cè)
typeofinstanceofObject.prototype.toString.call()
值類型和引用類型在內(nèi)存中的存儲(chǔ)方式(畫圖說(shuō)明)
- 值類型按值存儲(chǔ)
- 引用類型按引用存儲(chǔ)
值類型復(fù)制和引用類型復(fù)制(畫圖說(shuō)明)
- 值類型按值復(fù)制
- 引用類型按引用復(fù)制
值類型和引用類型參數(shù)傳遞(畫圖說(shuō)明)
- 值類型按值傳遞
- 引用類型按引用傳遞
值類型與引用類型的差別
- 基本類型在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中
- 從一個(gè)變量向另一個(gè)變量復(fù)制基本類型的值,復(fù)制的是值的副本
- 引用類型的值是對(duì)象,保存在堆內(nèi)存
- 包含引用類型值的變量實(shí)際上包含的并不是對(duì)象本身,而是一個(gè)指向該對(duì)象的指針
- 從一個(gè)變量向另一個(gè)變量復(fù)制引用類型的值的時(shí)候,復(fù)制是引用指針,因此兩個(gè)變量最終都指向同一個(gè)對(duì)象
小結(jié)
- 類型檢測(cè)方式
- 值類型和引用類型的存儲(chǔ)方式
- 值類型復(fù)制和引用類型復(fù)制
- 方法參數(shù)中 值類型數(shù)據(jù)傳遞 和 引用類型數(shù)據(jù)傳遞
JavaScript 執(zhí)行過(guò)程
JavaScript 運(yùn)行分為兩個(gè)階段:
- 預(yù)解析
- 全局預(yù)解析(所有變量和函數(shù)聲明都會(huì)提前;同名的函數(shù)和變量函數(shù)的優(yōu)先級(jí)高)
- 函數(shù)內(nèi)部預(yù)解析(所有的變量、函數(shù)和形參都會(huì)參與預(yù)解析)
- 函數(shù)
- 形參
- 普通變量
- 執(zhí)行
先預(yù)解析全局作用域,然后執(zhí)行全局作用域中的代碼,
在執(zhí)行全局代碼的過(guò)程中遇到函數(shù)調(diào)用就會(huì)先進(jìn)行函數(shù)預(yù)解析,然后再執(zhí)行函數(shù)內(nèi)代碼。
JavaScript 面向?qū)ο缶幊?/h2>
對(duì)象.png
面向?qū)ο蠼榻B
什么是對(duì)象
Everything is object (萬(wàn)物皆對(duì)象)
20160823024542444.jpg

Everything is object (萬(wàn)物皆對(duì)象)

對(duì)象到底是什么,我們可以從兩次層次來(lái)理解。
(1) 對(duì)象是單個(gè)事物的抽象。
一本書、一輛汽車、一個(gè)人都可以是對(duì)象,一個(gè)數(shù)據(jù)庫(kù)、一張網(wǎng)頁(yè)、一個(gè)與遠(yuǎn)程服務(wù)器的連接也可以是對(duì)象。當(dāng)實(shí)物被抽象成對(duì)象,實(shí)物之間的關(guān)系就變成了對(duì)象之間的關(guān)系,從而就可以模擬現(xiàn)實(shí)情況,針對(duì)對(duì)象進(jìn)行編程。
(2) 對(duì)象是一個(gè)容器,封裝了屬性(property)和方法(method)。
屬性是對(duì)象的狀態(tài),方法是對(duì)象的行為(完成某種任務(wù))。比如,我們可以把動(dòng)物抽象為animal對(duì)象,使用“屬性”記錄具體是那一種動(dòng)物,使用“方法”表示動(dòng)物的某種行為(奔跑、捕獵、休息等等)。
在實(shí)際開(kāi)發(fā)中,對(duì)象是一個(gè)抽象的概念,可以將其簡(jiǎn)單理解為:數(shù)據(jù)集或功能集。
ECMAScript-262 把對(duì)象定義為:無(wú)序?qū)傩缘募希鋵傩钥梢园局?、?duì)象或者函數(shù)。
嚴(yán)格來(lái)講,這就相當(dāng)于說(shuō)對(duì)象是一組沒(méi)有特定順序的值。對(duì)象的每個(gè)屬性或方法都有一個(gè)名字,而每個(gè)名字都
映射到一個(gè)值。
提示:每個(gè)對(duì)象都是基于一個(gè)引用類型創(chuàng)建的,這些類型可以是系統(tǒng)內(nèi)置的原生類型,也可以是開(kāi)發(fā)人員自定義的類型。
什么是面向?qū)ο?/h4>
面向?qū)ο蟛皇切碌臇|西,它只是過(guò)程式代碼的一種高度封裝,目的在于提高代碼的開(kāi)發(fā)效率和可維護(hù)性。
664ba37eeee9f4623c06c066867f1d38_r.jpg
面向?qū)ο缶幊?—— Object Oriented Programming,簡(jiǎn)稱 OOP ,是一種編程開(kāi)發(fā)思想。
它將真實(shí)世界各種復(fù)雜的關(guān)系,抽象為一個(gè)個(gè)對(duì)象,然后由對(duì)象之間的分工與合作,完成對(duì)真實(shí)世界的模擬。
面向?qū)ο蟛皇切碌臇|西,它只是過(guò)程式代碼的一種高度封裝,目的在于提高代碼的開(kāi)發(fā)效率和可維護(hù)性。

面向?qū)ο缶幊?—— Object Oriented Programming,簡(jiǎn)稱 OOP ,是一種編程開(kāi)發(fā)思想。
它將真實(shí)世界各種復(fù)雜的關(guān)系,抽象為一個(gè)個(gè)對(duì)象,然后由對(duì)象之間的分工與合作,完成對(duì)真實(shí)世界的模擬。
在面向?qū)ο蟪绦蜷_(kāi)發(fā)思想中,每一個(gè)對(duì)象都是功能中心,具有明確分工,可以完成接受信息、處理數(shù)據(jù)、發(fā)出信息等任務(wù)。
因此,面向?qū)ο缶幊叹哂徐`活、代碼可復(fù)用、高度模塊化等特點(diǎn),容易維護(hù)和開(kāi)發(fā),比起由一系列函數(shù)或指令組成的傳統(tǒng)的過(guò)程式編程(procedural programming),更適合多人合作的大型軟件項(xiàng)目。
面向?qū)ο笈c面向過(guò)程:
- 面向過(guò)程就是親力親為,事無(wú)巨細(xì),面面俱到,步步緊跟,有條不紊
- 面向?qū)ο缶褪钦乙粋€(gè)對(duì)象,指揮得結(jié)果
- 面向?qū)ο髮?zhí)行者轉(zhuǎn)變成指揮者
- 面向?qū)ο蟛皇敲嫦蜻^(guò)程的替代,而是面向過(guò)程的封裝
面向?qū)ο蟮奶匦裕?/p>
- 封裝性
- 繼承性
- [多態(tài)性]
擴(kuò)展閱讀:
程序中面向?qū)ο蟮幕倔w現(xiàn)
在 JavaScript 中,所有數(shù)據(jù)類型都可以視為對(duì)象,當(dāng)然也可以自定義對(duì)象。
自定義的對(duì)象數(shù)據(jù)類型就是面向?qū)ο笾械念悾?Class )的概念。
我們以一個(gè)例子來(lái)說(shuō)明面向過(guò)程和面向?qū)ο笤诔绦蛄鞒躺系牟煌帯?/p>
假設(shè)我們要處理學(xué)生的成績(jī)表,為了表示一個(gè)學(xué)生的成績(jī),面向過(guò)程的程序可以用一個(gè)對(duì)象表示:
var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }
而處理學(xué)生成績(jī)可以通過(guò)函數(shù)實(shí)現(xiàn),比如打印學(xué)生的成績(jī):
function printScore (student) {
console.log('姓名:' + student.name + ' ' + '成績(jī):' + student.score)
}
如果采用面向?qū)ο蟮某绦蛟O(shè)計(jì)思想,我們首選思考的不是程序的執(zhí)行流程,
而是 Student 這種數(shù)據(jù)類型應(yīng)該被視為一個(gè)對(duì)象,這個(gè)對(duì)象擁有 name 和 score 這兩個(gè)屬性(Property)。
如果要打印一個(gè)學(xué)生的成績(jī),首先必須創(chuàng)建出這個(gè)學(xué)生對(duì)應(yīng)的對(duì)象,然后,給對(duì)象發(fā)一個(gè) printScore 消息,讓對(duì)象自己把自己的數(shù)據(jù)打印出來(lái)。
抽象數(shù)據(jù)行為模板(Class):
function Student (name, score) {
this.name = name
this.score = score
}
Student.prototype.printScore = function () {
console.log('姓名:' + this.name + ' ' + '成績(jī):' + this.score)
}
根據(jù)模板創(chuàng)建具體實(shí)例對(duì)象(Instance):
var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)
實(shí)例對(duì)象具有自己的具體行為(給對(duì)象發(fā)消息):
std1.printScore() // => 姓名:Michael 成績(jī):98
std2.printScore() // => 姓名:Bob 成績(jī) 81
面向?qū)ο蟮脑O(shè)計(jì)思想是從自然界中來(lái)的,因?yàn)樵谧匀唤缰?,類(Class)和實(shí)例(Instance)的概念是很自然的。
Class 是一種抽象概念,比如我們定義的 Class——Student ,是指學(xué)生這個(gè)概念,
而實(shí)例(Instance)則是一個(gè)個(gè)具體的 Student ,比如, Michael 和 Bob 是兩個(gè)具體的 Student 。
所以,面向?qū)ο蟮脑O(shè)計(jì)思想是:
- 抽象出 Class
- 根據(jù) Class 創(chuàng)建 Instance
- 指揮 Instance 得結(jié)果
面向?qū)ο蟮某橄蟪潭扔直群瘮?shù)要高,因?yàn)橐粋€(gè) Class 既包含數(shù)據(jù),又包含操作數(shù)據(jù)的方法。
創(chuàng)建對(duì)象
簡(jiǎn)單方式
我們可以直接通過(guò) new Object() 創(chuàng)建:
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
每次創(chuàng)建通過(guò) new Object() 比較麻煩,所以可以通過(guò)它的簡(jiǎn)寫形式對(duì)象字面量來(lái)創(chuàng)建:
var person = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
對(duì)于上面的寫法固然沒(méi)有問(wèn)題,但是假如我們要生成兩個(gè) person 實(shí)例對(duì)象呢?
var person1 = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
var person2 = {
name: 'Mike',
age: 16,
sayName: function () {
console.log(this.name)
}
}
通過(guò)上面的代碼我們不難看出,這樣寫的代碼太過(guò)冗余,重復(fù)性太高。
簡(jiǎn)單方式的改進(jìn):工廠函數(shù)
我們可以寫一個(gè)函數(shù),解決代碼重復(fù)問(wèn)題:
function createPerson (name, age) {
return {
name: name,
age: age,
sayName: function () {
console.log(this.name)
}
}
}
然后生成實(shí)例對(duì)象:
var p1 = createPerson('Jack', 18)
var p2 = createPerson('Mike', 18)
這樣封裝確實(shí)爽多了,通過(guò)工廠模式我們解決了創(chuàng)建多個(gè)相似對(duì)象代碼冗余的問(wèn)題,
但卻沒(méi)有解決對(duì)象識(shí)別的問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)。
構(gòu)造函數(shù)
內(nèi)容引導(dǎo):
- 構(gòu)造函數(shù)語(yǔ)法
- 分析構(gòu)造函數(shù)
- 構(gòu)造函數(shù)和實(shí)例對(duì)象的關(guān)系
- 實(shí)例的 constructor 屬性
- instanceof 操作符
- 普通函數(shù)調(diào)用和構(gòu)造函數(shù)調(diào)用的區(qū)別
- 構(gòu)造函數(shù)的返回值
- 構(gòu)造函數(shù)的靜態(tài)成員和實(shí)例成員
- 函數(shù)也是對(duì)象
- 實(shí)例成員
- 靜態(tài)成員
- 構(gòu)造函數(shù)的問(wèn)題
更優(yōu)雅的工廠函數(shù):構(gòu)造函數(shù)
一種更優(yōu)雅的工廠函數(shù)就是下面這樣,構(gòu)造函數(shù):
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
解析構(gòu)造函數(shù)代碼的執(zhí)行
在上面的示例中,Person() 函數(shù)取代了 createPerson() 函數(shù),但是實(shí)現(xiàn)效果是一樣的。
這是為什么呢?
我們注意到,Person() 中的代碼與 createPerson() 有以下幾點(diǎn)不同之處:
- 沒(méi)有顯示的創(chuàng)建對(duì)象
- 直接將屬性和方法賦給了
this對(duì)象 - 沒(méi)有
return語(yǔ)句 - 函數(shù)名使用的是大寫的
Person
而要?jiǎng)?chuàng)建 Person 實(shí)例,則必須使用 new 操作符。
以這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下 4 個(gè)步驟:
- 創(chuàng)建一個(gè)新對(duì)象
- 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象)
- 執(zhí)行構(gòu)造函數(shù)中的代碼
- 返回新對(duì)象
下面是具體的偽代碼:
function Person (name, age) {
// 當(dāng)使用 new 操作符調(diào)用 Person() 的時(shí)候,實(shí)際上這里會(huì)先創(chuàng)建一個(gè)對(duì)象
// var instance = {}
// 然后讓內(nèi)部的 this 指向 instance 對(duì)象
// this = instance
// 接下來(lái)所有針對(duì) this 的操作實(shí)際上操作的就是 instance
this.name = name
this.age = age
this.sayName = function () {
console.log(this.name)
}
// 在函數(shù)的結(jié)尾處會(huì)將 this 返回,也就是 instance
// return this
}
構(gòu)造函數(shù)和實(shí)例對(duì)象的關(guān)系
使用構(gòu)造函數(shù)的好處不僅僅在于代碼的簡(jiǎn)潔性,更重要的是我們可以識(shí)別對(duì)象的具體類型了。
在每一個(gè)實(shí)例對(duì)象中的_proto_中同時(shí)有一個(gè) constructor 屬性,該屬性指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù):
console.log(p1.constructor === Person) // => true
console.log(p2.constructor === Person) // => true
console.log(p1.constructor === p2.constructor) // => true
對(duì)象的 constructor 屬性最初是用來(lái)標(biāo)識(shí)對(duì)象類型的,
但是,如果要檢測(cè)對(duì)象的類型,還是使用 instanceof 操作符更可靠一些:
console.log(p1 instanceof Person) // => true
console.log(p2 instanceof Person) // => true
總結(jié):
- 構(gòu)造函數(shù)是根據(jù)具體的事物抽象出來(lái)的抽象模板
- 實(shí)例對(duì)象是根據(jù)抽象的構(gòu)造函數(shù)模板得到的具體實(shí)例對(duì)象
- 每一個(gè)實(shí)例對(duì)象都具有一個(gè)
constructor屬性,指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù)- 注意:
constructor是實(shí)例的屬性的說(shuō)法不嚴(yán)謹(jǐn),具體后面的原型會(huì)講到
- 注意:
- 可以通過(guò)實(shí)例的
constructor屬性判斷實(shí)例和構(gòu)造函數(shù)之間的關(guān)系- 注意:這種方式不嚴(yán)謹(jǐn),推薦使用
instanceof操作符,后面學(xué)原型會(huì)解釋為什么
- 注意:這種方式不嚴(yán)謹(jǐn),推薦使用
構(gòu)造函數(shù)的問(wèn)題
使用構(gòu)造函數(shù)帶來(lái)的最大的好處就是創(chuàng)建對(duì)象更方便了,但是其本身也存在一個(gè)浪費(fèi)內(nèi)存的問(wèn)題:
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)
在該示例中,從表面上好像沒(méi)什么問(wèn)題,但是實(shí)際上這樣做,有一個(gè)很大的弊端。
那就是對(duì)于每一個(gè)實(shí)例對(duì)象,type 和 sayHello 都是一模一樣的內(nèi)容,
每一次生成一個(gè)實(shí)例,都必須為重復(fù)的內(nèi)容,多占用一些內(nèi)存,如果實(shí)例對(duì)象很多,會(huì)造成極大的內(nèi)存浪費(fèi)。
console.log(p1.sayHello === p2.sayHello) // => false
對(duì)于這種問(wèn)題我們可以把需要共享的函數(shù)定義到構(gòu)造函數(shù)外部:
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
這樣確實(shí)可以了,但是如果有多個(gè)需要共享的函數(shù)的話就會(huì)造成全局命名空間沖突的問(wèn)題。
你肯定想到了可以把多個(gè)函數(shù)放到一個(gè)對(duì)象中用來(lái)避免全局命名空間沖突的問(wèn)題:
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
至此,我們利用自己的方式基本上解決了構(gòu)造函數(shù)的內(nèi)存浪費(fèi)問(wèn)題。
但是代碼看起來(lái)還是那么的格格不入,那有沒(méi)有更好的方式呢?
小結(jié)
- 構(gòu)造函數(shù)語(yǔ)法
- 分析構(gòu)造函數(shù)
- 構(gòu)造函數(shù)和實(shí)例對(duì)象的關(guān)系
- 實(shí)例的 constructor 屬性
- instanceof 操作符
- 構(gòu)造函數(shù)的問(wèn)題
原型
內(nèi)容引導(dǎo):
- 使用 prototype 原型對(duì)象解決構(gòu)造函數(shù)的問(wèn)題
- 分析 構(gòu)造函數(shù)、prototype 原型對(duì)象、實(shí)例對(duì)象 三者之間的關(guān)系
- 屬性成員搜索原則:原型鏈
- 實(shí)例對(duì)象讀寫原型對(duì)象中的成員
- 原型對(duì)象的簡(jiǎn)寫形式
- 原生對(duì)象的原型
- Object
- Array
- String
- ...
- 原型對(duì)象的問(wèn)題
- 構(gòu)造的函數(shù)和原型對(duì)象使用建議
更好的解決方案: prototype
Javascript 規(guī)定,每一個(gè)構(gòu)造函數(shù)都有一個(gè) prototype 屬性,指向另一個(gè)對(duì)象。
這個(gè)對(duì)象的所有屬性和方法,都會(huì)被構(gòu)造函數(shù)的實(shí)例繼承。
這也就意味著,我們可以把所有對(duì)象實(shí)例需要共享的屬性和方法直接定義在 prototype 對(duì)象上。
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
這時(shí)所有實(shí)例的 type 屬性和 sayName() 方法,
其實(shí)都是同一個(gè)內(nèi)存地址,指向 prototype 對(duì)象,因此就提高了運(yùn)行效率。
構(gòu)造函數(shù)、實(shí)例、原型三者之間的關(guān)系

任何函數(shù)都具有一個(gè) prototype 屬性,該屬性是一個(gè)對(duì)象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
console.log('hi!')
}
構(gòu)造函數(shù)的 prototype 對(duì)象默認(rèn)都有一個(gè) constructor 屬性,指向 prototype 對(duì)象所在函數(shù)。
console.log(F.constructor === F) // => true
通過(guò)構(gòu)造函數(shù)得到的實(shí)例對(duì)象內(nèi)部會(huì)包含一個(gè)指向構(gòu)造函數(shù)的 prototype 對(duì)象的指針 __proto__。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
<p class="tip">
__proto__ 是非標(biāo)準(zhǔn)屬性。
</p>
實(shí)例對(duì)象可以直接訪問(wèn)原型對(duì)象成員。
instance.sayHi() // => hi!
總結(jié):
- 任何函數(shù)都具有一個(gè)
prototype屬性,該屬性是一個(gè)對(duì)象 - 構(gòu)造函數(shù)的
prototype對(duì)象默認(rèn)都有一個(gè)constructor屬性,指向prototype對(duì)象所在函數(shù) - 通過(guò)構(gòu)造函數(shù)得到的實(shí)例對(duì)象內(nèi)部會(huì)包含一個(gè)指向構(gòu)造函數(shù)的
prototype對(duì)象的指針__proto__ - 所有實(shí)例都直接或間接繼承了原型對(duì)象的成員
屬性成員的搜索原則:原型鏈
了解了 構(gòu)造函數(shù)-實(shí)例-原型對(duì)象 三者之間的關(guān)系后,接下來(lái)我們來(lái)解釋一下為什么實(shí)例對(duì)象可以訪問(wèn)原型對(duì)象中的成員。
每當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性
- 搜索首先從對(duì)象實(shí)例本身開(kāi)始
- 如果在實(shí)例中找到了具有給定名字的屬性,則返回該屬性的值
- 如果沒(méi)有找到,則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性
- 如果在原型對(duì)象中找到了這個(gè)屬性,則返回該屬性的值
也就是說(shuō),在我們調(diào)用 person1.sayName() 的時(shí)候,會(huì)先后執(zhí)行兩次搜索:
- 首先,解析器會(huì)問(wèn):“實(shí)例 person1 有 sayName 屬性嗎?”答:“沒(méi)有。
- ”然后,它繼續(xù)搜索,再問(wèn):“ person1 的原型有 sayName 屬性嗎?”答:“有。
- ”于是,它就讀取那個(gè)保存在原型對(duì)象中的函數(shù)。
- 當(dāng)我們調(diào)用 person2.sayName() 時(shí),將會(huì)重現(xiàn)相同的搜索過(guò)程,得到相同的結(jié)果。
而這正是多個(gè)對(duì)象實(shí)例共享原型所保存的屬性和方法的基本原理。
總結(jié):
- 先在自己身上找,找到即返回
- 自己身上找不到,則沿著原型鏈向上查找,找到即返回
- 如果一直到原型鏈的末端還沒(méi)有找到,則返回
undefined
實(shí)例對(duì)象讀寫原型對(duì)象成員
讀取:
- 先在自己身上找,找到即返回
- 自己身上找不到,則沿著原型鏈向上查找,找到即返回
- 如果一直到原型鏈的末端還沒(méi)有找到,則返回
undefined
值類型成員寫入(實(shí)例對(duì)象.值類型成員 = xx):
- 當(dāng)實(shí)例期望重寫原型對(duì)象中的某個(gè)普通數(shù)據(jù)成員時(shí)實(shí)際上會(huì)把該成員添加到自己身上
- 也就是說(shuō)該行為實(shí)際上會(huì)屏蔽掉對(duì)原型對(duì)象成員的訪問(wèn)
引用類型成員寫入(實(shí)例對(duì)象.引用類型成員 = xx):
- 同上
復(fù)雜類型修改(實(shí)例對(duì)象.成員.xx = xx):
- 同樣會(huì)先在自己身上找該成員,如果自己身上找到則直接修改
- 如果自己身上找不到,則沿著原型鏈繼續(xù)查找,如果找到則修改
- 如果一直到原型鏈的末端還沒(méi)有找到該成員,則報(bào)錯(cuò)(
實(shí)例對(duì)象.undefined.xx = xx)
更簡(jiǎn)單的原型語(yǔ)法
我們注意到,前面例子中每添加一個(gè)屬性和方法就要敲一遍 Person.prototype 。
為減少不必要的輸入,更常見(jiàn)的做法是用一個(gè)包含所有屬性和方法的對(duì)象字面量來(lái)重寫整個(gè)原型對(duì)象:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
}
}
在該示例中,我們將 Person.prototype 重置到了一個(gè)新的對(duì)象。
這樣做的好處就是為 Person.prototype 添加成員簡(jiǎn)單了,但是也會(huì)帶來(lái)一個(gè)問(wèn)題,那就是原型對(duì)象丟失了 constructor 成員。
所以,我們?yōu)榱吮3?constructor 的指向正確,建議的寫法是:
function Person (name, age) {
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person, // => 手動(dòng)將 constructor 指向正確的構(gòu)造函數(shù)
type: 'human',
sayHello: function () {
console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
}
}
原生對(duì)象的原型
<p class="tip">
所有函數(shù)都有 prototype 屬性對(duì)象。
</p>
- Object.prototype
- Function.prototype
- Array.prototype
- String.prototype
- Number.prototype
- Date.prototype
- ...
練習(xí):為數(shù)組對(duì)象和字符串對(duì)象擴(kuò)展原型方法。
原型對(duì)象的問(wèn)題
- 共享數(shù)組
- 共享對(duì)象
如果真的希望可以被實(shí)例對(duì)象之間共享和修改這些共享數(shù)據(jù)那就不是問(wèn)題。但是如果不希望實(shí)例之間共享和修改這些共享數(shù)據(jù)則就是問(wèn)題。
一個(gè)更好的建議是,最好不要讓實(shí)例之間互相共享這些數(shù)組或者對(duì)象成員,一旦修改的話會(huì)導(dǎo)致數(shù)據(jù)的走向很不明確而且難以維護(hù)。
原型對(duì)象使用建議
- 私有成員(一般就是非函數(shù)成員)放到構(gòu)造函數(shù)中
- 共享成員(一般就是函數(shù))放到原型對(duì)象中
- 如果重置了
prototype記得修正constructor的指向
繼承
什么是繼承
- 現(xiàn)實(shí)生活中的繼承
- 程序中的繼承
構(gòu)造函數(shù)的屬性繼承:借用構(gòu)造函數(shù)
function Person (name, age) {
this.type = 'human'
this.name = name
this.age = age
}
function Student (name, age) {
// 借用構(gòu)造函數(shù)繼承屬性成員
Person.call(this, name, age)
}
var s1 = Student('張三', 18)
console.log(s1.type, s1.name, s1.age) // => human 張三 18
構(gòu)造函數(shù)的原型方法繼承:拷貝繼承(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)
}
// 原型對(duì)象拷貝繼承原型對(duì)象成員
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)
}
// 利用原型的特性實(shí)現(xiàn)繼承
Student.prototype = new Person()
var s1 = Student('張三', 18)
console.log(s1.type) // => human
s1.sayName() // => hello 張三
函數(shù)進(jìn)階
函數(shù)的定義方式
- 函數(shù)聲明
- 函數(shù)表達(dá)式
new Function
函數(shù)聲明
function foo () {
}
函數(shù)表達(dá)式
var foo = function () {
}
函數(shù)聲明與函數(shù)表達(dá)式的區(qū)別
- 函數(shù)聲明必須有名字
- 函數(shù)聲明會(huì)函數(shù)提升,在預(yù)解析階段就已創(chuàng)建,聲明前后都可以調(diào)用
- 函數(shù)表達(dá)式類似于變量賦值
- 函數(shù)表達(dá)式可以沒(méi)有名字,例如匿名函數(shù)
- 函數(shù)表達(dá)式?jīng)]有變量提升,在執(zhí)行階段創(chuàng)建,必須在表達(dá)式執(zhí)行之后才可以調(diào)用
下面是一個(gè)根據(jù)條件定義函數(shù)的例子:
if (true) {
function f () {
console.log(1)
}
} else {
function f () {
console.log(2)
}
}
以上代碼執(zhí)行結(jié)果在不同瀏覽器中結(jié)果不一致。
不過(guò)我們可以使用函數(shù)表達(dá)式解決上面的問(wèn)題:
var f
if (true) {
f = function () {
console.log(1)
}
} else {
f = function () {
console.log(2)
}
}
函數(shù)的調(diào)用方式
- 普通函數(shù)
- 構(gòu)造函數(shù)
- 對(duì)象方法
函數(shù)內(nèi) this 指向的不同場(chǎng)景
函數(shù)的調(diào)用方式?jīng)Q定了 this 指向的不同:
| 調(diào)用方式 | 非嚴(yán)格模式 | 備注 |
|---|---|---|
| 普通函數(shù)調(diào)用 | window | 嚴(yán)格模式下是 undefined |
| 構(gòu)造函數(shù)調(diào)用 | 實(shí)例對(duì)象 | 原型方法中 this 也是實(shí)例對(duì)象 |
| 對(duì)象方法調(diào)用 | 該方法所屬對(duì)象 | 緊挨著的對(duì)象 |
| 事件綁定方法 | 綁定事件對(duì)象 | |
| 定時(shí)器函數(shù) | window |
這就是對(duì)函數(shù)內(nèi)部 this 指向的基本整理,寫代碼寫多了自然而然就熟悉了。
函數(shù)也是對(duì)象
- 所有函數(shù)都是
Function的實(shí)例
call、apply、bind
那了解了函數(shù) this 指向的不同場(chǎng)景之后,我們知道有些情況下我們?yōu)榱耸褂媚撤N特定環(huán)境的 this 引用,
這時(shí)候時(shí)候我們就需要采用一些特殊手段來(lái)處理了,例如我們經(jīng)常在定時(shí)器外部備份 this 引用,然后在定時(shí)器函數(shù)內(nèi)部使用外部 this 的引用。
然而實(shí)際上對(duì)于這種做法我們的 JavaScript 為我們專門提供了一些函數(shù)方法用來(lái)幫我們更優(yōu)雅的處理函數(shù)內(nèi)部 this 指向問(wèn)題。
這就是接下來(lái)我們要學(xué)習(xí)的 call、apply、bind 三個(gè)函數(shù)方法。
call
call() 方法調(diào)用一個(gè)函數(shù), 其具有一個(gè)指定的 this 值和分別地提供的參數(shù)(參數(shù)的列表)。
<p class="danger">
注意:該方法的作用和 apply() 方法類似,只有一個(gè)區(qū)別,就是 call() 方法接受的是若干個(gè)參數(shù)的列表,而 apply() 方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組。
</p>
語(yǔ)法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
參數(shù):
-
thisArg- 在 fun 函數(shù)運(yùn)行時(shí)指定的 this 值
- 如果指定了 null 或者 undefined 則內(nèi)部 this 指向 window
-
arg1, arg2, ...- 指定的參數(shù)列表
apply
apply() 方法調(diào)用一個(gè)函數(shù), 其具有一個(gè)指定的 this 值,以及作為一個(gè)數(shù)組(或類似數(shù)組的對(duì)象)提供的參數(shù)。
<p class="danger">
注意:該方法的作用和 call() 方法類似,只有一個(gè)區(qū)別,就是 call() 方法接受的是若干個(gè)參數(shù)的列表,而 apply() 方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組。
</p>
語(yǔ)法:
fun.apply(thisArg, [argsArray])
參數(shù):
thisArgargsArray
apply() 與 call() 非常相似,不同之處在于提供參數(shù)的方式。
apply() 使用參數(shù)數(shù)組而不是一組參數(shù)列表。例如:
fun.apply(this, ['eat', 'bananas'])
bind
bind() 函數(shù)會(huì)創(chuàng)建一個(gè)新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標(biāo)函數(shù))具有相同的函數(shù)體(在 ECMAScript 5 規(guī)范中內(nèi)置的call屬性)。
當(dāng)目標(biāo)函數(shù)被調(diào)用時(shí) this 值綁定到 bind() 的第一個(gè)參數(shù),該參數(shù)不能被重寫。綁定函數(shù)被調(diào)用時(shí),bind() 也接受預(yù)設(shè)的參數(shù)提供給原函數(shù)。
一個(gè)綁定函數(shù)也能使用new操作符創(chuàng)建對(duì)象:這種行為就像把原函數(shù)當(dāng)成構(gòu)造器。提供的 this 值被忽略,同時(shí)調(diào)用時(shí)的參數(shù)被提供給模擬函數(shù)。
語(yǔ)法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數(shù):
-
thisArg
- 當(dāng)綁定函數(shù)被調(diào)用時(shí),該參數(shù)會(huì)作為原函數(shù)運(yùn)行時(shí)的 this 指向。當(dāng)使用new 操作符調(diào)用綁定函數(shù)時(shí),該參數(shù)無(wú)效。
-
arg1, arg2, ...
- 當(dāng)綁定函數(shù)被調(diào)用時(shí),這些參數(shù)將置于實(shí)參之前傳遞給被綁定的方法。
返回值:
返回由指定的this值和初始化參數(shù)改造的原函數(shù)拷貝。
示例1:
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 返回 81
var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種情況下,"this"指向全局作用域
// 創(chuàng)建一個(gè)新函數(shù),將"this"綁定到module對(duì)象
// 新手可能會(huì)被全局的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(); // 一秒鐘后, 調(diào)用'declare'方法
小結(jié)
-
call 和 apply 特性一樣
- 都是用來(lái)調(diào)用函數(shù),而且是立即調(diào)用
- 但是可以在調(diào)用函數(shù)的同時(shí),通過(guò)第一個(gè)參數(shù)指定函數(shù)內(nèi)部
this的指向 - call 調(diào)用的時(shí)候,參數(shù)必須以參數(shù)列表的形式進(jìn)行傳遞,也就是以逗號(hào)分隔的方式依次傳遞即可
- apply 調(diào)用的時(shí)候,參數(shù)必須是一個(gè)數(shù)組,然后在執(zhí)行的時(shí)候,會(huì)將數(shù)組內(nèi)部的元素一個(gè)一個(gè)拿出來(lái),與形參一一對(duì)應(yīng)進(jìn)行傳遞
- 如果第一個(gè)參數(shù)指定了
null或者undefined則內(nèi)部 this 指向 window
-
bind
- 可以用來(lái)指定內(nèi)部 this 的指向,然后生成一個(gè)改變了 this 指向的新的函數(shù)
- 它和 call、apply 最大的區(qū)別是:bind 不會(huì)調(diào)用
- bind 支持傳遞參數(shù),它的傳參方式比較特殊,一共有兩個(gè)位置可以傳遞
- 在 bind 的同時(shí),以參數(shù)列表的形式進(jìn)行傳遞
- 在調(diào)用的時(shí)候,以參數(shù)列表的形式進(jìn)行傳遞
- 那到底以誰(shuí) bind 的時(shí)候傳遞的參數(shù)為準(zhǔn)呢還是以調(diào)用的時(shí)候傳遞的參數(shù)為準(zhǔn)
- 兩者合并:bind 的時(shí)候傳遞的參數(shù)和調(diào)用的時(shí)候傳遞的參數(shù)會(huì)合并到一起,傳遞到函數(shù)內(nèi)部
函數(shù)的其它成員
- arguments
- 實(shí)參集合
- caller
- 函數(shù)的調(diào)用者
- length
- 形參的個(gè)數(shù)
- name
- 函數(shù)的名稱
function fn(x, y, z) {
console.log(fn.length) // => 形參的個(gè)數(shù)
console.log(arguments) // 偽數(shù)組實(shí)參參數(shù)集合
console.log(arguments.callee === fn) // 函數(shù)本身
console.log(fn.caller) // 函數(shù)的調(diào)用者
console.log(fn.name) // => 函數(shù)的名字
}
function f() {
fn(10, 20, 30)
}
f()
高階函數(shù)
- 函數(shù)可以作為參數(shù)
- 函數(shù)可以作為返回值
作為參數(shù)
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
函數(shù)閉包
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
作用域、作用域鏈、預(yù)解析
- 全局作用域
- 函數(shù)作用域
- 沒(méi)有塊級(jí)作用域
{
var foo = 'bar'
}
console.log(foo)
if (true) {
var a = 123
}
console.log(a)
作用域鏈?zhǔn)纠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()
}
- 內(nèi)層作用域可以訪問(wèn)外層作用域,反之不行
什么是閉包
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),
由于在 Javascript 語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,
因此可以把閉包簡(jiǎn)單理解成 “定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。
所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
閉包的用途:
- 可以在函數(shù)外部讀取函數(shù)內(nèi)部成員
- 讓函數(shù)內(nèi)成員始終存活在內(nèi)存中
一些關(guān)于閉包的例子
示例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()())
小結(jié)
函數(shù)遞歸
遞歸執(zhí)行模型
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()
舉個(gè)栗子:計(jì)算階乘的遞歸函數(shù)
function factorial (num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}
遞歸應(yīng)用場(chǎng)景
- 深拷貝
- 菜單樹(shù)
- 遍歷 DOM 樹(shù)
正則表達(dá)式
- 了解正則表達(dá)式基本語(yǔ)法
- 能夠使用JavaScript的正則對(duì)象
正則表達(dá)式簡(jiǎn)介
什么是正則表達(dá)式
正則表達(dá)式:用于匹配規(guī)律規(guī)則的表達(dá)式,正則表達(dá)式最初是科學(xué)家對(duì)人類神經(jīng)系統(tǒng)的工作原理的早期研究,現(xiàn)在在編程語(yǔ)言中有廣泛的應(yīng)用。正則表通常被用來(lái)檢索、替換那些符合某個(gè)模式(規(guī)則)的文本。
正則表達(dá)式是對(duì)字符串操作的一種邏輯公式,就是用事先定義好的一些特定字符、及這些特定字符的組合,組成一個(gè)“規(guī)則字符串”,這個(gè)“規(guī)則字符串”用來(lái)表達(dá)對(duì)字符串的一種過(guò)濾邏輯。
正則表達(dá)式的作用
- 給定的字符串是否符合正則表達(dá)式的過(guò)濾邏輯(匹配)
- 可以通過(guò)正則表達(dá)式,從字符串中獲取我們想要的特定部分(提取)
- 強(qiáng)大的字符串替換能力(替換)
正則表達(dá)式的特點(diǎn)
- 靈活性、邏輯性和功能性非常的強(qiáng)
- 可以迅速地用極簡(jiǎn)單的方式達(dá)到字符串的復(fù)雜控制
- 對(duì)于剛接觸的人來(lái)說(shuō),比較晦澀難懂
正則表達(dá)式的測(cè)試
- 在線測(cè)試正則
- 工具中使用正則表達(dá)式
- sublime/vscode/word
- 演示替換所有的數(shù)字
正則表達(dá)式的組成
- 普通字符
- 特殊字符(元字符):正則表達(dá)式中有特殊意義的字符
示例演示:
-
\d匹配數(shù)字 -
ab\d匹配 ab1、ab2
元字符串
通過(guò)測(cè)試工具演示下面元字符的使用
常用元字符串
| 元字符 | 說(shuō)明 |
|---|---|
| \d | 匹配數(shù)字 |
| \D | 匹配任意非數(shù)字的字符 |
| \w | 匹配字母或數(shù)字或下劃線 |
| \W | 匹配任意不是字母,數(shù)字,下劃線 |
| \s | 匹配任意的空白符 |
| \S | 匹配任意不是空白符的字符 |
| . | 匹配除換行符以外的任意單個(gè)字符 |
| ^ | 表示匹配行首的文本(以誰(shuí)開(kāi)始) |
| $ | 表示匹配行尾的文本(以誰(shuí)結(jié)束) |
限定符
| 限定符 | 說(shuō)明 |
|---|---|
| * | 重復(fù)零次或更多次 |
| + | 重復(fù)一次或更多次 |
| ? | 重復(fù)零次或一次 |
| {n} | 重復(fù)n次 |
| {n,} | 重復(fù)n次或更多次 |
| {n,m} | 重復(fù)n到m次 |
其它
[] 字符串用中括號(hào)括起來(lái),表示匹配其中的任一字符,相當(dāng)于或的意思
[^] 匹配除中括號(hào)以內(nèi)的內(nèi)容
\ 轉(zhuǎn)義符
| 或者,選擇兩者中的一個(gè)。注意|將左右兩邊分為兩部分,而不管左右兩邊有多長(zhǎng)多亂
() 從兩個(gè)直接量中選擇一個(gè),分組
eg:gr(a|e)y匹配gray和grey
[\u4e00-\u9fa5] 匹配漢字
案例
驗(yàn)證手機(jī)號(hào):
^\d{11}$
驗(yàn)證郵編:
^\d{6}$
驗(yàn)證日期 2012-5-01
^\d{4}-\d{1,2}-\d{1,2}$
驗(yàn)證郵箱 xxx@itcast.cn:
^\w+@\w+\.\w+$
驗(yàn)證IP地址 192.168.1.10
^\d{1,3}\(.\d{1,3}){3}$
JavaScript 中使用正則表達(dá)式
創(chuàng)建正則對(duì)象
方式1:
var reg = new Regex('\d', 'i');
var reg = new Regex('\d', 'gi');
方式2:
var reg = /\d/i;
var reg = /\d/gi;
參數(shù)
| 標(biāo)志 | 說(shuō)明 |
|---|---|
| i | 忽略大小寫 |
| g | 全局匹配 |
| gi | 全局匹配+忽略大小寫 |
正則匹配
// 匹配日期
var dateStr = '2015-10-10';
var reg = /^\d{4}-\d{1,2}-\d{1,2}$/
console.log(reg.test(dateStr));
匹配正則表達(dá)式
// console.log(/./.test("除了回車換行以為的任意字符"));//true
// console.log(/.*/.test("0個(gè)到多個(gè)"));//true
// console.log(/.+/.test("1個(gè)到多個(gè)"));//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
正則表達(dá)式案例
1.驗(yàn)證密碼強(qiáng)弱
2.驗(yàn)證郵箱:[0-9a-zA-Z_.-]+[@][0-9a-zA-Z._-]+([.][a-zA-Z]+){1,2}
3.驗(yàn)證中文名字[\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 2、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';
// 正則表達(dá)式中的()作為分組來(lái)使用,獲取分組匹配到的結(jié)果用Regex.$1 $2 $3....來(lái)獲取
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);
案例:表單驗(yàn)證
QQ號(hào):<input type="text" id="txtQQ"><span></span><br>
郵箱:<input type="text" id="txtEMail"><span></span><br>
手機(jī):<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 () {
//獲取當(dāng)前文本框?qū)?yīng)的span
var span = this.nextElementSibling;
var reg = /^\d{5,12}$/;
//判斷驗(yàn)證是否成功
if(!reg.test(this.value) ){
//驗(yàn)證不成功
span.innerText = "請(qǐng)輸入正確的QQ號(hào)";
span.style.color = "red";
}else{
//驗(yàn)證成功
span.innerText = "";
span.style.color = "";
}
};
//txtEMail
txtEMail.onblur = function () {
//獲取當(dāng)前文本框?qū)?yīng)的span
var span = this.nextElementSibling;
var reg = /^\w+@\w+\.\w+(\.\w+)?$/;
//判斷驗(yàn)證是否成功
if(!reg.test(this.value) ){
//驗(yàn)證不成功
span.innerText = "請(qǐng)輸入正確的EMail地址";
span.style.color = "red";
}else{
//驗(yàn)證成功
span.innerText = "";
span.style.color = "";
}
};
表單驗(yàn)證部分,封裝成函數(shù):
var regBirthday = /^\d{4}-\d{1,2}-\d{1,2}$/;
addCheck(txtBirthday, regBirthday, "請(qǐng)輸入正確的出生日期");
//給文本框添加驗(yàn)證
function addCheck(element, reg, tip) {
element.onblur = function () {
//獲取當(dāng)前文本框?qū)?yīng)的span
var span = this.nextElementSibling;
//判斷驗(yàn)證是否成功
if(!reg.test(this.value) ){
//驗(yàn)證不成功
span.innerText = tip;
span.style.color = "red";
}else{
//驗(yàn)證成功
span.innerText = "";
span.style.color = "";
}
};
}
通過(guò)給元素增加自定義驗(yàn)證屬性對(duì)表單進(jìn)行驗(yàn)證:
<form id="frm">
QQ號(hào):<input type="text" name="txtQQ" data-rule="qq"><span></span><br>
郵箱:<input type="text" name="txtEMail" data-rule="email"><span></span><br>
手機(jī):<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>
// 所有的驗(yàn)證規(guī)則
var rules = [
{
name: 'qq',
reg: /^\d{5,12}$/,
tip: "請(qǐng)輸入正確的QQ"
},
{
name: 'email',
reg: /^\w+@\w+\.\w+(\.\w+)?$/,
tip: "請(qǐng)輸入正確的郵箱地址"
},
{
name: 'phone',
reg: /^\d{11}$/,
tip: "請(qǐng)輸入正確的手機(jī)號(hào)碼"
},
{
name: 'date',
reg: /^\d{4}-\d{1,2}-\d{1,2}$/,
tip: "請(qǐng)輸入正確的出生日期"
},
{
name: 'cn',
reg: /^[\u4e00-\u9fa5]{2,4}$/,
tip: "請(qǐng)輸入正確的姓名"
}];
addCheck('frm');
//給文本框添加驗(yàn)證
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屬性的元素添加驗(yàn)證
if (element.name) {
element.onblur = function () {
// 使用dataset獲取data-自定義屬性的值
var ruleName = this.dataset.rule;
var rule =getRuleByRuleName(rules, ruleName);
var span = this.nextElementSibling;
//判斷驗(yàn)證是否成功
if(!rule.reg.test(this.value) ){
//驗(yàn)證不成功
span.innerText = rule.tip;
span.style.color = "red";
}else{
//驗(yàn)證成功
span.innerText = "";
span.style.color = "";
}
}
}
}
}
// 根據(jù)規(guī)則的名稱獲取規(guī)則對(duì)象
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;
}
補(bǔ)充
偽數(shù)組和數(shù)組
在JavaScript中,除了5種原始數(shù)據(jù)類型之外,其他所有的都是對(duì)象,包括函數(shù)(Function)。
對(duì)象與數(shù)組的關(guān)系
在說(shuō)區(qū)別之前,需要先提到另外一個(gè)知識(shí),就是 JavaScript 的原型繼承。
所有 JavaScript 的內(nèi)置構(gòu)造函數(shù)都是繼承自 Object.prototype 。
在這個(gè)前提下,可以理解為使用 new Array() 或 [] 創(chuàng)建出來(lái)的數(shù)組對(duì)象,都會(huì)擁有 Object.prototype 的屬性值。
var obj = {};// 擁有 Object.prototype 的屬性值
var arr = [];
//使用數(shù)組直接量創(chuàng)建的數(shù)組,由于 Array.prototype 的屬性繼承自 Object.prototype,
//那么,它將同時(shí)擁有 Array.prototype 和 Object.prototype 的屬性值
可以得到對(duì)象和數(shù)組的第一個(gè)區(qū)別:對(duì)象沒(méi)有數(shù)組 Array.prototype 的屬性值。
什么是數(shù)組
數(shù)組具有一個(gè)最基本特征:索引,這是對(duì)象所沒(méi)有的,下面來(lái)看一段代碼:
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',是因?yàn)閷?duì)象就是普通的鍵值對(duì)存取數(shù)據(jù)
- 而arr[2]輸出'a' 則不同,數(shù)組是通過(guò)索引來(lái)存取數(shù)據(jù),arr[2]之所以輸出'a',是因?yàn)閿?shù)組arr索引2的位置已經(jīng)存儲(chǔ)了數(shù)據(jù)
- obj.length并不具有數(shù)組的特性,并且obj沒(méi)有保存屬性length,那么自然就會(huì)輸出undefined
- 而對(duì)于數(shù)組來(lái)說(shuō),length是數(shù)組的一個(gè)內(nèi)置屬性,數(shù)組會(huì)根據(jù)索引長(zhǎng)度來(lái)更改length的值
- 為什么arr.length輸出3,而不是1
- 在給數(shù)組添加元素時(shí),并沒(méi)有按照連續(xù)的索引添加,所以導(dǎo)致數(shù)組的索引不連續(xù),那么就導(dǎo)致索引長(zhǎng)度大于元素個(gè)數(shù)
什么是偽數(shù)組
- 擁有 length 屬性,其它屬性(索引)為非負(fù)整數(shù)(對(duì)象中的索引會(huì)被當(dāng)做字符串來(lái)處理,這里你可以當(dāng)做是個(gè)非負(fù)整數(shù)串來(lái)理解)
- 不具有數(shù)組所具有的方法
偽數(shù)組,就是像數(shù)組一樣有 length 屬性,也有 0、1、2、3 等屬性的對(duì)象,看起來(lái)就像數(shù)組一樣,但不是數(shù)組,比如:
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,'+');
常見(jiàn)的偽數(shù)組有:
- 函數(shù)內(nèi)部的
arguments - DOM 對(duì)象列表(比如通過(guò)
document.getElementsByTags得到的列表) - jQuery 對(duì)象(比如
$("div"))
偽數(shù)組是一個(gè) Object,而真實(shí)的數(shù)組是一個(gè) Array。
偽數(shù)組存在的意義,是可以讓普通的對(duì)象也能正常使用數(shù)組的很多方法,比如:
var arr = Array.prototype.slice.call(arguments);
Array.prototype.forEach.call(arguments, function(v) {
// 循環(huán)arguments對(duì)象
});
// push
// some
// every
// filter
// map
// ...
以上在借用數(shù)組的原型方法的時(shí)候都可以通過(guò)數(shù)組直接量來(lái)簡(jiǎn)化使用:
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)
})
小結(jié)
- 對(duì)象沒(méi)有數(shù)組 Array.prototype 的屬性值,類型是 Object ,而數(shù)組類型是 Array
- 數(shù)組是基于索引的實(shí)現(xiàn), length 會(huì)自動(dòng)更新,而對(duì)象是鍵值對(duì)
- 使用對(duì)象可以創(chuàng)建偽數(shù)組,偽數(shù)組可以正常使用數(shù)組的大部分方法
JavaScript 垃圾回收機(jī)制
JavaScript 運(yùn)行機(jī)制:Event Loop
Object
靜態(tài)成員
- Object.assign()
- Object.create()
- Object.keys()
- Object.defineProperty()
實(shí)例成員
- constructor
- hasOwnProperty()
- isPrototypeOf
- propertyIsEnumerable()
- toString()
- valueOf()
附錄
A 代碼規(guī)范
代碼風(fēng)格
校驗(yàn)工具
B Chrome 開(kāi)發(fā)者工具
C 文檔相關(guān)工具
- 電子文檔制作工具: docute
- 流程圖工具:DiagramDesigner