对js面向对象的理解
轉自:http://www.cnblogs.com/jingwhale/p/4678656.html
js面向對象理解
ECMAScript 有兩種開發模式:1.函數式(過程化),2.面向對象(OOP)。
面向對象的語言有一個標志,那就是類的概念,而通過類可以創建任意多個具有相同屬性和方法的對象。但是,ECMAScript 沒有類的概念,因此它的對象也與基于類的語言中的對象有所不同。
面向對象有3大特征:封裝,繼承,多態
封裝
封裝其實就是隱藏內部信息,只對外提供一個接口
js(如果沒有作特殊說明,本文中的js僅包含ES5以內的內容)本身是沒有class類型的,但是每個函數都有一個prototype屬性。prototype指向一個對象,當函數作為構造函數時,prototype則起到類似class的作用。
創建對象3種方式
var box = new Object(); //創建一個Object 對象 box.name = 'Lee'; //創建一個name 屬性并賦值 box.age = 100; //創建一個age 屬性并賦值 box.run = function () { //創建一個run()方法并返回值 return this.name + this.age + '運行中...'; }; alert(box.run()); //輸出屬性和方法的值上面創建了一個對象,并且創建屬性和方法,在run()方法里的this,就是代表box 對象本身。這種是JavaScript 創建對象最基本的方法,但有個缺點,想創建多個類似的對象,就會產生大量的代碼。
為了解決多個類似對象聲明的問題,我們可以使用一種叫做工廠模式的方法,這種方法就是為了解決實例化對象產生大量重復的問題。
1.工廠模式
使用簡單的函數創建對象,為對象添加屬性和方法,然后返回對象
function createObject(name, age) { //集中實例化的函數 var obj = new Object(); obj.name = name; obj.age = age; obj.run = function () { return this.name + this.age + '運行中...'; }; return obj; } var box1 = createObject('Lee', 100); //第一個實例 var box2 = createObject('Jack', 200); //第二個實例 alert(box1.run()); alert(box2.run()); //保持獨立工廠模式解決了重復實例化的問題,但是它有許多問題,創建不同對象其中屬性和方法都會重復建立,消耗內存;還有函數識別問題等等。
2.構造函數
構造函數的方法有一些規范:
1)函數名和實例化構造名相同且大寫,(PS:非強制,但這么寫有助于區分構造函數和
普通函數);
2)通過構造函數創建對象,必須使用new 運算符。
構造函數可以創建對象執行的過程:
1)當使用了構造函數,并且new 構造函數(),那么就后臺執行了new Object();
2)將構造函數的作用域給新對象,(即new Object()創建出的對象),而函數體內的this 就
代表new Object()出來的對象。
3)執行構造函數內的代碼;
4)返回新對象(后臺直接返回)。
注:
1)構造函數和普通函數的唯一區別,就是他們調用的方式不同。只不過,構造函數也是函數,必須用new 運算符來調用,否則就是普通函數。
2)this就是代表當前作用域對象的引用。如果在全局范圍this 就代表window 對象,如果在構造函數體內,就代表當前的構造函數所聲明的對象。
這種方法解決了函數識別問題,但消耗內存問題沒有解決。同時又帶來了一個新的問題,全局中的this 在對象調用的時候是Box 本身,而當作普通函數調用的時候,this 又代表window。即this作用域的問題。
3.原型
我們創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個對象,它的用途是包含可以由特定類型的所有實例共享的屬性和方法。邏輯上可以這么理解:prototype 通過調用構造函數而創建的那個對象的原型對象。使用原型的好處可以讓所有對象實例共享它所包含的屬性和方法。也就是說,不必在構造函數中定義對象信息,而是可以直接將這些信息添加到原型中。
function Box() {} //聲明一個構造函數 Box.prototype.name = 'Lee'; //在原型里添加屬性 Box.prototype.age = 100; Box.prototype.run = function () { //在原型里添加方法 return this.name + this.age + '運行中...'; };構造函數的聲明方式和原型模式的聲明方式存儲情況如下:
image
所以,它解決了消耗內存問題。當然它也可以解決this作用域等問題。
我們經常把屬性(一些在實例化對象時屬性值改變的),定義在構造函數內;把公用的方法添加在原型上面,也就是混合方式構造對象(構造方法+原型方式):
var person = function(name){this.name = name};person.prototype.getName = function(){return this.name; }var zjh = new person(‘zhangjiahao’);zjh.getName(); //zhangjiahao下面詳細介紹原型:
1.原型對象
每個javascript對象都有一個原型對象,這個對象在不同的解釋器下的實現不同。比如在firefox下,每個對象都有一個隱藏的__proto__屬性,這個屬性就是“原型對象”的引用。
2.原型鏈
由于原型對象本身也是對象,根據上邊的定義,它也有自己的原型,而它自己的原型對象又可以有自己的原型,這樣就組成了一條鏈,這個就是原型鏈,JavaScritp引擎在訪問對象的屬性時,如果在對象本身中沒有找到,則會去原型鏈中查找,如果找到,直接返回值,如果整個鏈都遍歷且沒有找到屬性,則返回undefined.原型鏈一般實現為一個鏈表,這樣就可以按照一定的順序來查找。
1)__proto__和prototype
JS在創建對象(不論是普通對象還是函數對象)的時候,都有一個叫做__proto__的內置屬性,用于指向創建它的函數對象的原型對象prototype。以上面的例子為例:
console.log(zjh.proto === person.prototype) //true
同樣,person.prototype對象也有__proto__屬性,它指向創建它的函數對象(Object)的prototype
console.log(person.prototype.proto === Object.prototype) //true
繼續,Object.prototype對象也有__proto__屬性,但它比較特殊,為null
console.log(Object.prototype.proto) //null
我們把這個有__proto__串起來的直到Object.prototype.__proto__為null的鏈叫做原型鏈。如下圖:
2dbd5870d84a471896d69f7d1980ae63
2)constructor
原型對象prototype中都有個預定義的constructor屬性,用來引用它的函數對象。這是一種循環引用
person.prototype.constructor === person //true
Function.prototype.constructor === Function //true
Object.prototype.constructor === Object //true
3)為加深對理解,我們再舉一個例子:
結果:
execute task_1[ACTIVE]:task1
execute task_2[STARTING]:task2
構造器會自動為task1,task2兩個對象設置原型對象Task.prototype,這個對象被Task(在此最為構造器)的prototype屬性引用,參看下圖中的箭頭指向。
image
由于Task本身仍舊是函數,因此其”proto”屬性為Function.prototype, 而內建的函數原型對象的”proto”屬性則為Object.prototype對象。最后Obejct.prototype的”proto”值為null。
總結:
實例對象的__proto__指向,其構造函數的原型;構造函數原型的constructor指向對應的構造函數。構造函數的prototype獲得構造函數的原型。
有時某種原因constructor指向有問題,可以通過
constructor:構造函數名;//constructor : Task
重新指向。
繼承
繼承是面向對象中一個比較核心的概念。其他正統面向對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承。而ECMAScript 只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成。
在JavaScript 里,被繼承的函數稱為超類型(父類,基類也行,其他語言叫法),繼承的函數稱為子類型(子類,派生類)
1.call+遍歷
屬性使用對象冒充(call)(實質上是改變了this指針的指向)繼承基類,方法用遍歷基類原型。
可以實現多繼承。
2.寄生組合繼承
主要是Desk.prototype = new Box(); Desk 繼承了Box,通過原型,形成鏈條。主要通過臨時中轉函數和寄生函數實現。
臨時中轉函數:基于已有的對象創建新對象,同時還不必因此創建自定義類型
寄生函數:目的是為了封裝創建對象的過程
臨時中轉函數和寄生函數主要做的工作流程:
臨時中轉函數:返回的是基類的實例對象函數
寄生函數:將返回的基類的實例對象函數的constructor指向派生類,派生類的prototype指向基類的實例對象函數(是一個函數原型),從而實現繼承。
總結
以上是生活随笔為你收集整理的对js面向对象的理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 浏览器兼容问题总结
- 下一篇: vue-cli打包遇到的问题