JavaScript高級(jí)


學(xué)習(xí)目標(biāo):

  • 理解面向?qū)ο箝_(kāi)發(fā)思想
  • 掌握 JavaScript 面向?qū)ο箝_(kāi)發(fā)相關(guān)模式
  • 掌握在 JavaScript 中使用正則表達(dá)式

typora-copy-images-to media

JavaScript 高級(jí)

課程介紹

課程大綱

在線地址:JavaScript 高級(jí)

目標(biāo)

  • 理解面向?qū)ο箝_(kāi)發(fā)思想
  • 掌握 JavaScript 面向?qū)ο箝_(kāi)發(fā)相關(guān)模式
  • 掌握在 JavaScript 中使用正則表達(dá)式

案例演示


基本概念復(fù)習(xí)

由于 JavaScript 高級(jí)還是針對(duì) JavaScript 語(yǔ)言本身的一個(gè)進(jìn)階學(xué)習(xí),所以在開(kāi)始之前我們先對(duì)以前所學(xué)過(guò)的 JavaScript 相關(guān)知識(shí)點(diǎn)做一個(gè)快速?gòu)?fù)習(xí)總結(jié)。

重新介紹 JavaScript

JavaScript 是什么

  • 解析執(zhí)行:輕量級(jí)解釋型的,或是 JIT 編譯型的程序設(shè)計(jì)語(yǔ)言
  • 語(yǔ)言特點(diǎn):動(dòng)態(tài),頭等函數(shù) (First-class Function)
    • 又稱(chēng)函數(shù)是 JavaScript 中的一等公民
  • 執(zhí)行環(huán)境:在宿主環(huán)境(host environment)下運(yùn)行,瀏覽器是最常見(jiàn)的 JavaScript 宿主環(huán)境
    • 但是在很多非瀏覽器環(huán)境中也使用 JavaScript ,例如 node.js
  • 編程范式:基于原型、多范式的動(dòng)態(tài)腳本語(yǔ)言,并且支持面向?qū)ο蟆⒚钍胶吐暶魇剑ㄈ纾汉瘮?shù)式編程)編程風(fēng)格

JavaScript 與瀏覽器的關(guān)系

<img src="media/browser-js.png" alt="">

JavaScript 的組成

組成部分 說(shuō)明
Ecmascript 描述了該語(yǔ)言的語(yǔ)法和基本對(duì)象
DOM 描述了處理網(wǎng)頁(yè)內(nèi)容的方法和接口
BOM 描述了與瀏覽器進(jìn)行交互的方法和接口

JavaScript 可以做什么

Any application that can be written in JavaScript, will eventually be written in JavaScript.
凡是能用 JavaScript 寫(xiě)出來(lái)的,最終都會(huì)用 JavaScript 寫(xiě)出來(lái)

JavaScript 發(fā)展歷史

JavaScript 標(biāo)準(zhǔn)參考教程 - JavaScript 語(yǔ)言的歷史

  • JavaScript 的誕生
  • JavaScript 與 Ecmascript 的關(guān)系
  • JavaScript 與 Java 的關(guān)系
  • JavaScript 的版本
  • JavaScript 周邊大事記

小結(jié)

基本概念

本小節(jié)快速過(guò)即可,主要是對(duì)學(xué)過(guò)的內(nèi)容做知識(shí)點(diǎn)梳理。

  • 語(yǔ)法
    • 區(qū)分大小寫(xiě)
    • 標(biāo)識(shí)符
    • 注釋
    • 嚴(yán)格模式
    • 語(yǔ)句
  • 關(guān)鍵字和保留字
  • 變量
  • 數(shù)據(jù)類(lèi)型
    • typeof 操作符
    • Undefined
    • Null
    • Boolean
    • Number
    • String
    • Object
  • 操作符
  • 流程控制語(yǔ)句
  • 函數(shù)

JavaScript 中的數(shù)據(jù)類(lèi)型

JavaScript 有 5 種簡(jiǎn)單數(shù)據(jù)類(lèi)型:Undefined、Null、Boolean、Number、String 和 1 種復(fù)雜數(shù)據(jù)類(lèi)型 Object 。

基本類(lèi)型(值類(lèi)型)

  • Undefined
  • Null
  • Boolean
  • Number
  • String

