js面向對象理解


js面向對象理解

ECMAScript 有兩種開發模式:1.函數式(過程化),2.面向對象(OOP)。面向對象的語言有一個標志,那就是類的概念,而通過類可以創建任意多個具有相同屬性和方法的對象。但是,ECMAScript 沒有類的概念,因此它的對象也與基於類的語言中的對象有所不同。

js(如果沒有作特殊說明,本文中的js僅包含ES5以內的內容)本身是沒有class類型的,但是每個函數都有一個prototype屬性。prototype指向一個對象,當函數作為構造函數時,prototype則起到類似class的作用。

一.創建對象

創建一個對象,然后給這個對象新建屬性和方法。

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 創建對象最基本的方法,但有個缺點,想創建多個類似的對象,就會產生大量的代碼。

為了解決多個類似對象聲明的問題,我們可以使用一種叫做工廠模式的方法,這種方法就是為了解決實例化對象產生大量重復的問題。

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()); //保持獨立

工廠模式解決了重復實例化的問題,但是它有許多問題,創建不同對象其中屬性和方法都會重復建立,消耗內存;還有函數識別問題等等。

二.構造函數的方法

構造函數的方法有一些規范:


1)函數名和實例化構造名相同且大寫,(PS:非強制,但這么寫有助於區分構造函數和
普通函數);
2)通過構造函數創建對象,必須使用new 運算符。

function Box(name, age) { //構造函數模式
this.name = name;
this.age = age;
this.run = function () {
return this.name + this.age + '運行中...';
};
}
var box1 = new Box('Lee', 100); //new Box()即可
var box2 = new Box('Jack', 200);
alert(box1.run());
alert(box1 instanceof Box); //很清晰的識別他從屬於Box

構造函數可以創建對象執行的過程:


1)當使用了構造函數,並且new 構造函數(),那么就后台執行了new Object();
2)將構造函數的作用域給新對象,(即new Object()創建出的對象),而函數體內的this 就
代表new Object()出來的對象。
3)執行構造函數內的代碼;
4)返回新對象(后台直接返回)。

注:

1)構造函數和普通函數的唯一區別,就是他們調用的方式不同。只不過,構造函數也是函數,必須用new 運算符來調用,否則就是普通函數。

2)this就是代表當前作用域對象的引用。如果在全局范圍this 就代表window 對象,如果在構造函數體內,就代表當前的構造函數所聲明的對象。

這種方法解決了函數識別問題,但消耗內存問題沒有解決。同時又帶來了一個新的問題,全局中的this 在對象調用的時候是Box 本身,而當作普通函數調用的時候,this 又代表window。即this作用域的問題。

三.原型

我們創建的每個函數都有一個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)為加深對理解,我們再舉一個例子:

function Task(id){  
    this.id = id;  
}  
    
Task.prototype.status = "STOPPED";  
Task.prototype.execute = function(args){  
    return "execute task_"+this.id+"["+this.status+"]:"+args;  
}  
    
var task1 = new Task(1);  
var task2 = new Task(2);  
    
task1.status = "ACTIVE";  
task2.status = "STARTING";  
    
print(task1.execute("task1"));  
print(task2.execute("task2"));
結果:
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指針的指向)繼承基類,方法用遍歷基類原型。

function A()
{
    this.abc=12;
}

A.prototype.show=function ()
{
    alert(this.abc);
};

//繼承A
function B()
{
    //繼承屬性;this->new B()
    A.call(this);   //有參數可以傳參數A.call(this,name,age)
}

//繼承方法;B.prototype=A.prototype;
for(var i in A.prototype)
{
    B.prototype[i]=A.prototype[i];
}
//添加自己的方法
B.prototype.fn=function ()
{
    alert('abc');
};

var objB=new B();
var objA=new A();objB.show();

可以實現多繼承。

2.寄生組合繼承

主要是Desk.prototype = new Box(); Desk 繼承了Box,通過原型,形成鏈條。主要通過臨時中轉函數和寄生函數實現。

臨時中轉函數:基於已有的對象創建新對象,同時還不必因此創建自定義類型
寄生函數:目的是為了封裝創建對象的過程

//臨時中轉函數
function obj(o) { //o表示將要傳遞進入的一個對象
function F() {}   //F構造是一個臨時新建的對象,用來存儲傳遞過來的對象
F.prototype = o;  //將o對象實例賦值給F構造的原型對象
return new F();   //最后返回這個得到傳遞過來對象的對象實例
}
//寄生函數
function create(box, desk) {
var f = obj(box.prototype);
f.constructor = desk;  //調整原型構造指針
desk.prototype = f;
}
function Box(name) {
this.name = name;
this.arr = ['apple','pear','orange'];
}
Box.prototype.run = function () {
return this.name;
};
function Desk(name, age) {
Box.call(this, name);
this.age = age;
}
//通過寄生組合繼承實現繼承
create(Box, Desk); //這句話用來替代Desk.prototype = new Box();
var desk = new Desk('Lee',100);
desk.arr.push('peach');
alert(desk.arr);
alert(desk.run());

臨時中轉函數和寄生函數主要做的工作流程:

臨時中轉函數:返回的是基類的實例對象函數
寄生函數:將返回的基類的實例對象函數的constructor指向派生類,派生類的prototype指向基類的實例對象函數(是一個函數原型),從而實現繼承。

-------------------------------------------------------------------------------------------------------------------------------------

轉載需注明轉載字樣,標注原作者和原博文地址。

更多閱讀:

http://www.108js.com/article/article1/10201.html?id=1092

https://msdn.microsoft.com/zh-cn/library/bb397568.aspx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM