面向?qū)ο?/h1>
- 首先,我們要明確,面向?qū)ο蟛皇钦Z法,是一個思想,是一種 編程模式
- 面向: 面(臉),向(朝著)
- 面向過程: 臉朝著過程 =》 關(guān)注著過程的編程模式
- 面向?qū)ο螅?臉朝著對象 =》 關(guān)注著對象的編程模式
- 實(shí)現(xiàn)一個效果
- 在面向過程的時候,我們要關(guān)注每一個元素,每一個元素之間的關(guān)系,順序,。。。
- 在面向過程的時候,我們要關(guān)注的就是找到一個對象來幫我做這個事情,我等待結(jié)果
- 例子 ??: 我要吃面條
- 面向過程
- 用多少面粉
- 用多少水
- 怎么和面
- 怎么切面條
- 做開水
- 煮面
- 吃面
- 面向?qū)ο?
- 找到一個面館
- 叫一碗面
- 等著吃
- 面向?qū)ο缶褪菍γ嫦蜻^程的封裝
- 我們以前的編程思想是,每一個功能,都按照需求一步一步的逐步完成
- 我們以后的編程思想是,每一個功能,都先創(chuàng)造一個 面館,這個 面館 能幫我們作出一個 面(完成這個功能的對象),然后用 面館 創(chuàng)造出一個 面,我們只要等到結(jié)果就好了
創(chuàng)建對象的方式
- 因?yàn)槊嫦驅(qū)ο缶褪且粋€找到對象的過程
- 所以我們先要了解如何創(chuàng)建一個對象
調(diào)用系統(tǒng)內(nèi)置的構(gòu)造函數(shù)創(chuàng)建對象
js 給我們內(nèi)置了一個 Object 構(gòu)造函數(shù)
這個構(gòu)造函數(shù)就是用來創(chuàng)造對象的
當(dāng) 構(gòu)造函數(shù) 和 new 關(guān)鍵字連用的時候,就可以為我們創(chuàng)造出一個對象
-
因?yàn)?js 是一個動態(tài)的語言,那么我們就可以動態(tài)的向?qū)ο笾刑砑映蓡T了
// 就能得到一個空對象
var o1 = new Object()
// 正常操作對象
o1.name = 'Jack'
o1.age = 18
o1.gender = '男'
字面量的方式創(chuàng)建一個對象
直接使用字面量的形式,也就是直接寫 {}
-
可以在寫的時候就添加好成員,也可以動態(tài)的添加
// 字面量方式創(chuàng)建對象
var o1 = {
name: 'Jack',
age: 18,
gender: '男'
}
// 再來一個
var o2 = {}
o2.name = 'Rose'
o2.age = 20
o2.gender = '女'
使用工廠函數(shù)的方式創(chuàng)建對象
先書寫一個工廠函數(shù)
這個工廠函數(shù)里面可以創(chuàng)造出一個對象,并且給對象添加一些屬性,還能把對象返回
-
使用這個工廠函數(shù)創(chuàng)造對象
// 1. 先創(chuàng)建一個工廠函數(shù)
function createObj() {
// 手動創(chuàng)建一個對象
var obj = new Object()
// 手動的向?qū)ο笾刑砑映蓡T
obj.name = 'Jack'
obj.age = 18
obj.gender = '男'
// 手動返回一個對象
return obj
}
// 2. 使用這個工廠函數(shù)創(chuàng)建對象
var o1 = createObj()
var o2 = createObj()
使用自定義構(gòu)造函數(shù)創(chuàng)建對象
-
工廠函數(shù)需要經(jīng)歷三個步驟
- 手動創(chuàng)建對象
- 手動添加成員
- 手動返回對象
-
構(gòu)造函數(shù)會比工廠函數(shù)簡單一下
- 自動創(chuàng)建對象
- 手動添加成員
- 自動返回對象
先書寫一個構(gòu)造函數(shù)
在構(gòu)造函數(shù)內(nèi)向?qū)ο筇砑右恍┏蓡T
使用這個構(gòu)造函數(shù)創(chuàng)造一個對象(和 new 連用)
構(gòu)造函數(shù)可以創(chuàng)建對象,并且創(chuàng)建一個帶有屬性和方法的對象
面向?qū)ο缶褪且朕k法找到一個有屬性和方法的對象
-
面向?qū)ο缶褪俏覀冏约褐圃?構(gòu)造函數(shù) 的過程
// 1. 先創(chuàng)造一個構(gòu)造函數(shù)
function Person(name, gender) {
this.age = 18
this.name = name
this.gender = gender
}
// 2. 使用構(gòu)造函數(shù)創(chuàng)建對象
var p1 = new Person('Jack', 'man')
var p2 = new Person('Rose', 'woman')
構(gòu)造函數(shù)詳解
- 我們了解了對象的創(chuàng)建方式
- 我們的面向?qū)ο缶褪且茨苤苯拥玫揭粋€對象
- 要么就弄出一個能創(chuàng)造對象的東西,我們自己創(chuàng)造對象
- 我們的構(gòu)造函數(shù)就能創(chuàng)造對象,所以接下來我們就詳細(xì)聊聊 構(gòu)造函數(shù)
構(gòu)造函數(shù)的基本使用
-
和普通函數(shù)一樣,只不過 調(diào)用的時候要和 new 連用,不然就是一個普通函數(shù)調(diào)用
function Person() {}
var o1 = new Person() // 能得到一個空對象
var o2 = Person() // 什么也得不到,這個就是普通函數(shù)調(diào)用
- 注意: 不寫 new 的時候就是普通函數(shù)調(diào)用,沒有創(chuàng)造對象的能力
-
首字母大寫
function person() {}
var o1 = new person() // 能得到一個對象
function Person() {}
var o2 = new Person() // 能得到一個對象
- 注意: 首字母不大寫,只要和 new 連用,就有創(chuàng)造對象的能力
-
當(dāng)調(diào)用的時候如果不需要傳遞參數(shù)可以不寫 (),建議都寫上
function Person() {}
var o1 = new Person() // 能得到一個空對象
var o2 = new Person // 能得到一個空對象
- 注意: 如果不需要傳遞參數(shù),那么可以不寫 (),如果傳遞參數(shù)就必須寫
-
構(gòu)造函數(shù)內(nèi)部的 this,由于和 new 連用的關(guān)系,是指向當(dāng)前實(shí)例對象的
function Person() {
console.log(this)
}
var o1 = new Person() // 本次調(diào)用的時候,this => o1
var o2 = new Person() // 本次調(diào)用的時候,this => o2
- 注意: 每次 new 的時候,函數(shù)內(nèi)部的 this 都是指向當(dāng)前這次的實(shí)例化對象
-
因?yàn)闃?gòu)造函數(shù)會自動返回一個對象,所以構(gòu)造函數(shù)內(nèi)部不要寫 return
- 你如果 return 一個基本數(shù)據(jù)類型,那么寫了沒有意義
- 如果你 return 一個引用數(shù)據(jù)類型,那么構(gòu)造函數(shù)本身的意義就沒有了
使用構(gòu)造函數(shù)創(chuàng)建一個對象
-
我們在使用構(gòu)造函數(shù)的時候,可以通過一些代碼和內(nèi)容來向當(dāng)前的對象中添加一些內(nèi)容
function Person() {
this.name = 'Jack'
this.age = 18
}
var o1 = new Person()
var o2 = new Person()
- 我們得到的兩個對象里面都有自己的成員 name 和 age
-
我們在寫構(gòu)造函數(shù)的時候,是不是也可以添加一些方法進(jìn)去呢?
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
var o1 = new Person()
var o2 = new Person()
- 顯然是可以的,我們得到的兩個對象中都有
sayHi 這個函數(shù)
- 也都可以正常調(diào)用
-
但是這樣好不好呢?缺點(diǎn)在哪里?
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
// 第一次 new 的時候, Person 這個函數(shù)要執(zhí)行一遍
// 執(zhí)行一遍就會創(chuàng)造一個新的函數(shù),并且把函數(shù)地址賦值給 this.sayHi
var o1 = new Person()
// 第二次 new 的時候, Person 這個函數(shù)要執(zhí)行一遍
// 執(zhí)行一遍就會創(chuàng)造一個新的函數(shù),并且把函數(shù)地址賦值給 this.sayHi
var o2 = new Person()
- 這樣的話,那么我們兩個對象內(nèi)的
sayHi 函數(shù)就是一個代碼一模一樣,功能一模一樣
- 但是是兩個空間函數(shù),占用兩個內(nèi)存空間
- 也就是說
o1.sayHi 是一個地址,o2.sayHi 是一個地址
- 所以我們執(zhí)行
console.log(o1.sayhi === o2.sayHi) 的到的結(jié)果是 false
- 缺點(diǎn): 一模一樣的函數(shù)出現(xiàn)了兩次,占用了兩個空間地址
-
怎么解決這個問題呢?
- 就需要用到一個東西,叫做 原型,后面講
es6新增的類和繼承
- 在面向過程的時候,我們要關(guān)注每一個元素,每一個元素之間的關(guān)系,順序,。。。
- 在面向過程的時候,我們要關(guān)注的就是找到一個對象來幫我做這個事情,我等待結(jié)果
- 面向過程
- 用多少面粉
- 用多少水
- 怎么和面
- 怎么切面條
- 做開水
- 煮面
- 吃面
- 面向?qū)ο?
- 找到一個面館
- 叫一碗面
- 等著吃
- 面向?qū)ο缶褪菍γ嫦蜻^程的封裝
js 給我們內(nèi)置了一個 Object 構(gòu)造函數(shù)
這個構(gòu)造函數(shù)就是用來創(chuàng)造對象的
當(dāng) 構(gòu)造函數(shù) 和 new 關(guān)鍵字連用的時候,就可以為我們創(chuàng)造出一個對象
因?yàn)?js 是一個動態(tài)的語言,那么我們就可以動態(tài)的向?qū)ο笾刑砑映蓡T了
// 就能得到一個空對象
var o1 = new Object()
// 正常操作對象
o1.name = 'Jack'
o1.age = 18
o1.gender = '男'
直接使用字面量的形式,也就是直接寫 {}
可以在寫的時候就添加好成員,也可以動態(tài)的添加
// 字面量方式創(chuàng)建對象
var o1 = {
name: 'Jack',
age: 18,
gender: '男'
}
// 再來一個
var o2 = {}
o2.name = 'Rose'
o2.age = 20
o2.gender = '女'
先書寫一個工廠函數(shù)
這個工廠函數(shù)里面可以創(chuàng)造出一個對象,并且給對象添加一些屬性,還能把對象返回
使用這個工廠函數(shù)創(chuàng)造對象
// 1. 先創(chuàng)建一個工廠函數(shù)
function createObj() {
// 手動創(chuàng)建一個對象
var obj = new Object()
// 手動的向?qū)ο笾刑砑映蓡T
obj.name = 'Jack'
obj.age = 18
obj.gender = '男'
// 手動返回一個對象
return obj
}
// 2. 使用這個工廠函數(shù)創(chuàng)建對象
var o1 = createObj()
var o2 = createObj()
工廠函數(shù)需要經(jīng)歷三個步驟
- 手動創(chuàng)建對象
- 手動添加成員
- 手動返回對象
構(gòu)造函數(shù)會比工廠函數(shù)簡單一下
- 自動創(chuàng)建對象
- 手動添加成員
- 自動返回對象
先書寫一個構(gòu)造函數(shù)
在構(gòu)造函數(shù)內(nèi)向?qū)ο筇砑右恍┏蓡T
使用這個構(gòu)造函數(shù)創(chuàng)造一個對象(和 new 連用)
構(gòu)造函數(shù)可以創(chuàng)建對象,并且創(chuàng)建一個帶有屬性和方法的對象
面向?qū)ο缶褪且朕k法找到一個有屬性和方法的對象
面向?qū)ο缶褪俏覀冏约褐圃?構(gòu)造函數(shù) 的過程
// 1. 先創(chuàng)造一個構(gòu)造函數(shù)
function Person(name, gender) {
this.age = 18
this.name = name
this.gender = gender
}
// 2. 使用構(gòu)造函數(shù)創(chuàng)建對象
var p1 = new Person('Jack', 'man')
var p2 = new Person('Rose', 'woman')
和普通函數(shù)一樣,只不過 調(diào)用的時候要和 new 連用,不然就是一個普通函數(shù)調(diào)用
function Person() {}
var o1 = new Person() // 能得到一個空對象
var o2 = Person() // 什么也得不到,這個就是普通函數(shù)調(diào)用
- 注意: 不寫 new 的時候就是普通函數(shù)調(diào)用,沒有創(chuàng)造對象的能力
首字母大寫
function person() {}
var o1 = new person() // 能得到一個對象
function Person() {}
var o2 = new Person() // 能得到一個對象
- 注意: 首字母不大寫,只要和 new 連用,就有創(chuàng)造對象的能力
當(dāng)調(diào)用的時候如果不需要傳遞參數(shù)可以不寫 (),建議都寫上
function Person() {}
var o1 = new Person() // 能得到一個空對象
var o2 = new Person // 能得到一個空對象
- 注意: 如果不需要傳遞參數(shù),那么可以不寫 (),如果傳遞參數(shù)就必須寫
構(gòu)造函數(shù)內(nèi)部的 this,由于和 new 連用的關(guān)系,是指向當(dāng)前實(shí)例對象的
function Person() {
console.log(this)
}
var o1 = new Person() // 本次調(diào)用的時候,this => o1
var o2 = new Person() // 本次調(diào)用的時候,this => o2
- 注意: 每次 new 的時候,函數(shù)內(nèi)部的 this 都是指向當(dāng)前這次的實(shí)例化對象
因?yàn)闃?gòu)造函數(shù)會自動返回一個對象,所以構(gòu)造函數(shù)內(nèi)部不要寫 return
- 你如果 return 一個基本數(shù)據(jù)類型,那么寫了沒有意義
- 如果你 return 一個引用數(shù)據(jù)類型,那么構(gòu)造函數(shù)本身的意義就沒有了
我們在使用構(gòu)造函數(shù)的時候,可以通過一些代碼和內(nèi)容來向當(dāng)前的對象中添加一些內(nèi)容
function Person() {
this.name = 'Jack'
this.age = 18
}
var o1 = new Person()
var o2 = new Person()
- 我們得到的兩個對象里面都有自己的成員 name 和 age
我們在寫構(gòu)造函數(shù)的時候,是不是也可以添加一些方法進(jìn)去呢?
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
var o1 = new Person()
var o2 = new Person()
- 顯然是可以的,我們得到的兩個對象中都有
sayHi這個函數(shù) - 也都可以正常調(diào)用
但是這樣好不好呢?缺點(diǎn)在哪里?
function Person() {
this.name = 'Jack'
this.age = 18
this.sayHi = function () {
console.log('hello constructor')
}
}
// 第一次 new 的時候, Person 這個函數(shù)要執(zhí)行一遍
// 執(zhí)行一遍就會創(chuàng)造一個新的函數(shù),并且把函數(shù)地址賦值給 this.sayHi
var o1 = new Person()
// 第二次 new 的時候, Person 這個函數(shù)要執(zhí)行一遍
// 執(zhí)行一遍就會創(chuàng)造一個新的函數(shù),并且把函數(shù)地址賦值給 this.sayHi
var o2 = new Person()
- 這樣的話,那么我們兩個對象內(nèi)的
sayHi函數(shù)就是一個代碼一模一樣,功能一模一樣 - 但是是兩個空間函數(shù),占用兩個內(nèi)存空間
- 也就是說
o1.sayHi是一個地址,o2.sayHi是一個地址 - 所以我們執(zhí)行
console.log(o1.sayhi === o2.sayHi)的到的結(jié)果是false - 缺點(diǎn): 一模一樣的函數(shù)出現(xiàn)了兩次,占用了兩個空間地址
怎么解決這個問題呢?
- 就需要用到一個東西,叫做 原型,后面講
class的寫法及繼承JavaScript 語言中,生成實(shí)例對象的傳統(tǒng)方法是通過構(gòu)造函數(shù)。下面是一個例子
function Point(x, y) {
this.x = x;
this.y = y;}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';};
var p = new Point(1, 2);
上面這種寫法跟傳統(tǒng)的面向?qū)ο笳Z言(比如 C++ 和 Java)差異很大,很容易讓新學(xué)習(xí)這門語言的程序員感到困惑。
定義類
ES6 提供了更接近傳統(tǒng)語言的寫法,引入了 Class(類)這個概念,作為對象的模板。通過class關(guān)鍵字,可以定義類。
基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向?qū)ο缶幊痰恼Z法而已。上面的代碼用 ES6 的class改寫,就是下面這樣。
//定義類
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}}
上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構(gòu)造方法,而this關(guān)鍵字則代表實(shí)例對象。也就是說,ES5 的構(gòu)造函數(shù)Point,對應(yīng) ES6 的Point類的構(gòu)造方法
point類除了構(gòu)造方法,還定義了一個toString方法。注意,定義“類”的方法的時候,前面不需要加上function這個關(guān)鍵字,直接把函數(shù)定義放進(jìn)去了就可以了。另外,方法之間不需要逗號分隔,加了會報錯。
//hasOwnProperty 可以用來判斷對象是否有每一個屬性
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
ES6 的類,完全可以看作構(gòu)造函數(shù)的另一種寫法
class Point {// ...}
typeof Point // "function"上面代碼表明,類的數(shù)據(jù)類型就是函數(shù),類本身就指向構(gòu)造函數(shù)
let methodName = 'getArea';
class Square {
//構(gòu)造函數(shù)
constructor(length) {
// ...
}
//實(shí)例方法 對象方法
[methodName]() {
// ...
}}
//實(shí)例方法 對象方法
wang(){
}
上面代碼中,Square類的方法名getArea,是從表達(dá)式得到的。
類內(nèi)部,默認(rèn)就是嚴(yán)格模式,所以不需要使用use strict指定運(yùn)行模式。只要你的代碼寫在類或模塊之中,就只有嚴(yán)格模式可用。
考慮到未來所有的代碼,其實(shí)都是運(yùn)行在模塊之中,所以 ES6 實(shí)際上把整個語言升級到了嚴(yán)格模式。
constructor 方法``
constructor方法是類的默認(rèn)方法,通過new命令生成對象實(shí)例時,自動調(diào)用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認(rèn)添加。
class Point {}// 等同于class Point { constructor() {}}上面代碼中,定義了一個空的類Point,JavaScript 引擎會自動為它添加一個空的constructor方法。
constructor方法默認(rèn)返回實(shí)例對象(即this)
類必須使用new調(diào)用,否則會報錯。這是它跟普通構(gòu)造函數(shù)的一個主要區(qū)別,后者不用new也可以執(zhí)行。
class Foo { constructor() { }}
Foo()// TypeError: Class constructor Foo cannot be invoked without 'new'
生成類的實(shí)例對象的寫法,與 ES5 完全一樣,也是使用new命令。前面說過,如果忘記加上new,像函數(shù)那樣調(diào)用Class,將會報錯。
class Point {// ...}// 報錯var point = Point(2, 3);// 正確var point = new Point(2, 3);
類不存在變量提升(hoist),這一點(diǎn)與 ES5 完全不同。
new Foo(); // ReferenceErrorclass Foo {}
類方法
加上static關(guān)鍵字,就表示該方法不會被實(shí)例繼承,而是直接通過類來調(diào)用,這就稱為“靜態(tài)方法”。class Foo { static classMethod() { return 'hello'; }}
Foo.classMethod() // 'hello'var foo = new Foo();foo.classMethod()// TypeError: foo.classMethod is not a function
上面代碼中,F(xiàn)oo類的classMethod方法前有static關(guān)鍵字,表明該方法是一個靜態(tài)方法,可以直接在Foo類上調(diào)用(Foo.classMethod()),而不是在Foo類的實(shí)例上調(diào)用。如果在實(shí)例上調(diào)用靜態(tài)方法,會拋出一個錯誤,表示不存在該方法。
繼承
類的繼承Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承 這比 ES5 的通過修改原型鏈(在后面章節(jié)會講解)實(shí)現(xiàn)繼承,要清晰和方便很多。
class Point {}
class ColorPoint extends Point {}
上面代碼定義了一個ColorPoint類,該類通過extends關(guān)鍵字,繼承了Point類的所有屬性和方法。但是由于沒有部署任何代碼,所以這兩個類完全一樣,等于復(fù)制了一個Point類。下面,我們在ColorPoint內(nèi)部加上代碼。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 調(diào)用父類的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 調(diào)用父類的toString()
}}
上面代碼中,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字,它在這里表示父類的構(gòu)造函數(shù),用來新建父類的this對象。
ES6 要求,子類的構(gòu)造函數(shù)必須執(zhí)行一次super函數(shù)
子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時會報錯。這是因?yàn)樽宇愖约旱?code>this對象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實(shí)例屬性和方法,然后再對其進(jìn)行加工,加上子類自己的實(shí)例屬性和方法。如果不調(diào)用super方法,子類就得不到this對象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}}
let cp = new ColorPoint(); // ReferenceEr
上面代碼中,ColorPoint繼承了父類Point,但是它的構(gòu)造函數(shù)沒有調(diào)用super方法,導(dǎo)致新建實(shí)例時報錯。
需要注意的地方是,在子類的構(gòu)造函數(shù)中,只有調(diào)用super之后,才可以使用this關(guān)鍵字,否則會報錯。這是因?yàn)樽宇悓?shí)例的構(gòu)建,是基于對父類實(shí)例加工,只有super方法才能返回父類實(shí)例
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正確
}}
上面代碼中,子類的constructor方法沒有調(diào)用super之前,就使用this關(guān)鍵字,結(jié)果報錯,而放在super方法之后就是正確的。
總結(jié)
-
到了這里,我們就發(fā)現(xiàn)了面向?qū)ο蟮乃枷肽J搅?/p>
- 當(dāng)我想完成一個功能的時候
- 先看看內(nèi)置構(gòu)造函數(shù)有沒有能給我提供一個完成功能對象的能力
- 如果沒有,我們就自己寫一個構(gòu)造函數(shù),能創(chuàng)造出一個完成功能的對象
- 然后在用我們寫的構(gòu)造函數(shù) new 一個對象出來,幫助我們完成功能就行了
-
比如: tab選項(xiàng)卡
- 我們要一個對象
- 對象包含一個屬性:是每一個點(diǎn)擊的按鈕
- 對象包含一個屬性:是每一個切換的盒子
- 對象包含一個方法:是點(diǎn)擊按鈕能切換盒子的能力
- 那么我們就需要自己寫一個構(gòu)造函數(shù),要求 new 出來的對象有這些內(nèi)容就好了
- 然后在用構(gòu)造函數(shù) new 一個對象就行了