復(fù)雜類(lèi)型(引用類(lèi)型)

  • Object
  • Array
  • Date
  • RegExp
  • Function
  • 基本包裝類(lèi)型
    • Boolean
    • Number
    • String
  • 單體內(nèi)置對(duì)象
    • Global
    • Math

類(lèi)型檢測(cè)

  • typeof
  • instanceof
  • Object.prototype.toString.call()

值類(lèi)型和引用類(lèi)型在內(nèi)存中的存儲(chǔ)方式(畫(huà)圖說(shuō)明)

  • 值類(lèi)型按值存儲(chǔ)
  • 引用類(lèi)型按引用存儲(chǔ)

值類(lèi)型復(fù)制和引用類(lèi)型復(fù)制(畫(huà)圖說(shuō)明)

  • 值類(lèi)型按值復(fù)制
  • 引用類(lèi)型按引用復(fù)制

值類(lèi)型和引用類(lèi)型參數(shù)傳遞(畫(huà)圖說(shuō)明)

  • 值類(lèi)型按值傳遞
  • 引用類(lèi)型按引用傳遞

值類(lèi)型與引用類(lèi)型的差別

  • 基本類(lèi)型在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中
  • 從一個(gè)變量向另一個(gè)變量復(fù)制基本類(lèi)型的值,復(fù)制的是值的副本
  • 引用類(lèi)型的值是對(duì)象,保存在堆內(nèi)存
  • 包含引用類(lèi)型值的變量實(shí)際上包含的并不是對(duì)象本身,而是一個(gè)指向該對(duì)象的指針
  • 從一個(gè)變量向另一個(gè)變量復(fù)制引用類(lèi)型的值的時(shí)候,復(fù)制是引用指針,因此兩個(gè)變量最終都指向同一個(gè)對(duì)象

小結(jié)

  • 類(lèi)型檢測(cè)方式
  • 值類(lèi)型和引用類(lèi)型的存儲(chǔ)方式
  • 值類(lèi)型復(fù)制和引用類(lèi)型復(fù)制
  • 方法參數(shù)中 值類(lèi)型數(shù)據(jù)傳遞 和 引用類(lèi)型數(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>

<img src="./media/mxdxkf.png" width="400" alt="">

面向?qū)ο蠼榻B

什么是對(duì)象

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

<img src="./media/20160823024542444.jpg" alt="">

對(duì)象到底是什么,我們可以從兩次層次來(lái)理解。

(1) 對(duì)象是單個(gè)事物的抽象。

一本書(shū)、一輛汽車(chē)、一個(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è)值。

<p class="tip">
提示:每個(gè)對(duì)象都是基于一個(gè)引用類(lèi)型創(chuàng)建的,這些類(lèi)型可以是系統(tǒng)內(nèi)置的原生類(lèi)型,也可以是開(kāi)發(fā)人員自定義的類(lèi)型。
</p>

什么是面向?qū)ο?/h4>

面向?qū)ο蟛皇切碌臇|西,它只是過(guò)程式代碼的一種高度封裝,目的在于提高代碼的開(kāi)發(fā)效率和可維護(hù)性。

<img src="./media/664ba37eeee9f4623c06c066867f1d38_r.jpg" width="400" alt="">

面向?qū)ο缶幊?—— Object Oriented Programming,簡(jiǎn)稱(chēng) 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ù)類(lèi)型都可以視為對(duì)象,當(dāng)然也可以自定義對(duì)象。
自定義的對(duì)象數(shù)據(jù)類(lèi)型就是面向?qū)ο笾械念?lèi)( 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ù)類(lèi)型應(yīng)該被視為一個(gè)對(duì)象,這個(gè)對(duì)象擁有 namescore 這兩個(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)樵谧匀唤缰?,?lèi)(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)寫(xiě)形式對(duì)象字面量來(lái)創(chuàng)建:

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

對(duì)于上面的寫(xiě)法固然沒(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ò)上面的代碼我們不難看出,這樣寫(xiě)的代碼太過(guò)冗余,重復(fù)性太高。

簡(jiǎn)單方式的改進(jìn):工廠函數(shù)

我們可以寫(xiě)一個(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ì)象的類(lèi)型)。

構(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ù)名使用的是大寫(xiě)的 Person

而要?jiǎng)?chuàng)建 Person 實(shí)例,則必須使用 new 操作符。
以這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下 4 個(gè)步驟:

  1. 創(chuàng)建一個(gè)新對(duì)象
  2. 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象)
  3. 執(zhí)行構(gòu)造函數(shù)中的代碼
  4. 返回新對(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ì)象的具體類(lèi)型了。
在每一個(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ì)象類(lèi)型的,
但是,如果要檢測(cè)對(duì)象的類(lèi)型,還是使用 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ì)解釋為什么

構(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ì)象,typesayHello 都是一模一樣的內(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ì)象讀寫(xiě)原型對(duì)象中的成員
  • 原型對(duì)象的簡(jiǎn)寫(xiě)形式
  • 原生對(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)行效率。

<img src="./media/構(gòu)造函數(shù)-實(shí)例-原型之間的關(guān)系.png" alt="">

任何函數(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ì)象讀寫(xiě)原型對(duì)象成員

讀?。?/p>

  • 先在自己身上找,找到即返回
  • 自己身上找不到,則沿著原型鏈向上查找,找到即返回
  • 如果一直到原型鏈的末端還沒(méi)有找到,則返回 undefined

值類(lèi)型成員寫(xiě)入(實(shí)例對(duì)象.值類(lèi)型成員 = xx):

  • 當(dāng)實(shí)例期望重寫(xiě)原型對(duì)象中的某個(gè)普通數(shù)據(jù)成員時(shí)實(shí)際上會(huì)把該成員添加到自己身上
  • 也就是說(shuō)該行為實(shí)際上會(huì)屏蔽掉對(duì)原型對(duì)象成員的訪問(wèn)

引用類(lèi)型成員寫(xiě)入(實(shí)例對(duì)象.引用類(lèi)型成員 = xx):

  • 同上

復(fù)雜類(lèi)型修改(實(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)重寫(xiě)整個(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 的指向正確,建議的寫(xiě)法是:

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 的指向

案例:隨機(jī)方塊


面向?qū)ο笥螒虬咐贺澇陨?/h2>

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

案例介紹

游戲演示

在線演示地址:貪吃蛇

案例目標(biāo)

游戲的目的是用來(lái)體會(huì)js高級(jí)語(yǔ)法的使用 不需要具備抽象對(duì)象的能力,使用面向?qū)ο蟮姆绞椒治鰡?wèn)題,需要一個(gè)漫長(zhǎng)的過(guò)程。

功能實(shí)現(xiàn)

搭建頁(yè)面

放一個(gè)容器盛放游戲場(chǎng)景 div#map,設(shè)置樣式

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

分析對(duì)象

  • 游戲?qū)ο?/li>
  • 蛇對(duì)象
  • 食物對(duì)象

創(chuàng)建食物對(duì)象

  • Food

    • 屬性

      • x
      • y
      • width
      • height
      • color
    • 方法

      • render 隨機(jī)創(chuàng)建一個(gè)食物對(duì)象,并輸出到map上
  • 創(chuàng)建Food的構(gòu)造函數(shù),并設(shè)置屬性

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';
}
  • 通過(guò)原型設(shè)置render方法,實(shí)現(xiàn)隨機(jī)產(chǎn)生食物對(duì)象,并渲染到map上
Food.prototype.render = function (map) {
  // 隨機(jī)食物的位置,map.寬度/food.寬度,總共有多少分food的寬度,隨機(jī)一下。然后再乘以food的寬度
  this.x = parseInt(Math.random() * map.offsetWidth / this.width) * this.width;
  this.y = parseInt(Math.random() * map.offsetHeight / this.height) * this.height;

  // 動(dòng)態(tài)創(chuàng)建食物對(duì)應(yīng)的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);
}
  • 通過(guò)自調(diào)用函數(shù),進(jìn)行封裝,通過(guò)window暴露Food對(duì)象
window.Food = Food;

創(chuàng)建蛇對(duì)象

  • Snake

  • 屬性

    • width 蛇節(jié)的寬度 默認(rèn)20
    • height 蛇節(jié)的高度 默認(rèn)20
    • body 數(shù)組,蛇的頭部和身體,第一個(gè)位置是蛇頭
    • direction 蛇運(yùn)動(dòng)的方向 默認(rèn)right 可以是 left top bottom
  • 方法

    • render 把蛇渲染到map上
  • Snake構(gòu)造函數(shù)

