1. 程式人生 > >JavaScript 精粹 基礎 進階(8)OOP面向對象編程(上)

JavaScript 精粹 基礎 進階(8)OOP面向對象編程(上)

table als 現在 代碼 eat .class block http poi

轉載請註明出處

原文連接 http://blog.huanghanlian.com/article/5b698f14b8ea642ea9213f50

面向對象編程,oop並不是針對與javascript,很多語言都實現了oop這樣一個編程發法論,比如說java,c++,都是實現了oop的語言。

概念與繼承

概念

面向對象程序設計(Object-oriented programming OOP)是一種程序設計範型,同時也是一種程序開發的方法。對象指的是類的實例,它將對象作為程序的基本單元,將程序和數據封裝其中,以提高軟件的重用性,靈活性和擴展性。 來自於 ----維基百科

OOP重點的一些特性:

  • 繼承

  • 封裝

  • 多態

  • 抽象

基於原型的繼承

function Foo() {
    this.y = 2;
};
Foo.prototype.x = 1;
console.log(Foo.prototype); //object
var obj1 = new Foo();
console.log(obj1.y); //2
console.log(obj1.x); //1

函數聲明創建Foo()函數,這個函數就會有一個內置的Foo.prototype,並且這個屬性是對象,並且是預設的。

function Foo() {
    this.y = 2;
};

console.log(Foo.prototype); //object

技術分享圖片

function Foo() {
    this.y = 2;
};
Foo.prototype.x = 1;
console.log(Foo.prototype); //object

技術分享圖片

然後我們把Foo.prototype對象增加一個屬性x並且賦值為1。

function Foo() {
    this.y = 2;
};
Foo.prototype.x = 1;
console.log(Foo.prototype); //object
var obj1 = new Foo();
console.log(obj1.y); //2
console.log(obj1.x); //1 

然後使用new操作符new Foo();

來創建一個Foo();的實例,叫obj1,

當時用new去調用函數的時候,那麽構造器也就是說這樣一個函數就會作為一個構造器來使用,並且this會指向一個對象,而對象的原型會指向構造器的Foo.prototype屬性。obj1實際上會成為Foo構造器中的this,最後會作為返回值,並且在構造器裏面調用的時候會把y賦值為2,並且obj1的原型,也就是他的proto會指向Foo.prototype內置屬性,最後可以看到obj.y會返回2,obj.x會返回1,y是這個對象上的直接量,而x是原型鏈上的,也就是Foo.prototype的

    function Foo() {
        this.y = 2;
    };
    Foo.prototype.x = 1;
    console.log(Foo.prototype);
    var obj1 = new Foo();

    console.log(obj1);

技術分享圖片

prototype屬性與原型

    function Foo() {};
    console.log(Foo.prototype);
    Foo.prototype.x=1;
    var obj=new Foo();

技術分享圖片

使用函數聲明去中創建一個函數的時候,這個函數就會有一個prototype屬性,並且他默認會有兩個屬性。

  • 一個是constructor:Fooconstructor屬性會指向它本身Foo。

  • 另外一個屬性是__proto____proto__Foo.prototype的原型,那麽他的原型會指向Object.prototype也就是說一般的對象比如用花括號括起來的對象字面量,他也會有__proto__他會指向Object.prototype因此Object.prototype上面的一些方法比如說toStringvalueOf才會被每一個一般的對象所使用,

x:1這個是我通過賦值語句增加的。

這句是Foo.prototype的結構

也就是說,這裏面有一個Foo函數,這個Foo函數呢會有一個prototype的對象屬性,他的作用呢就是在當使用new Foo()去構造Foo的實例的時候,那麽構造器的prototype的屬性,會用作new出來的這些對象的原型。

所以要搞清楚prototype和原型是兩回事。

prototype是函數對象上的預設的對象屬性,而原型呢是我們對象上的一個原型,原型通常都是他的構造器的prototype屬性。

實現class繼承另外一個class

function Person(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.legs_num = 2;
Person.prototype.arms_num = 2;
Person.prototype.walk = function() {
    console.log(this.name + "is walking...");
};

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new,and form" + this.className + ".");
};

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

console.log(Student.prototype);

var t = new Student("黃繼鵬", 23, "class2");
t.hi();
console.log(t.legs_num);
t.walk();
t.learn(‘math‘)

這裏有一個函數Person,人的意思意思是說只要人類的class。 在這個構造函數裏面,通過this.name = name;this.age = age;去做一個賦值,如何Person作為函數直接去調用的話,那麽這裏的this會指向全局對象,在瀏覽器裏就會指向window,使用new去調用Person函數的時候,this會指向一個原型為Person.prototype的一個空對象,然後通過this.name去給這個空對象賦值,最後這裏沒有寫返回值,使用new會this會作為返回值。

通過Person.prototype.hi來創建所有Person實例共享的方法。

再創建Student函數,學生這樣一個class,那麽學生是也是人,他是可以繼承人的,每一個學生也是人,並且學生會有他的班級名稱或者一些其他的功能方法,

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};

創建Student函數,這裏多傳了一個className參數,在Student函數也算是子類裏先調用下父類,Person.call(this, name, age);然後把this作為Person裏面的this再把name和age傳進去,註意這裏的this在new被實例的時候會是這個實例的返回值也就是直接量,

this.className = className;並且把Student的實例做好賦值,

把Student.prototype能繼承Person.prototype的一些方法

Student.prototype = Object.create(Person.prototype);

使用這樣一個方法去拿到Person.prototype對象作為原型的值,這樣Student.prototype原型就會有Person.prototype的值了。

如果去掉Object.create()的話。人有一些方法,但是學生也有自己的一些方法,Student.prototype = Person.prototype;Person.prototype;賦值給Student.prototype的時候,當我想增加學生自己的方法時,比如說

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

這樣的話由於他們指向的是同一個對象,給Student.prototype增加對象的時候同時也給Person.prototype;增加了同樣的屬性,這不是我們想要的。

所以說通過Student.prototype = Object.create(Person.prototype);創建了一個空的對象,而這個空對象的原型指向了Person.prototype

這樣的話我們既可以在訪問Student.prototype的時候,可以向上查找Person.prototype同時可以在不影響Person.prototype的前提下創建一些自己的Student.prototype上的方法。

Student.prototype.constructor = Student;

每一個prototype屬性對象都會有一個constructor屬性,他的值是指向函數本身,實際上這裏面沒有太大的用處,因為我們可以任意的去修改,但是為了保證一致性,我們把這個改成Student.prototype.constructor = Student;

面向對象例子測試

function Person(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.legs_num = 2;
Person.prototype.arms_num = 2;
Person.prototype.walk = function() {
    console.log(this.name + "is walking...");
};

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new,and form" + this.className + ".");
};

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

var t = new Student("黃繼鵬", 23, "class2");
var poi=new Person("李漢",22);
console.log(poi);
console.log(t);

技術分享圖片

再談原型鏈

再談原型鏈

function Person(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.legs_num = 2;
Person.prototype.arms_num = 2;
Person.prototype.walk = function() {
    console.log(this.name + "is walking...");
};

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new,and form" + this.className + ".");
};

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

console.log(Student.prototype);

var peng = new Student("黃繼鵬", 23, "class2");
peng.hi();
console.log(peng.legs_num);
peng.walk();
peng.learn(‘math‘)

技術分享圖片

這個圖裏面說明了上面代碼例子的示意圖

通過var peng = new Student("黃繼鵬", 23, "class2");來創建了一個實例peng

peng的實例他的原型我們用__proto__表示,就會指向構造器Student.prototype的屬性。

Student.prototype上面有hilearn方法,Student.prototype是通過Student.prototype = Object.create(Person.prototype);構造的,所以說Student.prototype是一個對象,並且的原型指向Person.prototype

Person.prototype。也給她設置了很多屬性,hi...等。

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.hi其實是內置的普通對象,內置對象他本身也會有他的原型,他的原型就是Object.prototype

也就是因為這樣所以說隨便一個對象才會有hasOwnPropertyvalueOftoString等一些公共的函數,這些函數都是從Object.prototype而來的。

當我們去調用

peng.hi();方法的時候,首先看這個對象上本身有沒有hi方法,在本身沒有所以會像上查找,差遭到peng原型也就是Student.prototype有這樣一個函數方法,所以最終調用的是Student.prototype上面的hi方法。

如果Student.prototype不去寫hi方法的時候peng.hi();會去調用Person.prototype.hi這樣一個方法,

當我們調用peng.walk();的時候,先找peng上發現沒有,然後Student.prototype上面,也沒有,Person.prototypewalk所以最終調用結果是Person.prototype上面的walk方法。

那麽我想去調用peng.toString的時候也是一層一層向上查找。找到Object.prototype那麽最後到null也就是最根源沒有了。

關於一切的一般對象都會指向Object.prototype做一個實際的實驗

var obj={x:1,y:2}

比如用obj等於一個花括號空的字面量,給他屬性。

那麽我們知道obj就是一個普通的對象,obj.x就為1

可以通過obj.__proto__這樣的機制可以讓你去訪問對象的原型

技術分享圖片

除了obj.__proto__以外,在es5裏面提供了一個方法能夠返回一個對象的原型,就是Object.getPrototypeOf(obj)這樣一個方法,可以返回對象原型,

技術分享圖片

通過三個等號來判斷Object.getPrototypeOf(obj)是不是等於Object.prototype

技術分享圖片

返回true

也就是說我們隨便一個對象仔面了也好或者是函數函數內置的prototype屬性然後去判斷 他的原型可以看到也是Object.prototype

技術分享圖片

也正因為如此所以說我們才可以調用obj.toString()obj.valueOf()
實際上這些方法都是取自Object.prototype上的,

技術分享圖片

並不是所有對象最終原型鏈上最終都有Object.prototype

比如說我們創建obj2對象,然後用var obj2=Object.create(null)obj2.create(null)的作用是創建空對象,並且這個對象的原型指向這樣一個參數,但是這裏參數是null,obj2這個時候他的原型就是undefinedobj2.toString就是undefined那麽通過Object.create(null)創建出來的對象,就沒有Object.prototype的一些方法。所以說並不是所有的對象都繼承Object.prototype

只是一般我們對象字面量或者是函數的prototype預制的一般的對象上都有Object.prototype

技術分享圖片

並不是所有的函數對象都有prototype這樣一個預制屬性的

function abc() {};
console.log(abc.prototype);
var hh = abc.bind(null);
console.log(hh.prototype);

技術分享圖片

使用es5友誼和bind函數,bind函數是用來修改函數在運行時的this的,bind函數返回的也是一個函數,但是bind函數就沒有prototype預設屬性。

prototype屬性

javascript中的prototype原型,不像java的class,是一旦寫好了以後不太容易去動態改變的,但是javascript中原型實際上也是普通的對象,那麽意味著在程序運行的階段我們也可以動態的給prototype添加或者刪除一些屬性,

function Person(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.legs_num = 2;
Person.prototype.arms_num = 2;
Person.prototype.walk = function() {
    console.log(this.name + "is walking...");
};

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new,and form" + this.className + ".");
};

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

console.log(Student.prototype);

var peng = new Student("黃繼鵬", 23, "class2");
peng.hi();
console.log(peng.legs_num);
peng.walk();
peng.learn(‘math‘)

Student.prototype.x=101;
console.log(peng.x);//101

Student.prototype={y:2};
console.log(peng.y);//undefined
console.log(peng.x);//101

var nunnly=new Student("nunnly", 23, "class3");

console.log(nunnly.y);//2
console.log(nunnly.x);//undefined

技術分享圖片

Student.prototype.x=101;
console.log(peng.x);//101

Student.prototype={y:2};
console.log(peng.y);//undefined
console.log(peng.x);//101

var nunnly=new Student("nunnly", 23, "class3");

console.log(nunnly.y);//2
console.log(nunnly.x);//undefined

比如說這裏的Student.prototype同過Student.prototype.x=101;huang的原型動態的添加一個屬性x那麽我們發現所有的實例都會受到影響,現在去調用console.log(peng.x);發現他賦值為101了,

直接修改Student.prototype={y:2};構造器的屬性,把他賦值為一個新的對象,y:2

有趣的現象

console.log(peng.y);//undefined
console.log(peng.x);//101

當我們去修改Student.prototype的時候並不能修改已經實例化的對象,也就是說已經實例化的peng他的原型已經指向當時的Student.prototype如果你修改了Student.prototype的話,並不會影響已經創建的實例,之所以修改的x沒有問題,是因為我們修改的是peng原型的那個對象,

但是再去用new重新實例化對象,那麽會發現x不見了,並且y是新的y值。

內置構造器的prototype

Object.prototype.x = 1;
var obj = {
    y: 3
};
console.log(obj.x); //1
for (var key in obj) {
    console.log(key + "=" + obj[key]); //y=3 x=1
}

比如說我們想讓所有的對象他的原型鏈上都會有x屬性會發現所有對象都會有x屬性,這樣的設置會在for...in的時候會被枚舉出來,那麽怎麽解決這個問題呢

Object.defineProperty(Object.prototype, ‘x‘, {writable: true,value: 1});
var obj = {
    y: 3
};
console.log(obj.x); //1
for (var key in obj) {
    console.log(key + "=" + obj[key]); //y=3
}
  • value:屬性的值給屬性賦值
  • writable:如果為false,屬性的值就不能被重寫。
  • get: 一旦目標屬性被訪問就會調回此方法,並將此方法的運算結果返回用戶。
  • set:一旦目標屬性被賦值,就會調回此方法。
  • configurable:如果為false,則任何嘗試刪除目標屬性或修改屬性以下特性(writable, configurable, enumerable)的行為將被無效化。
  • enumerable:是否能在for...in循環中遍歷出來或在Object.keys中列舉出來。

