JavaScript 精粹 基礎 進階(8)OOP面向對象編程(上)
原文連接 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:Foo
constructor屬性會指向它本身Foo。 - 另外一個屬性是
__proto__
,__proto__
是Foo.prototype
的原型,那麽他的原型會指向Object.prototype
也就是說一般的對象比如用花括號括起來的對象字面量,他也會有__proto__
他會指向Object.prototype
因此Object.prototype
上面的一些方法比如說toString
,valueOf
才會被每一個一般的對象所使用,
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
上面有hi
和learn
方法,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
。
也就是因為這樣所以說隨便一個對象才會有hasOwnProperty
,valueOf
,toString
等一些公共的函數,這些函數都是從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.prototype
有walk
所以最終調用結果是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這個時候他的原型就是undefined
,obj2.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.prototype
。new 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面向對象編程(上)