摘要:不區(qū)分類和實例的概念,而是通過原型來實現(xiàn)面向?qū)ο缶幊獭P聞?chuàng)建的的原型鏈?zhǔn)且簿褪钦f,的原型指向函數(shù)的原型。最后,創(chuàng)建一個對象代碼和前面章節(jié)完全一樣小明繼承用定義對象的另一個巨大的好處是繼承更方便了。
JavaScript不區(qū)分類和實例的概念,而是通過原型(prototype)來實現(xiàn)面向?qū)ο缶幊獭?/p>
原型是指當(dāng)我們想要創(chuàng)建xiaoming這個具體的學(xué)生時,我們并沒有一個Student類型可用。那怎么辦?恰好有這么一個現(xiàn)成的對象:
var robot = { name: "Robot", height: 1.6, run: function () { console.log(this.name + " is running..."); } };
我們看這個robot對象有名字,有身高,還會跑,有點像小明,干脆就根據(jù)它來“創(chuàng)建”小明得了!
于是我們把它改名為Student,然后創(chuàng)建出xiaoming:
var Student = { name: "Robot", height: 1.2, run: function () { console.log(this.name + " is running..."); } }; var xiaoming = { name: "小明" }; xiaoming.__proto__ = Student;
注意最后一行代碼把xiaoming的原型指向了對象Student,看上去xiaoming仿佛是從Student繼承下來的:
xiaoming.name; // "小明" xiaoming.run(); // 小明 is running...
xiaoming有自己的name屬性,但并沒有定義run()方法。不過,由于小明是從Student繼承而來,只要Student有run()方法,xiaoming也可以調(diào)用:
JavaScript的原型鏈和Java的Class區(qū)別就在,它沒有“Class”的概念,所有對象都是實例,所謂繼承關(guān)系不過是把一個對象的原型指向另一個對象而已。
如果你把xiaoming的原型指向其他對象:
var Bird = { fly: function () { console.log(this.name + " is flying..."); } }; xiaoming.__proto__ = Bird;
現(xiàn)在xiaoming已經(jīng)無法run()了,他已經(jīng)變成了一只鳥:
xiaoming.fly(); // 小明 is flying...
在JavaScrip代碼運行時期,你可以把xiaoming從Student變成Bird,或者變成任何對象。
請注意,上述代碼僅用于演示目的。在編寫JavaScript代碼時,不要直接用obj.__proto__去改變一個對象的原型,并且,低版本的IE也無法使用__proto__。Object.create()方法可以傳入一個原型對象,并創(chuàng)建一個基于該原型的新對象,但是新對象什么屬性都沒有,因此,我們可以編寫一個函數(shù)來創(chuàng)建xiaoming:
// 原型對象: var Student = { name: "Robot", height: 1.2, run: function () { console.log(this.name + " is running..."); } }; function createStudent(name) { // 基于Student原型創(chuàng)建一個新對象: var s = Object.create(Student); // 初始化新對象: s.name = name; return s; } var xiaoming = createStudent("小明"); xiaoming.run(); // 小明 is running... xiaoming.__proto__ === Student; // true創(chuàng)建對象
JavaScript對每個創(chuàng)建的對象都會設(shè)置一個原型,指向它的原型對象。
當(dāng)我們用obj.xxx訪問一個對象的屬性時,JavaScript引擎先在當(dāng)前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到Object.prototype對象,最后,如果還沒有找到,就只能返回undefined。
例如,創(chuàng)建一個Array對象:
var arr = [1, 2, 3];
其原型鏈?zhǔn)牵?/p>
arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype定義了indexOf()、shift()等方法,因此你可以在所有的Array對象上直接調(diào)用這些方法。
當(dāng)我們創(chuàng)建一個函數(shù)時:
function foo() { return 0; }
函數(shù)也是一個對象,它的原型鏈?zhǔn)牵?/p>
foo ----> Function.prototype ----> Object.prototype ----> null
由于Function.prototype定義了apply()等方法,因此,所有函數(shù)都可以調(diào)用apply()方法。
很容易想到,如果原型鏈很長,那么訪問一個對象的屬性就會因為花更多的時間查找而變得更慢,因此要注意不要把原型鏈搞得太長。
構(gòu)造函數(shù)除了直接用{ ... }創(chuàng)建一個對象外,JavaScript還可以用一種構(gòu)造函數(shù)的方法來創(chuàng)建對象。它的用法是,先定義一個構(gòu)造函數(shù):
function Student(name) { this.name = name; this.hello = function () { alert("Hello, " + this.name + "!"); } }
這確實是一個普通函數(shù),但是在JavaScript中,可以用關(guān)鍵字new來調(diào)用這個函數(shù),并返回一個對象:
var xiaoming = new Student("小明"); xiaoming.name; // "小明" xiaoming.hello(); // Hello, 小明!
注意,如果不寫new,這就是一個普通函數(shù),它返回undefined。但是,如果寫了new,它就變成了一個構(gòu)造函數(shù),它綁定的this指向新創(chuàng)建的對象,并默認(rèn)返回this,也就是說,不需要在最后寫return this;。
新創(chuàng)建的xiaoming的原型鏈?zhǔn)牵?/p>
xiaoming ----> Student.prototype ----> Object.prototype ----> null
也就是說,xiaoming的原型指向函數(shù)Student的原型。如果你又創(chuàng)建了xiaohong、xiaojun,那么這些對象的原型與xiaoming是一樣的:
xiaoming ↘ xiaohong -→ Student.prototype ----> Object.prototype ----> null xiaojun ↗
用new Student()創(chuàng)建的對象還從原型上獲得了一個constructor屬性,它指向函數(shù)Student本身:
xiaoming.constructor === Student.prototype.constructor; // true Student.prototype.constructor === Student; // true Object.getPrototypeOf(xiaoming) === Student.prototype; // true xiaoming instanceof Student; // true
看暈了吧?用一張圖來表示這些亂七八糟的關(guān)系就是:
紅色箭頭是原型鏈。注意,Student.prototype指向的對象就是xiaoming、xiaohong的原型對象,這個原型對象自己還有個屬性constructor,指向Student函數(shù)本身。
另外,函數(shù)Student恰好有個屬性prototype指向xiaoming、xiaohong的原型對象,但是xiaoming、xiaohong這些對象可沒有prototype這個屬性,不過可以用__proto__這個非標(biāo)準(zhǔn)用法來查看。
現(xiàn)在我們就認(rèn)為xiaoming、xiaohong這些對象“繼承”自Student。
不過還有一個小問題,注意觀察:
xiaoming.name; // "小明" xiaohong.name; // "小紅" xiaoming.hello; // function: Student.hello() xiaohong.hello; // function: Student.hello() xiaoming.hello === xiaohong.hello; // false
xiaoming和xiaohong各自的name不同,這是對的,否則我們無法區(qū)分誰是誰了。
xiaoming和xiaohong各自的hello是一個函數(shù),但它們是兩個不同的函數(shù),雖然函數(shù)名稱和代碼都是相同的!
如果我們通過new Student()創(chuàng)建了很多對象,這些對象的hello函數(shù)實際上只需要共享同一個函數(shù)就可以了,這樣可以節(jié)省很多內(nèi)存。
要讓創(chuàng)建的對象共享一個hello函數(shù),根據(jù)對象的屬性查找原則,我們只要把hello函數(shù)移動到xiaoming、xiaohong這些對象共同的原型上就可以了,也就是Student.prototype:
修改代碼如下:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); };
用new創(chuàng)建基于原型的JavaScript的對象就是這么簡單!
忘記寫new怎么辦
如果一個函數(shù)被定義為用于創(chuàng)建對象的構(gòu)造函數(shù),但是調(diào)用時忘記了寫new怎么辦?
在strict模式下,this.name =name將報錯,因為this綁定為undefined,在非strict模式下,this.name =name不報錯,因為this綁定為window,于是無意間創(chuàng)建了全局變量name,并且返回undefined,這個結(jié)果更糟糕。
所以,調(diào)用構(gòu)造函數(shù)千萬不要忘記寫new。為了區(qū)分普通函數(shù)和構(gòu)造函數(shù),按照約定,構(gòu)造函數(shù)首字母應(yīng)當(dāng)大寫,而普通函數(shù)首字母應(yīng)當(dāng)小寫,這樣,一些語法檢查工具如jslint將可以幫你檢測到漏寫的new。
最后,我們還可以編寫一個createStudent()函數(shù),在內(nèi)部封裝所有的new操作。一個常用的編程模式像這樣:
function Student(props) { this.name = props.name || "匿名"; // 默認(rèn)值為"匿名" this.grade = props.grade || 1; // 默認(rèn)值為1 } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); }; function createStudent(props) { return new Student(props || {}) }
這個createStudent()函數(shù)有幾個巨大的優(yōu)點:一是不需要new來調(diào)用,二是參數(shù)非常靈活,可以不傳,也可以這么傳:
var xiaoming = createStudent({ name: "小明" }); xiaoming.grade; // 1
如果創(chuàng)建的對象有很多屬性,我們只需要傳遞需要的某些屬性,剩下的屬性可以用默認(rèn)值。由于參數(shù)是一個Object,我們無需記憶參數(shù)的順序。如果恰好從JSON拿到了一個對象,就可以直接創(chuàng)建出xiaoming。
原型繼承先回顧Student構(gòu)造函數(shù):
function Student(props) { this.name = props.name || "Unnamed"; } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); }
現(xiàn)在,我們要基于Student擴展出PrimaryStudent,可以先定義出PrimaryStudent:
function PrimaryStudent(props) { // 調(diào)用Student構(gòu)造函數(shù),綁定this變量: Student.call(this, props); this.grade = props.grade || 1; }
但是,調(diào)用了Student構(gòu)造函數(shù)不等于繼承了Student,PrimaryStudent創(chuàng)建的對象的原型是:
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Object.prototype ----> null
必須想辦法把原型鏈修改為:
new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null
這樣,原型鏈對了,繼承關(guān)系就對了。新的基于PrimaryStudent創(chuàng)建的對象不但能調(diào)用PrimaryStudent.prototype定義的方法,也可以調(diào)用Student.prototype定義的方法。
如果你想用最簡單粗暴的方法這么干:
PrimaryStudent.prototype = Student.prototype;
是不行的!如果這樣的話,PrimaryStudent和Student共享一個原型對象,那還要定義PrimaryStudent干啥?
我們必須借助一個中間對象來實現(xiàn)正確的原型鏈,這個中間對象的原型要指向Student.prototype。為了實現(xiàn)這一點,參考道爺(就是發(fā)明JSON的那個道格拉斯)的代碼,中間對象可以用一個空函數(shù)F來實現(xiàn):
// PrimaryStudent構(gòu)造函數(shù): function PrimaryStudent(props) { Student.call(this, props); this.grade = props.grade || 1; } // 空函數(shù)F: function F() { } // 把F的原型指向Student.prototype: F.prototype = Student.prototype; // 把PrimaryStudent的原型指向一個新的F對象,F(xiàn)對象的原型正好指向Student.prototype: PrimaryStudent.prototype = new F(); // 把PrimaryStudent原型的構(gòu)造函數(shù)修復(fù)為PrimaryStudent: PrimaryStudent.prototype.constructor = PrimaryStudent; // 繼續(xù)在PrimaryStudent原型(就是new F()對象)上定義方法: PrimaryStudent.prototype.getGrade = function () { return this.grade; }; // 創(chuàng)建xiaoming: var xiaoming = new PrimaryStudent({ name: "小明", grade: 2 }); xiaoming.name; // "小明" xiaoming.grade; // 2 // 驗證原型: xiaoming.__proto__ === PrimaryStudent.prototype; // true xiaoming.__proto__.__proto__ === Student.prototype; // true // 驗證繼承關(guān)系: xiaoming instanceof PrimaryStudent; // true xiaoming instanceof Student; // true
instanceof
instanceof 用于判斷一個變量是否是某個對象的實例,如 var a=new Array();alert(a instanceof Array); 會返回 true,同時 alert(a instanceof Object) 也會返回 true;這是因為 Array 是 object 的子類。再如:function test(){};var a=new test();alert(a instanceof test) 會返回true.
談到 instanceof 我們要多插入一個問題,就是 function 的 arguments,我們大家也許都認(rèn)為 arguments 是一個 Array,但如果使用 instaceof 去測試會發(fā)現(xiàn) arguments 不是一個 Array 對象,盡管看起來很像。
另外:
測試 var a=new Array();if (a instanceof Object) alert("Y");else alert("N");
得"Y’.
但 if (window instanceof Object) alert("Y");else alert("N");
得"N".
所以,這里的 instanceof 測試的 object 是指 js 語法中的 object,不是指 dom 模型對象。
使用 typeof 會有些區(qū)別.
alert(typeof(window)) 會得 object.
注意,函數(shù)F僅用于橋接,我們僅創(chuàng)建了一個new F()實例,而且,沒有改變原有的Student定義的原型鏈。
如果把繼承這個動作用一個inherits()函數(shù)封裝起來,還可以隱藏F的定義,并簡化代碼:
function inherits(Child, Parent) { var F = function () {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; } function Student(props) { this.name = props.name || "Unnamed"; } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); } function PrimaryStudent(props) { Student.call(this, props); this.grade = props.grade || 1; } // 實現(xiàn)原型繼承鏈: inherits(PrimaryStudent, Student); // 綁定其他方法到PrimaryStudent原型: PrimaryStudent.prototype.getGrade = function () { return this.grade; };
小結(jié)
class繼承JavaScript的原型繼承實現(xiàn)方式就是:
定義新的構(gòu)造函數(shù),并在內(nèi)部用call()調(diào)用希望“繼承”的構(gòu)造函數(shù),并綁定this;
借助中間函數(shù)F實現(xiàn)原型鏈繼承,最好通過封裝的inherits函數(shù)完成;
繼續(xù)在新的構(gòu)造函數(shù)的原型上定義新方法。
在上面的章節(jié)中我們看到了JavaScript的對象模型是基于原型實現(xiàn)的,特點是簡單,缺點是理解起來比傳統(tǒng)的類-實例模型要困難,最大的缺點是繼承的實現(xiàn)需要編寫大量代碼,并且需要正確實現(xiàn)原型鏈。
有沒有更簡單的寫法?有!
新的關(guān)鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函數(shù)實現(xiàn)Student的方法:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); }
如果用新的class關(guān)鍵字來編寫Student,可以這樣寫:
class Student { constructor(name) { this.name = name; } hello() { alert("Hello, " + this.name + "!"); } }
比較一下就可以發(fā)現(xiàn),class的定義包含了構(gòu)造函數(shù)constructor和定義在原型對象上的函數(shù)hello()(注意沒有function關(guān)鍵字),這樣就避免了Student.prototype.hello = function () {...}這樣分散的代碼。
最后,創(chuàng)建一個Student對象代碼和前面章節(jié)完全一樣:
var xiaoming = new Student("小明"); xiaoming.hello();
class繼承
用class定義對象的另一個巨大的好處是繼承更方便了。想一想我們從Student派生一個PrimaryStudent需要編寫的代碼量。現(xiàn)在,原型繼承的中間對象,原型對象的構(gòu)造函數(shù)等等都不需要考慮了,直接通過extends來實現(xiàn):
class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 記得用super調(diào)用父類的構(gòu)造方法! this.grade = grade; } myGrade() { alert("I am at grade " + this.grade); } }
注意PrimaryStudent的定義也是class關(guān)鍵字實現(xiàn)的,而extends則表示原型鏈對象來自Student。子類的構(gòu)造函數(shù)可能會與父類不太相同,例如,PrimaryStudent需要name和grade兩個參數(shù),并且需要通過super(name)來調(diào)用父類的構(gòu)造函數(shù),否則父類的name屬性無法正常初始化。
PrimaryStudent已經(jīng)自動獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什么區(qū)別呢?實際上它們沒有任何區(qū)別,class的作用就是讓JavaScript引擎去實現(xiàn)原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
你一定會問,class這么好用,能不能現(xiàn)在就用上?
現(xiàn)在用還早了點,因為不是所有的主流瀏覽器都支持ES6的class。如果一定要現(xiàn)在就用上,就需要一個工具把class代碼轉(zhuǎn)換為傳統(tǒng)的prototype代碼,可以試試Babel這個工具。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86945.html
摘要:例如,在一個中,刪掉偶數(shù),只保留奇數(shù),可以這么寫把一個中的空字符串刪掉,可以這么寫注意以下的版本沒有方法可見用這個高階函數(shù),關(guān)鍵在于正確實現(xiàn)一個篩選函數(shù)。回調(diào)函數(shù)接收的回調(diào)函數(shù),其實可以有多個參數(shù)。 1.map 由于map()方法定義在JavaScript的Array中,我們調(diào)用Array的map()方法,傳入我們自己的函數(shù),就得到了一個新的Array作為結(jié)果: function po...
摘要:對象不但充當(dāng)全局作用域,而且表示瀏覽器窗口。對象有和屬性,可以獲取瀏覽器窗口的內(nèi)部寬度和高度。對象表示當(dāng)前頁面的信息。由于在瀏覽器中以形式表示為樹形結(jié)構(gòu),對象就是整個樹的根節(jié)點。這個行為由瀏覽器實現(xiàn),主流瀏覽器均支持選項,從開始支持。 瀏覽器 目前主流的瀏覽器: IE 6~11:從IE10開始支持ES6標(biāo)準(zhǔn); Chrome:基于Webkit內(nèi)核,內(nèi)置了非常強悍的JavaScript引...
JSON JSON是JavaScript Object Notation的縮寫,它是一種數(shù)據(jù)交換格式。 道格拉斯·克羅克福特(Douglas Crockford)--雅虎的高級架構(gòu)師--發(fā)明了JSON這種超輕量級的數(shù)據(jù)交換格式. 序列化 讓我們先把小明這個對象序列化成JSON格式的字符串: var xiaoming = { name: 小明, age: 14, gender...
摘要:在設(shè)計時,有兩種比較運算符第一種是比較,它會自動轉(zhuǎn)換數(shù)據(jù)類型再比較,很多時候,會得到非常詭異的結(jié)果第二種是比較,它不會自動轉(zhuǎn)換數(shù)據(jù)類型,如果數(shù)據(jù)類型不一致,返回,如果一致,再比較。 數(shù)據(jù)類型和變量 數(shù)據(jù)類型計算機顧名思義就是可以做數(shù)學(xué)計算的機器,因此,計算機程序理所當(dāng)然地可以處理各種數(shù)值。但是,計算機能處理的遠(yuǎn)不止數(shù)值,還可以處理文本、圖形、音頻、視頻、網(wǎng)頁等各種各樣的數(shù)據(jù),不同的數(shù)據(jù)...
摘要:讓我們拆開寫小明正常結(jié)果單獨調(diào)用函數(shù)怎么返回了請注意,我們已經(jīng)進入到了的一個大坑里。如果單獨調(diào)用函數(shù),比如,此時,該函數(shù)的指向全局對象,也就是。 函數(shù) 1. arguments JavaScript還有一個免費贈送的關(guān)鍵字arguments,它只在函數(shù)內(nèi)部起作用,并且永遠(yuǎn)指向當(dāng)前函數(shù)的調(diào)用者傳入的所有參數(shù)。arguments類似Array但它不是一個Array: function fo...
閱讀 1123·2023-04-26 00:12
閱讀 3249·2021-11-17 09:33
閱讀 1061·2021-09-04 16:45
閱讀 1186·2021-09-02 15:40
閱讀 2146·2019-08-30 15:56
閱讀 2951·2019-08-30 15:53
閱讀 3548·2019-08-30 11:23
閱讀 1932·2019-08-29 13:54