摘要:它繼承自構(gòu)造函數(shù)被執(zhí)行,相應(yīng)的參數(shù)會(huì)被傳入,同時(shí)上下文會(huì)指向這個(gè)新的實(shí)例除非明確返回值,否則返回新的實(shí)例至此,我們實(shí)現(xiàn)了里面的類對象和屬性的概念,和有相同的屬性,但是值并不相同即屬性是私有的。
眾所周知,JS并沒有類(class)的概念,雖然說ES6開始有了類的概念,但是,這并不是說JS有了像Ruby、Java這些基于類的面向?qū)ο笳Z言一樣,有了全新的繼承模型。ES6中的類,僅僅只是基于現(xiàn)有的原型繼承的一種語法糖,下面我們好好分析一下,具體是如何實(shí)現(xiàn)的
面向?qū)ο笏枷?/b>在講正題之前,我們先來討論一下各種面試題都可能出現(xiàn)的一個(gè)問題,什么是面向?qū)ο缶幊蹋∣OP)?
類:定義某一事物的抽象特點(diǎn),包含屬性和方法,舉個(gè)栗子,狗這個(gè)類包含狗的一些基礎(chǔ)特征,如毛皮顏色,吠叫等能力。
對象:類的一個(gè)實(shí)例,還是舉個(gè)栗子,小明家的白色的狗和小紅家紅色的狗。
屬性:對象的特征,比如剛提到的狗皮毛的顏色。
方法:對象的行為,比如剛才提到的狗的吠叫能力。
封裝性:通過限制只有特定類的對象可以訪問特定類的成員,一般包含public protected private 三種,不同語言的實(shí)現(xiàn)不同。
繼承性:一個(gè)類會(huì)有子類,這個(gè)子類是更具體化的一個(gè)抽象,它包含父類的一些屬性和方法,并且有可能有不同于父類的屬性和方法。
多態(tài)性:多意為‘許多’,態(tài)意為‘形態(tài)’。不同類可以定義相同的方法或?qū)傩浴?/p>
抽象性:復(fù)雜現(xiàn)實(shí)問題轉(zhuǎn)化為類定義的途徑,包括以上所有內(nèi)容。
如何實(shí)現(xiàn)對象(類)的定義由于JS并沒有類(class)的概念,更多的時(shí)候我們把它叫做對象(function),然后把對象叫做實(shí)例(instance),跟團(tuán)隊(duì)里面的人討論OOP的時(shí)候,經(jīng)常會(huì)有概念上的一些誤解,特此說明一下。
構(gòu)造函數(shù):一個(gè)指明了對象類型的函數(shù),通常我們可以通過構(gòu)造函數(shù)類創(chuàng)建在js里面,我們通常都是通過構(gòu)造函數(shù)來創(chuàng)建對象(class),然后通過new這個(gè)關(guān)鍵字來實(shí)例化一個(gè)對象,如:
function Dog(name){ this.name = name; } var d1 = new Dog("dodo"); d1.constructor // Dog(name){ // this.name = name; // } var d2 = new Dog("do2do");
為什么通過構(gòu)造函數(shù)可以實(shí)現(xiàn)對象(class)屬性的定義呢?首先,我們必須理解這個(gè)語法new constructor[([arguments])]
我們來具體看看當(dāng)new Dog("name")時(shí),具體做了哪些事情
一個(gè)新實(shí)例被創(chuàng)建。它繼承自Dog.prototype
構(gòu)造函數(shù)被執(zhí)行,相應(yīng)的參數(shù)會(huì)被傳入,同時(shí)上下文(this)會(huì)指向這個(gè)新的實(shí)例
除非明確返回值,否則返回新的實(shí)例
至此,我們實(shí)現(xiàn)了OOP里面的類(Dog)、對象(d1,d2)、和屬性(name)的概念,d1和d2有相同的name屬性,但是值并不相同,即屬性是私有的。
原型對象(prototype)注: 新創(chuàng)建的實(shí)例,都包含一個(gè)constructor屬性,該屬性指向他們的構(gòu)造函數(shù)Dog
接下來,我們即將討論如何定義方法,其實(shí),我們完全可以這樣定義我們的方法,如:
function Dog(name){ this.name = name; this.bark = function(){ console.log(this.name + " bark"); }; } var d1 = new Dog("dodo"); d1.bark(); // dodo bark
但是,一般我們不推薦這么做,正如我們所知Dog是一個(gè)構(gòu)造函數(shù),每次實(shí)例化時(shí),都會(huì)執(zhí)行這個(gè)函數(shù),也就是說,bark 這個(gè)方法每次都會(huì)被定義, 比較浪費(fèi)內(nèi)存。但是我們通??梢杂?b>constructor和閉包的方式來實(shí)現(xiàn)私有屬性,如:
function Dog(name){ this.name = name; // barkCount 是私有屬性,因?yàn)閷?shí)例并不知道這個(gè)屬性 var barkCount = 0; this.bark = function(){ barkCount ++; console.log(this.name + " bark"); }; this.getBarkCount = function(){ console.log(this.name + " has barked " + barkCount + " times"); }; } var d1 = new Dog("dodo"); d1.bark(); d1.bark(); d1.getBarkCount(); // dodo has barked 2 times
好像扯得有點(diǎn)遠(yuǎn),我們回歸我們的主角prototype,函數(shù)Dog有一個(gè)特殊的屬性,這個(gè)屬性就叫原型,如上所述,當(dāng)用new運(yùn)算符創(chuàng)建實(shí)例時(shí),會(huì)把Dog的原型對象的引用復(fù)制到新的實(shí)例內(nèi)部的[[Prototype]]屬性,即d1.[[Prototype]] = Dog.prototype,因?yàn)樗械膶?shí)例的[[Prototype]]都指向Dog的原型對象,那么,我們就可以很方便的定義我們的方法了,如:
function Dog(name){ this.name = name; } Dog.prototype = { bark: function(){ console.log(this.name + " bark"); } }; var d1 = new Dog("dodo"); d1.bark(); // dodo bark
我們可以通過d1.__proto__ == Dog.prototype,來驗(yàn)證我們的想法。用原型對象還有一個(gè)好處,由于實(shí)例化的對象的[[Prototype]]指向Dog的原型對象,那么我們可以通過添加Dog的原型對象的方法,來添加已經(jīng)實(shí)例化后的實(shí)例d1的方法。如:
Dog.prototype.run = function(){ console.log(this.name + " is running!"); } d1.run(); // dodo is running!
原型鏈注:所有對象的__proto__都指向其構(gòu)造器的prototype
上面已經(jīng)描述如何定義一個(gè)類,接下來我們將要了解,如何實(shí)現(xiàn)類的繼承。在此之前,我們先了解js里一個(gè)老生常談的概念:原型鏈:每個(gè)對象都有一個(gè)指向它的原型(prototype)對象的內(nèi)部鏈接。這個(gè)原型對象又有自己的原型,直到某個(gè)對象的原型為 null 為止(也就是不再有原型指向),組成這條鏈的最后一環(huán)。這種一級一級的鏈結(jié)構(gòu)就稱為原型鏈
mozilla給出一個(gè)挺好的例子:
// 假定有一個(gè)對象 o, 其自身的屬性(own properties)有 a 和 b: // {a: 1, b: 2} // o 的原型 o.[[Prototype]]有屬性 b 和 c: // {b: 3, c: 4} // 最后, o.[[Prototype]].[[Prototype]] 是 null. // 這就是原型鏈的末尾,即 null, // 根據(jù)定義,null 沒有[[Prototype]]. // 綜上,整個(gè)原型鏈如下: // {a:1, b:2} ---> {b:3, c:4} ---> null console.log(o.a); // 1 // a是o的自身屬性嗎?是的,該屬性的值為1 console.log(o.b); // 2 // b是o的自身屬性嗎?是的,該屬性的值為2 // o.[[Prototype]]上還有一個(gè)"b"屬性,但是它不會(huì)被訪問到.這種情況稱為"屬性遮蔽 (property shadowing)". console.log(o.c); // 4 // c是o的自身屬性嗎?不是,那看看o.[[Prototype]]上有沒有. // c是o.[[Prototype]]的自身屬性嗎?是的,該屬性的值為4 console.log(o.d); // undefined // d是o的自身屬性嗎?不是,那看看o.[[Prototype]]上有沒有. // d是o.[[Prototype]]的自身屬性嗎?不是,那看看o.[[Prototype]].[[Prototype]]上有沒有. // o.[[Prototype]].[[Prototype]]為null,停止搜索, // 沒有d屬性,返回undefined
現(xiàn)在我們可以通過我們理解的構(gòu)造函數(shù)和原型對象來實(shí)現(xiàn)繼承的概念了,代碼如下:
function Dog(name){ this.name = name; } // 這種寫法會(huì)修改dog實(shí)例的constructor,可以通過Dog.prototype.constructor = Dog來重置 Dog.prototype = { bark: function(){ console.log(this.name + " bark"); } }; // 重置Dog實(shí)例的構(gòu)造函數(shù)為本身 Dog.prototype.constructor = Dog; // Haski 的構(gòu)造函數(shù) function Haski(name){ // 繼承Dog的構(gòu)造函數(shù) Dog.call(this, name); // 可以補(bǔ)充更多Haski的屬性 this.type = "Haski"; }; // 1. 設(shè)置Haski的prototype為Dog的實(shí)例對象 // 2. 此時(shí)Haski的原型鏈?zhǔn)?Haski -> Dog的實(shí)例 -> Dog -> Object // 3. 此時(shí),Haski包含了Dog的所有屬性和方法,而且還有一個(gè)指針,指向Dog的原型對象 // 4. 這種做法是不推薦的,下面會(huì)改進(jìn) Haski.prototype = new Dog(); // 重置Haski實(shí)例的構(gòu)造函數(shù)為本身 Haski.prototype.constructor = Haski; // 可以為子類添加更多的方法 Haski.prototype.say = function(){ console.log("I"m " + this.name); } var ha = new Haski("Ha"); // Ha bark ha.bark(); // Ha bark ha.say(); // I"m Ha
注: 子類在定義prototype時(shí),不可直接使用Haski.prototype = {}定義,這樣會(huì)重寫Haski的原型鏈,把Haski的原型當(dāng)做Object的實(shí)例,而非Dog的實(shí)例
但是,當(dāng)我想找一下ha的原型鏈時(shí),會(huì)發(fā)現(xiàn)ha的原型對象指向的是Dog的實(shí)例,而且還有一個(gè)值為undefined的name屬性,在實(shí)例化時(shí),name是沒必要的, 如下圖:
所以,我們需要修改一下我們的實(shí)現(xiàn),代碼如下:
// 修改前 Haski.prototype = new Dog(); // 修改后 Haski.prototype = Object.create(Dog.prototype);
注: __proto__ 方法已棄用,從 ECMAScript 6 開始, [[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問
自此,我們已經(jīng)實(shí)現(xiàn)繼承的概念,父類有自己的方法,子類繼承了父類的屬性和方法,而且還可以定義自己的屬性和方法。
ES6 如何實(shí)現(xiàn)"use strict"; // 聲明 Dog 類 class Dog { // 構(gòu)造函數(shù) constructor(name){ this.name = name; } // 普通方法 dark(){ console.log(this.name + "bark"); } // 靜態(tài)方法,也叫類方法 static staticMethod(){ console.log("I"m static method!"); } } // 通過`extends`關(guān)鍵字來實(shí)現(xiàn)繼承 class Haski extends Dog { constructor(name){ // 調(diào)用父類的構(gòu)造函數(shù) super(name); this.type = "Haski"; } // 定義子類方法 say(){ console.log("I"m" + this.name); } }
在ES6中,我們只需通過class extends super constructor 即可比較方便的完成原來使用JS比較難理解的實(shí)現(xiàn),我們可以通過babel的解析器,來看看babel是怎么把這些語法糖轉(zhuǎn)成JS的實(shí)現(xiàn)的。具體代碼可以參考
"use strict"; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } // 聲明 Dog 類 var Dog = function () { // 構(gòu)造函數(shù) function Dog(name) { _classCallCheck(this, Dog); this.name = name; } // 普通方法 _createClass(Dog, [{ key: "dark", value: function dark() { console.log(this.name + "bark"); } // 靜態(tài)方法,也叫類方法 }], [{ key: "staticMethod", value: function staticMethod() { console.log("I"m static method!"); } }]); return Dog; }(); // 通過`extends`關(guān)鍵字來實(shí)現(xiàn)繼承 var Haski = function (_Dog) { _inherits(Haski, _Dog); function Haski(name) { _classCallCheck(this, Haski); var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Haski).call(this, name)); // 調(diào)用父類的構(gòu)造函數(shù) _this.type = "Haski"; return _this; } _createClass(Haski, [{ key: "say", value: function say() { console.log("I"m" + this.name); } }]); return Haski; }(Dog);
參考文獻(xiàn)教是最好的學(xué),我正在嘗試把我自己理解的內(nèi)容分享出來,希望我能講清楚,如果描述有誤,歡迎指正。
Introduction to Object-Oriented JavaScript
Classes
Declaring javascript object method in constructor function vs. in prototype
Inheritance and the prototype chain
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/107009.html
摘要:個(gè)人前端文章整理從最開始萌生寫文章的想法,到著手開始寫,再到現(xiàn)在已經(jīng)一年的時(shí)間了,由于工作比較忙,更新緩慢,后面還是會(huì)繼更新,現(xiàn)將已經(jīng)寫好的文章整理一個(gè)目錄,方便更多的小伙伴去學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000017490740?w=1920&h=1080); 個(gè)人前端文章整理 從最開始萌生寫文章的想法,到著手...
摘要:可以通過構(gòu)造函數(shù)和原型的方式模擬實(shí)現(xiàn)類的功能。原型式繼承與類式繼承類式繼承是在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型的構(gòu)造函數(shù)。寄生式繼承這種繼承方式是把原型式工廠模式結(jié)合起來,目的是為了封裝創(chuàng)建的過程。 js繼承的概念 js里常用的如下兩種繼承方式: 原型鏈繼承(對象間的繼承) 類式繼承(構(gòu)造函數(shù)間的繼承) 由于js不像java那樣是真正面向?qū)ο蟮恼Z言,js是基于對象的,它沒有類的概念。...
摘要:語法父類名表示當(dāng)前類繼承于哪個(gè)類的標(biāo)簽。成員標(biāo)簽成員標(biāo)簽作用于類中的配置屬性函數(shù)事件。表明可被子類繼承,和一起使用。示例獲取圓的面積圓的半徑面積值作用于函數(shù),表明函數(shù)的標(biāo)簽。作用于函數(shù),表明構(gòu)造函數(shù)參數(shù)的標(biāo)簽,用法同。 字?jǐn)?shù):3692字 閱讀時(shí)間:15分鐘 前言 ? 首先,咱們有一個(gè)前提,JSDuck對我們而言只是一個(gè)便于API查看的文檔化工具。因此,只要它能夠滿足我們文...
摘要:選擇器大致可以分成類基本選擇器,層次選擇器,屬性選擇器,偽類,偽元素。但偽類和偽元素相對比較抽象,稍微有一點(diǎn)點(diǎn)理解上的難度。本篇就是我對偽類和偽元素的理解。 CSS選擇器大致可以分成5類:基本選擇器,層次選擇器,屬性選擇器,偽類,偽元素?;?,層次,屬性選擇器比較容易理解,畢竟它們選擇的對象都屬于DOM中看得見摸得著的元素。但偽類和偽元素相對比較抽象,稍微有一點(diǎn)點(diǎn)理解上的難度。本篇就是...
閱讀 2070·2021-08-11 11:13
閱讀 1099·2021-07-25 21:37
閱讀 2633·2019-08-29 18:42
閱讀 2576·2019-08-26 12:18
閱讀 976·2019-08-26 11:29
閱讀 1753·2019-08-23 17:17
閱讀 2713·2019-08-23 15:55
閱讀 2668·2019-08-23 14:34