摘要:應(yīng)該非常小心,避免出現(xiàn)不使用命令直接調(diào)用構(gòu)造函數(shù)的情況。上面代碼表示,使用屬性,確定實(shí)例對象的構(gòu)造函數(shù)是,而不是。當(dāng)然,從繼承鏈來看,只有一個(gè)父類,但是由于在的實(shí)例上,同時(shí)執(zhí)行和的構(gòu)造函數(shù),所以它同時(shí)繼承了這兩個(gè)類的方法。
基本概念
類和實(shí)例是大多數(shù)面向?qū)ο缶幊陶Z言的基本概念
類:類是對象的類型模板
實(shí)例:實(shí)例是根據(jù)類創(chuàng)建的對象
但是,JavaScript語言的對象體系,不是基于“類”的,而是基于構(gòu)造函數(shù)(constructor)和原型鏈(prototype)。了與普通函數(shù)區(qū)別,構(gòu)造函數(shù)名字的第一個(gè)字母通常大寫。
構(gòu)造函數(shù)的特點(diǎn)有兩個(gè)。
函數(shù)體內(nèi)部使用了this關(guān)鍵字,代表了所要生成的對象實(shí)例。
生成對象的時(shí)候,必需用new命令
new與構(gòu)造函數(shù)
new命令本身就可以執(zhí)行構(gòu)造函數(shù),所以后面的構(gòu)造函數(shù)可以帶括號(hào),也可以不帶括號(hào)。
下面兩行代碼是等價(jià)的。
var v = new Vehicle(); var v = new Vehicle;
應(yīng)該非常小心,避免出現(xiàn)不使用new命令、直接調(diào)用構(gòu)造函數(shù)的情況。為了保證構(gòu)造函數(shù)必須與new命令一起使用,一個(gè)解決辦法是,在構(gòu)造函數(shù)內(nèi)部使用嚴(yán)格模式,即第一行加上use strict。
原理:由于在嚴(yán)格模式中,函數(shù)內(nèi)部的this不能指向全局對象,默認(rèn)等于undefined,導(dǎo)致不加new調(diào)用會(huì)報(bào)錯(cuò)(JavaScript不允許對undefined添加屬性)。
new命令的原理
創(chuàng)建一個(gè)空對象,作為將要返回的對象實(shí)例
將這個(gè)空對象的原型,指向構(gòu)造函數(shù)的prototype屬性
將這個(gè)空對象賦值給函數(shù)內(nèi)部的this關(guān)鍵字
開始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼
即:
var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj);
構(gòu)造函數(shù)的return
如果構(gòu)造函數(shù)內(nèi)部有return語句,而且return后面跟著一個(gè)對象,new命令會(huì)返回return語句指定的對象;否則,就會(huì)不管return語句,返回this對象。
如果對普通函數(shù)(內(nèi)部沒有this關(guān)鍵字的函數(shù))使用new命令,則會(huì)返回一個(gè)空對象
這里遇到了一個(gè)問題,問題描述如下普通函數(shù)用new測試的時(shí)候箭頭函數(shù)報(bào)錯(cuò)了
JavaScript對每個(gè)創(chuàng)建的對象都會(huì)設(shè)置一個(gè)原型,指向它的原型對象。
當(dāng)我們用obj.xxx訪問一個(gè)對象的屬性時(shí),JavaScript引擎先在當(dāng)前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到Object.prototype對象,最后,如果還沒有找到,就只能返回undefined。
例如,創(chuàng)建一個(gè)Array對象:
var arr = [1, 2, 3];
其原型鏈?zhǔn)牵?br>arr ----> Array.prototype ----> Object.prototype ----> null
Array.prototype定義了indexOf()、shift()等方法,因此你可以在所有的Array對象上直接調(diào)用這些方法。
很容易想到,如果原型鏈很長,那么訪問一個(gè)對象的屬性就會(huì)因?yàn)榛ǜ嗟臅r(shí)間查找而變得更慢,因此要注意不要把原型鏈搞得太長。
function Student(name) { this.name = name; this.hello = function () { alert("Hello, " + this.name + "!"); } } var xiaoming= new Student("xiaoming"), xiaohong= new Student("xiaohong");
xiaoming ↘
xiaohong -→ Student.prototype ----> Object.prototype ----> null
xiaojun ↗
用new Student()創(chuàng)建的對象還從原型上獲得了一個(gè)constructor屬性,它指向函數(shù)Student本身:
xiaoming.constructor === Student.prototype.constructor; // true Student.prototype.constructor === Student; // true Object.getPrototypeOf(xiaoming) === Student.prototype; // true xiaoming instanceof Student; // trueconstructor
constructor屬性的作用,是分辨原型對象到底屬于哪個(gè)構(gòu)造函數(shù)。
function F() {}; var f = new F(); f.constructor === F // true f.constructor === RegExp // false
上面代碼表示,使用constructor屬性,確定實(shí)例對象f的構(gòu)造函數(shù)是F,而不是RegExp。構(gòu)造函數(shù)繼承 VS 原型鏈繼承
xiaoming.name //"xiaoming" xiaohong.name //"xiaohong" xiaoming.hello /*function() { alert("Hello, " + this.name + "!"); } */ xiaohong.hello /*function() { alert("Hello, " + this.name + "!"); } */ xiaoming.hello === xiaohong.hello //false
xiaoming和xiaohong各自的name不同,這是對的,否則我們無法區(qū)分誰是誰了。
xiaoming和xiaohong各自的hello是一個(gè)函數(shù),但它們是兩個(gè)不同的函數(shù),雖然函數(shù)名稱和代碼都是相同的!
如果我們通過new Student()創(chuàng)建了很多對象,這些對象的hello函數(shù)實(shí)際上只需要共享同一個(gè)函數(shù)就可以了,這樣可以節(jié)省很多內(nèi)存。
要讓創(chuàng)建的對象共享一個(gè)hello函數(shù),根據(jù)對象的屬性查找原則,我們只要把hello函數(shù)移動(dòng)到xiaoming、xiaohong這些對象共同的原型上就可以了,也就是Student.prototype:
修改代碼如下:
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert("Hello, " + this.name + "!"); }; xiaoming.hello === xiaohong.hello //true繼承方式對比
借用構(gòu)造函數(shù)繼承
基本思想很簡單,在子類型的構(gòu)造函數(shù)內(nèi)部調(diào)用父類型的構(gòu)造函數(shù):
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了 SuperType SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" var instance2 = new SubType(); alert(instance2.colors); //"red,blue,green"
問題:方法都在構(gòu)造函數(shù)內(nèi)部定義,函數(shù)的復(fù)用性就無從談起了。在超類型的原型中定義的方法,對子類型而言是不可見的。考慮這些問題,借用構(gòu)造函數(shù)也是很少多帶帶使用。
組合繼承
實(shí)現(xiàn)的思路是使用原型鏈實(shí)現(xiàn)對原型屬性和方法的繼承,而通過constructor stealing技術(shù)實(shí)現(xiàn)對實(shí)例屬性的繼承。
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ alert(this.name); }; function SubType(name, age){ //繼承屬性 SuperType.call(this, name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,融合兩者之長,是最常用的JS繼承模式。
原型式繼承
如果只是想讓一個(gè)對象與另一個(gè)對象保持類似的情況下,沒有必要興師動(dòng)眾地創(chuàng)建構(gòu)造函數(shù)。我們可以使用原型式繼承。
Rect.prototype = Object.create(Shape.prototype);
原型繼承
JavaScript的原型繼承實(shí)現(xiàn)方式就是:
定義新的構(gòu)造函數(shù),并在內(nèi)部用call()調(diào)用希望“繼承”的構(gòu)造函數(shù),并綁定this;
借助中間函數(shù)F實(shí)現(xiàn)原型鏈繼承,最好通過封裝的inherits函數(shù)完成;
繼續(xù)在新的構(gòu)造函數(shù)的原型上定義新方法。
var print = require("./print.js"); function Student(props) { this.name = props.name || "Unnamed"; } Student.prototype.hello = function () { print("Hello, " + this.name + "!"); }; function Sub(props) { Student.call(this, props); this.grade = props.grade || 1; } Sub.prototype.getGrade = function() { return this.grade; }; function inherits(Child, Parent) { var F = function() {}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; } inherits(Sub, Student); var xiaoming = new Sub({ name: "xiaoming", grade: 100 }); print(xiaoming.name + " " + xiaoming.grade); print(xiaoming instanceof Student); print(xiaoming instanceof Sub);class繼承
class Student { constructor(name) { this.name = name; } hello() { alert("Hello, " + this.name + "!"); } } 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); } }
ES6引入的class和原有的JavaScript原型繼承有什么區(qū)別呢?實(shí)際上它們沒有任何區(qū)別,class的作用就是讓JavaScript引擎去實(shí)現(xiàn)原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
這里遇到的問題是isPrototypeOf的問題
JavaScript不提供多重繼承功能,即不允許一個(gè)對象同時(shí)繼承多個(gè)對象。但是,可以通過變通方法,實(shí)現(xiàn)這個(gè)功能。
function M1() { this.hello = "hello"; } function M2() { this.world = "world"; } function S() { M1.call(this); M2.call(this); } S.prototype = M1.prototype; var s = new S(); s.hello // "hello" s.world // "world" s instanceof M2 //false
上面代碼中,子類S同時(shí)繼承了父類M1和M2。當(dāng)然,從繼承鏈來看,S只有一個(gè)父類M1,但是由于在S的實(shí)例上,同時(shí)執(zhí)行M1和M2的構(gòu)造函數(shù),所以它同時(shí)繼承了這兩個(gè)類的方法。
擴(kuò)展
apply的應(yīng)用:轉(zhuǎn)換類似數(shù)組的對象
Array.prototype.slice.apply({0:1,length:1}) // [1] Array.prototype.slice.apply({0:1}) // [] Array.prototype.slice.apply({0:1,length:2}) // [1, undefined] Array.prototype.slice.apply({length:1}) // [undefined]
bind結(jié)合call,可以改寫一些JavaScript原生方法的使用形式
[1, 2, 3].slice(0, 1) // [1] // 等同于 Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
call方法實(shí)質(zhì)上是調(diào)用Function.prototype.call方法,因此上面的表達(dá)式可以用bind方法改寫。
var push = Function.prototype.call.bind(Array.prototype.push); var pop = Function.prototype.call.bind(Array.prototype.pop); var a = [1 ,2 ,3]; push(a, 4) a // [1, 2, 3, 4] pop(a) a // [1, 2, 3]
某個(gè)屬性到底是原型鏈上哪個(gè)對象自身的屬性。
function getDefiningObject(obj, propKey) { while (obj && !{}.hasOwnProperty.call(obj, propKey)) { obj = Object.getPrototypeOf(obj); } return obj; }
獲取實(shí)例對象obj的原型對象,有三種方法。
obj.__proto__ obj.constructor.prototype Object.getPrototypeOf(obj)
推薦最后一種
面向?qū)ο蟾杏X還沒怎么搞明白,模塊的東西還沒弄明白,有時(shí)間補(bǔ)上
參考資料廖雪峰老師的教程
阮一峰老師的教程
BruceYuj的博客
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/92352.html
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對象。對象就是數(shù)據(jù),對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍(lán)圖或原型。在中,對象通過對類的實(shí)體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對象。對象就是數(shù)據(jù),對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍(lán)圖或原型。在中,對象通過對類的實(shí)體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個(gè)人類,即創(chuàng)建了一個(gè)具體的對象。對象就是數(shù)據(jù),對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍(lán)圖或原型。在中,對象通過對類的實(shí)體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實(shí)例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:不區(qū)分類和實(shí)例的概念,而是通過原型來實(shí)現(xiàn)面向?qū)ο缶幊獭P聞?chuàng)建的的原型鏈?zhǔn)且簿褪钦f,的原型指向函數(shù)的原型。最后,創(chuàng)建一個(gè)對象代碼和前面章節(jié)完全一樣小明繼承用定義對象的另一個(gè)巨大的好處是繼承更方便了。 JavaScript不區(qū)分類和實(shí)例的概念,而是通過原型(prototype)來實(shí)現(xiàn)面向?qū)ο缶幊獭?原型是指當(dāng)我們想要?jiǎng)?chuàng)建xiaoming這個(gè)具體的學(xué)生時(shí),我們并沒有一個(gè)Student類型可用...
摘要:前端日報(bào)精選是如何工作的內(nèi)存管理如何處理個(gè)常見的內(nèi)存泄漏譯中的面向?qū)ο笤驮玩溊^承源碼事件機(jī)制考拉升級(jí)經(jīng)驗(yàn)掘金中文第期你知道編譯與解釋的區(qū)別嗎視頻在白鷺引擎中的實(shí)踐王澤變量自定義屬性使用指南眾成翻譯禁止手機(jī)虛擬鍵盤彈出做 2017-09-27 前端日報(bào) 精選 JavaScript是如何工作的:內(nèi)存管理 + 如何處理4個(gè)常見的內(nèi)存泄漏(譯) js中的面向?qū)ο蟆⒃汀⒃玩湣⒗^承Vue....
閱讀 1368·2021-09-13 10:25
閱讀 552·2019-08-30 15:53
閱讀 2265·2019-08-30 15:44
閱讀 2026·2019-08-29 17:20
閱讀 1594·2019-08-29 16:36
閱讀 1795·2019-08-29 14:10
閱讀 1785·2019-08-29 12:44
閱讀 1166·2019-08-23 14:13