創建對象-new/原型鏈

function foo(){}    //定義函數對象 foo
foo.prototype.z = 3;      //函數對象默認帶foo.prototype對象屬性  這個對象會作為new實例的對象原型  對象添加z屬性=3

var obj =new foo();    //用構造器方式構造新的對象
obj.y = 2;    //通過賦值添加2個屬性給obj
obj.x = 1;   //通過new去構造這樣一個對象他的主要特點是,他的原型會指向構造器的foo.prototype屬性

//一般foo.prototype對象他的原型又會指向Object.prototype
//Object.prototype他也會有他的原型最後指向null整個原型鏈的末端

obj.x; // 1  //訪問obj.x發現對象上有x返回1
obj.y; // 2  //訪問obj.y發現對象上有x返回2
obj.z; // 3  //obj上沒有z並不會停止查找,會去查找他的原型foo.prototype.z返回3
typeof obj.toString; // ‘function‘  這是一個函數,toString是Object.prototype上面的每個對象都有
‘z‘ in obj; // true     obj.z是從foo.prototype繼承而來的,所以說obj裏面有z
obj.hasOwnProperty(‘z‘); // false   表示z並不是obj直接對象上的,而是對象原型鏈上的。

技術分享圖片

instanceof

instanceof

instanceof數據類型判斷方法

console.log([1, 2] instanceof Array); //true
console.log(new Object() instanceof Array); //false

左邊要求是一個對象instanceof右邊要求是一個函數或者說構造器
他會判斷右邊的構造器的 prototype的屬性是否出現在左邊這個對象的原型鏈上。

console.log([1, 2] instanceof Array); //true

[1,2]這裏是數組字面量,數組的字面量他也有他的原型,他的原型就是Array.prototype所以返回true

console.log(new Object() instanceof Array); //false

new Object()new一個空對象空對象的原型會指向Object.prototypenew Object()的原型鏈不是Array.prototype所以返回false

需要註意的是

console.log([1, 2] instanceof Object); //true

因為數組他的原型是Array.prototype,而Array.prototype的原型就是Object.prototype,所以返回true

所以說instanceof我們可以判斷某一個對象他的原型鏈上是否有右邊這個函數構造器的prototype對象屬性。

function per() {};

function sor() {};
sor.prototype = new per();
sor.prototype.constructor = sor;

var peng = new sor();
var han = new per();
console.log(peng instanceof sor); //true
console.log(peng instanceof per); //true
console.log(han instanceof sor); //false
console.log(han instanceof per); //true

實現繼承的方式

實現繼承的方式

if (!Object.create) {
    Object.create = function(proto) {
        function F() {};
        F.prototype = proto;
        return new F();
    };
}

function Person(name, age) {
    this.name = name;
    this.age = age;
};

Person.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new");
};

Person.prototype.legs_num = 2;
Person.prototype.arms_num = 2;
Person.prototype.walk = function() {
    console.log(this.name + "is walking...");
};

function Student(name, age, className) {
    Person.call(this, name, age);
    this.className = className;
};
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.hi = function() {
    console.log(‘Hi my name is‘ + this.name + ",I‘m" + this.age + "years old new,and form" + this.className + ".");
};

Student.prototype.learn = function(subject) {
    console.log(this.name + ‘is learing‘ + subject + ‘at‘ + this.className + ‘.‘);
};

console.log(Student.prototype);

var t = new Student("黃繼鵬", 23, "class2");
t.hi();
console.log(t.legs_num);
t.walk();
t.learn(‘math‘);

Object.create()也有他的問題,他是es5之後才支持的,但是沒有關系在es5之前我們可以寫一個模擬的方法。

if (!Object.create) {
    Object.create = function(proto) {
        function F() {};
        F.prototype = proto;
        return new F();
    };
}

這裏面我們可以判斷下有沒有Object.create如果沒有的話,我們可以把他賦值為一個函數,這裏會傳進來一個參數,寫一個臨時的空函數,把空函數的prototype屬性賦值給想要作為原型的對象,然後返回new F(),會創建一個對象,這個對象的原型指向構造器的prototype,利用這樣的規則返回空對象,並且對象原型指向參數也就是要繼承的原型。

JavaScript 精粹 基礎 進階(8)OOP面向對象編程(上)