var position = 'absolute';
var elements = [];
function Snake(width, height, direction) {
  // 設(shè)置每一個(gè)蛇節(jié)的寬度
  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';
  }
}
  • 在自調(diào)用函數(shù)中暴露Snake對(duì)象
window.Snake = Snake;

創(chuàng)建游戲?qū)ο?/h4>

游戲?qū)ο?,用?lái)管理游戲中的所有對(duì)象和開(kāi)始游戲

  • Game

    • 屬性

      • food

      • snake

      • map

    • 方法

      • start 開(kāi)始游戲(繪制所有游戲?qū)ο螅?/li>
  • 構(gòu)造函數(shù)
function Game(map) {
  this.food = new Food();
  this.snake = new Snake();
  this.map = map;
}
  • 開(kāi)始游戲,渲染食物對(duì)象和蛇對(duì)象
Game.prototype.start = function () {
  this.food.render(this.map);
  this.snake.render(this.map);
}

游戲的邏輯

寫(xiě)蛇的move方法

  • 在蛇對(duì)象(snake.js)中,在Snake的原型上新增move方法
  1. 讓蛇移動(dòng)起來(lái),把蛇身體的每一部分往前移動(dòng)一下
  2. 蛇頭部分根據(jù)不同的方向決定 往哪里移動(dòng)
Snake.prototype.move = function (food, map) {
  // 讓蛇身體的每一部分往前移動(dòng)一下
  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;
  }
  // 根據(jù)移動(dòng)的方向,決定蛇頭如何處理
  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中測(cè)試
this.snake.move(this.food, this.map);
this.snake.render(this.map);

讓蛇自己動(dòng)起來(lái)

  • 私有方法

    什么是私有方法?
      不能被外部訪問(wèn)的方法
    如何創(chuàng)建私有方法?
      使用自調(diào)用函數(shù)包裹
    
  • 在game.js中 添加runSnake的私有方法,開(kāi)啟定時(shí)器調(diào)用蛇的move和render方法,讓蛇動(dòng)起來(lái)

  • 判斷蛇是否撞墻

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中調(diào)用
function remove() {
  // 刪除渲染的蛇
  var i = elements.length - 1;
  for(; i >= 0; i--) {
    // 刪除頁(yè)面上渲染的蛇
    elements[i].parentNode.removeChild(elements[I]);
    // 刪除elements數(shù)組中的元素
    elements.splice(i, 1);
  }
}
  • 在game中通過(guò)鍵盤(pán)控制蛇的移動(dòng)方向
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方法中調(diào)用
bindKey();

判斷蛇是否吃到食物

// 在Snake的move方法中

// 在移動(dòng)的過(guò)程中判斷蛇是否吃到食物
// 如果蛇頭和食物的位置重合代表吃到食物
// 食物的坐標(biāo)是像素,蛇的坐標(biāo)是幾個(gè)寬度,進(jìn)行轉(zhuǎn)換
var headX = this.body[0].x * this.width;
var headY = this.body[0].y * this.height;
if (headX === food.x && headY === food.y) {
  // 吃到食物,往蛇節(jié)的最后加一節(jié)
  var last = this.body[this.body.length - 1];
  this.body.push({
    x: last.x,
    y: last.y,
    color: last.color
  })
  // 把現(xiàn)在的食物對(duì)象刪除,并重新隨機(jī)渲染一個(gè)食物對(duì)象
  food.render(map);
}

其它處理

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

避免html中出現(xiàn)js代碼

自調(diào)用函數(shù)的參數(shù)

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

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

將來(lái)代碼壓縮的時(shí)候,可以吧 function (window) 壓縮成 function (w)

  • 傳入undefined

在將來(lái)會(huì)看到別人寫(xiě)的代碼中會(huì)把undefined作為函數(shù)的參數(shù)(當(dāng)前案例沒(méi)有使用)
因?yàn)樵谟械睦习姹镜臑g覽器中 undefined可以被重新賦值,防止undefined 被重新賦值

整理代碼

現(xiàn)在的代碼結(jié)構(gòu)清晰,誰(shuí)出問(wèn)題就找到對(duì)應(yīng)的js文件即可。
通過(guò)自調(diào)用函數(shù),已經(jīng)防止了變量命名污染的問(wèn)題

但是,由于js文件數(shù)較多,需要在頁(yè)面上引用,會(huì)產(chǎn)生文件依賴的問(wèn)題(先引入那個(gè)js,再引入哪個(gè)js)
將來(lái)通過(guò)工具把js文件合并并壓縮?,F(xiàn)在手工合并js文件演示

  • 問(wèn)題1
// 如果存在多個(gè)自調(diào)用函數(shù)要用分號(hào)分割,否則語(yǔ)法錯(cuò)誤
// 下面代碼會(huì)報(bào)錯(cuò)
(function () {
}())

(function () {
}())
// 所以代碼規(guī)范中會(huì)建議在自調(diào)用函數(shù)之前加上分號(hào)
// 下面代碼沒(méi)有問(wèn)題
;(function () {
}())

;(function () {
}())
  • 問(wèn)題2
// 當(dāng)自調(diào)用函數(shù) 前面有函數(shù)聲明時(shí),會(huì)把自調(diào)用函數(shù)作為參數(shù)
// 所以建議自調(diào)用函數(shù)前,加上;
var a = function () {
  alert('11');
}
    
(function () {
  alert('22');
}())

繼承

什么是繼承

  • 現(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 = new 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 = new 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 = new 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á)式類(lèi)似于變量賦值
  • 函數(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 指向的基本整理,寫(xiě)代碼寫(xiě)多了自然而然就熟悉了。

函數(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 為我們專(zhuān)門(mén)提供了一些函數(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() 方法類(lèi)似,只有一個(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ù)組(或類(lèi)似數(shù)組的對(duì)象)提供的參數(shù)。

<p class="danger">
注意:該方法的作用和 call() 方法類(lèi)似,只有一個(gè)區(qū)別,就是 call() 方法接受的是若干個(gè)參數(shù)的列表,而 apply() 方法接受的是一個(gè)包含多個(gè)參數(shù)的數(shù)組。
</p>

語(yǔ)法:

fun.apply(thisArg, [argsArray])

參數(shù):

  • thisArg
  • argsArray

apply()call() 非常相似,不同之處在于提供參數(shù)的方式。
apply() 使用參數(shù)數(shù)組而不是一組參數(shù)列表。例如:

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

bind

bind() 函數(shù)會(huì)創(chuàng)建一個(gè)新函數(shù)(稱(chēng)為綁定函數(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ù)不能被重寫(xiě)。綁定函數(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è)位置可以傳遞
        1. 在 bind 的同時(shí),以參數(shù)列表的形式進(jìn)行傳遞
        1. 在調(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ù)的名稱(chēng)
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:判斷類(lèi)型

示例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ì)人類(lèi)神經(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á)式的作用

  1. 給定的字符串是否符合正則表達(dá)式的過(guò)濾邏輯(匹配)
  2. 可以通過(guò)正則表達(dá)式,從字符串中獲取我們想要的特定部分(提取)
  3. 強(qiáng)大的字符串替換能力(替換)

正則表達(dá)式的特點(diǎn)

  1. 靈活性、邏輯性和功能性非常的強(qiáng)
  2. 可以迅速地用極簡(jiǎn)單的方式達(dá)到字符串的復(fù)雜控制
  3. 對(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 忽略大小寫(xiě)
g 全局匹配
gi 全局匹配+忽略大小寫(xiě)

正則匹配

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

匹配正則表達(dá)式

// console.log(/./.test("除了回車(chē)換行以為的任意字符"));//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ī)則的名稱(chēng)獲取規(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ù)類(lèi)型之外,其他所有的都是對(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ù)組

  1. 擁有 length 屬性,其它屬性(索引)為非負(fù)整數(shù)(對(duì)象中的索引會(huì)被當(dāng)做字符串來(lái)處理,這里你可以當(dāng)做是個(gè)非負(fù)整數(shù)串來(lái)理解)
  2. 不具有數(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 的屬性值,類(lèi)型是 Object ,而數(shù)組類(lèi)型是 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)工具

以上是復(fù)制他人的內(nèi)容,僅供學(xué)習(xí)參考!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容