學習目標:
- 理解面向?qū)ο箝_發(fā)思想
- 掌握 JavaScript 面向?qū)ο箝_發(fā)相關(guān)模式
- 掌握在 JavaScript 中使用正則表達式
JavaScript 高級
課程介紹
課程大綱
目標
- 理解面向?qū)ο箝_發(fā)思想
- 掌握 JavaScript 面向?qū)ο箝_發(fā)相關(guān)模式
- 掌握在 JavaScript 中使用正則表達式
基本概念復(fù)習
由于 JavaScript 高級還是針對 JavaScript 語言本身的一個進階學習试幽,所以在開始之前我們先對以前所學過的 JavaScript 相關(guān)知識點做一個快速復(fù)習總結(jié)升熊。
JavaScript 的組成
組成部分 | 說明 |
---|---|
Ecmascript | 描述了該語言的語法和基本對象 |
DOM | 描述了處理網(wǎng)頁內(nèi)容的方法和接口 |
BOM | 描述了與瀏覽器進行交互的方法和接口 |
JavaScript 可以做什么
Any application that can be written in JavaScript, will eventually be written in JavaScript.
凡是能用 JavaScript 寫出來的,最終都會用 JavaScript 寫出來
小結(jié)
基本概念
本小節(jié)快速過即可似忧,主要是對學過的內(nèi)容做知識點梳理项钮。
- 語法
- 區(qū)分大小寫
- 標識符
- 注釋
- 嚴格模式
- 語句
- 關(guān)鍵字和保留字
- 變量
- 數(shù)據(jù)類型
- typeof 操作符
- Undefined
- Null
- Boolean
- Number
- String
- Object
- 操作符
- 流程控制語句
- 函數(shù)
JavaScript 中的數(shù)據(jù)類型
JavaScript 有 5 種簡單數(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
Math
類型檢測
typeof
instanceof
值類型與引用類型的差別
- 基本類型在內(nèi)存中占據(jù)固定大小的空間,因此被保存在棧內(nèi)存中
- 從一個變量向另一個變量復(fù)制基本類型的值定踱,復(fù)制的是值的副本
- 引用類型的值是對象棍潘,保存在堆內(nèi)存
- 包含引用類型值的變量實際上包含的并不是對象本身,而是一個指向該對象的指針
- 從一個變量向另一個變量復(fù)制引用類型的值的時候崖媚,復(fù)制是引用指針亦歉,因此兩個變量最終都指向同一個對象
JavaScript 執(zhí)行過程
JavaScript 運行分為兩個階段:
- 預(yù)解析
- 全局預(yù)解析(所有變量和函數(shù)聲明都會提前;同名的函數(shù)和變量函數(shù)的優(yōu)先級高)
- 函數(shù)內(nèi)部預(yù)解析(所有的變量畅哑、函數(shù)和形參都會參與預(yù)解析)
- 函數(shù)
- 形參
- 普通變量
- 執(zhí)行
先預(yù)解析全局作用域肴楷,然后執(zhí)行全局作用域中的代碼
JavaScript 面向?qū)ο缶幊?/h2>
面向?qū)ο蠼榻B
什么是面向?qū)ο?/h4>
面向?qū)ο蟛皇切碌臇|西,它只是過程式代碼的一種高度封裝荠呐,目的在于提高代碼的開發(fā)效率和可維護性赛蔫。
面向?qū)ο蟛皇切碌臇|西,它只是過程式代碼的一種高度封裝荠呐,目的在于提高代碼的開發(fā)效率和可維護性赛蔫。
面向?qū)ο缶幊?—— Object Oriented Programming,簡稱 OOP 泥张,是一種編程開發(fā)思想呵恢。
它將真實世界各種復(fù)雜的關(guān)系,抽象為一個個對象媚创,然后由對象之間的分工與合作渗钉,完成對真實世界的模擬。
在面向?qū)ο蟪绦蜷_發(fā)思想中钞钙,每一個對象都是功能中心鳄橘,具有明確分工,可以完成接受信息芒炼、處理數(shù)據(jù)瘫怜、發(fā)出信息等任務(wù)。
因此本刽,面向?qū)ο缶幊叹哂徐`活鲸湃、代碼可復(fù)用、高度模塊化等特點盅安,容易維護和開發(fā)唤锉,比起由一系列函數(shù)或指令組成的傳統(tǒng)的過程式編程(procedural programming)世囊,更適合多人合作的大型軟件項目别瞭。
面向?qū)ο蟮奶匦裕?/p>
- 封裝性
- 繼承性
- [多態(tài)性]
程序中面向?qū)ο蟮幕倔w現(xiàn)
在 JavaScript 中,所有數(shù)據(jù)類型都可以視為對象株憾,當然也可以自定義對象蝙寨。
自定義的對象數(shù)據(jù)類型就是面向?qū)ο笾械念悾?Class )的概念晒衩。
我們以一個例子來說明面向過程和面向?qū)ο笤诔绦蛄鞒躺系牟煌帯?/p>
假設(shè)我們要處理學生的成績表,為了表示一個學生的成績墙歪,面向過程的程序可以用一個對象表示:
var std1 = { name: 'Michael', score: 98 }
var std2 = { name: 'Bob', score: 81 }
而處理學生成績可以通過函數(shù)實現(xiàn)听系,比如打印學生的成績:
function printScore (student) {
console.log('姓名:' + student.name + ' ' + '成績:' + student.score)
}
如果采用面向?qū)ο蟮某绦蛟O(shè)計思想,我們首選思考的不是程序的執(zhí)行流程虹菲,
而是 Student
這種數(shù)據(jù)類型應(yīng)該被視為一個對象靠胜,這個對象擁有 name
和 score
這兩個屬性(Property)。
如果要打印一個學生的成績毕源,首先必須創(chuàng)建出這個學生對應(yīng)的對象浪漠,然后,給對象發(fā)一個 printScore
消息霎褐,讓對象自己把自己的數(shù)據(jù)打印出來址愿。
抽象數(shù)據(jù)行為模板(Class):
function Student (name, score) {
this.name = name
this.score = score
}
Student.prototype.printScore = function () {
console.log('姓名:' + this.name + ' ' + '成績:' + this.score)
}
根據(jù)模板創(chuàng)建具體實例對象(Instance):
var std1 = new Student('Michael', 98)
var std2 = new Student('Bob', 81)
實例對象具有自己的具體行為(給對象發(fā)消息):
std1.printScore() // => 姓名:Michael 成績:98
std2.printScore() // => 姓名:Bob 成績 81
面向?qū)ο蟮脑O(shè)計思想是:
- 抽象出 Class
- 根據(jù) Class 創(chuàng)建 Instance
- 指揮 Instance 得結(jié)果
創(chuàng)建對象
簡單方式
我們可以直接通過 new Object()
創(chuàng)建:
var person = new Object()
person.name = 'Jack'
person.age = 18
person.sayName = function () {
console.log(this.name)
}
每次創(chuàng)建通過 new Object()
比較麻煩,所以可以通過它的簡寫形式對象字面量來創(chuàng)建:
var person = {
name: 'Jack',
age: 18,
sayName: function () {
console.log(this.name)
}
}
簡單方式的改進:工廠函數(shù)
我們可以寫一個函數(shù)冻璃,解決代碼重復(fù)問題:
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)
這樣封裝確實爽多了响谓,通過工廠模式我們解決了創(chuàng)建多個相似對象代碼冗余的問題,
但卻沒有解決對象識別的問題(即怎樣知道一個對象的類型)省艳。
構(gòu)造函數(shù)
更優(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ù)和實例對象的關(guān)系
使用構(gòu)造函數(shù)的好處不僅僅在于代碼的簡潔性,更重要的是我們可以識別對象的具體類型了跋炕。
在每一個實例對象中的_proto_中同時有一個 constructor
屬性失驶,該屬性指向創(chuàng)建該實例的構(gòu)造函數(shù):
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
構(gòu)造函數(shù)的問題
使用構(gòu)造函數(shù)帶來的最大的好處就是創(chuàng)建對象更方便了,但是其本身也存在一個浪費內(nèi)存的問題:
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
都是一模一樣的內(nèi)容,
每一次生成一個實例德召,都必須為重復(fù)的內(nèi)容白魂,多占用一些內(nèi)存,如果實例對象很多上岗,會造成極大的內(nèi)存浪費福荸。
console.log(p1.sayHello === p2.sayHello) // => false
對于這種問題我們可以把需要共享的函數(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ù)的話就會造成全局命名空間沖突的問題肴掷。
原型
更好的解決方案: prototype
Javascript 規(guī)定敬锐,每一個構(gòu)造函數(shù)都有一個 prototype
屬性背传,指向另一個對象。
這個對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實例繼承。
這也就意味著壳坪,我們可以把所有對象實例需要共享的屬性和方法直接定義在 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
構(gòu)造函數(shù)、實例、原型三者之間的關(guān)系
任何函數(shù)都具有一個 prototype
屬性,該屬性是一個對象。
function F () {}
console.log(F.prototype) // => object
F.prototype.sayHi = function () {
console.log('hi!')
}
構(gòu)造函數(shù)的 prototype
對象默認都有一個 constructor
屬性丰泊,指向 prototype
對象所在函數(shù)。
console.log(F.constructor === F) // => true
通過構(gòu)造函數(shù)得到的實例對象內(nèi)部會包含一個指向構(gòu)造函數(shù)的 prototype
對象的指針 __proto__
始绍。
var instance = new F()
console.log(instance.__proto__ === F.prototype) // => true
<p class="tip">
__proto__
是非標準屬性瞳购。
</p>
實例對象可以直接訪問原型對象成員。
instance.sayHi() // => hi!
屬性成員的搜索原則:原型鏈
了解了 構(gòu)造函數(shù)-實例-原型對象 三者之間的關(guān)系后亏推,接下來我們來解釋一下為什么實例對象可以訪問原型對象中的成員学赛。
每當代碼讀取某個對象的某個屬性時,都會執(zhí)行一次搜索吞杭,目標是具有給定名字的屬性
- 搜索首先從對象實例本身開始
- 如果在實例中找到了具有給定名字的屬性盏浇,則返回該屬性的值
- 如果沒有找到,則繼續(xù)搜索指針指向的原型對象芽狗,在原型對象中查找具有給定名字的屬性
- 如果在原型對象中找到了這個屬性绢掰,則返回該屬性的值
原生對象的原型
<p class="tip">
所有函數(shù)都有 prototype 屬性對象。
</p>
- Object.prototype
- Function.prototype
- Array.prototype
- String.prototype
- Number.prototype
- Date.prototype
- ...
練習:為數(shù)組對象和字符串對象擴展原型方法童擎。
原型對象的問題
- 共享數(shù)組
- 共享對象
如果真的希望可以被實例對象之間共享和修改這些共享數(shù)據(jù)那就不是問題滴劲。但是如果不希望實例之間共享和修改這些共享數(shù)據(jù)則就是問題。
一個更好的建議是顾复,最好不要讓實例之間互相共享這些數(shù)組或者對象成員班挖,一旦修改的話會導致數(shù)據(jù)的走向很不明確而且難以維護。
原型對象使用建議
- 私有成員(一般就是非函數(shù)成員)放到構(gòu)造函數(shù)中
- 共享成員(一般就是函數(shù))放到原型對象中
面向?qū)ο笥螒虬咐贺澇陨?/h2>
案例介紹
案例目標
游戲的目的是用來體會js高級語法的使用 不需要具備抽象對象的能力芯砸,使用面向?qū)ο蟮姆绞椒治鰡栴}萧芙,需要一個漫長的過程。
功能實現(xiàn)
搭建頁面
放一個容器盛放游戲場景 div#map假丧,設(shè)置樣式
#map {
width: 800px;
height: 600px;
background-color: #ccc;
position: relative;
}
分析對象
- 游戲?qū)ο?/li>
- 蛇對象
- 食物對象
創(chuàng)建食物對象
-
Food
-
屬性
- x
- y
- width
- height
- color
-
方法
- render 隨機創(chuàng)建一個食物對象双揪,并輸出到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';
}
- 通過原型設(shè)置render方法包帚,實現(xiàn)隨機產(chǎn)生食物對象渔期,并渲染到map上
Food.prototype.render = function (map) {
// 隨機食物的位置,map.寬度/food.寬度婴噩,總共有多少分food的寬度擎场,隨機一下。然后再乘以food的寬度
this.x = parseInt(Math.random() * map.offsetWidth / this.width) * this.width;
this.y = parseInt(Math.random() * map.offsetHeight / this.height) * this.height;
// 動態(tài)創(chuàng)建食物對應(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);
}
- 通過自調(diào)用函數(shù)几莽,進行封裝迅办,通過window暴露Food對象
window.Food = Food;
創(chuàng)建蛇對象
Snake
-
屬性
- width 蛇節(jié)的寬度 默認20
- height 蛇節(jié)的高度 默認20
- body 數(shù)組,蛇的頭部和身體章蚣,第一個位置是蛇頭
- direction 蛇運動的方向 默認right 可以是 left top bottom
-
方法
- render 把蛇渲染到map上
Snake構(gòu)造函數(shù)
var position = 'absolute';
var elements = [];
function Snake(width, height, direction) {
// 設(shè)置每一個蛇節(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對象
window.Snake = Snake;
創(chuàng)建游戲?qū)ο?/h4>
游戲?qū)ο笳酒郏脕砉芾碛螒蛑械乃袑ο蠛烷_始游戲
-
Game
-
屬性
food
snake
map
-
方法
- start 開始游戲(繪制所有游戲?qū)ο螅?/li>
-
- 構(gòu)造函數(shù)
function Game(map) {
this.food = new Food();
this.snake = new Snake();
this.map = map;
}
- 開始游戲,渲染食物對象和蛇對象
Game.prototype.start = function () {
this.food.render(this.map);
this.snake.render(this.map);
}
游戲的邏輯
寫蛇的move方法
- 在蛇對象(snake.js)中纤垂,在Snake的原型上新增move方法
- 讓蛇移動起來矾策,把蛇身體的每一部分往前移動一下
- 蛇頭部分根據(jù)不同的方向決定 往哪里移動
Snake.prototype.move = function (food, map) {
// 讓蛇身體的每一部分往前移動一下
var i = this.body.length - 1;
for(; i > 0; i--) {
this.body[i].x = this.body[i - 1].x;
this.body[i].y = this.body[i - 1].y;
}
// 根據(jù)移動的方向,決定蛇頭如何處理
switch(this.direction) {
case 'left':
this.body[0].x -= 1;
break;
case 'right':
this.body[0].x += 1;
break;
case 'top':
this.body[0].y -= 1;
break;
case 'bottom':
this.body[0].y += 1;
break;
}
}
- 在game中測試
this.snake.move(this.food, this.map);
this.snake.render(this.map);
讓蛇自己動起來
-
私有方法
什么是私有方法峭沦? 不能被外部訪問的方法 如何創(chuàng)建私有方法贾虽? 使用自調(diào)用函數(shù)包裹
在game.js中 添加runSnake的私有方法,開啟定時器調(diào)用蛇的move和render方法吼鱼,讓蛇動起來
判斷蛇是否撞墻
function runSnake() {
var timerId = setInterval(function() {
this.snake.move(this.food, this.map);
// 在渲染前蓬豁,刪除之前的蛇
this.snake.render(this.map);
// 判斷蛇是否撞墻
var maxX = this.map.offsetWidth / this.snake.width;
var maxY = this.map.offsetHeight / this.snake.height;
var headX = this.snake.body[0].x;
var headY = this.snake.body[0].y;
if (headX < 0 || headX >= maxX) {
clearInterval(timerId);
alert('Game Over');
}
if (headY < 0 || headY >= maxY) {
clearInterval(timerId);
alert('Game Over');
}
}.bind(that), 150);
}
- 在snake中添加刪除蛇的私有方法,在render中調(diào)用
function remove() {
// 刪除渲染的蛇
var i = elements.length - 1;
for(; i >= 0; i--) {
// 刪除頁面上渲染的蛇
elements[i].parentNode.removeChild(elements[i]);
// 刪除elements數(shù)組中的元素
elements.splice(i, 1);
}
}
- 在game中通過鍵盤控制蛇的移動方向
function bindKey() {
document.addEventListener('keydown', function(e) {
switch (e.keyCode) {
case 37:
// left
this.snake.direction = 'left';
break;
case 38:
// top
this.snake.direction = 'top';
break;
case 39:
// right
this.snake.direction = 'right';
break;
case 40:
// bottom
this.snake.direction = 'bottom';
break;
}
}.bind(that), false);
}
- 在start方法中調(diào)用
bindKey();
判斷蛇是否吃到食物
// 在Snake的move方法中
// 在移動的過程中判斷蛇是否吃到食物
// 如果蛇頭和食物的位置重合代表吃到食物
// 食物的坐標是像素菇肃,蛇的坐標是幾個寬度地粪,進行轉(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)在的食物對象刪除琐谤,并重新隨機渲染一個食物對象
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對象
將來代碼壓縮的時候蟆技,可以吧 function (window) 壓縮成 function (w)
- 傳入undefined
在將來會看到別人寫的代碼中會把undefined作為函數(shù)的參數(shù)(當前案例沒有使用)
因為在有的老版本的瀏覽器中 undefined可以被重新賦值,防止undefined 被重新賦值
繼承
什么是繼承
- 現(xiàn)實生活中的繼承
- 程序中的繼承
構(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)
}
// 原型對象拷貝繼承原型對象成員
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)
}
// 利用原型的特性實現(xiàn)繼承
Student.prototype = new Person()
var s1 = Student('張三', 18)
console.log(s1.type) // => human
s1.sayName() // => hello 張三
函數(shù)進階
函數(shù)的定義方式
- 函數(shù)聲明
- 函數(shù)表達式
new Function
函數(shù)聲明
function foo () {
}
函數(shù)表達式
var foo = function () {
}
函數(shù)聲明與函數(shù)表達式的區(qū)別
- 函數(shù)聲明必須有名字
- 函數(shù)聲明會函數(shù)提升斗忌,在預(yù)解析階段就已創(chuàng)建质礼,聲明前后都可以調(diào)用
- 函數(shù)表達式類似于變量賦值
- 函數(shù)表達式可以沒有名字,例如匿名函數(shù)
- 函數(shù)表達式?jīng)]有變量提升织阳,在執(zhí)行階段創(chuàng)建几苍,必須在表達式執(zhí)行之后才可以調(diào)用
下面是一個根據(jù)條件定義函數(shù)的例子:
if (true) {
function f () {
console.log(1)
}
} else {
function f () {
console.log(2)
}
}
以上代碼執(zhí)行結(jié)果在不同瀏覽器中結(jié)果不一致。
不過我們可以使用函數(shù)表達式解決上面的問題:
var f
if (true) {
f = function () {
console.log(1)
}
} else {
f = function () {
console.log(2)
}
}
函數(shù)的調(diào)用方式
- 普通函數(shù)
- 構(gòu)造函數(shù)
- 對象方法
函數(shù)內(nèi) this
指向的不同場景
函數(shù)的調(diào)用方式?jīng)Q定了 this
指向的不同:
調(diào)用方式 | 非嚴格模式 | 備注 |
---|---|---|
普通函數(shù)調(diào)用 | window | 嚴格模式下是 undefined |
構(gòu)造函數(shù)調(diào)用 | 實例對象 | 原型方法中 this 也是實例對象 |
對象方法調(diào)用 | 該方法所屬對象 | 緊挨著的對象 |
事件綁定方法 | 綁定事件對象 | |
定時器函數(shù) | window |
這就是對函數(shù)內(nèi)部 this 指向的基本整理陈哑,寫代碼寫多了自然而然就熟悉了妻坝。
函數(shù)也是對象
- 所有函數(shù)都是
Function
的實例
call、apply惊窖、bind
那了解了函數(shù) this 指向的不同場景之后刽宪,我們知道有些情況下我們?yōu)榱耸褂媚撤N特定環(huán)境的 this 引用,
這時候時候我們就需要采用一些特殊手段來處理了界酒,例如我們經(jīng)常在定時器外部備份 this 引用圣拄,然后在定時器函數(shù)內(nèi)部使用外部 this 的引用。
然而實際上對于這種做法我們的 JavaScript 為我們專門提供了一些函數(shù)方法用來幫我們更優(yōu)雅的處理函數(shù)內(nèi)部 this 指向問題毁欣。
這就是接下來我們要學習的 call庇谆、apply岳掐、bind 三個函數(shù)方法。
call
call()
方法調(diào)用一個函數(shù), 其具有一個指定的 this
值和分別地提供的參數(shù)(參數(shù)的列表)饭耳。
<p class="danger">
注意:該方法的作用和 apply()
方法類似串述,只有一個區(qū)別,就是 call()
方法接受的是若干個參數(shù)的列表寞肖,而 apply()
方法接受的是一個包含多個參數(shù)的數(shù)組纲酗。
</p>
語法:
fun.call(thisArg[, arg1[, arg2[, ...]]])
參數(shù):
-
thisArg
- 在 fun 函數(shù)運行時指定的 this 值
- 如果指定了 null 或者 undefined 則內(nèi)部 this 指向 window
-
arg1, arg2, ...
- 指定的參數(shù)列表
apply
apply()
方法調(diào)用一個函數(shù), 其具有一個指定的 this
值,以及作為一個數(shù)組(或類似數(shù)組的對象)提供的參數(shù)新蟆。
<p class="danger">
注意:該方法的作用和 call()
方法類似觅赊,只有一個區(qū)別,就是 call()
方法接受的是若干個參數(shù)的列表琼稻,而 apply()
方法接受的是一個包含多個參數(shù)的數(shù)組吮螺。
</p>
語法:
fun.apply(thisArg, [argsArray])
參數(shù):
thisArg
argsArray
apply()
與 call()
非常相似,不同之處在于提供參數(shù)的方式帕翻。
apply()
使用參數(shù)數(shù)組而不是一組參數(shù)列表规脸。例如:
fun.apply(this, ['eat', 'bananas'])
bind
bind() 函數(shù)會創(chuàng)建一個新函數(shù)(稱為綁定函數(shù)),新函數(shù)與被調(diào)函數(shù)(綁定函數(shù)的目標函數(shù))具有相同的函數(shù)體(在 ECMAScript 5 規(guī)范中內(nèi)置的call屬性)熊咽。
當目標函數(shù)被調(diào)用時 this 值綁定到 bind() 的第一個參數(shù)莫鸭,該參數(shù)不能被重寫。綁定函數(shù)被調(diào)用時横殴,bind() 也接受預(yù)設(shè)的參數(shù)提供給原函數(shù)被因。
一個綁定函數(shù)也能使用new操作符創(chuàng)建對象:這種行為就像把原函數(shù)當成構(gòu)造器。提供的 this 值被忽略衫仑,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)梨与。
語法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
參數(shù):
-
thisArg
- 當綁定函數(shù)被調(diào)用時,該參數(shù)會作為原函數(shù)運行時的 this 指向文狱。當使用new 操作符調(diào)用綁定函數(shù)時粥鞋,該參數(shù)無效。
-
arg1, arg2, ...
- 當綁定函數(shù)被調(diào)用時瞄崇,這些參數(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)建一個新函數(shù)等浊,將"this"綁定到module對象
// 新手可能會被全局的x變量和module里的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81
示例2:
function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// Declare bloom after a delay of 1 second
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒鐘后, 調(diào)用'declare'方法
函數(shù)的其它成員
- arguments
- 實參集合
- caller
- 函數(shù)的調(diào)用者
- length
- 形參的個數(shù)
- name
- 函數(shù)的名稱
function fn(x, y, z) {
console.log(fn.length) // => 形參的個數(shù)
console.log(arguments) // 偽數(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ù)作用域
- 沒有塊級作用域
{
var foo = 'bar'
}
console.log(foo)
if (true) {
var a = 123
}
console.log(a)
作用域鏈示例代碼:
var a = 10
function fn () {
var b = 20
function fn1 () {
var c = 30
console.log(a + b + c)
}
function fn2 () {
var d = 40
console.log(c + d)
}
fn1()
fn2()
}
- 內(nèi)層作用域可以訪問外層作用域筹燕,反之不行
什么是閉包
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),
由于在 Javascript 語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量撒踪,
因此可以把閉包簡單理解成 “定義在一個函數(shù)內(nèi)部的函數(shù)”过咬。
所以,在本質(zhì)上制妄,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁掸绞。
閉包的用途:
- 可以在函數(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)
閉包的思考題
思考題 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()
計算階乘的遞歸函數(shù)
function factorial (num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1)
}
}
遞歸應(yīng)用場景
- 深拷貝
- 菜單樹
- 遍歷 DOM 樹
正則表達式
- 了解正則表達式基本語法
- 能夠使用JavaScript的正則對象
正則表達式簡介
什么是正則表達式
正則表達式:用于匹配規(guī)律規(guī)則的表達式,正則表達式最初是科學家對人類神經(jīng)系統(tǒng)的工作原理的早期研究忍捡,現(xiàn)在在編程語言中有廣泛的應(yīng)用集漾。正則表通常被用來檢索切黔、替換那些符合某個模式(規(guī)則)的文本砸脊。
正則表達式是對字符串操作的一種邏輯公式,就是用事先定義好的一些特定字符纬霞、及這些特定字符的組合凌埂,組成一個“規(guī)則字符串”,這個“規(guī)則字符串”用來表達對字符串的一種過濾邏輯诗芜。
正則表達式的作用
- 給定的字符串是否符合正則表達式的過濾邏輯(匹配)
- 可以通過正則表達式瞳抓,從字符串中獲取我們想要的特定部分(提取)
- 強大的字符串替換能力(替換)
正則表達式的特點
- 靈活性、邏輯性和功能性非常的強
- 可以迅速地用極簡單的方式達到字符串的復(fù)雜控制
- 對于剛接觸的人來說伏恐,比較晦澀難懂
正則表達式的測試
- 在線測試正則
- 工具中使用正則表達式
- sublime/vscode/word
- 演示替換所有的數(shù)字
正則表達式的組成
- 普通字符
- 特殊字符(元字符):正則表達式中有特殊意義的字符
示例演示:
-
\d
匹配數(shù)字 -
ab\d
匹配 ab1孩哑、ab2
元字符串
通過測試工具演示下面元字符的使用
常用元字符串
元字符 | 說明 |
---|---|
\d | 匹配數(shù)字 |
\D | 匹配任意非數(shù)字的字符 |
\w | 匹配字母或數(shù)字或下劃線 |
\W | 匹配任意不是字母,數(shù)字翠桦,下劃線 |
\s | 匹配任意的空白符 |
\S | 匹配任意不是空白符的字符 |
. | 匹配除換行符以外的任意單個字符 |
^ | 表示匹配行首的文本(以誰開始) |
$ | 表示匹配行尾的文本(以誰結(jié)束) |
限定符
限定符 | 說明 |
---|---|
* | 重復(fù)零次或更多次 |
+ | 重復(fù)一次或更多次 |
? | 重復(fù)零次或一次 |
{n} | 重復(fù)n次 |
{n,} | 重復(fù)n次或更多次 |
{n,m} | 重復(fù)n到m次 |
其它
[] 字符串用中括號括起來横蜒,表示匹配其中的任一字符,相當于或的意思
[^] 匹配除中括號以內(nèi)的內(nèi)容
\ 轉(zhuǎn)義符
| 或者销凑,選擇兩者中的一個丛晌。注意|將左右兩邊分為兩部分,而不管左右兩邊有多長多亂
() 從兩個直接量中選擇一個斗幼,分組
eg:gr(a|e)y匹配gray和grey
[\u4e00-\u9fa5] 匹配漢字
案例
驗證手機號:
^\d{11}$
驗證郵編:
^\d{6}$
驗證日期 2012-5-01
^\d{4}-\d{1,2}-\d{1,2}$
驗證郵箱 xxx@itcast.cn:
^\w+@\w+\.\w+$
驗證IP地址 192.168.1.10
^\d{1,3}\(.\d{1,3}){3}$
JavaScript 中使用正則表達式
創(chuàng)建正則對象
方式1:
var reg = new Regex('\d', 'i');
var reg = new Regex('\d', 'gi');
方式2:
var reg = /\d/i;
var reg = /\d/gi;
參數(shù)
標志 | 說明 |
---|---|
i | 忽略大小寫 |
g | 全局匹配 |
gi | 全局匹配+忽略大小寫 |
正則匹配
// 匹配日期
var dateStr = '2015-10-10';
var reg = /^\d{4}-\d{1,2}-\d{1,2}$/
console.log(reg.test(dateStr));
匹配正則表達式
// console.log(/./.test("除了回車換行以為的任意字符"));//true
// console.log(/.*/.test("0個到多個"));//true
// console.log(/.+/.test("1個到多個"));//true
// console.log(/.?/.test("哈哈"));//true
// console.log(/[0-9]/.test("9527"));//true
// console.log(/[a-z]/.test("what"));//true
// console.log(/[A-Z]/.test("Are"));//true
// console.log(/[a-zA-Z]/.test("干啥子"));//false
// console.log(/[0-9a-zA-Z]/.test("9ebg"));//true
// console.log(/b|(ara)/.test("abra"));//true
// console.log(/[a-z]{2,3}/.test("arfsf"));//true
console.log(/\d/.test("998"));//true
console.log(/\d*/.test("998"));//true
console.log(/\d+/.test("998"));//true
console.log(/\d{0,}/.test("998"));//true
console.log(/\d{2,3}/.test("998"));//true
console.log(/\D/.test("eat"));//true
console.log(/\s/.test(" "));//true
console.log(/\S/.test("嘎嘎"));//true
console.log(/\w/.test("_"));//true
console.log(/\W/.test("_"));//true
正則表達式案例
1.驗證密碼強弱
2.驗證郵箱:[0-9a-zA-Z_.-]+[@][0-9a-zA-Z._-]+([.][a-zA-Z]+){1,2}
3.驗證中文名字[\u4e00-\u9fa5]
正則提取
// 1. 提取工資
var str = "張三:1000澎蛛,李四:5000,王五:8000蜕窿。";
var array = str.match(/\d+/g);
console.log(array);
// 2. 提取email地址
var str = "123123@xx.com,fangfang@valuedopinions.cn 286669312@qq.com 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';
// 正則表達式中的()作為分組來使用,獲取分組匹配到的結(jié)果用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 () {
//獲取當前文本框?qū)?yīng)的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 () {
//獲取當前文本框?qū)?yīng)的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 = "";
}
};
表單驗證部分荧恍,封裝成函數(shù):
var regBirthday = /^\d{4}-\d{1,2}-\d{1,2}$/;
addCheck(txtBirthday, regBirthday, "請輸入正確的出生日期");
//給文本框添加驗證
function addCheck(element, reg, tip) {
element.onblur = function () {
//獲取當前文本框?qū)?yīng)的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>
// 所有的驗證規(guī)則
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 = "";
}
}
}
}
}
// 根據(jù)規(guī)則的名稱獲取規(guī)則對象
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;
}